/* * Shell mode for QEmacs. * * Copyright (c) 2001, 2002 Fabrice Bellard. * * 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 */ #include #include #include #include #include #include #include #include #include #include #include #include "qe.h" /* XXX: status line */ /* XXX: better tab handling */ /* XXX: bold & italic ? */ /* XXX: send real cursor position (CSI n) */ static ModeDef shell_mode; #define MAX_ESC_PARAMS 3 enum TTYState { TTY_STATE_NORM, TTY_STATE_ESC, TTY_STATE_ESC2, TTY_STATE_CSI, TTY_STATE_STRING, }; typedef struct ShellState { /* buffer state */ int pty_fd; int pid; /* -1 if not launched */ int color, def_color; int cur_offset; /* current offset at position x, y */ int esc_params[MAX_ESC_PARAMS]; int has_params[MAX_ESC_PARAMS]; int nb_esc_params; int state; int esc1; int shifted; int grab_keys; EditBuffer *b; EditBuffer *b_color; /* color buffer, one byte per char */ int is_shell; /* only used to display final message */ 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; } ShellState; static int shell_get_colorized_line(EditState *e, unsigned int *buf, int buf_size, int offset, int line_num); /* move to mode */ static int shell_launched = 0; static int shell_mode_init(EditState *s, __unused__ ModeSavedData *saved_data) { s->tab_size = 8; s->wrap = WRAP_TRUNCATE; s->interactive = 1; set_colorize_func(s, NULL); s->get_colorized_line_func = shell_get_colorized_line; return 0; } #define PTYCHAR1 "pqrstuvwxyz" #define PTYCHAR2 "0123456789abcdef" /* allocate one pty/tty pair */ static int get_pty(char *tty_str) { int fd; char ptydev[] = "/dev/pty??"; char ttydev[] = "/dev/tty??"; int len = strlen(ttydev); const char *c1, *c2; 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) { strcpy(tty_str, ttydev); return fd; } close(fd); } } } return -1; } static int run_process(const char *path, const char **argv, int *fd_ptr, int *pid_ptr) { int pty_fd, pid, i, nb_fds; char tty_name[32]; struct winsize ws; pty_fd = get_pty(tty_name); if (pty_fd < 0) { put_status(NULL, "run_process: cannot get tty"); return -1; } fcntl(pty_fd, F_SETFL, O_NONBLOCK); /* set dummy screen size */ ws.ws_col = 80; ws.ws_row = 25; 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 */ nb_fds = getdtablesize(); for (i = 0; i < nb_fds; i++) close(i); /* open pseudo tty for standard i/o */ open(tty_name, O_RDWR); dup(0); dup(0); setsid(); //setenv("TERM", "linux", 1); //setenv("QELEVEL", "1", 1); execv(path, (char *const*)argv); exit(1); } /* return file info */ *fd_ptr = pty_fd; *pid_ptr = pid; return 0; } /* VT100 emulation */ #define TTY_YSIZE 25 static void tty_init(ShellState *s) { char *term; s->state = TTY_STATE_NORM; s->cur_offset = 0; s->def_color = TTY_GET_COLOR(7, 0); s->color = s->def_color; 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~"; } } static void tty_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; } } /* Compute offset the of the char at column x and row y (0 based). * Can insert spaces or rows if needed. * x and y may each be relative to the current position. */ /* XXX: optimize !!!!! */ static void tty_goto_xy(ShellState *s, int x, int y, int relative) { int total_lines, cur_line, line_num, col_num, offset, offset1, c; unsigned char buf1[10]; /* compute offset */ 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, NULL) != '\n') total_lines++; line_num = total_lines - TTY_YSIZE; if (line_num < 0) line_num = 0; if (relative) { eb_get_pos(s->b, &cur_line, &col_num, s->cur_offset); cur_line -= line_num; if (cur_line < 0) cur_line = 0; if (relative & 1) x += col_num; if (relative & 2) y += cur_line; } if (y < 0) y = 0; else if (y >= TTY_YSIZE) y = TTY_YSIZE - 1; if (x < 0) x = 0; line_num += y; /* add lines if necessary */ while (line_num >= total_lines) { buf1[0] = '\n'; eb_insert(s->b, s->b->total_size, buf1, 1); total_lines++; } offset = eb_goto_pos(s->b, line_num, 0); for (; x > 0; x--) { c = eb_nextc(s->b, offset, &offset1); if (c == '\n') { buf1[0] = ' '; for (; x > 0; x--) { eb_insert(s->b, offset, buf1, 1); offset++; } break; } else { offset = offset1; } } s->cur_offset = offset; } static int tty_put_char(ShellState *s, int c) { char buf[1]; int c1, cur_len, offset; buf[0] = c; c1 = eb_nextc(s->b, s->cur_offset, &offset); if (c1 == '\n') { /* insert */ eb_insert(s->b, s->cur_offset, buf, 1); } else { /* check for (c1 != c) is not advisable optimisation because * re-writing the same character may cause color changes. */ cur_len = offset - s->cur_offset; if (cur_len == 1) { eb_write(s->b, s->cur_offset, buf, 1); } else { eb_delete(s->b, s->cur_offset, cur_len); eb_insert(s->b, s->cur_offset, buf, 1); } } return s->cur_offset + 1; } static void tty_csi_m(ShellState *s, int c, int has_param) { /* we handle only a few possible modes */ switch (has_param ? c : 0) { case 0: /* exit_attribute_mode */ s->color = s->def_color; break; case 1: /* enter_bold_mode */ s->color |= TTY_BOLD; break; case 22: /* exit_bold_mode */ s->color &= ~TTY_BOLD; break; case 4: /* enter_underline_mode */ case 5: /* enter_blink_mode */ case 7: /* enter_reverse_mode, enter_standout_mode */ case 8: /* enter_secure_mode */ case 24: /* exit_underline_mode */ case 25: /* exit_blink_mode */ case 27: /* exit_reverse_mode, exit_standout_mode */ case 28: /* exit_secure_mode */ case 38: /* set extended foreground color ? */ case 39: /* orig_pair(1) */ case 48: /* set extended background color ? */ case 49: /* orig_pair(2) */ break; default: /* 0:black 1:red 2:green 3:yellow 4:blue 5:magenta 6:cyan 7:white */ if (c >= 30 && c <= 37) { /* set foreground color */ s->color &= ~(TTY_BOLD | TTY_BG_COLOR(7)); s->color |= TTY_FG_COLOR(c - 30); } else if (c >= 40 && c <= 47) { /* set background color */ s->color &= ~(TTY_BOLD | TTY_FG_COLOR(7)); s->color |= TTY_BG_COLOR(c - 40); } break; } } /* Well, almost a hack to update cursor */ static void tty_update_cursor(__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 } /* CG: much cleaner way! */ /* would need a kill hook as well ? */ static void shell_display_hook(EditState *e) { ShellState *s = e->b->priv_data; if (e->interactive) e->offset = s->cur_offset; } static void shell_key(void *opaque, int key) { ShellState *s = opaque; char buf[10]; const char *p; int len; 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) tty_write(s, p, len); } static void tty_emulate(ShellState *s, int c) { int i, offset, offset1, offset2, n; char buf1[10]; #define ESC2(c1,c2) (((c1)<<8)|((unsigned char)c2)) /* some bytes are state independent */ switch (c) { case 0x18: case 0x1A: s->state = TTY_STATE_NORM; return; case 0x1B: s->state = TTY_STATE_ESC; return; #if 0 case 0x9B: goto csi_entry; #endif } switch (s->state) { case TTY_STATE_NORM: switch (c) { /* BEL Bell (Ctrl-G) */ /* FF Form Feed or New Page (NP) (Ctrl-L) same as LF */ /* VT Vertical Tab (Ctrl-K) same as LF */ case 8: /* ^H BS = backspace */ { int c1; c1 = eb_prevc(s->b, s->cur_offset, &offset); if (c1 != '\n') { s->cur_offset = offset; /* back_color_erase */ //tty_put_char(s, ' '); } } break; case 9: /* ^I HT = horizontal tab */ { int col_num, cur_line; eb_get_pos(s->b, &cur_line, &col_num, s->cur_offset); tty_goto_xy(s, (col_num + 8) & ~7, 0, 2); break; } case 10: /* ^J NL = line feed */ /* go to next line */ /* CG: should check if column should be kept */ offset = s->cur_offset; for (;;) { if (offset == s->b->total_size) { /* add a new line */ buf1[0] = '\n'; eb_insert(s->b, offset, buf1, 1); offset = s->b->total_size; break; } c = eb_nextc(s->b, offset, &offset); if (c == '\n') break; } s->cur_offset = offset; break; case 13: /* ^M CR = carriage return */ /* move to bol */ s->cur_offset = eb_goto_bol(s->b, s->cur_offset); break; case 14: /* ^N SO = shift out */ // was in qemacs-0.3.1.g2.gw //eb_set_charset(s->b, &charset_8859_1); s->shifted = 1; break; case 15: /* ^O SI = shift in */ // was in qemacs-0.3.1.g2.gw //eb_set_charset(s->b, &charset_cp1125); s->shifted = 0; break; default: if (c >= 32) { int c1, cur_len, len; /* CG: assuming ISO-8859-1 characters */ /* CG: horrible kludge for alternate charset support */ if (s->shifted && c >= 96 && c < 128) c += 32; /* write char (should factorize with do_char() code */ len = unicode_to_charset(buf1, c, s->b->charset); c1 = eb_nextc(s->b, s->cur_offset, &offset); /* Should simplify with tty_put_char */ if (c1 == '\n') { /* insert */ eb_insert(s->b, s->cur_offset, buf1, len); } else { cur_len = offset - s->cur_offset; if (cur_len == len) { eb_write(s->b, s->cur_offset, buf1, len); } else { eb_delete(s->b, s->cur_offset, cur_len); eb_insert(s->b, s->cur_offset, buf1, len); } } s->cur_offset += len; } break; } break; case TTY_STATE_ESC: if (c == '[') { for (i = 0; i < MAX_ESC_PARAMS; i++) { s->esc_params[i] = 1; s->has_params[i] = 0; } s->nb_esc_params = 0; s->esc1 = 0; s->state = TTY_STATE_CSI; } else { /* 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, */ switch (c) { case '(': case ')': case '*': case '+': case ']': s->esc1 = c; s->state = TTY_STATE_ESC2; break; case 'H': // hts (set_tab) case '7': // sc (save_cursor) case '8': // rc (restore_cursor) case 'M': // ri (scroll_reverse) case 'c': // rs1 (reset_1string) case '>': // rmkx, is2, rs2 (keypad_local ???) case '=': // smkx (keypad_xmit ???) // XXX: do these default: s->state = TTY_STATE_NORM; break; } } break; case TTY_STATE_ESC2: s->state = TTY_STATE_NORM; switch (ESC2(s->esc1, c)) { case ESC2('(','B'): /* exit_alt_charset_mode */ s->shifted = 0; break; case ESC2('(','0'): /* enter_alt_charset_mode */ s->shifted = 1; break; case ESC2(')','B'): case ESC2(')','0'): case ESC2('*','B'): case ESC2('+','B'): case ESC2(']','R'): /* XXX: ??? */ break; case ESC2(']','0'): /* xterm's set-window-title */ s->state = TTY_STATE_STRING; break; } break; case TTY_STATE_STRING: /* ignore string parameter upto \a (^G) */ if (c == '\007') { s->state = TTY_STATE_NORM; } break; case TTY_STATE_CSI: if (c == '?') { s->esc1 = c; break; } if (qe_isdigit(c)) { if (s->nb_esc_params < MAX_ESC_PARAMS) { if (!s->has_params[s->nb_esc_params]) { s->esc_params[s->nb_esc_params] = 0; s->has_params[s->nb_esc_params] = 1; } s->esc_params[s->nb_esc_params] = s->esc_params[s->nb_esc_params] * 10 + c - '0'; } break; } else { s->nb_esc_params++; if (c == ';') break; s->state = TTY_STATE_NORM; switch (ESC2(s->esc1,c)) { case ESC2('?','h'): /* set terminal mode */ /* 1047, 1048 -> cup mode: * xterm 1049 private mode, * should grab all keys while active! */ if (s->esc_params[0] == 1047 || s->esc_params[0] == 1048 || s->esc_params[0] == 1049) { s->grab_keys = 1; qe_grab_keys(shell_key, s); /* Should also clear screen */ } break; case ESC2('?','l'): /* reset terminal mode */ if (s->esc_params[0] == 1047 || s->esc_params[0] == 1048 || s->esc_params[0] == 1049) { qe_ungrab_keys(); s->grab_keys = 0; } break; case 'A': /* move relative up */ tty_goto_xy(s, 0, -s->esc_params[0], 3); break; case 'B': /* move relative down */ tty_goto_xy(s, 0, s->esc_params[0], 3); break; case 'C': /* move relative forward */ tty_goto_xy(s, s->esc_params[0], 0, 3); break; case 'D': /* move relative backward */ tty_goto_xy(s, -s->esc_params[0], 0, 3); break; case 'G': /* goto column_address */ tty_goto_xy(s, s->esc_params[0] - 1, 0, 2); break; case 'H': /* goto xy */ tty_goto_xy(s, s->esc_params[1] - 1, s->esc_params[0] - 1, 0); break; case 'd': /* goto y */ tty_goto_xy(s, 0, s->esc_params[0] - 1, 1); break; case 'J': /* clear to end of screen */ case 'L': /* insert lines */ case 'M': /* delete lines */ case 'S': /* scroll forward n lines */ case 'T': /* scroll back n lines */ break; case 'X': /* erase n characters */ for (n = s->esc_params[0]; n > 0; n--) { s->cur_offset = tty_put_char(s, ' '); } break; case 'K': /* clear eol (parm=1 -> bol) */ offset1 = eb_goto_eol(s->b, s->cur_offset); eb_delete(s->b, s->cur_offset, offset1 - s->cur_offset); break; case 'P': /* delete chars */ offset1 = s->cur_offset; for (n = s->esc_params[0]; n > 0; n--) { c = eb_nextc(s->b, offset1, &offset2); if (c == '\n') break; offset1 = offset2; } eb_delete(s->b, s->cur_offset, offset1 - s->cur_offset); break; case '@': /* insert chars */ buf1[0] = ' '; for (n = s->esc_params[0]; n > 0; n--) { eb_insert(s->b, s->cur_offset, buf1, 1); } break; case 'm': /* colors */ n = s->nb_esc_params; if (n == 0) n = 1; for (i = 0; i < n; i++) { tty_csi_m(s, s->esc_params[i], s->has_params[i]); } break; case 'n': if (s->esc_params[0] == 6) { /* XXX: send cursor position, just to be able to launch qemacs in qemacs (in 8859-1) ! */ char buf2[20]; int col_num, cur_line; eb_get_pos(s->b, &cur_line, &col_num, s->cur_offset); /* XXX: actually send position of point in window */ snprintf(buf2, sizeof(buf2), "\033[%d;%dR", 1, col_num + 1); tty_write(s, buf2, -1); } break; case 'r': /* change_scroll_region (2 args) */ break; default: break; } } break; } #undef ESC2 tty_update_cursor(s); } /* modify the color according to the current one (may be incorrect if we are editing because we should write default color) */ static void shell_color_callback(__unused__ EditBuffer *b, void *opaque, enum LogOperation op, int offset, int size) { ShellState *s = opaque; unsigned char buf[32]; int len; switch (op) { case LOGOP_WRITE: while (size > 0) { len = size; if (len > ssizeof(buf)) len = ssizeof(buf); memset(buf, s->color, len); eb_write(s->b_color, offset, buf, len); size -= len; offset += len; } break; case LOGOP_INSERT: while (size > 0) { len = size; if (len > ssizeof(buf)) len = ssizeof(buf); memset(buf, s->color, len); eb_insert(s->b_color, offset, buf, len); size -= len; } break; case LOGOP_DELETE: eb_delete(s->b_color, offset, size); break; default: break; } } static int shell_get_colorized_line(EditState *e, unsigned int *buf, int buf_size, int offset, __unused__ int line_num) { EditBuffer *b = e->b; ShellState *s = b->priv_data; EditBuffer *b_color = s->b_color; int color, offset1, c; unsigned int *buf_ptr, *buf_end; unsigned char buf1[1]; /* record line */ buf_ptr = buf; buf_end = buf + buf_size; for (;;) { eb_read(b_color, offset, buf1, 1); color = buf1[0]; c = eb_nextc(b, offset, &offset1); if (c == '\n') break; if (buf_ptr < buf_end) { /* XXX: test */ if (color != s->def_color) { c |= (QE_STYLE_TTY | color) << STYLE_SHIFT; } *buf_ptr++ = c; } offset = offset1; } return buf_ptr - buf; } /* buffer related functions */ /* called when characters are available on the tty */ static void shell_read_cb(void *opaque) { ShellState *s = opaque; QEmacsState *qs = s->qe_state; unsigned char buf[1024]; int len, i; len = read(s->pty_fd, buf, sizeof(buf)); if (len <= 0) return; if (qs->trace_buffer) eb_trace_bytes(buf, len, EB_TRACE_SHELL); for (i = 0; i < len; i++) tty_emulate(s, buf[i]); /* now we do some refresh */ edit_display(qs); dpy_flush(qs->screen); } static void shell_pid_cb(void *opaque, int status) { ShellState *s = opaque; EditBuffer *b = s->b; QEmacsState *qs = s->qe_state; EditState *e; char buf[1024]; if (s->is_shell) { snprintf(buf, sizeof(buf), "\nProcess shell finished\n"); } else { 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), "\nCompilation finished at %s", time_str); } else { snprintf(buf, sizeof(buf), "\nCompilation exited abnormally with code %d at %s", status, time_str); } } eb_write(b, b->total_size, buf, strlen(buf)); 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; } edit_display(qs); dpy_flush(qs->screen); } static void shell_close(EditBuffer *b) { ShellState *s = b->priv_data; int status; eb_free_callback(b, eb_offset_callback, &s->cur_offset); 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); } s->pid = -1; } if (s->pty_fd >= 0) { set_read_handler(s->pty_fd, NULL, NULL); } qe_free(&s); } EditBuffer *new_shell_buffer(EditBuffer *b0, const char *name, const char *path, const char **argv, int is_shell) { ShellState *s; EditBuffer *b, *b_color; b = b0; if (!b) b = eb_new("", BF_SAVELOG); if (!b) return NULL; set_buffer_name(b, name); /* ensure that the name is unique */ eb_set_charset(b, &charset_vt100); s = qe_mallocz(ShellState); if (!s) { if (!b0) eb_free(b); return NULL; } b->priv_data = s; b->close = shell_close; eb_add_callback(b, eb_offset_callback, &s->cur_offset); s->b = b; s->pty_fd = -1; s->pid = -1; s->is_shell = is_shell; s->qe_state = &qe_state; tty_init(s); /* add color buffer */ if (is_shell) { b_color = eb_new("*color*", BF_SYSTEM); if (!b_color) { if (!b0) eb_free(b); qe_free(&s); return NULL; } /* no undo info in this color buffer */ b_color->save_log = 0; eb_add_callback(b, shell_color_callback, s); s->b_color = b_color; } /* launch shell */ if (run_process(path, argv, &s->pty_fd, &s->pid) < 0) { if (!b0) eb_free(b); return NULL; } set_read_handler(s->pty_fd, shell_read_cb, s); set_pid_handler(s->pid, shell_pid_cb, s); return b; } static void do_shell(EditState *s, int force) { QEmacsState *qs = s->qe_state; EditState *e; EditBuffer *b; const char *argv[3]; const char *shell_path; /* CG: Should prompt for buffer name if arg: * find a syntax for optional string argument w/ prompt */ /* find shell buffer if any */ if (!force || force == NO_ARG) { b = eb_find("*shell*"); if (b) { e = edit_find(b); if (e) qs->active_window = e; else switch_to_buffer(s, b); return; } } /* find shell name */ shell_path = getenv("SHELL"); if (!shell_path) shell_path = "/bin/sh"; /* create new buffer */ argv[0] = shell_path; argv[1] = NULL; b = new_shell_buffer(NULL, "*shell*", shell_path, argv, 1); if (!b) return; switch_to_buffer(s, b); do_set_mode(s, &shell_mode, NULL); put_status(s, "Press C-o to toggle between shell/edit mode"); shell_launched = 1; } static void shell_move_left_right(EditState *e, int dir) { if (e->interactive) { ShellState *s = e->b->priv_data; tty_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) { if (e->interactive) { ShellState *s = e->b->priv_data; tty_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) { if (e->interactive) { ShellState *s = e->b->priv_data; tty_write(s, dir > 0 ? s->kcud1 : s->kcuu1, -1); } else { text_move_up_down(e, dir); } } static void shell_scroll_up_down(EditState *e, int dir) { ShellState *s = e->b->priv_data; e->interactive = 0; text_scroll_up_down(e, dir); e->interactive = (e->offset == s->cur_offset); } static void shell_move_bol(EditState *e) { if (e->interactive) { ShellState *s = e->b->priv_data; tty_write(s, "\001", 1); /* Control-A */ } else { text_move_bol(e); } } static void shell_move_eol(EditState *e) { if (e->interactive) { ShellState *s = e->b->priv_data; tty_write(s, "\005", 1); /* Control-E */ } else { text_move_eol(e); } } static void shell_write_char(EditState *e, int c) { char ch; if (e->interactive) { ShellState *s = e->b->priv_data; ch = c; /* TODO: convert to tty escape sequences? */ tty_write(s, &ch, 1); } else { /* Should dispatch as in fundamental mode */ switch (c) { case 4: do_delete_char(e, NO_ARG); break; // Do not do this: it is useless and causes infinite recursion //case 9: // do_tab(e, 1); // break; case 11: do_kill_line(e, 1); break; case 127: do_backspace(e, NO_ARG); break; case '\r': do_return(e); break; default: text_write_char(e, c); break; } } } static void do_shell_toggle_input(EditState *e) { e->interactive = !e->interactive; if (e->interactive) { ShellState *s = e->b->priv_data; if (s->grab_keys) qe_grab_keys(shell_key, s); } #if 0 if (e->interactive) { ShellState *s = e->b->priv_data; tty_update_cursor(s); } #endif } /* CG: these variables should move to mode structure */ static int error_offset = -1; static int last_line_num = -1; static char last_filename[MAX_FILENAME_SIZE]; static void do_compile(EditState *e, const char *cmd) { const char *argv[4]; EditBuffer *b; /* if the buffer already exists, kill it */ b = eb_find("*compilation*"); if (b) { /* XXX: e should not become invalid */ b->modified = 0; do_kill_buffer(e, "*compilation*"); } error_offset = -1; last_line_num = -1; /* create new buffer */ argv[0] = "/bin/sh"; argv[1] = "-c"; argv[2] = (char *)cmd; argv[3] = NULL; b = new_shell_buffer(NULL, "*compilation*", "/bin/sh", argv, 0); if (!b) return; /* XXX: try to split window if necessary */ switch_to_buffer(e, b); } static void do_compile_error(EditState *s, int dir) { QEmacsState *qs = s->qe_state; EditState *e; EditBuffer *b; int offset, found_offset; char filename[MAX_FILENAME_SIZE], *q; int line_num, c; /* 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("*compilation*")) == NULL && (b = eb_find("*shell*")) == NULL && (b = eb_find("*errors*")) == NULL) { put_status(s, "No compilation buffer"); return; } /* find next/prev error */ offset = error_offset; if (offset < 0) { offset = 0; goto find_error; } for (;;) { if (dir > 0) { if (offset >= b->total_size) { put_status(s, "No more errors"); return; } offset = eb_next_line(b, offset); } else { if (offset <= 0) { put_status(s, "No previous error"); return; } offset = eb_prev_line(b, offset); } find_error: found_offset = offset; /* extract filename */ q = filename; for (;;) { c = eb_nextc(b, offset, &offset); if (c == '\n' || c == '\t' || c == ' ') goto next_line; if (c == ':') break; if ((q - filename) < ssizeof(filename) - 1) *q++ = c; } *q = '\0'; /* extract line number */ line_num = 0; for (;;) { c = eb_nextc(b, offset, &offset); if (c == ':') break; if (!qe_isdigit(c)) goto next_line; line_num = line_num * 10 + c - '0'; } if (line_num >= 1) { if (line_num != last_line_num || strcmp(filename, last_filename) != 0) { last_line_num = line_num; pstrcpy(last_filename, sizeof(last_filename), filename); 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 */ do_load(s, filename); do_goto_line(s, line_num); } /* specific shell commands */ static CmdDef shell_commands[] = { CMD0( KEY_CTRL('o'), KEY_NONE, "shell-toggle-input", do_shell_toggle_input) CMD1( '\r', KEY_NONE, "shell-return", shell_write_char, '\r') /* CG: should send s->kbs */ CMD1( 127, KEY_NONE, "shell-backward-delete-char", shell_write_char, 127) CMD1( KEY_CTRL('c'), KEY_NONE, "shell-intr", shell_write_char, 3) CMD1( KEY_CTRL('d'), KEY_DELETE, "shell-delete-char", shell_write_char, 4) CMD1( KEY_CTRL('i'), KEY_NONE, "shell-tabulate", shell_write_char, 9) CMD1( KEY_CTRL('k'), KEY_NONE, "shell-kill-line", shell_write_char, 11) CMD1( KEY_CTRL('y'), KEY_NONE, "shell-yank", shell_write_char, 25) CMD_DEF_END, }; /* compilation commands */ static CmdDef compile_commands[] = { CMD_( KEY_CTRLXRET('\r'), KEY_NONE, "shell", do_shell, "ui") CMD_( KEY_CTRLX(KEY_CTRL('e')), KEY_NONE, "compile", do_compile, "s{Compile command: }|compile|") CMD1( KEY_CTRLX(KEY_CTRL('p')), KEY_NONE, "previous-error", do_compile_error, -1) /* u */ CMD1( KEY_CTRLX(KEY_CTRL('n')), KEY_CTRLX('`'), "next-error", do_compile_error, 1) /* u */ CMD_DEF_END, }; static int shell_init(void) { /* first register mode */ memcpy(&shell_mode, &text_mode, sizeof(ModeDef)); shell_mode.name = "shell"; shell_mode.mode_probe = NULL; shell_mode.mode_init = shell_mode_init; 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.write_char = shell_write_char; shell_mode.mode_flags |= MODEF_NOCMD; qe_register_mode(&shell_mode); /* commands and default keys */ qe_register_cmd_table(shell_commands, "shell"); qe_register_cmd_table(compile_commands, NULL); return 0; } qe_module_init(shell_init);