1 1.3 christos /* $NetBSD: v_scroll.c,v 1.3 2014/01/26 21:43:45 christos Exp $ */ 2 1.1 christos /*- 3 1.1 christos * Copyright (c) 1992, 1993, 1994 4 1.1 christos * The Regents of the University of California. All rights reserved. 5 1.1 christos * Copyright (c) 1992, 1993, 1994, 1995, 1996 6 1.1 christos * Keith Bostic. All rights reserved. 7 1.1 christos * 8 1.1 christos * See the LICENSE file for redistribution information. 9 1.1 christos */ 10 1.1 christos 11 1.1 christos #include "config.h" 12 1.1 christos 13 1.3 christos #include <sys/cdefs.h> 14 1.3 christos #if 0 15 1.1 christos #ifndef lint 16 1.1 christos static const char sccsid[] = "Id: v_scroll.c,v 10.12 2001/06/25 15:19:34 skimo Exp (Berkeley) Date: 2001/06/25 15:19:34 "; 17 1.1 christos #endif /* not lint */ 18 1.3 christos #else 19 1.3 christos __RCSID("$NetBSD: v_scroll.c,v 1.3 2014/01/26 21:43:45 christos Exp $"); 20 1.3 christos #endif 21 1.1 christos 22 1.1 christos #include <sys/types.h> 23 1.1 christos #include <sys/queue.h> 24 1.1 christos #include <sys/time.h> 25 1.1 christos 26 1.1 christos #include <bitstring.h> 27 1.1 christos #include <errno.h> 28 1.1 christos #include <limits.h> 29 1.1 christos #include <stdio.h> 30 1.1 christos 31 1.1 christos #include "../common/common.h" 32 1.1 christos #include "vi.h" 33 1.1 christos 34 1.1 christos static void goto_adjust __P((VICMD *)); 35 1.1 christos 36 1.1 christos /* 37 1.1 christos * The historic vi had a problem in that all movements were by physical 38 1.1 christos * lines, not by logical, or screen lines. Arguments can be made that this 39 1.1 christos * is the right thing to do. For example, single line movements, such as 40 1.1 christos * 'j' or 'k', should probably work on physical lines. Commands like "dj", 41 1.1 christos * or "j.", where '.' is a change command, make more sense for physical lines 42 1.1 christos * than they do for logical lines. 43 1.1 christos * 44 1.1 christos * These arguments, however, don't apply to scrolling commands like ^D and 45 1.1 christos * ^F -- if the window is fairly small, using physical lines can result in 46 1.1 christos * a half-page scroll repainting the entire screen, which is not what the 47 1.1 christos * user wanted. Second, if the line is larger than the screen, using physical 48 1.1 christos * lines can make it impossible to display parts of the line -- there aren't 49 1.1 christos * any commands that don't display the beginning of the line in historic vi, 50 1.1 christos * and if both the beginning and end of the line can't be on the screen at 51 1.1 christos * the same time, you lose. This is even worse in the case of the H, L, and 52 1.1 christos * M commands -- for large lines, they may all refer to the same line and 53 1.1 christos * will result in no movement at all. 54 1.1 christos * 55 1.1 christos * Another issue is that page and half-page scrolling commands historically 56 1.1 christos * moved to the first non-blank character in the new line. If the line is 57 1.1 christos * approximately the same size as the screen, this loses because the cursor 58 1.1 christos * before and after a ^D, may refer to the same location on the screen. In 59 1.1 christos * this implementation, scrolling commands set the cursor to the first non- 60 1.1 christos * blank character if the line changes because of the scroll. Otherwise, 61 1.1 christos * the cursor is left alone. 62 1.1 christos * 63 1.1 christos * This implementation does the scrolling (^B, ^D, ^F, ^U, ^Y, ^E), and the 64 1.1 christos * cursor positioning commands (H, L, M) commands using logical lines, not 65 1.1 christos * physical. 66 1.1 christos */ 67 1.1 christos 68 1.1 christos /* 69 1.1 christos * v_lgoto -- [count]G 70 1.1 christos * Go to first non-blank character of the line count, the last line 71 1.1 christos * of the file by default. 72 1.1 christos * 73 1.1 christos * PUBLIC: int v_lgoto __P((SCR *, VICMD *)); 74 1.1 christos */ 75 1.1 christos int 76 1.1 christos v_lgoto(SCR *sp, VICMD *vp) 77 1.1 christos { 78 1.1 christos db_recno_t nlines; 79 1.1 christos 80 1.1 christos if (F_ISSET(vp, VC_C1SET)) { 81 1.1 christos if (!db_exist(sp, vp->count)) { 82 1.1 christos /* 83 1.1 christos * !!! 84 1.1 christos * Historically, 1G was legal in an empty file. 85 1.1 christos */ 86 1.1 christos if (vp->count == 1) { 87 1.1 christos if (db_last(sp, &nlines)) 88 1.1 christos return (1); 89 1.1 christos if (nlines == 0) 90 1.1 christos return (0); 91 1.1 christos } 92 1.1 christos v_eof(sp, &vp->m_start); 93 1.1 christos return (1); 94 1.1 christos } 95 1.1 christos vp->m_stop.lno = vp->count; 96 1.1 christos } else { 97 1.1 christos if (db_last(sp, &nlines)) 98 1.1 christos return (1); 99 1.1 christos vp->m_stop.lno = nlines ? nlines : 1; 100 1.1 christos } 101 1.1 christos goto_adjust(vp); 102 1.1 christos return (0); 103 1.1 christos } 104 1.1 christos 105 1.1 christos /* 106 1.1 christos * v_home -- [count]H 107 1.1 christos * Move to the first non-blank character of the logical line 108 1.1 christos * count - 1 from the top of the screen, 0 by default. 109 1.1 christos * 110 1.1 christos * PUBLIC: int v_home __P((SCR *, VICMD *)); 111 1.1 christos */ 112 1.1 christos int 113 1.1 christos v_home(SCR *sp, VICMD *vp) 114 1.1 christos { 115 1.1 christos if (vs_sm_position(sp, &vp->m_stop, 116 1.1 christos F_ISSET(vp, VC_C1SET) ? vp->count - 1 : 0, P_TOP)) 117 1.1 christos return (1); 118 1.1 christos goto_adjust(vp); 119 1.1 christos return (0); 120 1.1 christos } 121 1.1 christos 122 1.1 christos /* 123 1.1 christos * v_middle -- M 124 1.1 christos * Move to the first non-blank character of the logical line 125 1.1 christos * in the middle of the screen. 126 1.1 christos * 127 1.1 christos * PUBLIC: int v_middle __P((SCR *, VICMD *)); 128 1.1 christos */ 129 1.1 christos int 130 1.1 christos v_middle(SCR *sp, VICMD *vp) 131 1.1 christos { 132 1.1 christos /* 133 1.1 christos * Yielding to none in our quest for compatibility with every 134 1.1 christos * historical blemish of vi, no matter how strange it might be, 135 1.1 christos * we permit the user to enter a count and then ignore it. 136 1.1 christos */ 137 1.1 christos if (vs_sm_position(sp, &vp->m_stop, 0, P_MIDDLE)) 138 1.1 christos return (1); 139 1.1 christos goto_adjust(vp); 140 1.1 christos return (0); 141 1.1 christos } 142 1.1 christos 143 1.1 christos /* 144 1.1 christos * v_bottom -- [count]L 145 1.1 christos * Move to the first non-blank character of the logical line 146 1.1 christos * count - 1 from the bottom of the screen, 0 by default. 147 1.1 christos * 148 1.1 christos * PUBLIC: int v_bottom __P((SCR *, VICMD *)); 149 1.1 christos */ 150 1.1 christos int 151 1.1 christos v_bottom(SCR *sp, VICMD *vp) 152 1.1 christos { 153 1.1 christos if (vs_sm_position(sp, &vp->m_stop, 154 1.1 christos F_ISSET(vp, VC_C1SET) ? vp->count - 1 : 0, P_BOTTOM)) 155 1.1 christos return (1); 156 1.1 christos goto_adjust(vp); 157 1.1 christos return (0); 158 1.1 christos } 159 1.1 christos 160 1.1 christos static void 161 1.1 christos goto_adjust(VICMD *vp) 162 1.1 christos { 163 1.1 christos /* Guess that it's the end of the range. */ 164 1.1 christos vp->m_final = vp->m_stop; 165 1.1 christos 166 1.1 christos /* 167 1.1 christos * Non-motion commands move the cursor to the end of the range, and 168 1.1 christos * then to the NEXT nonblank of the line. Historic vi always moved 169 1.1 christos * to the first nonblank in the line; since the H, M, and L commands 170 1.1 christos * are logical motions in this implementation, we do the next nonblank 171 1.1 christos * so that it looks approximately the same to the user. To make this 172 1.1 christos * happen, the VM_RCM_SETNNB flag is set in the vcmd.c command table. 173 1.1 christos * 174 1.1 christos * If it's a motion, it's more complicated. The best possible solution 175 1.1 christos * is probably to display the first nonblank of the line the cursor 176 1.1 christos * will eventually rest on. This is tricky, particularly given that if 177 1.1 christos * the associated command is a delete, we don't yet know what line that 178 1.1 christos * will be. So, we clear the VM_RCM_SETNNB flag, and set the first 179 1.1 christos * nonblank flag (VM_RCM_SETFNB). Note, if the lines are sufficiently 180 1.1 christos * long, this can cause the cursor to warp out of the screen. It's too 181 1.1 christos * hard to fix. 182 1.1 christos * 183 1.1 christos * XXX 184 1.1 christos * The G command is always first nonblank, so it's okay to reset it. 185 1.1 christos */ 186 1.1 christos if (ISMOTION(vp)) { 187 1.1 christos F_CLR(vp, VM_RCM_MASK); 188 1.1 christos F_SET(vp, VM_RCM_SETFNB); 189 1.1 christos } else 190 1.1 christos return; 191 1.1 christos 192 1.1 christos /* 193 1.1 christos * If moving backward in the file, delete and yank move to the end 194 1.1 christos * of the range, unless the line didn't change, in which case yank 195 1.1 christos * doesn't move. If moving forward in the file, delete and yank 196 1.1 christos * stay at the start of the range. Ignore others. 197 1.1 christos */ 198 1.1 christos if (vp->m_stop.lno < vp->m_start.lno || 199 1.2 christos (vp->m_stop.lno == vp->m_start.lno && 200 1.2 christos vp->m_stop.cno < vp->m_start.cno)) { 201 1.1 christos if (ISCMD(vp->rkp, 'y') && vp->m_stop.lno == vp->m_start.lno) 202 1.1 christos vp->m_final = vp->m_start; 203 1.1 christos } else 204 1.1 christos vp->m_final = vp->m_start; 205 1.1 christos } 206 1.1 christos 207 1.1 christos /* 208 1.1 christos * v_up -- [count]^P, [count]k, [count]- 209 1.1 christos * Move up by lines. 210 1.1 christos * 211 1.1 christos * PUBLIC: int v_up __P((SCR *, VICMD *)); 212 1.1 christos */ 213 1.1 christos int 214 1.1 christos v_up(SCR *sp, VICMD *vp) 215 1.1 christos { 216 1.1 christos db_recno_t lno; 217 1.1 christos 218 1.1 christos lno = F_ISSET(vp, VC_C1SET) ? vp->count : 1; 219 1.1 christos if (vp->m_start.lno <= lno) { 220 1.1 christos v_sof(sp, &vp->m_start); 221 1.1 christos return (1); 222 1.1 christos } 223 1.1 christos vp->m_stop.lno = vp->m_start.lno - lno; 224 1.1 christos vp->m_final = vp->m_stop; 225 1.1 christos return (0); 226 1.1 christos } 227 1.1 christos 228 1.1 christos /* 229 1.1 christos * v_cr -- [count]^M 230 1.1 christos * In a script window, send the line to the shell. 231 1.1 christos * In a regular window, move down by lines. 232 1.1 christos * 233 1.1 christos * PUBLIC: int v_cr __P((SCR *, VICMD *)); 234 1.1 christos */ 235 1.1 christos int 236 1.1 christos v_cr(SCR *sp, VICMD *vp) 237 1.1 christos { 238 1.1 christos /* If it's a colon command-line edit window, it's an ex command. */ 239 1.1 christos if (F_ISSET(sp, SC_COMEDIT)) 240 1.1 christos return (v_ecl_exec(sp)); 241 1.1 christos 242 1.1 christos /* If it's a script window, exec the line. */ 243 1.1 christos if (F_ISSET(sp, SC_SCRIPT)) 244 1.1 christos return (sscr_exec(sp, vp->m_start.lno)); 245 1.1 christos 246 1.1 christos /* Otherwise, it's the same as v_down(). */ 247 1.1 christos return (v_down(sp, vp)); 248 1.1 christos } 249 1.1 christos 250 1.1 christos /* 251 1.1 christos * v_down -- [count]^J, [count]^N, [count]j, [count]^M, [count]+ 252 1.1 christos * Move down by lines. 253 1.1 christos * 254 1.1 christos * PUBLIC: int v_down __P((SCR *, VICMD *)); 255 1.1 christos */ 256 1.1 christos int 257 1.1 christos v_down(SCR *sp, VICMD *vp) 258 1.1 christos { 259 1.1 christos db_recno_t lno; 260 1.1 christos 261 1.1 christos lno = vp->m_start.lno + (F_ISSET(vp, VC_C1SET) ? vp->count : 1); 262 1.1 christos if (!db_exist(sp, lno)) { 263 1.1 christos v_eof(sp, &vp->m_start); 264 1.1 christos return (1); 265 1.1 christos } 266 1.1 christos vp->m_stop.lno = lno; 267 1.1 christos vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; 268 1.1 christos return (0); 269 1.1 christos } 270 1.1 christos 271 1.1 christos /* 272 1.1 christos * v_hpageup -- [count]^U 273 1.1 christos * Page up half screens. 274 1.1 christos * 275 1.1 christos * PUBLIC: int v_hpageup __P((SCR *, VICMD *)); 276 1.1 christos */ 277 1.1 christos int 278 1.1 christos v_hpageup(SCR *sp, VICMD *vp) 279 1.1 christos { 280 1.1 christos /* 281 1.1 christos * Half screens always succeed unless already at SOF. 282 1.1 christos * 283 1.1 christos * !!! 284 1.1 christos * Half screens set the scroll value, even if the command 285 1.1 christos * ultimately failed, in historic vi. Probably a don't care. 286 1.1 christos */ 287 1.1 christos if (F_ISSET(vp, VC_C1SET)) 288 1.1 christos sp->defscroll = vp->count; 289 1.1 christos if (vs_sm_scroll(sp, &vp->m_stop, sp->defscroll, CNTRL_U)) 290 1.1 christos return (1); 291 1.1 christos vp->m_final = vp->m_stop; 292 1.1 christos return (0); 293 1.1 christos } 294 1.1 christos 295 1.1 christos /* 296 1.1 christos * v_hpagedown -- [count]^D 297 1.1 christos * Page down half screens. 298 1.1 christos * 299 1.1 christos * PUBLIC: int v_hpagedown __P((SCR *, VICMD *)); 300 1.1 christos */ 301 1.1 christos int 302 1.1 christos v_hpagedown(SCR *sp, VICMD *vp) 303 1.1 christos { 304 1.1 christos /* 305 1.1 christos * Half screens always succeed unless already at EOF. 306 1.1 christos * 307 1.1 christos * !!! 308 1.1 christos * Half screens set the scroll value, even if the command 309 1.1 christos * ultimately failed, in historic vi. Probably a don't care. 310 1.1 christos */ 311 1.1 christos if (F_ISSET(vp, VC_C1SET)) 312 1.1 christos sp->defscroll = vp->count; 313 1.1 christos if (vs_sm_scroll(sp, &vp->m_stop, sp->defscroll, CNTRL_D)) 314 1.1 christos return (1); 315 1.1 christos vp->m_final = vp->m_stop; 316 1.1 christos return (0); 317 1.1 christos } 318 1.1 christos 319 1.1 christos /* 320 1.1 christos * v_pagedown -- [count]^F 321 1.1 christos * Page down full screens. 322 1.1 christos * !!! 323 1.1 christos * Historic vi did not move to the EOF if the screen couldn't move, i.e. 324 1.1 christos * if EOF was already displayed on the screen. This implementation does 325 1.1 christos * move to EOF in that case, making ^F more like the the historic ^D. 326 1.1 christos * 327 1.1 christos * PUBLIC: int v_pagedown __P((SCR *, VICMD *)); 328 1.1 christos */ 329 1.1 christos int 330 1.1 christos v_pagedown(SCR *sp, VICMD *vp) 331 1.1 christos { 332 1.1 christos db_recno_t offset; 333 1.1 christos 334 1.1 christos /* 335 1.1 christos * !!! 336 1.1 christos * The calculation in IEEE Std 1003.2-1992 (POSIX) is: 337 1.1 christos * 338 1.1 christos * top_line = top_line + count * (window - 2); 339 1.1 christos * 340 1.1 christos * which was historically wrong. The correct one is: 341 1.1 christos * 342 1.1 christos * top_line = top_line + count * window - 2; 343 1.1 christos * 344 1.1 christos * i.e. the two line "overlap" was only subtracted once. Which 345 1.1 christos * makes no sense, but then again, an overlap makes no sense for 346 1.1 christos * any screen but the "next" one anyway. We do it the historical 347 1.1 christos * way as there's no good reason to change it. 348 1.1 christos * 349 1.1 christos * If the screen has been split horizontally, use the smaller of 350 1.1 christos * the current window size and the window option value. 351 1.1 christos * 352 1.1 christos * It possible for this calculation to be less than 1; move at 353 1.1 christos * least one line. 354 1.1 christos */ 355 1.1 christos offset = (F_ISSET(vp, VC_C1SET) ? vp->count : 1) * (IS_HSPLIT(sp) ? 356 1.1 christos MIN(sp->t_maxrows, O_VAL(sp, O_WINDOW)) : O_VAL(sp, O_WINDOW)); 357 1.1 christos offset = offset <= 2 ? 1 : offset - 2; 358 1.1 christos if (vs_sm_scroll(sp, &vp->m_stop, offset, CNTRL_F)) 359 1.1 christos return (1); 360 1.1 christos vp->m_final = vp->m_stop; 361 1.1 christos return (0); 362 1.1 christos } 363 1.1 christos 364 1.1 christos /* 365 1.1 christos * v_pageup -- [count]^B 366 1.1 christos * Page up full screens. 367 1.1 christos * 368 1.1 christos * !!! 369 1.1 christos * Historic vi did not move to the SOF if the screen couldn't move, i.e. 370 1.1 christos * if SOF was already displayed on the screen. This implementation does 371 1.1 christos * move to SOF in that case, making ^B more like the the historic ^U. 372 1.1 christos * 373 1.1 christos * PUBLIC: int v_pageup __P((SCR *, VICMD *)); 374 1.1 christos */ 375 1.1 christos int 376 1.1 christos v_pageup(SCR *sp, VICMD *vp) 377 1.1 christos { 378 1.1 christos db_recno_t offset; 379 1.1 christos 380 1.1 christos /* 381 1.1 christos * !!! 382 1.1 christos * The calculation in IEEE Std 1003.2-1992 (POSIX) is: 383 1.1 christos * 384 1.1 christos * top_line = top_line - count * (window - 2); 385 1.1 christos * 386 1.1 christos * which was historically wrong. The correct one is: 387 1.1 christos * 388 1.1 christos * top_line = (top_line - count * window) + 2; 389 1.1 christos * 390 1.1 christos * A simpler expression is that, as with ^F, we scroll exactly: 391 1.1 christos * 392 1.1 christos * count * window - 2 393 1.1 christos * 394 1.1 christos * lines. 395 1.1 christos * 396 1.1 christos * Bizarre. As with ^F, an overlap makes no sense for anything 397 1.1 christos * but the first screen. We do it the historical way as there's 398 1.1 christos * no good reason to change it. 399 1.1 christos * 400 1.1 christos * If the screen has been split horizontally, use the smaller of 401 1.1 christos * the current window size and the window option value. 402 1.1 christos * 403 1.1 christos * It possible for this calculation to be less than 1; move at 404 1.1 christos * least one line. 405 1.1 christos */ 406 1.1 christos offset = (F_ISSET(vp, VC_C1SET) ? vp->count : 1) * (IS_HSPLIT(sp) ? 407 1.1 christos MIN(sp->t_maxrows, O_VAL(sp, O_WINDOW)) : O_VAL(sp, O_WINDOW)); 408 1.1 christos offset = offset <= 2 ? 1 : offset - 2; 409 1.1 christos if (vs_sm_scroll(sp, &vp->m_stop, offset, CNTRL_B)) 410 1.1 christos return (1); 411 1.1 christos vp->m_final = vp->m_stop; 412 1.1 christos return (0); 413 1.1 christos } 414 1.1 christos 415 1.1 christos /* 416 1.1 christos * v_lineup -- [count]^Y 417 1.1 christos * Page up by lines. 418 1.1 christos * 419 1.1 christos * PUBLIC: int v_lineup __P((SCR *, VICMD *)); 420 1.1 christos */ 421 1.1 christos int 422 1.1 christos v_lineup(SCR *sp, VICMD *vp) 423 1.1 christos { 424 1.1 christos /* 425 1.1 christos * The cursor moves down, staying with its original line, unless it 426 1.1 christos * reaches the bottom of the screen. 427 1.1 christos */ 428 1.1 christos if (vs_sm_scroll(sp, 429 1.1 christos &vp->m_stop, F_ISSET(vp, VC_C1SET) ? vp->count : 1, CNTRL_Y)) 430 1.1 christos return (1); 431 1.1 christos vp->m_final = vp->m_stop; 432 1.1 christos return (0); 433 1.1 christos } 434 1.1 christos 435 1.1 christos /* 436 1.1 christos * v_linedown -- [count]^E 437 1.1 christos * Page down by lines. 438 1.1 christos * 439 1.1 christos * PUBLIC: int v_linedown __P((SCR *, VICMD *)); 440 1.1 christos */ 441 1.1 christos int 442 1.1 christos v_linedown(SCR *sp, VICMD *vp) 443 1.1 christos { 444 1.1 christos /* 445 1.1 christos * The cursor moves up, staying with its original line, unless it 446 1.1 christos * reaches the top of the screen. 447 1.1 christos */ 448 1.1 christos if (vs_sm_scroll(sp, 449 1.1 christos &vp->m_stop, F_ISSET(vp, VC_C1SET) ? vp->count : 1, CNTRL_E)) 450 1.1 christos return (1); 451 1.1 christos vp->m_final = vp->m_stop; 452 1.1 christos return (0); 453 1.1 christos } 454