1 2 3 /* This version of microEmacs is based on the public domain C 4 * version written by Dave G. Conroy. 5 * The D programming language version is written by Walter Bright. 6 * http://www.digitalmars.com/d/ 7 * This program is in the public domain. 8 */ 9 10 11 /* 12 * The functions in this file handle redisplay. There are two halves, the 13 * ones that update the virtual display screen, and the ones that make the 14 * physical display screen the same as the virtual display screen. These 15 * functions use hints that are left in the windows by the commands. 16 * 17 * REVISION HISTORY: 18 * 19 * ? Steve Wilhite, 1-Dec-85 20 * - massive cleanup on code. 21 */ 22 23 module display; 24 25 import core.memory; 26 import core.stdc.stdarg; 27 import core.stdc.stdio; 28 import core.stdc.stdlib; 29 import core.stdc..string; 30 31 import std.format; 32 import std.path; 33 34 import ed; 35 import line; 36 import window; 37 import main; 38 import buffer; 39 import disprev; 40 import search; 41 import syntaxd; 42 import syntaxc; 43 import syntaxcpp; 44 import terminal; 45 import url; 46 import utf; 47 48 int max(int a, int b) { return a > b ? a : b; } 49 50 enum SHOWCONTROL = 1; 51 52 char column_mode = FALSE; 53 54 // debug=WFDEBUG /* Window flag debug. */ 55 attchar_t[] blnk_ln; 56 57 version (Windows) 58 alias ushort vchar; 59 else 60 alias char vchar; 61 62 ubyte[] vrowflags; 63 enum VFCHG = 0x0001; /* Changed. */ 64 65 int display_recalc() 66 { 67 foreach (wp; windows) 68 wp.w_flag |= WFHARD | WFMODE; 69 return TRUE; 70 } 71 72 version (Windows) 73 { 74 int display_eol_bg(bool f, int n) 75 { 76 config.eolattr += 0x10; 77 return display_recalc(); 78 } 79 80 int display_norm_bg(bool f, int n) 81 { 82 config.normattr += 0x10; 83 return display_recalc(); 84 } 85 86 int display_norm_fg(bool f, int n) 87 { 88 config.normattr = (config.normattr & 0xF0) + ((config.normattr + 1) & 0xF); 89 return display_recalc(); 90 } 91 92 int display_mode_bg(bool f, int n) 93 { 94 config.modeattr += 0x10; 95 return display_recalc(); 96 } 97 98 int display_mode_fg(bool f, int n) 99 { 100 config.modeattr = (config.modeattr & 0xF0) + ((config.modeattr + 1) & 0xF); 101 return display_recalc(); 102 } 103 104 int display_mark_bg(bool f, int n) 105 { 106 config.markattr += 0x10; 107 return display_recalc(); 108 } 109 110 int display_mark_fg(bool f, int n) 111 { 112 config.markattr = (config.markattr & 0xF0) + ((config.markattr + 1) & 0xF); 113 return display_recalc(); 114 } 115 } 116 else 117 { 118 int display_eol_bg(bool f, int n) 119 { 120 return FALSE; 121 } 122 123 int display_norm_bg(bool f, int n) 124 { 125 return FALSE; 126 } 127 128 int display_norm_fg(bool f, int n) 129 { 130 return FALSE; 131 } 132 133 int display_mode_bg(bool f, int n) 134 { 135 return FALSE; 136 } 137 138 int display_mode_fg(bool f, int n) 139 { 140 return FALSE; 141 } 142 143 int display_mark_bg(bool f, int n) 144 { 145 return FALSE; 146 } 147 148 int display_mark_fg(bool f, int n) 149 { 150 return FALSE; 151 } 152 } 153 154 int sgarbf = TRUE; /* TRUE if screen is garbage */ 155 int mpresf = FALSE; /* TRUE if message in last line */ 156 int vtrow = 0; /* Row location of SW cursor */ 157 int vtcol = 0; /* Column location of SW cursor */ 158 int ttrow = HUGE; /* Row location of HW cursor */ 159 int ttcol = HUGE; /* Column location of HW cursor */ 160 attr_t attr; /* Attribute for chars to vtputc() */ 161 int hardtabsize = 8; // hardware tab size 162 163 attchar_t[][] vscreen; /* Virtual screen. */ 164 attchar_t[][] pscreen; /* Physical screen. */ 165 166 /* 167 * Initialize the data structures used by the display code. The edge vectors 168 * used to access the screens are set up. The operating system's terminal I/O 169 * channel is set up. All the other things get initialized at compile time. 170 * The original window has "WFCHG" set, so that it will get completely 171 * redrawn on the first call to "update". 172 */ 173 void vtinit() 174 { 175 term.t_open(); 176 vscreen = new attchar_t[][term.t_nrow]; 177 pscreen = new attchar_t[][term.t_nrow]; 178 vrowflags = new ubyte[term.t_nrow]; 179 180 version (Posix) 181 { 182 blnk_ln = new attchar_t[term.t_ncol]; 183 } 184 185 foreach (i; 0 .. term.t_nrow) 186 { 187 vscreen[i] = new attchar_t[term.t_ncol]; 188 pscreen[i] = new attchar_t[term.t_ncol]; 189 } 190 } 191 192 /* 193 * Clean up the virtual terminal system, in anticipation for a return to the 194 * operating system. Move down to the last line and clear it out (the next 195 * system prompt will be written in the line). Shut down the channel to the 196 * terminal. 197 */ 198 void vttidy() 199 { 200 foreach (i; 0 .. term.t_nrow) 201 { 202 //delete vscreen[i]; 203 //delete pscreen[i]; 204 core.memory.GC.free(vscreen[i].ptr); 205 core.memory.GC.free(pscreen[i].ptr); 206 } 207 //delete vscreen; 208 //delete pscreen; 209 core.memory.GC.free(vscreen.ptr); 210 core.memory.GC.free(pscreen.ptr); 211 212 version (Windows) 213 { 214 movecursor(term.t_nrow - 1, 0); 215 term.t_close(); 216 printf("\n"); /* scroll up one line */ 217 } 218 else 219 { 220 movecursor(term.t_nrow - 1, 0); 221 term.t_putchar('\n'); /* scroll up one line */ 222 term.t_close(); 223 } 224 } 225 226 /* 227 * Set the virtual cursor to the specified row and column on the virtual 228 * screen. There is no checking for nonsense values; this might be a good 229 * idea during the early stages. 230 */ 231 void vtmove(int row, int col) 232 { 233 vtrow = row; 234 vtcol = col; 235 } 236 237 /* 238 * Write a character to the virtual screen. The virtual row and column are 239 * updated. If the line is too long put a "+" in the last column. This routine 240 * only puts printing characters into the virtual terminal buffers. Only 241 * column overflow is checked. 242 * Startcol is the starting column on the screen. 243 */ 244 void vtputc(dchar c, int startcol, int tabbase = 0) 245 { 246 vtputc(c, startcol, tabbase, attr); 247 } 248 249 void vtputc(dchar c, int startcol, int tabbase, attr_t attr) 250 { 251 auto vp = vscreen[vtrow]; 252 253 if (vtcol - startcol >= term.t_ncol) 254 { 255 vp[term.t_ncol - 1].chr = '+'; 256 vp[term.t_ncol - 1].attr = config.modeattr; 257 } 258 else if (c == '\t') 259 { 260 auto i = hardtabsize - ((vtcol - tabbase) % hardtabsize); 261 do 262 vtputc(config.tabchar, startcol, tabbase, attr); 263 while (--i); 264 } 265 else if (SHOWCONTROL && (c < 0x20 || c == 0x7F)) 266 { 267 vtputc('^',startcol, tabbase, attr); 268 vtputc(c ^ 0x40,startcol, tabbase, attr); 269 } 270 else 271 { 272 if (vtcol - startcol == 0 && startcol != 0) 273 { 274 vp[0].chr = '+'; 275 vp[0].attr = config.modeattr; 276 } 277 else if (vtcol - startcol >= 0) 278 { 279 vp[vtcol - startcol].chr = c; 280 vp[vtcol - startcol].attr = attr; 281 } 282 vtcol++; 283 } 284 } 285 286 /**************************** 287 * Compute column number of line given index into that line. 288 */ 289 290 int getcol(LINE *dotp, int doto) 291 { 292 return getcol2(dotp.l_text, doto); 293 } 294 295 int getcol2(const(char)[] dotp, int doto) 296 { 297 int curcol = 0; 298 size_t i = 0; 299 while (i < doto) 300 { 301 const c = decodeUTF8(dotp, i); 302 303 if (c == '\t') 304 { 305 if (hardtabsize == 8) 306 curcol |= 7; 307 else 308 { 309 curcol = ((curcol + hardtabsize) / hardtabsize) * hardtabsize - 1; 310 } 311 } 312 else if (SHOWCONTROL && (c < 0x20 || c == 0x7F)) 313 ++curcol; 314 ++curcol; 315 } 316 return curcol; 317 } 318 319 /****************************************** 320 * Inverse of getcol(), i.e. find offset into line that is closest 321 * to or past column col. 322 */ 323 324 int coltodoto(LINE* lp, int col) 325 { 326 size_t len = llength(lp); 327 size_t i = 0; 328 while (i < len) 329 { 330 if (getcol(lp, cast(int)i) >= col) 331 return cast(int)i; 332 decodeUTF8(lp.l_text, i); 333 } 334 return cast(int)i; 335 } 336 337 /*********************** 338 * Write a string to vtputc(). 339 */ 340 341 static void vtputs(const char[] s, int startcol, int tabbase = 0) 342 { 343 for (size_t i = 0; i < s.length; ) 344 { 345 dchar c = decodeUTF8(s, i); 346 vtputc(c, startcol, tabbase); 347 } 348 } 349 350 /* 351 * Erase from the end of the software cursor to the end of the line on which 352 * the software cursor is located. 353 */ 354 void vteeol(int startcol) 355 { 356 const col = max(vtcol - startcol, 0); 357 vscreen[vtrow][col .. term.t_ncol] = attchar_t(' ', config.eolattr); 358 vtcol = startcol + term.t_ncol; 359 } 360 361 /* 362 * Make sure that the display is right. This is a three part process. First, 363 * scan through all of the windows looking for dirty ones. Check the framing, 364 * and refresh the screen. Second, make sure that "currow" and "curcol" are 365 * correct for the current window. Third, make the virtual and physical 366 * screens the same. 367 */ 368 369 void update() 370 { 371 LINE *lp; 372 int k; 373 int l_first,l_last; 374 int scroll_done_flag; 375 int wcol; 376 int curcol; /* cursor column from left of text */ 377 char inmark; /* if column marking, and in region */ 378 version (MOUSE) 379 char hidden; 380 381 __gshared attr_t[] lineAttr; 382 383 if (ttkeysininput()) /* if more user input */ 384 return; /* skip updating till caught up */ 385 386 curcol = getcol(curwp.w_dotp,curwp.w_doto); 387 if ((lastflag&CFCPCN) == 0) /* Reset goal if last */ 388 curgoal = curcol; /* not backline() or forwline() */ 389 390 /* If cursor is off left or right side, set update bit so it'll scroll */ 391 if (curwp.w_startcol && curcol <= curwp.w_startcol || 392 curcol > curwp.w_startcol + term.t_ncol - 2) 393 curwp.w_flag |= WFHARD; 394 395 foreach (wp; windows) // for each window 396 { 397 const highlight = wp.w_bufp.b_lang; 398 399 /* Look at any window with update flags set on. */ 400 if (wp.w_flag != 0) 401 { char marking = wp.w_markp != null; 402 int col_left,col_right; /* for column marking */ 403 404 /* If not force reframe, check the framing. */ 405 if ((wp.w_flag & WFFORCE) == 0) 406 { 407 lp = wp.w_linep; /* top line on screen */ 408 409 for (int i = 0; 1; ++i) 410 { 411 /* if not on screen */ 412 if (i == wp.w_ntrows) 413 { if (lp == wp.w_dotp || 414 wp.w_dotp == wp.w_bufp.b_linep) 415 wp.w_force = -1; /* one up from bottom */ 416 else if (wp.w_dotp == lback(wp.w_linep)) 417 wp.w_force = 1; /* one before top */ 418 break; 419 } 420 421 if (lp == wp.w_dotp) /* if dot is on screen */ 422 goto Lout; /* no reframe necessary */ 423 424 if (lp == wp.w_bufp.b_linep) 425 break; /* reached end of buffer */ 426 427 lp = lforw(lp); 428 } 429 430 } /* if not force reframe */ 431 432 /* A reframe is necessary. 433 * Compute a new value for the line at the 434 * top of the window. Then set the "WFHARD" flag to force full 435 * redraw. 436 */ 437 { 438 int i = wp.w_force; 439 440 if (i > 0) /* if set dot to be on ith window line */ 441 { 442 --i; 443 444 if (i >= wp.w_ntrows) 445 i = wp.w_ntrows-1; /* clip i to size of window */ 446 } 447 else if (i < 0) /* if set dot to be -ith line from bottom */ 448 { 449 i += wp.w_ntrows; 450 451 if (i < 0) 452 i = 0; /* clip to top of screen */ 453 } 454 else /* set to center of screen */ 455 i = wp.w_ntrows/2; 456 457 lp = wp.w_dotp; 458 while (i != 0 && lback(lp) != wp.w_bufp.b_linep) 459 { 460 --i; 461 lp = lback(lp); 462 } 463 } 464 465 wp.w_linep = lp; 466 wp.w_flag |= WFHARD; /* Force full. */ 467 468 Lout: 469 /* Determine cursor column. If cursor is off the left or the */ 470 /* right, readjust starting column and do a WFHARD update */ 471 wcol = getcol(wp.w_dotp,wp.w_doto); 472 if (wp.w_startcol && wcol <= wp.w_startcol) 473 { wp.w_startcol = wcol ? wcol - 1 : 0; 474 wp.w_flag |= WFHARD; 475 } 476 else if (wp.w_startcol < wcol - term.t_ncol + 2) 477 { wp.w_startcol = wcol - term.t_ncol + 2; 478 wp.w_flag |= WFHARD; 479 } 480 481 /* Determine if we should start out with a standout */ 482 /* attribute or not, depends on if mark is before the window. */ 483 attr = config.normattr; 484 if (marking) 485 { 486 inmark = FALSE; 487 for (lp = lforw(wp.w_bufp.b_linep); 488 lp != wp.w_linep; 489 lp = lforw(lp)) 490 if (lp == wp.w_markp) 491 { inmark = TRUE; 492 break; 493 } 494 if (column_mode) 495 { 496 /* Calculate left and right column numbers of region */ 497 col_right = markcol;/*getcol(wp.w_markp,wp.w_marko);*/ 498 if (curgoal <= col_right) 499 col_left = curgoal; 500 else 501 { col_left = col_right; 502 col_right = curgoal; 503 } 504 } 505 else 506 { if (inmark) 507 { attr = config.markattr; 508 inmark = FALSE; 509 } 510 } 511 } 512 513 /* The window is framed properly now. 514 * Try to use reduced update. Mode line update has its own special 515 * flag. The fast update is used if the only thing to do is within 516 * the line editing. 517 */ 518 lp = wp.w_linep; /* line of top of window */ 519 int i = wp.w_toprow; /* display row # of top of window */ 520 521 if ((wp.w_flag & (WFEDIT | WFHARD)) != 0 || 522 marking && wp.w_flag & WFMOVE) 523 { 524 const oneLine = (wp.w_flag & (WFFORCE | WFEDIT | WFHARD | WFMOVE)) == WFEDIT; 525 526 if (oneLine) 527 { 528 /* Determine row number and line pointer for cursor line */ 529 while (lp != wp.w_dotp) 530 { ++i; 531 lp = lforw(lp); 532 } 533 } 534 535 /* update every line in the window */ 536 while (i < wp.w_toprow+wp.w_ntrows) 537 { 538 bool nextLine = !oneLine; 539 vrowflags[i] |= VFCHG; 540 vtmove(i, 0); 541 if (lp != wp.w_bufp.b_linep) /* if not end of buffer */ 542 { 543 if (highlight != Language.text) 544 { 545 if (lineAttr.length < lp.l_text.length) 546 { 547 size_t newlen = (lp.l_text.length * 3) / 2; 548 attr_t* p = cast(attr_t*)realloc(lineAttr.ptr, newlen); 549 assert(p); 550 lineAttr = p[0 .. newlen]; 551 } 552 const nextState = 553 highlight == Language.D ? syntaxHighlightD(lp.syntaxState, lp.l_text, lineAttr) : 554 highlight == Language.C ? syntaxHighlightC(lp.syntaxState, lp.l_text, lineAttr) : 555 syntaxHighlightCPP(lp.syntaxState, lp.l_text, lineAttr); 556 auto lpn = lforw(lp); 557 if (lpn != wp.w_bufp.b_linep) /* if not end of buffer */ 558 { 559 nextLine |= lpn.syntaxState != nextState; 560 lpn.syntaxState = nextState; 561 } 562 } 563 564 if (marking && column_mode) 565 { if (wp.w_markp == lp) 566 inmark++; 567 if (wp.w_dotp == lp) 568 inmark++; 569 attr = config.normattr; 570 } 571 572 for (size_t j = 0; 1; ) 573 { if (marking) 574 { if (column_mode) 575 { 576 if (inmark && col_left <= vtcol) 577 attr = config.markattr; 578 if (col_right <= vtcol) 579 attr = config.normattr; 580 } 581 else 582 { 583 if (wp.w_markp == lp && wp.w_marko == j) 584 attr ^= config.normattr ^ config.markattr; 585 if (wp.w_dotp == lp && wp.w_doto == j) 586 attr ^= config.normattr ^ config.markattr; 587 } 588 } 589 if (j >= llength(lp)) 590 break; 591 592 const cattr = (highlight != Language.text && attr == config.normattr) 593 ? lineAttr[j] : attr; 594 595 auto b = inURL(lp.l_text[], j); 596 auto s = inSearch(lp.l_text[], j); 597 dchar c = decodeUTF8(lp.l_text, j); 598 if (attr == config.normattr && (b || s)) 599 vtputc(c, wp.w_startcol, 0, s ? config.searchattr : config.urlattr); 600 else 601 vtputc(c, wp.w_startcol, 0, cattr); 602 } 603 if (inmark == 2) 604 inmark = 0; 605 lp = lforw(lp); 606 } 607 vteeol(wp.w_startcol); 608 ++i; 609 if (!nextLine) 610 break; 611 } 612 } 613 debug (WFDEBUG) 614 { 615 } 616 else 617 { 618 if ((wp.w_flag&WFMODE) != 0) /* if mode line is modified */ 619 modeline(wp); 620 wp.w_flag = 0; 621 wp.w_force = 0; 622 } 623 } /* if any update flags on */ 624 debug (WFDEBUG) 625 { 626 modeline(wp); 627 wp.w_flag = 0; 628 wp.w_force = 0; 629 } 630 } /* for each window */ 631 632 /* Always recompute the row and column number of the hardware cursor. This 633 * is the only update for simple moves. 634 */ 635 lp = curwp.w_linep; 636 currow = curwp.w_toprow; 637 638 while (lp != curwp.w_dotp) 639 { 640 ++currow; 641 lp = lforw(lp); 642 } 643 644 /* Special hacking if the screen is garbage. Clear the hardware screen, 645 * and update your copy to agree with it. Set all the virtual screen 646 * change bits, to force a full update. 647 */ 648 version (MOUSE) 649 hidden = 0; 650 651 if (sgarbf != FALSE) 652 { 653 for (int i = 0; i < term.t_nrow; ++i) 654 { 655 vrowflags[i] |= VFCHG; 656 for (int j = 0; j < term.t_ncol; j++) 657 { 658 pscreen[i][j].chr = ' '; 659 pscreen[i][j].attr = config.normattr; 660 } 661 } 662 663 version (MOUSE) 664 { 665 if (!hidden && mouse) 666 { msm_hidecursor(); 667 hidden++; 668 } 669 } 670 ttrow = HUGE; 671 ttcol = HUGE; // don't know where they are 672 movecursor(0, 0); /* Erase the screen. */ 673 term.t_eeop(); 674 sgarbf = FALSE; /* Erase-page clears */ 675 mpresf = FALSE; /* the message area. */ 676 } 677 678 version (Posix) 679 { 680 /* Here we check to see if we can scroll any of the lines. 681 * This silly routine just checks for possibilites of scrolling 682 * lines one line in either direction, but not multiple lines. 683 */ 684 if( !term.t_canscroll ) goto no_scroll_possible; 685 scroll_done_flag = 0; 686 for (int i = 0; i < term.t_nrow; i++) 687 { 688 if( vrowflags[i] & VFCHG ) 689 { 690 /* if not first line */ 691 /* and current line is identical to previous line */ 692 /* and previous line is not blank */ 693 if( i > 0 694 && vrowflags[i - 1] & VFCHG 695 && vscreen[i] == pscreen[i-1] 696 && pscreen[i-1] != blnk_ln ) 697 { 698 /* Scroll screen down */ 699 l_first = i-1; /* first line of scrolling region */ 700 while( i<term.t_nrow 701 && vrowflags[i - 1] & VFCHG 702 && vscreen[i] == pscreen[i-1] ) 703 i++; 704 l_last = i-1; /* last line of scrolling region */ 705 term.t_scrolldn( l_first, l_last ); 706 scroll_done_flag++; 707 for (int j = l_first+1; j < l_last+1; j++ ) 708 vrowflags[j] &= ~VFCHG; 709 for (int j = l_last; j > l_first; j-- ) 710 pscreen[j][] = pscreen[j - 1][]; 711 pscreen[l_first][] = attchar_t.init; 712 713 /* Set change flag on last line to get rid of */ 714 /* bug that caused lines to 'vanish'. */ 715 vrowflags[l_first] |= VFCHG; 716 } 717 else if( i < term.t_nrow-1 718 && vrowflags[i + 1] & VFCHG 719 && vscreen[i] == pscreen[i+1] 720 && pscreen[i+1] != blnk_ln ) 721 { 722 l_first = i; 723 while( i<term.t_nrow-1 724 && vrowflags[i + 1] & VFCHG 725 && vscreen[i] == pscreen[i+1] ) 726 i++; 727 l_last = i; 728 term.t_scrollup( l_first, l_last ); 729 scroll_done_flag++; 730 for (int j = l_first; j < l_last; j++ ) 731 vrowflags[j] &= ~VFCHG; 732 for (int j = l_first; j < l_last; j++ ) 733 pscreen[j][] = pscreen[j + 1]; 734 pscreen[l_last][] = attchar_t.init; 735 736 /* Set change flag on last line to get rid of */ 737 /* bug that caused lines to 'vanish'. */ 738 vrowflags[l_last] |= VFCHG; 739 } 740 } 741 } 742 if( scroll_done_flag ) 743 { 744 ttrow = ttrow-1; /* force a change */ 745 movecursor(currow, curcol - curwp.w_startcol); 746 } 747 748 no_scroll_possible: 749 ; 750 } 751 /* Make sure that the physical and virtual displays agree. Unlike before, 752 * the "updateline" code is only called with a line that has been updated 753 * for sure. 754 */ 755 for (int i = 0; i < term.t_nrow; ++i) 756 { 757 if (vrowflags[i] & VFCHG) 758 { 759 version (MOUSE) 760 { 761 if (!hidden && mouse) 762 { msm_hidecursor(); 763 hidden++; 764 } 765 } 766 vrowflags[i] &= ~VFCHG; 767 updateline(i, vscreen[i], pscreen[i]); 768 } 769 } 770 771 /* Finally, update the hardware cursor and flush out buffers. */ 772 773 version (Windows) 774 { 775 term.t_move(currow,curcol - curwp.w_startcol); /* putline() trashed the cursor pos */ 776 ttrow = currow; 777 ttcol = curcol - curwp.w_startcol; 778 } 779 else 780 { 781 movecursor(currow, curcol - curwp.w_startcol); 782 } 783 term.t_flush(); 784 version (MOUSE) 785 { 786 if (hidden && mouse) 787 msm_showcursor(); 788 } 789 } 790 791 /* 792 * Update a single line. This does not know how to use insert or delete 793 * character sequences; we are using VT52 functionality. Update the physical 794 * row and column variables. It does try an exploit erase to end of line. The 795 * RAINBOW version of this routine uses fast video. 796 */ 797 version (Posix) 798 { 799 void updateline(int row, attchar_t[] vline, attchar_t[] pline) 800 { 801 attchar_t *cp3; 802 attchar_t *cp4; 803 attchar_t *cp5; 804 int nbflag; 805 806 auto cp1 = &vline[0]; /* Compute left match. */ 807 auto cp2 = &pline[0]; 808 809 while (cp1 != vline.ptr + term.t_ncol && cp1[0] == cp2[0]) 810 { 811 ++cp1; 812 ++cp2; 813 } 814 815 /* This can still happen, even though we only call this routine on changed 816 * lines. A hard update is always done when a line splits, a massive 817 * change is done, or a buffer is displayed twice. This optimizes out most 818 * of the excess updating. A lot of computes are used, but these tend to 819 * be hard operations that do a lot of update, so I don't really care. 820 */ 821 if (cp1 == vline.ptr + term.t_ncol) /* All equal. */ 822 return; 823 824 nbflag = FALSE; 825 cp3 = vline.ptr + term.t_ncol; /* Compute right match. */ 826 cp4 = pline.ptr + term.t_ncol; 827 828 while (cp3[-1] == cp4[-1]) 829 { 830 --cp3; 831 --cp4; 832 if (cp3.chr != ' ' || cp3.attr) /* Note if any nonblank */ 833 nbflag = TRUE; /* in right match. */ 834 } 835 836 cp5 = cp3; 837 838 if (nbflag == FALSE) /* Erase to EOL ? */ 839 { 840 while (cp5!=cp1 && cp5[-1].chr==' ' && cp5[-1].attr == 0) 841 --cp5; 842 843 if (cp3-cp5 <= 3) /* Use only if erase is */ 844 cp5 = cp3; /* fewer characters. */ 845 } 846 847 movecursor(row, cast(int)(cp1-&vline[0])); /* Go to start of line. */ 848 849 __gshared attr_t tstand = 0; // != 0 if standout mode is active 850 while (cp1 != cp5) /* Ordinary. */ 851 { 852 if (cp1.attr != tstand) 853 { 854 if (cp1.attr) 855 { 856 //term.t_standout(); 857 term.setColor(cast(Color)cp1.attr); 858 tstand = cp1.attr; 859 } 860 else 861 { 862 //term.t_standend(); 863 term.resetColor(); 864 tstand = 0; 865 } 866 } 867 term.t_putchar(cp1.chr); 868 ++ttcol; 869 *cp2++ = *cp1++; 870 } 871 872 if (cp5 != cp3) /* Erase. */ 873 { 874 term.t_eeol(); 875 while (cp1 != cp3) 876 *cp2++ = *cp1++; 877 } 878 if( tstand ) 879 { //term.t_standend(); 880 term.resetColor(); 881 tstand = 0; 882 } 883 } 884 } 885 886 /* 887 * Redisplay the mode line for the window pointed to by the "wp". This is the 888 * only routine that has any idea of how the modeline is formatted. You can 889 * change the modeline format by hacking at this routine. Called by "update" 890 * any time there is a dirty window. 891 */ 892 void modeline(WINDOW* wp) 893 { 894 char *cp; 895 int c; 896 int n; 897 BUFFER *bp; 898 899 n = wp.w_toprow+wp.w_ntrows; /* Location. */ 900 vrowflags[n] |= VFCHG; /* Redraw next time. */ 901 vtmove(n, 0); /* Seek to right line. */ 902 attr = config.modeattr; 903 bp = wp.w_bufp; 904 905 if (bp.b_flag & BFRDONLY) 906 vtputc('R',0); 907 else if ((bp.b_flag&BFCHG) != 0) /* "*" if changed. */ 908 vtputc('*',0); 909 else 910 vtputc('-',0); 911 912 if (kbdmip) // if inputting a macro 913 vtputc('M',0); 914 else 915 vtputc(' ',0); 916 917 vtputs(EMACSREV,0); 918 vtputc(' ', 0); 919 920 if (globMatch(bp.b_bname,bp.b_fname) == 0) 921 { vtputs("-- Buffer: "c,0); 922 vtputs(bp.b_bname,0); 923 vtputc(' ',0); 924 } 925 if (bp.b_fname.length) /* File name. */ 926 { 927 vtputs("-- File: "c,0); 928 vtputs(bp.b_fname,0); 929 vtputc(' ',0); 930 } 931 932 vtputs("- ", 0); 933 const lang = bp.b_lang == Language.D ? "D " : 934 bp.b_lang == Language.C ? "C " : 935 bp.b_lang == Language.CPP ? "C++ " : 936 "text "; 937 vtputs(lang, 0); 938 vtputc('-', 0); 939 940 debug (WFDEBUG) 941 { 942 vtputc('-',0); 943 vtputc((wp.w_flag&WFMODE)!=0 ? 'M' : '-',0); 944 vtputc((wp.w_flag&WFHARD)!=0 ? 'H' : '-',0); 945 vtputc((wp.w_flag&WFEDIT)!=0 ? 'E' : '-',0); 946 vtputc((wp.w_flag&WFMOVE)!=0 ? 'V' : '-',0); 947 vtputc((wp.w_flag&WFFORCE)!=0 ? 'F' : '-',0); 948 } 949 950 while (vtcol < term.t_ncol) /* Pad to full width. */ 951 vtputc('-',0); 952 } 953 954 /* 955 * Send a command to the terminal to move the hardware cursor to row "row" 956 * and column "col". The row and column arguments are origin 0. Optimize out 957 * random calls. Update "ttrow" and "ttcol". 958 */ 959 void movecursor(int row, int col) 960 { 961 if (row != ttrow || col != ttcol) 962 { 963 ttrow = row; 964 ttcol = col; 965 term.t_move(row, col); 966 } 967 } 968 969 970 /* 971 * Erase the message line. This is a special routine because the message line 972 * is not considered to be part of the virtual screen. It always works 973 * immediately; the terminal buffer is flushed via a call to the flusher. 974 */ 975 void mlerase() 976 { 977 auto vp = vscreen[term.t_nrow - 1]; 978 foreach (ref c; vp[0 .. term.t_ncol]) 979 c = attchar_t(' ', config.eolattr); 980 vrowflags[term.t_nrow - 1] |= VFCHG; 981 mpresf = FALSE; 982 } 983 984 /* 985 * Ask a yes or no question in the message line. Return either TRUE, FALSE, or 986 * ABORT. The ABORT status is returned if the user bumps out of the question 987 * with a ^G. Used any time a confirmation is required. 988 */ 989 int mlyesno(string prompt) 990 { 991 string buf; 992 993 for (;;) 994 { 995 auto s = mlreply(prompt, null, buf); 996 997 if (s == ABORT) 998 return (ABORT); 999 1000 if (s != FALSE) 1001 { 1002 if (buf[0]=='y' || buf[0]=='Y') 1003 return (TRUE); 1004 1005 if (buf[0]=='n' || buf[0]=='N') 1006 return (FALSE); 1007 } 1008 } 1009 } 1010 1011 1012 /*********************************** 1013 * Simple circular history buffer for message line. 1014 */ 1015 1016 const HISTORY_MAX = 10; 1017 string[HISTORY_MAX] history; 1018 int history_top; 1019 1020 int HDEC(int hi) { return (hi == 0) ? HISTORY_MAX - 1 : hi - 1; } 1021 int HINC(int hi) { return (hi == HISTORY_MAX - 1) ? 0 : hi + 1; } 1022 1023 /* 1024 * Write a prompt into the message line, then read back a response. Keep 1025 * track of the physical position of the cursor. If we are in a keyboard 1026 * macro throw the prompt away, and return the remembered response. This 1027 * lets macros run at full speed. The reply is always terminated by a carriage 1028 * return. Handle erase, kill, and abort keys. 1029 */ 1030 int mlreply(string prompt, string init, out string result) 1031 { 1032 int dot; // insertion point in buffer 1033 int buflen; // number of characters in buffer 1034 int startcol; 1035 int changes; 1036 int hi; 1037 1038 int i; 1039 int c; 1040 1041 if (kbdmop != null) 1042 { 1043 int len; 1044 while ((cast(char*)kbdmop)[len]) 1045 ++len; 1046 result = (cast(char*)kbdmop)[0 .. len].idup; 1047 kbdmop = cast(dchar*)(cast(char*)kbdmop + len + 1); 1048 return (len != 0); 1049 } 1050 1051 hi = history_top; 1052 startcol = 0; 1053 attr = config.normattr; 1054 changes = 1; 1055 1056 mpresf = TRUE; 1057 1058 char[] buf; 1059 auto promptlen = cast(int)prompt.length; 1060 buf = init.dup; 1061 buflen = cast(int)buf.length; 1062 dot = buflen; 1063 1064 for (;;) 1065 { 1066 if (changes) 1067 { 1068 auto col = promptlen + getcol2(buf, dot); 1069 if (col >= startcol + term.t_ncol - 2) 1070 startcol = col - term.t_ncol + 2; 1071 if (col < startcol + promptlen) 1072 startcol = col - promptlen; 1073 1074 vtmove(term.t_nrow - 1, 0); 1075 vtputs(prompt,startcol); 1076 vtputs(buf, startcol, promptlen); 1077 vteeol(startcol); 1078 mlchange(); 1079 update(); 1080 attr = config.normattr; 1081 vtmove(term.t_nrow - 1, col); 1082 1083 changes = 0; 1084 } 1085 1086 movecursor(vtrow, vtcol - startcol); 1087 term.t_flush(); 1088 c = term.t_getchar(); 1089 1090 switch (c) 1091 { case 0x0D: /* Return, end of line */ 1092 if (kbdmip != null) 1093 { 1094 if (kbdmip + buflen + 1 > &kbdm[$-3]) 1095 goto err; /* error */ 1096 1097 memcpy(kbdmip, buf.ptr, buflen * buf[0].sizeof); 1098 (cast(char*)kbdmip)[buflen] = 0; 1099 (*cast(char**)&kbdmip) += buflen + 1; 1100 } 1101 if (buflen != 0) 1102 { 1103 hi = HDEC(history_top); 1104 if (!history[hi] || buf != history[hi]) 1105 { 1106 // Store in history buffer 1107 history[history_top] = buf.idup; 1108 history_top = HINC(history_top); 1109 if (history[history_top]) 1110 { 1111 //delete history[history_top]; 1112 core.memory.GC.free(cast(void*)history[history_top].ptr); 1113 history[history_top] = null; 1114 } 1115 } 1116 result = cast(immutable)buf; 1117 return 1; 1118 } 1119 return 0; 1120 1121 case 0x07: /* Bell, abort */ 1122 vtputc(7, startcol); 1123 mlchange(); 1124 goto err; /* error */ 1125 1126 case 0x01: // ^A, beginning of line 1127 case HOMEKEY: 1128 if (dot != 0) 1129 { 1130 dot = 0; 1131 startcol = 0; 1132 changes = 1; 1133 } 1134 break; 1135 1136 case 0x05: // ^E, beginning of line 1137 case ENDKEY: 1138 if (dot != buflen) 1139 { 1140 dot = buflen; 1141 changes = 1; 1142 } 1143 break; 1144 1145 case 0x0B: // ^K, delete to end of line 1146 if (dot != buflen) 1147 { 1148 buflen = dot; 1149 buf = buf[0 .. buflen]; 1150 changes = 1; 1151 } 1152 break; 1153 1154 case 0x7F: /* Rubout, erase */ 1155 case 0x08: /* Backspace, erase */ 1156 if (dot != 0) 1157 { 1158 memmove(buf.ptr + dot - 1, buf.ptr + dot, (buflen - dot) * buf[0].sizeof); 1159 --dot; 1160 --buflen; 1161 buf = buf[0 .. buflen]; 1162 changes = 1; 1163 } 1164 break; 1165 1166 case DelKEY: 1167 if (dot < buflen) 1168 { 1169 memmove(buf.ptr + dot, buf.ptr + dot + 1, (buflen - dot - 1) * buf[0].sizeof); 1170 --buflen; 1171 buf = buf[0 .. buflen]; 1172 changes = 1; 1173 } 1174 break; 1175 1176 1177 case 0x15: // ^U means delete line 1178 dot = 0; 1179 buflen = 0; 1180 buf = buf[0 .. buflen]; 1181 startcol = 0; 1182 changes = 1; 1183 break; 1184 1185 case 'Y' - 0x40: /* ^Y means yank */ 1186 { int n; 1187 1188 for (n = 0; (c = kill_remove(n)) != -1; n++) 1189 { 1190 buf.length = buf.length + 1; 1191 memmove(buf.ptr + dot + 1, buf.ptr + dot, (buflen - dot) * buf[0].sizeof); 1192 buflen++; 1193 buf[dot++] = cast(char)c; 1194 } 1195 changes = 1; 1196 } 1197 break; 1198 1199 case LTKEY: 1200 if (dot != 0) 1201 { 1202 dot--; 1203 changes = 1; 1204 } 1205 break; 1206 1207 case RTKEY: 1208 if (dot < buflen) 1209 { 1210 dot++; 1211 changes = 1; 1212 } 1213 break; 1214 1215 case UPKEY: 1216 i = HDEC(hi); 1217 if (hi == history_top && history[i] && buf == history[i]) 1218 i = HDEC(i); 1219 goto L1; 1220 1221 case DNKEY: 1222 i = HINC(hi); 1223 L1: 1224 if (history[i]) 1225 { 1226 buf = history[i].dup; 1227 buflen = cast(int)buf.length; 1228 dot = buflen; 1229 startcol = 0; 1230 changes = 1; 1231 hi = i; 1232 } 1233 else 1234 ctrlg(FALSE, 0); 1235 break; 1236 1237 //case InsKEY: 1238 case 0x11: /* ^Q, quote next */ 1239 c = term.t_getchar(); 1240 goto default; 1241 1242 default: 1243 if (c < 0 || c >= 0x7F) 1244 { // Error 1245 ctrlg(FALSE, 0); 1246 } 1247 else 1248 { 1249 buf.length = buf.length + 1; 1250 memmove(buf.ptr + dot + 1, buf.ptr + dot, (buflen - dot) * buf[0].sizeof); 1251 buflen++; 1252 buf[dot++] = cast(char)c; 1253 changes = 1; 1254 } 1255 break; 1256 } 1257 } 1258 1259 err: 1260 ctrlg(FALSE, 0); 1261 return (ABORT); 1262 } 1263 1264 /* 1265 * Write a message into the message line. Keep track of the physical cursor 1266 * position. 1267 * Set the "message line" flag TRUE. 1268 */ 1269 void mlwrite(string buffer) 1270 { 1271 int savecol; 1272 1273 vtmove(term.t_nrow - 1, 0); 1274 attr = config.normattr; 1275 vtputs(buffer,0); 1276 1277 savecol = vtcol; 1278 vteeol(0); 1279 vtcol = savecol; 1280 mlchange(); 1281 mpresf = TRUE; 1282 } 1283 1284 extern (C) void mlwrite(const(char)* fmt, ...) 1285 { 1286 int c; 1287 char[200+1] buffer = void; 1288 char *p; 1289 va_list ap; 1290 int savecol; 1291 1292 va_start(ap, fmt); 1293 auto n = vsnprintf(buffer.ptr, buffer.length, fmt, ap); 1294 va_end(ap); 1295 1296 // http://www.cplusplus.com/reference/cstdio/vsnprintf/ 1297 if (n <= 0) 1298 n = 0; 1299 else if (n >= buffer.length) 1300 n = buffer.length - 1; 1301 1302 vtmove(term.t_nrow - 1, 0); 1303 attr = config.normattr; 1304 vtputs(buffer[0 .. n],0,0); 1305 1306 savecol = vtcol; 1307 vteeol(0); 1308 vtcol = savecol; 1309 mlchange(); 1310 mpresf = TRUE; 1311 } 1312 1313 1314 void mlchange() 1315 { 1316 vrowflags[term.t_nrow - 1] |= VFCHG; 1317 }