Word-by-word (double-click) selection now spans line breaks if the
[u/mdw/putty] / terminal.c
index ab97e3c..1e2d50c 100644 (file)
@@ -63,7 +63,7 @@ static unsigned long curstype;               /* type of cursor on real screen */
 
 struct beeptime {
     struct beeptime *next;
-    long ticks;
+    unsigned long ticks;
 };
 static struct beeptime *beephead, *beeptail;
 int nbeeps;
@@ -86,6 +86,7 @@ typedef struct {
 #define incpos(p) ( (p).x == cols ? ((p).x = 0, (p).y++, 1) : ((p).x++, 0) )
 #define decpos(p) ( (p).x == 0 ? ((p).x = cols, (p).y--, 1) : ((p).x--, 0) )
 
+static bufchain inbuf;                /* terminal input buffer */
 static pos curs;                      /* cursor */
 static pos savecurs;                  /* saved cursor position */
 static int marg_t, marg_b;            /* scroll margins */
@@ -96,7 +97,7 @@ static int cset;                     /* 0 or 1: which char set */
 static int save_cset, save_csattr;     /* saved with cursor position */
 static int save_utf;                  /* saved with cursor position */
 static int rvideo;                    /* global reverse video flag */
-static int rvbell_timeout;            /* for ESC[?5hESC[?5l vbell */
+static unsigned long rvbell_startpoint;/* for ESC[?5hESC[?5l vbell */
 static int cursor_on;                 /* cursor enabled flag */
 static int reset_132;                 /* Flag ESC c resets to 80 cols */
 static int use_bce;                   /* Use Background coloured erase */
@@ -291,7 +292,7 @@ static void power_on(void)
     big_cursor = 0;
     save_attr = curr_attr = ATTR_DEFAULT;
     term_editing = term_echoing = FALSE;
-    ldisc_send(NULL, 0);              /* cause ldisc to notice changes */
+    ldisc_send(NULL, 0, 0);           /* cause ldisc to notice changes */
     app_cursor_keys = cfg.app_cursor;
     app_keypad_keys = cfg.app_keypad;
     use_bce = cfg.bce;
@@ -319,13 +320,15 @@ void term_update(void)
     Context ctx;
     ctx = get_ctx();
     if (ctx) {
-       if (seen_disp_event)
-           update_sbar();
+       int need_sbar_update = seen_disp_event;
        if ((seen_key_event && (cfg.scroll_on_key)) ||
            (seen_disp_event && (cfg.scroll_on_disp))) {
            disptop = 0;               /* return to main screen */
            seen_disp_event = seen_key_event = 0;
+           need_sbar_update = TRUE;
        }
+       if (need_sbar_update)
+           update_sbar();
        do_paint(ctx, TRUE);
        sys_cursor(curs.x, curs.y - disptop);
        free_ctx(ctx);
@@ -668,8 +671,12 @@ static void scroll(int topline, int botline, int lines, int sb)
             * of course, if the line _hasn't_ moved into the
             * scrollback then we don't do this, and cut them off
             * at the top of the scroll region.
+            * 
+            * This applies to selstart and selend (for an existing
+            * selection), and also selanchor (for one being
+            * selected as we speak).
             */
-           seltop = sb ? -savelines : 0;
+           seltop = sb ? -savelines : topline;
 
            if (selstart.y >= seltop && selstart.y <= botline) {
                selstart.y--;
@@ -685,6 +692,13 @@ static void scroll(int topline, int botline, int lines, int sb)
                    selend.x = 0;
                }
            }
+           if (selanchor.y >= seltop && selanchor.y <= botline) {
+               selanchor.y--;
+               if (selanchor.y < seltop) {
+                   selanchor.y = seltop;
+                   selanchor.x = 0;
+               }
+           }
 
            lines--;
        }
