Shell: improve current directory traking

* parse process output for shell prompts with current directory
* parse `make -w -C` output for temporary subdirectory excursions
* handle property type and flags separately
* add `QE_PROP_MARK` to prevent offset update at range boundary
* use `QE_PROP_DUP` to optimize buffer properties with single allocation
* use mask in `eb_delete_properties` to delete tags selectively
* fix `eb_find_property` range glitch
* fix offset tracking for CWD properties
* simplify default directory selection for shell commands
* fix compiler warnings on variable shadow definitions
* optimize `next_error()`: no lookback needed anymore
* display buffer properties in `describe-buffer`
* update TODO.md
This commit is contained in:
Charlie Gordon 2026-06-13 18:08:40 +02:00
commit 0cb68d2d74
10 changed files with 429 additions and 201 deletions

View file

@ -416,7 +416,7 @@ $(OBJS_DIR)/haiku.o: haiku.cpp $(DEPENDS) Makefile
endif # ifneq(,$(TARGET))
libqhtml: force
$(MAKE) -C libqhtml all
$(MAKE) -w -C libqhtml all
#
# Host utilities
@ -650,7 +650,7 @@ TAGS: force
# autotest target
test:
$(MAKE) -C tests test
$(MAKE) -w -C tests test
colortest:
tests/16colors.pl

View file

