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 }