@@ -834,7 +848,7 @@ static void insch(int n)
  */
 static void toggle_mode(int mode, int query, int state)
 {
-    long ticks;
+    unsigned long ticks;
 
     if (query)
        switch (mode) {
@@ -863,14 +877,20 @@ static void toggle_mode(int mode, int query, int state)
             * always be an actually _visible_ visual bell.
             */
            ticks = GetTickCount();
-           if (rvideo && !state &&    /* we're turning it off */
-               ticks < rvbell_timeout) {       /* and it's not long since it was turned on */
+           /* turn off a previous vbell to avoid inconsistencies */
+           if (ticks - vbell_startpoint >= VBELL_TIMEOUT)
+               in_vbell = FALSE;
+           if (rvideo && !state &&    /* we're turning it off... */
+               (ticks - rvbell_startpoint) < VBELL_TIMEOUT) {  /* ...soon */
+               /* If there's no vbell timeout already, or this one lasts
+                * longer, replace vbell_timeout with ours. */
+               if (!in_vbell ||
+                   (rvbell_startpoint - vbell_startpoint < VBELL_TIMEOUT))
+                   vbell_startpoint = rvbell_startpoint;
                in_vbell = TRUE;       /* we may clear rvideo but we set in_vbell */
-               if (vbell_timeout < rvbell_timeout)     /* don't move vbell end forward */
-                   vbell_timeout = rvbell_timeout;     /* vbell end is at least then */
            } else if (!rvideo && state) {
                /* This is an ON, so we notice the time and save it. */
-               rvbell_timeout = ticks + VBELL_TIMEOUT;
+               rvbell_startpoint = ticks;
            }
            rvideo = state;
            seen_disp_event = TRUE;
@@ -888,7 +908,7 @@ static void toggle_mode(int mode, int query, int state)
            break;
          case 10:                     /* set local edit mode */
            term_editing = state;
-           ldisc_send(NULL, 0);       /* cause ldisc to notice changes */
+           ldisc_send(NULL, 0, 0);    /* cause ldisc to notice changes */
            break;
          case 25:                     /* enable/disable cursor */
            compatibility2(OTHER, VT220);
@@ -917,7 +937,7 @@ static void toggle_mode(int mode, int query, int state)
            break;
          case 12:                     /* set echo mode */
            term_echoing = !state;
-           ldisc_send(NULL, 0);       /* cause ldisc to notice changes */
+           ldisc_send(NULL, 0, 0);    /* cause ldisc to notice changes */
            break;
          case 20:                     /* Return sends ... */
            cr_lf_return = state;
@@ -960,20 +980,38 @@ static void do_osc(void)
  */
 void term_out(void)
 {
-    int c, inbuf_reap;
+    int c, unget;
+    unsigned char localbuf[256], *chars;
+    int nchars = 0;
+
+    unget = -1;
+
+    while (nchars > 0 || bufchain_size(&inbuf) > 0) {
+       if (unget == -1) {
+           if (nchars == 0) {
+               void *ret;
+               bufchain_prefix(&inbuf, &ret, &nchars);
+               if (nchars > sizeof(localbuf))
+                   nchars = sizeof(localbuf);
+               memcpy(localbuf, ret, nchars);
+               bufchain_consume(&inbuf, nchars);
+               chars = localbuf;
+               assert(chars != NULL);
+           }
+           c = *chars++;
+           nchars--;
 
-    /*
-     * Optionally log the session traffic to a file. Useful for
-     * debugging and possibly also useful for actual logging.
-     */
-    if (cfg.logtype == LGTYP_DEBUG)
-       for (inbuf_reap = 0; inbuf_reap < inbuf_head; inbuf_reap++) {
-           logtraffic((unsigned char) inbuf[inbuf_reap], LGTYP_DEBUG);
+           /*
+            * Optionally log the session traffic to a file. Useful for
+            * debugging and possibly also useful for actual logging.
+            */
+           if (cfg.logtype == LGTYP_DEBUG)
+               logtraffic((unsigned char) c, LGTYP_DEBUG);
+       } else {
+           c = unget;
+           unget = -1;
        }
 
-    for (inbuf_reap = 0; inbuf_reap < inbuf_head; inbuf_reap++) {
-       c = inbuf[inbuf_reap];
-
        /* Note only VT220+ are 8-bit VT102 is seven bit, it shouldn't even
         * be able to display 8-bit characters, but I'll let that go 'cause
         * of i18n.
@@ -1016,7 +1054,7 @@ void term_out(void)
                  case 4:
                  case 5:
                    if ((c & 0xC0) != 0x80) {
-                       inbuf_reap--;
+                       unget = c;
                        c = UCSERR;
                        utf_state = 0;
                        break;
@@ -1156,13 +1194,13 @@ void term_out(void)
                        } else
                            *d++ = *s;
                    }
-                   lpage_send(CP_ACP, abuf, d - abuf);
+                   lpage_send(CP_ACP, abuf, d - abuf, 0);
                }
                break;
              case '\007':
                {
                    struct beeptime *newbeep;
-                   long ticks;
+                   unsigned long ticks;
 
                    ticks = GetTickCount();
 
@@ -1193,7 +1231,7 @@ void term_out(void)
                    }
 
                    if (cfg.bellovl && beep_overloaded &&
-                       ticks - lastbeep >= cfg.bellovl_s) {
+                       ticks - lastbeep >= (unsigned)cfg.bellovl_s) {
                        /*
                         * If we're currently overloaded and the
                         * last beep was more than s seconds ago,
@@ -1218,7 +1256,7 @@ void term_out(void)
                        beep(cfg.beep);
                        if (cfg.beep == BELL_VISUAL) {
                            in_vbell = TRUE;
-                           vbell_timeout = ticks + VBELL_TIMEOUT;
+                           vbell_startpoint = ticks;
                            term_update();
                        }
                    }
@@ -1462,7 +1500,7 @@ void term_out(void)
                    break;
                  case 'Z':            /* terminal type query */
                    compatibility(VT100);
-                   ldisc_send(id_string, strlen(id_string));
+                   ldisc_send(id_string, strlen(id_string), 0);
                    break;
                  case 'c':            /* restore power-on settings */
                    compatibility(VT100);
@@ -1614,7 +1652,7 @@ void term_out(void)
                        compatibility(OTHER);
                        /* this reports xterm version 136 so that VIM can
                           use the drag messages from the mouse reporting */
-                       ldisc_send("\033[>0;136;0c", 11);
+                       ldisc_send("\033[>0;136;0c", 11, 0);
                        break;
                      case 'a':       /* move right N cols */
                        compatibility(ANSI);
@@ -1710,16 +1748,16 @@ void term_out(void)
                      case 'c':       /* terminal type query */
                        compatibility(VT100);
                        /* This is the response for a VT102 */
-                       ldisc_send(id_string, strlen(id_string));
+                       ldisc_send(id_string, strlen(id_string), 0);
                        break;
                      case 'n':       /* cursor position query */
                        if (esc_args[0] == 6) {
                            char buf[32];
                            sprintf(buf, "\033[%d;%dR", curs.y + 1,
                                    curs.x + 1);
-                           ldisc_send(buf, strlen(buf));
+                           ldisc_send(buf, strlen(buf), 0);
                        } else if (esc_args[0] == 5) {
-                           ldisc_send("\033[0n", 4);
+                           ldisc_send("\033[0n", 4, 0);
                        }
                        break;
                      case 'h':       /* toggle modes to high */
@@ -1979,7 +2017,7 @@ void term_out(void)
                            if (i == 0 || i == 1) {
                                strcpy(buf, "\033[2;1;1;112;112;1;0x");
                                buf[2] += i;
-                               ldisc_send(buf, 20);
+                               ldisc_send(buf, 20, 0);
                            }
                        }
                        break;
@@ -2296,7 +2334,7 @@ void term_out(void)
                    termstate = VT52_Y1;
                    break;
                  case 'Z':
-                   ldisc_send("\033/Z", 3);
+                   ldisc_send("\033/Z", 3, 0);
                    break;
                  case '=':
                    app_keypad_keys = TRUE;
@@ -2467,7 +2505,6 @@ void term_out(void)
            check_selection(curs, cursplus);
        }
     }
-    inbuf_head = 0;
 }
 
 #if 0
@@ -2497,16 +2534,16 @@ static void do_paint(Context ctx, int may_optimise)
     pos scrpos;
     char ch[1024];
     long cursor_background = ERASE_CHAR;
-    long ticks;
+    unsigned long ticks;
 
     /*
      * Check the visual bell state.
      */
     if (in_vbell) {
        ticks = GetTickCount();
-       if (ticks - vbell_timeout >= 0)
-           in_vbell = FALSE;
-    }
+       if (ticks - vbell_startpoint >= VBELL_TIMEOUT)
+           in_vbell = FALSE; 
+   }
 
     rv = (!rvideo ^ !in_vbell ? ATTR_REVERSE : 0);
 
@@ -3030,6 +3067,7 @@ static pos sel_spread_half(pos p, int dir)
 {
     unsigned long *ldata;
     short wvalue;
+    int topy = -count234(scrollback);
 
     ldata = lineptr(p.y);
 
@@ -3056,11 +3094,47 @@ static pos sel_spread_half(pos p, int dir)
         */
        wvalue = wordtype(ldata[p.x]);
        if (dir == +1) {
-           while (p.x < cols && wordtype(ldata[p.x + 1]) == wvalue)
-               p.x++;
+           while (1) {
+               if (p.x < cols-1) {
+                   if (wordtype(ldata[p.x + 1]) == wvalue)
+                       p.x++;
+                   else
+                       break;
+               } else {
+                   if (ldata[cols] & LATTR_WRAPPED) {
+                       unsigned long *ldata2;
+                       ldata2 = lineptr(p.y+1);
+                       if (wordtype(ldata2[0]) == wvalue) {
+                           p.x = 0;
+                           p.y++;
+                           ldata = ldata2;
+                       } else
+                           break;
+                   } else
+                       break;
+               }
+           }
        } else {
-           while (p.x > 0 && wordtype(ldata[p.x - 1]) == wvalue)
-               p.x--;
+           while (1) {
+               if (p.x > 0) {
+                   if (wordtype(ldata[p.x - 1]) == wvalue)
+                       p.x--;
+                   else
+                       break;
+               } else {
+                   unsigned long *ldata2;
+                   if (p.y <= topy)
+                       break;
+                   ldata2 = lineptr(p.y-1);
+                   if ((ldata2[cols] & LATTR_WRAPPED) &&
+                       wordtype(ldata2[cols-1]) == wvalue) {
+                       p.x = cols-1;
+                       p.y--;
+                       ldata = ldata2;
+                   } else
+                       break;
+               }
+           }
        }
        break;
       case SM_LINE:
@@ -3119,7 +3193,7 @@ void term_do_paste(void)
 
         /* Assume a small paste will be OK in one go. */
         if (paste_len < 256) {
-            luni_send(paste_buffer, paste_len);
+            luni_send(paste_buffer, paste_len, 0);
             if (paste_buffer)
                 sfree(paste_buffer);
             paste_buffer = 0;
@@ -3210,7 +3284,7 @@ void term_mouse(Mouse_Button b, Mouse_Action a, int x, int y,
        c = x + 33;
 
        sprintf(abuf, "\033[M%c%c%c", encstate, c, r);
-       ldisc_send(abuf, 6);
+       ldisc_send(abuf, 6, 0);
        return;
     }
 
@@ -3306,7 +3380,7 @@ void term_paste()
            if (paste_buffer[paste_pos + n++] == '\r')
                break;
        }
-       luni_send(paste_buffer + paste_pos, n);
+       luni_send(paste_buffer + paste_pos, n, 0);
        paste_pos += n;
 
        if (paste_pos < paste_len) {
@@ -3345,18 +3419,15 @@ int term_ldisc(int option)
  */
 int from_backend(int is_stderr, char *data, int len)
 {
-    while (len--) {
-       if (inbuf_head >= INBUF_SIZE)
-           term_out();
-       inbuf[inbuf_head++] = *data++;
-    }
+    bufchain_add(&inbuf, data, len);
 
     /*
-     * We process all stdout/stderr data immediately we receive it,
-     * and don't return until it's all gone. Therefore, there's no
-     * reason at all to return anything other than zero from this
-     * function.
-     * 
+     * term_out() always completely empties inbuf. Therefore,
+     * there's no reason at all to return anything other than zero
+     * from this function, because there _can't_ be a question of
+     * the remote side needing to wait until term_out() has cleared
+     * a backlog.
+     *
      * This is a slightly suboptimal way to deal with SSH2 - in
      * principle, the window mechanism would allow us to continue
      * to accept data on forwarded ports and X connections even
@@ -3366,7 +3437,7 @@ int from_backend(int is_stderr, char *data, int len)
      * portability. So we manage stdout buffering the old SSH1 way:
      * if the terminal processing goes slowly, the whole SSH
      * connection stops accepting data until it's ready.
-     * 
+     *
      * In practice, I can't imagine this causing serious trouble.
      */
     return 0;