Improve find_file_next and next-error

* no longer scan directory in `file_file_next` if pattern lacks wildcards
* patterns without wildcards can have subdirectory
* `next-error`: if file is not found in the current buffer directory, try
  and locate it in subdirectories
This commit is contained in:
Charlie Gordon 2026-07-06 20:26:51 +02:00
commit 9079f85034
7 changed files with 99 additions and 55 deletions

View file

@ -375,6 +375,7 @@ int fbf_render_init(const char *font_path)
fprintf(stderr, "Could not load font '%s'\n", filename);
}
}
find_file_close(&ffs);
return 0;
}

View file

@ -144,12 +144,13 @@ static int match_error(EditBuffer *b, int start_offset, ShellError *dest);
static void set_error_offset(EditBuffer *b, int offset)
{
pstrcpy(error_state.buffer, sizeof(error_state.buffer), b ? b->name : "");
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';
ShellError *sep = &error_state;
pstrcpy(sep->buffer, sizeof(sep->buffer), b ? b->name : "");
sep->offset = offset - 1;
sep->line_num = sep->col_num = -1;
*sep->filename = '\0';
sep->msg_offset = offset;
*sep->message = '\0';
}
static QEProperty *shell_add_cwd(EditBuffer *b, int offset, const char *cwd, int force) {
@ -3960,7 +3961,6 @@ static void do_compile(EditState *s, const char *cmd)
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;
@ -4015,19 +4015,15 @@ static int match_error(EditBuffer *b, int start_offset, ShellError *dest)
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)) {
|| !strequal(filename, 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);
pstrcpy(dest->filename, countof(dest->filename), filename);
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
}
@ -4037,7 +4033,9 @@ static int match_error(EditBuffer *b, int start_offset, ShellError *dest)
static void do_next_error(EditState *s, int arg, int dir)
{
char fullname[MAX_FILENAME_SIZE];
QEmacsState *qs = s->qs;
ShellError *sep = &error_state;
EditState *e;
EditBuffer *b;
int offset;
@ -4058,7 +4056,7 @@ static void do_next_error(EditState *s, int arg, int dir)
set_error_offset(s->b, s->offset);
}
if ((b = qe_find_buffer_name(qs, error_state.buffer)) == NULL) {
if ((b = qe_find_buffer_name(qs, sep->buffer)) == NULL) {
if (s->b->flags & BF_ERROR) {
b = s->b;
} else {
@ -4080,7 +4078,7 @@ static void do_next_error(EditState *s, int arg, int dir)
}
/* find next/prev error */
offset = error_state.offset;
offset = sep->offset;
/* CG: should use higher level parsing */
for (;;) {
@ -4097,22 +4095,32 @@ static void do_next_error(EditState *s, int arg, int dir)
}
offset = eb_prev_line(b, offset);
}
if (match_error(b, offset, &error_state) > 1) {
if (match_error(b, offset, sep) > 1) {
// found a new error
break;
}
}
canonicalize_absolute_buffer_path(b, sep->offset, fullname, countof(fullname), sep->filename);
if ((stat(fullname, &sb) < 0 || !S_ISREG(sb.st_mode))
&& !is_abs_path(sep->filename) && *sep->filename != '~') {
// try and locate filename in a subdirectory up to 2 levels down
char path[MAX_FILENAME_SIZE];
FindFileState *ffs;
get_default_path(b, offset, path, countof(path));
ffs = find_file_open(path, sep->filename, FF_NODIR | 2);
find_file_next(ffs, fullname, sizeof(fullname));
find_file_close(&ffs);
}
/* 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));
if (stat(fullname, &sb) < 0) {
put_error(s, "Cannot locate file '%s': %s",
fullname, 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");
put_error(s, "Cannot load '%s': not a regular file", fullname);
return;
}
@ -4134,15 +4142,15 @@ static void do_next_error(EditState *s, int arg, int dir)
if (e) {
/* update error window to show the error at top */
e->interactive = 0;
e->offset_top = e->offset = error_state.offset;
e->offset_top = e->offset = sep->offset;
if (qs->shell_buffer_read_only)
b->flags |= BF_READONLY;
}
/* 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_find_file(s, fullname, 0);
do_goto_line(s, sep->line_num, sep->col_num);
do_center_cursor(s, 1);
#if 0 // conflicts with muscle memory (chqrlie)
@ -4151,7 +4159,7 @@ static void do_next_error(EditState *s, int arg, int dir)
qe_register_transient_binding(qs, "previous-error", "M-p");
}
#endif
put_status(s, "=> %s", error_state.message);
put_status(s, "=> %s", sep->message);
}
static int match_digits(const char32_t *buf, int n, char32_t sep) {

View file

@ -2038,6 +2038,13 @@ Start a directory enumeration.
* argument `pattern` a file pattern using `?` and `*` with the classic
semantics used by unix shells
* argument `flags` a combination of options:
- `FF_PATH`: the `path` argument is a list of directories
- `FF_NODIR`: do not match directory names
- `FF_NOXXDIR`: do not match `.` or `..`
- `FF_ONLYDIR`: do not match non directories
- 0 ... 15: maximum subdirectory depth for recursive matching
Return a pointer to an opaque FindFileState structure.
### `int from_hex(int c);`
@ -2461,17 +2468,17 @@ and insertion sort for small chunks. The GNU lib C on linux also
has a function `qsort_r()` with similar semantics but a different
calling convention.
### `int qe_shell_match(const char *string, const char *pattern);`
### `int qe_shell_match(const char *str, const char *pattern);`
Test whether a filename or pathname matches a shell-style pattern
* argument `string` a valid pointer to a string representing a
* argument `str` a valid pointer to a string representing a
filename
* argument `pattern` a valid pointer to a file pattern using `?`
and `*` with the classic semantics used by unix shells
Return non zero if `string` matches `pattern`.
Return non zero if `str` matches `pattern`.
Note: this function is a simplified version of POSIX function
`fnmatch` defined in header `<fnmatch.h>`

2
qe.c
View file

@ -9078,7 +9078,7 @@ void qe_kill_buffer(QEmacsState *qs, EditBuffer *b)
}
/* return TRUE if absolute path. works for files and URLs */
static int is_abs_path(const char *path)
int is_abs_path(const char *path)
{
size_t prefix;

1
qe.h
View file

@ -134,6 +134,7 @@ struct CompleteState {
char current[MAX_FILENAME_SIZE];
};
int is_abs_path(const char *path);
void canonicalize_absolute_path(EditState *s, char *buf, int buf_size, const char *path1);
void canonicalize_absolute_buffer_path(EditBuffer *b, int offset,
char *buf, int buf_size,

74
util.c
View file

@ -35,14 +35,14 @@
#include "config.h" /* for CONFIG_WIN32 */
#include "util.h"
int qe_shell_match(const char *string, const char *pattern) {
int qe_shell_match(const char *str, const char *pattern) {
/*@API utils
Test whether a filename or pathname matches a shell-style pattern
@argument `string` a valid pointer to a string representing a
@argument `str` a valid pointer to a string representing a
filename
@argument `pattern` a valid pointer to a file pattern using `?`
and `*` with the classic semantics used by unix shells
@return non zero if `string` matches `pattern`.
@return non zero if `str` matches `pattern`.
@note this function is a simplified version of POSIX function
`fnmatch` defined in header `<fnmatch.h>`
*/
@ -51,22 +51,22 @@ int qe_shell_match(const char *string, const char *pattern) {
if (c == '*') {
if (*pattern == '\0')
return 1;
while (*string) {
if (qe_shell_match(string, pattern))
while (*str) {
if (qe_shell_match(str, pattern))
return 1;
string++;
str++;
}
return 0;
} else
if (c == '?') {
if (*string++ == '\0')
if (*str++ == '\0')
return 0;
} else
if (c != *string++) {
if (c != *str++) {
return 0;
}
}
return *string == '\0';
return *str == '\0';
}
#define MAX_FILENAME_SIZE 1024 /* Size for a filename buffer */
@ -78,6 +78,7 @@ struct FindFileState {
const char *bufptr;
int flags;
int depth;
size_t dirlen;
DIR *dir;
DIR *parent_dir[FF_DEPTH];
size_t parent_len[FF_DEPTH];
@ -89,19 +90,26 @@ FindFileState *find_file_open(const char *path, const char *pattern, int flags)
@argument `path` the initial directory for the enumeration.
@argument `pattern` a file pattern using `?` and `*` with the classic
semantics used by unix shells
@argument `flags` a combination of options:
- `FF_PATH`: the `path` argument is a list of directories
- `FF_NODIR`: do not match directory names
- `FF_NOXXDIR`: do not match `.` or `..`
- `FF_ONLYDIR`: do not match non directories
- 0 ... 15: maximum subdirectory depth for recursive matching
@return a pointer to an opaque FindFileState structure.
*/
// XXX: should check if pattern has wildcards
FindFileState *s;
s = qe_mallocz(FindFileState);
if (!s)
return NULL;
pstrcpy(s->path, sizeof(s->path), path);
pstrcpy(s->pattern, sizeof(s->pattern), pattern);
s->bufptr = s->path;
s->dir = NULL;
s->flags = flags;
if (s) {
pstrcpy(s->path, sizeof(s->path), path);
pstrcpy(s->pattern, sizeof(s->pattern), pattern);
s->bufptr = s->path;
s->dir = NULL;
s->flags = flags;
if (!s->pattern[strcspn(s->pattern, "*?")])
s->flags |= FF_NOPAT;
}
return s;
}
@ -113,7 +121,7 @@ int find_file_next(FindFileState *s, char *filename, int filename_size_max) {
array in bytes.
@return `0` if there is a match, `-1` if no more files match the pattern.
*/
// XXX: should match wildcards in directory names
struct stat st;
struct dirent *dirent;
const char *p;
@ -127,7 +135,8 @@ int find_file_next(FindFileState *s, char *filename, int filename_size_max) {
s->depth--;
s->dir = s->parent_dir[s->depth];
s->parent_dir[s->depth] = NULL;
s->dirpath[s->parent_len[s->depth]] = '\0';
s->dirlen = s->parent_len[s->depth];
s->dirpath[s->dirlen] = '\0';
continue;
}
p = s->bufptr;
@ -137,10 +146,25 @@ int find_file_next(FindFileState *s, char *filename, int filename_size_max) {
p += strcspn(p, ":");
else
p += strlen(p);
pstrncpy(s->dirpath, sizeof(s->dirpath), s->bufptr, p - s->bufptr);
pstrncpy(s->dirpath, countof(s->dirpath), s->bufptr, p - s->bufptr);
if (*p == ':')
p++;
s->bufptr = p;
new_dir:
s->dirlen = strlen(s->dirpath);
if (s->flags & FF_NOPAT) {
makepath(s->dirpath, countof(s->dirpath), s->dirpath, s->pattern);
if (!stat(s->dirpath, &st)) {
if ((S_ISDIR(st.st_mode) && !(s->flags & FF_NODIR))
|| (S_ISREG(st.st_mode) && !(s->flags & FF_ONLYDIR))) {
pstrcpy(filename, filename_size_max, s->dirpath);
return 0;
}
}
s->dirpath[s->dirlen] = '\0';
if (!(s->flags & FF_DEPTH))
continue;
}
s->dir = opendir(s->dirpath);
} else {
int isdir = 0;
@ -160,11 +184,11 @@ int find_file_next(FindFileState *s, char *filename, int filename_size_max) {
} else {
if (s->depth < (s->flags & FF_DEPTH)) {
s->parent_dir[s->depth] = s->dir;
s->parent_len[s->depth] = strlen(s->dirpath);
s->parent_len[s->depth] = s->dirlen;
s->depth++;
makepath(s->dirpath, sizeof(s->dirpath), s->dirpath, dirent->d_name);
s->dir = opendir(s->dirpath);
continue;
s->dir = NULL;
makepath(s->dirpath, countof(s->dirpath), s->dirpath, dirent->d_name);
goto new_dir;
}
}
if (s->flags & FF_NODIR)
@ -173,6 +197,8 @@ int find_file_next(FindFileState *s, char *filename, int filename_size_max) {
if (s->flags & FF_ONLYDIR)
continue;
}
if (s->flags & FF_NOPAT)
continue;
if (qe_shell_match(dirent->d_name, s->pattern)) {
makepath(filename, filename_size_max,
s->dirpath, dirent->d_name);

1
util.h
View file

@ -53,6 +53,7 @@ typedef struct FindFileState FindFileState;
#define FF_NODIR 0x020 /* only match regular files */
#define FF_NOXXDIR 0x040 /* do not match . or .. */
#define FF_ONLYDIR 0x080 /* do not match non directories */
#define FF_NOPAT 0x100 /* do not use shell matching */
#define FF_DEPTH 0x00f /* max recursion depth */
FindFileState *find_file_open(const char *path, const char *pattern, int flags);