@ -321,7 +321,6 @@ This is the TODO list for qemacs
* pass argval and pagewise to `do_scroll_up_down()` or split command
* files: fix `SPC` / `TAB` distinct behaviors on **~/comp/project/gnachman/**
* basic: update default settings to `indent_tabs_mode = 0`, `indent_width = 4`, my-colors
* basic: add property lists in buffer and window for default directory and similar properties (override)
* basic: reset last command when executing macro
* basic: make `do_word_right`, `do_word_left`, `do_bol`, `do_eol`... return new offset
* basic: use visual movement for left, right, up, down and character based for `C-b C-f C-n C-p`

View file

@ -833,11 +833,11 @@ void eb_free(EditBuffer **bp)
qe_free(&cb);
}
eb_delete_properties(b, 0, INT_MAX, 1);
eb_delete_properties(b, 0, INT_MAX, QE_PROP_ALL);
eb_cache_remove(b);
/* eb_clear frees b->log_buffer.
* it should also call eb_free_style_buffer(b)
* and eb_delete_properties(b, 0, INT_MAX);
* and eb_delete_properties(b, 0, INT_MAX, QE_PROP_ALL);
*/
eb_clear(b);
@ -2766,66 +2766,101 @@ static void eb_plist_callback(EditBuffer *b, void *opaque, int edge,
/* update properties */
if (op == LOGOP_INSERT) {
for (p = b->property_list; p; p = p->next) {
if (p->offset >= offset)
if (p->offset >= offset) {
if (p->offset == offset && (p->flags & QE_PROP_MARK))
continue;
p->offset += size;
}
}
} else
if (op == LOGOP_DELETE) {
for (pp = &b->property_list; (p = *pp) != NULL;) {
if (p->offset >= offset) {
if (p->offset == offset && (p->flags & QE_PROP_MARK))
continue;
if (p->offset < offset + size) {
/* property is anchored inside block: remove it */
*pp = (*pp)->next;
if (p->type & QE_PROP_FREE) {
qe_free(&p->data);
if (!(p->flags & QE_PROP_KEEP)) {
*pp = p->next;
if (p->flags & QE_PROP_FREE) {
qe_free(&p->data);
}
qe_free(&p);
continue;
}
qe_free(&p);
p->offset = offset;
continue;
}
p->offset -= size;
}
pp = &(*pp)->next;
pp = &p->next;
}
}
}
void eb_add_property(EditBuffer *b, int offset, int type, void *data) {
QEProperty *eb_add_property(EditBuffer *b, int offset, int type, int flags, const void *data) {
QEProperty *p;
QEProperty **pp;
int extra = 0;
if (flags & QE_PROP_DUP) {
extra = strlen(data) + 1;
flags &= ~QE_PROP_FREE;
}
if (!b->property_list) {
eb_add_callback(b, eb_plist_callback, NULL, 0);
}
/* insert property in ascending order of offset */
for (pp = &b->property_list; (p = *pp) != NULL; pp = &(*pp)->next) {
for (pp = &b->property_list; (p = *pp) != NULL; pp = &p->next) {
if (p->offset > offset)
break;
}
p = qe_mallocz(QEProperty);
p = qe_mallocz_hack(QEProperty, extra);
p->offset = offset;
p->type = type;
p->data = data;
p->flags = flags;
if (extra)
data = memcpy(p + 1, data, extra);
p->data = unconst(void*)data;
p->next = *pp;
*pp = p;
return *pp = p;
}
int eb_del_property(EditBuffer *b, QEProperty *prop) {
QEProperty *p;
QEProperty **pp;
for (pp = &b->property_list; (p = *pp) != NULL; pp = &p->next) {
if (p == prop) {
*pp = p->next;
if (p->flags & QE_PROP_FREE) {
qe_free(&p->data);
}
qe_free(&p);
return 1;
}
}
return 0;
}
void eb_add_tag(EditBuffer *b, int offset, const char *s) {
QEProperty *p;
/* prevent tag duplicates */
for (p = b->property_list; p != NULL; p = p->next) {
/* prevent tag duplicates with exact same offset */
for (p = b->property_list; p && p->offset <= offset; p = p->next) {
if (p->offset == offset && p->type == QE_PROP_TAG && strequal(p->data, s))
return;
}
eb_add_property(b, offset, QE_PROP_TAG, qe_strdup(s));
eb_add_property(b, offset, QE_PROP_TAG, QE_PROP_DUP, s);
}
QEProperty *eb_find_property(EditBuffer *b, int offset, int offset2, int type) {
QEProperty *eb_find_property(EditBuffer *b, int offset, int offset2, int type, QEProperty *stop) {
QEProperty *found = NULL;
QEProperty *p;
for (p = b->property_list; p && p->offset < offset2; p = p->next) {
for (p = b->property_list; p && p != stop && p->offset < offset2; p = p->next) {
if (p->offset >= offset && p->type == type) {
/* return the last property between offset and offset2 */
found = p;
@ -2834,7 +2869,7 @@ QEProperty *eb_find_property(EditBuffer *b, int offset, int offset2, int type) {
return found;
}
void eb_delete_properties(EditBuffer *b, int offset, int offset2, int force) {
void eb_delete_properties(EditBuffer *b, int offset, int offset2, int mask) {
QEProperty *p;
QEProperty **pp;
@ -2842,14 +2877,14 @@ void eb_delete_properties(EditBuffer *b, int offset, int offset2, int force) {
return;
for (pp = &b->property_list; (p = *pp) != NULL && p->offset < offset2;) {
if ((!(p->type & QE_PROP_SKIP) || force) && p->offset >= offset) {
*pp = (*pp)->next;
if (p->type & QE_PROP_FREE) {
if (p->offset >= offset && p->offset < offset2 && (p->type & mask)) {
*pp = p->next;
if (p->flags & QE_PROP_FREE) {
qe_free(&p->data);
}
qe_free(&p);
} else {
pp = &(*pp)->next;
pp = &p->next;
}
}
if (!b->property_list) {

View file

@ -1923,7 +1923,7 @@ static void do_describe_buffer(EditState *s, int argval)
if (!b1)
return;
eb_putc(b1, '\n');
eb_printf(b1, "Buffer fields:\n");
eb_printf(b1, " name: %s\n", b->name);
eb_printf(b1, " filename: %s\n", b->filename);
@ -2010,7 +2010,7 @@ static void do_describe_buffer(EditState *s, int argval)
u8 iobuf[4096];
int count[256];
int total_size = b->total_size;
int offset, c, i, col, max_count, count_width;
int offset, c, i, col, len, max_count, count_width;
int word_char, word_count, nb_chars, line, column;
eb_get_pos(b, &line, &column, total_size);
@ -2045,6 +2045,31 @@ static void do_describe_buffer(EditState *s, int argval)
eb_printf(b1, " words: %d\n", word_count);
eb_printf(b1, " lines: %d\n", line + (column > 0));
if (b->property_list) {
QEProperty *p;
eb_printf(b1, "\nBuffer property list:\n");
for (p = b->property_list; p; p = p->next) {
eb_printf(b1, " %7d %c%c%c ", p->offset,
(p->flags & QE_PROP_FREE) ? 'F' : ' ',
(p->flags & QE_PROP_KEEP) ? 'K' : ' ',
(p->flags & QE_PROP_MARK) ? 'M' : ' ');
switch (p->type) {
case QE_PROP_TAG:
eb_printf(b1, "tag %s", (char*)p->data);
break;
case QE_PROP_CWD:
eb_printf(b1, "cwd %s", (char*)p->data);
break;
default:
eb_printf(b1, "%3d %p", p->type, p->data);
break;
}
eb_putc(b1, '\n');
}
}
eb_printf(b1, "\nByte stats:\n");
for (col = i = 0; i < 256; i++) {
@ -2055,10 +2080,15 @@ static void do_describe_buffer(EditState *s, int argval)
if (i > 0 && i < 0x7f) {
char cbuf[8];
byte_quote(cbuf, sizeof cbuf, i);
col += eb_printf(b1, "'%s'", cbuf);
len = eb_printf(b1, "'%s'", cbuf);
} else {
col += eb_printf(b1, "0x%02x", (unsigned)i);
len = eb_printf(b1, "0x%02x", (unsigned)i);
}
while (len < 4) {
eb_putc(b1, ' ');
len++;
}
col += len;
if (col >= 60) {
eb_putc(b1, '\n');
col = 0;
@ -2104,7 +2134,7 @@ static void do_describe_window(EditState *s, int argval)
if (!b1)
return;
eb_putc(b1, '\n');
eb_printf(b1, "Window fields:\n");
w = 28;
eb_printf(b1, "%*s: %d, %d\n", w, "xleft, ytop", s->xleft, s->ytop);
@ -2201,7 +2231,7 @@ static void do_describe_screen(EditState *e, int argval)
if (!b1)
return;
eb_putc(b1, '\n');
eb_printf(b1, "Screen fields:\n");
w = 16;
eb_printf(b1, "%*s: %s\n", w, "dpy.name", s->dpy.name);

View file

@ -245,13 +245,7 @@ static void bufed_build_list(EditState *s, BufedState *bs)
b->cur_style = BUFED_STYLE_DIRECTORY;
else
b->cur_style = BUFED_STYLE_FILENAME;
if (b1->flags & BF_SHELL) {
get_default_path(b1, b1->offset, path, sizeof(path));
make_user_path(path, sizeof(path), path);
get_dirname(path, sizeof(path), path);
} else {
make_user_path(path, sizeof(path), b1->filename);
}
make_user_path(path, sizeof(path), b1->filename);
eb_put_filename(b, path, bs->pf_flags);
b->cur_style = style0;
}

View file

@ -57,6 +57,11 @@ enum QETermState {
QE_TERM_STATE_STRING,
};
typedef struct ShellCwdEntry {
QEProperty *prop;
int error_count;
} ShellCwdEntry;
typedef struct ShellState {
QEModeData base;
/* buffer state */
@ -100,14 +105,18 @@ typedef struct ShellState {
const char *caption; /* process caption for exit message */
int shell_flags;
int last_char; /* last char sent to the process */
int cwd_stack_depth;
ShellCwdEntry cwd_stack[16];
} ShellState;
typedef struct ShellError {
int offset;
int msg_offset;
int line_num;
int col_num;
char buffer[MAX_BUFFERNAME_SIZE];
char filename[MAX_FILENAME_SIZE];
char message[128];
} ShellError;
typedef struct ShellKillContext {
@ -130,6 +139,8 @@ static ShellError error_state = {
static void do_shell_refresh(EditState *e, int flags);
static void shell_close(ShellState *s);
static int shell_check_curpath(ShellState *s, int offset, int c);
static int match_error(EditBuffer *b, int start_offset, ShellError *dest);
static void set_error_offset(EditBuffer *b, int offset)
{
@ -137,6 +148,20 @@ static void set_error_offset(EditBuffer *b, int offset)
error_state.offset = offset - 1;
error_state.line_num = error_state.col_num = -1;
*error_state.filename = '\0';
error_state.msg_offset = offset;
*error_state.message = '\0';
}
static QEProperty *shell_add_cwd(EditBuffer *b, int offset, const char *cwd, int force) {
// Always set the buffer filename for bufed
pstrcpy(unconst(char *)b->filename, countof(b->filename), cwd);
if (!force) {
QEProperty *last = eb_find_property(b, 0, offset, QE_PROP_CWD, NULL);
if (last && strequal(last->data, cwd))
return NULL;
}
return eb_add_property(b, offset, QE_PROP_CWD,
QE_PROP_DUP | QE_PROP_KEEP | QE_PROP_MARK, cwd);
}
#define PTYCHAR1 "pqrstuvwxyzabcde"
@ -1528,10 +1553,10 @@ static void qe_term_emulate(ShellState *s, int c)
/* add a new line */
/* CG: XXX: ignoring charset */
qe_term_set_style(s);
offset += eb_insert_char32(s->b, offset, '\n');
s->cur_offset = offset;
offset1 = offset + eb_insert_char32(s->b, offset, '\n');
s->cur_offset = offset1;
/* update current screen_top */
qe_term_get_pos(s, offset, &x, &y);
qe_term_get_pos(s, offset1, &x, &y);
} else {
// XXX: test if cursor is on last row and append a newline
// XXX: potential style issue if appending a newline
@ -1539,6 +1564,10 @@ static void qe_term_emulate(ShellState *s, int c)
}
}
s->b->last_log = 0; /* close undo record */
if (c == 10 && (s->shell_flags & SF_INTERACTIVE) && !s->grab_keys) {
// Track directory from make -w -C messages and error messages
shell_check_curpath(s, offset, c);
}
break;
case 13: /* CR Carriage Return (Ctrl-M). */
/* move to visual beginning of line */
@ -1611,6 +1640,12 @@ static void qe_term_emulate(ShellState *s, int c)
len = 1;
}
s->cur_offset = qe_term_overwrite(s, offset, 1, buf1, len);
// Track directory from shell prompt
if ((c == '#' || c == '$' || c == '>')
&& (s->shell_flags & SF_INTERACTIVE) && !s->grab_keys) {
shell_check_curpath(s, offset, c);
}
} else {
TRACE_MSG(s, "control");
}
@ -1905,55 +1940,40 @@ static void qe_term_emulate(ShellState *s, int c)
{
char cwd[MAX_FILENAME_SIZE];
buf_t cwdbuf[1];
int pos = 7; /* first character after "file://" */
const char *p;
if (!strstart(s->osc_buf, "file://", NULL)) {
if (!strstart(s->osc_buf, "file://", &p)) {
put_error(qs->active_window,
"term_emulate: OSC 7 URI must start with 'file://'");
break;
}
/* skip hostname if any */
while (pos < s->osc_len && s->osc_buf[pos] != '/')
pos++;
while (*p && *p != '/')
p++;
if (pos >= s->osc_len) {
if (!*p) {
put_error(qs->active_window,
"term_emulate: missing path in OSC 7 URI: '%s'", s->osc_buf);
break;
}
/* decode URI path */
buf_init(cwdbuf, cwd, sizeof(cwd));
while (pos < s->osc_len) {
if (s->osc_buf[pos] == '%'
&& pos + 2 < s->osc_len
&& qe_isxdigit(s->osc_buf[pos + 1])
&& qe_isxdigit(s->osc_buf[pos + 2]))
{
buf_put_byte(cwdbuf, ((qe_digit_value(s->osc_buf[pos + 1]) << 4)
+ qe_digit_value(s->osc_buf[pos + 2])));
pos += 3;
while (*p) {
if (*p == '%' && qe_isxdigit(p[1]) && qe_isxdigit(p[2])) {
buf_put_byte(cwdbuf, qe_digit_value(p[1]) * 16 + qe_digit_value(p[2]));
p += 3;
} else {
buf_put_byte(cwdbuf, s->osc_buf[pos]);
pos += 1;
buf_put_byte(cwdbuf, *p);
p += 1;
}
}
if (!strequal(s->b->filename, cwd)) {
/* XXX: properties placed at end of buffer move forward whenever
output is appended, causing successive cwd properties to share
the same position instead of marking where cwd was observed. */
int offset = s->b->offset - 1;
if (offset > 0) { /* defer first property addition */
if (!s->b->property_list && *s->b->filename)
eb_add_property(s->b, 0, QE_PROP_CWD, qe_strdup(s->b->filename));
eb_add_property(s->b, offset, QE_PROP_CWD, qe_strdup(cwd));
}
pstrcpy(unconst(char *)s->b->filename, sizeof(s->b->filename), cwd);
}
append_slash(cwd, countof(cwd));
shell_add_cwd(s->b, s->b->offset, cwd, 0);
}
break;
default:
TRACE_PRINTF(s, "unhandled string: %s", s->osc_buf);
TRACE_PRINTF(s, "unhandled string: %.*s", min_int(s->osc_len, 32), s->osc_buf);
break;
}
break;
@ -2581,7 +2601,7 @@ static void shell_read_cb(void *opaque)
}
EditState *e = qs->active_window;
if (e->b == b) {
if (e->b == b && !(s->shell_flags & SF_NO_DRAG)) {
if ((e->offset == prev_offset) || (qs->last_cmd_func == (CmdFunc)do_eof))
e->offset = b->total_size;
}
@ -2752,6 +2772,7 @@ EditBuffer *qe_new_shell_buffer(QEmacsState *qs, EditBuffer *b0, EditState *e,
EditBuffer *b;
const char *lang;
int cols, rows;
char curpath[MAX_FILENAME_SIZE];
if (!b0 && (shell_flags & SF_REUSE_BUFFER)) {
b0 = qe_find_buffer_name(qs, bufname);
@ -2771,8 +2792,19 @@ EditBuffer *qe_new_shell_buffer(QEmacsState *qs, EditBuffer *b0, EditState *e,
}
shell_flags &= ~(SF_REUSE_BUFFER | SF_ERASE_BUFFER);
if (path)
pstrcpy(unconst(char *)b->filename, sizeof(b->filename), path);
// Set up filename and initial directory property
if (path) {
pstrcpy(curpath, countof(curpath), path);
} else
if (e) {
get_default_path(e->b, e->offset, curpath, countof(curpath));
} else {
*curpath = '\0';
getcwd(curpath, countof(curpath));
}
append_slash(curpath, countof(curpath));
shell_add_cwd(b, 0, curpath, 1);
eb_set_buffer_name(b, bufname); /* ensure that the name is unique */
if (shell_flags & SF_COLOR) {
eb_create_style_buffer(b, BF_STYLE_COMP);
@ -2860,7 +2892,7 @@ static void do_shell(EditState *e, int argval)
### `shell(int argval)`
Start a shell buffer or move to the last shell buffer used.
If a argval is provided and equal to one, the command attempts
Unless an argument different from one is provided, the command attempts
to switch to an existing, active shell buffer. If the process has
already terminated, it will restart the shell in the original directory.
If no shell buffer exists, or argval is not equal to one, it starts a
@ -3021,6 +3053,9 @@ static EditState *shell_target_window(EditState *e, EditBuffer *b)
}
}
if (e1 == NULL)
e1 = e;
switch_to_buffer(e1, b);
return e1;
}
@ -3052,7 +3087,7 @@ static void do_man(EditState *s, const char *arg)
return;
/* create new buffer */
b = qe_new_shell_buffer(qs, NULL, s, bufname, NULL, NULL, cmd, SF_COLOR);
b = qe_new_shell_buffer(qs, NULL, s, bufname, NULL, NULL, cmd, SF_COLOR | SF_NO_DRAG);
if (!b)
return;
@ -3115,7 +3150,7 @@ void qe_diff_buffer_with_file(EditState *s, EditBuffer *b)
#endif
snprintf(bufname, sizeof(bufname), "*Diff %s*", fname);
b1 = qe_new_shell_buffer(qs, NULL, s, bufname, NULL, NULL, cmd, SF_COLOR);
b1 = qe_new_shell_buffer(qs, NULL, s, bufname, NULL, NULL, cmd, SF_COLOR | SF_NO_DRAG);
if (!b1) {
unlink(tmpdir);
return;
@ -3664,10 +3699,105 @@ static void do_shell_toggle_input(EditState *e)
}
}
/* check shell output for prompt and track current directory */
static int shell_check_curpath(ShellState *s, int offset, int trigger)
{
char line[1024];
char curpath[MAX_FILENAME_SIZE];
EditBuffer *b = s->b;
int bol, start, stop0, stop, i, len, offset1;
bol = eb_goto_bol(b, offset);
len = eb_fgets(b, line, countof(line), bol, &offset1);
line[len] = '\0'; /* strip the trailing newline if any */
if (trigger == '\n') {
// Track make -w -C messages about temporary directory excursion
const char *p;
if ((p = strstr(line, "Entering directory ")) != NULL) {
// Push new directory onto the stack and add a property
ShellCwdEntry *cwdp;
p += strlen("Entering directory ");
if (*p == '`') p++;
start = p - line;
for (stop = start; line[stop] && line[stop] != '\''; stop++)
continue;
line[stop] = '\0';
canonicalize_absolute_buffer_path(s->b, bol, curpath, countof(curpath), p);
append_slash(curpath, countof(curpath));
if (s->cwd_stack_depth == countof(s->cwd_stack)) {
memmove(s->cwd_stack, s->cwd_stack + 1,
sizeof(*s->cwd_stack) * (countof(s->cwd_stack) - 1));
s->cwd_stack_depth--;
}
cwdp = &s->cwd_stack[s->cwd_stack_depth++];
cwdp->error_count = 0;
cwdp->prop = shell_add_cwd(b, offset1, curpath, 1);
return 1;
}
if ((p = strstr(line, "Leaving directory ")) != NULL) {
// Pop directory from the stack and delete the property if no errors
if (s->cwd_stack_depth) {
ShellCwdEntry *cwdp = &s->cwd_stack[--s->cwd_stack_depth];
QEProperty *prev = eb_find_property(b, 0, cwdp->prop->offset,
QE_PROP_CWD, cwdp->prop);
if (prev) {
if (!cwdp->error_count)
eb_del_property(b, cwdp->prop);
shell_add_cwd(b, offset1, prev->data, 0);
}
}
return 1;
}
// Check for error message:
// - maybe parse errors at this time to track error location
// for files that are loaded already, especially if the buffer
// is out of sync with the corresponding file.
if (match_error(b, bol, NULL)) {
if (s->cwd_stack_depth) {
s->cwd_stack[--s->cwd_stack_depth].error_count += 1;
}
return 1;
}
return 0;
}
// Parse shell prompt for current directory
start = stop = stop0 = 0;
for (i = 0; i < len;) {
char c = line[i++];
if (c == '#' || c == '$' || c == '>') {
stop = stop0;
break;
}
if (c == ':' && line[i] != '\\'
&& !(line[i] == '/' && line[i + 1] == '/')
&& !(line[i] == '/' && line[i + 1] == '*'))
start = i;
if (c == ' ') {
if (!start || start == i - 1)
start = i;
} else {
stop0 = i;
}
}
if (stop > start) {
line[stop] = '\0';
/* XXX: should use a lower level function to avoid potential recursion */
canonicalize_absolute_path(NULL, curpath, countof(curpath), line + start);
if (is_directory(curpath)) {
append_slash(curpath, countof(curpath));
shell_add_cwd(b, bol, curpath, 0);
return 1;
}
}
return 0;
}
static char *shell_get_default_path(EditBuffer *b, int offset,
char *buf, int buf_size)
{
QEProperty *p = eb_find_property(b, 0, offset, QE_PROP_CWD);
QEProperty *p = eb_find_property(b, 0, offset, QE_PROP_CWD, NULL);
return pstrcpy(buf, buf_size, p ? p->data : b->filename);
}
@ -3806,17 +3936,91 @@ static void do_compile(EditState *s, const char *cmd)
set_error_offset(b, 0);
}
/* Scan a buffer for an error message or a grep location */
static int match_error(EditBuffer *b, int start_offset, ShellError *dest)
{
char filename[MAX_FILENAME_SIZE];
char fullpath[MAX_FILENAME_SIZE];
buf_t fname[1];
int line_num, col_num, len;
int offset = start_offset;
char32_t c;
/* parse filename:linenum:message */
/* and: filename(linenum[, col]) ...: ... */
/* extract filename */
buf_init(fname, filename, countof(filename));
for (;;) {
c = eb_nextc(b, offset, &offset);
if (c == '\n' || c == '\t' || c == ' ')
return 0;
if (c == ':' || c == '(')
break;
buf_putc_utf8(fname, c);
}
/* extract line number */
for (line_num = 0;;) {
c = eb_nextc(b, offset, &offset);
if (c == ':' || c == ',' || c == '.' || c == ')')
break;
if (!qe_isdigit(c))
return 0;
line_num = line_num * 10 + c - '0';
}
if (line_num < 1)
return 0;
/* extract optional column number */
col_num = 0;
if (c == ':' || c == ',' || c == '.') {
int offset0 = offset;
char32_t c0 = c;
for (;;) {
c = eb_nextc(b, offset, &offset);
if (c == ' ') continue;
if (!qe_isdigit(c))
break;
col_num = col_num * 10 + c - '0';
}
if (col_num == 0) {
offset = offset0;
c = c0;
}
}
while (c != ':') {
if (c == '\n')
return 0;
c = eb_nextc(b, offset, &offset);
}
if (dest) {
canonicalize_absolute_buffer_path(b, start_offset,
fullpath, countof(fullpath),
filename);
if (line_num != dest->line_num
|| col_num != dest->col_num
|| !strequal(fullpath, dest->filename)) {
dest->offset = start_offset;
dest->msg_offset = offset;
dest->line_num = line_num;
dest->col_num = col_num;
pstrcpy(dest->filename, countof(dest->filename), fullpath);
len = eb_fgets(b, dest->message, countof(dest->message),
offset, &offset);
dest->message[len] = '\0'; /* strip the trailing newline if any */
return 2; // new error
}
}
return 1; // has error
}
static void do_next_error(EditState *s, int arg, int dir)
{
QEmacsState *qs = s->qs;
EditState *e, *e_next;
EditBuffer *b;
int offset, found_offset;
char filename[MAX_FILENAME_SIZE];
char fullpath[MAX_FILENAME_SIZE];
buf_t fnamebuf, *fname;
int line_num, col_num;
char32_t c;
int offset;
struct stat sb;
if (s->flags & (WF_POPUP | WF_MINIBUF)) {
@ -3870,68 +4074,24 @@ static void do_next_error(EditState *s, int arg, int dir)
}
offset = eb_prev_line(b, offset);
}
found_offset = offset;
/* parse filename:linenum:message */
/* and: filename(linenum[, col]) ...: ... */
/* extract filename */
fname = buf_init(&fnamebuf, filename, countof(filename));
for (;;) {
c = eb_nextc(b, offset, &offset);
if (c == '\n' || c == '\t' || c == ' ')
goto next_line;
if (c == ':' || c == '(')
break;
buf_putc_utf8(fname, c);
if (match_error(b, offset, &error_state) > 1) {
// found a new error
break;
}
/* XXX: should find directory backward from error offset */
canonicalize_absolute_buffer_path(b, found_offset,
fullpath, sizeof(fullpath), filename);
/* extract line number */
for (line_num = col_num = 0;;) {
c = eb_nextc(b, offset, &offset);
if (c == ':' || c == ',' || c == '.' || c == ')')
break;
if (!qe_isdigit(c))
goto next_line;
line_num = line_num * 10 + c - '0';
}
if (c == ':' || c == ',' || c == '.') {
int offset0 = offset;
char32_t c0 = c;
for (;;) {
c = eb_nextc(b, offset, &offset);
if (c == ' ') continue;
if (!qe_isdigit(c))
break;
col_num = col_num * 10 + c - '0';
}
if (col_num == 0) {
offset = offset0;
c = c0;
}
}
while (c != ':') {
if (c == '\n')
goto next_line;
c = eb_nextc(b, offset, &offset);
}
if (line_num >= 1) {
if (line_num != error_state.line_num
|| col_num != error_state.col_num
|| !strequal(fullpath, error_state.filename)) {
error_state.line_num = line_num;
error_state.col_num = col_num;
pstrcpy(error_state.filename, sizeof(error_state.filename), fullpath);
break;
}
}
next_line:
offset = found_offset;
}
error_state.offset = found_offset;
/* check file and go to the error */
if (stat(error_state.filename, &sb) < 0) {
// XXX: should prompt for fullpath as a fallback
put_error(qs->active_window, "Cannot find error in '%s': %s",
error_state.filename, strerror(errno));
return;
} else
if (!S_ISREG(sb.st_mode)) {
put_error(qs->active_window, "Cannot find error in '%s': %s",
error_state.filename, "not a regular file");
return;
}
for (e = qs->first_window; e != NULL; e = e_next) {
e_next = e->next_window;
@ -3939,45 +4099,38 @@ static void do_next_error(EditState *s, int arg, int dir)
edit_close(&e);
}
/* check file and go to the error */
if (stat(fullpath, &sb) < 0) {
// XXX: should prompt for fullpath as a fallback
put_error(qs->active_window, "Cannot find error in '%s': %s",
fullpath, strerror(errno));
} else
if (!S_ISREG(sb.st_mode)) {
put_error(qs->active_window, "Cannot find error in '%s': %s",
fullpath, "not a regular file");
} else {
/* find a window showing the error source
buffer, split a new one if none is found */
if ((e = eb_find_window(b, NULL)) == NULL)
e = shell_target_window(s, b);
/* find a window showing the error source buffer,
split a new one if none is found */
if ((e = eb_find_window(b, NULL)) == NULL && qs->shell_command_other_window) {
e = shell_target_window(s, b);
/* if there is only one window and is showing the error
source buffer then split a new window to find the file in */
/* if there is only one window whoing the error source buffer
then find or split a new window to find the file in */
if (e == s && (s = get_next_window(s, WF_MINIBUF | WF_HIDDEN, 0)) == NULL)
s = shell_target_window(e, b);
}
/* update error window */
if (e) {
/* update error window to show the error at top */
e->interactive = 0;
e->offset_top = e->offset = error_state.offset;
if (qs->shell_buffer_read_only)
b->flags |= BF_READONLY;
/* set and update active window */
qs->active_window = s;
do_find_file(s, fullpath, 0);
do_goto_line(s, line_num, col_num);
do_center_cursor(s, 1);
}
/* set and update active window */
qs->active_window = s;
do_find_file(s, error_state.filename, 0);
do_goto_line(s, error_state.line_num, error_state.col_num);
do_center_cursor(s, 1);
#if 0 // conflicts with muscle memory (chqrlie)
if (!qs->first_transient_key) {
qe_register_transient_binding(qs, "next-error", "M-n");
qe_register_transient_binding(qs, "previous-error", "M-p");
}
#endif
put_status(s, "=> %s", error_state.message);
}
static int match_digits(const char32_t *buf, int n, char32_t sep) {
@ -4063,11 +4216,10 @@ static int shell_grab_filename(const char32_t *buf, int n,
return i;
}
#define STATE_SHELL_SHIFT 8
#define STATE_SHELL_SHIFT 7
#define STATE_SHELL_MODE 0x001F
#define STATE_SHELL_SKIP 0x0020
#define STATE_SHELL_KEEP 0x0040
#define STATE_SHELL_TAG 0x0080
#define STATE_SHELL_MASK ((1 << STATE_SHELL_SHIFT) - 1)
static ModeDef *mode_cache[STATE_SHELL_MODE + 1];
@ -4096,11 +4248,21 @@ void shell_colorize_line(QEColorizeContext *cp,
/* detect match lines for known languages and colorize accordingly */
char filename[MAX_FILENAME_SIZE];
ModeDef *m;
int i = 0, start = 0;
int i, start = 0, stop = n;
if (qe_isspace(str[0])) {
if (cp->colorize_state & STATE_SHELL_SKIP)
start = 1;
/* patch style common lines */
if (cp->colorize_state & STATE_SHELL_SKIP) {
start = stop = 1;
} else {
/* clang line number prefix */
for (i = 0; i < 8; i++) {
if (str[i] != ' ' && !qe_isdigit(str[i]))
break;
}
if (str[i] == '|' && str[i + 1] == ' ')
start = stop = i + 2;
}
} else {
/* Detect patches and colorize according to filename */
if ((cp->colorize_state & STATE_SHELL_KEEP) && match_diff(str, n)) { /* patch/diff location lines */
@ -4142,12 +4304,13 @@ void shell_colorize_line(QEColorizeContext *cp,
else
cp->line_style = QE_STYLE_DIFF_NEW;
start = 1;
stop = 0;
}
}
} else
if (str[0] == '<' || str[0] == '>') {
if (str[1] == ' ')
start = 2;
start = stop = 2;
else
return;
} else
@ -4165,7 +4328,7 @@ void shell_colorize_line(QEColorizeContext *cp,
return;
} else {
/* Detect compiler messages and colorize according to filename */
while (i < n) {
for (i = 0; i < n;) {
int w = 0;
char32_t c = str[i];
if (qe_isspace(c)) {
@ -4224,8 +4387,10 @@ void shell_colorize_line(QEColorizeContext *cp,
i += 1;
i += match_digits(str + i, n - i, ')');
i += (str[i] == ':');
cp->colorize_state = mc | STATE_SHELL_TAG;
cp->colorize_state = mc;
start = i;
stop = 0;
SET_STYLE(sbuf, 0, i, QE_STYLE_ERROR_LOCATION);
break;
}
/* colorize compiler and grep -[ABC] output */
@ -4235,12 +4400,15 @@ void shell_colorize_line(QEColorizeContext *cp,
i += match_digits(str + i, n - i, c); /* line number */
i += match_digits(str + i, n - i, c); /* optional col number */
start = i;
cp->colorize_state = mc | STATE_SHELL_TAG;
cp->colorize_state = mc;
stop = 0;
SET_STYLE(sbuf, 0, i, QE_STYLE_ERROR_LOCATION);
if (match_string(str + i, n - i, " error:")
|| match_string(str + i, n - i, " note:")
|| match_string(str + i, n - i, " warning:")) {
/* clang diagnostic, will colorize the next line */
start = n;
/* compiler diagnostic, will colorize subsequent lines */
cp->colorize_state |= STATE_SHELL_KEEP;
return;
}
break;
}
@ -4262,12 +4430,7 @@ void shell_colorize_line(QEColorizeContext *cp,
} else {
cp->colorize_state = 0;
}
if (save_state & STATE_SHELL_TAG) {
SET_STYLE(sbuf, 0, i, QE_STYLE_ERROR_LOCATION);
cp->combine_stop = 0;
} else {
cp->combine_stop = start;
}
cp->combine_stop = stop;
cp->combine_skip = start; // restrict trailing blank colorizer
} else {
cp->colorize_state = 0;

View file

@ -177,7 +177,7 @@ FILENAME.
### `shell(int argval)`
Start a shell buffer or move to the last shell buffer used.
If a argval is provided and equal to one, the command attempts
Unless an argument different from one is provided, the command attempts
to switch to an existing, active shell buffer. If the process has
already terminated, it will restart the shell in the original directory.
If no shell buffer exists, or argval is not equal to one, it starts a

18
qe.c
View file

@ -580,11 +580,13 @@ void do_set_emulation(EditState *s, const char *name) {
if (strequal(name, "epsilon")) {
qe_register_emulation_bindings(qs, epsilon_bindings);
qs->emulation_flags = 1;
qs->shell_command_other_window = 0;
qs->flag_split_window_change_focus = 1;
} else
if (strequal(name, "emacs") || strequal(name, "xemacs")) {
qe_register_emulation_bindings(qs, emacs_bindings);
qs->emulation_flags = 0;
qs->shell_command_other_window = 1;
qs->flag_split_window_change_focus = 0;
} else
if (strequal(name, "gosmacs")) {
@ -3453,7 +3455,7 @@ void text_mode_line(EditState *s, buf_t *out)
buf_printf(out, "--<%d", -s->x_disp[0]);
if (s->x_disp[1])
buf_printf(out, "-->%d", -s->x_disp[1]);
tag = eb_find_property(s->b, 0, s->offset + 1, QE_PROP_TAG);
tag = eb_find_property(s->b, 0, s->offset, QE_PROP_TAG, NULL);
if (tag)
buf_printf(out, "--%s", (char*)tag->data);
#if 0
@ -4079,9 +4081,9 @@ static void flush_line(DisplayState *ds,
}
if (ds->line_num < e->shadow_nb_lines && !disable_crc) {
QELineShadow *ls;
uint64_t crc;
uint64_t crc = ds->line_style + e->region_style * 0x100L + e->default_style * 0x10000L;
crc = compute_crc(fragments, sizeof(*fragments) * nb_fragments, ds->line_style);
crc = compute_crc(fragments, sizeof(*fragments) * nb_fragments, crc);
crc = compute_crc(ds->line_chars, sizeof(*ds->line_chars) * ds->line_index, crc);
ls = &e->line_shadow[ds->line_num];
if (ls->y != ds->y || ls->x != ds->x_line
@ -4849,7 +4851,7 @@ static int syntax_get_colorized_line(QEColorizeContext *cp,
line++;
if (line < s->colorize_nb_valid_lines)
s->colorize_nb_valid_lines = line;
eb_delete_properties(b, s->colorize_max_valid_offset, INT_MAX, 0);
eb_delete_properties(b, s->colorize_max_valid_offset, INT_MAX, QE_PROP_TAG);
s->colorize_max_valid_offset = INT_MAX;
}
@ -8605,8 +8607,8 @@ void do_kill_buffer(EditState *s, const char *bufname, int force)
kill_process_confirm_cb, md);
} else {
/* if modified and associated to a filename, then ask */
if (!force && !(b->flags & (BF_DIRED | BF_SHELL))
&& b->modified && b->filename[0] != '\0') {
if (!force && b->modified
&& !(b->flags & (BF_DIRED | BF_SHELL)) && b->filename[0] != '\0') {
qe_stop_macro(qs);
snprintf(buf, sizeof(buf),
"Buffer %s modified; kill anyway? (yes or no) ", bufname);
@ -9579,8 +9581,8 @@ static void quit_examine_buffers(QuitState *is)
while (is->b != NULL) {
b = is->b;
if (!(b->flags & (BF_SYSTEM | BF_DIRED | BF_SHELL))
&& b->filename[0] != '\0' && b->modified) {
if (b->modified && b->filename[0] != '\0'
&& !(b->flags & (BF_SYSTEM | BF_DIRED | BF_SHELL))) {
switch (is->state) {
case QS_ASK:
/* XXX: display cursor */

22
qe.h
View file

@ -642,19 +642,24 @@ extern EditBufferDataType raw_data_type;
struct QEProperty {
int offset;
#define QE_PROP_FREE 1
#define QE_PROP_SKIP 4
#define QE_PROP_TAG 3
#define QE_PROP_CWD 13
int type;
#define QE_PROP_FREE 1 // p->data should be freed
#define QE_PROP_DUP 2 // data should be duplicated with strdup
#define QE_PROP_KEEP 4 // property is not removed in delete_range
#define QE_PROP_MARK 8 // property offset is not adjusted on exact match
int flags : 16;
#define QE_PROP_TAG 1 // a source tag
#define QE_PROP_CWD 2 // a current directory in a shell buffer
#define QE_PROP_ALL 3
int type : 16;
void *data;
QEProperty *next;
};
void eb_add_property(EditBuffer *b, int offset, int type, void *data);
QEProperty *eb_find_property(EditBuffer *b, int offset, int offset2, int type);
QEProperty *eb_add_property(EditBuffer *b, int offset, int type, int flags, const void *data);
int eb_del_property(EditBuffer *b, QEProperty *prop);
QEProperty *eb_find_property(EditBuffer *b, int offset, int offset2, int type, QEProperty *stop);
void eb_add_tag(EditBuffer *b, int offset, const char *s);
void eb_delete_properties(EditBuffer *b, int offset, int offset2, int force);
void eb_delete_properties(EditBuffer *b, int offset, int offset2, int mask);
/* qe module handling */
@ -1990,6 +1995,7 @@ void shell_colorize_line(QEColorizeContext *cp,
#define SF_AUTO_CODING 0x04
#define SF_AUTO_MODE 0x08
#define SF_BUFED_MODE 0x10
#define SF_NO_DRAG 0x20
#define SF_REUSE_BUFFER 0x1000
#define SF_ERASE_BUFFER 0x2000
EditBuffer *qe_new_shell_buffer(QEmacsState *qs, EditBuffer *b0, EditState *e,

View file

@ -203,7 +203,6 @@ QVarType qe_get_variable(EditState *s, const char *name,
const char *endp;
/* find standard variable and user variables */
// XXX: should also have window, buffer, mode properties?
vp = qe_find_variable(s->qs, name);
if (!vp) {
/* Try environment */