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 }