evil-qemacs/shell.c
Charlie Gordon 494e9f7866 include cflags.mk for extra gcc warnings if present
utf8_encode now returns the number of characters copied,
  simplified code accordingly
made local functions static
tagged unused function arguments
fixed shadowing issues
added support for GCC attributes for unused arguments and format strings
use snprintf() instead of sprintf()
moved scattered declarations to qe.h
removed duplicate declarations in header files
eb_write() payload argument is now const
fixed crash bug: reset trace_buffer upon deletion
improved trace_buffer readability: trace all pseudo tty streams separately
added missing structure initializers
removed buffered string handling from cutils.h
added new buffered string API for statically allocated buffers (should unify)
small improvements in X11 initialization
improved consistency in tty.c (function names...)
  added STDIN and STDOUT streams in QEditScreen to stop using stdin and stdout
  use unlocked stream I/O if available (faster)
  changed and documented terminal setup and exit sequences
  improved charset handling
mode_line functions now return the character count (like snprintf)
2006-12-31 19:34:00 +00:00

1457 lines
41 KiB
C

/*
* 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <time.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <termios.h>
#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 (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 */
/* CG: should use high intensity colors */
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 27: /* exit_standout_mode */
case 39: /* orig_pair(1) */
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_GET_COLOR(c - 30, TTY_GET_BG(s->color));
} else
if (c >= 40 && c <= 47) {
/* set background color */
s->color = TTY_GET_COLOR(TTY_GET_FG(s->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 */
for (;;) {
c = eb_prevc(s->b, s->cur_offset, &offset1);
if (c == '\n')
break;
s->cur_offset = offset1;
}
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 (c >= '0' && c <= '9') {
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 = s->cur_offset;
for (;;) {
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 '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];
snprintf(buf2, sizeof(buf2), "\033[%d;%dR", 1, 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 > (int)sizeof(buf))
len = (int)sizeof(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 > (int)sizeof(buf))
len = (int)sizeof(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 (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);
}
free(s);
}
EditBuffer *new_shell_buffer(const char *name, const char *path,
const char **argv, int is_shell)
{
ShellState *s;
EditBuffer *b, *b_color;
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 = malloc(sizeof(ShellState));
if (!s) {
eb_free(b);
return NULL;
}
memset(s, 0, sizeof(ShellState));
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) {
eb_free(b);
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) {
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("*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;
tty_write(s, &ch, 1);
} else {
switch (c) {
case 4:
do_delete_char(e);
break;
case 9:
do_tab(e);
break;
case 11:
do_kill_region(e, 2);
break;
case 127:
do_backspace(e);
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("*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, offset1, 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;
}
for (;;) {
c = eb_nextc(b, offset, &offset);
if (c == '\n')
break;
}
} else {
if (offset <= 0) {
put_status(s, "No previous error");
return;
}
eb_prevc(b, offset, &offset);
for (;;) {
c = eb_prevc(b, offset, &offset1);
if (c == '\n')
break;
offset = offset1;
}
}
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) < (int)sizeof(filename) - 1)
*q++ = c;
}
*q = '\0';
/* extract line number */
line_num = 0;
for (;;) {
c = eb_nextc(b, offset, &offset);
if (c == ':')
break;
if (!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, "i")
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)
CMD1( KEY_CTRLX(KEY_CTRL('n')), KEY_NONE, "next-error",
do_compile_error, 1)
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);