/* * Shell mode for QEmacs. * * Copyright (c) 2001-2002 Fabrice Bellard. * Copyright (c) 2002-2017 Charlie Gordon. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "qe.h" /* XXX: status line */ /* XXX: better tab handling */ /* XXX: send real cursor position (CSI n) */ static ModeDef shell_mode, pager_mode; #define MAX_CSI_PARAMS 16 enum QETermState { QE_TERM_STATE_NORM, QE_TERM_STATE_UTF8, QE_TERM_STATE_ESC, QE_TERM_STATE_ESC2, QE_TERM_STATE_CSI, QE_TERM_STATE_STRING, }; typedef struct ShellState { QEModeData base; /* buffer state */ int cols, rows; int use_alternate_screen; int alternate_screen_top; int scroll_top, scroll_bottom; /* scroll region (top included, bottom excluded) */ int pty_fd; int pid; /* -1 if not launched */ unsigned int attr, fgcolor, bgcolor, reverse; int cur_offset; /* current offset at position x, y */ int cur_prompt; /* offset of end of prompt on current line */ int save_x, save_y; int nb_params; int params[MAX_CSI_PARAMS + 1]; int state; int esc1, esc2, lastc; int shifted; int cset, charset[2]; int grab_keys; unsigned char term_buf[256]; int term_len, term_pos; int utf8_len; EditBuffer *b; EditBuffer *b_color; /* color buffer, one byte per char */ struct QEmacsState *qe_state; const char *ka1, *ka3, *kb2, *kc1, *kc3, *kcbt, *kspd; const char *kbeg, *kbs, *kent, *kdch1, *kich1; const char *kcub1, *kcud1, *kcuf1, *kcuu1; const char *kf1, *kf2, *kf3, *kf4, *kf5; const char *kf6, *kf7, *kf8, *kf9, *kf10; const char *kf11, *kf12, *kf13, *kf14, *kf15; const char *kf16, *kf17, *kf18, *kf19, *kf20; const char *khome, *kend, *kmous, *knp, *kpp; const char *caption; /* process caption for exit message */ int shell_flags; int last_char; /* last char sent to the process */ char curpath[MAX_FILENAME_SIZE]; /* should keep a list with validity ranges */ } ShellState; /* CG: these variables should be encapsulated in a global structure */ static char error_buffer[MAX_BUFFERNAME_SIZE]; static int error_offset = -1; static int error_line_num = -1; static int error_col_num = -1; static char error_filename[MAX_FILENAME_SIZE]; static char *shell_get_curpath(EditBuffer *b, int offset, char *buf, int buf_size); static void set_error_offset(EditBuffer *b, int offset) { pstrcpy(error_buffer, sizeof(error_buffer), b ? b->name : ""); error_offset = offset - 1; error_line_num = error_col_num = -1; *error_filename = '\0'; } #define PTYCHAR1 "pqrstuvwxyzabcde" #define PTYCHAR2 "0123456789abcdef" /* allocate one pty/tty pair */ static int get_pty(char *tty_str, int size) { int fd; char ptydev[] = "/dev/pty??"; char ttydev[] = "/dev/tty??"; int len = strlen(ttydev); const char *c1, *c2; #ifdef CONFIG_PTSNAME /* First try Unix98 pseudo tty master */ /* CG: should check if posix_openpt is more appropriate than /dev/ptmx */ //fd = posix_openpt(O_RDWR | O_NOCTTY); fd = open("/dev/ptmx", O_RDWR); if (fd >= 0) { #if 0 /* ptsname_r is a sensible renentrant version of ptsname, but * it lacks portability, notably on OpenBSD and cygwin. So we * have to use ill conceived ptsname. */ if (!ptsname_r(fd, tty_str, size) && !grantpt(fd) && !unlockpt(fd)) return fd; #else const char *name = ptsname(fd); if (name) { pstrcpy(tty_str, size, name); if (!grantpt(fd) && !unlockpt(fd)) return fd; } #endif close(fd); } #endif /* then try BSD pseudo tty pre-created pairs */ for (c1 = PTYCHAR1; *c1; c1++) { ptydev[len-2] = ttydev[len-2] = *c1; for (c2 = PTYCHAR2; *c2; c2++) { ptydev[len-1] = ttydev[len-1] = *c2; if ((fd = open(ptydev, O_RDWR)) >= 0) { if (access(ttydev, R_OK|W_OK) == 0) { pstrcpy(tty_str, size, ttydev); return fd; } close(fd); } } } return -1; } const char *get_shell(void) { const char *shell_path; /* find shell name */ shell_path = getenv("SHELL"); if (!shell_path) shell_path = "/bin/sh"; return shell_path; } #define QE_TERM_XSIZE 80 #define QE_TERM_YSIZE 25 #define QE_TERM_YSIZE_INFINITE 10000 static int run_process(const char *cmd, int *fd_ptr, int *pid_ptr, int cols, int rows, const char *path, int shell_flags) { int pty_fd, pid, i, nb_fds; char tty_name[MAX_FILENAME_SIZE]; char lines_string[20]; char columns_string[20]; struct winsize ws; pty_fd = get_pty(tty_name, sizeof(tty_name)); if (pty_fd < 0) { put_status(NULL, "run_process: cannot get tty: %s", strerror(errno)); return -1; } fcntl(pty_fd, F_SETFL, O_NONBLOCK); /* set dummy screen size */ ws.ws_col = cols; ws.ws_row = rows; ws.ws_xpixel = ws.ws_col; ws.ws_ypixel = ws.ws_row; ioctl(pty_fd, TIOCSWINSZ, &ws); pid = fork(); if (pid < 0) { put_status(NULL, "run_process: cannot fork"); return -1; } if (pid == 0) { /* child process */ const char *argv[4]; int argc = 0; int fd0, fd1, fd2; argv[argc++] = get_shell(); if (cmd) { argv[argc++] = "-c"; argv[argc++] = cmd; } argv[argc] = NULL; /* detach controlling terminal */ #ifndef CONFIG_DARWIN setsid(); #endif /* close all files */ nb_fds = getdtablesize(); for (i = 0; i < nb_fds; i++) close(i); /* open pseudo tty for standard I/O */ if (shell_flags & SF_INTERACTIVE) { /* interactive shell: input from / output to pseudo terminal */ fd0 = open(tty_name, O_RDWR); fd1 = dup(0); fd2 = dup(0); } else { /* collect output from non interactive process: no input */ fd0 = open("/dev/null", O_RDONLY); fd1 = open(tty_name, O_RDWR); fd2 = dup(1); } if (fd0 != 0 || fd1 != 1 || fd2 != 2) { setenv("QE-STATUS", "invalid handles", 1); } #ifdef CONFIG_DARWIN setsid(); #endif if (shell_flags & SF_INFINITE) { rows += QE_TERM_YSIZE_INFINITE; } snprintf(lines_string, sizeof lines_string, "%d", rows); snprintf(columns_string, sizeof columns_string, "%d", cols); setenv("LINES", lines_string, 1); setenv("COLUMNS", columns_string, 1); setenv("TERM", "xterm-256color", 1); unsetenv("PAGER"); //setenv("QELEVEL", "1", 1); if (path) { if (chdir(path)) { setenv("QE-STATUS", "cannot chdir", 1); } } execv(argv[0], (char * const*)argv); exit(1); } /* return file info */ *fd_ptr = pty_fd; *pid_ptr = pid; return 0; } /* VT100 emulation */ static void qe_trace_term(ShellState *s, const char *msg) { eb_trace_bytes(msg, -1, EB_TRACE_FLUSH | EB_TRACE_EMULATE); eb_trace_bytes(": ", -1, EB_TRACE_EMULATE); eb_trace_bytes(s->term_buf, s->term_len, EB_TRACE_EMULATE); } #define TRACE_MSG(s, m) qe_trace_term(s, m) static void qe_term_init(ShellState *s) { char *term; s->state = QE_TERM_STATE_NORM; /* Should compute def_color from shell default style at display * time and force full redisplay upon style change. */ s->fgcolor = QE_TERM_DEF_FG; s->bgcolor = QE_TERM_DEF_BG; s->attr = 0; s->reverse = 0; s->lastc = ' '; term = getenv("TERM"); /* vt100 terminfo definitions */ s->kbs = "\010"; s->ka1 = "\033Oq"; s->ka3 = "\033Os"; s->kb2 = "\033Or"; s->kc1 = "\033Op"; s->kc3 = "\033On"; s->kcub1 = "\033OD"; s->kcud1 = "\033OB"; s->kcuf1 = "\033OC"; s->kcuu1 = "\033OA"; s->kent = "\033OM"; s->kf1 = "\033OP"; s->kf2 = "\033OQ"; s->kf3 = "\033OR"; s->kf4 = "\033OS"; s->kf5 = "\033Ot"; s->kf6 = "\033Ou"; s->kf7 = "\033Ov"; s->kf8 = "\033Ol"; s->kf9 = "\033Ow"; s->kf10 = "\033Ox"; /* ansi terminfo definitions */ if (strstart(term, "ansi", NULL)) { s->kbs = "\010"; s->kcbt = "\033[Z"; s->kcub1 = "\033[D"; s->kcud1 = "\033[B"; s->kcuf1 = "\033[C"; s->kcuu1 = "\033[A"; s->khome = "\033[H"; s->kich1 = "\033[L"; } if (strstart(term, "vt220", NULL)) { s->kcub1 = "\033[D"; s->kcud1 = "\033[B"; s->kcuf1 = "\033[C"; s->kcuu1 = "\033[A"; s->kdch1 = "\033[3~"; s->kend = "\033[4~"; s->khome = "\033[1~"; s->kich1 = "\033[2~"; s->knp = "\033[6~"; s->kpp = "\033[5~"; s->kf1 = "\033OP"; s->kf2 = "\033OQ"; s->kf3 = "\033OR"; s->kf4 = "\033OS"; s->kf5 = "\033[17~"; s->kf6 = "\033[18~"; s->kf7 = "\033[19~"; s->kf8 = "\033[20~"; s->kf9 = "\033[21~"; s->kf10 = "\033[29~"; } if (strstart(term, "cygwin", NULL)) { s->kbs = "\10"; goto linux_cygwin; } if (strstart(term, "linux", NULL)) { s->kbs = "\177"; s->kb2 = "\033[G"; s->kcbt = "\033[Z"; s->kspd = "\032"; // ^Z linux_cygwin: s->kcub1 = "\033[D"; s->kcud1 = "\033[B"; s->kcuf1 = "\033[C"; s->kcuu1 = "\033[A"; s->kdch1 = "\033[3~"; s->kend = "\033[4~"; s->khome = "\033[1~"; s->kich1 = "\033[2~"; s->knp = "\033[6~"; s->kpp = "\033[5~"; s->kf1 = "\033[[A"; s->kf2 = "\033[[B"; s->kf3 = "\033[[C"; s->kf4 = "\033[[D"; s->kf5 = "\033[[E"; s->kf6 = "\033[17~"; s->kf7 = "\033[18~"; s->kf8 = "\033[19~"; s->kf9 = "\033[20~"; s->kf10 = "\033[21~"; s->kf11 = "\033[23~"; s->kf12 = "\033[24~"; s->kf13 = "\033[25~"; s->kf14 = "\033[26~"; s->kf15 = "\033[28~"; s->kf16 = "\033[29~"; s->kf17 = "\033[31~"; s->kf18 = "\033[32~"; s->kf19 = "\033[33~"; s->kf20 = "\033[34~"; } if (strstart(term, "xterm", NULL)) { s->ka1 = "\033Ow"; s->ka3 = "\033Ou"; s->kb2 = "\033Oy"; s->kbeg = "\033OE"; s->kbs = "\03377"; s->kc1 = "\033Oq"; s->kc3 = "\033Os"; s->kcub1 = "\033OD"; s->kcud1 = "\033OB"; s->kcuf1 = "\033OC"; s->kcuu1 = "\033OA"; s->kdch1 = "\033[3~"; s->kend = "\033[4~"; s->kent = "\033OM"; s->khome = "\033[1~"; s->kich1 = "\033[2~"; s->kmous = "\033[M"; s->knp = "\033[6~"; s->kpp = "\033[5~"; s->kf1 = "\033OP"; s->kf2 = "\033OQ"; s->kf3 = "\033OR"; s->kf4 = "\033OS"; s->kf5 = "\033[15~"; s->kf6 = "\033[17~"; s->kf7 = "\033[18~"; s->kf8 = "\033[19~"; s->kf9 = "\033[20~"; s->kf10 = "\033[21~"; s->kf11 = "\033[23~"; s->kf12 = "\033[24~"; s->kf13 = "\033[25~"; s->kf14 = "\033[26~"; s->kf15 = "\033[28~"; s->kf16 = "\033[29~"; s->kf17 = "\033[31~"; s->kf18 = "\033[32~"; s->kf19 = "\033[33~"; s->kf20 = "\033[34~"; } } // XXX: should use an auxiliary buffer to make this asynchous static void qe_term_write(ShellState *s, const char *buf, int len) { int ret; if (len < 0) len = strlen(buf); if (s->qe_state->trace_buffer) eb_trace_bytes(buf, len, EB_TRACE_PTY); while (len > 0) { ret = write(s->pty_fd, buf, len); if (ret == -1 && (errno == EAGAIN || errno == EINTR)) continue; if (ret <= 0) break; buf += ret; len -= ret; s->last_char = buf[-1]; } } static inline void qe_term_set_style(ShellState *s) { QETermStyle composite_color; if (s->reverse) { composite_color = QE_TERM_MAKE_COLOR(s->bgcolor, s->fgcolor); } else { composite_color = QE_TERM_MAKE_COLOR(s->fgcolor, s->bgcolor); } s->b->cur_style = QE_TERM_COMPOSITE | s->attr | composite_color; } static void qe_term_get_pos(ShellState *s, int destoffset, int *start, int *px, int *py) { int total_lines, col_num, offset, offset1, c; int x, y, w, start_line, start_offset; if (s->use_alternate_screen) { start_offset = minp(&s->alternate_screen_top, s->b->total_size); } else { eb_get_pos(s->b, &total_lines, &col_num, s->b->total_size); if (s->cur_offset >= s->b->total_size || eb_prevc(s->b, s->b->total_size, &offset1) != '\n') total_lines++; /* XXX: this is incorrect for overlong lines */ start_line = max(0, total_lines - s->rows); start_offset = eb_goto_pos(s->b, start_line, 0); } if (start) { *start = start_offset; } if (px || py) { destoffset = clamp(destoffset, 0, s->b->total_size); offset = start_offset; for (x = y = 0; offset < destoffset;) { c = eb_nextc(s->b, offset, &offset); if (c == '\n') { y++; x = 0; } else { w = unicode_tty_glyph_width(c); x += w; } } if (px) *px = x; if (py) *py = y; } } /* Compute offset of the char at column x and row y (0 based). * Can insert spaces or newlines if needed. * x and y may each be relative to the current position. */ static void qe_term_goto_xy(ShellState *s, int destx, int desty, int relative) { int x, y, w, start_offset, offset, offset1, c; if (relative) { qe_term_get_pos(s, s->cur_offset, &start_offset, &x, &y); if (relative & 1) destx += x; if (relative & 2) desty += y; } else { qe_term_get_pos(s, s->cur_offset, &start_offset, NULL, NULL); } if (desty < 0 || desty >= QE_TERM_YSIZE_INFINITE - 1) desty = 0; else if (desty >= s->rows) desty = s->rows - 1; if (destx < 0) destx = 0; else if (destx >= s->cols) destx = s->cols; x = y = 0; offset = start_offset; while (y < desty || x < destx) { if (offset >= s->b->total_size) { offset = s->b->total_size; /* XXX: color may be wrong */ qe_term_set_style(s); if (y < desty) { offset += eb_insert_uchars(s->b, offset, '\n', desty - y); y = desty; x = 0; } if (x < destx) { offset += eb_insert_uchars(s->b, offset, ' ', destx - x); x = destx; } } else { c = eb_nextc(s->b, offset, &offset1); if (c == '\n') { if (y < desty) { y++; x = 0; offset = offset1; } else { offset += eb_insert_uchars(s->b, offset, ' ', destx - x); x = destx; } } else { /* if destination x is in the middle of a wide character, actual x will be too far */ w = unicode_tty_glyph_width(c); x += w; offset = offset1; } } } s->cur_offset = offset; } static void qe_term_goto_tab(ShellState *s, int n) { int col_num, cur_line; eb_get_pos(s->b, &cur_line, &col_num, s->cur_offset); /* assuming tab stops every 8 positions */ qe_term_goto_xy(s, max(0, col_num + n * 8) & ~7, 0, 2); } static int qe_term_overwrite(ShellState *s, int offset, const char *buf, int len) { int offset1; int c1; c1 = eb_nextc(s->b, offset, &offset1); qe_term_set_style(s); if (c1 == '\n') { /* insert */ eb_insert(s->b, offset, buf, len); } else { /* check for buffer content change is not an advisable optimisation * because re-writing the same character may cause color changes. */ int cur_len = offset1 - offset; if (cur_len == len) { eb_write(s->b, offset, buf, len); } else { eb_delete(s->b, offset, cur_len); eb_insert(s->b, offset, buf, len); } } return offset + len; } static int qe_term_put_char(ShellState *s, int offset, int c, int n) { /* qe_term_put_char purposely ignores charset when writing chars */ char buf[1]; int i; buf[0] = c; for (i = 0; i < n; i++) { offset = qe_term_overwrite(s, offset, buf, 1); } return offset; } static int qe_term_erase_chars(ShellState *s, int offset, int n) { return qe_term_put_char(s, offset, ' ', n); } static int qe_term_skip_lines(ShellState *s, int offset, int n) { int i; for (i = 0; i < n; i++) { offset = eb_next_line(s->b, offset); } return offset; } static int qe_term_delete_lines(ShellState *s, int offset, int n) { int i, offset1; if (n > 0) { for (offset1 = offset, i = 0; i < n; i++) { offset1 = eb_next_line(s->b, offset1); } eb_delete_range(s->b, offset, offset1); } return offset; } static int qe_term_insert_lines(ShellState *s, int offset, int n) { if (n > 0) { offset += eb_insert_uchars(s->b, offset, '\n', n); } return offset; } static int qe_term_csi_m(ShellState *s, const int *params, int count) { int c = *params; /* Comment from putty/terminal.c: * * A VT100 without the AVO only had one attribute, either underline or * reverse video depending on the cursor type, this was selected by * CSI 7m. * * case 2: * This is sometimes DIM, eg on the GIGI and Linux (aka FAINT in iTerm2) * case 8: * This is sometimes INVIS various ANSI. * case 21: * This like 22 disables BOLD, DIM and INVIS * * The ANSI colours appear on any terminal that has colour (obviously) * but the interaction between sgr0 and the colours varies but is usually * related to the background colour erase item. The interaction between * colour attributes and the mono ones is also very implementation * dependent. * * The 39 and 49 attributes are likely to be unimplemented. */ switch (c) { case -1: case 0: /* Normal (default). [exit_attribute_mode] */ s->fgcolor = QE_TERM_DEF_FG; s->bgcolor = QE_TERM_DEF_BG; s->reverse = 0; s->attr = 0; break; case 1: /* Bold. [enter_bold_mode] */ s->attr |= QE_TERM_BOLD; break; case 2: /* Faint, decreased intensity (ISO 6429). */ goto unhandled; case 3: /* Italicized (ISO 6429). [enter_italic_mode] */ s->attr |= QE_TERM_ITALIC; break; case 4: /* Underlined. [enter_underline_mode] */ s->attr |= QE_TERM_UNDERLINE; break; case 5: /* Blink (appears as Bold). [enter_blink_mode] */ s->attr |= QE_TERM_BLINK; break; case 6: /* SCO light background, ANSI.SYS blink rapid */ goto unhandled; case 7: /* Inverse. [enter_reverse_mode, enter_standout_mode] */ s->reverse = 1; break; case 8: /* Invisible, i.e., hidden (VT300). enter_secure_mode */ case 9: /* Crossed-out characters (ISO 6429). cygwin dim mode */ case 10: /* SCO acs off, ANSI default font */ case 11: /* SCO acs on (CP437) */ case 12: /* SCO acs on, |0x80 */ case 13: case 14: case 15: case 16: case 17: case 18: case 19: /* 11-19: nth alternate font */ case 20: /* Fraktur */ goto unhandled; case 21: /* Doubly-underlined (ISO 6429). Bold off sometimes */ goto unhandled; case 22: /* Normal (neither bold nor faint). [exit_bold_mode] */ s->attr &= ~QE_TERM_BOLD; break; case 23: /* Not italicized (ISO 6429). Not Fraktur. [exit_italic_mode] */ s->attr &= ~QE_TERM_ITALIC; break; case 24: /* Not underlined. [exit_underline_mode] */ s->attr &= ~QE_TERM_UNDERLINE; break; case 25: /* Steady (not blinking). [exit_blink_mode] */ s->attr &= ~QE_TERM_BLINK; break; case 26: /* reserved */ goto unhandled; case 27: /* Positive (not inverse). [exit_reverse_mode, exit_standout_mode] */ s->reverse = 0; break; case 28: /* Visible, i.e., not hidden (VT300). [exit_secure_mode] */ case 29: /* Not crossed-out (ISO 6429). */ goto unhandled; case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37: /* set foreground color */ /* 0:black 1:red 2:green 3:yellow 4:blue 5:magenta 6:cyan 7:white */ /* XXX: should distinguish system colors and palette colors */ s->fgcolor = c - 30; if (QE_TERM_FG_COLORS < 256) { s->fgcolor = qe_map_color(xterm_colors[c - 30], xterm_colors, QE_TERM_FG_COLORS, NULL); } break; case 38: /* set extended foreground color (ISO-8613-3) */ // First subparam means: # additional subparams: Accepts optional params: // 1: transparent 0 NO // 2: RGB 3 YES // 3: CMY 3 YES // 4: CMYK 4 YES // 5: Indexed color 1 NO // // Optional paramters go at position 7 and 8, and indicate toleranace as an // integer; and color space (0=CIELUV, 1=CIELAB). Example: // // CSI 38:2:255:128:64:0:5:1 m // // Also accepted for xterm compatibility, but never with optional parameters: // CSI 38;2;255;128;64 m // // Set the foreground color to red=255, green=128, blue=64 with a tolerance of // 5 in the CIELAB color space. The 0 at the 6th position has no meaning and // is just a filler. // // For 256-color mode (indexed) use this for the foreground: // CSI 38;5;N m // where N is a value between 0 and 255. See the colors described in screen_char_t // in the comments for fgColorCode. if (count >= 3 && params[1] == 5) { /* set foreground color to third esc_param */ /* complete syntax is \033[38;5;Nm where N is in range 0..255 */ int color = clamp(params[2], 0, 255); QEColor rgb = xterm_colors[color]; /* map color to qe-term palette */ s->fgcolor = color; if (QE_TERM_FG_COLORS < 256) s->fgcolor = qe_map_color(rgb, xterm_colors, QE_TERM_FG_COLORS, NULL); return 3; } if (count >= 5 && params[1] == 2) { /* set foreground color to 24-bit color */ /* complete syntax is \033[38;2;r;g;bm where r,g,b are in 0..255 */ QEColor rgb = QERGB25(clamp(params[2], 0, 255), clamp(params[3], 0, 255), clamp(params[4], 0, 255)); /* map 24-bit colors to qe-term palette */ s->fgcolor = qe_map_color(rgb, xterm_colors, QE_TERM_FG_COLORS, NULL); return 5; } return 2; case 39: /* orig_pair(1) [default-foreground] */ s->fgcolor = QE_TERM_DEF_FG; break; case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47: /* set background color */ /* XXX: should distinguish system colors and palette colors */ s->bgcolor = c - 40; if (QE_TERM_BG_COLORS < 256) { s->bgcolor = qe_map_color(xterm_colors[c - 40], xterm_colors, QE_TERM_BG_COLORS, NULL); } break; case 48: /* set extended background color (ISO-8613-3) */ if (count >= 3 && params[1] == 5) { /* set background color to third esc_param */ /* complete syntax is \033[48;5;Nm where N is in range 0..255 */ int color = clamp(params[2], 0, 255); QEColor rgb = xterm_colors[color]; /* map color to qe-term palette */ s->bgcolor = color; if (QE_TERM_BG_COLORS < 256) s->bgcolor = qe_map_color(rgb, xterm_colors, QE_TERM_BG_COLORS, NULL); return 3; } if (count >= 5 && params[1] == 2) { /* set background color to 24-bit color */ /* complete syntax is \033[48;2;r;g;bm where r,g,b are in 0..255 */ QEColor rgb = QERGB25(clamp(params[2], 0, 255), clamp(params[3], 0, 255), clamp(params[4], 0, 255)); /* map 24-bit colors to qe-term palette */ s->bgcolor = qe_map_color(rgb, xterm_colors, QE_TERM_BG_COLORS, NULL); return 5; } return 2; case 49: /* orig_pair(2) [default-background] */ s->bgcolor = QE_TERM_DEF_BG; break; case 50: /* Reserved */ case 51: /* Framed */ case 52: /* Encircled */ case 53: /* Overlined */ case 54: /* Not framed or encircled */ case 55: /* Not overlined */ case 56: case 57: case 58: case 59: /* Reserved */ case 60: /* ideogram underline or right side line * hardly ever supported */ case 61: /* ideogram double underline or double line on the right side * hardly ever supported */ case 62: /* ideogram overline or left side line * hardly ever supported */ case 63: /* ideogram double overline or double line on the left side * hardly ever supported */ case 64: /* ideogram stress marking * hardly ever supported */ case 65: /* ideogram attributes off, reset the effects of all of 60-64 * hardly ever supported */ goto unhandled; case 90: case 91: case 92: case 93: case 94: case 95: case 96: case 97: /* Set foreground text color, high intensity, aixterm (not in standard) */ /* XXX: should distinguish system colors and palette colors */ s->fgcolor = c - 90 + 8; if (QE_TERM_FG_COLORS < 256) { s->fgcolor = qe_map_color(xterm_colors[c - 90 + 8], xterm_colors, QE_TERM_FG_COLORS, NULL); } break; case 100: case 101: case 102: case 103: case 104: case 105: case 106: case 107: /* Set background color, high intensity, aixterm (not in standard) */ /* XXX: should distinguish system colors and palette colors */ s->bgcolor = c - 100 + 8; if (QE_TERM_BG_COLORS < 256) { s->bgcolor = qe_map_color(xterm_colors[c - 100 + 8], xterm_colors, QE_TERM_BG_COLORS, NULL); } break; default: unhandled: TRACE_MSG(s, "unhandled SGR"); break; } return 1; } /* Well, almost a hack to update cursor */ static void qe_term_update_cursor(qe__unused__ ShellState *s) { #if 0 QEmacsState *qs = s->qe_state; EditState *e; if (s->cur_offset == -1) return; for (e = qs->first_window; e != NULL; e = e->next_window) { if (s->b == e->b && e->interactive) { e->offset = s->cur_offset; } } #endif } static inline ShellState *shell_get_state(EditState *e, int status) { return qe_get_buffer_mode_data(e->b, &shell_mode, status ? e : NULL); } /* CG: much cleaner way! */ /* would need a kill hook as well ? */ static void shell_display_hook(EditState *e) { ShellState *s; if (e->interactive) { if ((s = shell_get_state(e, 0)) != NULL) e->offset = s->cur_offset; if (s->use_alternate_screen) e->offset_top = s->alternate_screen_top; } } static void shell_key(void *opaque, int key) { ShellState *s = opaque; char buf[10]; const char *p; int len; if (!s || s->base.mode != &shell_mode) return; if (key == KEY_CTRL('o')) { qe_ungrab_keys(); unget_key(key); return; } p = buf; len = -1; switch (key) { case KEY_UP: p = s->kcuu1; break; case KEY_DOWN: p = s->kcud1; break; case KEY_RIGHT: p = s->kcuf1; break; case KEY_LEFT: p = s->kcub1; break; //case KEY_CTRL_UP: //case KEY_CTRL_DOWN: //case KEY_CTRL_RIGHT: //case KEY_CTRL_LEFT: //case KEY_CTRL_END: //case KEY_CTRL_HOME: //case KEY_CTRL_PAGEUP: //case KEY_CTRL_PAGEDOWN: case KEY_SHIFT_TAB: p = s->kcbt; break; case KEY_HOME: p = s->khome; break; case KEY_INSERT: p = s->kich1; break; case KEY_DELETE: p = s->kdch1; break; case KEY_END: p = s->kend; break; case KEY_PAGEUP: p = s->kpp; break; case KEY_PAGEDOWN: p = s->knp; break; case KEY_F1: p = s->kf1; break; case KEY_F2: p = s->kf2; break; case KEY_F3: p = s->kf3; break; case KEY_F4: p = s->kf4; break; case KEY_F5: p = s->kf5; break; case KEY_F6: p = s->kf6; break; case KEY_F7: p = s->kf7; break; case KEY_F8: p = s->kf8; break; case KEY_F9: p = s->kf9; break; case KEY_F10: p = s->kf10; break; case KEY_F11: p = s->kf11; break; case KEY_F12: p = s->kf12; break; case KEY_F13: p = s->kf13; break; case KEY_F14: p = s->kf14; break; case KEY_F15: p = s->kf15; break; case KEY_F16: p = s->kf16; break; case KEY_F17: p = s->kf17; break; case KEY_F18: p = s->kf18; break; case KEY_F19: p = s->kf19; break; case KEY_F20: p = s->kf20; break; default: if (key < 256) { buf[0] = key; len = 1; } else if (key >= KEY_META(0) && key <= KEY_META(255)) { buf[0] = '\033'; buf[1] = key; len = 2; } else { p = NULL; } break; } if (p) { qe_term_write(s, p, len); } } static unsigned char const sco_color[16] = { 0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15, }; static void qe_term_emulate(ShellState *s, int c) { int i, n, param1, param2, len; int offset, offset1, offset2; char buf1[10]; offset = clampp(&s->cur_offset, 0, s->b->total_size); if (s->state == QE_TERM_STATE_NORM) { s->term_pos = 0; } if (s->term_pos < countof(s->term_buf)) { s->term_buf[s->term_pos++] = c; s->term_len = s->term_pos; } #define ESC2(c1,c2) (((c1) << 8) | (unsigned char)(c2)) /* some bytes are state independent */ switch (c) { case 0x18: case 0x1A: s->state = QE_TERM_STATE_NORM; return; case 0x1B: s->state = QE_TERM_STATE_ESC; return; #if 0 case 0x9B: goto csi_entry; #endif } switch (s->state) { case QE_TERM_STATE_NORM: switch (c) { case 5: /* ENQ Return Terminal Status (Ctrl-E). * Default response is an empty string, but may be overridden * by a resource answerbackString. */ break; case 7: /* BEL Bell (Ctrl-G). */ put_status(NULL, "Ding!"); break; case 8: /* BS Backspace (Ctrl-H). */ if (s->use_alternate_screen) { qe_term_goto_xy(s, -1, 0, 3); } else { int c1; /* XXX: potentially incorrect for combining glyphs */ c1 = eb_prevc(s->b, offset, &offset1); if (c1 != '\n') { s->cur_offset = offset1; } } break; case 9: /* TAB Horizontal Tab (HT) (Ctrl-I). */ qe_term_goto_tab(s, 1); break; case 10: /* Line Feed or New Line (NL). (LF is Ctrl-J). */ case 11: /* VT Vertical Tab (Ctrl-K). * This is treated the same as LF. */ case 12: /* FF Form Feed or New Page (NP) (Ctrl-L). * FF is treated the same as LF. */ if (s->use_alternate_screen) { int col, row; qe_term_get_pos(s, offset, &offset1, &col, &row); if (row >= s->rows - 1) { /* if on the last row, scroll the screen */ s->alternate_screen_top = eb_next_line(s->b, offset1); } qe_term_goto_xy(s, 0, 1, 3); } else { /* go to next line */ /* CG: should check if column should be kept in cooked mode */ for (;;) { if (offset >= s->b->total_size) { /* add a new line */ /* CG: XXX: ignoring charset */ qe_term_set_style(s); offset += eb_insert_uchar(s->b, offset, '\n'); break; } c = eb_nextc(s->b, offset, &offset); if (c == '\n') break; } s->cur_offset = offset; } s->b->last_log = 0; /* close undo record */ break; case 13: /* CR Carriage Return (Ctrl-M). */ /* move to bol */ s->cur_offset = eb_goto_bol(s->b, offset); break; case 14: /* SO Shift Out (Ctrl-N) -> * Switch to Alternate Character Set. This * invokes the G1 character set. */ s->shifted = s->charset[s->cset = 1]; break; case 15: /* SI Shift In (Ctrl-O) -> * Switch to Standard Character Set. This * invokes the G0 character set (the default). */ s->shifted = s->charset[s->cset = 0]; break; default: if (c >= 32) { /* CG: assuming ISO-8859-1 characters */ /* CG: horrible kludge for alternate charset support */ if (s->shifted && c >= 96 && c < 128) { #if 0 /* Should actually use these tables: */ static const wchar_t unitab_xterm_poorman[32] = "*#****o~**+++++-----++++|****L. "; #endif if (s->b->charset == &charset_utf8) { static const wchar_t unitab_xterm_std[32] = { 0x2666, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1, 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x0020 }; s->lastc = c = unitab_xterm_std[c - 96]; len = utf8_encode(buf1, c); } else { /* CG: quick 8 bit hack: store line drawing * characters in [96..127] as meta control * characters in [128..159]. * This hack is reversed in tty_term_flush(). */ c += 32; buf1[0] = s->lastc = c; len = 1; } } else { /* write char (should factorize with do_char() code */ /* CG: Charset support is inherently broken here because * bytes are inserted one at a time and charset conversion * should not be performed between shell output and buffer * contents. UTF8 is special cased, other charsets need work. */ /* CG: further improvement direction includes automatic * conversion from ISO-8859-1 to UTF-8 for invalid UTF-8 * byte sequences. */ if (s->b->charset == &charset_utf8) { s->utf8_len = utf8_length[c]; if (s->utf8_len > 1) { s->state = QE_TERM_STATE_UTF8; break; } } //len = eb_encode_uchar(s->b, buf1, c); buf1[0] = s->lastc = c; len = 1; } /* XXX: incorrect if overwritten character has different width than character present in buffer, especially in case of combining marks */ s->cur_offset = qe_term_overwrite(s, offset, buf1, len); } else { TRACE_MSG(s, "control"); } break; } break; case QE_TERM_STATE_UTF8: /* XXX: should check that c is a utf-8 continuation byte */ if (s->term_pos >= s->utf8_len) { s->cur_offset = qe_term_overwrite(s, offset, cs8(s->term_buf), s->utf8_len); s->state = QE_TERM_STATE_NORM; } break; case QE_TERM_STATE_ESC: /* CG: should deal with other sequences: * ansi: hts=\EH, s0ds=\E(B, s1ds=\E)B, s2ds=\E*B, s3ds=\E+B, * linux: hts=\EH, rc=\E8, ri=\EM, rs1=\Ec\E]R, sc=\E7, * vt100: enacs=\E(B\E)0, hts=\EH, rc=\E8, ri=\EM$<5>, * rmkx=\E[?1l\E>, * rs2=\E>\E[?3l\E[?4l\E[?5l\E[?7h\E[?8h, sc=\E7, * smkx=\E[?1h\E=, * xterm: enacs=\E(B\E)0, hts=\EH, is2=\E[!p\E[?3;4l\E[4l\E>, * rc=\E8, ri=\EM, rmkx=\E[?1l\E>, rs1=\Ec, * rs2=\E[!p\E[?3;4l\E[4l\E>, sc=\E7, smkx=\E[?1h\E=, * set-window-title=\E]0;title text\007, * tests: \E#8 fill screen with 'E's * tests: \Ec reset */ s->esc1 = c; s->state = QE_TERM_STATE_NORM; switch (c) { case '[': // Control Sequence Introducer (CSI is 0x9b). s->nb_params = 0; s->params[0] = -1; s->params[1] = -1; s->esc1 = 0; s->state = QE_TERM_STATE_CSI; break; case ' ': case '#': case '%': case '(': case ')': case '*': case '+': case '-': case '.': case '/': case ']': // Operating System Command (OSC is 0x9d). s->state = QE_TERM_STATE_ESC2; break; case '^': // Privacy Message (PM is 0x9e). case '_': // Application Program Command (APC is 0x9f). case 'P': // Device Control String (DCS is 0x90). s->state = QE_TERM_STATE_STRING; break; case '\\': // String Terminator (ST is 0x9c). TRACE_MSG(s, "unhandled string"); break; case '6': // Back Index (DECBI), VT420 and up. break; case '7': // Save Cursor (DECSC). [sc] qe_term_get_pos(s, offset, NULL, &s->save_x, &s->save_y); break; case '8': // Restore Cursor (DECRC). [rc] qe_term_goto_xy(s, s->save_x, s->save_y, 0); break; case 'c': // Full Reset (RIS). [rs1, reset_1string] s->shifted = s->charset[s->cset = 0]; break; case '9': // Forward Index (DECFI), VT420 and up. case '=': // Application Keypad (DECKPAM). [smkx] case '>': // Normal Keypad (DECKPNM). [rmkx, is2, rs2] break; case 'D': // Index (IND is 0x84). case 'E': // Next Line (NEL is 0x85). case 'F': // Cursor to lower left corner of screen. // This is enabled by the hpLowerleftBugCompat resource. case 'H': // Tab Set (HTS is 0x88). [set_tab] case 'M': // Reverse Index (RI is 0x8d). [ri] case 'O': // Single Shift Select of G3 Character Set (SS3 is 0x8f). // This affects next character only. case 'V': // Start of Guarded Area (SPA is 0x96). case 'W': // End of Guarded Area (EPA is 0x97). case 'X': // Start of String (SOS is 0x98). case 'Z': // Return Terminal ID (DECID is 0x9a). // Obsolete form of CSI c (DA). case 'l': // Memory Lock (per HP terminals). Locks memory above the cursor. case 'm': // Memory Unlock (per HP terminals). case 'n': // Invoke the G2 Character Set as GL (LS2). case 'o': // Invoke the G3 Character Set as GL (LS3). case '|': // Invoke the G3 Character Set as GR (LS3R). case '}': // Invoke the G2 Character Set as GR (LS2R). case '~': // Invoke the G1 Character Set as GR (LS1R). default: TRACE_MSG(s, "unhandled"); break; } break; case QE_TERM_STATE_ESC2: s->state = QE_TERM_STATE_NORM; s->esc2 = c; switch (ESC2(s->esc1, c)) { case ESC2('%','8'): /* set utf mode */ case ESC2('%','G'): /* Select UTF-8 character set (ISO 2022). */ case ESC2('%','@'): /* Select default character set. That is ISO 8859-1 (ISO 2022). */ TRACE_MSG(s, "utf mode"); break; /* Designate G0 Character Set (ISO 2022, VT100). */ case ESC2('(','0'): /* set charset0 CSET_LINEDRW */ s->charset[0] = 1; break; case ESC2('(','A'): /* set charset0 CSET_GBCHR */ case ESC2('(','B'): /* set charset0 CSET_ASCII */ case ESC2('(','U'): /* set charset0 CSET_SCOACS */ s->charset[0] = 0; break; /* Designate G1 Character Set (ISO 2022, VT100). */ case ESC2(')','0'): /* set charset1 CSET_LINEDRW */ s->charset[1] = 1; break; case ESC2(')','A'): /* set charset1 CSET_GBCHR */ case ESC2(')','B'): /* set charset1 CSET_ASCII */ case ESC2(')','U'): /* set charset1 CSET_SCOACS */ s->charset[1] = 0; break; /* Designate G2 Character Set (ISO 2022, VT220). */ case ESC2('*','B'): /* Designate G3 Character Set (ISO 2022, VT220). */ case ESC2('+','B'): /* Designate G1 Character Set (VT300). */ case ESC2('-','B'): /* Designate G2 Character Set (VT300). */ case ESC2('.','B'): /* Designate G3 Character Set (VT300). */ case ESC2('/','B'): TRACE_MSG(s, "set charset"); break; /* XXX: OSC sequences should parse as OSC Ps;Pt ST */ case ESC2(']','0'): /* Change Icon Name and Window Title to Pt. */ case ESC2(']','1'): /* Change Icon Name to Pt. */ case ESC2(']','2'): /* Change Window Title to Pt. */ case ESC2(']','3'): /* Set X property on top-level window */ /* Pt should be in the form "prop=value", or just "prop" to delete the property */ case ESC2(']','4'): /* xterm's define-extended color "\033]4;c;name\007" */ /* Change Color #c to cname. Any number of c name pairs may be given. */ /* iTerm2 has a specific behavior for colors 16 to 22: 16: terminalSetForegroundColor 17: terminalSetBackgroundColor 18: terminalSetBoldColor 19: terminalSetSelectionColor 20: terminalSetSelectedTextColor 21: terminalSetCursorColor 22: terminalSetCursorTextColor */ /* xterm has the following set extended attribute: */ /* 10 Change color names starting with text foreground to Pt (a list of one or more color names or RGB specifications, separated by semicolon, up to eight, as per XParseColor). */ /* 11 Change colors starting with text background to Pt */ /* 12 Change colors starting with text cursor to Pt */ /* 13 Change colors starting with mouse foreground to Pt */ /* 14 Change colors starting with mouse background to Pt */ /* 15 Change colors starting with Tek foreground to Pt */ /* 16 Change colors starting with Tek background to Pt */ /* 17 Change colors starting with highlight to Pt */ /* 46 Change Log File to Pt (normally disabled by a compile-time option) */ /* 50 Set Font to Pt If Pt begins with a "#", index in the font menu, relative (if the next character is a plus or minus sign) or absolute. A number is expected but not required after the sign (the default is the current entry for relative, zero for absolute indexing). */ case ESC2(']','W'): /* word-set (define char wordness) */ s->state = QE_TERM_STATE_STRING; break; case ESC2(']','P'): /* linux set palette */ case ESC2(']','R'): /* linux reset palette */ /* XXX: Todo */ TRACE_MSG(s, "linux palette"); /* followed by 7 digit palette entry nrrggbb with n: letter 0-f for standard palette entries g-m for extended attributes: rr, gg, bb 2 hex digit values */ break; default: TRACE_MSG(s, "unhandled"); break; } s->shifted = s->charset[s->cset]; break; case QE_TERM_STATE_STRING: /* CG: should store the string */ /* Stop string on CR or LF, for protection */ if (c == '\012' || c == '\015') { s->state = QE_TERM_STATE_NORM; TRACE_MSG(s, "broken string"); break; } /* Stop string on \a (^G) or M-\ -- need better test for ESC \ */ if (c == '\007' || c == 0234 || c == '\\') { /* CG: ESC2(']','0') should set shell caption */ /* CG: ESC2(']','4') should parse color definition string */ /* (example: "\033]4;16;rgb:00/00/00\033\134" ) */ // executeXtermSetRgb // iTerm2 reports the current rgb value with ";?", // e.g. "105;?" -> report as \033]4;P;rgb:00/cc/ff\007", s->state = QE_TERM_STATE_NORM; TRACE_MSG(s, "unhandled string"); } break; case QE_TERM_STATE_CSI: if (c == '?' || c == '=' || c == '"' || c == ' ' || c == '\'' || c == '&') { s->esc1 = c; break; } if (qe_isdigit(c)) { if (s->params[s->nb_params] < 0) { s->params[s->nb_params] = 0; } s->params[s->nb_params] *= 10; s->params[s->nb_params] += c - '0'; break; } if (s->nb_params == 0 || (s->nb_params < MAX_CSI_PARAMS && s->params[s->nb_params] >= 0)) { s->nb_params++; s->params[s->nb_params] = -1; } if (c == ';' || c == ':') break; s->state = QE_TERM_STATE_NORM; /* default param is 1 for most commands */ param1 = s->params[0] >= 0 ? s->params[0] : 1; param2 = s->params[1] >= 0 ? s->params[1] : 1; switch (ESC2(s->esc1,c)) { case '@': /* ICH: Insert Ps (Blank) Character(s) (default = 1) */ qe_term_set_style(s); /* XXX: should delete extra characters at the end of the line */ eb_insert_uchars(s->b, offset, ' ', min(param1, s->cols)); /* cur_offset may have been updated by callback */ s->cur_offset = offset; break; case 'A': /* CUU: Cursor Up Ps Times (default = 1) */ qe_term_goto_xy(s, 0, -param1, 3); break; case 'B': /* CUD: Cursor Down Ps Times (default = 1) */ case 'e': /* VPR: Line Position Relative [rows] (default = 1) */ qe_term_goto_xy(s, 0, param1, 3); break; case 'C': /* CUF: Cursor Forward Ps Times (default = 1) */ case 'a': /* HPR: Character Position Relative [columns] (default = 1) */ qe_term_goto_xy(s, param1, 0, 3); break; case 'D': /* CUB: Cursor Backward Ps Times (default = 1) */ qe_term_goto_xy(s, -param1, 0, 3); break; case 'E': /* CNL: Cursor Next Line Ps Times (default = 1) and CR. */ qe_term_goto_xy(s, 0, param1, 2); break; case 'F': /* CPL: Cursor Preceding Line Ps Times (default = 1) and CR. */ qe_term_goto_xy(s, 0, -param1, 2); break; case 'G': /* CHA: Cursor Character Absolute [column]. */ case '`': /* HPA: Character Position Absolute [column] (default = 1) */ qe_term_goto_xy(s, param1 - 1, 0, 2); break; case 'H': /* CUP: Cursor Position [row;column] (default = [1,1]). */ case 'f': /* HVP: Horizontal and Vertical Position [row;column] (default = [1,1]) */ qe_term_goto_xy(s, param2 - 1, param1 - 1, 0); break; case 'I': /* CHT: Cursor Forward Tabulation Ps tab stops (default = 1). */ qe_term_goto_tab(s, param1); break; case 'J': /* ED: Erase in Display. */ case ESC2('?','J'): /* DECSED: Selective Erase in Display. */ /* XXX: should just force top of window to in infinite scroll mode */ { /* 0: Below (default), 1: Above, 2: All, 3: Saved Lines (xterm) */ /* XXX: should handle eol style */ int offset0, bos, eos, col, row; bos = eos = 0; // default param is 0 if (s->params[0] <= 0) { /* Erase to end of screen */ eos = 1; } else if (s->params[0] == 1) { /* Erase from beginning of screen */ bos = 1; } else if (s->params[0] == 2 || s->params[0] == 3) { /* Erase complete screen */ bos = eos = 1; } /* update cursor as overwriting characters may change offsets */ if (bos) { qe_term_get_pos(s, offset, &offset0, &col, &row); eb_goto_bol(s->b, offset); qe_term_set_style(s); if (row > 0) { offset = qe_term_delete_lines(s, offset0, row); offset = qe_term_insert_lines(s, offset, row); } else { offset = eb_goto_bol(s->b, offset); } offset = qe_term_erase_chars(s, offset, col); } if (eos) { eb_delete(s->b, offset, s->b->total_size - offset); } s->cur_offset = offset; } break; case 'K': /* EL: Erase in Line. */ case ESC2('?','K'): /* DECSEL: Selective Erase in Line. */ { /* 0: to Right (default), 1: to Left, 2: All */ /* XXX: should handle eol style */ int cur_line, col_num, n1, n2; /* XXX: potentially incorrect for combining marks */ n1 = n2 = 0; eb_get_pos(s->b, &cur_line, &col_num, offset); offset1 = eb_goto_bol(s->b, offset); offset2 = eb_goto_eol(s->b, offset); // default param is 0 if (s->params[0] <= 0) { n2 = offset2 - offset; } else if (s->params[0] == 1) { n1 = col_num; } else if (s->params[0] == 2) { n1 = col_num; n2 = offset2 - offset; } if (n1) { /* update offset as overwriting characters may change offsets */ qe_term_set_style(s); offset = qe_term_erase_chars(s, offset1, n1); } if (n2) { eb_delete(s->b, offset, n2); } /* update cursor as callback may have changed it */ s->cur_offset = offset; } break; case 'L': /* IL: Insert Ps Line(s) (default = 1). */ //TRACE_MSG(s, "insert lines"); { int row, zone; offset = eb_goto_bol(s->b, offset); qe_term_get_pos(s, offset, NULL, NULL, &row); zone = max(0, s->scroll_bottom - row); param1 = min(param1, zone); qe_term_set_style(s); offset1 = qe_term_insert_lines(s, offset, param1); offset1 = qe_term_skip_lines(s, offset1, zone - param1); qe_term_delete_lines(s, offset1, param1); s->cur_offset = offset; } break; case 'M': /* DL: Delete Ps Line(s) (default = 1). */ //TRACE_MSG(s, "delete lines"); { int row, zone; offset = eb_goto_bol(s->b, offset); qe_term_get_pos(s, offset, NULL, NULL, &row); zone = max(0, s->scroll_bottom - row); param1 = min(param1, zone); qe_term_set_style(s); offset1 = qe_term_delete_lines(s, offset, param1); offset1 = qe_term_skip_lines(s, offset1, zone - param1); qe_term_insert_lines(s, offset1, param1); s->cur_offset = offset; } break; case 'P': /* DCH: Delete Ps Character(s) (default = 1). */ param1 = min(param1, s->cols); offset1 = offset; for (n = param1; n > 0; n--) { c = eb_nextc(s->b, offset1, &offset2); if (c == '\n') break; offset1 = offset2; } eb_delete_range(s->b, offset, offset1); break; case 'S': /* SU: Scroll up Ps lines (default = 1). */ /* scroll the whole page up */ TRACE_MSG(s, "scroll up"); break; case 'T': /* SD: Scroll down Ps lines (default = 1). */ /* scroll the whole page down */ TRACE_MSG(s, "scroll down"); break; case 'X': /* ECH: Erase Ps Character(s) (default = 1). */ param1 = min(param1, s->cols); qe_term_put_char(s, offset, ' ', param1); break; case 'Z': /* CBT: Cursor Backward Tabulation Ps tab stops (default = 1). */ qe_term_goto_tab(s, -param1); break; case 'b': /* REP: Repeat the preceding graphic character Ps times. */ /* XXX: utf-8 issue for non ASCII characters */ param1 = min(param1, s->cols); s->cur_offset = qe_term_put_char(s, offset, s->lastc, param1); break; case 'c': /* DA: Send Device Attributes (Primary DA) */ // default param is 0 if (s->params[0] <= 0) { /* Report Advanced Video option (AVO) */ qe_term_write(s, "\033[?1;2c", -1); } break; case ESC2('>','c'): /* DA: Send Device Attributes (Secondary DA) */ // default param is 0 if (s->params[0] <= 0) { /* Report Qemacs emulator version 0.5 */ qe_term_write(s, "\033[>42;0;5c", -1); } break; case 'd': /* VPA: Line Position Absolute [row] (default = 1). */ param1 = min(param1, s->rows); qe_term_goto_xy(s, 0, param1 - 1, 1); break; case 'g': /* TBC: Tab Clear. */ // 0 (default) -> Clear Current Column, 3 -> Clear All */ TRACE_MSG(s, "clear tabs"); break; case 'h': /* SM: Set Mode. */ for (i = 0; i < s->nb_params; i++) { switch (s->params[i]) { case 2: /* Keyboard Action Mode (AM). */ case 4: /* Insert Mode (IRM). */ case 12: /* Send/receive (SRM). */ case 20: /* Automatic Newline (LNM). */ break; } } break; case ESC2('?','h'): /* DEC Private Mode Set (DECSET) */ for (i = 0; i < s->nb_params; i++) { switch (s->params[i]) { case 1: /* Application Cursor Keys (DECCKM). */ case 3: /* Set Number of Columns to 132 (DECCOLM) */ case 4: /* Smooth (Slow) Scroll (DECSCLM). */ break; case 5: /* Reverse Video (DECSCNM) */ s->reverse = 1; break; case 7: /* Wraparound Mode (DECAWM). */ break; case 12: /* Start Blinking Cursor (att610). */ break; case 25: /* Show Cursor (DECTCEM). */ break; case 1000: /* Send Mouse X & Y on button press and release. */ break; case 1034: /* Interpret "meta" key, sets eighth bit. * (enables the eightBitInput resource). */ break; case 1047: /* Use Alternate Screen Buffer. */ case 1048: /* Save cursor as in DECSC. */ case 1049: /* Save cursor as in DECSC and use Alternate Screen Buffer, clearing it first. */ if (s->shell_flags & SF_INTERACTIVE) { /* only grab keys in interactive qe_term buffers */ s->grab_keys = 1; qe_grab_keys(shell_key, s); /* Should also clear screen */ } if (!s->use_alternate_screen) { offset = s->b->total_size; if (eb_prevc(s->b, offset, &offset1) != '\n') { qe_term_set_style(s); offset += eb_insert_uchar(s->b, offset, '\n'); } s->use_alternate_screen = 1; s->cur_offset = s->alternate_screen_top = offset; } break; default: TRACE_MSG(s, "mode set"); break; } } break; case 'i': /* MC: Media Copy */ for (i = 0; i < s->nb_params; i++) { switch (s->params[i]) { case 0: /* Print screen (default). */ case 4: /* Turn off printer controller mode. */ case 5: /* Turn on printer controller mode. */ case 10: /* HTML screen dump. */ case 11: /* SVG screen dump. */ break; } } TRACE_MSG(s, "media copy"); break; case ESC2('?','i'): /* MC: Media Copy (MC, DEC-specific). */ for (i = 0; i < s->nb_params; i++) { switch (s->params[i]) { case 1: /* Print line containing cursor. */ case 4: /* Turn off autoprint mode. */ case 5: /* Turn on autoprint mode. */ case 10: /* Print composed display, ignores DECPEX. */ case 11: /* Print all pages. */ break; } } TRACE_MSG(s, "media copy"); break; case 'l': /* RM: Reset Mode. */ for (i = 0; i < s->nb_params; i++) { switch (s->params[i]) { case 2: /* Keyboard Action Mode (AM). */ case 4: /* Insert Mode (IRM). */ case 12: /* Send/receive (SRM). */ case 20: /* Automatic Newline (LNM). */ break; } } break; case ESC2('?','l'): /* DEC Private Mode Reset (DECRST). */ for (i = 0; i < s->nb_params; i++) { switch (s->params[i]) { case 1: /* Normal Cursor Keys (DECCKM). */ case 3: /* Set Number of Columns to 80 (DECCOLM) */ case 4: /* Jump (Fast) Scroll (DECSCLM). */ break; case 5: /* Normal Video (DECSCNM). */ s->reverse = 1; break; case 7: /* No Wraparound Mode (DECAWM). */ break; case 12: /* Stop Blinking Cursor (att610). */ break; case 25: /* Hide Cursor (DECTCEM). */ break; case 1000: /* Don't send Mouse X & Y on button press and release. */ case 1034: /* Don't interpret "meta" key. (This disables the eightBitInput resource). */ break; case 1047: /* Use Normal Screen Buffer, clearing screen first if in the Alternate Screen. */ case 1048: /* Restore cursor as in DECRC. */ case 1049: /* Use Normal Screen Buffer and restore cursor as in DECRC. */ if (s->shell_flags & SF_INTERACTIVE) { qe_ungrab_keys(); s->grab_keys = 0; } if (s->use_alternate_screen) { qe_term_goto_xy(s, 0, s->rows, 0); eb_delete_range(s->b, s->cur_offset, s->b->total_size); s->use_alternate_screen = 0; } s->cur_offset = s->b->total_size; break; default: TRACE_MSG(s, "mode reset"); break; } } break; case 'm': /* SGR: Set Graphics Rendition (Character Attributes). */ for (i = 0; i < s->nb_params;) { i += qe_term_csi_m(s, s->params + i, s->nb_params - i); } break; case 'n': /* DSR: Device Status Report. */ if (param1 == 5) { /* Status Report: terminal is OK */ qe_term_write(s, "\033[0n", -1); } else if (param1 == 6) { /* Report Cursor Position (CPR) [row;column]. */ char buf2[20]; int col_num, cur_line; eb_get_pos(s->b, &cur_line, &col_num, offset); /* XXX: actually send position of point in window */ snprintf(buf2, sizeof(buf2), "\033[%d;%dR", 1, col_num + 1); qe_term_write(s, buf2, -1); } break; case 'p': /* DECSCL: Set conformance level. */ case 'q': /* DECLL: Load LEDs. */ case ESC2(' ','q'): /* Set cursor style (DECSCUSR, VT520). */ goto unhandled; case 'r': /* DECSTBM: Set Scrolling Region [top;bottom] (default = full size of window) */ /* XXX: the scrolling region should also affect LF operation */ s->scroll_top = clamp(s->params[0] - 1, 0, s->rows); s->scroll_bottom = s->params[1] > 0 ? clamp(s->params[1], 1, s->rows) : s->rows; break; case ESC2('$','r'): /* DECCARA: Change Attributes in Rectangular Area, VT400 and up. */ goto unhandled; case ESC2('?','r'): /* Restore DEC Private Mode Values. The value of Ps previously saved is restored. Ps values are the same as for DECSET. */ TRACE_MSG(s, "mode restore"); break; case ESC2('?','s'): /* Save DEC Private Mode Values. Ps values are the same as for DECSET.. */ TRACE_MSG(s, "mode save"); break; case 's': /* Save cursor (ANSI.SYS), available only when DECLRMM is disabled. */ /* DECSLRM: Set left and right margins, available only when DECLRMM is enabled (VT420 and up). */ qe_term_get_pos(s, offset, NULL, &s->save_x, &s->save_y); break; case 't': /* DECSLPP: set page size - ie window height */ /* also used for window changing and reports */ TRACE_MSG(s, "set page size"); break; case ESC2('>','t'): /* Set one or more features of the title modes. */ // Ps = 0 -> Set window/icon labels using hexadecimal. // Ps = 1 -> Query window/icon labels using hexadecimal. // Ps = 2 -> Set window/icon labels using UTF-8. // Ps = 3 -> Query window/icon labels using UTF-8. (See discussion of "Title Modes") break; case ESC2('$','t'): /* DECRARA: Reverse Attributes in Rectangular Area, VT400 and up. */ goto unhandled; case 'u': /* Restore cursor (ANSI.SYS). */ qe_term_goto_xy(s, s->save_x, s->save_y, 0); break; case ESC2('$','v'): /* DECCRA: Copy Rectangular Area (VT400 and up). */ goto unhandled; case 'x': /* DECREQTPARM: Request Terminal Parameters */ case ESC2('$','x'): /* DECFRA: Fill Rectangular Area, VT420 and up. */ case ESC2('$','z'): /* DECERA: Erase Rectangular Area, VT400 and up. */ case ESC2('$','{'): /* DECSERA: Selective Erase Rectangular Area, VT400 and up. */ case ESC2('\'','{'): /* DECSLE: Select Locator Events. */ case ESC2('\'','|'): /* DECRQLP: Request Locator Position. */ case ESC2('\'','}'): /* DECIC: Insert Ps Column(s) (default = 1), VT420 and up. */ case ESC2('\'','~'): /* DECDC: Delete Ps Column(s) (default = 1), VT420 and up. */ case ESC2('=','c'): /* Hide or Show Cursor */ /* 0: hide, 1: restore, 2: block */ case ESC2('=','C'): /* set cursor shape */ case ESC2('=','D'): /* set blinking attr on/off */ case ESC2('=','E'): /* set blinking on/off */ goto unhandled; case ESC2('=','F'): /* select SCO foreground color */ s->fgcolor = sco_color[param1 & 15]; break; case ESC2('=','G'): /* select SCO background color */ s->bgcolor = sco_color[param1 & 15]; break; default: unhandled: TRACE_MSG(s, "unhandled"); break; } break; } #undef ESC2 qe_term_update_cursor(s); } /* buffer related functions */ /* called when characters are available from the process */ static void shell_read_cb(void *opaque) { ShellState *s = opaque; QEmacsState *qs; EditBuffer *b; unsigned char buf[16 * 1024]; int len, i, save_readonly; if (!s || s->base.mode != &shell_mode) return; len = read(s->pty_fd, buf, sizeof(buf)); if (len <= 0) return; b = s->b; qs = s->qe_state; if (qs->trace_buffer) { eb_trace_bytes(buf, len, EB_TRACE_SHELL); } /* Suspend BF_READONLY flag to allow shell output to readonly buffer */ save_readonly = b->flags & BF_READONLY; b->flags &= ~BF_READONLY; b->last_log = 0; if (s->shell_flags & SF_COLOR) { /* optional terminal emulation (shell, ssh, make, latex, man modes) */ for (i = 0; i < len; i++) { qe_term_emulate(s, buf[i]); } if (s->last_char == '\000' || s->last_char == '\001' || s->last_char == '\003' || s->last_char == '\r' || s->last_char == '\n') { /* if the last char sent to the process was the enter key, C-C * to kill the process, C-A to go to beginning of line, or if * nothing was sent to the process yet, assume the process is * prompting for input and save the current input position as * the start of input. */ s->cur_prompt = s->cur_offset; if (qs->active_window && qs->active_window->b == b && qs->active_window->interactive) { /* Set mark to potential tentative position (useful?) */ b->mark = s->cur_prompt; } } shell_get_curpath(b, s->cur_offset, s->curpath, sizeof(s->curpath)); } else { int pos = b->total_size; int threshold = 3 << 20; /* 3MB for large pictures */ eb_write(b, b->total_size, buf, len); if (pos < threshold && pos + len >= threshold) { EditState *e; for (e = qs->first_window; e != NULL; e = e->next_window) { if (e->b == b) { if (s->shell_flags & SF_AUTO_CODING) do_set_auto_coding(e, 0); if (s->shell_flags & SF_AUTO_MODE) qe_set_next_mode(e, 0, 0); } } } } if (save_readonly) { b->modified = 0; b->flags |= save_readonly; } /* now we do some refresh (should just invalidate?) */ edit_display(qs); dpy_flush(qs->screen); } static void shell_mode_free(EditBuffer *b, void *state) { ShellState *s = state; int status; eb_free_callback(b, eb_offset_callback, &s->cur_offset); eb_free_callback(b, eb_offset_callback, &s->cur_prompt); eb_free_callback(b, eb_offset_callback, &s->alternate_screen_top); if (s->pid != -1) { kill(s->pid, SIGINT); /* wait first 100 ms */ usleep(100 * 1000); if (waitpid(s->pid, &status, WNOHANG) != s->pid) { /* if still not killed, then try harder (useful for shells) */ kill(s->pid, SIGKILL); /* CG: should add timeout facility and error message */ while (waitpid(s->pid, &status, 0) != s->pid) continue; } set_pid_handler(s->pid, NULL, NULL); s->pid = -1; } if (s->pty_fd >= 0) { set_read_handler(s->pty_fd, NULL, NULL); close(s->pty_fd); s->pty_fd = -1; } } static void shell_pid_cb(void *opaque, int status) { ShellState *s; EditBuffer *b; QEmacsState *qs; EditState *e; char buf[1024]; /* Extra check in case mode data was freed already. * It is not completely fool proof as the same address might have * been reallocated for the same purpose, quite unlikely. */ s = check_mode_data(&opaque); if (!s || s->base.mode != &shell_mode) return; b = s->b; qs = s->qe_state; *buf = '\0'; if (s->caption) { time_t ti; char *time_str; ti = time(NULL); time_str = ctime(&ti); if (WIFEXITED(status)) status = WEXITSTATUS(status); else status = -1; if (status == 0) { snprintf(buf, sizeof(buf), "\n%s finished at %s\n", s->caption, time_str); } else { snprintf(buf, sizeof(buf), "\n%s exited abnormally with code %d at %s\n", s->caption, status, time_str); } } { /* Flush output to buffer, bypassing readonly flag */ int save_readonly = s->b->flags & BF_READONLY; s->b->flags &= ~BF_READONLY; eb_write(b, b->total_size, buf, strlen(buf)); if (save_readonly) { s->b->modified = 0; s->b->flags |= save_readonly; } } set_pid_handler(s->pid, NULL, NULL); s->pid = -1; /* no need to leave the pty opened */ if (s->pty_fd >= 0) { set_read_handler(s->pty_fd, NULL, NULL); close(s->pty_fd); s->pty_fd = -1; } /* remove shell input mode */ s->grab_keys = 0; qe_ungrab_keys(); for (e = qs->first_window; e != NULL; e = e->next_window) { if (e->b == b) { e->interactive = 0; if (s->shell_flags & SF_AUTO_CODING) do_set_auto_coding(e, 0); if (s->shell_flags & SF_AUTO_MODE) qe_set_next_mode(e, 0, 0); } } if (!(s->shell_flags & SF_INTERACTIVE)) { /* Must Unlink the shell data to avoid potential crash */ //shell_mode_free(b, s); // called by qe_free_mode_data qe_free_mode_data(&s->base); } edit_display(qs); dpy_flush(qs->screen); } EditBuffer *new_shell_buffer(EditBuffer *b0, EditState *e, const char *bufname, const char *caption, const char *path, const char *cmd, int shell_flags) { QEmacsState *qs = &qe_state; ShellState *s; EditBuffer *b; const char *lang; int cols, rows; b = b0; if (!b) { b = eb_new(bufname, BF_SAVELOG); if (!b) return NULL; } eb_set_buffer_name(b, bufname); /* ensure that the name is unique */ if (shell_flags & SF_COLOR) { eb_create_style_buffer(b, QE_TERM_STYLE_BITS <= 16 ? BF_STYLE2 : QE_TERM_STYLE_BITS <= 32 ? BF_STYLE4 : BF_STYLE8); } /* Select shell output buffer encoding from LANG setting */ if (((lang = getenv("LANG")) != NULL && strstr(lang, "UTF-8")) || qs->screen->charset == &charset_utf8) { eb_set_charset(b, &charset_utf8, b->eol_type); } else { eb_set_charset(b, &charset_vt100, b->eol_type); } s = qe_get_buffer_mode_data(b, &shell_mode, NULL); if (!s) { s = (ShellState*)qe_create_buffer_mode_data(b, &shell_mode); if (!s) { if (!b0) eb_free(&b); return NULL; } /* Track cursor with edge effect */ eb_add_callback(b, eb_offset_callback, &s->cur_offset, 1); eb_add_callback(b, eb_offset_callback, &s->cur_prompt, 0); eb_add_callback(b, eb_offset_callback, &s->alternate_screen_top, 0); } s->b = b; s->pty_fd = -1; s->pid = -1; s->qe_state = qs; s->caption = caption; s->shell_flags = shell_flags; s->cur_prompt = s->cur_offset = b->total_size; qe_term_init(s); /* launch shell */ /* default values for cols, rows should come from the screen size */ cols = QE_TERM_XSIZE; rows = QE_TERM_YSIZE; if (e) { cols = e->cols; rows = e->rows; } s->cols = cols; s->rows = rows; if (run_process(cmd, &s->pty_fd, &s->pid, cols, rows, path, shell_flags) < 0) { if (!b0) eb_free(&b); return NULL; } /* XXX: ShellState life cycle is bogus */ set_read_handler(s->pty_fd, shell_read_cb, s); set_pid_handler(s->pid, shell_pid_cb, s); return b; } /* If a window is attached to buffer b, activate it, otherwise attach window s to buffer b. */ static EditBuffer *try_show_buffer(EditState **sp, const char *bufname) { EditState *e, *s = *sp; QEmacsState *qs = s->qe_state; EditBuffer *b; b = eb_find(bufname); if (b && s->b != b) { e = eb_find_window(b, NULL); if (e) { qs->active_window = *sp = e; } else { switch_to_buffer(s, b); } } return b; } static void do_shell(EditState *e, int force) { char curpath[MAX_FILENAME_SIZE]; EditBuffer *b = NULL; if (e->flags & (WF_POPUP | WF_MINIBUF)) return; /* Start the shell in the default directory of the current window */ get_default_path(e->b, e->offset, curpath, sizeof curpath); /* avoid messing with the dired pane */ e = qe_find_target_window(e, 1); /* find shell buffer if any */ if (!force || force == NO_ARG) { if (strstart(e->b->name, "*shell", NULL)) { /* If the current buffer is a shell buffer, use it */ b = e->b; } else { /* Find the last used shell buffer, if any */ if (strstart(error_buffer, "*shell", NULL)) { b = try_show_buffer(&e, error_buffer); } if (b == NULL) { b = try_show_buffer(&e, "*shell*"); } } if (b) { /* If the process is active, switch to interactive mode */ ShellState *s = shell_get_state(e, 0); if (s && s->pid >= 0) { e->offset = b->total_size; if ((s->shell_flags & SF_INTERACTIVE) && !s->grab_keys) { e->offset = s->cur_offset; e->interactive = 1; } return; } /* otherwise, restart the process here */ e->offset = b->total_size; /* restart the shell in the same directory */ get_default_path(e->b, e->offset, curpath, sizeof curpath); } } /* create new shell buffer or restart shell in current buffer */ b = new_shell_buffer(b, e, "*shell*", "Shell process", curpath, NULL, SF_COLOR | SF_INTERACTIVE); if (!b) return; b->default_mode = &shell_mode; switch_to_buffer(e, b); /* force interactive mode if restarting */ shell_mode.mode_init(e, b, 0); set_error_offset(b, 0); put_status(e, "Press C-o to toggle between shell/edit mode"); } static void do_man(EditState *s, const char *arg) { char bufname[32]; char cmd[128]; EditBuffer *b; if (s->flags & (WF_POPUP | WF_MINIBUF)) return; if (s->flags & WF_POPLEFT) { /* avoid messing with the dired pane */ s = find_window(s, KEY_RIGHT, s); s->qe_state->active_window = s; } /* Assume standard man command */ snprintf(cmd, sizeof(cmd), "man %s", arg); snprintf(bufname, sizeof(bufname), "*Man %s*", arg); if (try_show_buffer(&s, bufname)) return; /* create new buffer */ b = new_shell_buffer(NULL, s, bufname, NULL, NULL, cmd, SF_COLOR | SF_INFINITE); if (!b) return; b->data_type_name = "man"; b->flags |= BF_READONLY; switch_to_buffer(s, b); edit_set_mode(s, &pager_mode); } static void do_ssh(EditState *s, const char *arg) { char bufname[64]; char cmd[128]; EditBuffer *b; if (s->flags & (WF_POPUP | WF_MINIBUF)) return; if (s->flags & WF_POPLEFT) { /* avoid messing with the dired pane */ s = find_window(s, KEY_RIGHT, s); s->qe_state->active_window = s; } /* Use standard ssh command */ snprintf(cmd, sizeof(cmd), "ssh %s", arg); snprintf(bufname, sizeof(bufname), "*ssh-%s*", arg); /* create new buffer */ b = new_shell_buffer(NULL, s, bufname, "ssh", NULL, cmd, SF_COLOR | SF_INTERACTIVE); if (!b) return; b->data_type_name = "ssh"; b->default_mode = &shell_mode; switch_to_buffer(s, b); put_status(s, "Press C-o to toggle between shell/edit mode"); } static void shell_move_left_right(EditState *e, int dir) { ShellState *s = shell_get_state(e, 1); if (s && e->interactive) { qe_term_write(s, dir > 0 ? s->kcuf1 : s->kcub1, -1); } else { text_move_left_right_visual(e, dir); } } static void shell_move_word_left_right(EditState *e, int dir) { ShellState *s = shell_get_state(e, 1); if (s && e->interactive) { qe_term_write(s, dir > 0 ? "\033f" : "\033b", -1); } else { text_move_word_left_right(e, dir); } } static void shell_move_up_down(EditState *e, int dir) { ShellState *s = shell_get_state(e, 1); if (s && e->interactive) { qe_term_write(s, dir > 0 ? s->kcud1 : s->kcuu1, -1); } else { text_move_up_down(e, dir); // XXX: what if beyond? if (s && (s->shell_flags & SF_INTERACTIVE) && !s->grab_keys) e->interactive = (e->offset == s->cur_offset); } } static void shell_previous_next(EditState *e, int dir) { ShellState *s = shell_get_state(e, 1); if (s && e->interactive) { /* hack: M-p silently converted to C-p */ qe_term_write(s, dir > 0 ? s->kcud1 : s->kcuu1, -1); } else { /* hack: M-p silently converted to C-u C-p */ text_move_up_down(e, dir * 4); // XXX: what if beyond? if (s && (s->shell_flags & SF_INTERACTIVE) && !s->grab_keys) e->interactive = (e->offset == s->cur_offset); } } static void shell_exchange_point_and_mark(EditState *e) { ShellState *s = shell_get_state(e, 1); if (s && e->interactive) { qe_term_write(s, "\030\030", 2); /* C-x C-x */ } else { do_exchange_point_and_mark(e); // XXX: what if beyond? if (s && (s->shell_flags & SF_INTERACTIVE) && !s->grab_keys) e->interactive = (e->offset == s->cur_offset); } } static void shell_scroll_up_down(EditState *e, int dir) { ShellState *s = shell_get_state(e, 1); e->interactive = 0; text_scroll_up_down(e, dir); // XXX: what if beyond? if (s && (s->shell_flags & SF_INTERACTIVE) && !s->grab_keys) e->interactive = (e->offset == s->cur_offset); } static void shell_move_bol(EditState *e) { ShellState *s = shell_get_state(e, 1); /* exit shell interactive mode on home / ^A at start of shell input */ if (!s || (e->offset == s->cur_prompt && !s->grab_keys)) e->interactive = 0; if (s && e->interactive) { qe_term_write(s, "\001", 1); /* Control-A */ } else { text_move_bol(e); } } static void shell_move_eol(EditState *e) { ShellState *s = shell_get_state(e, 1); if (s && e->interactive) { qe_term_write(s, "\005", 1); /* Control-E */ } else { text_move_eol(e); /* XXX: restore shell interactive mode on end / ^E */ if (s && (s->shell_flags & SF_INTERACTIVE) && !s->grab_keys && e->offset >= s->cur_offset) { e->interactive = 1; if (e->offset > s->cur_offset) qe_term_write(s, "\005", 1); /* Control-E */ } } } static void shell_move_bof(EditState *e) { /* Exit shell interactive mode on home-buffer / M-< */ e->interactive = 0; text_move_bof(e); } static void shell_move_eof(EditState *e) { ShellState *s = shell_get_state(e, 1); if (s && e->interactive) { qe_term_write(s, "\005", 1); /* Control-E */ } else { text_move_eof(e); /* Restore shell interactive mode on end-buffer / M-> */ if (s && (s->shell_flags & SF_INTERACTIVE) && !s->grab_keys && e->offset >= s->cur_offset) { e->interactive = 1; if (e->offset != s->cur_offset) qe_term_write(s, "\005", 1); /* Control-E */ } } } static void shell_write_char(EditState *e, int c) { ShellState *s = shell_get_state(e, 1); if (s && e->interactive) { char buf[10]; int len; if (c >= KEY_META(0) && c <= KEY_META(0xff)) { buf[0] = '\033'; buf[1] = c - KEY_META(0); len = 2; } else { len = eb_encode_uchar(e->b, buf, c); } qe_term_write(s, buf, len); } else { text_write_char(e, c); } } static void shell_delete_bytes(EditState *e, int offset, int size) { ShellState *s = shell_get_state(e, 1); int start = offset; int end = offset + size; // XXX: should deal with regions spanning current input line and // previous buffer contents if (s && !s->grab_keys && end > s->cur_prompt) { int start_char, cur_char, end_char, size; if (start < s->cur_prompt) { /* delete part before the interactive input */ size = eb_delete_range(e->b, start, s->cur_prompt); end -= size; start = s->cur_prompt; } start_char = eb_get_char_offset(e->b, start); cur_char = eb_get_char_offset(e->b, s->cur_offset); end_char = eb_get_char_offset(e->b, end); if (start == s->cur_prompt && cur_char > start_char + 2) { qe_term_write(s, "\001", 1); /* C-a */ cur_char = start_char; } while (cur_char > end_char) { qe_term_write(s, "\002", 1); /* C-b */ cur_char--; } while (cur_char < start_char) { qe_term_write(s, "\006", 1); /* C-f */ cur_char++; } if (start_char == cur_char && end == e->b->total_size) { /* kill to end of line with control-k */ qe_term_write(s, "\013", 1); } else { while (cur_char < end_char) { qe_term_write(s, "\004", 1); /* C-d */ end_char--; } while (start_char < cur_char) { qe_term_write(s, "\010", 1); /* backspace */ cur_char--; end_char--; } } } else { eb_delete(e->b, offset, size); } } static void do_shell_enter(EditState *e) { struct timespec ts; if (e->interactive) { ShellState *s = shell_get_state(e, 1); if (s) { shell_get_curpath(e->b, e->offset, s->curpath, sizeof(s->curpath)); } shell_write_char(e, '\r'); /* give the process a chance to handle the input */ ts.tv_sec = 0; ts.tv_nsec = 1000000; /* 1 ms */ nanosleep(&ts, NULL); } else { do_return(e, 1); } /* reset offset to scan errors and matches from */ set_error_offset(e->b, e->offset); } static void do_shell_intr(EditState *e) { if (e->interactive) { shell_write_char(e, 3); } else { text_write_char(e, 3); } } static void do_shell_delete_char(EditState *e) { if (e->interactive) { shell_write_char(e, 4); } else { do_delete_char(e, NO_ARG); } } static void do_shell_backspace(EditState *e) { if (e->interactive) { shell_write_char(e, KEY_DEL); } else { do_backspace(e, NO_ARG); } } static void do_shell_kill_word(EditState *e, int dir) { ShellState *s = shell_get_state(e, 1); if (s && e->interactive) { /* copy word to the kill ring */ int start = e->offset; text_move_word_left_right(e, dir); if (e->offset < s->cur_prompt) { e->offset = s->cur_prompt; } do_kill(e, start, e->offset, dir, 1); // XXX: word pattern consistency issue shell_write_char(e, dir > 0 ? KEY_META('d') : KEY_META(KEY_DEL)); } else { do_kill_word(e, dir); } } static void do_shell_kill_line(EditState *e, int argval) { ShellState *s = shell_get_state(e, 1); int dir = (argval == NO_ARG || argval > 0) ? 1 : -1; int offset, p1 = e->offset, p2 = p1; if (s && e->interactive) { /* ignore count argument in interactive mode */ if (dir < 0) { /* kill backwards upto prompt position */ p2 = max(eb_goto_bol(e->b, p1), s->cur_prompt); do_kill(e, p1, p2, dir, 0); //shell_write_char(e, KEY_META('k')); } else { p2 = eb_goto_eol(e->b, p1); do_kill(e, p1, p2, dir, 0); //shell_write_char(e, KEY_CTRL('k')); } } else { /* Cannot use do_kill_line() because it relies on mode specific * cursor movement methods, which are handled asynchronously in * shell mode. */ if (argval == NO_ARG) { /* kill to end of line */ if (eb_nextc(e->b, p2, &offset) == '\n') { p2 = offset; } else { p2 = eb_goto_eol(e->b, p2); } } else if (argval <= 0) { /* kill backwards */ dir = -1; for (;;) { p2 = eb_goto_bol(e->b, p2); if (p2 <= 0 || argval == 0) break; p2 = eb_prev(e->b, p2); argval += 1; } } else { for (;;) { p2 = eb_goto_eol(e->b, p2); if (p2 >= e->b->total_size || argval == 0) break; p2 = eb_next(e->b, p2); argval -= 1; } } e->offset = p2; do_kill(e, p1, p2, dir, 0); } } static void do_shell_kill_beginning_of_line(EditState *s, int argval) { do_shell_kill_line(s, argval == NO_ARG ? 0 : -argval); } // XXX: need shell_set_mark, shell_kill_region... static void do_shell_yank(EditState *e) { if (e->interactive) { /* yank from kill-ring and insert via shell_write_char(). * This will cause a deadlock if kill buffer contents is too * large. Hard coded limit can be removed if shell input is * made asynchronous via an auxiliary buffer. */ int offset; QEmacsState *qs = e->qe_state; EditBuffer *b = qs->yank_buffers[qs->yank_current]; e->b->mark = e->offset; if (b) { if (b->total_size > 1024) { put_status(e, "too much data to yank at shell prompt"); return; } for (offset = 0; offset < b->total_size;) { int c = eb_nextc(b, offset, &offset); if (c == '\n') do_shell_enter(e); else shell_write_char(e, c); } } qs->this_cmd_func = (CmdFunc)do_yank; } else { do_yank(e); } } static void do_shell_changecase_word(EditState *e, int dir) { ShellState *s = shell_get_state(e, 1); if (s && e->interactive) { // XXX: word pattern consistency issue shell_write_char(e, dir == 2 ? KEY_META('c') : dir < 0 ? KEY_META('l') : KEY_META('u')); } else { do_changecase_word(e, dir); } } static void do_shell_transpose(EditState *e, int cmd) { ShellState *s = shell_get_state(e, 1); if (s && e->interactive) { shell_write_char(e, cmd == CMD_TRANSPOSE_CHARS ? KEY_CTRL('T') : KEY_META('t')); } else { do_transpose(e, cmd); } } static void do_shell_tabulate(EditState *e) { if (e->interactive) { shell_write_char(e, 9); } else { text_write_char(e, 9); } } static void do_shell_refresh(EditState *e) { ShellState *s; if ((s = shell_get_state(e, 1)) != NULL) { QEmacsState *qs = e->qe_state; EditState *e1; /* update the terminal size and notify process */ s->cols = e->wrap_cols = e->cols; s->rows = e->rows; for (e1 = qs->first_window; e1 != NULL; e1 = e1->next_window) { if (e1->b == e->b) { e1->wrap_cols = s->cols; } } if (s->pty_fd > 0) { struct winsize ws; ws.ws_col = s->cols; ws.ws_row = s->rows; ws.ws_xpixel = ws.ws_col; ws.ws_ypixel = ws.ws_row; ioctl(s->pty_fd, TIOCSWINSZ, &ws); } } do_refresh_complete(e); if (s) { put_status(e, "terminal size set to %d by %d", s->cols, s->rows); } } static void do_shell_toggle_input(EditState *e) { ShellState *s; if (!(s = shell_get_state(e, 1))) return; if (e->interactive) { e->interactive = 0; } else if (s->shell_flags & SF_INTERACTIVE) { e->interactive = 1; if (s->grab_keys) qe_grab_keys(shell_key, s); } #if 0 if (e->interactive) { qe_term_update_cursor(s); } #endif } /* get current directory from prompt on current line */ /* XXX: should extend behavior to handle more subtile cases */ static char *shell_get_curpath(EditBuffer *b, int offset, char *buf, int buf_size) { char line[1024]; char curpath[MAX_FILENAME_SIZE]; int start, last_blank, stop, i, len; len = eb_fgets(b, line, sizeof(line), eb_goto_bol(b, offset), &offset); line[len] = '\0'; /* strip the trailing newline if any */ start = 0; last_blank = 0; for (i = 0;; i++) { int c = line[i]; if (c == '\0') return NULL; if (c == '#' || c == '$' || c == '>') break; if (c == ':' && !start) start = i + 1; if (c == ' ') { if (!start) start = i + 1; last_blank = i; } } stop = i; if (last_blank == i - 1) stop = last_blank; line[stop] = '\0'; if (start == stop) return NULL; /* XXX: should use a lower level function to avoid potential recursion */ canonicalize_absolute_path(NULL, curpath, sizeof curpath, line + start); if (!is_directory(curpath)) return NULL; append_slash(curpath, sizeof curpath); return pstrcpy(buf, buf_size, curpath); } static char *shell_get_default_path(EditBuffer *b, int offset, char *buf, int buf_size) { ShellState *s = qe_get_buffer_mode_data(b, &shell_mode, NULL); if (s && (s->curpath[0] || shell_get_curpath(b, offset, s->curpath, sizeof(s->curpath)))) { return pstrcpy(buf, buf_size, s->curpath); } return shell_get_curpath(b, offset, buf, buf_size); } static void do_shell_command(EditState *e, const char *cmd) { char curpath[MAX_FILENAME_SIZE]; EditBuffer *b; get_default_path(e->b, e->offset, curpath, sizeof curpath); /* if the buffer already exists, kill it */ b = eb_find("*shell command output*"); if (b) { qe_kill_buffer(b); } /* create new buffer */ b = new_shell_buffer(NULL, e, "*shell command output*", NULL, curpath, cmd, SF_COLOR | SF_INFINITE); if (!b) return; /* XXX: try to split window if necessary */ switch_to_buffer(e, b); edit_set_mode(e, &pager_mode); } static void do_compile(EditState *s, const char *cmd) { char curpath[MAX_FILENAME_SIZE]; EditBuffer *b; if (s->flags & (WF_POPUP | WF_MINIBUF)) return; get_default_path(s->b, s->offset, curpath, sizeof curpath); if (s->flags & WF_POPLEFT) { /* avoid messing with the dired pane */ s = find_window(s, KEY_RIGHT, s); s->qe_state->active_window = s; } /* if the buffer already exists, kill it */ b = eb_find("*compilation*"); if (b) { qe_kill_buffer(b); } if (!cmd || !*cmd) cmd = "make"; /* create new buffer */ b = new_shell_buffer(NULL, s, "*compilation*", "Compilation", curpath, cmd, SF_COLOR | SF_INFINITE); if (!b) return; b->data_type_name = "compile"; /* XXX: try to split window if necessary */ switch_to_buffer(s, b); edit_set_mode(s, &pager_mode); set_error_offset(b, 0); } static void do_compile_error(EditState *s, int arg, int dir) { QEmacsState *qs = s->qe_state; EditState *e; EditBuffer *b; int offset, found_offset; char filename[MAX_FILENAME_SIZE]; char fullpath[MAX_FILENAME_SIZE]; buf_t fnamebuf, *fname; int c, line_num, col_num, len; char error_message[128]; if (arg != NO_ARG) { /* called with a prefix: set the error source to the current buffer */ set_error_offset(s->b, s->offset); } /* CG: should have a buffer flag for error source. * first check if current buffer is an error source. * if not, then scan for appropriate error source * in buffer least recently used order */ if ((b = eb_find(error_buffer)) == NULL) { if ((b = eb_find("*compilation*")) == NULL && (b = eb_find("*shell*")) == NULL && (b = eb_find("*errors*")) == NULL) { put_status(s, "No compilation buffer"); return; } set_error_offset(b, -1); } /* find next/prev error */ offset = error_offset; /* CG: should use higher level parsing */ for (;;) { if (dir > 0) { offset = eb_next_line(b, offset); if (offset >= b->total_size) { put_status(s, "No more errors"); return; } } else { if (offset <= 0) { put_status(s, "No previous error"); return; } offset = eb_prev_line(b, offset); } found_offset = offset; /* parse filename:linenum:message */ /* and: filename(linenum[, col]) ...: ... */ /* extract filename */ fname = buf_init(&fnamebuf, filename, countof(filename)); for (;;) { c = eb_nextc(b, offset, &offset); if (c == '\n' || c == '\t' || c == ' ') goto next_line; if (c == ':' || c == '(') break; buf_putc_utf8(fname, c); } canonicalize_absolute_buffer_path(b, found_offset, fullpath, sizeof(fullpath), filename); /* extract line number */ for (line_num = col_num = 0;;) { c = eb_nextc(b, offset, &offset); if (c == ':' || c == ',' || c == ')') break; if (!qe_isdigit(c)) goto next_line; line_num = line_num * 10 + c - '0'; } if (c == ',' || c == ':') { int offset0 = offset; int c0 = c; for (;;) { c = eb_nextc(b, offset, &offset); if (c == ' ') continue; if (!qe_isdigit(c)) break; col_num = col_num * 10 + c - '0'; } if (col_num == 0) { offset = offset0; c = c0; } } while (c != ':') { if (c == '\n') goto next_line; c = eb_nextc(b, offset, &offset); } len = eb_fgets(b, error_message, sizeof(error_message), offset, &offset); error_message[len] = '\0'; /* strip the trailing newline if any */ if (line_num >= 1) { if (line_num != error_line_num || col_num != error_col_num || !strequal(fullpath, error_filename)) { error_line_num = line_num; error_col_num = col_num; pstrcpy(error_filename, sizeof(error_filename), fullpath); break; } } next_line: offset = found_offset; } error_offset = found_offset; /* update offsets */ for (e = qs->first_window; e != NULL; e = e->next_window) { if (e->b == b) { e->offset = error_offset; } } /* CG: Should remove popups, sidepanes, helppanes... */ /* go to the error */ /* XXX: should check for file existence */ do_find_file(s, fullpath, 0); do_goto_line(qs->active_window, line_num, col_num); put_status(s, "=> %s", error_message); } static int match_digits(unsigned int *buf, int n, unsigned int sep) { if (n >= 2 && qe_isdigit(buf[0])) { int i = 1; while (i < n && qe_isdigit(buf[i])) i++; if (buf[i] == sep) { return i + 1; } } return 0; } static int match_string(unsigned int *buf, int n, const char *str) { int i; for (i = 0; i < n && str[i] && buf[i] == str[i]; i++) continue; return (str[i] == '\0') ? i : 0; } void shell_colorize_line(QEColorizeContext *cp, unsigned int *str, int n, ModeDef *syn) { /* detect match lines for known languages and colorize accordingly */ static char filename[MAX_FILENAME_SIZE]; ModeDef *m; int i = 0, len; if (cp->colorize_state) { cp->colorize_state -= 1; } else { len = 0; if (!qe_isspace(str[0])) { while (i < n) { int c = str[i++]; if (c == "()"[0]) { i += match_digits(str + i, n - i, "()"[1]); /* old style */ i += (str[i] == ':'); break; } if (c == ':') { i += match_digits(str + i, n - i, ':'); /* line number */ i += match_digits(str + i, n - i, ':'); /* col number */ if (match_string(str + i, n - i, " error:") || match_string(str + i, n - i, " note:") || match_string(str + i, n - i, " warning:")) { /* clang diagnostic, will colorize the next line */ cp->colorize_state = 1; i = n; } break; } if (qe_isspace(c)) { len = 0; break; } if (len < countof(filename) - 1) { /* capture filename extension */ filename[len++] = c; } } } filename[len] = '\0'; } if (cp->colorize_state == 0 && *filename) { /* XXX: should verify if filename exists, but this is difficult * if the current directory of the shell process changes and * is not possible for remote shells. */ /* XXX: filename based mode should be stored as a number into cp->colorize_state */ if (i < n && (m = qe_find_mode_filename(filename, MODEF_SYNTAX)) != NULL) { cp->colorize_state = 0; m->colorize_func(cp, str + i, n - i, m); cp->combine_stop = i; } cp->colorize_state = 0; } } /* shell mode specific commands */ static CmdDef shell_commands[] = { CMD0( KEY_CTRL('o'), KEY_NONE, "shell-toggle-input", do_shell_toggle_input) CMD2( '\r', KEY_NONE, "shell-enter", do_shell_enter, ES, "*") /* CG: should send s->kbs */ CMD2( KEY_DEL, KEY_NONE, "shell-backward-delete-char", do_shell_backspace, ES, "*") CMD0( KEY_CTRLC(KEY_CTRL('c')), KEY_NONE, /* C-c C-c */ "shell-intr", do_shell_intr) CMD2( KEY_CTRL('d'), KEY_DELETE, "shell-delete-char", do_shell_delete_char, ES, "*") CMD3( KEY_META('d'), KEY_NONE, "shell-kill-word", do_shell_kill_word, ESi, 1, "v") CMD3( KEY_META(KEY_DEL), KEY_META(KEY_BS) , "shell-backward-kill-word", do_shell_kill_word, ESi, -1, "v") CMD1( KEY_META('p'), KEY_NONE, "shell-previous", shell_previous_next, -1) CMD1( KEY_META('n'), KEY_NONE, "shell-next", shell_previous_next, 1) CMD0( KEY_CTRLX(KEY_CTRL('x')), KEY_NONE, "shell-exchange-point-and-mark", shell_exchange_point_and_mark) CMD2( KEY_CTRL('i'), KEY_NONE, "shell-tabulate", do_shell_tabulate, ES, "*") CMD0( KEY_CTRL('l'), KEY_NONE, "shell-refresh", do_shell_refresh) CMD2( KEY_CTRL('k'), KEY_NONE, "shell-kill-line", do_shell_kill_line, ESi, "ui") CMD2( KEY_META('k'), KEY_NONE, "shell-kill-beginning-of-line", do_shell_kill_beginning_of_line, ESi, "ui") CMD2( KEY_CTRL('y'), KEY_NONE, "shell-yank", do_shell_yank, ES, "*") CMD3( KEY_META('c'), KEY_NONE, "shell-capitalize-word", do_shell_changecase_word, ESi, 2, "*v") CMD3( KEY_META('l'), KEY_NONE, "shell-downcase-word", do_shell_changecase_word, ESi, -1, "*v") CMD3( KEY_META('u'), KEY_NONE, "shell-upcase-word", do_shell_changecase_word, ESi, 1, "*v") CMD3( KEY_CTRL('t'), KEY_NONE, "shell-transpose-chars", do_shell_transpose, ESi, CMD_TRANSPOSE_CHARS, "*v") CMD3( KEY_META('t'), KEY_NONE, "shell-transpose-words", do_shell_transpose, ESi, CMD_TRANSPOSE_WORDS, "*v") CMD_DEF_END, }; /* shell global commands */ static CmdDef shell_global_commands[] = { CMD2( KEY_CTRLXRET('\r'), KEY_NONE, "shell", do_shell, ESi, "ui") CMD2( KEY_META('!'), KEY_NONE, "shell-command", do_shell_command, ESs, "s{Shell command: }|shell-command|") CMD2( KEY_NONE, KEY_NONE, "ssh", do_ssh, ESs, "s{Open connection to (host or user@host: }|ssh|") CMD2( KEY_CTRLX(KEY_CTRL('e')), KEY_NONE, "compile", do_compile, ESs, "s{Compile command: }|compile|") CMD2( KEY_CTRLX('m'), KEY_NONE, "make", do_compile, ESs, "S{make}") CMD2( KEY_NONE, KEY_NONE, "man", do_man, ESs, "s{Show man page for: }|man|") CMD3( KEY_CTRLX(KEY_CTRL('p')), KEY_NONE, "previous-error", do_compile_error, ESii, -1, "uiv") CMD3( KEY_CTRLX(KEY_CTRL('n')), KEY_CTRLX('`'), "next-error", do_compile_error, ESii, 1, "uiv") CMD_DEF_END, }; static int shell_mode_probe(ModeDef *mode, ModeProbeData *p) { ShellState *s = qe_get_buffer_mode_data(p->b, &shell_mode, NULL); if (s && s->shell_flags & SF_INTERACTIVE) return 100; return 0; } static int shell_mode_init(EditState *e, EditBuffer *b, int flags) { if (e) { ShellState *s; if (!(s = shell_get_state(e, 1))) return -1; e->b->tab_width = 8; /* XXX: should come from mode.default_wrap */ e->wrap = WRAP_TERM; e->wrap_cols = s->cols; if ((s->shell_flags & SF_INTERACTIVE) && !s->grab_keys) e->interactive = 1; } return 0; } static int pager_mode_init(EditState *e, EditBuffer *b, int flags) { if (e) { e->b->tab_width = 8; /* XXX: should come from mode.default_wrap */ e->wrap = WRAP_TRUNCATE; } return 0; } static int shell_init(void) { /* populate and register shell mode and commands */ memcpy(&shell_mode, &text_mode, sizeof(ModeDef)); shell_mode.name = "shell"; shell_mode.mode_probe = shell_mode_probe; shell_mode.colorize_func = shell_colorize_line, shell_mode.buffer_instance_size = sizeof(ShellState); shell_mode.mode_init = shell_mode_init; shell_mode.mode_free = shell_mode_free; shell_mode.display_hook = shell_display_hook; shell_mode.move_left_right = shell_move_left_right; shell_mode.move_word_left_right = shell_move_word_left_right; shell_mode.move_up_down = shell_move_up_down; shell_mode.scroll_up_down = shell_scroll_up_down; shell_mode.move_bol = shell_move_bol; shell_mode.move_eol = shell_move_eol; shell_mode.move_bof = shell_move_bof; shell_mode.move_eof = shell_move_eof; shell_mode.write_char = shell_write_char; shell_mode.delete_bytes = shell_delete_bytes; shell_mode.get_default_path = shell_get_default_path; qe_register_mode(&shell_mode, MODEF_NOCMD | MODEF_VIEW); qe_register_cmd_table(shell_commands, &shell_mode); /* global shell related commands and default keys */ qe_register_cmd_table(shell_global_commands, NULL); /* populate and register pager mode and commands */ memcpy(&pager_mode, &text_mode, sizeof(ModeDef)); pager_mode.name = "pager"; pager_mode.mode_probe = NULL; pager_mode.mode_init = pager_mode_init; qe_register_mode(&pager_mode, MODEF_NOCMD | MODEF_VIEW); qe_mode_set_key(&pager_mode, "DEL", "scroll-down"); qe_mode_set_key(&pager_mode, "SPC", "scroll-up"); qe_mode_set_key(&pager_mode, "/", "search-forward"); return 0; } qe_module_init(shell_init);