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