]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - terminal.c
IND no longer notices lfhascr.
[PuTTY.git] / terminal.c
1 #ifndef macintosh
2 #include <windows.h>
3 #endif /* not macintosh */
4
5 #include <string.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8
9 #include "putty.h"
10
11 static unsigned long *text;            /* buffer of text on terminal screen */
12 static unsigned long *scrtop;          /* top of working screen */
13 static unsigned long *disptop;         /* top of displayed screen */
14 static unsigned long *sbtop;           /* top of scrollback */
15 static unsigned long *cpos;            /* cursor position (convenience) */
16 static unsigned long *disptext;        /* buffer of text on real screen */
17 static unsigned long *wanttext;        /* buffer of text we want on screen */
18 static unsigned long *alttext;         /* buffer of text on alt. screen */
19
20 static unsigned char *selspace;        /* buffer for building selections in */
21
22 #define TSIZE (sizeof(*text))
23 #define fix_cpos  do { cpos = scrtop + curs_y * (cols+1) + curs_x; } while(0)
24
25 static unsigned long curr_attr, save_attr;
26
27 static int curs_x, curs_y;             /* cursor */
28 static int save_x, save_y;             /* saved cursor position */
29 static int marg_t, marg_b;             /* scroll margins */
30 static int dec_om;                     /* DEC origin mode flag */
31 static int lfhascr;                    /* Auto-cr mode flag */
32 static int wrap, wrapnext;             /* wrap flags */
33 static int insert;                     /* insert-mode flag */
34 static int cset;                       /* 0 or 1: which char set */
35 static int save_cset, save_csattr;     /* saved with cursor position */
36 static int rvideo;                     /* global reverse video flag */
37
38 static unsigned long cset_attr[2];
39
40 /*
41  * Saved settings on the alternate screen.
42  */
43 static int alt_x, alt_y, alt_om, alt_wrap, alt_wnext, alt_ins, alt_cset;
44 static int alt_t, alt_b;
45 static int alt_which;
46
47 #define ARGS_MAX 32                    /* max # of esc sequence arguments */
48 #define ARG_DEFAULT -1                 /* if an arg isn't specified */
49 #define def(a,d) ( (a) == ARG_DEFAULT ? (d) : (a) )
50 static int esc_args[ARGS_MAX];
51 static int esc_nargs;
52 static int esc_query;
53
54 #define OSC_STR_MAX 2048
55 static int osc_strlen;
56 static char osc_string[OSC_STR_MAX+1];
57 static int osc_w;
58
59 static unsigned char *tabs;
60
61 #define MAXNL 5
62 static int nl_count;
63
64 static enum {
65     TOPLEVEL, IGNORE_NEXT,
66     SEEN_ESC, SEEN_CSI, SEEN_GZD4, SEEN_G1D4,
67     SEEN_OSC, SEEN_OSC_P, SEEN_OSC_W, OSC_STRING, OSC_MAYBE_ST,
68     SEEN_ESCHASH,
69     SEEN_ESC_CONFUSED, SEEN_CSI_CONFUSED,
70 } termstate;
71
72 static enum {
73     NO_SELECTION, ABOUT_TO, DRAGGING, SELECTED
74 } selstate;
75 static enum {
76     SM_CHAR, SM_WORD, SM_LINE
77 } selmode;
78 static unsigned long *selstart, *selend, *selanchor;
79
80 static short wordness[256] = {
81     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 01 */
82     0,1,2,1,1,1,1,1,1,1,1,1,1,2,2,2, 2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1, /* 23 */
83     1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,2, /* 45 */
84     1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1, /* 67 */
85     1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 89 */
86     1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* AB */
87     2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2, /* CD */
88     2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2, /* EF */
89 };
90
91 static unsigned char sel_nl[] = SEL_NL;
92
93 /*
94  * Internal prototypes.
95  */
96 static void do_paint (Context, int);
97 static void erase_lots (int, int, int);
98 static void swap_screen (int);
99 static void update_sbar (void);
100 static void deselect (void);
101 static void scroll_display(int, int, int);
102
103 /*
104  * Set up power-on settings for the terminal.
105  */
106 static void power_on(void) {
107     curs_x = curs_y = alt_x = alt_y = save_x = save_y = 0;
108     alt_t = marg_t = 0;
109     if (rows != -1)
110         alt_b = marg_b = rows - 1;
111     else
112         alt_b = marg_b = 0;
113     if (cols != -1) {
114         int i;
115         for (i = 0; i < cols; i++)
116             tabs[i] = (i % 8 == 0 ? TRUE : FALSE);
117     }
118     alt_om = dec_om = cfg.dec_om;
119     alt_wnext = wrapnext = alt_ins = insert = FALSE;
120     alt_wrap = wrap = cfg.wrap_mode;
121     alt_cset = cset = 0;
122     cset_attr[0] = cset_attr[1] = ATTR_ASCII;
123     rvideo = 0;
124     lfhascr = cfg.lfhascr;
125     save_attr = curr_attr = ATTR_DEFAULT;
126     app_cursor_keys = cfg.app_cursor;
127     app_keypad_keys = cfg.app_keypad;
128     alt_which = 0;
129     {
130         int i;
131         for (i = 0; i < 256; i++)
132             wordness[i] = cfg.wordness[i];
133     }
134     if (text) {
135         swap_screen (1);
136         erase_lots (FALSE, TRUE, TRUE);
137         swap_screen (0);
138         erase_lots (FALSE, TRUE, TRUE);
139     }
140 }
141
142 /*
143  * Force a screen update.
144  */
145 void term_update(void) {
146     Context ctx;
147     ctx = get_ctx();
148     if (ctx) {
149         do_paint (ctx, TRUE);
150         free_ctx (ctx);
151         nl_count = 0;
152     }
153 }
154
155 /*
156  * Same as power_on(), but an external function.
157  */
158 void term_pwron(void) {
159     power_on();
160     fix_cpos;
161     disptop = scrtop;
162     deselect();
163     term_update();
164 }
165
166 /*
167  * Clear the scrollback.
168  */
169 void term_clrsb(void) {
170     disptop = sbtop = scrtop;
171     update_sbar();
172 }
173
174 /*
175  * Initialise the terminal.
176  */
177 void term_init(void) {
178     text = sbtop = scrtop = disptop = cpos = NULL;
179     disptext = wanttext = NULL;
180     tabs = NULL;
181     selspace = NULL;
182     deselect();
183     rows = cols = -1;
184     nl_count = 0;
185     power_on();
186 }
187
188 /*
189  * Set up the terminal for a given size.
190  */
191 void term_size(int newrows, int newcols, int newsavelines) {
192     unsigned long *newtext, *newdisp, *newwant, *newalt;
193     int i, j, crows, ccols;
194
195     if (newrows == rows && newcols == cols && newsavelines == savelines)
196         return;                        /* nothing to do */
197
198     alt_t = marg_t = 0;
199     alt_b = marg_b = newrows - 1;
200
201     newtext = smalloc ((newrows+newsavelines)*(newcols+1)*TSIZE);
202     disptop = newtext + newsavelines*(newcols+1);
203     for (i=0; i<(newrows+newsavelines)*(newcols+1); i++)
204         newtext[i] = ERASE_CHAR;
205     if (rows != -1) {
206         crows = rows + (scrtop - sbtop) / (cols+1);
207         if (crows > newrows+newsavelines)
208             crows = newrows+newsavelines;
209         ccols = (cols < newcols ? cols : newcols);
210         for (i=0; i<crows; i++) {
211             int oldidx = (rows + savelines - crows + i) * (cols+1);
212             int newidx = (newrows + newsavelines - crows + i) * (newcols+1);
213             for (j=0; j<ccols; j++)
214                 newtext[newidx+j] = text[oldidx+j];
215             newtext[newidx+newcols] =
216                 (cols == newcols ? text[oldidx+cols] : 0);
217         }
218         sbtop = disptop - (crows - newrows) * (newcols+1);
219         if (sbtop > disptop)
220             sbtop = disptop;
221     } else
222         sbtop = disptop;
223     scrtop = disptop;
224     sfree (text);
225     text = newtext;
226
227     newdisp = smalloc (newrows*(newcols+1)*TSIZE);
228     for (i=0; i<newrows*(newcols+1); i++)
229         newdisp[i] = ATTR_INVALID;
230     sfree (disptext);
231     disptext = newdisp;
232
233     newwant = smalloc (newrows*(newcols+1)*TSIZE);
234     for (i=0; i<newrows*(newcols+1); i++)
235         newwant[i] = ATTR_INVALID;
236     sfree (wanttext);
237     wanttext = newwant;
238
239     newalt = smalloc (newrows*(newcols+1)*TSIZE);
240     for (i=0; i<newrows*(newcols+1); i++)
241         newalt[i] = ERASE_CHAR;
242     sfree (alttext);
243     alttext = newalt;
244
245     sfree (selspace);
246     selspace = smalloc ( (newrows+newsavelines) * (newcols+sizeof(sel_nl)) );
247
248     tabs = srealloc (tabs, newcols*sizeof(*tabs));
249     {
250         int i;
251         for (i = (cols > 0 ? cols : 0); i < newcols; i++)
252             tabs[i] = (i % 8 == 0 ? TRUE : FALSE);
253     }
254
255     if (rows > 0)
256         curs_y += newrows - rows;
257     if (curs_y < 0)
258         curs_y = 0;
259     if (curs_y >= newrows)
260         curs_y = newrows-1;
261     if (curs_x >= newcols)
262         curs_x = newcols-1;
263     alt_x = alt_y = 0;
264     wrapnext = alt_wnext = FALSE;
265
266     rows = newrows;
267     cols = newcols;
268     savelines = newsavelines;
269     fix_cpos;
270
271     deselect();
272     update_sbar();
273     term_update();
274 }
275
276 /*
277  * Swap screens.
278  */
279 static void swap_screen (int which) {
280     int t;
281     unsigned long tt;
282
283     if (which == alt_which)
284         return;
285
286     alt_which = which;
287
288     for (t=0; t<rows*(cols+1); t++) {
289         tt = scrtop[t]; scrtop[t] = alttext[t]; alttext[t] = tt;
290     }
291
292     t = curs_x; curs_x = alt_x; alt_x = t;
293     t = curs_y; curs_y = alt_y; alt_y = t;
294     t = marg_t; marg_t = alt_t; alt_t = t;
295     t = marg_b; marg_b = alt_b; alt_b = t;
296     t = dec_om; dec_om = alt_om; alt_om = t;
297     t = wrap; wrap = alt_wrap; alt_wrap = t;
298     t = wrapnext; wrapnext = alt_wnext; alt_wnext = t;
299     t = insert; insert = alt_ins; alt_ins = t;
300     t = cset; cset = alt_cset; alt_cset = t;
301
302     fix_cpos;
303 }
304
305 /*
306  * Retrieve a character from `inbuf'.
307  */
308 static int inbuf_getc(void) {
309     if (inbuf_head == inbuf_reap)
310         return -1;                     /* EOF */
311     else {
312         int n = inbuf_reap;
313         inbuf_reap = (inbuf_reap+1) & INBUF_MASK;
314         return inbuf[n];
315     }
316 }
317
318 /*
319  * Update the scroll bar.
320  */
321 static void update_sbar(void) {
322     int min;
323
324     min = (sbtop - text) / (cols+1);
325     set_sbar ((scrtop - text) / (cols+1) + rows - min,
326               (disptop - text) / (cols+1) - min,
327               rows);
328 }
329
330 /*
331  * Check whether the region bounded by the two pointers intersects
332  * the scroll region, and de-select the on-screen selection if so.
333  */
334 static void check_selection (unsigned long *from, unsigned long *to) {
335     if (from < selend && selstart < to)
336         deselect();
337 }
338
339 /*
340  * Scroll the screen. (`lines' is +ve for scrolling forward, -ve
341  * for backward.) `sb' is TRUE if the scrolling is permitted to
342  * affect the scrollback buffer.
343  */
344 static void scroll (int topline, int botline, int lines, int sb) {
345     unsigned long *scroll_top;
346     int scroll_size, size, i;
347
348     scroll_top = scrtop + topline*(cols+1);
349     size = (lines < 0 ? -lines : lines) * (cols+1);
350     scroll_size = (botline - topline + 1) * (cols+1) - size;
351
352     if (lines > 0 && topline == 0 && botline == (rows-1) && sb) {
353         /*
354          * Since we're going to scroll the whole screen upwards,
355          * let's also affect the scrollback buffer.
356          */
357         sbtop -= lines * (cols+1);
358         if (sbtop < text)
359             sbtop = text;
360         scroll_size += scroll_top - sbtop;
361         scroll_top = sbtop;
362         update_sbar();
363     }
364
365     if (scroll_size < 0) {
366         size += scroll_size;
367         scroll_size = 0;
368     }
369
370     if (lines > 0) {
371         if (scroll_size)
372             memmove (scroll_top, scroll_top + size, scroll_size*TSIZE);
373         for (i = 0; i < size; i++)
374             scroll_top[i+scroll_size] = ERASE_CHAR;
375         if (selstart > scroll_top &&
376             selstart < scroll_top + size + scroll_size) {
377             selstart -= size;
378             if (selstart < scroll_top)
379                 selstart = scroll_top;
380         }
381         if (selend > scroll_top &&
382             selend < scroll_top + size + scroll_size) {
383             selend -= size;
384             if (selend < scroll_top)
385                 selend = scroll_top;
386         }
387     } else {
388         if (scroll_size)
389             memmove (scroll_top + size, scroll_top, scroll_size*TSIZE);
390         for (i = 0; i < size; i++)
391             scroll_top[i] = ERASE_CHAR;
392         if (selstart > scroll_top &&
393             selstart < scroll_top + size + scroll_size) {
394             selstart += size;
395             if (selstart > scroll_top + size + scroll_size)
396                 selstart = scroll_top + size + scroll_size;
397         }
398         if (selend > scroll_top &&
399             selend < scroll_top + size + scroll_size) {
400             selend += size;
401             if (selend > scroll_top + size + scroll_size)
402                 selend = scroll_top + size + scroll_size;
403         }
404     }
405 #ifdef OPTIMISE_SCROLL
406     scroll_display(topline, botline, lines);
407 #endif
408 }
409
410 #ifdef OPTIMISE_SCROLL
411 static void scroll_display(int topline, int botline, int lines) {
412     unsigned long *start, *end;
413     int distance, size, i;
414
415     start = disptext + topline * (cols + 1);
416     end = disptext + (botline + 1) * (cols + 1);
417     distance = (lines > 0 ? lines : -lines) * (cols + 1);
418     size = end - start - distance;
419     if (lines > 0) {
420         memmove(start, start + distance, size * TSIZE);
421         for (i = 0; i < distance; i++)
422             (start + size)[i] |= ATTR_INVALID;
423     } else {
424         memmove(start + distance, start, size * TSIZE);
425         for (i = 0; i < distance; i++)
426             start[i] |= ATTR_INVALID;
427     }
428     do_scroll(topline, botline, lines);
429 }
430 #endif /* OPTIMISE_SCROLL */
431     
432
433 /*
434  * Move the cursor to a given position, clipping at boundaries. We
435  * may or may not want to clip at the scroll margin: marg_clip is 0
436  * not to, 1 to disallow _passing_ the margins, and 2 to disallow
437  * even _being_ outside the margins.
438  */
439 static void move (int x, int y, int marg_clip) {
440     if (x < 0)
441         x = 0;
442     if (x >= cols)
443         x = cols-1;
444     if (marg_clip) {
445         if ((curs_y >= marg_t || marg_clip == 2) && y < marg_t)
446             y = marg_t;
447         if ((curs_y <= marg_b || marg_clip == 2) && y > marg_b)
448             y = marg_b;
449     }
450     if (y < 0)
451         y = 0;
452     if (y >= rows)
453         y = rows-1;
454     curs_x = x;
455     curs_y = y;
456     fix_cpos;
457     wrapnext = FALSE;
458 }
459
460 /*
461  * Save or restore the cursor and SGR mode.
462  */
463 static void save_cursor(int save) {
464     if (save) {
465         save_x = curs_x;
466         save_y = curs_y;
467         save_attr = curr_attr;
468         save_cset = cset;
469         save_csattr = cset_attr[cset];
470     } else {
471         curs_x = save_x;
472         curs_y = save_y;
473         curr_attr = save_attr;
474         cset = save_cset;
475         cset_attr[cset] = save_csattr;
476         fix_cpos;
477     }
478 }
479
480 /*
481  * Erase a large portion of the screen: the whole screen, or the
482  * whole line, or parts thereof.
483  */
484 static void erase_lots (int line_only, int from_begin, int to_end) {
485     unsigned long *startpos, *endpos;
486
487     if (line_only) {
488         startpos = cpos - curs_x;
489         endpos = startpos + cols+1;
490     } else {
491         startpos = scrtop;
492         endpos = startpos + rows * (cols+1);
493     }
494     if (!from_begin)
495         startpos = cpos;
496     if (!to_end)
497         endpos = cpos;
498     check_selection (startpos, endpos);
499     while (startpos < endpos)
500         *startpos++ = ERASE_CHAR;
501 }
502
503 /*
504  * Insert or delete characters within the current line. n is +ve if
505  * insertion is desired, and -ve for deletion.
506  */
507 static void insch (int n) {
508     int dir = (n < 0 ? -1 : +1);
509     int m;
510
511     n = (n < 0 ? -n : n);
512     if (n > cols - curs_x)
513         n = cols - curs_x;
514     m = cols - curs_x - n;
515     check_selection (cpos, cpos+n);
516     if (dir < 0) {
517         memmove (cpos, cpos+n, m*TSIZE);
518         while (n--)
519             cpos[m++] = ERASE_CHAR;
520     } else {
521         memmove (cpos+n, cpos, m*TSIZE);
522         while (n--)
523             cpos[n] = ERASE_CHAR;
524     }
525 }
526
527 /*
528  * Toggle terminal mode `mode' to state `state'. (`query' indicates
529  * whether the mode is a DEC private one or a normal one.)
530  */
531 static void toggle_mode (int mode, int query, int state) {
532     if (query) switch (mode) {
533       case 1:                          /* application cursor keys */
534         app_cursor_keys = state;
535         break;
536       case 3:                          /* 80/132 columns */
537         deselect();
538         request_resize (state ? 132 : 80, rows);
539         break;
540       case 5:                          /* reverse video */
541         rvideo = state;
542         disptop = scrtop;
543         break;
544       case 6:                          /* DEC origin mode */
545         dec_om = state;
546         break;
547       case 7:                          /* auto wrap */
548         wrap = state;
549         break;
550       case 47:                         /* alternate screen */
551         deselect();
552         swap_screen (state);
553         disptop = scrtop;
554         break;
555     } else switch (mode) {
556       case 4:                          /* set insert mode */
557         insert = state;
558         break;
559     }
560 }
561
562 /*
563  * Process an OSC sequence: set window title or icon name.
564  */
565 static void do_osc(void) {
566     if (osc_w) {
567         while (osc_strlen--)
568             wordness[(unsigned char)osc_string[osc_strlen]] = esc_args[0];
569     } else {
570         osc_string[osc_strlen] = '\0';
571         switch (esc_args[0]) {
572           case 0:
573           case 1:
574             set_icon (osc_string);
575             if (esc_args[0] == 1)
576                 break;
577             /* fall through: parameter 0 means set both */
578           case 2:
579           case 21:
580             set_title (osc_string);
581             break;
582         }
583     }
584 }
585
586 enum c0 {
587     NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL,
588     BS, HT, LF, VT, FF, CR, SO, SI,
589     DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB,
590     CAN, EM, SUB, ESC, IS1, IS2, IS3, IS4
591 };
592
593 enum c1 {
594     BPH = 0x82, NBH, IND, NEL, SSA, ESA,
595     HTS, HTJ, VTS, PLD, PLU, RI, SS1, SS2,
596     DCS, PU1, PU2, STS, CCH, MW, SPA, EPA,
597     SOS, SCI = 0x9a, CSI, ST, OSC, PM, APC
598 };
599
600 /*
601  * Remove everything currently in `inbuf' and stick it up on the
602  * in-memory display. There's a big state machine in here to
603  * process escape sequences...
604  */
605 void term_out(void) {
606     int c;
607     int must_update = FALSE;
608     int reprocess = FALSE;
609
610     while (reprocess || (c = inbuf_getc()) != -1) {
611 #ifdef LOG
612         if (!reprocess) {
613             static FILE *fp = NULL;
614             if (!fp) fp = fopen("putty.log", "wb");
615             if (fp) fputc (c, fp);
616         }
617 #endif
618         reprocess = FALSE;
619         switch (termstate) {
620           case TOPLEVEL:
621             do_toplevel:
622             switch (c) {
623               case '\005':             /* terminal type query */
624                 back->send ("\033[?1;2c", 7);
625                 break;
626               case '\007':
627                 beep();
628                 disptop = scrtop;
629                 must_update = TRUE;
630                 break;
631               case '\b':
632                 if (curs_x == 0 && curs_y > 0)
633                     curs_x = cols-1, curs_y--;
634                 else if (wrapnext)
635                     wrapnext = FALSE;
636                 else
637                     curs_x--;
638                 fix_cpos;
639                 disptop = scrtop;
640                 must_update = TRUE;
641                 break;
642               case '\015':
643                 curs_x = 0;
644                 wrapnext = FALSE;
645                 fix_cpos;
646                 disptop = scrtop;
647                 must_update = TRUE;
648                 break;
649               case '\013':
650               case '\014':
651               case '\012':
652               case IND:
653                 if (curs_y == marg_b)
654                     scroll (marg_t, marg_b, 1, TRUE);
655                 else if (curs_y < rows-1)
656                     curs_y++;
657                 if (lfhascr && c != IND)
658                     curs_x = 0;
659                 fix_cpos;
660                 wrapnext = FALSE;
661                 disptop = scrtop;
662                 nl_count++;
663                 break;
664               case '\t':
665                 do {
666                     curs_x++;
667                 } while (curs_x < cols-1 && !tabs[curs_x]);
668                 if (curs_x >= cols)
669                     curs_x = cols-1;
670                 {
671                     unsigned long *old_cpos = cpos;
672                     fix_cpos;
673                     check_selection (old_cpos, cpos);
674                 }
675                 disptop = scrtop;
676                 must_update = TRUE;
677                 break;
678               case '\016':
679                 cset = 1;
680                 break;
681               case '\017':
682                 cset = 0;
683                 break;
684               case '\033':
685                 termstate = SEEN_ESC;
686                 break;
687               case NEL:                /* exactly equivalent to CR-LF */
688                 curs_x = 0;
689                 wrapnext = FALSE;
690                 if (curs_y == marg_b)
691                     scroll (marg_t, marg_b, 1, TRUE);
692                 else if (curs_y < rows-1)
693                     curs_y++;
694                 fix_cpos;
695                 wrapnext = FALSE;
696                 nl_count++;
697                 disptop = scrtop;
698                 break;
699               case HTS:                /* set a tab */
700                 tabs[curs_x] = TRUE;
701                 break;
702               case RI:                 /* reverse index - backwards LF */
703                 if (curs_y == marg_t)
704                     scroll (marg_t, marg_b, -1, TRUE);
705                 else if (curs_y > 0)
706                     curs_y--;
707                 fix_cpos;
708                 wrapnext = FALSE;
709                 disptop = scrtop;
710                 must_update = TRUE;
711                 break;
712               case SCI:                /* terminal type query */
713                 /* This sequence is standardised as something else entirely. */
714                 back->send ("\033[?6c", 5);
715                 break;
716               case 0233:
717                 termstate = SEEN_CSI;
718                 esc_nargs = 1;
719                 esc_args[0] = ARG_DEFAULT;
720                 esc_query = FALSE;
721                 break;
722               case 0235:
723                 termstate = SEEN_OSC;
724                 esc_args[0] = 0;
725                 break;
726               default:
727                 if (c >= ' ' && c < 0x7f || c >= 0xa0 ) {
728                     if (wrapnext) {
729                         cpos[1] = ATTR_WRAPPED;
730                         if (curs_y == marg_b)
731                             scroll (marg_t, marg_b, 1, TRUE);
732                         else if (curs_y < rows-1)
733                             curs_y++;
734                         curs_x = 0;
735                         fix_cpos;
736                         wrapnext = FALSE;
737                         nl_count++;
738                     }
739                     if (insert)
740                         insch (1);
741                     check_selection (cpos, cpos+1);
742                     *cpos++ = c | curr_attr | 
743                         (c <= 0x7F ? cset_attr[cset] : ATTR_ASCII);
744                     curs_x++;
745                     if (curs_x == cols) {
746                         cpos--;
747                         curs_x--;
748                         wrapnext = wrap;
749                     }
750                     disptop = scrtop;
751                 }
752                 break;
753             }
754             break;
755           case IGNORE_NEXT:
756             termstate = TOPLEVEL;
757             break;
758           case OSC_MAYBE_ST:
759             /*
760              * This state is virtually identical to SEEN_ESC, with the
761              * exception that we have an OSC sequence in the pipeline,
762              * and _if_ we see a backslash, we process it.
763              */
764             if (c == '\\') {
765                 do_osc();
766                 termstate = TOPLEVEL;
767                 break;
768             }
769             /* else fall through */
770           case SEEN_ESC:
771             /*
772              * According to ECMA-35, an escape sequence consists of
773              * ESC, a sequence (possibly empty) of intermediate bytes
774              * from column 02 (SPACE--/), and a final byte from
775              * columns 03-07 (0--~).
776              */
777             termstate = TOPLEVEL;
778             if (c >= 0x40 && c < 0x60) {
779                 /* Fe sequences -- C1 control as an escape sequence */
780                 c += 0x40;
781                 reprocess = TRUE;
782             } else switch (c) {
783                 /* nF sequences -- with intermediate bytes */
784               case '#':                /* Single control functions */
785                 termstate = SEEN_ESCHASH;
786                 break;
787               case '(':                /* GZD4: should set G0 */
788                 termstate = SEEN_GZD4;
789                 break;
790               case ')':                /* G1D4: should set G1 */
791                 termstate = SEEN_G1D4;
792                 break;
793                 /* Fp sequences -- private control functions */
794               case '7':                /* save cursor */
795                 save_cursor (TRUE);
796                 break;
797               case '8':                /* restore cursor */
798                 save_cursor (FALSE);
799                 disptop = scrtop;
800                 must_update = TRUE;
801                 break;
802               case '=':
803                 app_keypad_keys = TRUE;
804                 break;
805               case '>':
806                 app_keypad_keys = FALSE;
807                 break;
808                 /* Fs sequences -- standardised control functions */
809               case 'c':                /* RIS: restore power-on settings */
810                 power_on();
811                 fix_cpos;
812                 disptop = scrtop;
813                 must_update = TRUE;
814                 break;
815               default:
816                 termstate = SEEN_ESC_CONFUSED;
817                 reprocess = TRUE;
818                 break;
819             }
820             break;
821           case SEEN_ESC_CONFUSED:
822             /*
823              * We're in an escape sequence, but we no longer know what
824              * it means and we just want it to go away
825              */
826             termstate = TOPLEVEL;
827             if (c < 0x20 || c >= 0x7f)
828                 /*
829                  * ECMA-35 says this isn't allowed, so we can do what
830                  * we like.
831                  */
832                 reprocess = TRUE;
833             else if (c <= 0x30)
834                 /* Intermediate byte -- more to come */
835                 termstate = SEEN_ESC_CONFUSED;
836             /* Otherwise, that was a final byte and we're free! */
837             break;
838           case SEEN_CSI:
839             /*
840              * In theory, a control sequence consists of CSI, then a
841              * sequence (possibly empty) of parameter bytes (0--?)
842              * then a sequence (possibly empty) of intermediate bytes
843              * (SPACE--/), then a final byte (@--~).  We're rather
844              * more relaxed, and don't differentiate between parameter
845              * and intermediate bytes.
846              */
847             termstate = TOPLEVEL;      /* default */
848             switch (c) {
849               case '0': case '1': case '2': case '3': case '4':
850               case '5': case '6': case '7': case '8': case '9':
851                 if (esc_nargs <= ARGS_MAX) {
852                     if (esc_args[esc_nargs-1] == ARG_DEFAULT)
853                         esc_args[esc_nargs-1] = 0;
854                     esc_args[esc_nargs-1] =
855                         10 * esc_args[esc_nargs-1] + c - '0';
856                 }
857                 termstate = SEEN_CSI;
858                 break;
859               case ';':
860                 if (++esc_nargs <= ARGS_MAX)
861                     esc_args[esc_nargs-1] = ARG_DEFAULT;
862                 termstate = SEEN_CSI;
863                 break;
864               case '?':
865                 esc_query = TRUE;
866                 termstate = SEEN_CSI;
867                 break;
868               case 'A':                /* move up N lines */
869                 move (curs_x, curs_y - def(esc_args[0], 1), 1);
870                 disptop = scrtop;
871                 must_update = TRUE;
872                 break;
873               case 'B': case 'e':      /* move down N lines */
874                 move (curs_x, curs_y + def(esc_args[0], 1), 1);
875                 disptop = scrtop;
876                 must_update = TRUE;
877                 break;
878               case 'C': case 'a':      /* move right N cols */
879                 move (curs_x + def(esc_args[0], 1), curs_y, 1);
880                 disptop = scrtop;
881                 must_update = TRUE;
882                 break;
883               case 'D':                /* move left N cols */
884                 move (curs_x - def(esc_args[0], 1), curs_y, 1);
885                 disptop = scrtop;
886                 must_update = TRUE;
887                 break;
888               case 'E':                /* move down N lines and CR */
889                 move (0, curs_y + def(esc_args[0], 1), 1);
890                 disptop = scrtop;
891                 must_update = TRUE;
892                 break;
893               case 'F':                /* move up N lines and CR */
894                 move (0, curs_y - def(esc_args[0], 1), 1);
895                 disptop = scrtop;
896                 must_update = TRUE;
897                 break;
898               case 'G': case '`':      /* set horizontal posn */
899                 move (def(esc_args[0], 1) - 1, curs_y, 0);
900                 disptop = scrtop;
901                 must_update = TRUE;
902                 break;
903               case 'd':                /* set vertical posn */
904                 move (curs_x, (dec_om ? marg_t : 0) + def(esc_args[0], 1) - 1,
905                       (dec_om ? 2 : 0));
906                 disptop = scrtop;
907                 must_update = TRUE;
908                 break;
909               case 'H': case 'f':      /* set horz and vert posns at once */
910                 if (esc_nargs < 2)
911                     esc_args[1] = ARG_DEFAULT;
912                 move (def(esc_args[1], 1) - 1,
913                       (dec_om ? marg_t : 0) + def(esc_args[0], 1) - 1,
914                       (dec_om ? 2 : 0));
915                 disptop = scrtop;
916                 must_update = TRUE;
917                 break;
918               case 'J':                /* erase screen or parts of it */
919                 {
920                     unsigned int i = def(esc_args[0], 0) + 1;
921                     if (i > 3)
922                         i = 0;
923                     erase_lots(FALSE, !!(i & 2), !!(i & 1));
924                 }
925                 disptop = scrtop;
926                 must_update = TRUE;
927                 break;
928               case 'K':                /* erase line or parts of it */
929                 {
930                     unsigned int i = def(esc_args[0], 0) + 1;
931                     if (i > 3)
932                         i = 0;
933                     erase_lots(TRUE, !!(i & 2), !!(i & 1));
934                 }
935                 disptop = scrtop;
936                 must_update = TRUE;
937                 break;
938               case 'L':                /* insert lines */
939                 if (curs_y <= marg_b)
940                     scroll (curs_y, marg_b, -def(esc_args[0], 1), FALSE);
941                 disptop = scrtop;
942                 must_update = TRUE;
943                 break;
944               case 'M':                /* delete lines */
945                 if (curs_y <= marg_b)
946                     scroll (curs_y, marg_b, def(esc_args[0], 1), FALSE);
947                 disptop = scrtop;
948                 must_update = TRUE;
949                 break;
950               case '@':                /* insert chars */
951                 insch (def(esc_args[0], 1));
952                 disptop = scrtop;
953                 must_update = TRUE;
954                 break;
955               case 'P':                /* delete chars */
956                 insch (-def(esc_args[0], 1));
957                 disptop = scrtop;
958                 must_update = TRUE;
959                 break;
960               case 'c':                /* terminal type query */
961                 back->send ("\033[?6c", 5);
962                 break;
963               case 'n':                /* cursor position query */
964                 if (esc_args[0] == 6) {
965                     char buf[32];
966                     sprintf (buf, "\033[%d;%dR", curs_y + 1, curs_x + 1);
967                     back->send (buf, strlen(buf));
968                 }
969                 break;
970               case 'h':                /* toggle a mode to high */
971                 toggle_mode (esc_args[0], esc_query, TRUE);
972                 break;
973               case 'l':                /* toggle a mode to low */
974                 toggle_mode (esc_args[0], esc_query, FALSE);
975                 break;
976               case 'g':                /* clear tabs */
977                 if (esc_nargs == 1) {
978                     if (esc_args[0] == 0) {
979                         tabs[curs_x] = FALSE;
980                     } else if (esc_args[0] == 3) {
981                         int i;
982                         for (i = 0; i < cols; i++)
983                             tabs[i] = FALSE;
984                     }
985                 }
986                 break;
987               case 'r':                /* set scroll margins */
988                 if (esc_nargs <= 2) {
989                     int top, bot;
990                     top = def(esc_args[0], 1) - 1;
991                     if (top < 0)
992                         top = 0;
993                     bot = (esc_nargs == 1 ? rows :
994                            def(esc_args[1], rows)) - 1;
995                     if (bot >= rows)
996                         bot = rows-1;
997                     if (top <= bot) {
998                         marg_t = top;
999                         marg_b = bot;
1000                         curs_x = 0;
1001                         /*
1002                          * I used to think the cursor should be
1003                          * placed at the top of the newly marginned
1004                          * area. Apparently not: VMS TPU falls over
1005                          * if so.
1006                          */
1007                         curs_y = 0;
1008                         fix_cpos;
1009                         disptop = scrtop;
1010                         must_update = TRUE;
1011                     }
1012                 }
1013                 break;
1014               case 'm':                /* set graphics rendition */
1015                 {
1016                     int i;
1017                     for (i=0; i<esc_nargs; i++) {
1018                         switch (def(esc_args[i], 0)) {
1019                           case 0:      /* restore defaults */
1020                             curr_attr = ATTR_DEFAULT; break;
1021                           case 1:      /* enable bold */
1022                             curr_attr |= ATTR_BOLD; break;
1023                           case 4:      /* enable underline */
1024                           case 21:     /* (enable double underline) */
1025                             curr_attr |= ATTR_UNDER; break;
1026                           case 7:      /* enable reverse video */
1027                             curr_attr |= ATTR_REVERSE; break;
1028                           case 22:     /* disable bold */
1029                             curr_attr &= ~ATTR_BOLD; break;
1030                           case 24:     /* disable underline */
1031                             curr_attr &= ~ATTR_UNDER; break;
1032                           case 27:     /* disable reverse video */
1033                             curr_attr &= ~ATTR_REVERSE; break;
1034                           case 30: case 31: case 32: case 33:
1035                           case 34: case 35: case 36: case 37:
1036                             /* foreground */
1037                             curr_attr &= ~ATTR_FGMASK;
1038                             curr_attr |= (esc_args[i] - 30) << ATTR_FGSHIFT;
1039                             break;
1040                           case 39:     /* default-foreground */
1041                             curr_attr &= ~ATTR_FGMASK;
1042                             curr_attr |= ATTR_DEFFG;
1043                             break;
1044                           case 40: case 41: case 42: case 43:
1045                           case 44: case 45: case 46: case 47:
1046                             /* background */
1047                             curr_attr &= ~ATTR_BGMASK;
1048                             curr_attr |= (esc_args[i] - 40) << ATTR_BGSHIFT;
1049                             break;
1050                           case 49:     /* default-background */
1051                             curr_attr &= ~ATTR_BGMASK;
1052                             curr_attr |= ATTR_DEFBG;
1053                             break;
1054                         }
1055                     }
1056                 }
1057                 break;
1058               case 's':                /* save cursor */
1059                 save_cursor (TRUE);
1060                 break;
1061               case 'u':                /* restore cursor */
1062                 save_cursor (FALSE);
1063                 disptop = scrtop;
1064                 must_update = TRUE;
1065                 break;
1066               case 't':                /* set page size - ie window height */
1067                 request_resize (cols, def(esc_args[0], 24));
1068                 deselect();
1069                 break;
1070               case 'X':                /* write N spaces w/o moving cursor */
1071                 {
1072                     int n = def(esc_args[0], 1);
1073                     unsigned long *p = cpos;
1074                     if (n > cols - curs_x)
1075                         n = cols - curs_x;
1076                     check_selection (cpos, cpos+n);
1077                     while (n--)
1078                         *p++ = ERASE_CHAR;
1079                     disptop = scrtop;
1080                     must_update = TRUE;
1081                 }
1082                 break;
1083               case 'x':                /* report terminal characteristics */
1084                 {
1085                     char buf[32];
1086                     int i = def(esc_args[0], 0);
1087                     if (i == 0 || i == 1) {
1088                         strcpy (buf, "\033[2;1;1;112;112;1;0x");
1089                         buf[2] += i;
1090                         back->send (buf, 20);
1091                     }
1092                 }
1093                 break;
1094               default:
1095                 termstate = SEEN_CSI_CONFUSED;
1096                 reprocess = TRUE;
1097                 break;
1098             }
1099             break;
1100           case SEEN_CSI_CONFUSED:
1101             termstate = TOPLEVEL;
1102             if (c < 0x20 || c >= 0x7f)
1103                 reprocess = TRUE;
1104             else if (c < 0x40)
1105                 termstate = SEEN_CSI_CONFUSED;
1106           case SEEN_GZD4:
1107           case SEEN_G1D4:
1108             switch (c) {
1109               case 'A':
1110                 cset_attr[termstate == SEEN_GZD4 ? 0 : 1] = ATTR_GBCHR;
1111                 break;
1112               case '0':
1113                 cset_attr[termstate == SEEN_GZD4 ? 0 : 1] = ATTR_LINEDRW;
1114                 break;
1115               default:                 /* specifically, 'B' */
1116                 cset_attr[termstate == SEEN_GZD4 ? 0 : 1] = ATTR_ASCII;
1117                 break;
1118             }
1119             termstate = TOPLEVEL;
1120             break;
1121           case SEEN_OSC:
1122             osc_w = FALSE;
1123             switch (c) {
1124               case '\005': case '\007': case '\b': case '\016': case '\017':
1125               case '\033': case 0233: case 0234: case 0235: case '\015':
1126               case '\013': case '\014': case '\012': case '\t':
1127                 termstate = TOPLEVEL;
1128                 goto do_toplevel;      /* hack... */
1129               case 'P':                /* Linux palette sequence */
1130                 termstate = SEEN_OSC_P;
1131                 osc_strlen = 0;
1132                 break;
1133               case 'R':                /* Linux palette reset */
1134                 palette_reset();
1135                 term_invalidate();
1136                 termstate = TOPLEVEL;
1137                 break;
1138               case 'W':                /* word-set */
1139                 termstate = SEEN_OSC_W;
1140                 osc_w = TRUE;
1141                 break;
1142               case '0': case '1': case '2': case '3': case '4':
1143               case '5': case '6': case '7': case '8': case '9':
1144                 esc_args[0] = 10 * esc_args[0] + c - '0';
1145                 break;
1146               case 'L':
1147                 /*
1148                  * Grotty hack to support xterm and DECterm title
1149                  * sequences concurrently.
1150                  */
1151                 if (esc_args[0] == 2) {
1152                     esc_args[0] = 1;
1153                     break;
1154                 }
1155                 /* else fall through */
1156               default:
1157                 termstate = OSC_STRING;
1158                 osc_strlen = 0;
1159             }
1160             break;
1161           case OSC_STRING:
1162             if (c == 0234 || c == '\007') {
1163                 /*
1164                  * These characters terminate the string; ST and BEL
1165                  * terminate the sequence and trigger instant
1166                  * processing of it, whereas ESC goes back to SEEN_ESC
1167                  * mode unless it is followed by \, in which case it is
1168                  * synonymous with ST in the first place.
1169                  */
1170                 do_osc();
1171                 termstate = TOPLEVEL;
1172             } else if (c == '\033')
1173                     termstate = OSC_MAYBE_ST;
1174             else if (osc_strlen < OSC_STR_MAX)
1175                 osc_string[osc_strlen++] = c;
1176             break;
1177           case SEEN_OSC_P:
1178             {
1179                 int max = (osc_strlen == 0 ? 21 : 16);
1180                 int val;
1181                 if (c >= '0' && c <= '9')
1182                     val = c - '0';
1183                 else if (c >= 'A' && c <= 'A'+max-10)
1184                     val = c - 'A' + 10;
1185                 else if (c >= 'a' && c <= 'a'+max-10)
1186                     val = c - 'a' + 10;
1187                 else
1188                     termstate = TOPLEVEL;
1189                 osc_string[osc_strlen++] = val;
1190                 if (osc_strlen >= 7) {
1191                     palette_set (osc_string[0],
1192                                  osc_string[1] * 16 + osc_string[2],
1193                                  osc_string[3] * 16 + osc_string[4],
1194                                  osc_string[5] * 16 + osc_string[6]);
1195                     term_invalidate();
1196                     termstate = TOPLEVEL;
1197                 }
1198             }
1199             break;
1200           case SEEN_OSC_W:
1201             switch (c) {
1202               case '\005': case '\007': case '\b': case '\016': case '\017':
1203               case '\033': case 0233: case 0234: case 0235: case '\015':
1204               case '\013': case '\014': case '\012': case '\t':
1205                 termstate = TOPLEVEL;
1206                 goto do_toplevel;      /* hack... */
1207               case '0': case '1': case '2': case '3': case '4':
1208               case '5': case '6': case '7': case '8': case '9':
1209                 esc_args[0] = 10 * esc_args[0] + c - '0';
1210                 break;
1211               default:
1212                 termstate = OSC_STRING;
1213                 osc_strlen = 0;
1214             }
1215             break;
1216           case SEEN_ESCHASH:
1217             if (c == '8') {
1218                 unsigned long *p = scrtop;
1219                 int n = rows * (cols+1);
1220                 while (n--)
1221                     *p++ = ATTR_DEFAULT | 'E';
1222                 disptop = scrtop;
1223                 must_update = TRUE;
1224                 check_selection (scrtop, scrtop + rows * (cols+1));
1225             }
1226             termstate = TOPLEVEL;
1227             break;
1228         }
1229         check_selection (cpos, cpos+1);
1230     }
1231         
1232     if (must_update || nl_count > MAXNL)
1233         term_update();
1234 }
1235
1236 /*
1237  * Compare two lines to determine whether they are sufficiently
1238  * alike to scroll-optimise one to the other. Return the degree of
1239  * similarity.
1240  */
1241 static int linecmp (unsigned long *a, unsigned long *b) {
1242     int i, n;
1243
1244     for (i=n=0; i < cols; i++)
1245         n += (*a++ == *b++);
1246     return n;
1247 }
1248
1249 /*
1250  * Given a context, update the window. Out of paranoia, we don't
1251  * allow WM_PAINT responses to do scrolling optimisations.
1252  */
1253 static void do_paint (Context ctx, int may_optimise){ 
1254     int i, j, start, our_curs_y;
1255     unsigned long attr, rv, cursor;
1256     char ch[1024];
1257
1258     cursor = (has_focus ? ATTR_ACTCURS : ATTR_PASCURS);
1259     rv = (rvideo ? ATTR_REVERSE : 0);
1260     our_curs_y = curs_y + (scrtop - disptop) / (cols+1);
1261
1262     for (i=0; i<rows; i++) {
1263         int idx = i*(cols+1);
1264         for (j=0; j<=cols; j++,idx++) {
1265             unsigned long *d = disptop+idx;
1266             wanttext[idx] = ((*d ^ rv
1267                               ^ (selstart <= d && d < selend ?
1268                                  ATTR_REVERSE : 0)) |
1269                              (i==our_curs_y && j==curs_x ? cursor : 0));
1270         }
1271     }
1272
1273     /*
1274      * We would perform scrolling optimisations in here, if they
1275      * didn't have a nasty tendency to cause the whole sodding
1276      * program to hang for a second at speed-critical moments.
1277      * We'll leave it well alone...
1278      */
1279
1280     for (i=0; i<rows; i++) {
1281         int idx = i*(cols+1);
1282         start = -1;
1283         for (j=0; j<=cols; j++,idx++) {
1284             unsigned long t = wanttext[idx];
1285             int needs_update = (j < cols && t != disptext[idx]);
1286             int keep_going = (start != -1 && needs_update &&
1287                               (t & ATTR_MASK) == attr &&
1288                               j-start < sizeof(ch));
1289             if (start != -1 && !keep_going) {
1290                 do_text (ctx, start, i, ch, j-start, attr);
1291                 start = -1;
1292             }
1293             if (needs_update) {
1294                 if (start == -1) {
1295                     start = j;
1296                     attr = t & ATTR_MASK;
1297                 }
1298                 ch[j-start] = (char) (t & CHAR_MASK);
1299             }
1300             disptext[idx] = t;
1301         }
1302     }
1303 }
1304
1305 /*
1306  * Invalidate the whole screen so it will be repainted in full.
1307  */
1308 void term_invalidate(void) {
1309     int i;
1310
1311     for (i=0; i<rows*(cols+1); i++)
1312         disptext[i] = ATTR_INVALID;
1313 }
1314
1315 /*
1316  * Paint the window in response to a WM_PAINT message.
1317  */
1318 void term_paint (Context ctx, int l, int t, int r, int b) {
1319     int i, j, left, top, right, bottom;
1320
1321     left = l / font_width;
1322     right = (r - 1) / font_width;
1323     top = t / font_height;
1324     bottom = (b - 1) / font_height;
1325     for (i = top; i <= bottom && i < rows ; i++)
1326       for (j = left; j <= right && j < cols ; j++)
1327             disptext[i*(cols+1)+j] = ATTR_INVALID;
1328
1329     do_paint (ctx, FALSE);
1330 }
1331
1332 /*
1333  * Attempt to scroll the scrollback. The second parameter gives the
1334  * position we want to scroll to; the first is +1 to denote that
1335  * this position is relative to the beginning of the scrollback, -1
1336  * to denote it is relative to the end, and 0 to denote that it is
1337  * relative to the current position.
1338  */
1339 void term_scroll (int rel, int where) {
1340     int n = where * (cols+1);
1341 #ifdef OPTIMISE_SCROLL
1342     unsigned long *olddisptop = disptop;
1343     int shift;
1344 #endif /* OPTIMISE_SCROLL */
1345
1346     disptop = (rel < 0 ? scrtop :
1347                rel > 0 ? sbtop : disptop) + n;
1348     if (disptop < sbtop)
1349         disptop = sbtop;
1350     if (disptop > scrtop)
1351         disptop = scrtop;
1352     update_sbar();
1353 #ifdef OPTIMISE_SCROLL
1354     shift = (disptop - olddisptop) / (cols + 1);
1355     if (shift < rows && shift > -rows)
1356         scroll_display(0, rows - 1, shift);
1357 #endif /* OPTIMISE_SCROLL */
1358     term_update();
1359 }
1360
1361 /*
1362  * Spread the selection outwards according to the selection mode.
1363  */
1364 static unsigned long *sel_spread_half (unsigned long *p, int dir) {
1365     unsigned long *linestart, *lineend;
1366     int x;
1367     short wvalue;
1368
1369     x = (p - text) % (cols+1);
1370     linestart = p - x;
1371     lineend = linestart + cols;
1372
1373     switch (selmode) {
1374       case SM_CHAR:
1375         /*
1376          * In this mode, every character is a separate unit, except
1377          * for runs of spaces at the end of a non-wrapping line.
1378          */
1379         if (!(linestart[cols] & ATTR_WRAPPED)) {
1380             unsigned long *q = lineend;
1381             while (q > linestart && (q[-1] & CHAR_MASK) == 0x20)
1382                 q--;
1383             if (q == lineend)
1384                 q--;
1385             if (p >= q)
1386                 p = (dir == -1 ? q : lineend - 1);
1387         }
1388         break;
1389       case SM_WORD:
1390         /*
1391          * In this mode, the units are maximal runs of characters
1392          * whose `wordness' has the same value.
1393          */
1394         wvalue = wordness[*p & CHAR_MASK];
1395         if (dir == +1) {
1396             while (p < lineend && wordness[p[1] & CHAR_MASK] == wvalue)
1397                 p++;
1398         } else {
1399             while (p > linestart && wordness[p[-1] & CHAR_MASK] == wvalue)
1400                 p--;
1401         }
1402         break;
1403       case SM_LINE:
1404         /*
1405          * In this mode, every line is a unit.
1406          */
1407         p = (dir == -1 ? linestart : lineend - 1);
1408         break;
1409     }
1410     return p;
1411 }
1412
1413 static void sel_spread (void) {
1414     selstart = sel_spread_half (selstart, -1);
1415     selend = sel_spread_half (selend - 1, +1) + 1;
1416 }
1417
1418 void term_mouse (Mouse_Button b, Mouse_Action a, int x, int y) {
1419     unsigned long *selpoint;
1420     
1421     if (y<0) y = 0;
1422     if (y>=rows) y = rows-1;
1423     if (x<0) {
1424         if (y > 0) {
1425             x = cols-1;
1426             y--;
1427         } else
1428             x = 0;
1429     }
1430     if (x>=cols) x = cols-1;
1431
1432     selpoint = disptop + y * (cols+1) + x;
1433
1434     if (b == MB_SELECT && a == MA_CLICK) {
1435         deselect();
1436         selstate = ABOUT_TO;
1437         selanchor = selpoint;
1438         selmode = SM_CHAR;
1439     } else if (b == MB_SELECT && (a == MA_2CLK || a == MA_3CLK)) {
1440         deselect();
1441         selmode = (a == MA_2CLK ? SM_WORD : SM_LINE);
1442         selstate = DRAGGING;
1443         selstart = selanchor = selpoint;
1444         selend = selstart + 1;
1445         sel_spread();
1446     } else if ((b == MB_SELECT && a == MA_DRAG) ||
1447                (b == MB_EXTEND && a != MA_RELEASE)) {
1448         if (selstate == ABOUT_TO && selanchor == selpoint)
1449             return;
1450         if (b == MB_EXTEND && a != MA_DRAG && selstate == SELECTED) {
1451             if (selpoint-selstart < (selend-selstart)/2)
1452                 selanchor = selend - 1;
1453             else
1454                 selanchor = selstart;
1455             selstate = DRAGGING;
1456         }
1457         if (selstate != ABOUT_TO && selstate != DRAGGING)
1458             selanchor = selpoint;
1459         selstate = DRAGGING;
1460         if (selpoint < selanchor) {
1461             selstart = selpoint;
1462             selend = selanchor + 1;
1463         } else {
1464             selstart = selanchor;
1465             selend = selpoint + 1;
1466         }
1467         sel_spread();
1468     } else if ((b == MB_SELECT || b == MB_EXTEND) && a == MA_RELEASE) {
1469         if (selstate == DRAGGING) {
1470             /*
1471              * We've completed a selection. We now transfer the
1472              * data to the clipboard.
1473              */
1474             unsigned char *p = selspace;
1475             unsigned long *q = selstart;
1476
1477             while (q < selend) {
1478                 int nl = FALSE;
1479                 unsigned long *lineend = q - (q-text) % (cols+1) + cols;
1480                 unsigned long *nlpos = lineend;
1481
1482                 if (!(*nlpos & ATTR_WRAPPED)) {
1483                     while ((nlpos[-1] & CHAR_MASK) == 0x20 && nlpos > q)
1484                         nlpos--;
1485                     if (nlpos < selend)
1486                         nl = TRUE;
1487                 }
1488                 while (q < nlpos && q < selend)
1489                     *p++ = (unsigned char) (*q++ & CHAR_MASK);
1490                 if (nl) {
1491                     int i;
1492                     for (i=0; i<sizeof(sel_nl); i++)
1493                         *p++ = sel_nl[i];
1494                 }
1495                 q = lineend + 1;       /* start of next line */
1496             }
1497             write_clip (selspace, p - selspace);
1498             selstate = SELECTED;
1499         } else
1500             selstate = NO_SELECTION;
1501     } else if (b == MB_PASTE && (a==MA_CLICK || a==MA_2CLK || a==MA_3CLK)) {
1502         char *data;
1503         int len;
1504
1505         get_clip((void **) &data, &len);
1506         if (data) {
1507             char *p, *q;
1508             p = q = data;
1509             while (p < data+len) {
1510                 while (p < data+len &&
1511                        !(p <= data+len-sizeof(sel_nl) &&
1512                          !memcmp(p, sel_nl, sizeof(sel_nl))))
1513                     p++;
1514                 back->send (q, p-q);
1515                 if (p <= data+len-sizeof(sel_nl) &&
1516                     !memcmp(p, sel_nl, sizeof(sel_nl))) {
1517                     back->send ("\015", 1);
1518                     p += sizeof(sel_nl);
1519                 }
1520                 q = p;
1521             }
1522         }
1523         get_clip(NULL, NULL);
1524     }
1525
1526     term_update();
1527 }
1528
1529 static void deselect (void) {
1530     selstate = NO_SELECTION;
1531     selstart = selend = scrtop;
1532 }
1533
1534 void term_deselect (void) {
1535     deselect();
1536     term_update();
1537 }