X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/putty/blobdiff_plain/66ee282adab99e6ef6ab7e09700611c89d87c97c..42856df411d7491a1e40c4d21c985b03a6206e22:/terminal.c diff --git a/terminal.c b/terminal.c index 9fc1787b..a65d7157 100644 --- a/terminal.c +++ b/terminal.c @@ -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 */ @@ -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); @@ -497,6 +500,7 @@ void term_size(int newrows, int newcols, int newsavelines) update_sbar(); term_update(); + back->size(); } /* @@ -587,7 +591,7 @@ static void check_selection(pos from, pos to) static void scroll(int topline, int botline, int lines, int sb) { unsigned long *line, *line2; - int i; + int i, seltop; if (topline != 0 || alt_which != 0) sb = FALSE; @@ -637,6 +641,23 @@ static void scroll(int topline, int botline, int lines, int sb) } addpos234(scrollback, line, sblen); line = line2; + + /* + * If the user is currently looking at part of the + * scrollback, and they haven't enabled any options + * that are going to reset the scrollback as a + * result of this movement, then the chances are + * they'd like to keep looking at the same line. So + * we move their viewpoint at the same rate as the + * scroll, at least until their viewpoint hits the + * top end of the scrollback buffer, at which point + * we don't have the choice any more. + * + * Thanks to Jan Holmen Holsten for the idea and + * initial implementation. + */ + if (disptop > -savelines && disptop < 0) + disptop--; } line = resizeline(line, cols); for (i = 0; i < cols; i++) @@ -644,20 +665,40 @@ static void scroll(int topline, int botline, int lines, int sb) line[cols + 1] = 0; addpos234(screen, line, botline); - if (selstart.y >= topline && selstart.y <= botline) { + /* + * If the selection endpoints move into the scrollback, + * we keep them moving until they hit the top. However, + * 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; + + if (selstart.y >= seltop && selstart.y <= botline) { selstart.y--; - if (selstart.y < topline) { - selstart.y = topline; + if (selstart.y < seltop) { + selstart.y = seltop; selstart.x = 0; } } - if (selend.y >= topline && selend.y <= botline) { + if (selend.y >= seltop && selend.y <= botline) { selend.y--; - if (selend.y < topline) { - selend.y = topline; + if (selend.y < seltop) { + selend.y = seltop; selend.x = 0; } } + if (selanchor.y >= seltop && selanchor.y <= botline) { + selanchor.y--; + if (selanchor.y < seltop) { + selanchor.y = seltop; + selanchor.x = 0; + } + } lines--; } @@ -825,7 +866,7 @@ static void toggle_mode(int mode, int query, int state) break; case 3: /* 80/132 columns */ deselect(); - request_resize(state ? 132 : 80, rows, 1); + request_resize(state ? 132 : 80, rows); reset_132 = state; break; case 5: /* reverse video */ @@ -861,7 +902,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); @@ -890,7 +931,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; @@ -933,20 +974,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. @@ -989,7 +1048,7 @@ void term_out(void) case 4: case 5: if ((c & 0xC0) != 0x80) { - inbuf_reap--; + unget = c; c = UCSERR; utf_state = 0; break; @@ -1129,7 +1188,7 @@ 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': @@ -1313,12 +1372,18 @@ void term_out(void) width = wcwidth((wchar_t) c); switch (width) { case 2: - if (curs.x + 1 != cols) { - *cpos++ = c | ATTR_WIDE | curr_attr; - *cpos++ = UCSWIDE | curr_attr; - curs.x++; - break; + *cpos++ = c | curr_attr; + if (++curs.x == cols) { + *cpos |= LATTR_WRAPPED; + if (curs.y == marg_b) + scroll(marg_t, marg_b, 1, TRUE); + else if (curs.y < rows - 1) + curs.y++; + curs.x = 0; + fix_cpos; } + *cpos++ = UCSWIDE | curr_attr; + break; case 1: *cpos++ = c | curr_attr; break; @@ -1429,13 +1494,13 @@ 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); power_on(); if (reset_132) { - request_resize(80, rows, 1); + request_resize(80, rows); reset_132 = 0; } fix_cpos; @@ -1581,7 +1646,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); @@ -1677,16 +1742,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 */ @@ -1880,7 +1945,7 @@ void term_out(void) compatibility(VT340TEXT); if (esc_nargs <= 1 && (esc_args[0] < 1 || esc_args[0] >= 24)) { - request_resize(cols, def(esc_args[0], 24), 0); + request_resize(cols, def(esc_args[0], 24)); deselect(); } break; @@ -1906,9 +1971,7 @@ void term_out(void) */ compatibility(VT420); if (esc_nargs == 1 && esc_args[0] > 0) { - request_resize(cols, - def(esc_args[0], cfg.height), - 0); + request_resize(cols, def(esc_args[0], cfg.height)); deselect(); } break; @@ -1919,8 +1982,7 @@ void term_out(void) */ compatibility(VT340TEXT); if (esc_nargs <= 1) { - request_resize(def(esc_args[0], cfg.width), - rows, 0); + request_resize(def(esc_args[0], cfg.width), rows); deselect(); } break; @@ -1949,7 +2011,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; @@ -2048,9 +2110,9 @@ void term_out(void) */ if (!has_compat(VT420) && has_compat(VT100)) { if (reset_132) - request_resize(132, 24, 1); + request_resize(132, 24); else - request_resize(80, 24, 1); + request_resize(80, 24); } #endif break; @@ -2266,7 +2328,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; @@ -2437,7 +2499,6 @@ void term_out(void) check_selection(curs, cursplus); } } - inbuf_head = 0; } #if 0 @@ -2505,9 +2566,10 @@ static void do_paint(Context ctx, int may_optimise) if (dispcurs && (curstype != cursor || dispcurs != disptext + our_curs_y * (cols + 1) + curs.x)) { - if (dispcurs > disptext && (dispcurs[-1] & ATTR_WIDE)) + if (dispcurs > disptext && + (*dispcurs & (CHAR_MASK | CSET_MASK)) == UCSWIDE) dispcurs[-1] |= ATTR_INVALID; - if ((*dispcurs & ATTR_WIDE)) + if ( (dispcurs[1] & (CHAR_MASK | CSET_MASK)) == UCSWIDE) dispcurs[1] |= ATTR_INVALID; *dispcurs |= ATTR_INVALID; curstype = 0; @@ -2554,6 +2616,8 @@ static void do_paint(Context ctx, int may_optimise) } tattr |= (tchar & CSET_MASK); tchar &= CHAR_MASK; + if ((d[1] & (CHAR_MASK | CSET_MASK)) == UCSWIDE) + tattr |= ATTR_WIDE; /* Video reversing things */ tattr = (tattr ^ rv @@ -2570,6 +2634,17 @@ static void do_paint(Context ctx, int may_optimise) tattr &= ~ATTR_BLINK; } + /* + * Check the font we'll _probably_ be using to see if + * the character is wide when we don't want it to be. + */ + if ((tchar | tattr) != (disptext[idx]& ~ATTR_NARROW)) { + if ((tattr & ATTR_WIDE) == 0 && + CharWidth(ctx, (tchar | tattr) & 0xFFFF) == 2) + tattr |= ATTR_NARROW; + } else if (disptext[idx]&ATTR_NARROW) + tattr |= ATTR_NARROW; + /* Cursor here ? Save the 'background' */ if (i == our_curs_y && j == curs.x) { cursor_background = tattr | tchar; @@ -2690,14 +2765,14 @@ void term_invalidate(void) /* * Paint the window in response to a WM_PAINT message. */ -void term_paint(Context ctx, int l, int t, int r, int b) +void term_paint(Context ctx, int left, int top, int right, int bottom) { - int i, j, left, top, right, bottom; + int i, j; + if (left < 0) left = 0; + if (top < 0) top = 0; + if (right >= cols) right = cols-1; + if (bottom >= rows) bottom = rows-1; - left = l / font_width; - right = (r - 1) / font_width; - top = t / font_height; - bottom = (b - 1) / font_height; for (i = top; i <= bottom && i < rows; i++) { if ((disptext[i * (cols + 1) + cols] & LATTR_MODE) == LATTR_NORM) for (j = left; j <= right && j < cols; j++) @@ -2710,8 +2785,9 @@ void term_paint(Context ctx, int l, int t, int r, int b) /* This should happen soon enough, also for some reason it sometimes * fails to actually do anything when re-sizing ... painting the wrong * window perhaps ? - do_paint (ctx, FALSE); */ + if (alt_pressed) + do_paint (ctx, FALSE); } /* @@ -2961,6 +3037,12 @@ static int wordtype(int uc) break; } + /* For DBCS font's I can't do anything usefull. Even this will sometimes + * fail as there's such a thing as a double width space. :-( + */ + if (dbcs_screenfont && font_codepage == line_codepage) + return (uc != ' '); + if (uc < 0x80) return wordness[uc]; @@ -3068,7 +3150,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; @@ -3159,7 +3241,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; } @@ -3255,7 +3337,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) { @@ -3294,18 +3376,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 @@ -3315,7 +3394,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;