diff options
-rw-r--r-- | buffer.c | 24 | ||||
-rw-r--r-- | filesystem-posix.c | 7 | ||||
-rw-r--r-- | filesystem.h | 1 | ||||
-rw-r--r-- | string32.c | 13 | ||||
-rw-r--r-- | ui.c | 79 |
5 files changed, 113 insertions, 11 deletions
@@ -34,6 +34,19 @@ static void buffer_clear_redo_history(TextBuffer *buffer) { arr_clear(buffer->redo_history); } +static void buffer_clear_undo_history(TextBuffer *buffer) { + arr_foreach_ptr(buffer->undo_history, BufferEdit, edit) { + buffer_edit_free(edit); + } + arr_clear(buffer->undo_history); +} + +// clear all undo and redo events +void buffer_clear_undo_redo(TextBuffer *buffer) { + buffer_clear_undo_history(buffer); + buffer_clear_redo_history(buffer); +} + // add this edit to the undo history static void buffer_append_edit(TextBuffer *buffer, BufferEdit const *edit) { // whenever an edit is made, clear the redo history @@ -97,6 +110,10 @@ static void buffer_pos_validate(TextBuffer *buffer, BufferPos *p) { p->index = line_len; } +static void buffer_validate_cursor(TextBuffer *buffer) { + buffer_pos_validate(buffer, &buffer->cursor_pos); +} + static bool buffer_pos_valid(TextBuffer *buffer, BufferPos p) { return p.line < buffer->nlines && p.index <= buffer->lines[p.line].len; } @@ -1501,6 +1518,9 @@ void buffer_delete_chars_at_pos(TextBuffer *buffer, BufferPos pos, i64 nchars_) } buffer_remove_last_edit_if_empty(buffer); + + // cursor position could have been invalidated by this edit + buffer_validate_cursor(buffer); } // Delete characters between the given buffer positions. Returns number of characters deleted. @@ -1537,7 +1557,7 @@ void buffer_insert_text_at_cursor(TextBuffer *buffer, String32 str) { } void buffer_insert_char_at_cursor(TextBuffer *buffer, char32_t c) { - String32 s = {1, &c}; + String32 s = {&c, 1}; buffer_insert_text_at_cursor(buffer, s); } @@ -1607,7 +1627,7 @@ static Status buffer_undo_edit(TextBuffer *buffer, BufferEdit const *edit, Buffe // create inverse edit if (buffer_edit_create(buffer, inverse, edit->pos, edit->new_len, edit->prev_len)) { buffer_delete_chars_at_pos(buffer, edit->pos, (i64)edit->new_len); - String32 str = {edit->prev_len, edit->prev_text}; + String32 str = {edit->prev_text, edit->prev_len}; buffer_insert_text_at_pos(buffer, edit->pos, str); success = true; } diff --git a/filesystem-posix.c b/filesystem-posix.c index ebf1407..cf4a04b 100644 --- a/filesystem-posix.c +++ b/filesystem-posix.c @@ -7,8 +7,15 @@ FsType fs_path_type(char const *path) { struct stat statbuf = {0}; + char linkbuf[8]; + if (readlink(path, linkbuf, sizeof linkbuf) != -1) { + // unfortunately there is no way of telling from stat alone whether a directory is a symbolic link >:( + return FS_LINK; + } if (stat(path, &statbuf) != 0) return FS_NON_EXISTENT; + if (S_ISLNK(statbuf.st_mode)) + return FS_LINK; if (S_ISREG(statbuf.st_mode)) return FS_FILE; if (S_ISDIR(statbuf.st_mode)) diff --git a/filesystem.h b/filesystem.h index 2e561cd..764f45a 100644 --- a/filesystem.h +++ b/filesystem.h @@ -5,6 +5,7 @@ typedef enum { FS_NON_EXISTENT, FS_FILE, FS_DIRECTORY, + FS_LINK, FS_OTHER } FsType; @@ -1,9 +1,18 @@ // UTF-32 string typedef struct { - size_t len; char32_t *str; + size_t len; } String32; +String32 str32(char32_t *str, size_t len) { + String32 s = {str, len}; + return s; +} + +String32 str32_substr(String32 s, size_t from, size_t len) { + return str32(s.str + from, len); +} + void str32_free(String32 *s) { free(s->str); s->str = NULL; @@ -13,7 +22,7 @@ void str32_free(String32 *s) { // the string returned should be str32_free'd. // this will return an empty string if the allocation failed or the string is invalid UTF-8 String32 str32_from_utf8(char const *utf8) { - String32 string = {0, NULL}; + String32 string = {NULL, 0}; size_t len = strlen(utf8); if (len) { // the wide string uses at most as many "characters" (elements?) as the UTF-8 string @@ -43,13 +43,13 @@ static int qsort_file_entry_cmp(void const *av, void const *bv) { } // cd to the directory `name`. `name` cannot include any path separators. -static void file_selector_cd(FileSelector *fs, char const *name) { - if (*name == '\0' || streq(name, ".")) { +static void file_selector_cd1(FileSelector *fs, char const *name, size_t name_len) { + if (name_len == 0 || (name_len == 1 && name[0] == '.')) { // no name, or . return; } - if (streq(name, "..")) { + if (name_len == 2 && name[0] == '.' && name[1] == '.') { // .. char *last_sep = strrchr(fs->cwd, PATH_SEPARATOR); if (last_sep) { @@ -64,21 +64,84 @@ static void file_selector_cd(FileSelector *fs, char const *name) { } } } else { - // add path separator to end - arrcstr_append_str(fs->cwd, PATH_SEPARATOR_STR); + // add path separator to end if not already there (which could happen in the case of /) + if (fs->cwd[strlen(fs->cwd) - 1] != PATH_SEPARATOR) + arrcstr_append_str(fs->cwd, PATH_SEPARATOR_STR); // add name itself - arrcstr_append_str(fs->cwd, name); + arrcstr_append_strn(fs->cwd, name, name_len); + } +} + +// go to the directory `path`. make sure `path` only contains path separators like PATH_SEPARATOR, not any +// other members of ALL_PATH_SEPARATORS +static void file_selector_cd(FileSelector *fs, char const *path) { + size_t path_len = strlen(path); + if (path_len == 0) return; + + if (path[0] == PATH_SEPARATOR + #if _WIN32 + || path[1] == ':' && path[2] == PATH_SEPARATOR + #endif + ) { + // absolute path (e.g. /foo, c:\foo) + arr_clear(fs->cwd); + if (path_len > 1 && +#if _WIN32 + !(path_len == 3 && path[1] == ':') +#endif + path[path_len - 1] == PATH_SEPARATOR) { + // path ends with path separator + --path_len; + } + arrcstr_append_strn(fs->cwd, path, path_len); + return; + } + + char const *p = path; + + while (*p) { + size_t len = strcspn(p, PATH_SEPARATOR_STR); + file_selector_cd1(fs, p, len); + p += len; + p += strspn(p, PATH_SEPARATOR_STR); } } // returns the name of the selected file, or NULL // if none was selected. the returned pointer should be freed. static char *file_selector_update(Ted *ted, FileSelector *fs) { - String32 search_term32 = buffer_get_line(&ted->line_buffer, 0); + TextBuffer *line_buffer = &ted->line_buffer; + String32 search_term32 = buffer_get_line(line_buffer, 0); if (!fs->cwd) { // set the file selector's directory to our current directory. arrcstr_append_str(fs->cwd, ted->cwd); } + + + // check if the search term contains a path separator. if so, cd to the dirname. + u32 last_path_sep = U32_MAX; + for (u32 i = 0; i < search_term32.len; ++i) { + char32_t c = search_term32.str[i]; + if (c < CHAR_MAX && strchr(ALL_PATH_SEPARATORS, (char)c)) + last_path_sep = i; + } + + if (last_path_sep != U32_MAX) { + bool include_last_path_sep = last_path_sep == 0; + String32 dir_name32 = str32_substr(search_term32, 0, last_path_sep + include_last_path_sep); + char *dir_name = str32_to_utf8_cstr(dir_name32); + if (dir_name) { + // replace all members of ALL_PATH_SEPARATORS with PATH_SEPARATOR in dir_name (i.e. change / to \ on windows) + for (char *p = dir_name; *p; ++p) + if (strchr(ALL_PATH_SEPARATORS, *p)) + *p = PATH_SEPARATOR; + + file_selector_cd(fs, dir_name); + buffer_delete_chars_at_pos(line_buffer, buffer_start_of_file(line_buffer), last_path_sep + 1); // delete up to and including the last path separator + buffer_clear_undo_redo(line_buffer); + } + } + char *search_term = search_term32.len ? str32_to_utf8_cstr(search_term32) : NULL; bool submitted = fs->submitted; @@ -103,6 +166,7 @@ static char *file_selector_update(Ted *ted, FileSelector *fs) { break; case FS_DIRECTORY: file_selector_cd(fs, name); + buffer_clear(line_buffer); // clear search term break; default: break; } @@ -119,6 +183,7 @@ static char *file_selector_update(Ted *ted, FileSelector *fs) { break; case FS_DIRECTORY: file_selector_cd(fs, name); + buffer_clear(line_buffer); // clear search term break; default: break; } |