evil-qemacs/shell.c
Charlie Gordon 6dbffb4024 added '*' in command argument strings for commands that modify buffer contents
changed command definitions accordingly.
added C-x C-g binding for abort
added C-\ binding for call-last-kbd-macro
changed binding for switch-input-method to C-x C-\
added commands:
  not-modified on A-~ (reset buffer modified flag)
  load-file-from-path(file)
  mark-whole-buffer() on C-x h
  count-lines() on C-x l
  what-cursor-position on C-x =
allow key_grabber to quit and unget last key.
added splitpath() to dispatch directory and filename into separate buffers.
make separate functions for show_version and show_usage.
make separate function for parse_command_line.
added command line options:
  --help          -h   display help message and exit
  --no-init-file  -q   do not load config files
  --user          -u   load ~USER/.qe/config instead of your own
  --version       -V   display version information and exit
added -- as a marker for end of command line options.
allow most keys to be transmitted to shell process, including function keys.
improved shell buffer escape sequence parser.
added automatic detection of full screen shell applications and grab all keys
  try mc, Menuconfig, or even qemacs within qemacs !
improved *trace* buffer: do not trace keys when trace buffer is current,
  thus allowing navigation without side-effects
improved Makefile to not make html stuff if disabled
reduced CONFIG_TINY feature set
2005-05-09 09:10:37 +00:00

1344 lines
37 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,
};
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, 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);
fcntl(pty_fd, F_SETFL, O_NONBLOCK);
if (pty_fd < 0)
return -1;
/* 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)
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();
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 unsigned char *buf, int len)
{
int ret;
if (len < 0)
len = strlen(buf);
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 char at 'x' and 'y'. Can insert spaces or lines
if needed */
/* XXX: optimize !!!!! */
static void tty_gotoxy(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);
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;
x += col_num;
y += line_num;
}
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);
}
break;
} else {
offset = offset1;
}
}
s->cur_offset = offset;
}
void tty_csi_m(ShellState *s, int c)
{
/* we handle only a few possible modes */
switch(c) {
case 0:
s->color = s->def_color;
break;
default:
if (c >= 30 && c <= 37)
s->color = TTY_GET_COLOR(c - 30, TTY_GET_BG(s->color));
else if (c >= 40 && c <= 47) {
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(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;
}
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;
}
}
if (p)
tty_write(s, p, len);
}
static void tty_emulate(ShellState *s, int c)
{
int i, offset, offset1, offset2, n;
unsigned 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 */
/* TAB Horizontal Tab (HT) (Ctrl-I) */
/* 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;
}
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 */
s->shifted = 1;
break;
case 15: /* ^O SI = shift in */
s->shifted = 0;
break;
default:
if (c >= 32 || c == 9) {
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);
/* XXX: handle tab case */
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] = 0;
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=,
*/
switch (c) {
case '(':
case ')':
case '*':
case '+':
case ']':
s->esc1 = c;
s->state = TTY_STATE_ESC2;
break;
case 'H': // hts
case '7': // sc
case '8': // rc
case 'M': // ri
case 'c': // rs1
case '>': // rmkx, is2, rs2
case '=': // smkx
// 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'):
case ESC2(')','B'):
case ESC2('(','0'):
case ESC2(')','0'):
case ESC2('*','B'):
case ESC2('+','B'):
case ESC2(']','R'):
/* XXX: ??? */
break;
}
break;
case TTY_STATE_CSI:
if (c == '?') {
s->esc1 = c;
break;
}
if (c >= '0' && c <= '9') {
if (s->nb_esc_params < MAX_ESC_PARAMS) {
s->esc_params[s->nb_esc_params] =
s->esc_params[s->nb_esc_params] * 10 + c - '0';
s->has_params[s->nb_esc_params] = 1;
}
} 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:
* should grab all keys while active!
*/
if (s->esc_params[0] == 1047) {
s->grab_keys = 1;
qe_grab_keys(shell_key, s);
}
break;
case ESC2('?','l'): /* reset terminal mode */
if (s->esc_params[0] == 1047) {
qe_ungrab_keys();
s->grab_keys = 0;
}
break;
case 'A':
/* move relative up */
tty_gotoxy(s, 0, -(s->esc_params[0] + 1 - s->has_params[0]), 1);
break;
case 'B':
/* move relative down */
tty_gotoxy(s, 0, (s->esc_params[0] + 1 - s->has_params[0]), 1);
break;
case 'C':
/* move relative forward */
tty_gotoxy(s, (s->esc_params[0] + 1 - s->has_params[0]), 0, 1);
break;
case 'D':
/* move relative backward */
tty_gotoxy(s, -(s->esc_params[0] + 1 - s->has_params[0]), 0, 1);
break;
case 'H':
/* goto xy */
tty_gotoxy(s, s->esc_params[1] - s->has_params[1],
s->esc_params[0] - s->has_params[0], 0);
break;
case 'J': /* clear to end of screen */
case 'L': /* insert lines */
case 'M': /* delete lines */
case 'S': /* scroll forward P lines */
case 'T': /* scroll back P lines */
case 'X': /* erase P characters */
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 */
n = s->esc_params[0];
if (n <= 0)
n = 1;
offset1 = s->cur_offset;
for(;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 */
n = s->esc_params[0];
if (n <= 0)
n = 1;
buf1[0] = ' ';
for(;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]);
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;
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(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, 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_write(trace_buffer, trace_buffer->total_size, buf, len);
for (i = 0; i < len; i++)
tty_emulate(s, buf[i]);
/* now we do some refresh */
edit_display(qs);
dpy_flush(qs->screen);
}
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);
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;
}
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);
}
}
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);
}
}
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);
}
}
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);
}
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);
}
}
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);
}
}
void shell_write_char(EditState *e, int c)
{
unsigned 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;
}
}
}
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
}
static int error_offset = -1;
static int last_line_num = -1;
static char last_filename[1024];
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[1024], *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('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)
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);