diff options
| author | Mitja Felicijan <mitja.felicijan@gmail.com> | 2026-01-21 22:52:54 +0100 |
|---|---|---|
| committer | Mitja Felicijan <mitja.felicijan@gmail.com> | 2026-01-21 22:52:54 +0100 |
| commit | dcacc00e3750300617ba6e16eb346713f91a783a (patch) | |
| tree | 38e2d4fb5ed9d119711d4295c6eda4b014af73fd /examples/dte | |
| parent | 58dac10aeb8f5a041c46bddbeaf4c7966a99b998 (diff) | |
| download | crep-dcacc00e3750300617ba6e16eb346713f91a783a.tar.gz | |
Remove testing data
Diffstat (limited to 'examples/dte')
102 files changed, 0 insertions, 19527 deletions
diff --git a/examples/dte/README.md b/examples/dte/README.md deleted file mode 100644 index 6f74c23..0000000 --- a/examples/dte/README.md +++ /dev/null @@ -1,19 +0,0 @@ -dte source code -=============== - -This directory contains the `dte` source code. It makes liberal use -of ISO C99 features and POSIX 2008 APIs, but generally requires very -little else. - -The main editor code is in the base directory and various other -(somewhat reusable) parts are in sub-directories: - -* `command/` - command language parsing and execution -* `editorconfig/` - [EditorConfig] implementation -* `filetype/` - filetype detection -* `syntax/` - syntax highlighting -* `terminal/` - terminal input/output handling -* `util/` - data structures, string utilities, etc. - - -[EditorConfig]: https://editorconfig.org/ diff --git a/examples/dte/bind.c b/examples/dte/bind.c deleted file mode 100644 index 222ee27..0000000 --- a/examples/dte/bind.c +++ /dev/null @@ -1,115 +0,0 @@ -#include <limits.h> -#include <stdlib.h> -#include "bind.h" -#include "change.h" -#include "command/macro.h" -#include "command/run.h" -#include "command/serialize.h" -#include "util/debug.h" -#include "util/xmalloc.h" - -void add_binding(IntMap *bindings, KeyCode key, CachedCommand *cc) -{ - cached_command_free(intmap_insert_or_replace(bindings, key, cc)); -} - -void remove_binding(IntMap *bindings, KeyCode key) -{ - cached_command_free(intmap_remove(bindings, key)); -} - -const CachedCommand *lookup_binding(const IntMap *bindings, KeyCode key) -{ - return intmap_get(bindings, key); -} - -void free_bindings(IntMap *bindings) -{ - intmap_free(bindings, (FreeFunction)cached_command_free); -} - -bool handle_binding(EditorState *e, InputMode mode, KeyCode key) -{ - const IntMap *bindings = &e->modes[mode].key_bindings; - const CachedCommand *binding = lookup_binding(bindings, key); - if (!binding) { - return false; - } - - // If the command isn't cached or a macro is being recorded - const CommandSet *cmds = e->modes[mode].cmds; - if (!binding->cmd || (cmds->macro_record && macro_is_recording(&e->macro))) { - // Parse and run command string - CommandRunner runner = cmdrunner_for_mode(e, mode, true); - return handle_command(&runner, binding->cmd_str); - } - - // Command is cached; call it directly - begin_change(CHANGE_MERGE_NONE); - current_command = binding->cmd; - bool r = binding->cmd->cmd(e, &binding->a); - current_command = NULL; - end_change(); - return r; -} - -typedef struct { - KeyCode key; - const char *cmd; -} KeyBinding; - -static int binding_cmp(const void *ap, const void *bp) -{ - static_assert((MOD_MASK | KEY_SPECIAL_MAX) <= INT_MAX); - const KeyBinding *a = ap; - const KeyBinding *b = bp; - return (int)a->key - (int)b->key; -} - -UNITTEST { - KeyBinding a = {.key = KEY_F5}; - KeyBinding b = {.key = KEY_F5}; - BUG_ON(binding_cmp(&a, &b) != 0); - b.key = KEY_F3; - BUG_ON(binding_cmp(&a, &b) <= 0); - b.key = KEY_F12; - BUG_ON(binding_cmp(&a, &b) >= 0); -} - -bool dump_bindings(const IntMap *bindings, const char *flag, String *buf) -{ - const size_t count = bindings->count; - if (unlikely(count == 0)) { - return false; - } - - // Clone the contents of the map as an array of key/command pairs - KeyBinding *array = xnew(*array, count); - size_t n = 0; - for (IntMapIter it = intmap_iter(bindings); intmap_next(&it); ) { - const CachedCommand *cc = it.entry->value; - array[n++] = (KeyBinding) { - .key = it.entry->key, - .cmd = cc->cmd_str, - }; - } - - // Sort the array - BUG_ON(n != count); - qsort(array, count, sizeof(array[0]), binding_cmp); - - // Serialize the bindings in sorted order - char keystr[KEYCODE_STR_MAX]; - for (size_t i = 0; i < count; i++) { - string_append_literal(buf, "bind "); - string_append_cstring(buf, flag); - size_t keylen = keycode_to_string(array[i].key, keystr); - string_append_escaped_arg_sv(buf, string_view(keystr, keylen), true); - string_append_byte(buf, ' '); - string_append_escaped_arg(buf, array[i].cmd, true); - string_append_byte(buf, '\n'); - } - - free(array); - return true; -} diff --git a/examples/dte/bind.h b/examples/dte/bind.h deleted file mode 100644 index 22d89bd..0000000 --- a/examples/dte/bind.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef BIND_H -#define BIND_H - -#include <stdbool.h> -#include "command/cache.h" -#include "editor.h" -#include "terminal/key.h" -#include "util/intmap.h" -#include "util/macros.h" -#include "util/string.h" - -void add_binding(IntMap *bindings, KeyCode key, CachedCommand *cc) NONNULL_ARGS; -void remove_binding(IntMap *bindings, KeyCode key) NONNULL_ARGS; -const CachedCommand *lookup_binding(const IntMap *bindings, KeyCode key) NONNULL_ARGS; -bool handle_binding(EditorState *e, InputMode mode, KeyCode key) NONNULL_ARGS WARN_UNUSED_RESULT; -void free_bindings(IntMap *bindings) NONNULL_ARGS; -bool dump_bindings(const IntMap *bindings, const char *flag, String *buf) NONNULL_ARGS WARN_UNUSED_RESULT; - -#endif - diff --git a/examples/dte/block-iter.c b/examples/dte/block-iter.c deleted file mode 100644 index 73fc2ec..0000000 --- a/examples/dte/block-iter.c +++ /dev/null @@ -1,343 +0,0 @@ -#include <string.h> -#include "block-iter.h" -#include "util/debug.h" -#include "util/utf8.h" -#include "util/xmalloc.h" - -void block_iter_normalize(BlockIter *bi) -{ - const Block *blk = bi->blk; - if (bi->offset == blk->size && blk->node.next != bi->head) { - bi->blk = BLOCK(blk->node.next); - bi->offset = 0; - } -} - -/* - * Move after next newline (beginning of next line or end of file). - * Returns number of bytes iterator advanced. - */ -size_t block_iter_eat_line(BlockIter *bi) -{ - block_iter_normalize(bi); - const size_t offset = bi->offset; - if (unlikely(offset == bi->blk->size)) { - return 0; - } - - // There must be at least one newline - if (bi->blk->nl == 1) { - bi->offset = bi->blk->size; - } else { - const unsigned char *end; - end = memchr(bi->blk->data + offset, '\n', bi->blk->size - offset); - BUG_ON(!end); - bi->offset = (size_t)(end + 1 - bi->blk->data); - } - - return bi->offset - offset; -} - -/* - * Move to beginning of next line. - * If there is no next line, iterator is not advanced. - * Returns number of bytes iterator advanced. - */ -size_t block_iter_next_line(BlockIter *bi) -{ - block_iter_normalize(bi); - const size_t offset = bi->offset; - if (unlikely(offset == bi->blk->size)) { - return 0; - } - - // There must be at least one newline - size_t new_offset; - if (bi->blk->nl == 1) { - new_offset = bi->blk->size; - } else { - const unsigned char *end; - end = memchr(bi->blk->data + offset, '\n', bi->blk->size - offset); - BUG_ON(!end); - new_offset = (size_t)(end + 1 - bi->blk->data); - } - if (new_offset == bi->blk->size && bi->blk->node.next == bi->head) { - return 0; - } - - bi->offset = new_offset; - return bi->offset - offset; -} - -/* - * Move to beginning of previous line. - * Returns number of bytes moved, which is zero if there's no previous line. - */ -size_t block_iter_prev_line(BlockIter *bi) -{ - Block *blk = bi->blk; - size_t offset = bi->offset; - size_t start = offset; - - while (offset && blk->data[offset - 1] != '\n') { - offset--; - } - - if (!offset) { - if (blk->node.prev == bi->head) { - return 0; - } - bi->blk = blk = BLOCK(blk->node.prev); - offset = blk->size; - start += offset; - } - - offset--; - while (offset && blk->data[offset - 1] != '\n') { - offset--; - } - bi->offset = offset; - return start - offset; -} - -size_t block_iter_get_char(const BlockIter *bi, CodePoint *up) -{ - BlockIter tmp = *bi; - return block_iter_next_char(&tmp, up); -} - -size_t block_iter_next_char(BlockIter *bi, CodePoint *up) -{ - size_t offset = bi->offset; - if (unlikely(offset == bi->blk->size)) { - if (unlikely(bi->blk->node.next == bi->head)) { - return 0; - } - bi->blk = BLOCK(bi->blk->node.next); - bi->offset = offset = 0; - } - - // Note: this block can't be empty - *up = bi->blk->data[offset]; - if (likely(*up < 0x80)) { - bi->offset++; - return 1; - } - - *up = u_get_nonascii(bi->blk->data, bi->blk->size, &bi->offset); - return bi->offset - offset; -} - -size_t block_iter_prev_char(BlockIter *bi, CodePoint *up) -{ - size_t offset = bi->offset; - if (unlikely(offset == 0)) { - if (unlikely(bi->blk->node.prev == bi->head)) { - return 0; - } - bi->blk = BLOCK(bi->blk->node.prev); - bi->offset = offset = bi->blk->size; - } - - // Note: this block can't be empty - *up = bi->blk->data[offset - 1]; - if (likely(*up < 0x80)) { - bi->offset--; - return 1; - } - - *up = u_prev_char(bi->blk->data, &bi->offset); - return offset - bi->offset; -} - -size_t block_iter_next_column(BlockIter *bi) -{ - CodePoint u; - size_t size = block_iter_next_char(bi, &u); - while (block_iter_get_char(bi, &u) && u_is_zero_width(u)) { - size += block_iter_next_char(bi, &u); - } - return size; -} - -size_t block_iter_prev_column(BlockIter *bi) -{ - CodePoint u; - size_t skip, total = 0; - do { - skip = block_iter_prev_char(bi, &u); - total += skip; - } while (skip && u_is_zero_width(u)); - return total; -} - -size_t block_iter_bol(BlockIter *bi) -{ - block_iter_normalize(bi); - size_t offset = bi->offset; - if (offset == 0 || offset == bi->blk->size) { - return 0; - } - - if (bi->blk->nl == 1) { - offset = 0; - } else { - while (offset && bi->blk->data[offset - 1] != '\n') { - offset--; - } - } - - const size_t ret = bi->offset - offset; - bi->offset = offset; - return ret; -} - -size_t block_iter_eol(BlockIter *bi) -{ - block_iter_normalize(bi); - const Block *blk = bi->blk; - const size_t offset = bi->offset; - if (unlikely(offset == blk->size)) { - // Cursor at end of last block - return 0; - } - if (blk->nl == 1) { - bi->offset = blk->size - 1; - return bi->offset - offset; - } - const unsigned char *end = memchr(blk->data + offset, '\n', blk->size - offset); - BUG_ON(!end); - bi->offset = (size_t)(end - blk->data); - return bi->offset - offset; -} - -void block_iter_back_bytes(BlockIter *bi, size_t count) -{ - while (count > bi->offset) { - count -= bi->offset; - bi->blk = BLOCK(bi->blk->node.prev); - bi->offset = bi->blk->size; - } - bi->offset -= count; -} - -void block_iter_skip_bytes(BlockIter *bi, size_t count) -{ - size_t avail = bi->blk->size - bi->offset; - while (count > avail) { - count -= avail; - bi->blk = BLOCK(bi->blk->node.next); - bi->offset = 0; - avail = bi->blk->size; - } - bi->offset += count; -} - -void block_iter_goto_offset(BlockIter *bi, size_t offset) -{ - Block *blk; - block_for_each(blk, bi->head) { - if (offset <= blk->size) { - bi->blk = blk; - bi->offset = offset; - return; - } - offset -= blk->size; - } -} - -void block_iter_goto_line(BlockIter *bi, size_t line) -{ - Block *blk = BLOCK(bi->head->next); - size_t nl = 0; - while (blk->node.next != bi->head && nl + blk->nl < line) { - nl += blk->nl; - blk = BLOCK(blk->node.next); - } - - bi->blk = blk; - bi->offset = 0; - while (nl < line) { - if (!block_iter_eat_line(bi)) { - break; - } - nl++; - } -} - -size_t block_iter_get_offset(const BlockIter *bi) -{ - const Block *blk; - size_t offset = 0; - block_for_each(blk, bi->head) { - if (blk == bi->blk) { - break; - } - offset += blk->size; - } - return offset + bi->offset; -} - -char *block_iter_get_bytes(const BlockIter *bi, size_t len) -{ - if (len == 0) { - return NULL; - } - - const Block *blk = bi->blk; - size_t offset = bi->offset; - size_t pos = 0; - char *buf = xmalloc(len); - - while (pos < len) { - const size_t avail = blk->size - offset; - size_t count = MIN(len - pos, avail); - memcpy(buf + pos, blk->data + offset, count); - pos += count; - BUG_ON(pos < len && blk->node.next == bi->head); - blk = BLOCK(blk->node.next); - offset = 0; - } - - return buf; -} - -// bi should be at bol -void fill_line_nl_ref(BlockIter *bi, StringView *line) -{ - block_iter_normalize(bi); - line->data = bi->blk->data + bi->offset; - const size_t max = bi->blk->size - bi->offset; - if (unlikely(max == 0)) { - // Cursor at end of last block - line->length = 0; - return; - } - if (bi->blk->nl == 1) { - BUG_ON(line->data[max - 1] != '\n'); - line->length = max; - return; - } - const unsigned char *nl = memchr(line->data, '\n', max); - BUG_ON(!nl); - line->length = (size_t)(nl - line->data + 1); - BUG_ON(line->length == 0); -} - -void fill_line_ref(BlockIter *bi, StringView *line) -{ - fill_line_nl_ref(bi, line); - // Trim the newline - line->length -= (line->length > 0); -} - -// Set the `line` argument to point to the current line and return -// the offset of the cursor, relative to the start of the line -// (zero means cursor is at bol) -size_t fetch_this_line(const BlockIter *bi, StringView *line) -{ - BlockIter tmp = *bi; - size_t count = block_iter_bol(&tmp); - fill_line_ref(&tmp, line); - return count; -} diff --git a/examples/dte/block-iter.h b/examples/dte/block-iter.h deleted file mode 100644 index aaef8cf..0000000 --- a/examples/dte/block-iter.h +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef BLOCK_ITER_H -#define BLOCK_ITER_H - -#include <stdbool.h> -#include <stddef.h> -#include "block.h" -#include "util/list.h" -#include "util/macros.h" -#include "util/string-view.h" -#include "util/unicode.h" - -typedef struct { - Block *blk; - const ListHead *head; - size_t offset; -} BlockIter; - -static inline void block_iter_bof(BlockIter *bi) -{ - bi->blk = BLOCK(bi->head->next); - bi->offset = 0; -} - -static inline void block_iter_eof(BlockIter *bi) -{ - bi->blk = BLOCK(bi->head->prev); - bi->offset = bi->blk->size; -} - -static inline bool block_iter_is_eof(const BlockIter *bi) -{ - return bi->offset == bi->blk->size && bi->blk->node.next == bi->head; -} - -static inline bool block_iter_is_bol(const BlockIter *bi) -{ - return bi->offset == 0 || bi->blk->data[bi->offset - 1] == '\n'; -} - -static inline bool block_iter_is_eol(const BlockIter *bi) -{ - const Block *blk = bi->blk; - size_t offset = bi->offset; - if (offset == blk->size) { - if (blk->node.next == bi->head) { - // EOF - return true; - } - // Normalize - blk = BLOCK(blk->node.next); - offset = 0; - } - return blk->data[offset] == '\n'; -} - -void block_iter_normalize(BlockIter *bi); -size_t block_iter_eat_line(BlockIter *bi); -size_t block_iter_next_line(BlockIter *bi); -size_t block_iter_prev_line(BlockIter *bi); -size_t block_iter_next_char(BlockIter *bi, CodePoint *up); -size_t block_iter_prev_char(BlockIter *bi, CodePoint *up); -size_t block_iter_next_column(BlockIter *bi); -size_t block_iter_prev_column(BlockIter *bi); -size_t block_iter_bol(BlockIter *bi); -size_t block_iter_eol(BlockIter *bi); -void block_iter_back_bytes(BlockIter *bi, size_t count); -void block_iter_skip_bytes(BlockIter *bi, size_t count); -void block_iter_goto_offset(BlockIter *bi, size_t offset); -void block_iter_goto_line(BlockIter *bi, size_t line); -size_t block_iter_get_offset(const BlockIter *bi) WARN_UNUSED_RESULT; -size_t block_iter_get_char(const BlockIter *bi, CodePoint *up) WARN_UNUSED_RESULT; -char *block_iter_get_bytes(const BlockIter *bi, size_t len) WARN_UNUSED_RESULT; - -void fill_line_ref(BlockIter *bi, StringView *line); -void fill_line_nl_ref(BlockIter *bi, StringView *line); -size_t fetch_this_line(const BlockIter *bi, StringView *line); - -#endif diff --git a/examples/dte/block.c b/examples/dte/block.c deleted file mode 100644 index 3953571..0000000 --- a/examples/dte/block.c +++ /dev/null @@ -1,19 +0,0 @@ -#include <stdlib.h> -#include "block.h" -#include "util/xmalloc.h" - -Block *block_new(size_t alloc) -{ - Block *blk = xnew0(Block, 1); - alloc = round_size_to_next_multiple(alloc, BLOCK_ALLOC_MULTIPLE); - blk->data = xmalloc(alloc); - blk->alloc = alloc; - return blk; -} - -void block_free(Block *blk) -{ - list_del(&blk->node); - free(blk->data); - free(blk); -} diff --git a/examples/dte/block.h b/examples/dte/block.h deleted file mode 100644 index 6fbf361..0000000 --- a/examples/dte/block.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef BLOCK_H -#define BLOCK_H - -#include <stddef.h> -#include "util/list.h" -#include "util/macros.h" - -enum { - BLOCK_ALLOC_MULTIPLE = 64 -}; - -// Blocks always contain whole lines. -// There's one zero-sized block for an empty file. -// Otherwise zero-sized blocks are forbidden. -typedef struct { - ListHead node; - unsigned char NONSTRING *data; - size_t size; - size_t alloc; - size_t nl; -} Block; - -#define block_for_each(block_, list_head_) \ - for ( \ - block_ = BLOCK((list_head_)->next); \ - &block_->node != (list_head_); \ - block_ = BLOCK(block_->node.next) \ - ) - -static inline Block *BLOCK(ListHead *item) -{ - static_assert(offsetof(Block, node) == 0); - return (Block*)item; -} - -Block *block_new(size_t alloc) RETURNS_NONNULL; -void block_free(Block *blk) NONNULL_ARGS; - -#endif diff --git a/examples/dte/bookmark.c b/examples/dte/bookmark.c deleted file mode 100644 index 174405f..0000000 --- a/examples/dte/bookmark.c +++ /dev/null @@ -1,103 +0,0 @@ -#include <stdlib.h> -#include "bookmark.h" -#include "buffer.h" -#include "editor.h" -#include "misc.h" -#include "move.h" -#include "search.h" -#include "util/debug.h" -#include "util/xmalloc.h" - -FileLocation *get_current_file_location(const View *view) -{ - const char *filename = view->buffer->abs_filename; - FileLocation *loc = xmalloc(sizeof(*loc)); - *loc = (FileLocation) { - .filename = filename ? xstrdup(filename) : NULL, - .buffer_id = view->buffer->id, - .line = view->cy + 1, - .column = view->cx_char + 1 - }; - return loc; -} - -bool file_location_go(Window *window, const FileLocation *loc) -{ - View *view = window_open_buffer(window, loc->filename, true, NULL); - if (!view) { - // Failed to open file; error message should be visible - return false; - } - - if (window->view != view) { - set_view(view); - // Force centering view to cursor, because file changed - view->force_center = true; - } - - if (loc->pattern) { - if (!search_tag(view, loc->pattern)) { - return false; - } - } else if (loc->line > 0) { - move_to_filepos(view, loc->line, loc->column ? loc->column : 1); - } - - unselect(view); - return true; -} - -static bool file_location_return(Window *window, const FileLocation *loc) -{ - Buffer *buffer = find_buffer_by_id(&window->editor->buffers, loc->buffer_id); - View *view; - if (buffer) { - view = window_get_view(window, buffer); - } else { - if (!loc->filename) { - // Can't restore closed buffer that had no filename; try again - return false; - } - view = window_open_buffer(window, loc->filename, true, NULL); - } - - if (!view) { - // Open failed; don't try again - return true; - } - - set_view(view); - unselect(view); - move_to_filepos(view, loc->line, loc->column); - return true; -} - -void file_location_free(FileLocation *loc) -{ - free(loc->filename); - free(loc->pattern); - free(loc); -} - -void bookmark_push(PointerArray *bookmarks, FileLocation *loc) -{ - const size_t max_entries = 256; - if (bookmarks->count == max_entries) { - file_location_free(ptr_array_remove_idx(bookmarks, 0)); - } - BUG_ON(bookmarks->count >= max_entries); - ptr_array_append(bookmarks, loc); -} - -void bookmark_pop(Window *window, PointerArray *bookmarks) -{ - void **ptrs = bookmarks->ptrs; - size_t count = bookmarks->count; - bool go = true; - while (count > 0 && go) { - FileLocation *loc = ptrs[--count]; - go = !file_location_return(window, loc); - file_location_free(loc); - } - bookmarks->count = count; -} diff --git a/examples/dte/bookmark.h b/examples/dte/bookmark.h deleted file mode 100644 index 5496589..0000000 --- a/examples/dte/bookmark.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef BOOKMARK_H -#define BOOKMARK_H - -#include <stdbool.h> -#include "util/macros.h" -#include "util/ptr-array.h" -#include "view.h" -#include "window.h" - -typedef struct { - char *filename; // Needed after buffer is closed - unsigned long buffer_id; // Needed if buffer doesn't have a filename - char *pattern; // Regex from tag file (if set, line and column are 0) - unsigned long line, column; // File position (if non-zero, pattern is NULL) -} FileLocation; - -FileLocation *get_current_file_location(const View *view) NONNULL_ARGS_AND_RETURN; -bool file_location_go(Window *window, const FileLocation *loc) NONNULL_ARGS WARN_UNUSED_RESULT; -void file_location_free(FileLocation *loc) NONNULL_ARGS; - -void bookmark_push(PointerArray *bookmarks, FileLocation *loc) NONNULL_ARGS; -void bookmark_pop(Window *window, PointerArray *bookmarks) NONNULL_ARGS; - -#endif diff --git a/examples/dte/buffer.c b/examples/dte/buffer.c deleted file mode 100644 index 705ec0c..0000000 --- a/examples/dte/buffer.c +++ /dev/null @@ -1,480 +0,0 @@ -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> -#include "buffer.h" -#include "editor.h" -#include "file-option.h" -#include "filetype.h" -#include "lock.h" -#include "syntax/state.h" -#include "util/debug.h" -#include "util/intern.h" -#include "util/log.h" -#include "util/numtostr.h" -#include "util/path.h" -#include "util/str-util.h" -#include "util/time-util.h" -#include "util/xmalloc.h" - -void set_display_filename(Buffer *buffer, char *name) -{ - free(buffer->display_filename); - buffer->display_filename = name; -} - -/* - * Mark line range min...max (inclusive) "changed". These lines will be - * redrawn when screen is updated. This is called even when content has not - * been changed, but selection has or line has been deleted and all lines - * after the deleted line move up. - * - * Syntax highlighter has different logic. It cares about contents of the - * lines, not about selection or if the lines have been moved up or down. - */ -void buffer_mark_lines_changed(Buffer *buffer, long min, long max) -{ - if (min > max) { - long tmp = min; - min = max; - max = tmp; - } - buffer->changed_line_min = MIN(min, buffer->changed_line_min); - buffer->changed_line_max = MAX(max, buffer->changed_line_max); -} - -const char *buffer_filename(const Buffer *buffer) -{ - const char *name = buffer->display_filename; - return name ? name : "(No name)"; -} - -void buffer_set_encoding(Buffer *buffer, Encoding encoding, bool utf8_bom) -{ - if ( - buffer->encoding.type != encoding.type - || buffer->encoding.name != encoding.name - ) { - const EncodingType type = encoding.type; - if (type == UTF8) { - buffer->bom = utf8_bom; - } else { - buffer->bom = type < NR_ENCODING_TYPES && !!get_bom_for_encoding(type); - } - buffer->encoding = encoding; - } -} - -Buffer *buffer_new(PointerArray *buffers, const GlobalOptions *gopts, const Encoding *encoding) -{ - static unsigned long id; - Buffer *buffer = xnew0(Buffer, 1); - list_init(&buffer->blocks); - buffer->cur_change = &buffer->change_head; - buffer->saved_change = &buffer->change_head; - buffer->id = ++id; - buffer->crlf_newlines = gopts->crlf_newlines; - - if (encoding) { - buffer_set_encoding(buffer, *encoding, gopts->utf8_bom); - } else { - buffer->encoding.type = ENCODING_AUTODETECT; - } - - static_assert(sizeof(*gopts) >= sizeof(CommonOptions)); - memcpy(&buffer->options, gopts, sizeof(CommonOptions)); - buffer->options.brace_indent = 0; - buffer->options.filetype = str_intern("none"); - buffer->options.indent_regex = NULL; - - ptr_array_append(buffers, buffer); - return buffer; -} - -Buffer *open_empty_buffer(PointerArray *buffers, const GlobalOptions *gopts) -{ - Encoding enc = encoding_from_type(UTF8); - Buffer *buffer = buffer_new(buffers, gopts, &enc); - - // At least one block required - Block *blk = block_new(1); - list_add_before(&blk->node, &buffer->blocks); - - return buffer; -} - -void free_blocks(Buffer *buffer) -{ - ListHead *item = buffer->blocks.next; - while (item != &buffer->blocks) { - ListHead *next = item->next; - Block *blk = BLOCK(item); - free(blk->data); - free(blk); - item = next; - } -} - -void free_buffer(Buffer *buffer) -{ - if (buffer->locked) { - unlock_file(buffer->abs_filename); - } - - free_changes(&buffer->change_head); - free(buffer->line_start_states.ptrs); - free(buffer->views.ptrs); - free(buffer->display_filename); - free(buffer->abs_filename); - - if (buffer->stdout_buffer) { - return; - } - - free_blocks(buffer); - free(buffer); -} - -void remove_and_free_buffer(PointerArray *buffers, Buffer *buffer) -{ - ptr_array_remove(buffers, buffer); - free_buffer(buffer); -} - -static bool same_file(const Buffer *buffer, const struct stat *st) -{ - return (st->st_dev == buffer->file.dev) && (st->st_ino == buffer->file.ino); -} - -Buffer *find_buffer(const PointerArray *buffers, const char *abs_filename) -{ - struct stat st; - bool st_ok = stat(abs_filename, &st) == 0; - for (size_t i = 0, n = buffers->count; i < n; i++) { - Buffer *buffer = buffers->ptrs[i]; - const char *f = buffer->abs_filename; - if ((f && streq(f, abs_filename)) || (st_ok && same_file(buffer, &st))) { - return buffer; - } - } - return NULL; -} - -Buffer *find_buffer_by_id(const PointerArray *buffers, unsigned long id) -{ - for (size_t i = 0, n = buffers->count; i < n; i++) { - Buffer *buffer = buffers->ptrs[i]; - if (buffer->id == id) { - return buffer; - } - } - return NULL; -} - -bool buffer_detect_filetype(Buffer *buffer, const PointerArray *filetypes) -{ - StringView line = STRING_VIEW_INIT; - if (BLOCK(buffer->blocks.next)->size) { - BlockIter bi = block_iter(buffer); - fill_line_ref(&bi, &line); - } else if (!buffer->abs_filename) { - return false; - } - - const char *ft = find_ft(filetypes, buffer->abs_filename, line); - if (ft && !streq(ft, buffer->options.filetype)) { - buffer->options.filetype = str_intern(ft); - return true; - } - - return false; -} - -void update_short_filename_cwd(Buffer *buffer, const StringView *home, const char *cwd) -{ - const char *abs = buffer->abs_filename; - if (!abs) { - return; - } - char *name = cwd ? short_filename_cwd(abs, cwd, home) : xstrdup(abs); - set_display_filename(buffer, name); -} - -void update_short_filename(Buffer *buffer, const StringView *home) -{ - BUG_ON(!buffer->abs_filename); - set_display_filename(buffer, short_filename(buffer->abs_filename, home)); -} - -void buffer_update_syntax(EditorState *e, Buffer *buffer) -{ - Syntax *syn = NULL; - if (buffer->options.syntax) { - // Even "none" can have syntax - syn = find_syntax(&e->syntaxes, buffer->options.filetype); - if (!syn) { - syn = load_syntax_by_filetype(e, buffer->options.filetype); - } - } - if (syn == buffer->syn) { - return; - } - - buffer->syn = syn; - if (syn) { - // Start state of first line is constant - PointerArray *s = &buffer->line_start_states; - if (!s->alloc) { - ptr_array_init(s, 64); - } - s->ptrs[0] = syn->start_state; - s->count = 1; - } - - mark_all_lines_changed(buffer); -} - -static bool allow_odd_indent(uint8_t indents_bitmask) -{ - static_assert(INDENT_WIDTH_MAX == 8); - return !!(indents_bitmask & 0x55); // 0x55 == 0b01010101 -} - -static int indent_len(StringView line, uint8_t indents_bitmask, bool *tab_indent) -{ - bool space_before_tab = false; - size_t spaces = 0; - size_t tabs = 0; - size_t pos = 0; - - for (size_t n = line.length; pos < n; pos++) { - switch (line.data[pos]) { - case '\t': - tabs++; - if (spaces) { - space_before_tab = true; - } - continue; - case ' ': - spaces++; - continue; - } - break; - } - - *tab_indent = false; - if (pos == line.length) { - return -1; // Whitespace only - } - if (pos == 0) { - return 0; // Not indented - } - if (space_before_tab) { - return -2; // Mixed indent - } - if (tabs) { - // Tabs and possible spaces after tab for alignment - *tab_indent = true; - return tabs * 8; - } - if (line.length > spaces && line.data[spaces] == '*') { - // '*' after indent, could be long C style comment - if (spaces & 1 || allow_odd_indent(indents_bitmask)) { - return spaces - 1; - } - } - return spaces; -} - -UNITTEST { - bool tab; - int len = indent_len(strview_from_cstring(" 4 space"), 0, &tab); - BUG_ON(len != 4); - BUG_ON(tab); - - len = indent_len(strview_from_cstring("\t\t2 tab"), 0, &tab); - BUG_ON(len != 16); - BUG_ON(!tab); - - len = indent_len(strview_from_cstring("no indent"), 0, &tab); - BUG_ON(len != 0); - - len = indent_len(strview_from_cstring(" \t mixed"), 0, &tab); - BUG_ON(len != -2); - - len = indent_len(strview_from_cstring("\t \t "), 0, &tab); - BUG_ON(len != -1); // whitespace only - - len = indent_len(strview_from_cstring(" * 5 space"), 0, &tab); - BUG_ON(len != 4); - - StringView line = strview_from_cstring(" * 4 space"); - len = indent_len(line, 0, &tab); - BUG_ON(len != 4); - len = indent_len(line, 1 << 2, &tab); - BUG_ON(len != 3); -} - -static bool detect_indent(Buffer *buffer) -{ - LocalOptions *options = &buffer->options; - unsigned int bitset = options->detect_indent; - BlockIter bi = block_iter(buffer); - unsigned int tab_count = 0; - unsigned int space_count = 0; - int current_indent = 0; - int counts[INDENT_WIDTH_MAX + 1] = {0}; - BUG_ON((bitset & ((1u << INDENT_WIDTH_MAX) - 1)) != bitset); - - for (size_t i = 0, j = 1; i < 200 && j > 0; i++, j = block_iter_next_line(&bi)) { - StringView line; - fill_line_ref(&bi, &line); - bool tab; - int indent = indent_len(line, bitset, &tab); - switch (indent) { - case -2: // Ignore mixed indent because tab width might not be 8 - case -1: // Empty line; no change in indent - continue; - case 0: - current_indent = 0; - continue; - } - - BUG_ON(indent <= 0); - int change = indent - current_indent; - if (change >= 1 && change <= INDENT_WIDTH_MAX) { - counts[change]++; - } - - if (tab) { - tab_count++; - } else { - space_count++; - } - current_indent = indent; - } - - if (tab_count == 0 && space_count == 0) { - return false; - } - - if (tab_count > space_count) { - options->emulate_tab = false; - options->expand_tab = false; - options->indent_width = options->tab_width; - return true; - } - - size_t m = 0; - for (size_t i = 1; i < ARRAYLEN(counts); i++) { - unsigned int bit = 1u << (i - 1); - if ((bitset & bit) && counts[i] > counts[m]) { - m = i; - } - } - - if (m == 0) { - return false; - } - - options->emulate_tab = true; - options->expand_tab = true; - options->indent_width = m; - return true; -} - -void buffer_setup(EditorState *e, Buffer *buffer) -{ - const char *filename = buffer->abs_filename; - buffer->setup = true; - buffer_detect_filetype(buffer, &e->filetypes); - set_file_options(e, buffer); - set_editorconfig_options(buffer); - buffer_update_syntax(e, buffer); - if (buffer->options.detect_indent && filename) { - detect_indent(buffer); - } - sanity_check_local_options(&buffer->options); -} - -void buffer_count_blocks_and_bytes(const Buffer *buffer, uintmax_t counts[2]) -{ - uintmax_t blocks = 0; - uintmax_t bytes = 0; - Block *blk; - block_for_each(blk, &buffer->blocks) { - blocks += 1; - bytes += blk->size; - } - counts[0] = blocks; - counts[1] = bytes; -} - -// TODO: Human-readable size (MiB/GiB/etc.) for "Bytes" and FileInfo::size -String dump_buffer(const Buffer *buffer) -{ - uintmax_t counts[2]; - buffer_count_blocks_and_bytes(buffer, counts); - BUG_ON(counts[0] < 1); - BUG_ON(!buffer->setup); - String buf = string_new(1024); - - string_sprintf ( - &buf, - "%s %s\n%s %lu\n%s %s\n%s %s\n%s %ju\n%s %zu\n%s %ju\n", - " Name:", buffer_filename(buffer), - " ID:", buffer->id, - " Encoding:", buffer->encoding.name, - " Filetype:", buffer->options.filetype, - " Blocks:", counts[0], - " Lines:", buffer->nl, - " Bytes:", counts[1] - ); - - if ( - buffer->stdout_buffer || buffer->temporary || buffer->readonly - || buffer->locked || buffer->crlf_newlines || buffer->bom - ) { - string_sprintf ( - &buf, - " Flags:%s%s%s%s%s%s\n", - buffer->stdout_buffer ? " STDOUT" : "", - buffer->temporary ? " TMP" : "", - buffer->readonly ? " RO" : "", - buffer->locked ? " LOCKED" : "", - buffer->crlf_newlines ? " CRLF" : "", - buffer->bom ? " BOM" : "" - ); - } - - if (buffer->views.count > 1) { - string_sprintf(&buf, " Views: %zu\n", buffer->views.count); - } - - if (!buffer->abs_filename) { - return buf; - } - - const FileInfo *file = &buffer->file; - unsigned int perms = file->mode & 07777; - char modestr[12]; - char timestr[64]; - if (!timespec_to_str(&file->mtime, timestr, sizeof(timestr))) { - memcpy(timestr, STRN("[error]") + 1); - } - - string_sprintf ( - &buf, - "\nLast stat:\n----------\n\n" - "%s %s\n%s %s\n%s -%s (%04o)\n%s %jd\n%s %jd\n%s %ju\n%s %jd\n%s %ju\n", - " Path:", buffer->abs_filename, - " Modified:", timestr, - " Mode:", filemode_to_str(file->mode, modestr), perms, - " User:", (intmax_t)file->uid, - " Group:", (intmax_t)file->gid, - " Size:", (uintmax_t)file->size, - " Device:", (intmax_t)file->dev, - " Inode:", (uintmax_t)file->ino - ); - - return buf; -} diff --git a/examples/dte/buffer.h b/examples/dte/buffer.h deleted file mode 100644 index 4450a8f..0000000 --- a/examples/dte/buffer.h +++ /dev/null @@ -1,103 +0,0 @@ -#ifndef BUFFER_H -#define BUFFER_H - -#include <limits.h> -#include <stdbool.h> -#include <stdint.h> -#include <sys/types.h> -#include <time.h> -#include "block-iter.h" -#include "change.h" -#include "encoding.h" -#include "options.h" -#include "syntax/syntax.h" -#include "util/list.h" -#include "util/macros.h" -#include "util/ptr-array.h" -#include "util/string-view.h" -#include "util/string.h" - -// Subset of stat(3) struct -typedef struct { - dev_t dev; - ino_t ino; - mode_t mode; - uid_t uid; - gid_t gid; - off_t size; - struct timespec mtime; -} FileInfo; - -// A representation of a specific file, as it pertains to editing, -// including text contents, filename (if saved), undo history and -// some file-specific metadata and options. -typedef struct Buffer { - ListHead blocks; - Change change_head; - Change *cur_change; - Change *saved_change; // Used to determine if buffer is modified - FileInfo file; - unsigned long id; // Needed for identifying buffers whose filename is NULL - size_t nl; - PointerArray views; // Views pointing to this buffer - char *display_filename; - char *abs_filename; - bool readonly; - bool temporary; - bool stdout_buffer; - bool locked; - bool setup; - bool crlf_newlines; - bool bom; - Encoding encoding; // Encoding of the file (buffer always contains UTF-8) - LocalOptions options; - Syntax *syn; - long changed_line_min; - long changed_line_max; - // Index 0 is always syn->states.ptrs[0]. - // Lowest bit of an invalidated value is 1. - PointerArray line_start_states; -} Buffer; - -static inline void mark_all_lines_changed(Buffer *buffer) -{ - buffer->changed_line_min = 0; - buffer->changed_line_max = LONG_MAX; -} - -static inline bool buffer_modified(const Buffer *buffer) -{ - return buffer->saved_change != buffer->cur_change && !buffer->temporary; -} - -static inline BlockIter block_iter(Buffer *buffer) -{ - return (BlockIter) { - .blk = BLOCK(buffer->blocks.next), - .head = &buffer->blocks, - .offset = 0 - }; -} - -struct EditorState; - -void buffer_mark_lines_changed(Buffer *buffer, long min, long max) NONNULL_ARGS; -void buffer_set_encoding(Buffer *buffer, Encoding encoding, bool utf8_bom) NONNULL_ARGS; -const char *buffer_filename(const Buffer *buffer) NONNULL_ARGS_AND_RETURN; -void set_display_filename(Buffer *buffer, char *name) NONNULL_ARG(1); -void update_short_filename_cwd(Buffer *buffer, const StringView *home, const char *cwd) NONNULL_ARG(1, 2); -void update_short_filename(Buffer *buffer, const StringView *home) NONNULL_ARGS; -Buffer *find_buffer(const PointerArray *buffers, const char *abs_filename) NONNULL_ARGS; -Buffer *find_buffer_by_id(const PointerArray *buffers, unsigned long id) NONNULL_ARGS; -Buffer *buffer_new(PointerArray *buffers, const GlobalOptions *gopts, const Encoding *encoding) RETURNS_NONNULL NONNULL_ARG(1, 2); -Buffer *open_empty_buffer(PointerArray *buffers, const GlobalOptions *gopts) NONNULL_ARGS_AND_RETURN; -void free_buffer(Buffer *buffer) NONNULL_ARGS; -void remove_and_free_buffer(PointerArray *buffers, Buffer *buffer) NONNULL_ARGS; -void free_blocks(Buffer *buffer) NONNULL_ARGS; -bool buffer_detect_filetype(Buffer *buffer, const PointerArray *filetypes) NONNULL_ARGS; -void buffer_update_syntax(struct EditorState *e, Buffer *buffer) NONNULL_ARGS; -void buffer_setup(struct EditorState *e, Buffer *buffer) NONNULL_ARGS; -void buffer_count_blocks_and_bytes(const Buffer *buffer, uintmax_t counts[2]) NONNULL_ARGS; -String dump_buffer(const Buffer *buffer) NONNULL_ARGS; - -#endif diff --git a/examples/dte/change.c b/examples/dte/change.c deleted file mode 100644 index 529036d..0000000 --- a/examples/dte/change.c +++ /dev/null @@ -1,417 +0,0 @@ -#include <stdlib.h> -#include <string.h> -#include "change.h" -#include "buffer.h" -#include "edit.h" -#include "error.h" -#include "util/debug.h" -#include "util/xmalloc.h" - -static ChangeMergeEnum change_merge; -static ChangeMergeEnum prev_change_merge; - -static Change *alloc_change(void) -{ - return xcalloc(sizeof(Change)); -} - -static void add_change(Buffer *buffer, Change *change) -{ - Change *head = buffer->cur_change; - change->next = head; - xrenew(head->prev, head->nr_prev + 1); - head->prev[head->nr_prev++] = change; - buffer->cur_change = change; -} - -// This doesn't need to be local to buffer because commands are atomic -static Change *change_barrier; - -static bool is_change_chain_barrier(const Change *change) -{ - return !change->ins_count && !change->del_count; -} - -static Change *new_change(Buffer *buffer) -{ - if (change_barrier) { - /* - * We are recording series of changes (:replace for example) - * and now we have just made the first change so we have to - * mark beginning of the chain. - * - * We could have done this before when starting the change - * chain but then we may have ended up with an empty chain. - * We don't want to record empty changes ever. - */ - add_change(buffer, change_barrier); - change_barrier = NULL; - } - - Change *change = alloc_change(); - add_change(buffer, change); - return change; -} - -static size_t buffer_offset(const View *view) -{ - return block_iter_get_offset(&view->cursor); -} - -static void record_insert(View *view, size_t len) -{ - Change *change = view->buffer->cur_change; - BUG_ON(!len); - if ( - change_merge == prev_change_merge - && change_merge == CHANGE_MERGE_INSERT - ) { - BUG_ON(change->del_count); - change->ins_count += len; - return; - } - - change = new_change(view->buffer); - change->offset = buffer_offset(view); - change->ins_count = len; -} - -static void record_delete(View *view, char *buf, size_t len, bool move_after) -{ - BUG_ON(!len); - BUG_ON(!buf); - - Change *change = view->buffer->cur_change; - if (change_merge == prev_change_merge) { - if (change_merge == CHANGE_MERGE_DELETE) { - xrenew(change->buf, change->del_count + len); - memcpy(change->buf + change->del_count, buf, len); - change->del_count += len; - free(buf); - return; - } - if (change_merge == CHANGE_MERGE_ERASE) { - xrenew(buf, len + change->del_count); - memcpy(buf + len, change->buf, change->del_count); - change->del_count += len; - free(change->buf); - change->buf = buf; - change->offset -= len; - return; - } - } - - change = new_change(view->buffer); - change->offset = buffer_offset(view); - change->del_count = len; - change->move_after = move_after; - change->buf = buf; -} - -static void record_replace(View *view, char *deleted, size_t del_count, size_t ins_count) -{ - BUG_ON(del_count && !deleted); - BUG_ON(!del_count && deleted); - BUG_ON(!del_count && !ins_count); - - Change *change = new_change(view->buffer); - change->offset = buffer_offset(view); - change->ins_count = ins_count; - change->del_count = del_count; - change->buf = deleted; -} - -void begin_change(ChangeMergeEnum m) -{ - change_merge = m; -} - -void end_change(void) -{ - prev_change_merge = change_merge; -} - -void begin_change_chain(void) -{ - BUG_ON(change_barrier); - - // Allocate change chain barrier but add it to the change tree only if - // there will be any real changes - change_barrier = alloc_change(); - change_merge = CHANGE_MERGE_NONE; -} - -void end_change_chain(View *view) -{ - if (change_barrier) { - // There were no changes in this change chain - free(change_barrier); - change_barrier = NULL; - } else { - // There were some changes; add end of chain marker - add_change(view->buffer, alloc_change()); - } -} - -static void fix_cursors(const View *view, size_t offset, size_t del, size_t ins) -{ - const Buffer *buffer = view->buffer; - for (size_t i = 0, n = buffer->views.count; i < n; i++) { - View *v = buffer->views.ptrs[i]; - if (v != view && offset < v->saved_cursor_offset) { - if (offset + del <= v->saved_cursor_offset) { - v->saved_cursor_offset -= del; - v->saved_cursor_offset += ins; - } else { - v->saved_cursor_offset = offset; - } - } - } -} - -static void reverse_change(View *view, Change *change) -{ - if (view->buffer->views.count > 1) { - fix_cursors(view, change->offset, change->ins_count, change->del_count); - } - - block_iter_goto_offset(&view->cursor, change->offset); - if (!change->ins_count) { - // Convert delete to insert - do_insert(view, change->buf, change->del_count); - if (change->move_after) { - block_iter_skip_bytes(&view->cursor, change->del_count); - } - change->ins_count = change->del_count; - change->del_count = 0; - free(change->buf); - change->buf = NULL; - } else if (change->del_count) { - // Reverse replace - size_t del_count = change->ins_count; - size_t ins_count = change->del_count; - char *buf = do_replace(view, del_count, change->buf, ins_count); - free(change->buf); - change->buf = buf; - change->ins_count = ins_count; - change->del_count = del_count; - } else { - // Convert insert to delete - change->buf = do_delete(view, change->ins_count, true); - change->del_count = change->ins_count; - change->ins_count = 0; - } -} - -bool undo(View *view) -{ - Change *change = view->buffer->cur_change; - view_reset_preferred_x(view); - if (!change->next) { - return false; - } - - if (is_change_chain_barrier(change)) { - unsigned long count = 0; - while (1) { - change = change->next; - if (is_change_chain_barrier(change)) { - break; - } - reverse_change(view, change); - count++; - } - if (count > 1) { - info_msg("Undid %lu changes", count); - } - } else { - reverse_change(view, change); - } - - view->buffer->cur_change = change->next; - return true; -} - -bool redo(View *view, unsigned long change_id) -{ - Change *change = view->buffer->cur_change; - view_reset_preferred_x(view); - if (!change->prev) { - // Don't complain if change_id is 0 - if (change_id) { - error_msg("Nothing to redo"); - } - return false; - } - - const unsigned long nr_prev = change->nr_prev; - BUG_ON(nr_prev == 0); - if (change_id == 0) { - // Default to newest change - change_id = nr_prev - 1; - if (nr_prev > 1) { - unsigned long i = change_id + 1; - info_msg("Redoing newest (%lu) of %lu possible changes", i, nr_prev); - } - } else { - if (--change_id >= nr_prev) { - if (nr_prev == 1) { - return error_msg("There is only 1 possible change to redo"); - } - return error_msg("There are only %lu possible changes to redo", nr_prev); - } - } - - change = change->prev[change_id]; - if (is_change_chain_barrier(change)) { - unsigned long count = 0; - while (1) { - change = change->prev[change->nr_prev - 1]; - if (is_change_chain_barrier(change)) { - break; - } - reverse_change(view, change); - count++; - } - if (count > 1) { - info_msg("Redid %lu changes", count); - } - } else { - reverse_change(view, change); - } - - view->buffer->cur_change = change; - return true; -} - -void free_changes(Change *c) -{ -top: - while (c->nr_prev) { - c = c->prev[c->nr_prev - 1]; - } - - // c is leaf now - while (c->next) { - Change *next = c->next; - free(c->buf); - free(c); - - c = next; - if (--c->nr_prev) { - goto top; - } - - // We have become leaf - free(c->prev); - } -} - -void buffer_insert_bytes(View *view, const char *buf, const size_t len) -{ - view_reset_preferred_x(view); - if (len == 0) { - return; - } - - size_t rec_len = len; - if (buf[len - 1] != '\n' && block_iter_is_eof(&view->cursor)) { - // Force newline at EOF - do_insert(view, "\n", 1); - rec_len++; - } - - do_insert(view, buf, len); - record_insert(view, rec_len); - - if (view->buffer->views.count > 1) { - fix_cursors(view, block_iter_get_offset(&view->cursor), len, 0); - } -} - -static bool would_delete_last_bytes(const View *view, size_t count) -{ - const Block *blk = view->cursor.blk; - size_t offset = view->cursor.offset; - while (1) { - size_t avail = blk->size - offset; - if (avail > count) { - return false; - } - - if (blk->node.next == view->cursor.head) { - return true; - } - - count -= avail; - blk = BLOCK(blk->node.next); - offset = 0; - } -} - -static void buffer_delete_bytes_internal(View *view, size_t len, bool move_after) -{ - view_reset_preferred_x(view); - if (len == 0) { - return; - } - - // Check if all newlines from EOF would be deleted - if (would_delete_last_bytes(view, len)) { - BlockIter bi = view->cursor; - CodePoint u; - if (block_iter_prev_char(&bi, &u) && u != '\n') { - // No newline before cursor - if (--len == 0) { - begin_change(CHANGE_MERGE_NONE); - return; - } - } - } - record_delete(view, do_delete(view, len, true), len, move_after); - - if (view->buffer->views.count > 1) { - fix_cursors(view, block_iter_get_offset(&view->cursor), len, 0); - } -} - -void buffer_delete_bytes(View *view, size_t len) -{ - buffer_delete_bytes_internal(view, len, false); -} - -void buffer_erase_bytes(View *view, size_t len) -{ - buffer_delete_bytes_internal(view, len, true); -} - -void buffer_replace_bytes(View *view, size_t del_count, const char *ins, size_t ins_count) -{ - view_reset_preferred_x(view); - if (del_count == 0) { - buffer_insert_bytes(view, ins, ins_count); - return; - } - if (ins_count == 0) { - buffer_delete_bytes(view, del_count); - return; - } - - // Check if all newlines from EOF would be deleted - if (would_delete_last_bytes(view, del_count)) { - if (ins[ins_count - 1] != '\n') { - // Don't replace last newline - if (--del_count == 0) { - buffer_insert_bytes(view, ins, ins_count); - return; - } - } - } - - char *deleted = do_replace(view, del_count, ins, ins_count); - record_replace(view, deleted, del_count, ins_count); - - if (view->buffer->views.count > 1) { - fix_cursors(view, block_iter_get_offset(&view->cursor), del_count, ins_count); - } -} diff --git a/examples/dte/change.h b/examples/dte/change.h deleted file mode 100644 index a0d08f1..0000000 --- a/examples/dte/change.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef CHANGE_H -#define CHANGE_H - -#include <stdbool.h> -#include <stddef.h> -#include "util/macros.h" -#include "view.h" - -typedef enum { - CHANGE_MERGE_NONE, - CHANGE_MERGE_INSERT, - CHANGE_MERGE_DELETE, - CHANGE_MERGE_ERASE, -} ChangeMergeEnum; - -typedef struct Change { - struct Change *next; - struct Change **prev; - unsigned long nr_prev; - bool move_after; // Move after inserted text when undoing delete? - size_t offset; - size_t del_count; - size_t ins_count; - char *buf; // Deleted bytes (inserted bytes need not be saved) -} Change; - -void begin_change(ChangeMergeEnum m); -void end_change(void); -void begin_change_chain(void); -void end_change_chain(View *view) NONNULL_ARGS; -bool undo(View *view) NONNULL_ARGS WARN_UNUSED_RESULT; -bool redo(View *view, unsigned long change_id) NONNULL_ARGS WARN_UNUSED_RESULT; -void free_changes(Change *c) NONNULL_ARGS; -void buffer_insert_bytes(View *view, const char *buf, size_t len) NONNULL_ARG(1); -void buffer_delete_bytes(View *view, size_t len) NONNULL_ARGS; -void buffer_erase_bytes(View *view, size_t len) NONNULL_ARGS; -void buffer_replace_bytes(View *view, size_t del_count, const char *ins, size_t ins_count) NONNULL_ARG(1); - -#endif diff --git a/examples/dte/cmdline.c b/examples/dte/cmdline.c deleted file mode 100644 index 8e57604..0000000 --- a/examples/dte/cmdline.c +++ /dev/null @@ -1,540 +0,0 @@ -#include <stdlib.h> -#include <string.h> -#include "cmdline.h" -#include "command/args.h" -#include "command/macro.h" -#include "commands.h" -#include "completion.h" -#include "copy.h" -#include "editor.h" -#include "history.h" -#include "options.h" -#include "search.h" -#include "terminal/osc52.h" -#include "util/ascii.h" -#include "util/bsearch.h" -#include "util/debug.h" -#include "util/log.h" -#include "util/utf8.h" - -static void cmdline_delete(CommandLine *c) -{ - size_t pos = c->pos; - size_t len = 1; - - if (pos == c->buf.len) { - return; - } - - u_get_char(c->buf.buffer, c->buf.len, &pos); - len = pos - c->pos; - string_remove(&c->buf, c->pos, len); -} - -void cmdline_clear(CommandLine *c) -{ - string_clear(&c->buf); - c->pos = 0; - c->search_pos = NULL; -} - -void cmdline_free(CommandLine *c) -{ - cmdline_clear(c); - string_free(&c->buf); - free(c->search_text); - reset_completion(c); -} - -static void set_text(CommandLine *c, const char *text) -{ - string_clear(&c->buf); - const size_t text_len = strlen(text); - c->pos = text_len; - string_append_buf(&c->buf, text, text_len); -} - -void cmdline_set_text(CommandLine *c, const char *text) -{ - c->search_pos = NULL; - set_text(c, text); -} - -static bool cmd_bol(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - e->cmdline.pos = 0; - reset_completion(&e->cmdline); - return true; -} - -static bool cmd_cancel(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - CommandLine *c = &e->cmdline; - cmdline_clear(c); - set_input_mode(e, INPUT_NORMAL); - reset_completion(c); - return true; -} - -static bool cmd_clear(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - cmdline_clear(&e->cmdline); - return true; -} - -static bool cmd_copy(EditorState *e, const CommandArgs *a) -{ - bool internal = cmdargs_has_flag(a, 'i') || a->flag_set == 0; - bool clipboard = cmdargs_has_flag(a, 'b'); - bool primary = cmdargs_has_flag(a, 'p'); - - String *buf = &e->cmdline.buf; - size_t len = buf->len; - if (internal) { - char *str = string_clone_cstring(buf); - record_copy(&e->clipboard, str, len, false); - } - - Terminal *term = &e->terminal; - if ((clipboard || primary) && term->features & TFLAG_OSC52_COPY) { - const char *str = string_borrow_cstring(buf); - if (!term_osc52_copy(&term->obuf, str, len, clipboard, primary)) { - LOG_ERRNO("term_osc52_copy"); - // TODO: return false ? - } - } - - return true; -} - -static bool cmd_delete(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - CommandLine *c = &e->cmdline; - cmdline_delete(c); - c->search_pos = NULL; - reset_completion(c); - return true; -} - -static bool cmd_delete_eol(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - CommandLine *c = &e->cmdline; - c->buf.len = c->pos; - c->search_pos = NULL; - reset_completion(c); - return true; -} - -static bool cmd_delete_word(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - CommandLine *c = &e->cmdline; - const unsigned char *buf = c->buf.buffer; - const size_t len = c->buf.len; - size_t i = c->pos; - - if (i == len) { - return true; - } - - while (i < len && is_word_byte(buf[i])) { - i++; - } - - while (i < len && !is_word_byte(buf[i])) { - i++; - } - - string_remove(&c->buf, c->pos, i - c->pos); - - c->search_pos = NULL; - reset_completion(c); - return true; -} - -static bool cmd_eol(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - CommandLine *c = &e->cmdline; - c->pos = c->buf.len; - reset_completion(c); - return true; -} - -static bool cmd_erase(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - CommandLine *c = &e->cmdline; - if (c->pos > 0) { - u_prev_char(c->buf.buffer, &c->pos); - cmdline_delete(c); - } - c->search_pos = NULL; - reset_completion(c); - return true; -} - -static bool cmd_erase_bol(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - CommandLine *c = &e->cmdline; - string_remove(&c->buf, 0, c->pos); - c->pos = 0; - c->search_pos = NULL; - reset_completion(c); - return true; -} - -static bool cmd_erase_word(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - CommandLine *c = &e->cmdline; - size_t i = c->pos; - if (i == 0) { - return true; - } - - // open /path/to/file^W => open /path/to/ - - // erase whitespace - while (i && ascii_isspace(c->buf.buffer[i - 1])) { - i--; - } - - // erase non-word bytes - while (i && !is_word_byte(c->buf.buffer[i - 1])) { - i--; - } - - // erase word bytes - while (i && is_word_byte(c->buf.buffer[i - 1])) { - i--; - } - - string_remove(&c->buf, i, c->pos - i); - c->pos = i; - c->search_pos = NULL; - reset_completion(c); - return true; -} - -static bool do_history_prev(const History *hist, CommandLine *c) -{ - if (!c->search_pos) { - free(c->search_text); - c->search_text = string_clone_cstring(&c->buf); - } - - if (history_search_forward(hist, &c->search_pos, c->search_text)) { - BUG_ON(!c->search_pos); - set_text(c, c->search_pos->text); - } - - reset_completion(c); - return true; -} - -static bool do_history_next(const History *hist, CommandLine *c) -{ - if (!c->search_pos) { - goto out; - } - - if (history_search_backward(hist, &c->search_pos, c->search_text)) { - BUG_ON(!c->search_pos); - set_text(c, c->search_pos->text); - } else { - set_text(c, c->search_text); - c->search_pos = NULL; - } - -out: - reset_completion(c); - return true; -} - -static bool cmd_search_history_next(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - return do_history_next(&e->search_history, &e->cmdline); -} - -static bool cmd_search_history_prev(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - return do_history_prev(&e->search_history, &e->cmdline); -} - -static bool cmd_command_history_next(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - return do_history_next(&e->command_history, &e->cmdline); -} - -static bool cmd_command_history_prev(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - return do_history_prev(&e->command_history, &e->cmdline); -} - -static bool cmd_left(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - CommandLine *c = &e->cmdline; - if (c->pos) { - u_prev_char(c->buf.buffer, &c->pos); - } - reset_completion(c); - return true; -} - -static bool cmd_paste(EditorState *e, const CommandArgs *a) -{ - CommandLine *c = &e->cmdline; - const Clipboard *clip = &e->clipboard; - string_insert_buf(&c->buf, c->pos, clip->buf, clip->len); - if (cmdargs_has_flag(a, 'm')) { - c->pos += clip->len; - } - c->search_pos = NULL; - reset_completion(c); - return true; -} - -static bool cmd_right(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - CommandLine *c = &e->cmdline; - if (c->pos < c->buf.len) { - u_get_char(c->buf.buffer, c->buf.len, &c->pos); - } - reset_completion(c); - return true; -} - -static bool cmd_toggle(EditorState *e, const CommandArgs *a) -{ - const char *option_name = a->args[0]; - bool global = cmdargs_has_flag(a, 'g'); - size_t nr_values = a->nr_args - 1; - if (nr_values == 0) { - return toggle_option(e, option_name, global, false); - } - - char **values = a->args + 1; - return toggle_option_values(e, option_name, global, false, values, nr_values); -} - -static bool cmd_word_bwd(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - CommandLine *c = &e->cmdline; - if (c->pos <= 1) { - c->pos = 0; - return true; - } - - const unsigned char *const buf = c->buf.buffer; - size_t i = c->pos - 1; - - while (i > 0 && !is_word_byte(buf[i])) { - i--; - } - - while (i > 0 && is_word_byte(buf[i])) { - i--; - } - - if (i > 0) { - i++; - } - - c->pos = i; - reset_completion(c); - return true; -} - -static bool cmd_word_fwd(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - CommandLine *c = &e->cmdline; - const unsigned char *buf = c->buf.buffer; - const size_t len = c->buf.len; - size_t i = c->pos; - - while (i < len && is_word_byte(buf[i])) { - i++; - } - - while (i < len && !is_word_byte(buf[i])) { - i++; - } - - c->pos = i; - reset_completion(c); - return true; -} - -static bool cmd_complete_next(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - complete_command_next(e); - return true; -} - -static bool cmd_complete_prev(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - complete_command_prev(e); - return true; -} - -static bool cmd_direction(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - toggle_search_direction(&e->search); - return true; -} - -static bool cmd_command_mode_accept(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - CommandLine *c = &e->cmdline; - reset_completion(c); - set_input_mode(e, INPUT_NORMAL); - - const char *str = string_borrow_cstring(&c->buf); - cmdline_clear(c); - if (!cmdargs_has_flag(a, 'H') && str[0] != ' ') { - // This is done before handle_command() because "command [text]" - // can modify the contents of the command-line - history_add(&e->command_history, str); - } - - current_command = NULL; - return handle_normal_command(e, str, true); -} - -static bool cmd_search_mode_accept(EditorState *e, const CommandArgs *a) -{ - CommandLine *c = &e->cmdline; - if (cmdargs_has_flag(a, 'e')) { - if (c->buf.len == 0) { - return true; - } - // Escape the regex; to match as plain text - char *original = string_clone_cstring(&c->buf); - size_t len = c->buf.len; - string_clear(&c->buf); - for (size_t i = 0; i < len; i++) { - char ch = original[i]; - if (is_regex_special_char(ch)) { - string_append_byte(&c->buf, '\\'); - } - string_append_byte(&c->buf, ch); - } - free(original); - } - - const char *str = NULL; - bool add_to_history = !cmdargs_has_flag(a, 'H'); - if (c->buf.len > 0) { - str = string_borrow_cstring(&c->buf); - BUG_ON(!str); - search_set_regexp(&e->search, str); - if (add_to_history) { - history_add(&e->search_history, str); - } - } - - if (e->macro.recording) { - const char *args[5]; - size_t i = 0; - if (str) { - if (e->search.reverse) { - args[i++] = "-r"; - } - if (!add_to_history) { - args[i++] = "-H"; - } - if (unlikely(str[0] == '-')) { - args[i++] = "--"; - } - args[i++] = str; - } else { - args[i++] = e->search.reverse ? "-p" : "-n"; - } - args[i] = NULL; - macro_command_hook(&e->macro, "search", (char**)args); - } - - current_command = NULL; - bool found = search_next(e->view, &e->search, e->options.case_sensitive_search); - cmdline_clear(c); - set_input_mode(e, INPUT_NORMAL); - return found; -} - -IGNORE_WARNING("-Wincompatible-pointer-types") - -static const Command common_cmds[] = { - {"bol", "", false, 0, 0, cmd_bol}, - {"cancel", "", false, 0, 0, cmd_cancel}, - {"clear", "", false, 0, 0, cmd_clear}, - {"copy", "bip", false, 0, 0, cmd_copy}, - {"delete", "", false, 0, 0, cmd_delete}, - {"delete-eol", "", false, 0, 0, cmd_delete_eol}, - {"delete-word", "", false, 0, 0, cmd_delete_word}, - {"eol", "", false, 0, 0, cmd_eol}, - {"erase", "", false, 0, 0, cmd_erase}, - {"erase-bol", "", false, 0, 0, cmd_erase_bol}, - {"erase-word", "", false, 0, 0, cmd_erase_word}, - {"left", "", false, 0, 0, cmd_left}, - {"paste", "m", false, 0, 0, cmd_paste}, - {"right", "", false, 0, 0, cmd_right}, - {"toggle", "g", false, 1, -1, cmd_toggle}, - {"word-bwd", "", false, 0, 0, cmd_word_bwd}, - {"word-fwd", "", false, 0, 0, cmd_word_fwd}, -}; - -static const Command search_cmds[] = { - {"accept", "eH", false, 0, 0, cmd_search_mode_accept}, - {"direction", "", false, 0, 0, cmd_direction}, - {"history-next", "", false, 0, 0, cmd_search_history_next}, - {"history-prev", "", false, 0, 0, cmd_search_history_prev}, -}; - -static const Command command_cmds[] = { - {"accept", "H", false, 0, 0, cmd_command_mode_accept}, - {"complete-next", "", false, 0, 0, cmd_complete_next}, - {"complete-prev", "", false, 0, 0, cmd_complete_prev}, - {"history-next", "", false, 0, 0, cmd_command_history_next}, - {"history-prev", "", false, 0, 0, cmd_command_history_prev}, -}; - -UNIGNORE_WARNINGS - -static const Command *find_cmd_mode_command(const char *name) -{ - const Command *cmd = BSEARCH(name, common_cmds, command_cmp); - return cmd ? cmd : BSEARCH(name, command_cmds, command_cmp); -} - -static const Command *find_search_mode_command(const char *name) -{ - const Command *cmd = BSEARCH(name, common_cmds, command_cmp); - return cmd ? cmd : BSEARCH(name, search_cmds, command_cmp); -} - -const CommandSet cmd_mode_commands = { - .lookup = find_cmd_mode_command -}; - -const CommandSet search_mode_commands = { - .lookup = find_search_mode_command -}; diff --git a/examples/dte/cmdline.h b/examples/dte/cmdline.h deleted file mode 100644 index 70cc7a5..0000000 --- a/examples/dte/cmdline.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef CMDLINE_H -#define CMDLINE_H - -#include <stdbool.h> -#include <sys/types.h> -#include "command/run.h" -#include "history.h" -#include "util/macros.h" -#include "util/ptr-array.h" -#include "util/string-view.h" -#include "util/string.h" - -typedef struct { - char *orig; // Full cmdline string (backing buffer for `escaped` and `tail`) - char *parsed; // Result of passing `escaped` through parse_command_arg() - StringView escaped; // Middle part of `orig` (string to be replaced) - StringView tail; // Suffix part of `orig` (after `escaped`) - size_t head_len; // Length of prefix part of `orig` (before `escaped`) - PointerArray completions; // Array of completion candidates - size_t idx; // Index of currently selected completion - bool add_space_after_single_match; - bool tilde_expanded; -} CompletionState; - -typedef struct { - String buf; - size_t pos; - const HistoryEntry *search_pos; - char *search_text; - CompletionState completion; -} CommandLine; - -extern const CommandSet cmd_mode_commands; -extern const CommandSet search_mode_commands; - -void cmdline_set_text(CommandLine *c, const char *text) NONNULL_ARGS; -void cmdline_clear(CommandLine *c) NONNULL_ARGS; -void cmdline_free(CommandLine *c) NONNULL_ARGS; - -#endif diff --git a/examples/dte/commands.c b/examples/dte/commands.c deleted file mode 100644 index 8346309..0000000 --- a/examples/dte/commands.c +++ /dev/null @@ -1,2594 +0,0 @@ -#include <errno.h> -#include <fcntl.h> -#include <glob.h> -#include <signal.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> -#include <unistd.h> -#include "commands.h" -#include "bind.h" -#include "bookmark.h" -#include "buffer.h" -#include "change.h" -#include "cmdline.h" -#include "command/alias.h" -#include "command/args.h" -#include "command/macro.h" -#include "compiler.h" -#include "config.h" -#include "convert.h" -#include "copy.h" -#include "editor.h" -#include "encoding.h" -#include "error.h" -#include "exec.h" -#include "file-option.h" -#include "filetype.h" -#include "frame.h" -#include "history.h" -#include "load-save.h" -#include "lock.h" -#include "misc.h" -#include "move.h" -#include "msg.h" -#include "regexp.h" -#include "replace.h" -#include "screen.h" -#include "search.h" -#include "selection.h" -#include "shift.h" -#include "show.h" -#include "spawn.h" -#include "syntax/color.h" -#include "syntax/state.h" -#include "syntax/syntax.h" -#include "tag.h" -#include "terminal/cursor.h" -#include "terminal/mode.h" -#include "terminal/osc52.h" -#include "terminal/style.h" -#include "terminal/terminal.h" -#include "util/arith.h" -#include "util/array.h" -#include "util/ascii.h" -#include "util/bit.h" -#include "util/bsearch.h" -#include "util/debug.h" -#include "util/log.h" -#include "util/path.h" -#include "util/str-util.h" -#include "util/strtonum.h" -#include "util/time-util.h" -#include "util/xmalloc.h" -#include "util/xsnprintf.h" -#include "vars.h" -#include "view.h" -#include "window.h" - -NOINLINE -static void do_selection_noinline(View *view, SelectionType sel) -{ - // Should only be called from do_selection() - BUG_ON(sel == view->selection); - - if (sel == SELECT_NONE) { - unselect(view); - return; - } - - if (view->selection) { - if (view->selection != sel) { - view->selection = sel; - // TODO: be less brute force about this; only the first/last - // line of the selection can change in this case - mark_all_lines_changed(view->buffer); - } - return; - } - - view->sel_so = block_iter_get_offset(&view->cursor); - view->sel_eo = SEL_EO_RECALC; - view->selection = sel; - - // Need to mark current line changed because cursor might - // move up or down before screen is updated - view_update_cursor_y(view); - buffer_mark_lines_changed(view->buffer, view->cy, view->cy); -} - -static void do_selection(View *view, SelectionType sel) -{ - if (likely(sel == view->selection)) { - // If `sel` is SELECT_NONE here, it's always equal to select_mode - BUG_ON(!sel && view->select_mode); - return; - } - - do_selection_noinline(view, sel); -} - -static char last_flag_or_default(const CommandArgs *a, char def) -{ - size_t n = a->nr_flags; - return n ? a->flags[n - 1] : def; -} - -static char last_flag(const CommandArgs *a) -{ - return last_flag_or_default(a, 0); -} - -static bool has_flag(const CommandArgs *a, unsigned char flag) -{ - return cmdargs_has_flag(a, flag); -} - -static void handle_select_chars_or_lines_flags(View *view, const CommandArgs *a) -{ - SelectionType sel; - if (has_flag(a, 'l')) { - sel = SELECT_LINES; - } else if (has_flag(a, 'c')) { - static_assert(SELECT_CHARS < SELECT_LINES); - sel = MAX(SELECT_CHARS, view->select_mode); - } else { - sel = view->select_mode; - } - do_selection(view, sel); -} - -static void handle_select_chars_flag(View *view, const CommandArgs *a) -{ - BUG_ON(has_flag(a, 'l')); - handle_select_chars_or_lines_flags(view, a); -} - -static bool cmd_alias(EditorState *e, const CommandArgs *a) -{ - const char *const name = a->args[0]; - const char *const cmd = a->args[1]; - - if (unlikely(name[0] == '\0')) { - return error_msg("Empty alias name not allowed"); - } - if (unlikely(name[0] == '-')) { - // Disallowing this simplifies auto-completion for "alias " - return error_msg("Alias name cannot begin with '-'"); - } - - for (size_t i = 0; name[i]; i++) { - unsigned char c = name[i]; - if (unlikely(!(is_word_byte(c) || c == '-' || c == '?' || c == '!'))) { - return error_msg("Invalid byte in alias name: %c (0x%02hhX)", c, c); - } - } - - if (unlikely(find_normal_command(name))) { - return error_msg("Can't replace existing command %s with an alias", name); - } - - if (likely(cmd)) { - add_alias(&e->aliases, name, cmd); - } else { - remove_alias(&e->aliases, name); - } - - return true; -} - -static bool cmd_bind(EditorState *e, const CommandArgs *a) -{ - const char *keystr = a->args[0]; - const char *cmd = a->args[1]; - KeyCode key; - if (unlikely(!parse_key_string(&key, keystr))) { - return error_msg("invalid key string: %s", keystr); - } - - const bool modes[] = { - [INPUT_NORMAL] = a->nr_flags == 0 || has_flag(a, 'n'), - [INPUT_COMMAND] = has_flag(a, 'c'), - [INPUT_SEARCH] = has_flag(a, 's'), - }; - - static_assert(ARRAYLEN(modes) == ARRAYLEN(e->modes)); - - for (InputMode i = 0; i < ARRAYLEN(modes); i++) { - if (!modes[i]) { - continue; - } - IntMap *bindings = &e->modes[i].key_bindings; - if (likely(cmd)) { - CommandRunner runner = cmdrunner_for_mode(e, i, false); - add_binding(bindings, key, cached_command_new(&runner, cmd)); - } else { - remove_binding(bindings, key); - } - } - - return true; -} - -static bool cmd_bof(EditorState *e, const CommandArgs *a) -{ - handle_select_chars_or_lines_flags(e->view, a); - move_bof(e->view); - return true; -} - -static bool cmd_bol(EditorState *e, const CommandArgs *a) -{ - static const FlagMapping map[] = { - {'s', BOL_SMART}, - {'t', BOL_SMART | BOL_SMART_TOGGLE}, - }; - - SmartBolFlags flags = cmdargs_convert_flags(a, map, ARRAYLEN(map)); - handle_select_chars_flag(e->view, a); - move_bol_smart(e->view, flags); - return true; -} - -static bool cmd_bolsf(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - View *view = e->view; - handle_select_chars_or_lines_flags(view, a); - - if (!block_iter_bol(&view->cursor)) { - unsigned int margin = e->options.scroll_margin; - long top = view->vy + window_get_scroll_margin(e->window, margin); - if (view->cy > top) { - move_up(view, view->cy - top); - } else { - block_iter_bof(&view->cursor); - } - } - - view_reset_preferred_x(view); - return true; -} - -static bool cmd_bookmark(EditorState *e, const CommandArgs *a) -{ - if (has_flag(a, 'r')) { - bookmark_pop(e->window, &e->bookmarks); - return true; - } - - bookmark_push(&e->bookmarks, get_current_file_location(e->view)); - return true; -} - -static bool cmd_case(EditorState *e, const CommandArgs *a) -{ - change_case(e->view, last_flag_or_default(a, 't')); - return true; -} - -static void mark_tabbar_changed(Window *window, void* UNUSED_ARG(data)) -{ - window->update_tabbar = true; -} - -static bool cmd_cd(EditorState *e, const CommandArgs *a) -{ - const char *dir = a->args[0]; - if (unlikely(dir[0] == '\0')) { - return error_msg("directory argument cannot be empty"); - } - - if (streq(dir, "-")) { - dir = xgetenv("OLDPWD"); - if (!dir) { - return error_msg("OLDPWD not set"); - } - } - - char buf[8192]; - const char *cwd = getcwd(buf, sizeof(buf)); - if (chdir(dir) != 0) { - return error_msg_errno("changing directory failed"); - } - - if (likely(cwd)) { - int r = setenv("OLDPWD", cwd, 1); - if (unlikely(r != 0)) { - LOG_WARNING("failed to set OLDPWD: %s", strerror(errno)); - } - } - - cwd = getcwd(buf, sizeof(buf)); - if (likely(cwd)) { - int r = setenv("PWD", cwd, 1); - if (unlikely(r != 0)) { - LOG_WARNING("failed to set PWD: %s", strerror(errno)); - } - } - - for (size_t i = 0, n = e->buffers.count; i < n; i++) { - Buffer *buffer = e->buffers.ptrs[i]; - update_short_filename_cwd(buffer, &e->home_dir, cwd); - } - - frame_for_each_window(e->root_frame, mark_tabbar_changed, NULL); - return true; -} - -static bool cmd_center_view(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - e->view->force_center = true; - return true; -} - -static bool cmd_clear(EditorState *e, const CommandArgs *a) -{ - bool auto_indent = e->buffer->options.auto_indent && !has_flag(a, 'i'); - clear_lines(e->view, auto_indent); - return true; -} - -static bool cmd_close(EditorState *e, const CommandArgs *a) -{ - bool force = has_flag(a, 'f'); - if (!force && !view_can_close(e->view)) { - bool prompt = has_flag(a, 'p'); - if (!prompt) { - return error_msg ( - "The buffer is modified; " - "save or run 'close -f' to close without saving" - ); - } - static const char str[] = "Close without saving changes? [y/N]"; - if (dialog_prompt(e, str, "ny") != 'y') { - return false; - } - } - - bool allow_quit = has_flag(a, 'q'); - if (allow_quit && e->buffers.count == 1 && e->root_frame->frames.count <= 1) { - e->status = EDITOR_EXIT_OK; - return true; - } - - bool allow_wclose = has_flag(a, 'w'); - if (allow_wclose && e->window->views.count <= 1) { - window_close(e->window); - return true; - } - - window_close_current_view(e->window); - set_view(e->window->view); - return true; -} - -static bool cmd_command(EditorState *e, const CommandArgs *a) -{ - const char *text = a->args[0]; - set_input_mode(e, INPUT_COMMAND); - if (text) { - cmdline_set_text(&e->cmdline, text); - } - return true; -} - -static bool cmd_compile(EditorState *e, const CommandArgs *a) -{ - static const FlagMapping map[] = { - {'1', SPAWN_READ_STDOUT}, - {'p', SPAWN_PROMPT}, - {'s', SPAWN_QUIET}, - }; - - Compiler *c = find_compiler(&e->compilers, a->args[0]); - if (unlikely(!c)) { - return error_msg("No such error parser %s", a->args[0]); - } - - SpawnContext ctx = { - .editor = e, - .argv = (const char **)a->args + 1, - .flags = cmdargs_convert_flags(a, map, ARRAYLEN(map)), - }; - - clear_messages(&e->messages); - bool ok = spawn_compiler(&ctx, c, &e->messages); - if (e->messages.array.count) { - activate_current_message_save(e); - } - return ok; -} - -static bool cmd_copy(EditorState *e, const CommandArgs *a) -{ - View *view = e->view; - const BlockIter save = view->cursor; - size_t size; - bool line_copy; - if (view->selection) { - size = prepare_selection(view); - line_copy = (view->selection == SELECT_LINES); - } else { - block_iter_bol(&view->cursor); - BlockIter tmp = view->cursor; - size = block_iter_eat_line(&tmp); - line_copy = true; - } - - if (unlikely(size == 0)) { - return true; - } - - bool internal = has_flag(a, 'i'); - bool clipboard = has_flag(a, 'b'); - bool primary = has_flag(a, 'p'); - if (!(internal || clipboard || primary)) { - internal = true; - } - - if (internal) { - copy(&e->clipboard, view, size, line_copy); - } - - Terminal *term = &e->terminal; - if ((clipboard || primary) && term->features & TFLAG_OSC52_COPY) { - if (internal) { - view->cursor = save; - if (view->selection) { - size = prepare_selection(view); - } - } - char *buf = block_iter_get_bytes(&view->cursor, size); - if (!term_osc52_copy(&term->obuf, buf, size, clipboard, primary)) { - error_msg_errno("OSC 52 copy failed"); - } - free(buf); - } - - if (!has_flag(a, 'k')) { - unselect(view); - } - - view->cursor = save; - // TODO: return false if term_osc52_copy() failed? - return true; -} - -static bool cmd_cursor(EditorState *e, const CommandArgs *a) -{ - if (unlikely(a->nr_args == 0)) { - // Reset all cursor styles - for (CursorInputMode m = 0; m < ARRAYLEN(e->cursor_styles); m++) { - e->cursor_styles[m] = get_default_cursor_style(m); - } - e->cursor_style_changed = true; - return true; - } - - CursorInputMode mode = cursor_mode_from_str(a->args[0]); - if (unlikely(mode >= NR_CURSOR_MODES)) { - return error_msg("invalid mode argument: %s", a->args[0]); - } - - TermCursorStyle style = get_default_cursor_style(mode); - if (a->nr_args >= 2) { - style.type = cursor_type_from_str(a->args[1]); - if (unlikely(style.type == CURSOR_INVALID)) { - return error_msg("invalid cursor type: %s", a->args[1]); - } - } - - if (a->nr_args >= 3) { - style.color = cursor_color_from_str(a->args[2]); - if (unlikely(style.color == COLOR_INVALID)) { - return error_msg("invalid cursor color: %s", a->args[2]); - } - } - - e->cursor_styles[mode] = style; - e->cursor_style_changed = true; - return true; -} - -static bool cmd_cut(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - View *view = e->view; - const long x = view_get_preferred_x(view); - if (view->selection) { - bool is_lines = view->selection == SELECT_LINES; - cut(&e->clipboard, view, prepare_selection(view), is_lines); - if (view->selection == SELECT_LINES) { - move_to_preferred_x(view, x); - } - unselect(view); - } else { - BlockIter tmp; - block_iter_bol(&view->cursor); - tmp = view->cursor; - cut(&e->clipboard, view, block_iter_eat_line(&tmp), true); - move_to_preferred_x(view, x); - } - return true; -} - -static bool cmd_delete(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - delete_ch(e->view); - return true; -} - -static bool cmd_delete_eol(EditorState *e, const CommandArgs *a) -{ - View *view = e->view; - if (view->selection) { - // TODO: return false? - return true; - } - - bool delete_newline_if_at_eol = has_flag(a, 'n'); - BlockIter bi = view->cursor; - if (delete_newline_if_at_eol) { - CodePoint ch; - if (block_iter_get_char(&view->cursor, &ch) == 1 && ch == '\n') { - delete_ch(view); - return true; - } - } - - buffer_delete_bytes(view, block_iter_eol(&bi)); - return true; -} - -static bool cmd_delete_line(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - delete_lines(e->view); - return true; -} - -static bool cmd_delete_word(EditorState *e, const CommandArgs *a) -{ - bool skip_non_word = has_flag(a, 's'); - BlockIter bi = e->view->cursor; - buffer_delete_bytes(e->view, word_fwd(&bi, skip_non_word)); - return true; -} - -static bool cmd_down(EditorState *e, const CommandArgs *a) -{ - handle_select_chars_or_lines_flags(e->view, a); - move_down(e->view, 1); - return true; -} - -static bool cmd_eof(EditorState *e, const CommandArgs *a) -{ - handle_select_chars_or_lines_flags(e->view, a); - move_eof(e->view); - return true; -} - -static bool cmd_eol(EditorState *e, const CommandArgs *a) -{ - handle_select_chars_flag(e->view, a); - move_eol(e->view); - return true; -} - -static bool cmd_eolsf(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - View *view = e->view; - handle_select_chars_or_lines_flags(view, a); - - if (!block_iter_eol(&view->cursor)) { - Window *window = e->window; - long margin = window_get_scroll_margin(window, e->options.scroll_margin); - long bottom = view->vy + window->edit_h - 1 - margin; - if (view->cy < bottom) { - move_down(view, bottom - view->cy); - } else { - block_iter_eof(&view->cursor); - } - } - - view_reset_preferred_x(view); - return true; -} - -static bool cmd_erase(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - erase(e->view); - return true; -} - -static bool cmd_erase_bol(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - buffer_erase_bytes(e->view, block_iter_bol(&e->view->cursor)); - return true; -} - -static bool cmd_erase_word(EditorState *e, const CommandArgs *a) -{ - View *view = e->view; - bool skip_non_word = has_flag(a, 's'); - buffer_erase_bytes(view, word_bwd(&view->cursor, skip_non_word)); - return true; -} - -static bool cmd_errorfmt(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args == 0); - const char *name = a->args[0]; - if (a->nr_args == 1) { - remove_compiler(&e->compilers, name); - return true; - } - - bool ignore = has_flag(a, 'i'); - return add_error_fmt(&e->compilers, name, ignore, a->args[1], a->args + 2); -} - -static bool cmd_exec(EditorState *e, const CommandArgs *a) -{ - ExecAction actions[3] = {EXEC_TTY, EXEC_TTY, EXEC_TTY}; - SpawnFlags spawn_flags = 0; - bool lflag = false; - bool move_after_insert = false; - bool strip_nl = false; - - for (size_t i = 0, n = a->nr_flags, argidx = 0, fd; i < n; i++) { - switch (a->flags[i]) { - case 'e': fd = STDERR_FILENO; break; - case 'i': fd = STDIN_FILENO; break; - case 'o': fd = STDOUT_FILENO; break; - case 'p': spawn_flags |= SPAWN_PROMPT; continue; - case 's': spawn_flags |= SPAWN_QUIET; continue; - case 't': spawn_flags &= ~SPAWN_QUIET; continue; - case 'l': lflag = true; continue; - case 'm': move_after_insert = true; continue; - case 'n': strip_nl = true; continue; - default: BUG("unexpected flag"); return false; - } - const char *action_name = a->args[argidx++]; - ExecAction action = lookup_exec_action(action_name, fd); - if (unlikely(action == EXEC_INVALID)) { - return error_msg("invalid action for -%c: '%s'", a->flags[i], action_name); - } - actions[fd] = action; - } - - if (lflag && actions[STDIN_FILENO] == EXEC_BUFFER) { - // For compat. with old "filter" and "pipe-to" commands - actions[STDIN_FILENO] = EXEC_LINE; - } - - const char **argv = (const char **)a->args + a->nr_flag_args; - ssize_t outlen = handle_exec(e, argv, actions, spawn_flags, strip_nl); - if (outlen <= 0) { - return outlen == 0; - } - - if (move_after_insert && actions[STDOUT_FILENO] == EXEC_BUFFER) { - block_iter_skip_bytes(&e->view->cursor, outlen); - } - return true; -} - -static bool cmd_ft(EditorState *e, const CommandArgs *a) -{ - char **args = a->args; - const char *filetype = args[0]; - if (unlikely(!is_valid_filetype_name(filetype))) { - return error_msg("Invalid filetype name: '%s'", filetype); - } - - FileDetectionType dt = FT_EXTENSION; - switch (last_flag(a)) { - case 'b': - dt = FT_BASENAME; - break; - case 'c': - dt = FT_CONTENT; - break; - case 'f': - dt = FT_FILENAME; - break; - case 'i': - dt = FT_INTERPRETER; - break; - } - - size_t nfailed = 0; - for (size_t i = 1, n = a->nr_args; i < n; i++) { - if (!add_filetype(&e->filetypes, filetype, args[i], dt)) { - nfailed++; - } - } - - return nfailed == 0; -} - -static bool cmd_hi(EditorState *e, const CommandArgs *a) -{ - if (unlikely(a->nr_args == 0)) { - exec_builtin_color_reset(e); - goto update; - } - - char **strs = a->args + 1; - size_t strs_len = a->nr_args - 1; - TermColor color; - ssize_t n = parse_term_color(&color, strs, strs_len); - if (unlikely(n != strs_len)) { - if (n < 0) { - return error_msg("too many colors"); - } - BUG_ON(n > strs_len); - return error_msg("invalid color or attribute: '%s'", strs[n]); - } - - TermColorCapabilityType color_type = e->terminal.color_type; - bool optimize = e->options.optimize_true_color; - int32_t fg = color_to_nearest(color.fg, color_type, optimize); - int32_t bg = color_to_nearest(color.bg, color_type, optimize); - if ( - color_type != TERM_TRUE_COLOR - && has_flag(a, 'c') - && (fg != color.fg || bg != color.bg) - ) { - return true; - } - - color.fg = fg; - color.bg = bg; - set_highlight_color(&e->colors, a->args[0], &color); - -update: - // Don't call update_all_syntax_colors() needlessly; it's called - // right after config has been loaded - if (e->status != EDITOR_INITIALIZING) { - update_all_syntax_colors(&e->syntaxes, &e->colors); - mark_everything_changed(e); - } - return true; -} - -static bool cmd_include(EditorState *e, const CommandArgs *a) -{ - ConfigFlags flags = has_flag(a, 'q') ? CFG_NOFLAGS : CFG_MUST_EXIST; - if (has_flag(a, 'b')) { - flags |= CFG_BUILTIN; - } - int err = read_normal_config(e, a->args[0], flags); - // TODO: Clean up read_normal_config() so this can be simplified to `err == 0` - return err == 0 || (err == ENOENT && !(flags & CFG_MUST_EXIST)); -} - -static bool cmd_insert(EditorState *e, const CommandArgs *a) -{ - const char *str = a->args[0]; - if (has_flag(a, 'k')) { - for (size_t i = 0; str[i]; i++) { - insert_ch(e->view, str[i]); - } - return true; - } - - bool move_after = has_flag(a, 'm'); - insert_text(e->view, str, strlen(str), move_after); - return true; -} - -static bool cmd_join(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - join_lines(e->view); - return true; -} - -static bool cmd_left(EditorState *e, const CommandArgs *a) -{ - handle_select_chars_flag(e->view, a); - move_cursor_left(e->view); - return true; -} - -static bool cmd_line(EditorState *e, const CommandArgs *a) -{ - const char *str = a->args[0]; - size_t line, column; - if (unlikely(!str_to_xfilepos(str, &line, &column))) { - return error_msg("Invalid line number: %s", str); - } - - View *view = e->view; - long x = view_get_preferred_x(view); - unselect(view); - - if (column >= 1) { - // Column was specified; move to exact position - move_to_filepos(view, line, column); - } else { - // Column was omitted; move to line while preserving current column - move_to_line(view, line); - move_to_preferred_x(view, x); - } - - return true; -} - -static bool cmd_load_syntax(EditorState *e, const CommandArgs *a) -{ - const char *arg = a->args[0]; - const char *slash = strrchr(arg, '/'); - if (!slash) { - const char *filetype = arg; - if (find_syntax(&e->syntaxes, filetype)) { - return true; - } - return !!load_syntax_by_filetype(e, filetype); - } - - const char *filetype = slash + 1; - if (find_syntax(&e->syntaxes, filetype)) { - return error_msg("Syntax for filetype %s already loaded", filetype); - } - - int err; - return !!load_syntax_file(e, arg, CFG_MUST_EXIST, &err); -} - -static bool cmd_macro(EditorState *e, const CommandArgs *a) -{ - CommandMacroState *m = &e->macro; - const char *action = a->args[0]; - - if (streq(action, "play") || streq(action, "run")) { - for (size_t i = 0, n = m->macro.count; i < n; i++) { - const char *cmd_str = m->macro.ptrs[i]; - if (!handle_normal_command(e, cmd_str, false)) { - return false; - } - } - return true; - } - - const char *msg; - if (streq(action, "toggle")) { - if (m->recording) { - goto stop; - } - goto record; - } - - if (streq(action, "record")) { - record: - msg = macro_record(m) ? "Recording macro" : "Already recording"; - goto message; - } - - if (streq(action, "stop")) { - stop: - if (!macro_stop(m)) { - msg = "Not recording"; - goto message; - } - size_t count = m->macro.count; - const char *plural = (count != 1) ? "s" : ""; - info_msg("Macro recording stopped; %zu command%s saved", count, plural); - return true; - } - - if (streq(action, "cancel")) { - msg = macro_cancel(m) ? "Macro recording cancelled" : "Not recording"; - goto message; - } - - return error_msg("Unknown action '%s'", action); - -message: - info_msg("%s", msg); - // TODO: make this conditional? - return true; -} - -static bool cmd_match_bracket(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - View *view = e->view; - CodePoint cursor_char; - if (!block_iter_get_char(&view->cursor, &cursor_char)) { - return error_msg("No character under cursor"); - } - - CodePoint target = cursor_char; - BlockIter bi = view->cursor; - size_t level = 0; - CodePoint u = 0; - - switch (cursor_char) { - case '<': - case '[': - case '{': - target++; - // Fallthrough - case '(': - target++; - goto search_fwd; - case '>': - case ']': - case '}': - target--; - // Fallthrough - case ')': - target--; - goto search_bwd; - default: - return error_msg("Character under cursor not matchable"); - } - -search_fwd: - block_iter_next_char(&bi, &u); - BUG_ON(u != cursor_char); - while (block_iter_next_char(&bi, &u)) { - if (u == target) { - if (level == 0) { - block_iter_prev_char(&bi, &u); - view->cursor = bi; - return true; // Found - } - level--; - } else if (u == cursor_char) { - level++; - } - } - goto not_found; - -search_bwd: - while (block_iter_prev_char(&bi, &u)) { - if (u == target) { - if (level == 0) { - view->cursor = bi; - return true; // Found - } - level--; - } else if (u == cursor_char) { - level++; - } - } - -not_found: - return error_msg("No matching bracket found"); -} - -static bool cmd_move_tab(EditorState *e, const CommandArgs *a) -{ - Window *window = e->window; - const size_t ntabs = window->views.count; - const char *str = a->args[0]; - size_t to, from = ptr_array_idx(&window->views, e->view); - BUG_ON(from >= ntabs); - if (streq(str, "left")) { - to = size_decrement_wrapped(from, ntabs); - } else if (streq(str, "right")) { - to = size_increment_wrapped(from, ntabs); - } else { - if (!str_to_size(str, &to) || to == 0) { - return error_msg("Invalid tab position %s", str); - } - to = MIN(to, ntabs) - 1; - } - ptr_array_move(&window->views, from, to); - window->update_tabbar = true; - return true; -} - -static bool cmd_msg(EditorState *e, const CommandArgs *a) -{ - const char *str = a->args[0]; - uint_least64_t np = cmdargs_flagset_value('n') | cmdargs_flagset_value('p'); - if (u64_popcount(a->flag_set & np) + !!str >= 2) { - return error_msg("flags [-n|-p] and [number] argument are mutually exclusive"); - } - - MessageArray *msgs = &e->messages; - size_t count = msgs->array.count; - if (count == 0) { - return true; - } - - size_t p = msgs->pos; - BUG_ON(p >= count); - if (has_flag(a, 'n')) { - p = MIN(p + 1, count - 1); - } else if (has_flag(a, 'p')) { - p = p ? p - 1 : 0; - } else if (str) { - if (!str_to_size(str, &p) || p == 0) { - return error_msg("invalid message index: %s", str); - } - p = MIN(p - 1, count - 1); - } - - msgs->pos = p; - return activate_current_message(e); -} - -static bool cmd_new_line(EditorState *e, const CommandArgs *a) -{ - new_line(e->view, has_flag(a, 'a')); - return true; -} - -static bool cmd_next(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - size_t i = ptr_array_idx(&e->window->views, e->view); - size_t n = e->window->views.count; - BUG_ON(i >= n); - set_view(e->window->views.ptrs[size_increment_wrapped(i, n)]); - return true; -} - -static bool xglob(char **args, glob_t *globbuf) -{ - BUG_ON(!args); - BUG_ON(!args[0]); - int err = glob(*args, GLOB_NOCHECK, NULL, globbuf); - while (err == 0 && *++args) { - err = glob(*args, GLOB_NOCHECK | GLOB_APPEND, NULL, globbuf); - } - - if (likely(err == 0)) { - BUG_ON(globbuf->gl_pathc == 0); - BUG_ON(!globbuf->gl_pathv); - BUG_ON(!globbuf->gl_pathv[0]); - return true; - } - - BUG_ON(err == GLOB_NOMATCH); - globfree(globbuf); - return error_msg("glob: %s", (err == GLOB_NOSPACE) ? strerror(ENOMEM) : "failed"); -} - -static bool cmd_open(EditorState *e, const CommandArgs *a) -{ - bool temporary = has_flag(a, 't'); - if (unlikely(temporary && a->nr_args > 0)) { - return error_msg("'open -t' can't be used with filename arguments"); - } - - const char *requested_encoding = NULL; - char **args = a->args; - if (unlikely(a->nr_flag_args > 0)) { - // The "-e" flag is the only one that takes an argument, so the - // above condition implies it was used - BUG_ON(!has_flag(a, 'e')); - requested_encoding = args[a->nr_flag_args - 1]; - args += a->nr_flag_args; - } - - Encoding encoding = {.type = ENCODING_AUTODETECT}; - if (requested_encoding) { - EncodingType enctype = lookup_encoding(requested_encoding); - if (enctype == UTF8) { - encoding = encoding_from_type(enctype); - } else if (conversion_supported_by_iconv(requested_encoding, "UTF-8")) { - encoding = encoding_from_name(requested_encoding); - } else { - if (errno == EINVAL) { - return error_msg("Unsupported encoding '%s'", requested_encoding); - } - return error_msg ( - "iconv conversion from '%s' failed: %s", - requested_encoding, - strerror(errno) - ); - } - } - - if (a->nr_args == 0) { - View *view = window_open_new_file(e->window); - view->buffer->temporary = temporary; - if (requested_encoding) { - buffer_set_encoding(view->buffer, encoding, e->options.utf8_bom); - } - return true; - } - - char **paths = args; - glob_t globbuf; - bool use_glob = has_flag(a, 'g'); - if (use_glob) { - if (!xglob(args, &globbuf)) { - return false; - } - paths = globbuf.gl_pathv; - } - - View *first_opened; - if (!paths[1]) { - // Previous view is remembered when opening single file - first_opened = window_open_file(e->window, paths[0], &encoding); - } else { - // It makes no sense to remember previous view when opening multiple files - first_opened = window_open_files(e->window, paths, &encoding); - } - - if (use_glob) { - globfree(&globbuf); - } - - return !!first_opened; -} - -static bool cmd_option(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args < 3); - size_t nstrs = a->nr_args - 1; - if (unlikely(nstrs & 1)) { - return error_msg("Missing option value"); - } - - char **strs = a->args + 1; - if (unlikely(!validate_local_options(strs))) { - return false; - } - - PointerArray *opts = &e->file_options; - if (has_flag(a, 'r')) { - const StringView pattern = strview_from_cstring(a->args[0]); - return add_file_options(opts, FOPTS_FILENAME, pattern, strs, nstrs); - } - - const char *ft_list = a->args[0]; - size_t errors = 0; - for (size_t pos = 0, len = strlen(ft_list); pos < len; ) { - const StringView filetype = get_delim(ft_list, &pos, len, ','); - if (!add_file_options(opts, FOPTS_FILETYPE, filetype, strs, nstrs)) { - errors++; - } - } - - return !errors; -} - -static bool cmd_blkdown(EditorState *e, const CommandArgs *a) -{ - View *view = e->view; - handle_select_chars_or_lines_flags(view, a); - - // If current line is blank, skip past consecutive blank lines - StringView line; - fetch_this_line(&view->cursor, &line); - if (strview_isblank(&line)) { - while (block_iter_next_line(&view->cursor)) { - fill_line_ref(&view->cursor, &line); - if (!strview_isblank(&line)) { - break; - } - } - } - - // Skip past non-blank lines - while (block_iter_next_line(&view->cursor)) { - fill_line_ref(&view->cursor, &line); - if (strview_isblank(&line)) { - break; - } - } - - // If we reach the last populated line in the buffer, move down one line - BlockIter tmp = view->cursor; - block_iter_eol(&tmp); - block_iter_skip_bytes(&tmp, 1); - if (block_iter_is_eof(&tmp)) { - view->cursor = tmp; - } - - return true; -} - -static bool cmd_blkup(EditorState *e, const CommandArgs *a) -{ - View *view = e->view; - handle_select_chars_or_lines_flags(view, a); - - // If cursor is on the first line, just move to bol - if (view->cy == 0) { - block_iter_bol(&view->cursor); - return true; - } - - // If current line is blank, skip past consecutive blank lines - StringView line; - fetch_this_line(&view->cursor, &line); - if (strview_isblank(&line)) { - while (block_iter_prev_line(&view->cursor)) { - fill_line_ref(&view->cursor, &line); - if (!strview_isblank(&line)) { - break; - } - } - } - - // Skip past non-blank lines - while (block_iter_prev_line(&view->cursor)) { - fill_line_ref(&view->cursor, &line); - if (strview_isblank(&line)) { - break; - } - } - - return true; -} - -static bool cmd_paste(EditorState *e, const CommandArgs *a) -{ - bool move_after = has_flag(a, 'm'); - bool above_cursor = has_flag(a, 'a'); - bool at_cursor = has_flag(a, 'c'); - PasteLinesType type = PASTE_LINES_BELOW_CURSOR; - - if (above_cursor && at_cursor) { - return error_msg("flags -a and -c are mutually exclusive"); - } else if (above_cursor) { - type = PASTE_LINES_ABOVE_CURSOR; - } else if (at_cursor) { - type = PASTE_LINES_INLINE; - } - - paste(&e->clipboard, e->view, type, move_after); - return true; -} - -static bool cmd_pgdown(EditorState *e, const CommandArgs *a) -{ - View *view = e->view; - handle_select_chars_or_lines_flags(view, a); - - Window *window = e->window; - long margin = window_get_scroll_margin(window, e->options.scroll_margin); - long bottom = view->vy + window->edit_h - 1 - margin; - long count; - - if (view->cy < bottom) { - count = bottom - view->cy; - } else { - count = window->edit_h - 1 - margin * 2; - } - - move_down(view, count); - return true; -} - -static bool cmd_pgup(EditorState *e, const CommandArgs *a) -{ - View *view = e->view; - handle_select_chars_or_lines_flags(view, a); - - Window *window = e->window; - long margin = window_get_scroll_margin(window, e->options.scroll_margin); - long top = view->vy + margin; - long count; - - if (view->cy > top) { - count = view->cy - top; - } else { - count = window->edit_h - 1 - margin * 2; - } - - move_up(view, count); - return true; -} - -static bool cmd_prev(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - size_t i = ptr_array_idx(&e->window->views, e->view); - size_t n = e->window->views.count; - BUG_ON(i >= n); - set_view(e->window->views.ptrs[size_decrement_wrapped(i, n)]); - return true; -} - -static View *window_find_modified_view(Window *window) -{ - if (buffer_modified(window->view->buffer)) { - return window->view; - } - for (size_t i = 0, n = window->views.count; i < n; i++) { - View *view = window->views.ptrs[i]; - if (buffer_modified(view->buffer)) { - return view; - } - } - return NULL; -} - -static size_t count_modified_buffers(const PointerArray *buffers, View **first) -{ - View *modified = NULL; - size_t nr_modified = 0; - for (size_t i = 0, n = buffers->count; i < n; i++) { - Buffer *buffer = buffers->ptrs[i]; - if (!buffer_modified(buffer)) { - continue; - } - nr_modified++; - if (!modified) { - modified = buffer->views.ptrs[0]; - } - } - - BUG_ON(nr_modified > 0 && !modified); - *first = modified; - return nr_modified; -} - -static bool cmd_quit(EditorState *e, const CommandArgs *a) -{ - int exit_code = EDITOR_EXIT_OK; - if (a->nr_args) { - if (!str_to_int(a->args[0], &exit_code)) { - return error_msg("Not a valid integer argument: '%s'", a->args[0]); - } - int max = EDITOR_EXIT_MAX; - if (exit_code < 0 || exit_code > max) { - return error_msg("Exit code should be between 0 and %d", max); - } - } - - View *first_modified = NULL; - size_t n = count_modified_buffers(&e->buffers, &first_modified); - if (n == 0) { - goto exit; - } - - BUG_ON(!first_modified); - const char *plural = (n > 1) ? "s" : ""; - if (has_flag(a, 'f')) { - LOG_INFO("force quitting with %zu modified buffer%s", n, plural); - goto exit; - } - - // Activate a modified view (giving preference to the current view or - // a view in the current window) - View *view = window_find_modified_view(e->window); - set_view(view ? view : first_modified); - - if (!has_flag(a, 'p')) { - return error_msg("Save modified files or run 'quit -f' to quit without saving"); - } - - char question[128]; - xsnprintf ( - question, sizeof question, - "Quit without saving %zu modified buffer%s? [y/N]", - n, plural - ); - - if (dialog_prompt(e, question, "ny") != 'y') { - return false; - } - - LOG_INFO("quit prompt accepted with %zu modified buffer%s", n, plural); - -exit: - e->status = exit_code; - return true; -} - -static bool cmd_redo(EditorState *e, const CommandArgs *a) -{ - char *arg = a->args[0]; - unsigned long change_id = 0; - if (arg) { - if (!str_to_ulong(arg, &change_id) || change_id == 0) { - return error_msg("Invalid change id: %s", arg); - } - } - if (!redo(e->view, change_id)) { - return false; - } - - unselect(e->view); - return true; -} - -static bool cmd_refresh(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - mark_everything_changed(e); - return true; -} - -static bool repeat_insert(EditorState *e, const char *str, unsigned int count, bool move_after) -{ - size_t str_len = strlen(str); - size_t bufsize; - if (unlikely(size_multiply_overflows(count, str_len, &bufsize))) { - return error_msg("Repeated insert would overflow"); - } - if (unlikely(bufsize == 0)) { - return true; - } - - char *buf = malloc(bufsize); - if (unlikely(!buf)) { - return error_msg_errno("malloc"); - } - - char tmp[4096]; - if (str_len == 1) { - memset(buf, str[0], bufsize); - goto insert; - } else if (bufsize < 2 * sizeof(tmp) || str_len > sizeof(tmp) / 8) { - for (size_t i = 0; i < count; i++) { - memcpy(buf + (i * str_len), str, str_len); - } - goto insert; - } - - size_t strs_per_tmp = sizeof(tmp) / str_len; - size_t tmp_len = strs_per_tmp * str_len; - size_t tmps_per_buf = bufsize / tmp_len; - size_t remainder = bufsize % tmp_len; - - // Create a block of text containing `strs_per_tmp` concatenated strs - for (size_t i = 0; i < strs_per_tmp; i++) { - memcpy(tmp + (i * str_len), str, str_len); - } - - // Copy `tmps_per_buf` copies of `tmp` into `buf` - for (size_t i = 0; i < tmps_per_buf; i++) { - memcpy(buf + (i * tmp_len), tmp, tmp_len); - } - - // Copy the remainder into `buf` (if any) - if (remainder) { - memcpy(buf + (tmps_per_buf * tmp_len), tmp, remainder); - } - - LOG_DEBUG ( - "Optimized %u inserts of %zu bytes into %zu inserts of %zu bytes", - count, str_len, - tmps_per_buf, tmp_len - ); - -insert: - insert_text(e->view, buf, bufsize, move_after); - free(buf); - return true; -} - -static bool cmd_repeat(EditorState *e, const CommandArgs *a) -{ - unsigned int count; - if (unlikely(!str_to_uint(a->args[0], &count))) { - return error_msg("Not a valid repeat count: %s", a->args[0]); - } - if (unlikely(count == 0)) { - return true; - } - - const Command *cmd = find_normal_command(a->args[1]); - if (unlikely(!cmd)) { - return error_msg("No such command: %s", a->args[1]); - } - - CommandArgs a2 = cmdargs_new(a->args + 2); - current_command = cmd; - bool ok = parse_args(cmd, &a2); - current_command = NULL; - if (unlikely(!ok)) { - return false; - } - - CommandFunc fn = cmd->cmd; - if (fn == (CommandFunc)cmd_insert && !has_flag(&a2, 'k')) { - // Use optimized implementation for repeated "insert" - return repeat_insert(e, a2.args[0], count, has_flag(&a2, 'm')); - } - - while (count--) { - fn(e, &a2); - } - // TODO: return false if fn() fails? - return true; -} - -static bool cmd_replace(EditorState *e, const CommandArgs *a) -{ - static const FlagMapping map[] = { - {'b', REPLACE_BASIC}, - {'c', REPLACE_CONFIRM}, - {'g', REPLACE_GLOBAL}, - {'i', REPLACE_IGNORE_CASE}, - }; - - ReplaceFlags flags = cmdargs_convert_flags(a, map, ARRAYLEN(map)); - return reg_replace(e->view, a->args[0], a->args[1], flags); -} - -static bool cmd_right(EditorState *e, const CommandArgs *a) -{ - handle_select_chars_flag(e->view, a); - move_cursor_right(e->view); - return true; -} - -static bool stat_changed(const FileInfo *file, const struct stat *st) -{ - // Don't compare st_mode because we allow chmod 755 etc. - return !timespecs_equal(get_stat_mtime(st), &file->mtime) - || st->st_dev != file->dev - || st->st_ino != file->ino - || st->st_size != file->size; -} - -static bool save_unmodified_buffer(Buffer *buffer, const char *filename) -{ - SaveUnmodifiedType type = buffer->options.save_unmodified; - if (type == SAVE_NONE) { - LOG_INFO("buffer unchanged; leaving file untouched"); - return true; - } - - BUG_ON(type != SAVE_TOUCH); - struct timespec times[2]; - if (unlikely(clock_gettime(CLOCK_REALTIME, ×[0]) != 0)) { - LOG_ERRNO("aborting partial save; clock_gettime() failed"); - return false; - } - - times[1] = times[0]; - if (unlikely(utimensat(AT_FDCWD, filename, times, 0) != 0)) { - LOG_ERRNO("aborting partial save; utimensat() failed"); - return false; - } - - buffer->file.mtime = times[0]; - LOG_INFO("buffer unchanged; mtime/atime updated"); - return true; -} - -static bool cmd_save(EditorState *e, const CommandArgs *a) -{ - Buffer *buffer = e->buffer; - if (unlikely(buffer->stdout_buffer)) { - const char *f = buffer_filename(buffer); - info_msg("%s can't be saved; it will be piped to stdout on exit", f); - return true; - } - - bool dos_nl = has_flag(a, 'd'); - bool unix_nl = has_flag(a, 'u'); - bool crlf = buffer->crlf_newlines; - if (unlikely(dos_nl && unix_nl)) { - return error_msg("flags -d and -u can't be used together"); - } else if (dos_nl) { - crlf = true; - } else if (unix_nl) { - crlf = false; - } - - const char *requested_encoding = NULL; - char **args = a->args; - if (unlikely(a->nr_flag_args > 0)) { - BUG_ON(!has_flag(a, 'e')); - requested_encoding = args[a->nr_flag_args - 1]; - args += a->nr_flag_args; - } - - Encoding encoding = buffer->encoding; - bool bom = buffer->bom; - if (requested_encoding) { - EncodingType et = lookup_encoding(requested_encoding); - if (et == UTF8) { - if (encoding.type != UTF8) { - // Encoding changed - encoding = encoding_from_type(et); - bom = e->options.utf8_bom; - } - } else if (conversion_supported_by_iconv("UTF-8", requested_encoding)) { - encoding = encoding_from_name(requested_encoding); - if (encoding.name != buffer->encoding.name) { - // Encoding changed - bom = !!get_bom_for_encoding(encoding.type); - } - } else { - if (errno == EINVAL) { - return error_msg("Unsupported encoding '%s'", requested_encoding); - } - return error_msg ( - "iconv conversion to '%s' failed: %s", - requested_encoding, - strerror(errno) - ); - } - } - - bool b = has_flag(a, 'b'); - bool B = has_flag(a, 'B'); - if (unlikely(b && B)) { - return error_msg("flags -b and -B can't be used together"); - } else if (b) { - bom = true; - } else if (B) { - bom = false; - } - - char *absolute = buffer->abs_filename; - bool force = has_flag(a, 'f'); - bool new_locked = false; - if (a->nr_args > 0) { - if (args[0][0] == '\0') { - return error_msg("Empty filename not allowed"); - } - char *tmp = path_absolute(args[0]); - if (!tmp) { - return error_msg_errno("Failed to make absolute path"); - } - if (absolute && streq(tmp, absolute)) { - free(tmp); - } else { - absolute = tmp; - } - } else { - if (!absolute) { - if (!has_flag(a, 'p')) { - return error_msg("No filename"); - } - set_input_mode(e, INPUT_COMMAND); - cmdline_set_text(&e->cmdline, "save "); - return true; - } - if (buffer->readonly && !force) { - return error_msg("Use -f to force saving read-only file"); - } - } - - mode_t old_mode = buffer->file.mode; - bool hardlinks = false; - struct stat st; - bool stat_ok = !stat(absolute, &st); - if (!stat_ok) { - if (errno != ENOENT) { - error_msg("stat failed for %s: %s", absolute, strerror(errno)); - goto error; - } - } else { - if ( - absolute == buffer->abs_filename - && !force - && stat_changed(&buffer->file, &st) - ) { - error_msg ( - "File has been modified by another process; " - "use 'save -f' to force overwrite" - ); - goto error; - } - if (S_ISDIR(st.st_mode)) { - error_msg("Will not overwrite directory %s", absolute); - goto error; - } - hardlinks = (st.st_nlink >= 2); - } - - if (e->options.lock_files) { - if (absolute == buffer->abs_filename) { - if (!buffer->locked) { - if (!lock_file(absolute)) { - if (!force) { - error_msg("Can't lock file %s", absolute); - goto error; - } - } else { - buffer->locked = true; - } - } - } else { - if (!lock_file(absolute)) { - if (!force) { - error_msg("Can't lock file %s", absolute); - goto error; - } - } else { - new_locked = true; - } - } - } - - if (stat_ok) { - if (absolute != buffer->abs_filename && !force) { - error_msg("Use -f to overwrite %s", absolute); - goto error; - } - // Allow chmod 755 etc. - buffer->file.mode = st.st_mode; - } - - if ( - stat_ok - && buffer->options.save_unmodified != SAVE_FULL - && !stat_changed(&buffer->file, &st) - && st.st_uid == buffer->file.uid - && st.st_gid == buffer->file.gid - && !buffer_modified(buffer) - && absolute == buffer->abs_filename - && encoding.name == buffer->encoding.name - && crlf == buffer->crlf_newlines - && bom == buffer->bom - && save_unmodified_buffer(buffer, absolute) - ) { - BUG_ON(new_locked); - return true; - } - - if (!save_buffer(buffer, absolute, &encoding, crlf, bom, hardlinks)) { - goto error; - } - - buffer->saved_change = buffer->cur_change; - buffer->readonly = false; - buffer->temporary = false; - buffer->crlf_newlines = crlf; - buffer->bom = bom; - if (requested_encoding) { - buffer->encoding = encoding; - } - - if (absolute != buffer->abs_filename) { - if (buffer->locked) { - // Filename changes, release old file lock - unlock_file(buffer->abs_filename); - } - buffer->locked = new_locked; - - free(buffer->abs_filename); - buffer->abs_filename = absolute; - update_short_filename(buffer, &e->home_dir); - - // Filename change is not detected (only buffer_modified() change) - mark_buffer_tabbars_changed(buffer); - } - if (!old_mode && streq(buffer->options.filetype, "none")) { - // New file and most likely user has not changed the filetype - if (buffer_detect_filetype(buffer, &e->filetypes)) { - set_file_options(e, buffer); - set_editorconfig_options(buffer); - buffer_update_syntax(e, buffer); - } - } - - return true; - -error: - if (new_locked) { - unlock_file(absolute); - } - if (absolute != buffer->abs_filename) { - free(absolute); - } - return false; -} - -static bool cmd_scroll_down(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - View *view = e->view; - view->vy++; - if (view->cy < view->vy) { - move_down(view, 1); - } - return true; -} - -static bool cmd_scroll_pgdown(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - Window *window = e->window; - View *view = e->view; - long max = view->buffer->nl - window->edit_h + 1; - if (view->vy < max && max > 0) { - long count = window->edit_h - 1; - if (view->vy + count > max) { - count = max - view->vy; - } - view->vy += count; - move_down(view, count); - } else if (view->cy < view->buffer->nl) { - move_down(view, view->buffer->nl - view->cy); - } - return true; -} - -static bool cmd_scroll_pgup(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - Window *window = e->window; - View *view = e->view; - if (view->vy > 0) { - long count = MIN(window->edit_h - 1, view->vy); - view->vy -= count; - move_up(view, count); - } else if (view->cy > 0) { - move_up(view, view->cy); - } - return true; -} - -static bool cmd_scroll_up(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - Window *window = e->window; - View *view = e->view; - if (view->vy) { - view->vy--; - } - if (view->vy + window->edit_h <= view->cy) { - move_up(view, 1); - } - return true; -} - -static uint_least64_t get_flagset_npw(void) -{ - uint_least64_t npw = 0; - npw |= cmdargs_flagset_value('n'); - npw |= cmdargs_flagset_value('p'); - npw |= cmdargs_flagset_value('w'); - return npw; -} - -static bool cmd_search(EditorState *e, const CommandArgs *a) -{ - const char *pattern = a->args[0]; - if (u64_popcount(a->flag_set & get_flagset_npw()) + !!pattern >= 2) { - return error_msg("flags [-n|-p|-w] and [pattern] argument are mutually exclusive"); - } - - View *view = e->view; - char pattbuf[4096]; - bool use_word_under_cursor = has_flag(a, 'w'); - - if (use_word_under_cursor) { - StringView word = view_get_word_under_cursor(view); - if (word.length == 0) { - // Error message would not be very useful here - return false; - } - const RegexpWordBoundaryTokens *rwbt = &e->regexp_word_tokens; - const size_t bmax = sizeof(rwbt->start); - static_assert_compatible_types(rwbt->start, char[8]); - if (unlikely(word.length >= sizeof(pattbuf) - (bmax * 2))) { - return error_msg("word under cursor too long"); - } - char *ptr = stpncpy(pattbuf, rwbt->start, bmax); - memcpy(ptr, word.data, word.length); - memcpy(ptr + word.length, rwbt->end, bmax); - pattern = pattbuf; - } - - SearchState *search = &e->search; - SearchCaseSensitivity cs = e->options.case_sensitive_search; - unselect(view); - - if (has_flag(a, 'n')) { - return search_next(view, search, cs); - } - if (has_flag(a, 'p')) { - return search_prev(view, search, cs); - } - - search->reverse = has_flag(a, 'r'); - if (!pattern) { - set_input_mode(e, INPUT_SEARCH); - return true; - } - - bool found; - search_set_regexp(search, pattern); - if (use_word_under_cursor) { - found = search_next_word(view, search, cs); - } else { - found = search_next(view, search, cs); - } - - if (!has_flag(a, 'H')) { - history_add(&e->search_history, pattern); - } - - return found; -} - -static bool cmd_select_block(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - select_block(e->view); - - // TODO: return false if select_block() doesn't select anything? - return true; -} - -static bool cmd_select(EditorState *e, const CommandArgs *a) -{ - View *view = e->view; - SelectionType sel = has_flag(a, 'l') ? SELECT_LINES : SELECT_CHARS; - bool keep = has_flag(a, 'k'); - if (!keep && view->selection && view->selection == sel) { - sel = SELECT_NONE; - } - - view->select_mode = sel; - do_selection(view, sel); - return true; -} - -static bool cmd_set(EditorState *e, const CommandArgs *a) -{ - bool global = has_flag(a, 'g'); - bool local = has_flag(a, 'l'); - if (!e->buffer) { - if (unlikely(local)) { - return error_msg("Flag -l makes no sense in config file"); - } - global = true; - } - - char **args = a->args; - size_t count = a->nr_args; - if (count == 1) { - return set_bool_option(e, args[0], local, global); - } - if (count & 1) { - return error_msg("One or even number of arguments expected"); - } - - size_t errors = 0; - for (size_t i = 0; i < count; i += 2) { - if (!set_option(e, args[i], args[i + 1], local, global)) { - errors++; - } - } - - return !errors; -} - -static bool cmd_setenv(EditorState* UNUSED_ARG(e), const CommandArgs *a) -{ - const char *name = a->args[0]; - if (unlikely(streq(name, "DTE_VERSION"))) { - return error_msg("$DTE_VERSION cannot be changed"); - } - - const size_t nr_args = a->nr_args; - int res; - if (nr_args == 2) { - res = setenv(name, a->args[1], true); - } else { - BUG_ON(nr_args != 1); - res = unsetenv(name); - } - - if (likely(res == 0)) { - return true; - } - - if (errno == EINVAL) { - return error_msg("Invalid environment variable name '%s'", name); - } - - return error_msg_errno(nr_args == 2 ? "setenv" : "unsetenv"); -} - -static bool cmd_shift(EditorState *e, const CommandArgs *a) -{ - const char *arg = a->args[0]; - int count; - if (!str_to_int(arg, &count)) { - return error_msg("Invalid number: %s", arg); - } - if (count == 0) { - return error_msg("Count must be non-zero"); - } - shift_lines(e->view, count); - return true; -} - -static bool cmd_show(EditorState *e, const CommandArgs *a) -{ - bool write_to_cmdline = has_flag(a, 'c'); - if (write_to_cmdline && a->nr_args < 2) { - return error_msg("\"show -c\" requires 2 arguments"); - } - return show(e, a->args[0], a->args[1], write_to_cmdline); -} - -static bool cmd_suspend(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - if (e->status == EDITOR_INITIALIZING) { - LOG_WARNING("suspend request ignored"); - return false; - } - - if (e->session_leader) { - return error_msg("Session leader can't suspend"); - } - - ui_end(e); - bool suspended = !kill(0, SIGSTOP); - if (!suspended) { - error_msg_errno("kill"); - } - - term_raw(); - ui_start(e); - return suspended; -} - -static bool cmd_tag(EditorState *e, const CommandArgs *a) -{ - if (has_flag(a, 'r')) { - bookmark_pop(e->window, &e->bookmarks); - return true; - } - - StringView name; - if (a->args[0]) { - name = strview_from_cstring(a->args[0]); - } else { - name = view_get_word_under_cursor(e->view); - if (name.length == 0) { - return false; - } - } - - const char *filename = e->buffer->abs_filename; - size_t ntags = tag_lookup(&e->tagfile, &name, filename, &e->messages); - activate_current_message_save(e); - return (ntags > 0); -} - -static bool cmd_title(EditorState *e, const CommandArgs *a) -{ - Buffer *buffer = e->buffer; - if (buffer->abs_filename) { - return error_msg("saved buffers can't be retitled"); - } - set_display_filename(buffer, xstrdup(a->args[0])); - mark_buffer_tabbars_changed(buffer); - return true; -} - -static bool cmd_toggle(EditorState *e, const CommandArgs *a) -{ - bool global = has_flag(a, 'g'); - bool verbose = has_flag(a, 'v'); - const char *option_name = a->args[0]; - size_t nr_values = a->nr_args - 1; - if (nr_values == 0) { - return toggle_option(e, option_name, global, verbose); - } - - char **values = a->args + 1; - return toggle_option_values(e, option_name, global, verbose, values, nr_values); -} - -static bool cmd_undo(EditorState *e, const CommandArgs *a) -{ - View *view = e->view; - bool move_only = has_flag(a, 'm'); - if (move_only) { - const Change *change = view->buffer->cur_change; - if (!change->next) { - // If there's only 1 change, there's nothing meaningful to move to - return false; - } - block_iter_goto_offset(&view->cursor, change->offset); - view_reset_preferred_x(view); - return true; - } - - if (!undo(view)) { - return false; - } - - unselect(view); - return true; -} - -static bool cmd_unselect(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - unselect(e->view); - return true; -} - -static bool cmd_up(EditorState *e, const CommandArgs *a) -{ - handle_select_chars_or_lines_flags(e->view, a); - move_up(e->view, 1); - return true; -} - -static bool cmd_view(EditorState *e, const CommandArgs *a) -{ - Window *window = e->window; - BUG_ON(window->views.count == 0); - const char *arg = a->args[0]; - size_t idx; - if (streq(arg, "last")) { - idx = window->views.count - 1; - } else { - if (!str_to_size(arg, &idx) || idx == 0) { - return error_msg("Invalid view index: %s", arg); - } - idx = MIN(idx, window->views.count) - 1; - } - set_view(window->views.ptrs[idx]); - return true; -} - -static bool cmd_wclose(EditorState *e, const CommandArgs *a) -{ - View *view = window_find_unclosable_view(e->window); - bool force = has_flag(a, 'f'); - if (!view || force) { - goto close; - } - - bool prompt = has_flag(a, 'p'); - set_view(view); - if (!prompt) { - return error_msg ( - "Save modified files or run 'wclose -f' to close " - "window without saving" - ); - } - - if (dialog_prompt(e, "Close window without saving? [y/N]", "ny") != 'y') { - return false; - } - -close: - window_close(e->window); - return true; -} - -static bool cmd_wflip(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - Frame *frame = e->window->frame; - if (!frame->parent) { - return false; - } - frame->parent->vertical ^= 1; - mark_everything_changed(e); - return true; -} - -static bool cmd_wnext(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - e->window = next_window(e->window); - set_view(e->window->view); - mark_everything_changed(e); - debug_frame(e->root_frame); - return true; -} - -static bool cmd_word_bwd(EditorState *e, const CommandArgs *a) -{ - handle_select_chars_flag(e->view, a); - bool skip_non_word = has_flag(a, 's'); - word_bwd(&e->view->cursor, skip_non_word); - view_reset_preferred_x(e->view); - return true; -} - -static bool cmd_word_fwd(EditorState *e, const CommandArgs *a) -{ - handle_select_chars_flag(e->view, a); - bool skip_non_word = has_flag(a, 's'); - word_fwd(&e->view->cursor, skip_non_word); - view_reset_preferred_x(e->view); - return true; -} - -static bool cmd_wprev(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - e->window = prev_window(e->window); - set_view(e->window->view); - mark_everything_changed(e); - debug_frame(e->root_frame); - return true; -} - -static bool cmd_wrap_paragraph(EditorState *e, const CommandArgs *a) -{ - const char *arg = a->args[0]; - unsigned int width = e->buffer->options.text_width; - if (arg) { - if (!str_to_uint(arg, &width)) { - return error_msg("invalid paragraph width: %s", arg); - } - unsigned int max = TEXT_WIDTH_MAX; - if (width < 1 || width > max) { - return error_msg("width must be between 1 and %u", max); - } - } - format_paragraph(e->view, width); - return true; -} - -static bool cmd_wresize(EditorState *e, const CommandArgs *a) -{ - Window *window = e->window; - if (!window->frame->parent) { - // Only window - return false; - } - - ResizeDirection dir = RESIZE_DIRECTION_AUTO; - switch (last_flag(a)) { - case 'h': - dir = RESIZE_DIRECTION_HORIZONTAL; - break; - case 'v': - dir = RESIZE_DIRECTION_VERTICAL; - break; - } - - const char *arg = a->args[0]; - if (arg) { - int n; - if (!str_to_int(arg, &n)) { - return error_msg("Invalid resize value: %s", arg); - } - if (arg[0] == '+' || arg[0] == '-') { - add_to_frame_size(window->frame, dir, n); - } else { - resize_frame(window->frame, dir, n); - } - } else { - equalize_frame_sizes(window->frame->parent); - } - - mark_everything_changed(e); - debug_frame(e->root_frame); - // TODO: return false if resize failed? - return true; -} - -static bool cmd_wsplit(EditorState *e, const CommandArgs *a) -{ - bool before = has_flag(a, 'b'); - bool use_glob = has_flag(a, 'g') && a->nr_args > 0; - bool vertical = has_flag(a, 'h'); - bool root = has_flag(a, 'r'); - bool temporary = has_flag(a, 't'); - bool empty = temporary || has_flag(a, 'n'); - - if (unlikely(empty && a->nr_args > 0)) { - return error_msg("flags -n and -t can't be used with filename arguments"); - } - - char **paths = a->args; - glob_t globbuf; - if (use_glob) { - if (!xglob(a->args, &globbuf)) { - return false; - } - paths = globbuf.gl_pathv; - } - - Frame *frame; - if (root) { - frame = split_root_frame(e, vertical, before); - } else { - frame = split_frame(e->window, vertical, before); - } - - View *save = e->view; - e->window = frame->window; - e->view = NULL; - e->buffer = NULL; - mark_everything_changed(e); - - View *view; - if (empty) { - view = window_open_new_file(e->window); - view->buffer->temporary = temporary; - } else if (paths[0]) { - view = window_open_files(e->window, paths, NULL); - } else { - view = window_add_buffer(e->window, save->buffer); - view->cursor = save->cursor; - set_view(view); - } - - if (use_glob) { - globfree(&globbuf); - } - - if (!view) { - // Open failed, remove new window - remove_frame(e, e->window->frame); - e->view = save; - e->buffer = save->buffer; - e->window = save->window; - } - - debug_frame(e->root_frame); - return !!view; -} - -static bool cmd_wswap(EditorState *e, const CommandArgs *a) -{ - BUG_ON(a->nr_args); - Frame *frame = e->window->frame; - Frame *parent = frame->parent; - if (!parent) { - return false; - } - - size_t count = parent->frames.count; - size_t current = ptr_array_idx(&parent->frames, frame); - BUG_ON(current >= count); - size_t next = size_increment_wrapped(current, count); - - void **ptrs = parent->frames.ptrs; - Frame *tmp = ptrs[current]; - ptrs[current] = ptrs[next]; - ptrs[next] = tmp; - mark_everything_changed(e); - return true; -} - -IGNORE_WARNING("-Wincompatible-pointer-types") - -static const Command cmds[] = { - {"alias", "-", true, 1, 2, cmd_alias}, - {"bind", "-cns", true, 1, 2, cmd_bind}, - {"blkdown", "cl", false, 0, 0, cmd_blkdown}, - {"blkup", "cl", false, 0, 0, cmd_blkup}, - {"bof", "cl", false, 0, 0, cmd_bof}, - {"bol", "cst", false, 0, 0, cmd_bol}, - {"bolsf", "cl", false, 0, 0, cmd_bolsf}, - {"bookmark", "r", false, 0, 0, cmd_bookmark}, - {"case", "lu", false, 0, 0, cmd_case}, - {"cd", "", true, 1, 1, cmd_cd}, - {"center-view", "", false, 0, 0, cmd_center_view}, - {"clear", "i", false, 0, 0, cmd_clear}, - {"close", "fpqw", false, 0, 0, cmd_close}, - {"command", "-", false, 0, 1, cmd_command}, - {"compile", "-1ps", false, 2, -1, cmd_compile}, - {"copy", "bikp", false, 0, 0, cmd_copy}, - {"cursor", "", true, 0, 3, cmd_cursor}, - {"cut", "", false, 0, 0, cmd_cut}, - {"delete", "", false, 0, 0, cmd_delete}, - {"delete-eol", "n", false, 0, 0, cmd_delete_eol}, - {"delete-line", "", false, 0, 0, cmd_delete_line}, - {"delete-word", "s", false, 0, 0, cmd_delete_word}, - {"down", "cl", false, 0, 0, cmd_down}, - {"eof", "cl", false, 0, 0, cmd_eof}, - {"eol", "c", false, 0, 0, cmd_eol}, - {"eolsf", "cl", false, 0, 0, cmd_eolsf}, - {"erase", "", false, 0, 0, cmd_erase}, - {"erase-bol", "", false, 0, 0, cmd_erase_bol}, - {"erase-word", "s", false, 0, 0, cmd_erase_word}, - {"errorfmt", "i", true, 1, 2 + ERRORFMT_CAPTURE_MAX, cmd_errorfmt}, - {"exec", "-e=i=o=lmnpst", false, 1, -1, cmd_exec}, - {"ft", "-bcfi", true, 2, -1, cmd_ft}, - {"hi", "-c", true, 0, -1, cmd_hi}, - {"include", "bq", true, 1, 1, cmd_include}, - {"insert", "km", false, 1, 1, cmd_insert}, - {"join", "", false, 0, 0, cmd_join}, - {"left", "c", false, 0, 0, cmd_left}, - {"line", "", false, 1, 1, cmd_line}, - {"load-syntax", "", true, 1, 1, cmd_load_syntax}, - {"macro", "", false, 1, 1, cmd_macro}, - {"match-bracket", "", false, 0, 0, cmd_match_bracket}, - {"move-tab", "", false, 1, 1, cmd_move_tab}, - {"msg", "np", false, 0, 1, cmd_msg}, - {"new-line", "a", false, 0, 0, cmd_new_line}, - {"next", "", false, 0, 0, cmd_next}, - {"open", "e=gt", false, 0, -1, cmd_open}, - {"option", "-r", true, 3, -1, cmd_option}, - {"paste", "acm", false, 0, 0, cmd_paste}, - {"pgdown", "cl", false, 0, 0, cmd_pgdown}, - {"pgup", "cl", false, 0, 0, cmd_pgup}, - {"prev", "", false, 0, 0, cmd_prev}, - {"quit", "fp", false, 0, 1, cmd_quit}, - {"redo", "", false, 0, 1, cmd_redo}, - {"refresh", "", false, 0, 0, cmd_refresh}, - {"repeat", "-", false, 2, -1, cmd_repeat}, - {"replace", "bcgi", false, 2, 2, cmd_replace}, - {"right", "c", false, 0, 0, cmd_right}, - {"save", "Bbde=fpu", false, 0, 1, cmd_save}, - {"scroll-down", "", false, 0, 0, cmd_scroll_down}, - {"scroll-pgdown", "", false, 0, 0, cmd_scroll_pgdown}, - {"scroll-pgup", "", false, 0, 0, cmd_scroll_pgup}, - {"scroll-up", "", false, 0, 0, cmd_scroll_up}, - {"search", "Hnprw", false, 0, 1, cmd_search}, - {"select", "kl", false, 0, 0, cmd_select}, - {"select-block", "", false, 0, 0, cmd_select_block}, - {"set", "gl", true, 1, -1, cmd_set}, - {"setenv", "", true, 1, 2, cmd_setenv}, - {"shift", "", false, 1, 1, cmd_shift}, - {"show", "c", false, 1, 2, cmd_show}, - {"suspend", "", false, 0, 0, cmd_suspend}, - {"tag", "r", false, 0, 1, cmd_tag}, - {"title", "", false, 1, 1, cmd_title}, - {"toggle", "gv", false, 1, -1, cmd_toggle}, - {"undo", "m", false, 0, 0, cmd_undo}, - {"unselect", "", false, 0, 0, cmd_unselect}, - {"up", "cl", false, 0, 0, cmd_up}, - {"view", "", false, 1, 1, cmd_view}, - {"wclose", "fp", false, 0, 0, cmd_wclose}, - {"wflip", "", false, 0, 0, cmd_wflip}, - {"wnext", "", false, 0, 0, cmd_wnext}, - {"word-bwd", "cs", false, 0, 0, cmd_word_bwd}, - {"word-fwd", "cs", false, 0, 0, cmd_word_fwd}, - {"wprev", "", false, 0, 0, cmd_wprev}, - {"wrap-paragraph", "", false, 0, 1, cmd_wrap_paragraph}, - {"wresize", "hv", false, 0, 1, cmd_wresize}, - {"wsplit", "bghnrt", false, 0, -1, cmd_wsplit}, - {"wswap", "", false, 0, 0, cmd_wswap}, -}; - -UNIGNORE_WARNINGS - -static bool allow_macro_recording(const Command *cmd, char **args) -{ - CommandFunc fn = cmd->cmd; - if (fn == (CommandFunc)cmd_macro || fn == (CommandFunc)cmd_command) { - return false; - } - - if (fn == (CommandFunc)cmd_search) { - char **args_copy = copy_string_array(args, string_array_length(args)); - CommandArgs a = cmdargs_new(args_copy); - bool ret = true; - if (do_parse_args(cmd, &a) == ARGERR_NONE) { - if (a.nr_args == 0 && !(a.flag_set & get_flagset_npw())) { - // If command is "search" with no pattern argument and without - // flags -n, -p or -w, the command would put the editor into - // search mode, which shouldn't be recorded. - ret = false; - } - } - free_string_array(args_copy); - return ret; - } - - if (fn == (CommandFunc)cmd_exec) { - // TODO: don't record -o with open/tag/eval/msg - } - - return true; -} - -UNITTEST { - const char *args[4] = {NULL}; - char **argp = (char**)args; - const Command *cmd = find_normal_command("left"); - BUG_ON(!cmd); - BUG_ON(!allow_macro_recording(cmd, argp)); - - cmd = find_normal_command("exec"); - BUG_ON(!cmd); - BUG_ON(!allow_macro_recording(cmd, argp)); - - cmd = find_normal_command("command"); - BUG_ON(!cmd); - BUG_ON(allow_macro_recording(cmd, argp)); - - cmd = find_normal_command("macro"); - BUG_ON(!cmd); - BUG_ON(allow_macro_recording(cmd, argp)); - - cmd = find_normal_command("search"); - BUG_ON(!cmd); - BUG_ON(allow_macro_recording(cmd, argp)); - args[0] = "xyz"; - BUG_ON(!allow_macro_recording(cmd, argp)); - args[0] = "-n"; - BUG_ON(!allow_macro_recording(cmd, argp)); - args[0] = "-p"; - BUG_ON(!allow_macro_recording(cmd, argp)); - args[0] = "-w"; - BUG_ON(!allow_macro_recording(cmd, argp)); - args[0] = "-Hr"; - BUG_ON(allow_macro_recording(cmd, argp)); - args[1] = "str"; - BUG_ON(!allow_macro_recording(cmd, argp)); -} - -static void record_command(const Command *cmd, char **args, void *userdata) -{ - if (!allow_macro_recording(cmd, args)) { - return; - } - EditorState *e = userdata; - macro_command_hook(&e->macro, cmd->name, args); -} - -const Command *find_normal_command(const char *name) -{ - return BSEARCH(name, cmds, command_cmp); -} - -const CommandSet normal_commands = { - .lookup = find_normal_command, - .macro_record = record_command, - .expand_variable = expand_normal_var, - .expand_env_vars = true, -}; - -const char *find_normal_alias(const char *name, void *userdata) -{ - EditorState *e = userdata; - return find_alias(&e->aliases, name); -} - -bool handle_normal_command(EditorState *e, const char *cmd, bool allow_recording) -{ - CommandRunner runner = cmdrunner_for_mode(e, INPUT_NORMAL, allow_recording); - return handle_command(&runner, cmd); -} - -void exec_normal_config(EditorState *e, StringView config) -{ - CommandRunner runner = cmdrunner_for_mode(e, INPUT_NORMAL, false); - exec_config(&runner, config); -} - -int read_normal_config(EditorState *e, const char *filename, ConfigFlags flags) -{ - CommandRunner runner = cmdrunner_for_mode(e, INPUT_NORMAL, false); - return read_config(&runner, filename, flags); -} - -void collect_normal_commands(PointerArray *a, const char *prefix) -{ - COLLECT_STRING_FIELDS(cmds, name, a, prefix); -} - -UNITTEST { - CHECK_BSEARCH_ARRAY(cmds, name, strcmp); - - for (size_t i = 0, n = ARRAYLEN(cmds); i < n; i++) { - // Check that flags arrays is null-terminated within bounds - const char *const flags = cmds[i].flags; - BUG_ON(flags[ARRAYLEN(cmds[0].flags) - 1] != '\0'); - - // Count number of real flags (i.e. not including '-' or '=') - size_t nr_real_flags = 0; - for (size_t j = (flags[0] == '-' ? 1 : 0); flags[j]; j++) { - unsigned char flag = flags[j]; - if (ascii_isalnum(flag)) { - nr_real_flags++; - } else if (flag != '=') { - BUG("invalid command flag: 0x%02hhX", flag); - } - } - - // Check that max. number of real flags fits in CommandArgs::flags - // array (and also leaves 1 byte for null-terminator) - CommandArgs a; - BUG_ON(nr_real_flags >= ARRAYLEN(a.flags)); - } -} diff --git a/examples/dte/commands.h b/examples/dte/commands.h deleted file mode 100644 index cfebdd2..0000000 --- a/examples/dte/commands.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef COMMANDS_H -#define COMMANDS_H - -#include <stdbool.h> -#include "command/run.h" -#include "config.h" -#include "util/macros.h" -#include "util/ptr-array.h" -#include "util/string-view.h" - -extern const CommandSet normal_commands; - -struct EditorState; - -const Command *find_normal_command(const char *name) NONNULL_ARGS; -const char *find_normal_alias(const char *name, void *userdata) NONNULL_ARGS; -bool handle_normal_command(struct EditorState *e, const char *cmd, bool allow_recording) NONNULL_ARGS; -void exec_normal_config(struct EditorState *e, StringView config) NONNULL_ARGS; -int read_normal_config(struct EditorState *e, const char *filename, ConfigFlags flags) NONNULL_ARGS; -void collect_normal_commands(PointerArray *a, const char *prefix) NONNULL_ARGS; - -#endif diff --git a/examples/dte/compat.c b/examples/dte/compat.c deleted file mode 100644 index 9a26950..0000000 --- a/examples/dte/compat.c +++ /dev/null @@ -1,35 +0,0 @@ -#include "compat.h" - -const char feature_string[] = - "" -#if HAVE_DUP3 - " dup3" -#endif -#if HAVE_PIPE2 - " pipe2" -#endif -#if HAVE_FSYNC - " fsync" -#endif -#if HAVE_MEMMEM - " memmem" -#endif -#if HAVE_SIG2STR - " sig2str" -#endif -#if HAVE_SIGABBREV_NP && !HAVE_SIG2STR - " sigabbrev_np" -#endif -#if HAVE_TIOCGWINSZ - " TIOCGWINSZ" -#endif -#if HAVE_TCGETWINSIZE && !HAVE_TIOCGWINSZ - " tcgetwinsize" -#endif -#if HAVE_TIOCNOTTY - " TIOCNOTTY" -#endif -#if HAVE_POSIX_MADVISE - " posix_madvise" -#endif -; diff --git a/examples/dte/compat.h b/examples/dte/compat.h deleted file mode 100644 index bbb6d53..0000000 --- a/examples/dte/compat.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef COMPAT_H -#define COMPAT_H - -#include "../build/feature.h" - -extern const char feature_string[]; - -#endif diff --git a/examples/dte/compiler.c b/examples/dte/compiler.c deleted file mode 100644 index b1a0eaa..0000000 --- a/examples/dte/compiler.c +++ /dev/null @@ -1,151 +0,0 @@ -#include <stdlib.h> -#include <string.h> -#include "compiler.h" -#include "command/serialize.h" -#include "error.h" -#include "regexp.h" -#include "util/array.h" -#include "util/debug.h" -#include "util/intern.h" -#include "util/str-util.h" -#include "util/xmalloc.h" - -static const char capture_names[][8] = { - [ERRFMT_FILE] = "file", - [ERRFMT_LINE] = "line", - [ERRFMT_COLUMN] = "column", - [ERRFMT_MESSAGE] = "message" -}; - -UNITTEST { - CHECK_STRING_ARRAY(capture_names); -} - -static Compiler *find_or_add_compiler(HashMap *compilers, const char *name) -{ - Compiler *c = find_compiler(compilers, name); - return c ? c : hashmap_insert(compilers, xstrdup(name), xnew0(Compiler, 1)); -} - -Compiler *find_compiler(const HashMap *compilers, const char *name) -{ - return hashmap_get(compilers, name); -} - -bool add_error_fmt ( - HashMap *compilers, - const char *name, - bool ignore, - const char *format, - char **desc -) { - int8_t idx[] = { - [ERRFMT_FILE] = -1, - [ERRFMT_LINE] = -1, - [ERRFMT_COLUMN] = -1, - [ERRFMT_MESSAGE] = 0, - }; - - size_t max_idx = 0; - for (size_t i = 0, j = 0, n = ARRAYLEN(capture_names); desc[i]; i++) { - BUG_ON(i >= ERRORFMT_CAPTURE_MAX); - if (streq(desc[i], "_")) { - continue; - } - for (j = 0; j < n; j++) { - if (streq(desc[i], capture_names[j])) { - max_idx = i + 1; - idx[j] = max_idx; - break; - } - } - if (unlikely(j == n)) { - return error_msg("unknown substring name %s", desc[i]); - } - } - - ErrorFormat *f = xnew(ErrorFormat, 1); - f->ignore = ignore; - static_assert_compatible_types(f->capture_index, idx); - memcpy(f->capture_index, idx, sizeof(idx)); - - if (unlikely(!regexp_compile(&f->re, format, 0))) { - free(f); - return false; - } - - if (unlikely(max_idx > f->re.re_nsub)) { - regfree(&f->re); - free(f); - return error_msg("invalid substring count"); - } - - Compiler *compiler = find_or_add_compiler(compilers, name); - f->pattern = str_intern(format); - ptr_array_append(&compiler->error_formats, f); - return true; -} - -static void free_error_format(ErrorFormat *f) -{ - regfree(&f->re); - free(f); -} - -void free_compiler(Compiler *c) -{ - ptr_array_free_cb(&c->error_formats, FREE_FUNC(free_error_format)); - free(c); -} - -void remove_compiler(HashMap *compilers, const char *name) -{ - Compiler *c = hashmap_remove(compilers, name); - if (c) { - free_compiler(c); - } -} - -void collect_errorfmt_capture_names(PointerArray *a, const char *prefix) -{ - COLLECT_STRINGS(capture_names, a, prefix); - if (str_has_prefix("_", prefix)) { - ptr_array_append(a, xstrdup("_")); - } -} - -void dump_compiler(const Compiler *c, const char *name, String *s) -{ - for (size_t i = 0, n = c->error_formats.count; i < n; i++) { - ErrorFormat *e = c->error_formats.ptrs[i]; - string_append_literal(s, "errorfmt "); - if (e->ignore) { - string_append_literal(s, "-i "); - } - if (unlikely(name[0] == '-' || e->pattern[0] == '-')) { - string_append_literal(s, "-- "); - } - string_append_escaped_arg(s, name, true); - string_append_byte(s, ' '); - string_append_escaped_arg(s, e->pattern, true); - - static_assert(ARRAYLEN(e->capture_index) == 4); - const int8_t *a = e->capture_index; - int max_idx = MAX4(a[0], a[1], a[2], a[3]); - BUG_ON(max_idx > ERRORFMT_CAPTURE_MAX); - - for (int j = 1; j <= max_idx; j++) { - const char *capname = "_"; - for (size_t k = 0; k < ARRAYLEN(capture_names); k++) { - if (j == a[k]) { - capname = capture_names[k]; - break; - } - } - string_append_byte(s, ' '); - string_append_cstring(s, capname); - } - - string_append_byte(s, '\n'); - } -} diff --git a/examples/dte/compiler.h b/examples/dte/compiler.h deleted file mode 100644 index c1b0de6..0000000 --- a/examples/dte/compiler.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef COMPILER_H -#define COMPILER_H - -#include <regex.h> -#include <stdbool.h> -#include <stdint.h> -#include "util/hashmap.h" -#include "util/macros.h" -#include "util/ptr-array.h" -#include "util/string.h" - -enum { - ERRORFMT_CAPTURE_MAX = 16 -}; - -enum { - ERRFMT_FILE, - ERRFMT_LINE, - ERRFMT_COLUMN, - ERRFMT_MESSAGE, -}; - -typedef struct { - int8_t capture_index[4]; - bool ignore; - const char *pattern; // Original pattern string (interned) - regex_t re; // Compiled pattern -} ErrorFormat; - -typedef struct { - PointerArray error_formats; -} Compiler; - -Compiler *find_compiler(const HashMap *compilers, const char *name) NONNULL_ARGS; -void remove_compiler(HashMap *compilers, const char *name) NONNULL_ARGS; -void free_compiler(Compiler *c) NONNULL_ARGS; -void collect_errorfmt_capture_names(PointerArray *a, const char *prefix) NONNULL_ARGS; -void dump_compiler(const Compiler *c, const char *name, String *s) NONNULL_ARGS; - -NONNULL_ARGS WARN_UNUSED_RESULT -bool add_error_fmt ( - HashMap *compilers, - const char *name, - bool ignore, - const char *format, - char **desc -); - -#endif diff --git a/examples/dte/completion.c b/examples/dte/completion.c deleted file mode 100644 index 89e8c8c..0000000 --- a/examples/dte/completion.c +++ /dev/null @@ -1,879 +0,0 @@ -#include <fcntl.h> -#include <stdbool.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> -#include <unistd.h> -#include "completion.h" -#include "bind.h" -#include "command/alias.h" -#include "command/args.h" -#include "command/parse.h" -#include "command/run.h" -#include "command/serialize.h" -#include "commands.h" -#include "compiler.h" -#include "config.h" -#include "filetype.h" -#include "options.h" -#include "show.h" -#include "syntax/color.h" -#include "tag.h" -#include "terminal/cursor.h" -#include "terminal/key.h" -#include "terminal/style.h" -#include "util/arith.h" -#include "util/array.h" -#include "util/ascii.h" -#include "util/bsearch.h" -#include "util/debug.h" -#include "util/intmap.h" -#include "util/log.h" -#include "util/numtostr.h" -#include "util/path.h" -#include "util/str-util.h" -#include "util/string-view.h" -#include "util/string.h" -#include "util/xdirent.h" -#include "util/xmalloc.h" -#include "vars.h" - -extern char **environ; - -typedef enum { - COLLECT_ALL, // (directories and files) - COLLECT_EXECUTABLES, // (directories and executable files) - COLLECT_DIRS_ONLY, -} FileCollectionType; - -static bool is_executable(int dir_fd, const char *filename) -{ - return faccessat(dir_fd, filename, X_OK, 0) == 0; -} - -static bool do_collect_files ( - PointerArray *array, - const char *dirname, - const char *dirprefix, - const char *fileprefix, - FileCollectionType type -) { - DIR *const dir = xopendir(dirname); - if (!dir) { - return false; - } - - const int dir_fd = dirfd(dir); - if (unlikely(dir_fd < 0)) { - LOG_ERRNO("dirfd"); - xclosedir(dir); - return false; - } - - size_t dlen = strlen(dirprefix); - size_t flen = strlen(fileprefix); - const struct dirent *de; - - while ((de = xreaddir(dir))) { - const char *name = de->d_name; - if (streq(name, ".") || streq(name, "..") || unlikely(streq(name, ""))) { - continue; - } - - // TODO: add a global option to allow dotfiles to be included - // even when there's no prefix - if (flen ? strncmp(name, fileprefix, flen) : name[0] == '.') { - continue; - } - - struct stat st; - if (fstatat(dir_fd, name, &st, AT_SYMLINK_NOFOLLOW)) { - continue; - } - - bool is_dir = S_ISDIR(st.st_mode); - if (S_ISLNK(st.st_mode)) { - if (!fstatat(dir_fd, name, &st, 0)) { - is_dir = S_ISDIR(st.st_mode); - } - } - - if (!is_dir) { - switch (type) { - case COLLECT_DIRS_ONLY: - continue; - case COLLECT_ALL: - break; - case COLLECT_EXECUTABLES: - if (!is_executable(dir_fd, name)) { - continue; - } - if (!dlen) { - dirprefix = "./"; - dlen = 2; - } - break; - default: - BUG("unhandled FileCollectionType value"); - } - } - - ptr_array_append(array, path_joinx(dirprefix, name, is_dir)); - } - - xclosedir(dir); - return true; -} - -static void collect_files(EditorState *e, CompletionState *cs, FileCollectionType type) -{ - StringView esc = cs->escaped; - if (strview_has_prefix(&esc, "~/")) { - CommandRunner runner = cmdrunner_for_mode(e, INPUT_NORMAL, false); - char *str = parse_command_arg(&runner, esc.data, esc.length, false); - const char *slash = strrchr(str, '/'); - BUG_ON(!slash); - cs->tilde_expanded = true; - char *dir = path_dirname(cs->parsed); - char *dirprefix = path_dirname(str); - do_collect_files(&cs->completions, dir, dirprefix, slash + 1, type); - free(dirprefix); - free(dir); - free(str); - } else { - const char *slash = strrchr(cs->parsed, '/'); - if (!slash) { - do_collect_files(&cs->completions, ".", "", cs->parsed, type); - } else { - char *dir = path_dirname(cs->parsed); - do_collect_files(&cs->completions, dir, dir, slash + 1, type); - free(dir); - } - } - - if (cs->completions.count == 1) { - // Add space if completed string is not a directory - const char *s = cs->completions.ptrs[0]; - size_t len = strlen(s); - if (len > 0) { - cs->add_space_after_single_match = s[len - 1] != '/'; - } - } -} - -void collect_normal_aliases(EditorState *e, PointerArray *a, const char *prefix) -{ - collect_hashmap_keys(&e->aliases, a, prefix); -} - -static void collect_bound_keys(const IntMap *bindings, PointerArray *a, const char *prefix) -{ - char keystr[KEYCODE_STR_MAX]; - for (IntMapIter it = intmap_iter(bindings); intmap_next(&it); ) { - size_t keylen = keycode_to_string(it.entry->key, keystr); - if (str_has_prefix(keystr, prefix)) { - ptr_array_append(a, xmemdup(keystr, keylen + 1)); - } - } -} - -void collect_bound_normal_keys(EditorState *e, PointerArray *a, const char *prefix) -{ - collect_bound_keys(&e->modes[INPUT_NORMAL].key_bindings, a, prefix); -} - -void collect_hl_colors(EditorState *e, PointerArray *a, const char *prefix) -{ - collect_builtin_colors(a, prefix); - collect_hashmap_keys(&e->colors.other, a, prefix); -} - -void collect_compilers(EditorState *e, PointerArray *a, const char *prefix) -{ - collect_hashmap_keys(&e->compilers, a, prefix); -} - -void collect_env(EditorState* UNUSED_ARG(e), PointerArray *a, const char *prefix) -{ - if (strchr(prefix, '=')) { - return; - } - - for (size_t i = 0; environ[i]; i++) { - const char *var = environ[i]; - if (str_has_prefix(var, prefix)) { - const char *delim = strchr(var, '='); - if (likely(delim && delim != var)) { - ptr_array_append(a, xstrcut(var, delim - var)); - } - } - } -} - -static void complete_alias(EditorState *e, const CommandArgs *a) -{ - CompletionState *cs = &e->cmdline.completion; - if (a->nr_args == 0) { - collect_normal_aliases(e, &cs->completions, cs->parsed); - } else if (a->nr_args == 1 && cs->parsed[0] == '\0') { - const char *cmd = find_alias(&e->aliases, a->args[0]); - if (cmd) { - ptr_array_append(&cs->completions, xstrdup(cmd)); - } - } -} - -static void complete_bind(EditorState *e, const CommandArgs *a) -{ - static const char flags[] = { - [INPUT_NORMAL] = 'n', - [INPUT_COMMAND] = 'c', - [INPUT_SEARCH] = 's', - }; - - static_assert(ARRAYLEN(flags) == ARRAYLEN(e->modes)); - InputMode mode = INPUT_NORMAL; - for (size_t i = 0, count = 0; i < ARRAYLEN(flags); i++) { - if (cmdargs_has_flag(a, flags[i])) { - if (++count >= 2) { - return; // Don't complete bindings for multiple modes - } - mode = i; - } - } - - const IntMap *key_bindings = &e->modes[mode].key_bindings; - CompletionState *cs = &e->cmdline.completion; - if (a->nr_args == 0) { - collect_bound_keys(key_bindings, &cs->completions, cs->parsed); - return; - } - - if (a->nr_args != 1 || cs->parsed[0] != '\0') { - return; - } - - KeyCode key; - if (!parse_key_string(&key, a->args[0])) { - return; - } - const CachedCommand *cmd = lookup_binding(key_bindings, key); - if (!cmd) { - return; - } - - ptr_array_append(&cs->completions, xstrdup(cmd->cmd_str)); -} - -static void complete_cd(EditorState *e, const CommandArgs* UNUSED_ARG(a)) -{ - CompletionState *cs = &e->cmdline.completion; - collect_files(e, cs, COLLECT_DIRS_ONLY); - if (str_has_prefix("-", cs->parsed)) { - if (likely(xgetenv("OLDPWD"))) { - ptr_array_append(&cs->completions, xstrdup("-")); - } - } -} - -static void complete_exec(EditorState *e, const CommandArgs *a) -{ - // TODO: add completion for [-ioe] option arguments - CompletionState *cs = &e->cmdline.completion; - collect_files(e, cs, a->nr_args == 0 ? COLLECT_EXECUTABLES : COLLECT_ALL); -} - -static void complete_compile(EditorState *e, const CommandArgs *a) -{ - CompletionState *cs = &e->cmdline.completion; - size_t n = a->nr_args; - if (n == 0) { - collect_compilers(e, &cs->completions, cs->parsed); - } else { - collect_files(e, cs, n == 1 ? COLLECT_EXECUTABLES : COLLECT_ALL); - } -} - -static void complete_cursor(EditorState *e, const CommandArgs *a) -{ - CompletionState *cs = &e->cmdline.completion; - size_t n = a->nr_args; - if (n == 0) { - collect_cursor_modes(&cs->completions, cs->parsed); - } else if (n == 1) { - collect_cursor_types(&cs->completions, cs->parsed); - } else if (n == 2) { - collect_cursor_colors(&cs->completions, cs->parsed); - // Add an example #rrggbb color, to make things more discoverable - static const char rgb_example[] = "#22AABB"; - if (str_has_prefix(rgb_example, cs->parsed)) { - ptr_array_append(&cs->completions, xstrdup(rgb_example)); - } - } -} - -static void complete_errorfmt(EditorState *e, const CommandArgs *a) -{ - CompletionState *cs = &e->cmdline.completion; - if (a->nr_args == 0) { - collect_compilers(e, &cs->completions, cs->parsed); - } else if (a->nr_args >= 2 && !cmdargs_has_flag(a, 'i')) { - collect_errorfmt_capture_names(&cs->completions, cs->parsed); - } -} - -static void complete_ft(EditorState *e, const CommandArgs *a) -{ - CompletionState *cs = &e->cmdline.completion; - if (a->nr_args == 0) { - collect_ft(&e->filetypes, &cs->completions, cs->parsed); - } -} - -static void complete_hi(EditorState *e, const CommandArgs *a) -{ - CompletionState *cs = &e->cmdline.completion; - if (a->nr_args == 0) { - collect_hl_colors(e, &cs->completions, cs->parsed); - } else { - collect_colors_and_attributes(&cs->completions, cs->parsed); - } -} - -static void complete_include(EditorState *e, const CommandArgs *a) -{ - CompletionState *cs = &e->cmdline.completion; - if (a->nr_args == 0) { - if (cmdargs_has_flag(a, 'b')) { - collect_builtin_includes(&cs->completions, cs->parsed); - } else { - collect_files(e, cs, COLLECT_ALL); - } - } -} - -static void complete_macro(EditorState *e, const CommandArgs *a) -{ - static const char verbs[][8] = { - "cancel", - "play", - "record", - "stop", - "toggle", - }; - - if (a->nr_args != 0) { - return; - } - - CompletionState *cs = &e->cmdline.completion; - COLLECT_STRINGS(verbs, &cs->completions, cs->parsed); -} - -static void complete_move_tab(EditorState *e, const CommandArgs *a) -{ - if (a->nr_args != 0) { - return; - } - - static const char words[][8] = {"left", "right"}; - CompletionState *cs = &e->cmdline.completion; - COLLECT_STRINGS(words, &cs->completions, cs->parsed); -} - -static void complete_open(EditorState *e, const CommandArgs *a) -{ - if (!cmdargs_has_flag(a, 't')) { - collect_files(e, &e->cmdline.completion, COLLECT_ALL); - } -} - -static void complete_option(EditorState *e, const CommandArgs *a) -{ - CompletionState *cs = &e->cmdline.completion; - if (a->nr_args == 0) { - if (!cmdargs_has_flag(a, 'r')) { - collect_ft(&e->filetypes, &cs->completions, cs->parsed); - } - } else if (a->nr_args & 1) { - collect_auto_options(&cs->completions, cs->parsed); - } else { - collect_option_values(e, &cs->completions, a->args[a->nr_args - 1], cs->parsed); - } -} - -static void complete_save(EditorState *e, const CommandArgs* UNUSED_ARG(a)) -{ - collect_files(e, &e->cmdline.completion, COLLECT_ALL); -} - -static void complete_quit(EditorState *e, const CommandArgs* UNUSED_ARG(a)) -{ - CompletionState *cs = &e->cmdline.completion; - if (str_has_prefix("0", cs->parsed)) { - ptr_array_append(&cs->completions, xstrdup("0")); - } - if (str_has_prefix("1", cs->parsed)) { - ptr_array_append(&cs->completions, xstrdup("1")); - } -} - -static void complete_redo(EditorState *e, const CommandArgs* UNUSED_ARG(a)) -{ - const Change *change = e->buffer->cur_change; - CompletionState *cs = &e->cmdline.completion; - for (unsigned long i = 1, n = change->nr_prev; i <= n; i++) { - ptr_array_append(&cs->completions, xstrdup(ulong_to_str(i))); - } -} - -static void complete_set(EditorState *e, const CommandArgs *a) -{ - CompletionState *cs = &e->cmdline.completion; - if ((a->nr_args + 1) & 1) { - bool local = cmdargs_has_flag(a, 'l'); - bool global = cmdargs_has_flag(a, 'g'); - collect_options(&cs->completions, cs->parsed, local, global); - } else { - collect_option_values(e, &cs->completions, a->args[a->nr_args - 1], cs->parsed); - } -} - -static void complete_setenv(EditorState *e, const CommandArgs *a) -{ - CompletionState *cs = &e->cmdline.completion; - if (a->nr_args == 0) { - collect_env(e, &cs->completions, cs->parsed); - } else if (a->nr_args == 1 && cs->parsed[0] == '\0') { - BUG_ON(!a->args[0]); - const char *value = getenv(a->args[0]); - if (value) { - ptr_array_append(&cs->completions, xstrdup(value)); - } - } -} - -static void complete_show(EditorState *e, const CommandArgs *a) -{ - CompletionState *cs = &e->cmdline.completion; - if (a->nr_args == 0) { - collect_show_subcommands(&cs->completions, cs->parsed); - } else if (a->nr_args == 1) { - BUG_ON(!a->args[0]); - collect_show_subcommand_args(e, &cs->completions, a->args[0], cs->parsed); - } -} - -static void complete_tag(EditorState *e, const CommandArgs *a) -{ - CompletionState *cs = &e->cmdline.completion; - if (a->nr_args == 0 && !cmdargs_has_flag(a, 'r')) { - BUG_ON(!cs->parsed); - StringView prefix = strview_from_cstring(cs->parsed); - collect_tags(&e->tagfile, &cs->completions, &prefix); - } -} - -static void complete_toggle(EditorState *e, const CommandArgs *a) -{ - CompletionState *cs = &e->cmdline.completion; - if (a->nr_args == 0) { - bool global = cmdargs_has_flag(a, 'g'); - collect_toggleable_options(&cs->completions, cs->parsed, global); - } -} - -static void complete_wsplit(EditorState *e, const CommandArgs *a) -{ - CompletionState *cs = &e->cmdline.completion; - if (!cmdargs_has_flag(a, 't') && !cmdargs_has_flag(a, 'n')) { - collect_files(e, cs, COLLECT_ALL); - } -} - -typedef struct { - char cmd_name[12]; - void (*complete)(EditorState *e, const CommandArgs *a); -} CompletionHandler; - -static const CompletionHandler completion_handlers[] = { - {"alias", complete_alias}, - {"bind", complete_bind}, - {"cd", complete_cd}, - {"compile", complete_compile}, - {"cursor", complete_cursor}, - {"errorfmt", complete_errorfmt}, - {"exec", complete_exec}, - {"ft", complete_ft}, - {"hi", complete_hi}, - {"include", complete_include}, - {"macro", complete_macro}, - {"move-tab", complete_move_tab}, - {"open", complete_open}, - {"option", complete_option}, - {"quit", complete_quit}, - {"redo", complete_redo}, - {"save", complete_save}, - {"set", complete_set}, - {"setenv", complete_setenv}, - {"show", complete_show}, - {"tag", complete_tag}, - {"toggle", complete_toggle}, - {"wsplit", complete_wsplit}, -}; - -UNITTEST { - CHECK_BSEARCH_ARRAY(completion_handlers, cmd_name, strcmp); - // Ensure handlers are kept in sync with renamed/removed commands - for (size_t i = 0; i < ARRAYLEN(completion_handlers); i++) { - const char *name = completion_handlers[i].cmd_name; - if (!find_normal_command(name)) { - BUG("completion handler for non-existent command: \"%s\"", name); - } - } -} - -static bool can_collect_flags ( - char **args, - size_t argc, - size_t nr_flag_args, - bool allow_flags_after_nonflags -) { - if (allow_flags_after_nonflags) { - for (size_t i = 0; i < argc; i++) { - if (streq(args[i], "--")) { - return false; - } - } - return true; - } - - for (size_t i = 0, nonflag = 0; i < argc; i++) { - if (args[i][0] != '-') { - if (++nonflag > nr_flag_args) { - return false; - } - continue; - } - if (streq(args[i], "--")) { - return false; - } - } - - return true; -} - -static bool collect_command_flags ( - PointerArray *array, - char **args, - size_t argc, - const Command *cmd, - const CommandArgs *a, - const char *prefix -) { - BUG_ON(prefix[0] != '-'); - const char *flags = cmd->flags; - bool flags_after_nonflags = (flags[0] != '-'); - - if (!can_collect_flags(args, argc, a->nr_flag_args, flags_after_nonflags)) { - return false; - } - - flags += flags_after_nonflags ? 0 : 1; - if (ascii_isalnum(prefix[1]) && prefix[2] == '\0') { - if (strchr(flags, prefix[1])) { - ptr_array_append(array, xmemdup(prefix, 3)); - } - return true; - } - - if (prefix[1] != '\0') { - return true; - } - - char buf[3] = "-"; - for (size_t i = 0; flags[i]; i++) { - if (!ascii_isalnum(flags[i]) || cmdargs_has_flag(a, flags[i])) { - continue; - } - buf[1] = flags[i]; - ptr_array_append(array, xmemdup(buf, 3)); - } - - return true; -} - -static void collect_completions(EditorState *e, char **args, size_t argc) -{ - CompletionState *cs = &e->cmdline.completion; - PointerArray *arr = &cs->completions; - const char *prefix = cs->parsed; - if (!argc) { - collect_normal_commands(arr, prefix); - collect_normal_aliases(e, arr, prefix); - return; - } - - for (size_t i = 0; i < argc; i++) { - if (!args[i]) { - // Embedded NULLs indicate there are multiple commands. - // Just return early here and avoid handling this case. - return; - } - } - - const Command *cmd = find_normal_command(args[0]); - if (!cmd) { - return; - } - - char **args_copy = copy_string_array(args + 1, argc - 1); - CommandArgs a = cmdargs_new(args_copy); - ArgParseError err = do_parse_args(cmd, &a); - bool dash = (prefix[0] == '-'); - if ( - (err != ARGERR_NONE && err != ARGERR_TOO_FEW_ARGUMENTS) - || (a.nr_args >= cmd->max_args && cmd->max_args != 0xFF && !dash) - ) { - goto out; - } - - if (dash && collect_command_flags(arr, args + 1, argc - 1, cmd, &a, prefix)) { - goto out; - } - - if (cmd->max_args == 0) { - goto out; - } - - const CompletionHandler *h = BSEARCH(args[0], completion_handlers, vstrcmp); - if (h) { - h->complete(e, &a); - } else if (streq(args[0], "repeat")) { - if (a.nr_args == 1) { - collect_normal_commands(arr, prefix); - } else if (a.nr_args >= 2) { - collect_completions(e, args + 2, argc - 2); - } - } - -out: - free_string_array(args_copy); -} - -static bool is_var(const char *str, size_t len) -{ - if (len == 0 || str[0] != '$') { - return false; - } - if (len == 1) { - return true; - } - if (!is_alpha_or_underscore(str[1])) { - return false; - } - for (size_t i = 2; i < len; i++) { - if (!is_alnum_or_underscore(str[i])) { - return false; - } - } - return true; -} - -UNITTEST { - BUG_ON(!is_var(STRN("$VAR"))); - BUG_ON(!is_var(STRN("$xy_190"))); - BUG_ON(!is_var(STRN("$__x_y_z"))); - BUG_ON(!is_var(STRN("$x"))); - BUG_ON(!is_var(STRN("$A"))); - BUG_ON(!is_var(STRN("$_0"))); - BUG_ON(!is_var(STRN("$"))); - BUG_ON(is_var(STRN(""))); - BUG_ON(is_var(STRN("A"))); - BUG_ON(is_var(STRN("$.a"))); - BUG_ON(is_var(STRN("$xyz!"))); - BUG_ON(is_var(STRN("$1"))); - BUG_ON(is_var(STRN("$09"))); - BUG_ON(is_var(STRN("$1a"))); -} - -static int strptrcmp(const void *v1, const void *v2) -{ - const char *const *s1 = v1; - const char *const *s2 = v2; - return strcmp(*s1, *s2); -} - -static void init_completion(EditorState *e, const CommandLine *cmdline) -{ - CompletionState *cs = &e->cmdline.completion; - const CommandRunner runner = cmdrunner_for_mode(e, INPUT_NORMAL, false); - BUG_ON(cs->orig); - BUG_ON(runner.userdata != e); - BUG_ON(!runner.lookup_alias); - - const size_t cmdline_pos = cmdline->pos; - char *const cmd = string_clone_cstring(&cmdline->buf); - PointerArray array = PTR_ARRAY_INIT; - ssize_t semicolon = -1; - ssize_t completion_pos = -1; - - for (size_t pos = 0; true; ) { - while (ascii_isspace(cmd[pos])) { - pos++; - } - - if (pos >= cmdline_pos) { - completion_pos = cmdline_pos; - break; - } - - if (!cmd[pos]) { - break; - } - - if (cmd[pos] == ';') { - semicolon = array.count; - ptr_array_append(&array, NULL); - pos++; - continue; - } - - CommandParseError err; - size_t end = find_end(cmd, pos, &err); - if (err != CMDERR_NONE || end >= cmdline_pos) { - completion_pos = pos; - break; - } - - if (semicolon + 1 == array.count) { - char *name = xstrslice(cmd, pos, end); - const char *value = runner.lookup_alias(name, runner.userdata); - if (value) { - size_t save = array.count; - if (parse_commands(&runner, &array, value) != CMDERR_NONE) { - for (size_t i = save, n = array.count; i < n; i++) { - free(array.ptrs[i]); - array.ptrs[i] = NULL; - } - array.count = save; - ptr_array_append(&array, parse_command_arg(&runner, name, end - pos, true)); - } else { - // Remove NULL - array.count--; - } - } else { - ptr_array_append(&array, parse_command_arg(&runner, name, end - pos, true)); - } - free(name); - } else { - ptr_array_append(&array, parse_command_arg(&runner, cmd + pos, end - pos, true)); - } - pos = end; - } - - const char *str = cmd + completion_pos; - size_t len = cmdline_pos - completion_pos; - if (is_var(str, len)) { - char *name = xstrslice(str, 1, len); - completion_pos++; - collect_env(e, &cs->completions, name); - collect_normal_vars(&cs->completions, name); - free(name); - } else { - cs->escaped = string_view(str, len); - cs->parsed = parse_command_arg(&runner, str, len, true); - cs->add_space_after_single_match = true; - size_t count = array.count; - char **args = count ? (char**)array.ptrs + 1 + semicolon : NULL; - size_t argc = count ? array.count - semicolon - 1 : 0; - collect_completions(e, args, argc); - } - - ptr_array_free(&array); - ptr_array_sort(&cs->completions, strptrcmp); - cs->orig = cmd; // (takes ownership) - cs->tail = strview_from_cstring(cmd + cmdline_pos); - cs->head_len = completion_pos; -} - -static void do_complete_command(CommandLine *cmdline) -{ - const CompletionState *cs = &cmdline->completion; - const PointerArray *arr = &cs->completions; - const StringView middle = strview_from_cstring(arr->ptrs[cs->idx]); - const StringView tail = cs->tail; - const size_t head_length = cs->head_len; - - String buf = string_new(head_length + tail.length + middle.length + 16); - string_append_buf(&buf, cs->orig, head_length); - string_append_escaped_arg_sv(&buf, middle, !cs->tilde_expanded); - - bool single_completion = (arr->count == 1); - if (single_completion && cs->add_space_after_single_match) { - string_append_byte(&buf, ' '); - } - - size_t pos = buf.len; - string_append_strview(&buf, &tail); - cmdline_set_text(cmdline, string_borrow_cstring(&buf)); - cmdline->pos = pos; - string_free(&buf); - - if (single_completion) { - reset_completion(cmdline); - } -} - -void complete_command_next(EditorState *e) -{ - CompletionState *cs = &e->cmdline.completion; - const bool init = !cs->orig; - if (init) { - init_completion(e, &e->cmdline); - } - size_t count = cs->completions.count; - if (!count) { - return; - } - if (!init) { - cs->idx = size_increment_wrapped(cs->idx, count); - } - do_complete_command(&e->cmdline); -} - -void complete_command_prev(EditorState *e) -{ - CompletionState *cs = &e->cmdline.completion; - const bool init = !cs->orig; - if (init) { - init_completion(e, &e->cmdline); - } - size_t count = cs->completions.count; - if (!count) { - return; - } - if (!init) { - cs->idx = size_decrement_wrapped(cs->idx, count); - } - do_complete_command(&e->cmdline); -} - -void reset_completion(CommandLine *cmdline) -{ - CompletionState *cs = &cmdline->completion; - free(cs->parsed); - free(cs->orig); - ptr_array_free(&cs->completions); - *cs = (CompletionState){.orig = NULL}; -} - -void collect_hashmap_keys(const HashMap *map, PointerArray *a, const char *prefix) -{ - for (HashMapIter it = hashmap_iter(map); hashmap_next(&it); ) { - const char *name = it.entry->key; - if (str_has_prefix(name, prefix)) { - ptr_array_append(a, xstrdup(name)); - } - } -} diff --git a/examples/dte/completion.h b/examples/dte/completion.h deleted file mode 100644 index 873e994..0000000 --- a/examples/dte/completion.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef COMPLETION_H -#define COMPLETION_H - -#include "cmdline.h" -#include "editor.h" -#include "util/hashmap.h" -#include "util/macros.h" -#include "util/ptr-array.h" - -void complete_command_next(EditorState *e) NONNULL_ARGS; -void complete_command_prev(EditorState *e) NONNULL_ARGS; -void reset_completion(CommandLine *cmdline) NONNULL_ARGS; - -void collect_env(EditorState *e, PointerArray *a, const char *prefix) NONNULL_ARGS; -void collect_normal_aliases(EditorState *e, PointerArray *a, const char *prefix) NONNULL_ARGS; -void collect_bound_normal_keys(EditorState *e, PointerArray *a, const char *keystr_prefix) NONNULL_ARGS; -void collect_hl_colors(EditorState *e, PointerArray *a, const char *prefix) NONNULL_ARGS; -void collect_compilers(EditorState *e, PointerArray *a, const char *prefix) NONNULL_ARGS; -void collect_hashmap_keys(const HashMap *map, PointerArray *a, const char *prefix) NONNULL_ARGS; - -#endif diff --git a/examples/dte/config.c b/examples/dte/config.c deleted file mode 100644 index dd24465..0000000 --- a/examples/dte/config.c +++ /dev/null @@ -1,185 +0,0 @@ -#include <errno.h> -#include <stdbool.h> -#include <stdlib.h> -#include <string.h> -#include <sys/types.h> -#include "config.h" -#include "commands.h" -#include "editor.h" -#include "error.h" -#include "syntax/color.h" -#include "util/debug.h" -#include "util/readfile.h" -#include "util/str-util.h" -#include "../build/builtin-config.h" - -ConfigState current_config; - -// Odd number of backslashes at end of line? -static bool has_line_continuation(StringView line) -{ - ssize_t pos = line.length - 1; - while (pos >= 0 && line.data[pos] == '\\') { - pos--; - } - return (line.length - 1 - pos) & 1; -} - -UNITTEST { - BUG_ON(has_line_continuation(string_view(NULL, 0))); - BUG_ON(has_line_continuation(strview_from_cstring("0"))); - BUG_ON(!has_line_continuation(strview_from_cstring("1 \\"))); - BUG_ON(has_line_continuation(strview_from_cstring("2 \\\\"))); - BUG_ON(!has_line_continuation(strview_from_cstring("3 \\\\\\"))); - BUG_ON(has_line_continuation(strview_from_cstring("4 \\\\\\\\"))); -} - -void exec_config(CommandRunner *runner, StringView config) -{ - String buf = string_new(1024); - - for (size_t i = 0, n = config.length; i < n; current_config.line++) { - StringView line = buf_slice_next_line(config.data, &i, n); - strview_trim_left(&line); - if (buf.len == 0 && strview_has_prefix(&line, "#")) { - // Comment line - continue; - } - if (has_line_continuation(line)) { - line.length--; - string_append_strview(&buf, &line); - } else { - string_append_strview(&buf, &line); - handle_command(runner, string_borrow_cstring(&buf)); - string_clear(&buf); - } - } - - if (unlikely(buf.len)) { - // This can only happen if the last line had a line continuation - handle_command(runner, string_borrow_cstring(&buf)); - } - - string_free(&buf); -} - -String dump_builtin_configs(void) -{ - String str = string_new(1024); - for (size_t i = 0; i < ARRAYLEN(builtin_configs); i++) { - string_append_cstring(&str, builtin_configs[i].name); - string_append_byte(&str, '\n'); - } - return str; -} - -const BuiltinConfig *get_builtin_config(const char *name) -{ - for (size_t i = 0; i < ARRAYLEN(builtin_configs); i++) { - if (streq(name, builtin_configs[i].name)) { - return &builtin_configs[i]; - } - } - return NULL; -} - -const BuiltinConfig *get_builtin_configs_array(size_t *nconfigs) -{ - *nconfigs = ARRAYLEN(builtin_configs); - return &builtin_configs[0]; -} - -int do_read_config(CommandRunner *runner, const char *filename, ConfigFlags flags) -{ - const bool must_exist = flags & CFG_MUST_EXIST; - const bool builtin = flags & CFG_BUILTIN; - - if (builtin) { - const BuiltinConfig *cfg = get_builtin_config(filename); - int err = 0; - if (cfg) { - current_config.file = filename; - current_config.line = 1; - exec_config(runner, cfg->text); - } else if (must_exist) { - error_msg ( - "Error reading '%s': no built-in config exists for that path", - filename - ); - err = 1; - } - return err; - } - - char *buf; - ssize_t size = read_file(filename, &buf); - if (size < 0) { - int err = errno; - if (err != ENOENT || must_exist) { - error_msg("Error reading %s: %s", filename, strerror(err)); - } - return err; - } - - current_config.file = filename; - current_config.line = 1; - exec_config(runner, string_view(buf, size)); - free(buf); - return 0; -} - -int read_config(CommandRunner *runner, const char *filename, ConfigFlags flags) -{ - // Recursive - const ConfigState saved = current_config; - int ret = do_read_config(runner, filename, flags); - current_config = saved; - return ret; -} - -void exec_builtin_color_reset(EditorState *e) -{ - clear_hl_colors(&e->colors); - const StringView reset = string_view(builtin_color_reset, sizeof(builtin_color_reset) - 1); - const ConfigState saved = current_config; - current_config.file = "color/reset"; - current_config.line = 1; - exec_normal_config(e, reset); - current_config = saved; -} - -void exec_builtin_rc(EditorState *e) -{ - exec_builtin_color_reset(e); - const StringView rc = string_view(builtin_rc, sizeof(builtin_rc) - 1); - const ConfigState saved = current_config; - current_config.file = "rc"; - current_config.line = 1; - exec_normal_config(e, rc); - current_config = saved; -} - -void collect_builtin_configs(PointerArray *a, const char *prefix) -{ - for (size_t i = 0; i < ARRAYLEN(builtin_configs); i++) { - const char *name = builtin_configs[i].name; - if (str_has_prefix(name, prefix)) { - ptr_array_append(a, xstrdup(name)); - } - } -} - -void collect_builtin_includes(PointerArray *a, const char *prefix) -{ - for (size_t i = 0; i < ARRAYLEN(builtin_configs); i++) { - const char *name = builtin_configs[i].name; - if (str_has_prefix(name, prefix) && !str_has_prefix(name, "syntax/")) { - ptr_array_append(a, xstrdup(name)); - } - } -} - -UNITTEST { - BUG_ON(!get_builtin_config("rc")); - BUG_ON(!get_builtin_config("color/reset")); -} diff --git a/examples/dte/config.h b/examples/dte/config.h deleted file mode 100644 index 43d4c97..0000000 --- a/examples/dte/config.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef CONFIG_H -#define CONFIG_H - -#include <stddef.h> -#include "command/run.h" -#include "util/macros.h" -#include "util/ptr-array.h" -#include "util/string-view.h" -#include "util/string.h" - -typedef enum { - CFG_NOFLAGS = 0, - CFG_MUST_EXIST = 1 << 0, - CFG_BUILTIN = 1 << 1 -} ConfigFlags; - -typedef struct { - const char *const name; - const StringView text; -} BuiltinConfig; - -typedef struct { - const char *file; - unsigned int line; -} ConfigState; - -extern ConfigState current_config; - -struct EditorState; - -String dump_builtin_configs(void); -const BuiltinConfig *get_builtin_config(const char *name) PURE; -const BuiltinConfig *get_builtin_configs_array(size_t *nconfigs); -void exec_config(CommandRunner *runner, StringView config); -int do_read_config(CommandRunner *runner, const char *filename, ConfigFlags flags) WARN_UNUSED_RESULT; -int read_config(CommandRunner *runner, const char *filename, ConfigFlags f); -void exec_builtin_color_reset(struct EditorState *e); -void exec_builtin_rc(struct EditorState *e); -void collect_builtin_configs(PointerArray *a, const char *prefix) NONNULL_ARGS; -void collect_builtin_includes(PointerArray *a, const char *prefix) NONNULL_ARGS; - -#endif diff --git a/examples/dte/convert.c b/examples/dte/convert.c deleted file mode 100644 index 2020ee9..0000000 --- a/examples/dte/convert.c +++ /dev/null @@ -1,581 +0,0 @@ -#include <errno.h> -#include <inttypes.h> -#include <stdlib.h> -#include <string.h> -#include "convert.h" -#include "util/debug.h" -#include "util/intern.h" -#include "util/log.h" -#include "util/str-util.h" -#include "util/utf8.h" -#include "util/xmalloc.h" -#include "util/xreadwrite.h" - -struct FileEncoder { - struct cconv *cconv; - unsigned char *nbuf; - size_t nsize; - bool crlf; - int fd; -}; - -struct FileDecoder { - const char *encoding; - const unsigned char *ibuf; - ssize_t ipos, isize; - struct cconv *cconv; - bool (*read_line)(struct FileDecoder *dec, const char **linep, size_t *lenp); -}; - -const char *file_decoder_get_encoding(const FileDecoder *dec) -{ - return dec->encoding; -} - -static bool read_utf8_line(FileDecoder *dec, const char **linep, size_t *lenp) -{ - const char *line = dec->ibuf + dec->ipos; - const char *nl = memchr(line, '\n', dec->isize - dec->ipos); - size_t len; - - if (nl) { - len = nl - line; - dec->ipos += len + 1; - } else { - len = dec->isize - dec->ipos; - if (len == 0) { - return false; - } - dec->ipos += len; - } - - *linep = line; - *lenp = len; - return true; -} - -static size_t unix_to_dos ( - FileEncoder *enc, - const unsigned char *buf, - size_t size -) { - if (enc->nsize < size * 2) { - enc->nsize = size * 2; - xrenew(enc->nbuf, enc->nsize); - } - size_t d = 0; - for (size_t s = 0; s < size; s++) { - unsigned char ch = buf[s]; - if (ch == '\n') { - enc->nbuf[d++] = '\r'; - } - enc->nbuf[d++] = ch; - } - return d; -} - -#ifdef ICONV_DISABLE // iconv not available; use basic, UTF-8 implementation: - -bool conversion_supported_by_iconv ( - const char* UNUSED_ARG(from), - const char* UNUSED_ARG(to) -) { - errno = EINVAL; - return false; -} - -FileEncoder *new_file_encoder(const Encoding *encoding, bool crlf, int fd) -{ - if (unlikely(encoding->type != UTF8)) { - errno = EINVAL; - return NULL; - } - FileEncoder *enc = xnew0(FileEncoder, 1); - enc->crlf = crlf; - enc->fd = fd; - return enc; -} - -void free_file_encoder(FileEncoder *enc) -{ - free(enc->nbuf); - free(enc); -} - -ssize_t file_encoder_write(FileEncoder *enc, const unsigned char *buf, size_t n) -{ - if (enc->crlf) { - n = unix_to_dos(enc, buf, n); - buf = enc->nbuf; - } - return xwrite_all(enc->fd, buf, n); -} - -size_t file_encoder_get_nr_errors(const FileEncoder* UNUSED_ARG(enc)) -{ - return 0; -} - -FileDecoder *new_file_decoder(const char *encoding, const unsigned char *buf, size_t n) -{ - if (unlikely(encoding && !streq(encoding, "UTF-8"))) { - errno = EINVAL; - return NULL; - } - FileDecoder *dec = xnew0(FileDecoder, 1); - dec->ibuf = buf; - dec->isize = n; - return dec; -} - -void free_file_decoder(FileDecoder *dec) -{ - free(dec); -} - -bool file_decoder_read_line(FileDecoder *dec, const char **linep, size_t *lenp) -{ - return read_utf8_line(dec, linep, lenp); -} - -#else // ICONV_DISABLE is undefined; use full iconv implementation: - -#include <iconv.h> - -static const unsigned char replacement[2] = "\xc2\xbf"; // U+00BF - -struct cconv { - iconv_t cd; - char *obuf; - size_t osize; - size_t opos; - size_t consumed; - size_t errors; - - // Temporary input buffer - char tbuf[16]; - size_t tcount; - - // Replacement character 0xBF (inverted question mark) - char rbuf[4]; - size_t rcount; - - // Input character size in bytes, or zero for UTF-8 - size_t char_size; -}; - -static struct cconv *create(iconv_t cd) -{ - struct cconv *c = xnew0(struct cconv, 1); - c->cd = cd; - c->osize = 8192; - c->obuf = xmalloc(c->osize); - return c; -} - -static size_t encoding_char_size(const char *encoding) -{ - if (str_has_prefix(encoding, "UTF-16")) { - return 2; - } - if (str_has_prefix(encoding, "UTF-32")) { - return 4; - } - return 1; -} - -static size_t iconv_wrapper ( - iconv_t cd, - const char **restrict inbuf, - size_t *restrict inbytesleft, - char **restrict outbuf, - size_t *restrict outbytesleft -) { - // POSIX defines the second parameter of iconv(3) as "char **restrict" - // but NetBSD declares it as "const char **restrict" -#ifdef __NetBSD__ - const char **restrict in = inbuf; -#else - char **restrict in = (char **restrict)inbuf; -#endif - - return iconv(cd, in, inbytesleft, outbuf, outbytesleft); -} - -static void encode_replacement(struct cconv *c) -{ - const char *ib = replacement; - char *ob = c->rbuf; - size_t ic = sizeof(replacement); - size_t oc = sizeof(c->rbuf); - size_t rc = iconv_wrapper(c->cd, &ib, &ic, &ob, &oc); - - if (rc == (size_t)-1) { - c->rbuf[0] = '\xbf'; - c->rcount = 1; - } else { - c->rcount = ob - c->rbuf; - } -} - -static void resize_obuf(struct cconv *c) -{ - c->osize *= 2; - xrenew(c->obuf, c->osize); -} - -static void add_replacement(struct cconv *c) -{ - if (c->osize - c->opos < 4) { - resize_obuf(c); - } - - memcpy(c->obuf + c->opos, c->rbuf, c->rcount); - c->opos += c->rcount; -} - -static size_t handle_invalid(struct cconv *c, const char *buf, size_t count) -{ - LOG_DEBUG("%zu %zu", c->char_size, count); - add_replacement(c); - if (c->char_size == 0) { - // Converting from UTF-8 - size_t idx = 0; - CodePoint u = u_get_char(buf, count, &idx); - LOG_DEBUG("U+%04" PRIX32, u); - return idx; - } - if (c->char_size > count) { - // wtf - return 1; - } - return c->char_size; -} - -static int xiconv(struct cconv *c, const char **ib, size_t *ic) -{ - while (1) { - char *ob = c->obuf + c->opos; - size_t oc = c->osize - c->opos; - size_t rc = iconv_wrapper(c->cd, ib, ic, &ob, &oc); - c->opos = ob - c->obuf; - if (rc == (size_t)-1) { - switch (errno) { - case EILSEQ: - c->errors++; - // Reset - iconv(c->cd, NULL, NULL, NULL, NULL); - return errno; - case EINVAL: - return errno; - case E2BIG: - resize_obuf(c); - continue; - default: - BUG("iconv: %s", strerror(errno)); - } - } else { - c->errors += rc; - } - return 0; - } -} - -static size_t convert_incomplete(struct cconv *c, const char *input, size_t len) -{ - size_t ipos = 0; - while (c->tcount < sizeof(c->tbuf) && ipos < len) { - c->tbuf[c->tcount++] = input[ipos++]; - const char *ib = c->tbuf; - size_t ic = c->tcount; - int rc = xiconv(c, &ib, &ic); - if (ic > 0) { - memmove(c->tbuf, ib, ic); - } - c->tcount = ic; - if (rc == EINVAL) { - // Incomplete character at end of input buffer; try again - // with more input data - continue; - } - if (rc == EILSEQ) { - // Invalid multibyte sequence - size_t skip = handle_invalid(c, c->tbuf, c->tcount); - c->tcount -= skip; - if (c->tcount > 0) { - LOG_DEBUG("tcount=%zu, skip=%zu", c->tcount, skip); - memmove(c->tbuf, c->tbuf + skip, c->tcount); - continue; - } - return ipos; - } - break; - } - - LOG_DEBUG("%zu %zu", ipos, c->tcount); - return ipos; -} - -static void cconv_process(struct cconv *c, const char *input, size_t len) -{ - if (c->consumed > 0) { - size_t fill = c->opos - c->consumed; - memmove(c->obuf, c->obuf + c->consumed, fill); - c->opos = fill; - c->consumed = 0; - } - - if (c->tcount > 0) { - size_t ipos = convert_incomplete(c, input, len); - input += ipos; - len -= ipos; - } - - const char *ib = input; - for (size_t ic = len; ic > 0; ) { - int r = xiconv(c, &ib, &ic); - if (r == EINVAL) { - // Incomplete character at end of input buffer - if (ic < sizeof(c->tbuf)) { - memcpy(c->tbuf, ib, ic); - c->tcount = ic; - } else { - // FIXME - } - ic = 0; - continue; - } - if (r == EILSEQ) { - // Invalid multibyte sequence - size_t skip = handle_invalid(c, ib, ic); - ic -= skip; - ib += skip; - continue; - } - } -} - -static struct cconv *cconv_to_utf8(const char *encoding) -{ - iconv_t cd = iconv_open("UTF-8", encoding); - if (cd == (iconv_t)-1) { - return NULL; - } - struct cconv *c = create(cd); - memcpy(c->rbuf, replacement, sizeof(replacement)); - c->rcount = sizeof(replacement); - c->char_size = encoding_char_size(encoding); - return c; -} - -static struct cconv *cconv_from_utf8(const char *encoding) -{ - iconv_t cd = iconv_open(encoding, "UTF-8"); - if (cd == (iconv_t)-1) { - return NULL; - } - struct cconv *c = create(cd); - encode_replacement(c); - return c; -} - -static void cconv_flush(struct cconv *c) -{ - if (c->tcount > 0) { - // Replace incomplete character at end of input buffer - LOG_DEBUG("incomplete character at EOF"); - add_replacement(c); - c->tcount = 0; - } -} - -static char *cconv_consume_line(struct cconv *c, size_t *len) -{ - char *line = c->obuf + c->consumed; - char *nl = memchr(line, '\n', c->opos - c->consumed); - if (!nl) { - *len = 0; - return NULL; - } - - size_t n = nl - line + 1; - c->consumed += n; - *len = n; - return line; -} - -static char *cconv_consume_all(struct cconv *c, size_t *len) -{ - char *buf = c->obuf + c->consumed; - *len = c->opos - c->consumed; - c->consumed = c->opos; - return buf; -} - -static void cconv_free(struct cconv *c) -{ - iconv_close(c->cd); - free(c->obuf); - free(c); -} - -bool conversion_supported_by_iconv(const char *from, const char *to) -{ - if (unlikely(from[0] == '\0' || to[0] == '\0')) { - errno = EINVAL; - return false; - } - - iconv_t cd = iconv_open(to, from); - if (cd == (iconv_t)-1) { - return false; - } - - iconv_close(cd); - return true; -} - -FileEncoder *new_file_encoder(const Encoding *encoding, bool crlf, int fd) -{ - FileEncoder *enc = xnew0(FileEncoder, 1); - enc->crlf = crlf; - enc->fd = fd; - - if (encoding->type != UTF8) { - enc->cconv = cconv_from_utf8(encoding->name); - if (!enc->cconv) { - free(enc); - return NULL; - } - } - - return enc; -} - -void free_file_encoder(FileEncoder *enc) -{ - if (enc->cconv) { - cconv_free(enc->cconv); - } - free(enc->nbuf); - free(enc); -} - -// NOTE: buf must contain whole characters! -ssize_t file_encoder_write ( - FileEncoder *enc, - const unsigned char *buf, - size_t size -) { - if (enc->crlf) { - size = unix_to_dos(enc, buf, size); - buf = enc->nbuf; - } - if (enc->cconv) { - cconv_process(enc->cconv, buf, size); - cconv_flush(enc->cconv); - buf = cconv_consume_all(enc->cconv, &size); - } - return xwrite_all(enc->fd, buf, size); -} - -size_t file_encoder_get_nr_errors(const FileEncoder *enc) -{ - return enc->cconv ? enc->cconv->errors : 0; -} - -static bool fill(FileDecoder *dec) -{ - if (dec->ipos == dec->isize) { - return false; - } - - // Smaller than cconv.obuf to make realloc less likely - size_t max = 7 * 1024; - - size_t icount = MIN(dec->isize - dec->ipos, max); - cconv_process(dec->cconv, dec->ibuf + dec->ipos, icount); - dec->ipos += icount; - if (dec->ipos == dec->isize) { - // Must be flushed after all input has been fed - cconv_flush(dec->cconv); - } - return true; -} - -static bool decode_and_read_line(FileDecoder *dec, const char **linep, size_t *lenp) -{ - char *line; - size_t len; - while (1) { - line = cconv_consume_line(dec->cconv, &len); - if (line || !fill(dec)) { - break; - } - } - - if (line) { - // Newline not wanted - len--; - } else { - line = cconv_consume_all(dec->cconv, &len); - if (len == 0) { - return false; - } - } - - *linep = line; - *lenp = len; - return true; -} - -static bool set_encoding(FileDecoder *dec, const char *encoding) -{ - if (strcmp(encoding, "UTF-8") == 0) { - dec->read_line = read_utf8_line; - } else { - dec->cconv = cconv_to_utf8(encoding); - if (!dec->cconv) { - return false; - } - dec->read_line = decode_and_read_line; - } - dec->encoding = str_intern(encoding); - return true; -} - -FileDecoder *new_file_decoder ( - const char *encoding, - const unsigned char *buf, - size_t size -) { - FileDecoder *dec = xnew0(FileDecoder, 1); - dec->ibuf = buf; - dec->isize = size; - - if (!encoding) { - encoding = "UTF-8"; - } - - if (!set_encoding(dec, encoding)) { - free_file_decoder(dec); - return NULL; - } - - return dec; -} - -void free_file_decoder(FileDecoder *dec) -{ - if (dec->cconv) { - cconv_free(dec->cconv); - } - free(dec); -} - -bool file_decoder_read_line(FileDecoder *dec, const char **linep, size_t *lenp) -{ - return dec->read_line(dec, linep, lenp); -} - -#endif diff --git a/examples/dte/convert.h b/examples/dte/convert.h deleted file mode 100644 index 306609e..0000000 --- a/examples/dte/convert.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef ENCODING_CONVERT_H -#define ENCODING_CONVERT_H - -#include <stdbool.h> -#include <sys/types.h> -#include "encoding.h" -#include "util/macros.h" - -typedef struct FileDecoder FileDecoder; -typedef struct FileEncoder FileEncoder; - -bool conversion_supported_by_iconv(const char *from, const char *to) NONNULL_ARGS; - -FileDecoder *new_file_decoder(const char *encoding, const unsigned char *buf, size_t size); -void free_file_decoder(FileDecoder *dec); -bool file_decoder_read_line(FileDecoder *dec, const char **line, size_t *len) NONNULL_ARGS WARN_UNUSED_RESULT; -const char *file_decoder_get_encoding(const FileDecoder *dec) NONNULL_ARGS; - -FileEncoder *new_file_encoder(const Encoding *encoding, bool crlf, int fd) NONNULL_ARGS; -void free_file_encoder(FileEncoder *enc) NONNULL_ARGS; -ssize_t file_encoder_write(FileEncoder *enc, const unsigned char *buf, size_t size) NONNULL_ARGS WARN_UNUSED_RESULT; -size_t file_encoder_get_nr_errors(const FileEncoder *enc) NONNULL_ARGS; - -#endif diff --git a/examples/dte/copy.c b/examples/dte/copy.c deleted file mode 100644 index c3b989e..0000000 --- a/examples/dte/copy.c +++ /dev/null @@ -1,74 +0,0 @@ -#include <stdlib.h> -#include "copy.h" -#include "block-iter.h" -#include "change.h" -#include "misc.h" -#include "move.h" -#include "selection.h" -#include "util/debug.h" - -void record_copy(Clipboard *clip, char *buf, size_t len, bool is_lines) -{ - BUG_ON(len && !buf); - free(clip->buf); - clip->buf = buf; - clip->len = len; - clip->is_lines = is_lines; -} - -void copy(Clipboard *clip, View *view, size_t len, bool is_lines) -{ - if (len) { - char *buf = block_iter_get_bytes(&view->cursor, len); - record_copy(clip, buf, len, is_lines); - } -} - -void cut(Clipboard *clip, View *view, size_t len, bool is_lines) -{ - if (len) { - copy(clip, view, len, is_lines); - buffer_delete_bytes(view, len); - } -} - -void paste(Clipboard *clip, View *view, PasteLinesType type, bool move_after) -{ - if (clip->len == 0) { - return; - } - - BUG_ON(!clip->buf); - if (!clip->is_lines || type == PASTE_LINES_INLINE) { - insert_text(view, clip->buf, clip->len, move_after); - return; - } - - size_t del_count = 0; - if (view->selection) { - del_count = prepare_selection(view); - unselect(view); - } - - const long x = view_get_preferred_x(view); - if (!del_count) { - if (type == PASTE_LINES_BELOW_CURSOR) { - block_iter_eat_line(&view->cursor); - } else { - BUG_ON(type != PASTE_LINES_ABOVE_CURSOR); - block_iter_bol(&view->cursor); - } - } - - buffer_replace_bytes(view, del_count, clip->buf, clip->len); - - if (move_after) { - block_iter_skip_bytes(&view->cursor, clip->len); - } else { - // Try to keep cursor column - move_to_preferred_x(view, x); - } - - // New preferred_x - view_reset_preferred_x(view); -} diff --git a/examples/dte/copy.h b/examples/dte/copy.h deleted file mode 100644 index 2281b09..0000000 --- a/examples/dte/copy.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef COPY_H -#define COPY_H - -#include <stdbool.h> -#include <stddef.h> -#include "util/macros.h" -#include "view.h" - -typedef struct { - char *buf; - size_t len; - bool is_lines; -} Clipboard; - -typedef enum { - PASTE_LINES_BELOW_CURSOR, - PASTE_LINES_ABOVE_CURSOR, - PASTE_LINES_INLINE, -} PasteLinesType; - -void record_copy(Clipboard *clip, char *buf, size_t len, bool is_lines); -void copy(Clipboard *clip, View *view, size_t len, bool is_lines); -void cut(Clipboard *clip, View *view, size_t len, bool is_lines); -void paste(Clipboard *clip, View *view, PasteLinesType type, bool move_after); - -#endif diff --git a/examples/dte/ctags.c b/examples/dte/ctags.c deleted file mode 100644 index 6035630..0000000 --- a/examples/dte/ctags.c +++ /dev/null @@ -1,157 +0,0 @@ -#include <stdlib.h> -#include <string.h> -#include "ctags.h" -#include "util/ascii.h" -#include "util/debug.h" -#include "util/str-util.h" -#include "util/strtonum.h" -#include "util/xmalloc.h" - -static size_t parse_ex_pattern(const char *buf, size_t size, char **escaped) -{ - BUG_ON(size == 0); - BUG_ON(buf[0] != '/' && buf[0] != '?'); - - // The search pattern is not a real regular expression; special characters - // need to be escaped - char *pattern = xmalloc(size * 2); - char open_delim = buf[0]; - for (size_t i = 1, j = 0; i < size; i++) { - if (unlikely(buf[i] == '\0')) { - break; - } - if (buf[i] == '\\' && i + 1 < size) { - i++; - if (buf[i] == '\\') { - pattern[j++] = '\\'; - } - pattern[j++] = buf[i]; - continue; - } - if (buf[i] == open_delim) { - pattern[j] = '\0'; - *escaped = pattern; - return i + 1; - } - char c = buf[i]; - if (c == '*' || c == '[' || c == ']') { - pattern[j++] = '\\'; - } - pattern[j++] = buf[i]; - } - - free(pattern); - return 0; -} - -static size_t parse_ex_cmd(Tag *tag, const char *buf, size_t size) -{ - if (unlikely(size == 0)) { - return 0; - } - - size_t n; - if (buf[0] == '/' || buf[0] == '?') { - n = parse_ex_pattern(buf, size, &tag->pattern); - } else { - n = buf_parse_ulong(buf, size, &tag->lineno); - } - - if (n == 0) { - return 0; - } - - if (n + 1 < size && buf[n] == ';' && buf[n + 1] == '"') { - n += 2; - } - - return n; -} - -bool parse_ctags_line(Tag *tag, const char *line, size_t line_len) -{ - size_t pos = 0; - *tag = (Tag){.name = get_delim(line, &pos, line_len, '\t')}; - if (tag->name.length == 0 || pos >= line_len) { - return false; - } - - tag->filename = get_delim(line, &pos, line_len, '\t'); - if (tag->filename.length == 0 || pos >= line_len) { - return false; - } - - size_t len = parse_ex_cmd(tag, line + pos, line_len - pos); - if (len == 0) { - BUG_ON(tag->pattern); - return false; - } - - pos += len; - if (pos >= line_len) { - return true; - } - - /* - * Extension fields (key:[value]): - * - * file: visibility limited to this file - * struct:NAME tag is member of struct NAME - * union:NAME tag is member of union NAME - * typeref:struct:NAME::MEMBER_TYPE MEMBER_TYPE is type of the tag - */ - if (line[pos++] != '\t') { - // free `pattern` allocated by parse_ex_cmd() - free_tag(tag); - tag->pattern = NULL; - return false; - } - - while (pos < line_len) { - StringView field = get_delim(line, &pos, line_len, '\t'); - if (field.length == 1 && ascii_isalpha(field.data[0])) { - tag->kind = field.data[0]; - } else if (strview_equal_cstring(&field, "file:")) { - tag->local = true; - } - // TODO: struct/union/typeref - } - - return true; -} - -bool next_tag ( - const char *buf, - size_t buf_len, - size_t *posp, - const StringView *prefix, - bool exact, - Tag *tag -) { - const char *p = prefix->data; - size_t plen = prefix->length; - for (size_t pos = *posp; pos < buf_len; ) { - StringView line = buf_slice_next_line(buf, &pos, buf_len); - if (line.length == 0 || line.data[0] == '!') { - continue; - } - if (!strview_has_strn_prefix(&line, p, plen)) { - continue; - } - if (exact && line.data[plen] != '\t') { - continue; - } - if (!parse_ctags_line(tag, line.data, line.length)) { - continue; - } - *posp = pos; - return true; - } - return false; -} - -// NOTE: tag itself is not freed -void free_tag(Tag *tag) -{ - free(tag->pattern); -} diff --git a/examples/dte/ctags.h b/examples/dte/ctags.h deleted file mode 100644 index 4f22ba6..0000000 --- a/examples/dte/ctags.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef CTAGS_H -#define CTAGS_H - -#include <stdbool.h> -#include <stddef.h> -#include "util/macros.h" -#include "util/string-view.h" - -typedef struct { - StringView name; // Name of tag (points into TagFile::buf) - StringView filename; // File containing tag (points into TagFile::buf) - char *pattern; // Regex pattern used to locate tag (escaped ex command) - unsigned long lineno; // Line number in file (mutually exclusive with pattern) - char kind; // ASCII letter representing type of tag (e.g. f=function) - bool local; // Indicates if tag is local to file (e.g. "static" in C) -} Tag; - -NONNULL_ARGS WARN_UNUSED_RESULT -bool next_tag ( - const char *buf, - size_t buf_len, - size_t *posp, - const StringView *prefix, - bool exact, - Tag *t -); - -bool parse_ctags_line(Tag *t, const char *line, size_t line_len) NONNULL_ARG(1); -void free_tag(Tag *t) NONNULL_ARGS; - -#endif diff --git a/examples/dte/edit.c b/examples/dte/edit.c deleted file mode 100644 index 337a1e9..0000000 --- a/examples/dte/edit.c +++ /dev/null @@ -1,394 +0,0 @@ -#include <string.h> -#include "edit.h" -#include "block.h" -#include "buffer.h" -#include "syntax/highlight.h" -#include "util/debug.h" -#include "util/list.h" -#include "util/xmalloc.h" - -enum { - BLOCK_EDIT_SIZE = 512 -}; - -static void sanity_check_blocks(const View *view, bool check_newlines) -{ -#if DEBUG >= 1 - const Buffer *buffer = view->buffer; - BUG_ON(list_empty(&buffer->blocks)); - BUG_ON(view->cursor.offset > view->cursor.blk->size); - - const Block *blk = BLOCK(buffer->blocks.next); - if (blk->size == 0) { - // The only time a zero-sized block is valid is when it's the - // first and only block - BUG_ON(buffer->blocks.next->next != &buffer->blocks); - BUG_ON(view->cursor.blk != blk); - return; - } - - bool cursor_seen = false; - block_for_each(blk, &buffer->blocks) { - const size_t size = blk->size; - BUG_ON(size == 0); - BUG_ON(size > blk->alloc); - if (blk == view->cursor.blk) { - cursor_seen = true; - } - if (check_newlines) { - BUG_ON(blk->data[size - 1] != '\n'); - } - if (DEBUG > 2) { - BUG_ON(count_nl(blk->data, size) != blk->nl); - } - } - BUG_ON(!cursor_seen); -#else - // Silence "unused parameter" warnings - (void)view; - (void)check_newlines; -#endif -} - -static size_t copy_count_nl(char *dst, const char *src, size_t len) -{ - size_t nl = 0; - for (size_t i = 0; i < len; i++) { - dst[i] = src[i]; - if (src[i] == '\n') { - nl++; - } - } - return nl; -} - -static size_t insert_to_current(BlockIter *cursor, const char *buf, size_t len) -{ - Block *blk = cursor->blk; - size_t offset = cursor->offset; - size_t size = blk->size + len; - - if (size > blk->alloc) { - blk->alloc = round_size_to_next_multiple(size, BLOCK_ALLOC_MULTIPLE); - xrenew(blk->data, blk->alloc); - } - memmove(blk->data + offset + len, blk->data + offset, blk->size - offset); - size_t nl = copy_count_nl(blk->data + offset, buf, len); - blk->nl += nl; - blk->size = size; - return nl; -} - -/* - * Combine current block and new data into smaller blocks: - * - Block _must_ contain whole lines - * - Block _must_ contain at least one line - * - Preferred maximum size of block is BLOCK_EDIT_SIZE - * - Size of any block can be larger than BLOCK_EDIT_SIZE - * only if there's a very long line - */ -static size_t split_and_insert(BlockIter *cursor, const char *buf, size_t len) -{ - Block *blk = cursor->blk; - ListHead *prev_node = blk->node.prev; - const char *buf1 = blk->data; - const char *buf2 = buf; - const char *buf3 = blk->data + cursor->offset; - size_t size1 = cursor->offset; - size_t size2 = len; - size_t size3 = blk->size - size1; - size_t total = size1 + size2 + size3; - size_t start = 0; // Beginning of new block - size_t size = 0; // Size of new block - size_t pos = 0; // Current position - size_t nl_added = 0; - - while (start < total) { - // Size of new block if next line would be added - size_t new_size = 0; - size_t copied = 0; - - if (pos < size1) { - const char *nl = memchr(buf1 + pos, '\n', size1 - pos); - if (nl) { - new_size = nl - buf1 + 1 - start; - } - } - - if (!new_size && pos < size1 + size2) { - size_t offset = 0; - if (pos > size1) { - offset = pos - size1; - } - - const char *nl = memchr(buf2 + offset, '\n', size2 - offset); - if (nl) { - new_size = size1 + nl - buf2 + 1 - start; - } - } - - if (!new_size && pos < total) { - size_t offset = 0; - if (pos > size1 + size2) { - offset = pos - size1 - size2; - } - - const char *nl = memchr(buf3 + offset, '\n', size3 - offset); - if (nl) { - new_size = size1 + size2 + nl - buf3 + 1 - start; - } else { - new_size = total - start; - } - } - - if (new_size <= BLOCK_EDIT_SIZE) { - // Fits - size = new_size; - pos = start + new_size; - if (pos < total) { - continue; - } - } else { - // Does not fit - if (!size) { - // One block containing one very long line - size = new_size; - pos = start + new_size; - } - } - - BUG_ON(!size); - Block *new = block_new(size); - if (start < size1) { - size_t avail = size1 - start; - size_t count = MIN(size, avail); - new->nl += copy_count_nl(new->data, buf1 + start, count); - copied += count; - start += count; - } - if (start >= size1 && start < size1 + size2) { - size_t offset = start - size1; - size_t avail = size2 - offset; - size_t count = MIN(size - copied, avail); - new->nl += copy_count_nl(new->data + copied, buf2 + offset, count); - copied += count; - start += count; - } - if (start >= size1 + size2) { - size_t offset = start - size1 - size2; - size_t avail = size3 - offset; - size_t count = size - copied; - BUG_ON(count > avail); - new->nl += copy_count_nl(new->data + copied, buf3 + offset, count); - copied += count; - start += count; - } - - new->size = size; - BUG_ON(copied != size); - list_add_before(&new->node, &blk->node); - - nl_added += new->nl; - size = 0; - } - - cursor->blk = BLOCK(prev_node->next); - while (cursor->offset > cursor->blk->size) { - cursor->offset -= cursor->blk->size; - cursor->blk = BLOCK(cursor->blk->node.next); - } - - nl_added -= blk->nl; - block_free(blk); - return nl_added; -} - -static size_t insert_bytes(BlockIter *cursor, const char *buf, size_t len) -{ - // Blocks must contain whole lines. - // Last char of buf might not be newline. - block_iter_normalize(cursor); - - Block *blk = cursor->blk; - size_t new_size = blk->size + len; - if (new_size <= blk->alloc || new_size <= BLOCK_EDIT_SIZE) { - return insert_to_current(cursor, buf, len); - } - - if (blk->nl <= 1 && !memchr(buf, '\n', len)) { - // Can't split this possibly very long line. - // insert_to_current() is much faster than split_and_insert(). - return insert_to_current(cursor, buf, len); - } - return split_and_insert(cursor, buf, len); -} - -void do_insert(View *view, const char *buf, size_t len) -{ - Buffer *buffer = view->buffer; - size_t nl = insert_bytes(&view->cursor, buf, len); - buffer->nl += nl; - sanity_check_blocks(view, true); - - view_update_cursor_y(view); - buffer_mark_lines_changed(buffer, view->cy, nl ? LONG_MAX : view->cy); - if (buffer->syn) { - hl_insert(buffer, view->cy, nl); - } -} - -static bool only_block(const Buffer *buffer, const Block *blk) -{ - return blk->node.prev == &buffer->blocks && blk->node.next == &buffer->blocks; -} - -char *do_delete(View *view, size_t len, bool sanity_check_newlines) -{ - ListHead *saved_prev_node = NULL; - Block *blk = view->cursor.blk; - size_t offset = view->cursor.offset; - size_t pos = 0; - size_t deleted_nl = 0; - - if (!len) { - return NULL; - } - - if (!offset) { - // The block where cursor is can become empty and thereby may be deleted - saved_prev_node = blk->node.prev; - } - - Buffer *buffer = view->buffer; - char *deleted = xmalloc(len); - while (pos < len) { - ListHead *next = blk->node.next; - size_t avail = blk->size - offset; - size_t count = MIN(len - pos, avail); - size_t nl = copy_count_nl(deleted + pos, blk->data + offset, count); - if (count < avail) { - memmove ( - blk->data + offset, - blk->data + offset + count, - avail - count - ); - } - - deleted_nl += nl; - buffer->nl -= nl; - blk->nl -= nl; - blk->size -= count; - if (!blk->size && !only_block(buffer, blk)) { - block_free(blk); - } - - offset = 0; - pos += count; - blk = BLOCK(next); - - BUG_ON(pos < len && next == &buffer->blocks); - } - - if (saved_prev_node) { - // Cursor was at beginning of a block that was possibly deleted - if (saved_prev_node->next == &buffer->blocks) { - view->cursor.blk = BLOCK(saved_prev_node); - view->cursor.offset = view->cursor.blk->size; - } else { - view->cursor.blk = BLOCK(saved_prev_node->next); - } - } - - blk = view->cursor.blk; - if ( - blk->size - && blk->data[blk->size - 1] != '\n' - && blk->node.next != &buffer->blocks - ) { - Block *next = BLOCK(blk->node.next); - size_t size = blk->size + next->size; - - if (size > blk->alloc) { - blk->alloc = round_size_to_next_multiple(size, BLOCK_ALLOC_MULTIPLE); - xrenew(blk->data, blk->alloc); - } - memcpy(blk->data + blk->size, next->data, next->size); - blk->size = size; - blk->nl += next->nl; - block_free(next); - } - - sanity_check_blocks(view, sanity_check_newlines); - - view_update_cursor_y(view); - buffer_mark_lines_changed(buffer, view->cy, deleted_nl ? LONG_MAX : view->cy); - if (buffer->syn) { - hl_delete(buffer, view->cy, deleted_nl); - } - return deleted; -} - -char *do_replace(View *view, size_t del, const char *buf, size_t ins) -{ - block_iter_normalize(&view->cursor); - Block *blk = view->cursor.blk; - size_t offset = view->cursor.offset; - - size_t avail = blk->size - offset; - if (del >= avail) { - goto slow; - } - - size_t new_size = blk->size + ins - del; - if (new_size > BLOCK_EDIT_SIZE) { - // Should split - if (blk->nl > 1 || memchr(buf, '\n', ins)) { - // Most likely can be split - goto slow; - } - } - - if (new_size > blk->alloc) { - blk->alloc = round_size_to_next_multiple(new_size, BLOCK_ALLOC_MULTIPLE); - xrenew(blk->data, blk->alloc); - } - - // Modification is limited to one block - Buffer *buffer = view->buffer; - char *ptr = blk->data + offset; - char *deleted = xmalloc(del); - size_t del_nl = copy_count_nl(deleted, ptr, del); - blk->nl -= del_nl; - buffer->nl -= del_nl; - - if (del != ins) { - memmove(ptr + ins, ptr + del, avail - del); - } - - size_t ins_nl = copy_count_nl(ptr, buf, ins); - blk->nl += ins_nl; - buffer->nl += ins_nl; - blk->size = new_size; - sanity_check_blocks(view, true); - view_update_cursor_y(view); - - // If the number of inserted and removed bytes are the same, some - // line(s) changed but the lines after them didn't move up or down - long max = (del_nl == ins_nl) ? view->cy + del_nl : LONG_MAX; - buffer_mark_lines_changed(buffer, view->cy, max); - - if (buffer->syn) { - hl_delete(buffer, view->cy, del_nl); - hl_insert(buffer, view->cy, ins_nl); - } - - return deleted; - -slow: - // The "sanity_check_newlines" argument of do_delete() is false here - // because it may be removing a terminating newline that do_insert() - // is going to insert again at a different position: - deleted = do_delete(view, del, false); - do_insert(view, buf, ins); - return deleted; -} diff --git a/examples/dte/edit.h b/examples/dte/edit.h deleted file mode 100644 index 2de58b8..0000000 --- a/examples/dte/edit.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef EDIT_H -#define EDIT_H - -#include <stdbool.h> -#include <stddef.h> -#include "util/macros.h" -#include "view.h" - -void do_insert(View *view, const char *buf, size_t len) NONNULL_ARG(1); -char *do_delete(View *view, size_t len, bool sanity_check_newlines) NONNULL_ARGS; -char *do_replace(View *view, size_t del, const char *buf, size_t ins) NONNULL_ARGS_AND_RETURN; - -#endif diff --git a/examples/dte/editor.c b/examples/dte/editor.c deleted file mode 100644 index aa88d6e..0000000 --- a/examples/dte/editor.c +++ /dev/null @@ -1,321 +0,0 @@ -#include "compat.h" -#include <errno.h> -#include <langinfo.h> -#include <locale.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include "editor.h" -#include "bind.h" -#include "bookmark.h" -#include "command/macro.h" -#include "commands.h" -#include "compiler.h" -#include "encoding.h" -#include "error.h" -#include "file-option.h" -#include "filetype.h" -#include "lock.h" -#include "mode.h" -#include "regexp.h" -#include "screen.h" -#include "search.h" -#include "signals.h" -#include "syntax/syntax.h" -#include "tag.h" -#include "terminal/input.h" -#include "terminal/mode.h" -#include "terminal/output.h" -#include "terminal/style.h" -#include "util/ascii.h" -#include "util/debug.h" -#include "util/exitcode.h" -#include "util/intern.h" -#include "util/log.h" -#include "util/utf8.h" -#include "util/xmalloc.h" -#include "util/xstdio.h" -#include "window.h" -#include "../build/version.h" - -static void set_and_check_locale(void) -{ - const char *default_locale = setlocale(LC_CTYPE, ""); - if (likely(default_locale)) { - const char *codeset = nl_langinfo(CODESET); - LOG_INFO("locale: %s (codeset: %s)", default_locale, codeset); - if (likely(lookup_encoding(codeset) == UTF8)) { - return; - } - } else { - LOG_ERROR("failed to set default locale"); - } - - static const char fallbacks[][12] = {"C.UTF-8", "en_US.UTF-8"}; - const char *fallback = NULL; - for (size_t i = 0; i < ARRAYLEN(fallbacks) && !fallback; i++) { - fallback = setlocale(LC_CTYPE, fallbacks[i]); - } - if (fallback) { - LOG_INFO("using fallback locale for LC_CTYPE: %s", fallback); - return; - } - - LOG_ERROR("no UTF-8 fallback locales found"); - fputs("setlocale() failed\n", stderr); - exit(EX_CONFIG); -} - -EditorState *init_editor_state(void) -{ - EditorState *e = xnew(EditorState, 1); - *e = (EditorState) { - .status = EDITOR_INITIALIZING, - .input_mode = INPUT_NORMAL, - .version = VERSION, - .command_history = { - .max_entries = 512, - }, - .search_history = { - .max_entries = 128, - }, - .cursor_styles = { - [CURSOR_MODE_DEFAULT] = {.type = CURSOR_DEFAULT, .color = COLOR_DEFAULT}, - [CURSOR_MODE_INSERT] = {.type = CURSOR_KEEP, .color = COLOR_KEEP}, - [CURSOR_MODE_OVERWRITE] = {.type = CURSOR_KEEP, .color = COLOR_KEEP}, - [CURSOR_MODE_CMDLINE] = {.type = CURSOR_KEEP, .color = COLOR_KEEP}, - }, - .modes = { - [INPUT_NORMAL] = {.cmds = &normal_commands}, - [INPUT_COMMAND] = {.cmds = &cmd_mode_commands}, - [INPUT_SEARCH] = {.cmds = &search_mode_commands}, - }, - .options = { - .auto_indent = true, - .detect_indent = 0, - .editorconfig = false, - .emulate_tab = false, - .expand_tab = false, - .file_history = true, - .indent_width = 8, - .overwrite = false, - .save_unmodified = SAVE_FULL, - .syntax = true, - .tab_width = 8, - .text_width = 72, - .ws_error = WSE_SPECIAL, - - // Global-only options - .case_sensitive_search = CSS_TRUE, - .crlf_newlines = false, - .display_special = false, - .esc_timeout = 100, - .filesize_limit = 250, - .lock_files = true, - .optimize_true_color = true, - .scroll_margin = 0, - .select_cursor_char = true, - .set_window_title = false, - .show_line_numbers = false, - .statusline_left = str_intern(" %f%s%m%s%r%s%M"), - .statusline_right = str_intern(" %y,%X %u %o %E%s%b%s%n %t %p "), - .tab_bar = true, - .utf8_bom = false, - } - }; - - sanity_check_global_options(&e->options); - - for (size_t i = 0; i < ARRAYLEN(e->modes); i++) { - const CommandSet *cmds = e->modes[i].cmds; - BUG_ON(!cmds); - BUG_ON(!cmds->lookup); - } - - const char *home = getenv("HOME"); - const char *dte_home = getenv("DTE_HOME"); - e->home_dir = strview_intern(home ? home : ""); - if (dte_home) { - e->user_config_dir = xstrdup(dte_home); - } else { - e->user_config_dir = xasprintf("%s/.dte", e->home_dir.data); - } - - LOG_INFO("dte version: " VERSION); - LOG_INFO("features:%s", feature_string); - - pid_t pid = getpid(); - bool leader = pid == getsid(0); - e->session_leader = leader; - LOG_INFO("pid: %jd%s", (intmax_t)pid, leader ? " (session leader)" : ""); - - pid_t pgid = getpgrp(); - if (pgid != pid) { - LOG_INFO("pgid: %jd", (intmax_t)pgid); - } - - set_and_check_locale(); - init_file_locks_context(e->user_config_dir, pid); - - // Allow child processes to detect that they're running under dte - if (unlikely(setenv("DTE_VERSION", VERSION, true) != 0)) { - fatal_error("setenv", errno); - } - - RegexpWordBoundaryTokens *wb = &e->regexp_word_tokens; - if (regexp_init_word_boundary_tokens(wb)) { - LOG_INFO("regex word boundary tokens detected: %s %s", wb->start, wb->end); - } else { - LOG_WARNING("no regex word boundary tokens detected"); - } - - term_input_init(&e->terminal.ibuf); - term_output_init(&e->terminal.obuf); - hashmap_init(&e->aliases, 32); - intmap_init(&e->modes[INPUT_NORMAL].key_bindings, 150); - intmap_init(&e->modes[INPUT_COMMAND].key_bindings, 40); - intmap_init(&e->modes[INPUT_SEARCH].key_bindings, 40); - return e; -} - -void free_editor_state(EditorState *e) -{ - free(e->clipboard.buf); - free_file_options(&e->file_options); - free_filetypes(&e->filetypes); - free_syntaxes(&e->syntaxes); - file_history_free(&e->file_history); - history_free(&e->command_history); - history_free(&e->search_history); - search_free_regexp(&e->search); - term_output_free(&e->terminal.obuf); - term_input_free(&e->terminal.ibuf); - cmdline_free(&e->cmdline); - clear_messages(&e->messages); - free_macro(&e->macro); - tag_file_free(&e->tagfile); - - ptr_array_free_cb(&e->bookmarks, FREE_FUNC(file_location_free)); - ptr_array_free_cb(&e->buffers, FREE_FUNC(free_buffer)); - hashmap_free(&e->compilers, FREE_FUNC(free_compiler)); - hashmap_free(&e->colors.other, free); - hashmap_free(&e->aliases, free); - - for (size_t i = 0; i < ARRAYLEN(e->modes); i++) { - free_bindings(&e->modes[i].key_bindings); - } - - free_interned_strings(); - free_interned_regexps(); - - // TODO: intern this (so that it's freed by free_intern_pool()) - free((void*)e->user_config_dir); - - free(e); -} - -static void sanity_check(const View *view) -{ -#if DEBUG >= 1 - const Block *blk; - block_for_each(blk, &view->buffer->blocks) { - if (blk == view->cursor.blk) { - BUG_ON(view->cursor.offset > view->cursor.blk->size); - return; - } - } - BUG("cursor not seen"); -#else - (void)view; -#endif -} - -void any_key(Terminal *term, unsigned int esc_timeout) -{ - KeyCode key; - xfputs("Press any key to continue\r\n", stderr); - while ((key = term_read_key(term, esc_timeout)) == KEY_NONE) { - ; - } - bool bracketed_paste = key == KEY_BRACKETED_PASTE; - if (bracketed_paste || key == KEY_DETECTED_PASTE) { - term_discard_paste(&term->ibuf, bracketed_paste); - } -} - -NOINLINE -void ui_resize(EditorState *e) -{ - if (e->status == EDITOR_INITIALIZING) { - return; - } - resized = 0; - update_screen_size(&e->terminal, e->root_frame); - normal_update(e); -} - -void ui_start(EditorState *e) -{ - if (e->status == EDITOR_INITIALIZING) { - return; - } - - // Note: the order of these calls is important - Kitty saves/restores - // some terminal state when switching buffers, so switching to the - // alternate screen buffer needs to happen before modes are enabled - term_use_alt_screen_buffer(&e->terminal); - term_enable_private_modes(&e->terminal); - - ui_resize(e); -} - -void ui_end(EditorState *e) -{ - if (e->status == EDITOR_INITIALIZING) { - return; - } - Terminal *term = &e->terminal; - TermOutputBuffer *obuf = &term->obuf; - term_clear_screen(obuf); - term_move_cursor(obuf, 0, term->height - 1); - term_restore_cursor_style(term); - term_show_cursor(term); - term_restore_private_modes(term); - term_use_normal_screen_buffer(term); - term_end_sync_update(term); - term_output_flush(obuf); - term_cooked(); -} - -int main_loop(EditorState *e) -{ - while (e->status == EDITOR_RUNNING) { - if (unlikely(resized)) { - LOG_INFO("SIGWINCH received"); - ui_resize(e); - } - - KeyCode key = term_read_key(&e->terminal, e->options.esc_timeout); - if (unlikely(key == KEY_NONE)) { - continue; - } - - const ScreenState s = { - .is_modified = buffer_modified(e->buffer), - .id = e->buffer->id, - .cy = e->view->cy, - .vx = e->view->vx, - .vy = e->view->vy - }; - - clear_error(); - handle_input(e, key); - sanity_check(e->view); - update_screen(e, &s); - } - - BUG_ON(e->status < 0 || e->status > EDITOR_EXIT_MAX); - return e->status; -} diff --git a/examples/dte/editor.h b/examples/dte/editor.h deleted file mode 100644 index 86a8103..0000000 --- a/examples/dte/editor.h +++ /dev/null @@ -1,121 +0,0 @@ -#ifndef EDITOR_H -#define EDITOR_H - -#include <stdbool.h> -#include <stddef.h> -#include "buffer.h" -#include "cmdline.h" -#include "command/macro.h" -#include "command/run.h" -#include "commands.h" -#include "copy.h" -#include "file-history.h" -#include "frame.h" -#include "history.h" -#include "msg.h" -#include "options.h" -#include "regexp.h" -#include "search.h" -#include "syntax/color.h" -#include "tag.h" -#include "terminal/cursor.h" -#include "terminal/terminal.h" -#include "util/debug.h" -#include "util/hashmap.h" -#include "util/intmap.h" -#include "util/macros.h" -#include "util/ptr-array.h" -#include "util/string-view.h" -#include "view.h" - -typedef enum { - EDITOR_INITIALIZING = -2, - EDITOR_RUNNING = -1, - // Values 0-125 are exit codes - EDITOR_EXIT_OK = 0, - EDITOR_EXIT_MAX = 125, -} EditorStatus; - -typedef enum { - INPUT_NORMAL, - INPUT_COMMAND, - INPUT_SEARCH, -} InputMode; - -typedef struct { - const CommandSet *cmds; - IntMap key_bindings; -} ModeHandler; - -typedef struct EditorState { - EditorStatus status; - InputMode input_mode; - CommandLine cmdline; - SearchState search; - GlobalOptions options; - Terminal terminal; - StringView home_dir; - const char *user_config_dir; - bool child_controls_terminal; - bool everything_changed; - bool cursor_style_changed; - bool session_leader; - size_t cmdline_x; - ModeHandler modes[3]; - Clipboard clipboard; - TagFile tagfile; - HashMap aliases; - HashMap compilers; - HashMap syntaxes; - ColorScheme colors; - CommandMacroState macro; - TermCursorStyle cursor_styles[NR_CURSOR_MODES]; - Frame *root_frame; - struct Window *window; - View *view; - Buffer *buffer; - PointerArray buffers; - PointerArray filetypes; - PointerArray file_options; - PointerArray bookmarks; - MessageArray messages; - FileHistory file_history; - History search_history; - History command_history; - RegexpWordBoundaryTokens regexp_word_tokens; - const char *version; -} EditorState; - -static inline void mark_everything_changed(EditorState *e) -{ - e->everything_changed = true; -} - -static inline void set_input_mode(EditorState *e, InputMode mode) -{ - e->cursor_style_changed = true; - e->input_mode = mode; -} - -static inline CommandRunner cmdrunner_for_mode(EditorState *e, InputMode mode, bool allow_recording) -{ - BUG_ON(mode >= ARRAYLEN(e->modes)); - CommandRunner runner = { - .cmds = e->modes[mode].cmds, - .lookup_alias = (mode == INPUT_NORMAL) ? find_normal_alias : NULL, - .home_dir = &e->home_dir, - .allow_recording = allow_recording, - .userdata = e, - }; - return runner; -} - -EditorState *init_editor_state(void) RETURNS_NONNULL; -void free_editor_state(EditorState *e) NONNULL_ARGS; -void any_key(Terminal *term, unsigned int esc_timeout) NONNULL_ARGS; -int main_loop(EditorState *e) NONNULL_ARGS WARN_UNUSED_RESULT; -void ui_start(EditorState *e) NONNULL_ARGS; -void ui_end(EditorState *e) NONNULL_ARGS; -void ui_resize(EditorState *e) NONNULL_ARGS; - -#endif diff --git a/examples/dte/encoding.c b/examples/dte/encoding.c deleted file mode 100644 index 3fb87db..0000000 --- a/examples/dte/encoding.c +++ /dev/null @@ -1,132 +0,0 @@ -#include "encoding.h" -#include "util/ascii.h" -#include "util/bsearch.h" -#include "util/debug.h" -#include "util/intern.h" -#include "util/str-util.h" - -typedef struct { - const char alias[8]; - EncodingType encoding; -} EncodingAlias; - -static const char encoding_names[][16] = { - [UTF8] = "UTF-8", - [UTF16BE] = "UTF-16BE", - [UTF16LE] = "UTF-16LE", - [UTF32BE] = "UTF-32BE", - [UTF32LE] = "UTF-32LE", -}; - -static const EncodingAlias encoding_aliases[] = { - {"UCS-2", UTF16BE}, - {"UCS-2BE", UTF16BE}, - {"UCS-2LE", UTF16LE}, - {"UCS-4", UTF32BE}, - {"UCS-4BE", UTF32BE}, - {"UCS-4LE", UTF32LE}, - {"UCS2", UTF16BE}, - {"UCS4", UTF32BE}, - {"UTF-16", UTF16BE}, - {"UTF-32", UTF32BE}, - {"UTF16", UTF16BE}, - {"UTF16BE", UTF16BE}, - {"UTF16LE", UTF16LE}, - {"UTF32", UTF32BE}, - {"UTF32BE", UTF32BE}, - {"UTF32LE", UTF32LE}, - {"UTF8", UTF8}, -}; - -static const ByteOrderMark boms[NR_ENCODING_TYPES] = { - [UTF8] = {{0xef, 0xbb, 0xbf}, 3}, - [UTF16BE] = {{0xfe, 0xff}, 2}, - [UTF16LE] = {{0xff, 0xfe}, 2}, - [UTF32BE] = {{0x00, 0x00, 0xfe, 0xff}, 4}, - [UTF32LE] = {{0xff, 0xfe, 0x00, 0x00}, 4}, -}; - -UNITTEST { - CHECK_BSEARCH_ARRAY(encoding_aliases, alias, ascii_strcmp_icase); -} - -static int enc_alias_cmp(const void *key, const void *elem) -{ - const EncodingAlias *a = key; - const char *name = elem; - return ascii_strcmp_icase(a->alias, name); -} - -EncodingType lookup_encoding(const char *name) -{ - static_assert(ARRAYLEN(encoding_names) == NR_ENCODING_TYPES - 1); - for (size_t i = 0; i < ARRAYLEN(encoding_names); i++) { - if (ascii_streq_icase(name, encoding_names[i])) { - return (EncodingType) i; - } - } - - const EncodingAlias *a = BSEARCH(name, encoding_aliases, enc_alias_cmp); - return a ? a->encoding : UNKNOWN_ENCODING; -} - -static const char *encoding_type_to_string(EncodingType type) -{ - if (type < NR_ENCODING_TYPES && type != UNKNOWN_ENCODING) { - return str_intern(encoding_names[type]); - } - return NULL; -} - -Encoding encoding_from_name(const char *name) -{ - const EncodingType type = lookup_encoding(name); - const char *normalized_name; - if (type == UNKNOWN_ENCODING) { - char upper[256]; - size_t n; - for (n = 0; n < sizeof(upper) && name[n]; n++) { - upper[n] = ascii_toupper(name[n]); - } - normalized_name = mem_intern(upper, n); - } else { - normalized_name = encoding_type_to_string(type); - } - return (Encoding) { - .type = type, - .name = normalized_name - }; -} - -Encoding encoding_from_type(EncodingType type) -{ - return (Encoding) { - .type = type, - .name = encoding_type_to_string(type) - }; -} - -EncodingType detect_encoding_from_bom(const unsigned char *buf, size_t size) -{ - // Skip exhaustive checks if there's clearly no BOM - if (size < 2 || ((unsigned int)buf[0]) - 1 < 0xEE) { - return UNKNOWN_ENCODING; - } - - // Iterate array backwards to ensure UTF32LE is checked before UTF16LE - for (int i = NR_ENCODING_TYPES - 1; i >= 0; i--) { - const unsigned int bom_len = boms[i].len; - if (bom_len > 0 && size >= bom_len && mem_equal(buf, boms[i].bytes, bom_len)) { - return (EncodingType) i; - } - } - return UNKNOWN_ENCODING; -} - -const ByteOrderMark *get_bom_for_encoding(EncodingType encoding) -{ - static_assert(ARRAYLEN(boms) == NR_ENCODING_TYPES); - BUG_ON(encoding >= ARRAYLEN(boms)); - const ByteOrderMark *bom = &boms[encoding]; - return bom->len ? bom : NULL; -} diff --git a/examples/dte/encoding.h b/examples/dte/encoding.h deleted file mode 100644 index bb4cf67..0000000 --- a/examples/dte/encoding.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef ENCODING_ENCODING_H -#define ENCODING_ENCODING_H - -#include <stdbool.h> -#include <stddef.h> -#include "util/macros.h" - -typedef enum { - UTF8, - UTF16BE, - UTF16LE, - UTF32BE, - UTF32LE, - UNKNOWN_ENCODING, - NR_ENCODING_TYPES, - - // This value is used by the "open" command to instruct other - // routines that no specific encoding was requested and that - // it should be detected instead. It is always replaced by - // some other value by the time a file is successfully opened. - ENCODING_AUTODETECT -} EncodingType; - -typedef struct { - EncodingType type; - // An interned encoding name compatible with iconv_open(3) - const char *name; -} Encoding; - -typedef struct { - const unsigned char bytes[4]; - unsigned int len; -} ByteOrderMark; - -static inline bool same_encoding(const Encoding *a, const Encoding *b) -{ - return a->type == b->type && a->name == b->name; -} - -Encoding encoding_from_type(EncodingType type); -Encoding encoding_from_name(const char *name) NONNULL_ARGS; -EncodingType lookup_encoding(const char *name) NONNULL_ARGS; -EncodingType detect_encoding_from_bom(const unsigned char *buf, size_t size); -const ByteOrderMark *get_bom_for_encoding(EncodingType encoding); - -#endif diff --git a/examples/dte/error.c b/examples/dte/error.c deleted file mode 100644 index 87831c9..0000000 --- a/examples/dte/error.c +++ /dev/null @@ -1,95 +0,0 @@ -#include <errno.h> -#include <stdarg.h> -#include <stdio.h> -#include <string.h> -#include "error.h" -#include "command/run.h" -#include "config.h" -#include "util/log.h" -#include "util/xstdio.h" - -static char error_buf[512]; -static unsigned int nr_errors; -static bool msg_is_error; -static bool print_errors_to_stderr; - -void clear_error(void) -{ - error_buf[0] = '\0'; -} - -bool error_msg(const char *format, ...) -{ - const char *cmd = current_command ? current_command->name : NULL; - const char *file = current_config.file; - const unsigned int line = current_config.line; - const size_t size = sizeof(error_buf); - int pos = 0; - - if (file && cmd) { - pos = snprintf(error_buf, size, "%s:%u: %s: ", file, line, cmd); - } else if (file) { - pos = snprintf(error_buf, size, "%s:%u: ", file, line); - } else if (cmd) { - pos = snprintf(error_buf, size, "%s: ", cmd); - } - - if (unlikely(pos < 0)) { - // Note: POSIX snprintf(3) *does* set errno on failure (unlike ISO C) - LOG_ERRNO("snprintf"); - pos = 0; - } - - if (likely(pos < (size - 3))) { - va_list ap; - va_start(ap, format); - vsnprintf(error_buf + pos, size - pos, format, ap); - va_end(ap); - } else { - LOG_WARNING("no buffer space left for error message"); - } - - msg_is_error = true; - nr_errors++; - - if (print_errors_to_stderr) { - xfputs(error_buf, stderr); - xfputc('\n', stderr); - } - - LOG_INFO("%s", error_buf); - - // Always return false, to allow tail-calling as `return error_msg(...);` - // from command handlers, instead of `error_msg(...); return false;` - return false; -} - -bool error_msg_errno(const char *prefix) -{ - return error_msg("%s: %s", prefix, strerror(errno)); -} - -void info_msg(const char *format, ...) -{ - va_list ap; - va_start(ap, format); - vsnprintf(error_buf, sizeof(error_buf), format, ap); - va_end(ap); - msg_is_error = false; -} - -const char *get_msg(bool *is_error) -{ - *is_error = msg_is_error; - return error_buf; -} - -unsigned int get_nr_errors(void) -{ - return nr_errors; -} - -void set_print_errors_to_stderr(bool enable) -{ - print_errors_to_stderr = enable; -} diff --git a/examples/dte/error.h b/examples/dte/error.h deleted file mode 100644 index bf19414..0000000 --- a/examples/dte/error.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef ERROR_H -#define ERROR_H - -#include <stdbool.h> -#include "util/macros.h" - -bool error_msg(const char *format, ...) COLD PRINTF(1); -bool error_msg_errno(const char *prefix) COLD NONNULL_ARGS; -void info_msg(const char *format, ...) PRINTF(1); -void clear_error(void); -const char *get_msg(bool *is_error) NONNULL_ARGS; -unsigned int get_nr_errors(void); -void set_print_errors_to_stderr(bool enable); - -#endif diff --git a/examples/dte/exec.c b/examples/dte/exec.c deleted file mode 100644 index 416a2ef..0000000 --- a/examples/dte/exec.c +++ /dev/null @@ -1,366 +0,0 @@ -#include <stdint.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include "exec.h" -#include "block-iter.h" -#include "buffer.h" -#include "change.h" -#include "command/macro.h" -#include "commands.h" -#include "ctags.h" -#include "error.h" -#include "misc.h" -#include "move.h" -#include "msg.h" -#include "selection.h" -#include "show.h" -#include "tag.h" -#include "util/bsearch.h" -#include "util/debug.h" -#include "util/numtostr.h" -#include "util/ptr-array.h" -#include "util/str-util.h" -#include "util/string-view.h" -#include "util/string.h" -#include "util/strtonum.h" -#include "util/xsnprintf.h" -#include "view.h" -#include "window.h" - -enum { - IN = 1 << 0, - OUT = 1 << 1, - ERR = 1 << 2, - ALL = IN | OUT | ERR, -}; - -static const struct { - char name[8]; - uint8_t flags; -} exec_map[] = { - [EXEC_BUFFER] = {"buffer", IN | OUT}, - [EXEC_COMMAND] = {"command", IN}, - [EXEC_ERRMSG] = {"errmsg", ERR}, - [EXEC_EVAL] = {"eval", OUT}, - [EXEC_LINE] = {"line", IN}, - [EXEC_MSG] = {"msg", IN | OUT}, - [EXEC_NULL] = {"null", ALL}, - [EXEC_OPEN] = {"open", OUT}, - [EXEC_SEARCH] = {"search", IN}, - [EXEC_TAG] = {"tag", OUT}, - [EXEC_TTY] = {"tty", ALL}, - [EXEC_WORD] = {"word", IN}, -}; - -UNITTEST { - CHECK_BSEARCH_ARRAY(exec_map, name, strcmp); -} - -ExecAction lookup_exec_action(const char *name, int fd) -{ - BUG_ON(fd < 0 || fd > 2); - ssize_t i = BSEARCH_IDX(name, exec_map, vstrcmp); - return (i >= 0 && (exec_map[i].flags & 1u << fd)) ? i : EXEC_INVALID; -} - -static void open_files_from_string(EditorState *e, const String *str) -{ - PointerArray filenames = PTR_ARRAY_INIT; - for (size_t pos = 0, size = str->len; pos < size; ) { - char *filename = buf_next_line(str->buffer, &pos, size); - if (filename[0] != '\0') { - ptr_array_append(&filenames, filename); - } - } - - if (filenames.count == 0) { - return; - } - - ptr_array_append(&filenames, NULL); - window_open_files(e->window, (char**)filenames.ptrs, NULL); - - // TODO: re-enable this when the todo in allow_macro_recording() is done - // macro_command_hook(&e->macro, "open", (char**)filenames.ptrs); - - ptr_array_free_array(&filenames); -} - -static void parse_and_activate_message(EditorState *e, const String *str) -{ - MessageArray *msgs = &e->messages; - size_t count = msgs->array.count; - size_t x; - if (!count || !buf_parse_size(str->buffer, str->len, &x) || !x) { - return; - } - msgs->pos = MIN(x - 1, count - 1); - activate_current_message(e); -} - -static void parse_and_goto_tag(EditorState *e, const String *str) -{ - if (unlikely(str->len == 0)) { - error_msg("child produced no output"); - return; - } - - Tag tag; - size_t pos = 0; - StringView line = buf_slice_next_line(str->buffer, &pos, str->len); - if (pos == 0) { - return; - } - - if (!parse_ctags_line(&tag, line.data, line.length)) { - // Treat line as simple tag name - tag_lookup(&e->tagfile, &line, e->buffer->abs_filename, &e->messages); - goto activate; - } - - char buf[8192]; - const char *cwd = getcwd(buf, sizeof buf); - if (unlikely(!cwd)) { - error_msg_errno("getcwd() failed"); - return; - } - - StringView dir = strview_from_cstring(cwd); - clear_messages(&e->messages); - add_message_for_tag(&e->messages, &tag, &dir); - -activate: - activate_current_message_save(e); -} - -static const char **lines_and_columns_env(const Window *window) -{ - static char lines[DECIMAL_STR_MAX(window->edit_h)]; - static char columns[DECIMAL_STR_MAX(window->edit_w)]; - static const char *vars[] = { - "LINES", lines, - "COLUMNS", columns, - NULL, - }; - - buf_uint_to_str(window->edit_h, lines); - buf_uint_to_str(window->edit_w, columns); - return vars; -} - -static void show_spawn_error_msg(const String *errstr, int err) -{ - if (err <= 0) { - return; - } - - char msg[512]; - msg[0] = '\0'; - if (errstr->len) { - size_t pos = 0; - StringView line = buf_slice_next_line(errstr->buffer, &pos, errstr->len); - BUG_ON(pos == 0); - size_t len = MIN(line.length, sizeof(msg) - 8); - xsnprintf(msg, sizeof(msg), ": \"%.*s\"", (int)len, line.data); - } - - if (err >= 256) { - int sig = err >> 8; - const char *str = strsignal(sig); - error_msg("Child received signal %d (%s)%s", sig, str ? str : "??", msg); - } else if (err) { - error_msg("Child returned %d%s", err, msg); - } -} - -static SpawnAction spawn_action_from_exec_action(ExecAction action) -{ - BUG_ON(action == EXEC_INVALID); - if (action == EXEC_NULL) { - return SPAWN_NULL; - } else if (action == EXEC_TTY) { - return SPAWN_TTY; - } else { - return SPAWN_PIPE; - } -} - -ssize_t handle_exec ( - EditorState *e, - const char **argv, - ExecAction actions[3], - SpawnFlags spawn_flags, - bool strip_trailing_newline -) { - View *view = e->view; - const BlockIter saved_cursor = view->cursor; - const ssize_t saved_sel_so = view->sel_so; - const ssize_t saved_sel_eo = view->sel_eo; - char *alloc = NULL; - bool output_to_buffer = (actions[STDOUT_FILENO] == EXEC_BUFFER); - bool replace_input = false; - - SpawnContext ctx = { - .editor = e, - .argv = argv, - .outputs = {STRING_INIT, STRING_INIT}, - .flags = spawn_flags, - .env = output_to_buffer ? lines_and_columns_env(e->window) : NULL, - .actions = { - spawn_action_from_exec_action(actions[0]), - spawn_action_from_exec_action(actions[1]), - spawn_action_from_exec_action(actions[2]), - }, - }; - - switch (actions[STDIN_FILENO]) { - case EXEC_LINE: - if (view->selection) { - ctx.input.length = prepare_selection(view); - } else { - StringView line; - move_bol(view); - fill_line_ref(&view->cursor, &line); - ctx.input.length = line.length; - } - replace_input = true; - get_bytes: - alloc = block_iter_get_bytes(&view->cursor, ctx.input.length); - ctx.input.data = alloc; - break; - case EXEC_BUFFER: - if (view->selection) { - ctx.input.length = prepare_selection(view); - } else { - Block *blk; - block_for_each(blk, &view->buffer->blocks) { - ctx.input.length += blk->size; - } - move_bof(view); - } - replace_input = true; - goto get_bytes; - case EXEC_WORD: - if (view->selection) { - ctx.input.length = prepare_selection(view); - replace_input = true; - } else { - size_t offset; - StringView word = view_do_get_word_under_cursor(e->view, &offset); - if (word.length == 0) { - break; - } - // TODO: optimize this, so that the BlockIter moves by just the - // minimal word offset instead of iterating to a line offset - ctx.input.length = word.length; - move_bol(view); - view->cursor.offset += offset; - BUG_ON(view->cursor.offset >= view->cursor.blk->size); - } - goto get_bytes; - case EXEC_MSG: { - String messages = dump_messages(&e->messages); - ctx.input = strview_from_string(&messages), - alloc = messages.buffer; - break; - } - case EXEC_COMMAND: { - String hist = dump_command_history(e); - ctx.input = strview_from_string(&hist), - alloc = hist.buffer; - break; - } - case EXEC_SEARCH: { - String hist = dump_search_history(e); - ctx.input = strview_from_string(&hist), - alloc = hist.buffer; - break; - } - case EXEC_NULL: - case EXEC_TTY: - break; - // These can't be used as input actions and should be prevented by - // the validity checks in cmd_exec(): - case EXEC_OPEN: - case EXEC_TAG: - case EXEC_EVAL: - case EXEC_ERRMSG: - case EXEC_INVALID: - default: - BUG("unhandled action"); - return -1; - } - - int err = spawn(&ctx); - free(alloc); - if (err != 0) { - show_spawn_error_msg(&ctx.outputs[1], err); - string_free(&ctx.outputs[0]); - string_free(&ctx.outputs[1]); - view->cursor = saved_cursor; - return -1; - } - - string_free(&ctx.outputs[1]); - String *output = &ctx.outputs[0]; - if ( - strip_trailing_newline - && output_to_buffer - && output->len > 0 - && output->buffer[output->len - 1] == '\n' - ) { - output->len--; - if (output->len > 0 && output->buffer[output->len - 1] == '\r') { - output->len--; - } - } - - if (!output_to_buffer) { - view->cursor = saved_cursor; - view->sel_so = saved_sel_so; - view->sel_eo = saved_sel_eo; - mark_all_lines_changed(view->buffer); - } - - switch (actions[STDOUT_FILENO]) { - case EXEC_BUFFER: - if (replace_input || view->selection) { - size_t del_count = replace_input ? ctx.input.length : prepare_selection(view); - buffer_replace_bytes(view, del_count, output->buffer, output->len); - unselect(view); - } else { - buffer_insert_bytes(view, output->buffer, output->len); - } - break; - case EXEC_MSG: - parse_and_activate_message(e, output); - break; - case EXEC_OPEN: - open_files_from_string(e, output); - break; - case EXEC_TAG: - parse_and_goto_tag(e, output); - break; - case EXEC_EVAL: - exec_normal_config(e, strview_from_string(output)); - break; - case EXEC_NULL: - case EXEC_TTY: - break; - // These can't be used as output actions - case EXEC_COMMAND: - case EXEC_ERRMSG: - case EXEC_LINE: - case EXEC_SEARCH: - case EXEC_WORD: - case EXEC_INVALID: - default: - BUG("unhandled action"); - return -1; - } - - size_t output_len = output->len; - string_free(output); - return output_len; -} diff --git a/examples/dte/exec.h b/examples/dte/exec.h deleted file mode 100644 index e40a11f..0000000 --- a/examples/dte/exec.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef EXEC_H -#define EXEC_H - -#include <stdbool.h> -#include <sys/types.h> -#include "editor.h" -#include "spawn.h" -#include "util/macros.h" - -typedef enum { - EXEC_INVALID = -1, - // Note: items below here need to be kept sorted - EXEC_BUFFER = 0, - EXEC_COMMAND, - EXEC_ERRMSG, - EXEC_EVAL, - EXEC_LINE, - EXEC_MSG, - EXEC_NULL, - EXEC_OPEN, - EXEC_SEARCH, - EXEC_TAG, - EXEC_TTY, - EXEC_WORD, -} ExecAction; - -ssize_t handle_exec ( - EditorState *e, - const char **argv, - ExecAction actions[3], - SpawnFlags spawn_flags, - bool strip_trailing_newline -) NONNULL_ARGS; - -ExecAction lookup_exec_action(const char *name, int fd) NONNULL_ARGS; - -#endif diff --git a/examples/dte/file-history.c b/examples/dte/file-history.c deleted file mode 100644 index 8066b94..0000000 --- a/examples/dte/file-history.c +++ /dev/null @@ -1,153 +0,0 @@ -#include <errno.h> -#include <stdlib.h> -#include <string.h> -#include <sys/types.h> -#include "file-history.h" -#include "error.h" -#include "util/debug.h" -#include "util/readfile.h" -#include "util/str-util.h" -#include "util/string-view.h" -#include "util/strtonum.h" -#include "util/xmalloc.h" -#include "util/xstdio.h" - -enum { - MAX_ENTRIES = 512 -}; - -void file_history_add(FileHistory *history, unsigned long row, unsigned long col, const char *filename) -{ - BUG_ON(row == 0); - BUG_ON(col == 0); - HashMap *map = &history->entries; - FileHistoryEntry *e = hashmap_get(map, filename); - - if (e) { - if (e == history->last) { - e->row = row; - e->col = col; - return; - } - e->next->prev = e->prev; - if (unlikely(e == history->first)) { - history->first = e->next; - } else { - e->prev->next = e->next; - } - } else { - if (map->count == MAX_ENTRIES) { - // History is full; recycle the oldest entry - FileHistoryEntry *old_first = history->first; - FileHistoryEntry *new_first = old_first->next; - new_first->prev = NULL; - history->first = new_first; - e = hashmap_remove(map, old_first->filename); - BUG_ON(e != old_first); - } else { - e = xnew(FileHistoryEntry, 1); - } - e->filename = xstrdup(filename); - hashmap_insert(map, e->filename, e); - } - - // Insert the entry at the end of the list - FileHistoryEntry *old_last = history->last; - e->next = NULL; - e->prev = old_last; - e->row = row; - e->col = col; - history->last = e; - if (likely(old_last)) { - old_last->next = e; - } else { - history->first = e; - } -} - -static bool parse_ulong_field(StringView *sv, unsigned long *valp) -{ - size_t n = buf_parse_ulong(sv->data, sv->length, valp); - if (n == 0 || *valp == 0 || sv->data[n] != ' ') { - return false; - } - strview_remove_prefix(sv, n + 1); - return true; -} - -void file_history_load(FileHistory *history, char *filename) -{ - BUG_ON(!history); - BUG_ON(!filename); - BUG_ON(history->filename); - - hashmap_init(&history->entries, MAX_ENTRIES); - history->filename = filename; - - char *buf; - const ssize_t ssize = read_file(filename, &buf); - if (ssize < 0) { - if (errno != ENOENT) { - error_msg("Error reading %s: %s", filename, strerror(errno)); - } - return; - } - - for (size_t pos = 0, size = ssize; pos < size; ) { - unsigned long row, col; - StringView line = buf_slice_next_line(buf, &pos, size); - if (unlikely( - !parse_ulong_field(&line, &row) - || !parse_ulong_field(&line, &col) - || line.length < 2 - || line.data[0] != '/' - || buf[pos - 1] != '\n' - )) { - continue; - } - buf[pos - 1] = '\0'; // null-terminate line, by replacing '\n' with '\0' - file_history_add(history, row, col, line.data); - } - - free(buf); -} - -void file_history_save(const FileHistory *history) -{ - const char *filename = history->filename; - if (!filename) { - return; - } - - FILE *f = xfopen(filename, "w", O_CLOEXEC, 0666); - if (!f) { - error_msg("Error creating %s: %s", filename, strerror(errno)); - return; - } - - for (const FileHistoryEntry *e = history->first; e; e = e->next) { - xfprintf(f, "%lu %lu %s\n", e->row, e->col, e->filename); - } - - fclose(f); -} - -bool file_history_find(const FileHistory *history, const char *filename, unsigned long *row, unsigned long *col) -{ - const FileHistoryEntry *e = hashmap_get(&history->entries, filename); - if (!e) { - return false; - } - *row = e->row; - *col = e->col; - return true; -} - -void file_history_free(FileHistory *history) -{ - hashmap_free(&history->entries, free); - free(history->filename); - history->filename = NULL; - history->first = NULL; - history->last = NULL; -} diff --git a/examples/dte/file-history.h b/examples/dte/file-history.h deleted file mode 100644 index 0a51891..0000000 --- a/examples/dte/file-history.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef FILE_HISTORY_H -#define FILE_HISTORY_H - -#include <stdbool.h> -#include "util/hashmap.h" -#include "util/macros.h" - -typedef struct FileHistoryEntry { - struct FileHistoryEntry *next; - struct FileHistoryEntry *prev; - char *filename; - unsigned long row; - unsigned long col; -} FileHistoryEntry; - -typedef struct { - char *filename; - HashMap entries; - FileHistoryEntry *first; - FileHistoryEntry *last; -} FileHistory; - -void file_history_add(FileHistory *hist, unsigned long row, unsigned long col, const char *filename); -void file_history_load(FileHistory *hist, char *filename); -void file_history_save(const FileHistory *hist); -bool file_history_find(const FileHistory *hist, const char *filename, unsigned long *row, unsigned long *col) WARN_UNUSED_RESULT; -void file_history_free(FileHistory *history); - -#endif diff --git a/examples/dte/file-option.c b/examples/dte/file-option.c deleted file mode 100644 index df6af3d..0000000 --- a/examples/dte/file-option.c +++ /dev/null @@ -1,193 +0,0 @@ -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include "file-option.h" -#include "command/serialize.h" -#include "editor.h" -#include "editorconfig/editorconfig.h" -#include "error.h" -#include "options.h" -#include "regexp.h" -#include "util/debug.h" -#include "util/str-util.h" -#include "util/xmalloc.h" - -typedef struct { - FileOptionType type; - char **strs; - union { - char *filetype; - CachedRegexp *filename; - } u; -} FileOption; - -static void set_options(EditorState *e, char **args) -{ - for (size_t i = 0; args[i]; i += 2) { - set_option(e, args[i], args[i + 1], true, false); - } -} - -void set_editorconfig_options(Buffer *buffer) -{ - LocalOptions *options = &buffer->options; - if (!options->editorconfig) { - return; - } - - const char *path = buffer->abs_filename; - char cwd[8192]; - if (!path) { - // For buffers with no associated filename, use a dummy path of - // "$PWD/__", to obtain generic settings for the working directory - // or the user's default settings - static const char suffix[] = "/__"; - if (unlikely(!getcwd(cwd, sizeof(cwd) - sizeof(suffix)))) { - return; - } - memcpy(cwd + strlen(cwd), suffix, sizeof(suffix)); - path = cwd; - } - - EditorConfigOptions opts; - if (get_editorconfig_options(path, &opts) != 0) { - return; - } - - switch (opts.indent_style) { - case INDENT_STYLE_SPACE: - options->expand_tab = true; - options->emulate_tab = true; - options->detect_indent = 0; - break; - case INDENT_STYLE_TAB: - options->expand_tab = false; - options->emulate_tab = false; - options->detect_indent = 0; - break; - case INDENT_STYLE_UNSPECIFIED: - break; - } - - const unsigned int indent_size = opts.indent_size; - if (indent_size > 0 && indent_size <= INDENT_WIDTH_MAX) { - options->indent_width = indent_size; - options->detect_indent = 0; - } - - const unsigned int tab_width = opts.tab_width; - if (tab_width > 0 && tab_width <= TAB_WIDTH_MAX) { - options->tab_width = tab_width; - } - - const unsigned int max_line_length = opts.max_line_length; - if (max_line_length > 0 && max_line_length <= TEXT_WIDTH_MAX) { - options->text_width = max_line_length; - } -} - -void set_file_options(EditorState *e, Buffer *buffer) -{ - for (size_t i = 0, n = e->file_options.count; i < n; i++) { - const FileOption *opt = e->file_options.ptrs[i]; - if (opt->type == FOPTS_FILETYPE) { - if (streq(opt->u.filetype, buffer->options.filetype)) { - set_options(e, opt->strs); - } - continue; - } - - BUG_ON(opt->type != FOPTS_FILENAME); - const char *filename = buffer->abs_filename; - if (!filename) { - continue; - } - - const regex_t *re = &opt->u.filename->re; - regmatch_t m; - if (regexp_exec(re, filename, strlen(filename), 0, &m, 0)) { - set_options(e, opt->strs); - } - } -} - -bool add_file_options(PointerArray *file_options, FileOptionType type, StringView str, char **strs, size_t nstrs) -{ - size_t len = str.length; - if (unlikely(len == 0)) { - const char *desc = (type == FOPTS_FILETYPE) ? "filetype" : "pattern"; - return error_msg("can't add option with empty %s", desc); - } - - FileOption *opt = xnew(FileOption, 1); - if (type == FOPTS_FILETYPE) { - opt->u.filetype = xstrcut(str.data, len); - goto append; - } - - BUG_ON(type != FOPTS_FILENAME); - CachedRegexp *r = xmalloc(sizeof(*r) + len + 1); - memcpy(r->str, str.data, len); - r->str[len] = '\0'; - opt->u.filename = r; - - int err = regcomp(&r->re, r->str, DEFAULT_REGEX_FLAGS | REG_NEWLINE | REG_NOSUB); - if (unlikely(err)) { - regexp_error_msg(&r->re, r->str, err); - free(r); - free(opt); - return false; - } - -append: - opt->type = type; - opt->strs = copy_string_array(strs, nstrs); - ptr_array_append(file_options, opt); - return true; -} - -void dump_file_options(const PointerArray *file_options, String *buf) -{ - for (size_t i = 0, n = file_options->count; i < n; i++) { - const FileOption *opt = file_options->ptrs[i]; - const char *tp; - if (opt->type == FOPTS_FILENAME) { - tp = opt->u.filename->str; - } else { - tp = opt->u.filetype; - } - char **strs = opt->strs; - string_append_literal(buf, "option "); - if (opt->type == FOPTS_FILENAME) { - string_append_literal(buf, "-r "); - } - if (str_has_prefix(tp, "-") || string_array_contains_prefix(strs, "-")) { - string_append_literal(buf, "-- "); - } - string_append_escaped_arg(buf, tp, true); - for (size_t j = 0; strs[j]; j += 2) { - string_append_byte(buf, ' '); - string_append_cstring(buf, strs[j]); - string_append_byte(buf, ' '); - string_append_escaped_arg(buf, strs[j + 1], true); - } - string_append_byte(buf, '\n'); - } -} - -static void free_file_option(FileOption *opt) -{ - if (opt->type == FOPTS_FILENAME) { - free_cached_regexp(opt->u.filename); - } else { - BUG_ON(opt->type != FOPTS_FILETYPE); - free(opt->u.filetype); - } - free_string_array(opt->strs); - free(opt); -} - -void free_file_options(PointerArray *file_options) -{ - ptr_array_free_cb(file_options, FREE_FUNC(free_file_option)); -} diff --git a/examples/dte/file-option.h b/examples/dte/file-option.h deleted file mode 100644 index ab1e930..0000000 --- a/examples/dte/file-option.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef FILE_OPTION_H -#define FILE_OPTION_H - -#include <stdbool.h> -#include "buffer.h" -#include "util/macros.h" -#include "util/ptr-array.h" -#include "util/string-view.h" -#include "util/string.h" - -typedef enum { - FOPTS_FILENAME, - FOPTS_FILETYPE, -} FileOptionType; - -struct EditorState; - -void set_file_options(struct EditorState *e, Buffer *buffer) NONNULL_ARGS; -void set_editorconfig_options(Buffer *buffer) NONNULL_ARGS; -void dump_file_options(const PointerArray *file_options, String *buf); -void free_file_options(PointerArray *file_options); - -NONNULL_ARGS WARN_UNUSED_RESULT -bool add_file_options ( - PointerArray *file_options, - FileOptionType type, - StringView str, - char **strs, - size_t nstrs -); - -#endif diff --git a/examples/dte/filetype.c b/examples/dte/filetype.c deleted file mode 100644 index 5a40f7c..0000000 --- a/examples/dte/filetype.c +++ /dev/null @@ -1,333 +0,0 @@ -#include <stdint.h> -#include <stdlib.h> -#include "filetype.h" -#include "command/serialize.h" -#include "regexp.h" -#include "util/array.h" -#include "util/ascii.h" -#include "util/bsearch.h" -#include "util/debug.h" -#include "util/path.h" -#include "util/str-util.h" -#include "util/xmalloc.h" - -static int ft_compare(const void *key, const void *elem) -{ - const StringView *sv = key; - const char *ext = elem; // Cast to first member of struct - int res = memcmp(sv->data, ext, sv->length); - if (unlikely(res == 0 && ext[sv->length] != '\0')) { - res = -1; - } - return res; -} - -// Built-in filetypes -#include "filetype/names.c" -#include "filetype/basenames.c" -#include "filetype/directories.c" -#include "filetype/extensions.c" -#include "filetype/interpreters.c" -#include "filetype/ignored-exts.c" -#include "filetype/signatures.c" - -UNITTEST { - static_assert(NR_BUILTIN_FILETYPES < 256); - CHECK_BSEARCH_ARRAY(basenames, name, strcmp); - CHECK_BSEARCH_ARRAY(extensions, ext, strcmp); - CHECK_BSEARCH_ARRAY(interpreters, key, strcmp); - CHECK_BSEARCH_STR_ARRAY(ignored_extensions, strcmp); - CHECK_BSEARCH_STR_ARRAY(builtin_filetype_names, strcmp); - - for (size_t i = 0; i < ARRAYLEN(builtin_filetype_names); i++) { - const char *name = builtin_filetype_names[i]; - if (unlikely(!is_valid_filetype_name(name))) { - BUG("invalid name at builtin_filetype_names[%zu]: \"%s\"", i, name); - } - } -} - -typedef struct { - unsigned int str_len; - char str[]; -} FlexArrayStr; - -// Filetypes dynamically added via the `ft` command. -// Not grouped by name to make it possible to order them freely. -typedef struct { - union { - FlexArrayStr *str; - CachedRegexp *regexp; - } u; - uint8_t type; // FileDetectionType - char name[]; -} UserFileTypeEntry; - -static bool ft_uses_regex(FileDetectionType type) -{ - return type == FT_CONTENT || type == FT_FILENAME; -} - -bool add_filetype(PointerArray *filetypes, const char *name, const char *str, FileDetectionType type) -{ - BUG_ON(!is_valid_filetype_name(name)); - regex_t re; - bool use_re = ft_uses_regex(type); - if (use_re) { - int err = regcomp(&re, str, DEFAULT_REGEX_FLAGS | REG_NEWLINE | REG_NOSUB); - if (unlikely(err)) { - return regexp_error_msg(&re, str, err); - } - } - - size_t name_len = strlen(name); - size_t str_len = strlen(str); - UserFileTypeEntry *ft = xmalloc(sizeof(*ft) + name_len + 1); - ft->type = type; - - char *str_dest; - if (use_re) { - CachedRegexp *r = xmalloc(sizeof(*r) + str_len + 1); - r->re = re; - ft->u.regexp = r; - str_dest = r->str; - } else { - FlexArrayStr *s = xmalloc(sizeof(*s) + str_len + 1); - s->str_len = str_len; - ft->u.str = s; - str_dest = s->str; - } - - memcpy(ft->name, name, name_len + 1); - memcpy(str_dest, str, str_len + 1); - ptr_array_append(filetypes, ft); - return true; -} - -static StringView path_extension(StringView filename) -{ - StringView ext = STRING_VIEW_INIT; - ext.data = strview_memrchr(&filename, '.'); - if (!ext.data || ext.data == filename.data) { - return ext; - } - ext.data++; - ext.length = filename.length - (ext.data - filename.data); - return ext; -} - -static StringView get_filename_extension(StringView filename) -{ - StringView ext = path_extension(filename); - if (is_ignored_extension(ext)) { - filename.length -= ext.length + 1; - ext = path_extension(filename); - } - if (strview_has_suffix(&ext, "~")) { - ext.length--; - } - return ext; -} - -// Parse hashbang and return interpreter name, without version number. -// For example, if line is "#!/usr/bin/env python2", "python" is returned. -static StringView get_interpreter(StringView line) -{ - StringView sv = STRING_VIEW_INIT; - if (!strview_has_prefix(&line, "#!")) { - return sv; - } - - strview_remove_prefix(&line, 2); - strview_trim_left(&line); - if (line.length < 2 || line.data[0] != '/') { - return sv; - } - - size_t pos = 0; - sv = get_delim(line.data, &pos, line.length, ' '); - if (pos < line.length && strview_equal_cstring(&sv, "/usr/bin/env")) { - while (pos + 1 < line.length && line.data[pos] == ' ') { - pos++; - } - sv = get_delim(line.data, &pos, line.length, ' '); - } - - ssize_t last_slash_idx = strview_memrchr_idx(&sv, '/'); - if (last_slash_idx >= 0) { - strview_remove_prefix(&sv, last_slash_idx + 1); - } - - while (sv.length && ascii_is_digit_or_dot(sv.data[sv.length - 1])) { - sv.length--; - } - - return sv; -} - -static bool ft_str_match(const UserFileTypeEntry *ft, const StringView sv) -{ - const char *str = ft->u.str->str; - const size_t len = ft->u.str->str_len; - return sv.length > 0 && strview_equal_strn(&sv, str, len); -} - -static bool ft_regex_match(const UserFileTypeEntry *ft, const StringView sv) -{ - const regex_t *re = &ft->u.regexp->re; - regmatch_t m; - return sv.length > 0 && regexp_exec(re, sv.data, sv.length, 0, &m, 0); -} - -static bool ft_match(const UserFileTypeEntry *ft, const StringView sv) -{ - if (ft_uses_regex(ft->type)) { - return ft_regex_match(ft, sv); - } - return ft_str_match(ft, sv); -} - -const char *find_ft(const PointerArray *filetypes, const char *filename, StringView line) -{ - const char *b = filename ? path_basename(filename) : NULL; - const StringView base = strview_from_cstring(b); - const StringView ext = get_filename_extension(base); - const StringView path = strview_from_cstring(filename); - const StringView interpreter = get_interpreter(line); - BUG_ON(path.length == 0 && (base.length != 0 || ext.length != 0)); - BUG_ON(line.length == 0 && interpreter.length != 0); - - // The order of elements in this array determines the order of - // precedence for the lookup() functions (but note that changing - // the initializer below makes no difference to the array order) - const struct { - StringView sv; - FileTypeEnum (*lookup)(const StringView sv); - } table[] = { - [FT_INTERPRETER] = {interpreter, filetype_from_interpreter}, - [FT_BASENAME] = {base, filetype_from_basename}, - [FT_CONTENT] = {line, filetype_from_signature}, - [FT_EXTENSION] = {ext, filetype_from_extension}, - [FT_FILENAME] = {path, filetype_from_dir_prefix}, - }; - - // Search user `ft` entries - for (size_t i = 0, n = filetypes->count; i < n; i++) { - const UserFileTypeEntry *ft = filetypes->ptrs[i]; - if (ft_match(ft, table[ft->type].sv)) { - return ft->name; - } - } - - // Search built-in lookup tables - for (FileDetectionType i = 0; i < ARRAYLEN(table); i++) { - BUG_ON(!table[i].lookup); - FileTypeEnum ft = table[i].lookup(table[i].sv); - if (ft != NONE) { - return builtin_filetype_names[ft]; - } - } - - // Use "ini" filetype if first line looks like an ini [section] - strview_trim_right(&line); - if (line.length >= 4) { - const char *s = line.data; - const size_t n = line.length; - if (s[0] == '[' && s[n - 1] == ']' && is_word_byte(s[1])) { - if (!strview_contains_char_type(&line, ASCII_CNTRL)) { - return builtin_filetype_names[INI]; - } - } - } - - if (strview_equal_cstring(&ext, "conf")) { - if (strview_has_prefix(&path, "/etc/systemd/")) { - return builtin_filetype_names[INI]; - } - BUG_ON(!filename); - const StringView dir = path_slice_dirname(filename); - if ( - strview_has_prefix(&path, "/etc/") - || strview_has_prefix(&path, "/usr/share/") - || strview_has_prefix(&path, "/usr/local/share/") - || strview_has_suffix(&dir, "/tmpfiles.d") - ) { - return builtin_filetype_names[CONFIG]; - } - } - - return NULL; -} - -bool is_ft(const PointerArray *filetypes, const char *name) -{ - if (BSEARCH(name, builtin_filetype_names, vstrcmp)) { - return true; - } - - for (size_t i = 0, n = filetypes->count; i < n; i++) { - const UserFileTypeEntry *ft = filetypes->ptrs[i]; - if (streq(ft->name, name)) { - return true; - } - } - - return false; -} - -void collect_ft(const PointerArray *filetypes, PointerArray *a, const char *prefix) -{ - COLLECT_STRINGS(builtin_filetype_names, a, prefix); - for (size_t i = 0, n = filetypes->count; i < n; i++) { - const UserFileTypeEntry *ft = filetypes->ptrs[i]; - const char *name = ft->name; - if (str_has_prefix(name, prefix)) { - ptr_array_append(a, xstrdup(name)); - } - } -} - -static const char *ft_get_str(const UserFileTypeEntry *ft) -{ - return ft_uses_regex(ft->type) ? ft->u.regexp->str : ft->u.str->str; -} - -String dump_filetypes(const PointerArray *filetypes) -{ - static const char flags[][4] = { - [FT_EXTENSION] = "", - [FT_FILENAME] = "-f ", - [FT_CONTENT] = "-c ", - [FT_INTERPRETER] = "-i ", - [FT_BASENAME] = "-b ", - }; - - String s = string_new(4096); - for (size_t i = 0, n = filetypes->count; i < n; i++) { - const UserFileTypeEntry *ft = filetypes->ptrs[i]; - BUG_ON(ft->type >= ARRAYLEN(flags)); - BUG_ON(ft->name[0] == '-'); - string_append_literal(&s, "ft "); - string_append_cstring(&s, flags[ft->type]); - string_append_escaped_arg(&s, ft->name, true); - string_append_byte(&s, ' '); - string_append_escaped_arg(&s, ft_get_str(ft), true); - string_append_byte(&s, '\n'); - } - return s; -} - -static void free_filetype_entry(UserFileTypeEntry *ft) -{ - if (ft_uses_regex(ft->type)) { - free_cached_regexp(ft->u.regexp); - } else { - free(ft->u.str); - } - free(ft); -} - -void free_filetypes(PointerArray *filetypes) -{ - ptr_array_free_cb(filetypes, FREE_FUNC(free_filetype_entry)); -} diff --git a/examples/dte/filetype.h b/examples/dte/filetype.h deleted file mode 100644 index cec4d85..0000000 --- a/examples/dte/filetype.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef FILETYPE_H -#define FILETYPE_H - -#include <stdbool.h> -#include <string.h> -#include "util/macros.h" -#include "util/ptr-array.h" -#include "util/string-view.h" -#include "util/string.h" - -// Note: the order of these values changes the order of iteration -// in find_ft() -typedef enum { - FT_INTERPRETER, - FT_BASENAME, - FT_CONTENT, - FT_EXTENSION, - FT_FILENAME, -} FileDetectionType; - -PURE -static inline bool is_valid_filetype_name(const char *name) -{ - size_t n = strcspn(name, " \t/"); - return n > 0 && n < 64 && name[n] == '\0' && name[0] != '-'; -} - -bool add_filetype(PointerArray *filetypes, const char *name, const char *str, FileDetectionType type) NONNULL_ARGS WARN_UNUSED_RESULT; -bool is_ft(const PointerArray *filetypes, const char *name); -const char *find_ft(const PointerArray *filetypes, const char *filename, StringView line); -void collect_ft(const PointerArray *filetypes, PointerArray *a, const char *prefix); -String dump_filetypes(const PointerArray *filetypes); -void free_filetypes(PointerArray *filetypes); - -#endif diff --git a/examples/dte/frame.c b/examples/dte/frame.c deleted file mode 100644 index c06b2d8..0000000 --- a/examples/dte/frame.c +++ /dev/null @@ -1,496 +0,0 @@ -#include "frame.h" -#include "editor.h" -#include "util/debug.h" -#include "util/xmalloc.h" -#include "window.h" - -enum { - WINDOW_MIN_WIDTH = 8, - WINDOW_MIN_HEIGHT = 3, -}; - -static void sanity_check_frame(const Frame *frame) -{ - bool has_window = !!frame->window; - bool has_frames = frame->frames.count > 0; - if (has_window == has_frames) { - BUG("frames must contain a window or subframe(s), but never both"); - } - BUG_ON(has_window && frame != frame->window->frame); -} - -static int get_min_w(const Frame *frame) -{ - if (frame->window) { - return WINDOW_MIN_WIDTH; - } - - const PointerArray *subframes = &frame->frames; - const size_t count = subframes->count; - if (!frame->vertical) { - int w = count - 1; // Separators - for (size_t i = 0; i < count; i++) { - w += get_min_w(subframes->ptrs[i]); - } - return w; - } - - int max = 0; - for (size_t i = 0; i < count; i++) { - int w = get_min_w(subframes->ptrs[i]); - max = MAX(w, max); - } - return max; -} - -static int get_min_h(const Frame *frame) -{ - if (frame->window) { - return WINDOW_MIN_HEIGHT; - } - - const PointerArray *subframes = &frame->frames; - const size_t count = subframes->count; - if (frame->vertical) { - int h = 0; - for (size_t i = 0; i < count; i++) { - h += get_min_h(subframes->ptrs[i]); - } - return h; - } - - int max = 0; - for (size_t i = 0; i < count; i++) { - int h = get_min_h(subframes->ptrs[i]); - max = MAX(h, max); - } - return max; -} - -static int get_min(const Frame *frame) -{ - return frame->parent->vertical ? get_min_h(frame) : get_min_w(frame); -} - -static int get_size(const Frame *frame) -{ - return frame->parent->vertical ? frame->h : frame->w; -} - -static int get_container_size(const Frame *frame) -{ - return frame->vertical ? frame->h : frame->w; -} - -static void set_size(Frame *frame, int size) -{ - bool vertical = frame->parent->vertical; - int w = vertical ? frame->parent->w : size; - int h = vertical ? size : frame->parent->h; - set_frame_size(frame, w, h); -} - -static void divide_equally(const Frame *frame) -{ - size_t count = frame->frames.count; - BUG_ON(count == 0); - - int *min = xnew(int, count); - for (size_t i = 0; i < count; i++) { - min[i] = get_min(frame->frames.ptrs[i]); - } - - int *size = xnew0(int, count); - int s = get_container_size(frame); - int q, r, used; - size_t n = count; - - // Consume q and r as equally as possible - do { - used = 0; - q = s / n; - r = s % n; - for (size_t i = 0; i < count; i++) { - if (size[i] == 0 && min[i] > q) { - size[i] = min[i]; - used += min[i]; - n--; - } - } - s -= used; - } while (used && n > 0); - - for (size_t i = 0; i < count; i++) { - Frame *c = frame->frames.ptrs[i]; - if (size[i] == 0) { - size[i] = q + (r-- > 0); - } - set_size(c, size[i]); - } - - free(size); - free(min); -} - -static void fix_size(const Frame *frame) -{ - size_t count = frame->frames.count; - int *size = xnew(int, count); - int *min = xnew(int, count); - int total = 0; - for (size_t i = 0; i < count; i++) { - const Frame *c = frame->frames.ptrs[i]; - min[i] = get_min(c); - size[i] = MAX(get_size(c), min[i]); - total += size[i]; - } - - int s = get_container_size(frame); - if (total > s) { - int n = total - s; - for (ssize_t i = count - 1; n > 0 && i >= 0; i--) { - int new_size = MAX(size[i] - n, min[i]); - n -= size[i] - new_size; - size[i] = new_size; - } - } else { - size[count - 1] += s - total; - } - - for (size_t i = 0; i < count; i++) { - set_size(frame->frames.ptrs[i], size[i]); - } - - free(size); - free(min); -} - -static void add_to_sibling_size(Frame *frame, int count) -{ - const Frame *parent = frame->parent; - size_t idx = ptr_array_idx(&parent->frames, frame); - BUG_ON(idx >= parent->frames.count); - if (idx == parent->frames.count - 1) { - frame = parent->frames.ptrs[idx - 1]; - } else { - frame = parent->frames.ptrs[idx + 1]; - } - set_size(frame, get_size(frame) + count); -} - -static int sub(Frame *frame, int count) -{ - int min = get_min(frame); - int old = get_size(frame); - int new = MAX(min, old - count); - if (new != old) { - set_size(frame, new); - } - return count - (old - new); -} - -static void subtract_from_sibling_size(const Frame *frame, int count) -{ - const Frame *parent = frame->parent; - size_t idx = ptr_array_idx(&parent->frames, frame); - BUG_ON(idx >= parent->frames.count); - - for (size_t i = idx + 1, n = parent->frames.count; i < n; i++) { - count = sub(parent->frames.ptrs[i], count); - if (count == 0) { - return; - } - } - - for (size_t i = idx; i > 0; i--) { - count = sub(parent->frames.ptrs[i - 1], count); - if (count == 0) { - return; - } - } -} - -static void resize_to(Frame *frame, int size) -{ - const Frame *parent = frame->parent; - int total = parent->vertical ? parent->h : parent->w; - int count = parent->frames.count; - int min = get_min(frame); - int max = total - (count - 1) * min; - max = MAX(min, max); - size = CLAMP(size, min, max); - - int change = size - get_size(frame); - if (change == 0) { - return; - } - - set_size(frame, size); - if (change < 0) { - add_to_sibling_size(frame, -change); - } else { - subtract_from_sibling_size(frame, change); - } -} - -static bool rightmost_frame(const Frame *frame) -{ - const Frame *parent = frame->parent; - if (!parent) { - return true; - } - if (!parent->vertical) { - if (frame != parent->frames.ptrs[parent->frames.count - 1]) { - return false; - } - } - return rightmost_frame(parent); -} - -static Frame *new_frame(void) -{ - Frame *frame = xnew0(Frame, 1); - frame->equal_size = true; - return frame; -} - -static Frame *add_frame(Frame *parent, Window *window, size_t idx) -{ - Frame *frame = new_frame(); - frame->parent = parent; - frame->window = window; - window->frame = frame; - if (parent) { - BUG_ON(idx > parent->frames.count); - ptr_array_insert(&parent->frames, frame, idx); - parent->window = NULL; - } - return frame; -} - -Frame *new_root_frame(Window *window) -{ - return add_frame(NULL, window, 0); -} - -static Frame *find_resizable(Frame *frame, ResizeDirection dir) -{ - if (dir == RESIZE_DIRECTION_AUTO) { - return frame; - } - - while (frame->parent) { - if (dir == RESIZE_DIRECTION_VERTICAL && frame->parent->vertical) { - return frame; - } - if (dir == RESIZE_DIRECTION_HORIZONTAL && !frame->parent->vertical) { - return frame; - } - frame = frame->parent; - } - return NULL; -} - -void set_frame_size(Frame *frame, int w, int h) -{ - int min_w = get_min_w(frame); - int min_h = get_min_h(frame); - w = MAX(w, min_w); - h = MAX(h, min_h); - frame->w = w; - frame->h = h; - - if (frame->window) { - w -= rightmost_frame(frame) ? 0 : 1; // Separator - set_window_size(frame->window, w, h); - return; - } - - if (frame->equal_size) { - divide_equally(frame); - } else { - fix_size(frame); - } -} - -void equalize_frame_sizes(Frame *parent) -{ - parent->equal_size = true; - divide_equally(parent); - update_window_coordinates(parent); -} - -void resize_frame(Frame *frame, ResizeDirection dir, int size) -{ - frame = find_resizable(frame, dir); - if (!frame) { - return; - } - - Frame *parent = frame->parent; - parent->equal_size = false; - resize_to(frame, size); - update_window_coordinates(parent); -} - -void add_to_frame_size(Frame *frame, ResizeDirection dir, int amount) -{ - resize_frame(frame, dir, get_size(frame) + amount); -} - -static void update_frame_coordinates(const Frame *frame, int x, int y) -{ - if (frame->window) { - set_window_coordinates(frame->window, x, y); - return; - } - - for (size_t i = 0, n = frame->frames.count; i < n; i++) { - const Frame *c = frame->frames.ptrs[i]; - update_frame_coordinates(c, x, y); - if (frame->vertical) { - y += c->h; - } else { - x += c->w; - } - } -} - -static Frame *get_root_frame(Frame *frame) -{ - BUG_ON(!frame); - while (frame->parent) { - frame = frame->parent; - } - return frame; -} - -void update_window_coordinates(Frame *frame) -{ - update_frame_coordinates(get_root_frame(frame), 0, 0); -} - -Frame *split_frame(Window *window, bool vertical, bool before) -{ - Frame *frame = window->frame; - Frame *parent = frame->parent; - if (!parent || parent->vertical != vertical) { - // Reparent window - frame->vertical = vertical; - add_frame(frame, window, 0); - parent = frame; - } - - size_t idx = ptr_array_idx(&parent->frames, window->frame); - BUG_ON(idx >= parent->frames.count); - idx += before ? 0 : 1; - frame = add_frame(parent, new_window(window->editor), idx); - parent->equal_size = true; - - // Recalculate - set_frame_size(parent, parent->w, parent->h); - update_window_coordinates(parent); - return frame; -} - -// Doesn't really split root but adds new frame between root and its contents -Frame *split_root_frame(EditorState *e, bool vertical, bool before) -{ - Frame *old_root = e->root_frame; - Frame *new_root = new_frame(); - ptr_array_append(&new_root->frames, old_root); - old_root->parent = new_root; - new_root->vertical = vertical; - e->root_frame = new_root; - - Frame *frame = add_frame(new_root, new_window(e), before ? 0 : 1); - set_frame_size(new_root, old_root->w, old_root->h); - update_window_coordinates(new_root); - return frame; -} - -// NOTE: does not remove frame from frame->parent->frames -static void free_frame(Frame *frame) -{ - frame->parent = NULL; - ptr_array_free_cb(&frame->frames, FREE_FUNC(free_frame)); - - if (frame->window) { - window_free(frame->window); - frame->window = NULL; - } - - free(frame); -} - -void remove_frame(EditorState *e, Frame *frame) -{ - Frame *parent = frame->parent; - if (!parent) { - free_frame(frame); - return; - } - - ptr_array_remove(&parent->frames, frame); - free_frame(frame); - - if (parent->frames.count == 1) { - // Replace parent with the only child frame - Frame *gp = parent->parent; - Frame *c = parent->frames.ptrs[0]; - c->parent = gp; - c->w = parent->w; - c->h = parent->h; - if (gp) { - size_t idx = ptr_array_idx(&gp->frames, parent); - BUG_ON(idx >= gp->frames.count); - gp->frames.ptrs[idx] = c; - } else { - e->root_frame = c; - } - free(parent->frames.ptrs); - free(parent); - parent = c; - } - - // Recalculate - set_frame_size(parent, parent->w, parent->h); - update_window_coordinates(parent); -} - -void dump_frame(const Frame *frame, size_t level, String *str) -{ - sanity_check_frame(frame); - string_append_memset(str, ' ', level * 4); - string_sprintf(str, "%dx%d", frame->w, frame->h); - - const Window *win = frame->window; - if (win) { - string_append_byte(str, '\n'); - string_append_memset(str, ' ', (level + 1) * 4); - string_sprintf(str, "%d,%d %dx%d ", win->x, win->y, win->w, win->h); - string_append_cstring(str, buffer_filename(win->view->buffer)); - string_append_byte(str, '\n'); - return; - } - - string_append_cstring(str, frame->vertical ? " V" : " H"); - string_append_cstring(str, frame->equal_size ? "\n" : " !\n"); - - for (size_t i = 0, n = frame->frames.count; i < n; i++) { - const Frame *c = frame->frames.ptrs[i]; - dump_frame(c, level + 1, str); - } -} - -#if DEBUG >= 1 -void debug_frame(const Frame *frame) -{ - sanity_check_frame(frame); - for (size_t i = 0, n = frame->frames.count; i < n; i++) { - const Frame *c = frame->frames.ptrs[i]; - BUG_ON(c->parent != frame); - debug_frame(c); - } -} -#endif diff --git a/examples/dte/frame.h b/examples/dte/frame.h deleted file mode 100644 index 022cf2b..0000000 --- a/examples/dte/frame.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef FRAME_H -#define FRAME_H - -#include <stdbool.h> -#include <stddef.h> -#include "util/macros.h" -#include "util/ptr-array.h" -#include "util/string.h" - -// A container for other Frames or Windows. Frames and Windows form -// a tree structure, wherein Windows are the terminal (leaf) nodes. -typedef struct Frame { - struct Frame *parent; - // Every frame contains either one window or multiple subframes - PointerArray frames; - struct Window *window; - int w; // Width - int h; // Height - bool vertical; - bool equal_size; -} Frame; - -typedef enum { - RESIZE_DIRECTION_AUTO, - RESIZE_DIRECTION_HORIZONTAL, - RESIZE_DIRECTION_VERTICAL, -} ResizeDirection; - -struct EditorState; - -Frame *new_root_frame(struct Window *window); -void set_frame_size(Frame *frame, int w, int h); -void equalize_frame_sizes(Frame *parent); -void add_to_frame_size(Frame *frame, ResizeDirection dir, int amount); -void resize_frame(Frame *frame, ResizeDirection dir, int size); -void update_window_coordinates(Frame *frame); -Frame *split_frame(struct Window *window, bool vertical, bool before); -Frame *split_root_frame(struct EditorState *e, bool vertical, bool before); -void remove_frame(struct EditorState *e, Frame *frame); -void dump_frame(const Frame *frame, size_t level, String *str); - -#if DEBUG >= 1 - void debug_frame(const Frame *frame); -#else - static inline void debug_frame(const Frame* UNUSED_ARG(frame)) {} -#endif - -#endif diff --git a/examples/dte/history.c b/examples/dte/history.c deleted file mode 100644 index d03a6c2..0000000 --- a/examples/dte/history.c +++ /dev/null @@ -1,146 +0,0 @@ -#include <errno.h> -#include <stdlib.h> -#include <string.h> -#include "history.h" -#include "error.h" -#include "util/debug.h" -#include "util/readfile.h" -#include "util/str-util.h" -#include "util/xmalloc.h" -#include "util/xstdio.h" - -void history_add(History *history, const char *text) -{ - BUG_ON(history->max_entries < 2); - if (text[0] == '\0') { - return; - } - - HashMap *map = &history->entries; - HistoryEntry *e = hashmap_get(map, text); - - if (e) { - if (e == history->last) { - // Existing entry already at end of list; nothing more to do - return; - } - // Remove existing entry from list - e->next->prev = e->prev; - if (unlikely(e == history->first)) { - history->first = e->next; - } else { - e->prev->next = e->next; - } - } else { - if (map->count == history->max_entries) { - // History is full; recycle oldest entry - HistoryEntry *old_first = history->first; - HistoryEntry *new_first = old_first->next; - new_first->prev = NULL; - history->first = new_first; - e = hashmap_remove(map, old_first->text); - BUG_ON(e != old_first); - } else { - e = xnew(HistoryEntry, 1); - } - e->text = xstrdup(text); - hashmap_insert(map, e->text, e); - } - - // Insert entry at end of list - HistoryEntry *old_last = history->last; - e->next = NULL; - e->prev = old_last; - history->last = e; - if (likely(old_last)) { - old_last->next = e; - } else { - history->first = e; - } -} - -bool history_search_forward ( - const History *history, - const HistoryEntry **pos, - const char *text -) { - const HistoryEntry *start = *pos ? (*pos)->prev : history->last; - for (const HistoryEntry *e = start; e; e = e->prev) { - if (str_has_prefix(e->text, text)) { - *pos = e; - return true; - } - } - return false; -} - -bool history_search_backward ( - const History *history, - const HistoryEntry **pos, - const char *text -) { - const HistoryEntry *start = *pos ? (*pos)->next : history->first; - for (const HistoryEntry *e = start; e; e = e->next) { - if (str_has_prefix(e->text, text)) { - *pos = e; - return true; - } - } - return false; -} - -void history_load(History *history, char *filename) -{ - BUG_ON(!history); - BUG_ON(!filename); - BUG_ON(history->filename); - BUG_ON(history->max_entries < 2); - - hashmap_init(&history->entries, history->max_entries); - history->filename = filename; - - char *buf; - const ssize_t ssize = read_file(filename, &buf); - if (ssize < 0) { - if (errno != ENOENT) { - error_msg("Error reading %s: %s", filename, strerror(errno)); - } - return; - } - - for (size_t pos = 0, size = ssize; pos < size; ) { - history_add(history, buf_next_line(buf, &pos, size)); - } - - free(buf); -} - -void history_save(const History *history) -{ - const char *filename = history->filename; - if (!filename) { - return; - } - - FILE *f = xfopen(filename, "w", O_CLOEXEC, 0666); - if (!f) { - error_msg("Error creating %s: %s", filename, strerror(errno)); - return; - } - - for (const HistoryEntry *e = history->first; e; e = e->next) { - xfputs(e->text, f); - xfputc('\n', f); - } - - fclose(f); -} - -void history_free(History *history) -{ - hashmap_free(&history->entries, free); - free(history->filename); - history->filename = NULL; - history->first = NULL; - history->last = NULL; -} diff --git a/examples/dte/history.h b/examples/dte/history.h deleted file mode 100644 index 12073f5..0000000 --- a/examples/dte/history.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef HISTORY_H -#define HISTORY_H - -#include <stdbool.h> -#include <stddef.h> -#include "util/hashmap.h" -#include "util/macros.h" - -typedef struct HistoryEntry { - struct HistoryEntry *next; - struct HistoryEntry *prev; - char *text; -} HistoryEntry; - -// This is a HashMap with a doubly-linked list running through the -// entries, in a way similar to the Java LinkedHashMap class. The -// HashMap allows duplicates to be found and re-inserted at the end -// of the list in O(1) time and the doubly-linked entries allow -// ordered traversal. -typedef struct { - char *filename; - HashMap entries; - HistoryEntry *first; - HistoryEntry *last; - size_t max_entries; -} History; - -void history_add(History *history, const char *text); -bool history_search_forward(const History *history, const HistoryEntry **pos, const char *text) WARN_UNUSED_RESULT; -bool history_search_backward(const History *history, const HistoryEntry **pos, const char *text) WARN_UNUSED_RESULT; -void history_load(History *history, char *filename); -void history_save(const History *history); -void history_free(History *history); - -#endif diff --git a/examples/dte/indent.c b/examples/dte/indent.c deleted file mode 100644 index 0d5d2ae..0000000 --- a/examples/dte/indent.c +++ /dev/null @@ -1,193 +0,0 @@ -#include <sys/types.h> -#include "indent.h" -#include "regexp.h" -#include "util/xmalloc.h" - -char *make_indent(const LocalOptions *options, size_t width) -{ - if (width == 0) { - return NULL; - } - - if (use_spaces_for_indent(options)) { - char *str = xmalloc(width + 1); - str[width] = '\0'; - return memset(str, ' ', width); - } - - size_t tw = options->tab_width; - size_t ntabs = indent_level(width, tw); - size_t nspaces = indent_remainder(width, tw); - size_t n = ntabs + nspaces; - char *str = xmalloc(n + 1); - memset(str + ntabs, ' ', nspaces); - str[n] = '\0'; - return memset(str, '\t', ntabs); -} - -static bool indent_inc(const LocalOptions *options, const StringView *line) -{ - static regex_t re1, re2; - static bool compiled; - if (!compiled) { - // TODO: Make these patterns configurable via a local option - static const char pat1[] = "\\{[\t ]*(//.*|/\\*.*\\*/[\t ]*)?$"; - static const char pat2[] = "\\}[\t ]*(//.*|/\\*.*\\*/[\t ]*)?$"; - regexp_compile_or_fatal_error(&re1, pat1, REG_NEWLINE | REG_NOSUB); - regexp_compile_or_fatal_error(&re2, pat2, REG_NEWLINE | REG_NOSUB); - compiled = true; - } - - if (options->brace_indent) { - regmatch_t m; - if (regexp_exec(&re1, line->data, line->length, 0, &m, 0)) { - return true; - } - if (regexp_exec(&re2, line->data, line->length, 0, &m, 0)) { - return false; - } - } - - const InternedRegexp *ir = options->indent_regex; - if (!ir) { - return false; - } - - BUG_ON(ir->str[0] == '\0'); - regmatch_t m; - return regexp_exec(&ir->re, line->data, line->length, 0, &m, 0); -} - -char *get_indent_for_next_line(const LocalOptions *options, const StringView *line) -{ - size_t width = get_indent_width(options, line); - if (indent_inc(options, line)) { - width = next_indent_width(width, options->indent_width); - } - return make_indent(options, width); -} - -IndentInfo get_indent_info(const LocalOptions *options, const StringView *line) -{ - const char *buf = line->data; - const size_t len = line->length; - const size_t tw = options->tab_width; - const size_t iw = options->indent_width; - const bool space_indent = use_spaces_for_indent(options); - IndentInfo info = {.sane = true}; - size_t spaces = 0; - size_t tabs = 0; - size_t pos = 0; - - for (; pos < len; pos++) { - if (buf[pos] == ' ') { - info.width++; - spaces++; - } else if (buf[pos] == '\t') { - info.width = next_indent_width(info.width, tw); - tabs++; - } else { - break; - } - if (indent_remainder(info.width, iw) == 0 && info.sane) { - info.sane = space_indent ? !tabs : !spaces; - } - } - - info.level = indent_level(info.width, iw); - info.wsonly = (pos == len); - info.bytes = spaces + tabs; - return info; -} - -size_t get_indent_width(const LocalOptions *options, const StringView *line) -{ - const char *buf = line->data; - size_t width = 0; - for (size_t i = 0, n = line->length, tw = options->tab_width; i < n; i++) { - if (buf[i] == ' ') { - width++; - } else if (buf[i] == '\t') { - width = next_indent_width(width, tw); - } else { - break; - } - } - return width; -} - -static ssize_t get_current_indent_bytes(const LocalOptions *options, const char *buf, size_t cursor_offset) -{ - const size_t tw = options->tab_width; - const size_t iw = options->indent_width; - size_t ibytes = 0; - size_t iwidth = 0; - - for (size_t i = 0; i < cursor_offset; i++) { - if (indent_remainder(iwidth, iw) == 0) { - ibytes = 0; - iwidth = 0; - } - switch (buf[i]) { - case '\t': - iwidth = next_indent_width(iwidth, tw); - break; - case ' ': - iwidth++; - break; - default: - // Cursor not at indentation - return -1; - } - ibytes++; - } - - if (indent_remainder(iwidth, iw)) { - // Cursor at middle of indentation level - return -1; - } - - return (ssize_t)ibytes; -} - -size_t get_indent_level_bytes_left(const LocalOptions *options, BlockIter *cursor) -{ - StringView line; - size_t cursor_offset = fetch_this_line(cursor, &line); - if (!cursor_offset) { - return 0; - } - ssize_t ibytes = get_current_indent_bytes(options, line.data, cursor_offset); - return (ibytes < 0) ? 0 : (size_t)ibytes; -} - -size_t get_indent_level_bytes_right(const LocalOptions *options, BlockIter *cursor) -{ - StringView line; - size_t cursor_offset = fetch_this_line(cursor, &line); - ssize_t ibytes = get_current_indent_bytes(options, line.data, cursor_offset); - if (ibytes < 0) { - return 0; - } - - const size_t tw = options->tab_width; - const size_t iw = options->indent_width; - size_t iwidth = 0; - for (size_t i = cursor_offset, n = line.length; i < n; i++) { - switch (line.data[i]) { - case '\t': - iwidth = next_indent_width(iwidth, tw); - break; - case ' ': - iwidth++; - break; - default: - // No full indentation level at cursor position - return 0; - } - if (indent_remainder(iwidth, iw) == 0) { - return i - cursor_offset + 1; - } - } - return 0; -} diff --git a/examples/dte/indent.h b/examples/dte/indent.h deleted file mode 100644 index 6198546..0000000 --- a/examples/dte/indent.h +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef INDENT_H -#define INDENT_H - -#include <stdbool.h> -#include <stddef.h> -#include <string.h> -#include <strings.h> -#include "block-iter.h" -#include "options.h" -#include "util/debug.h" -#include "util/macros.h" -#include "util/string-view.h" - -typedef struct { - size_t bytes; // Size in bytes - size_t width; // Width in columns - size_t level; // Number of whole `indent-width` levels - bool wsonly; // Empty or whitespace-only line - - // Only spaces or tabs, depending on `use_spaces_for_indent()`. - // Note that a "sane" line can contain spaces after tabs for alignment. - bool sane; -} IndentInfo; - -// Divide `x` by `d`, to obtain the number of whole indent levels. -// If `d` is a power of 2, shift right by `ffs(d) - 1` instead, to -// avoid the expensive divide operation. This optimization applies -// to widths of 1, 2, 4 and 8, which covers all of the sensible ones. -static inline size_t indent_level(size_t x, size_t d) -{ - BUG_ON(d - 1 > 7); - return likely(IS_POWER_OF_2(d)) ? x >> (ffs(d) - 1) : x / d; -} - -static inline size_t indent_remainder(size_t x, size_t m) -{ - BUG_ON(m - 1 > 7); - return likely(IS_POWER_OF_2(m)) ? x & (m - 1) : x % m; -} - -static inline size_t next_indent_width(size_t x, size_t mul) -{ - BUG_ON(mul - 1 > 7); - if (likely(IS_POWER_OF_2(mul))) { - size_t mask = ~(mul - 1); - return (x & mask) + mul; - } - return ((x + mul) / mul) * mul; -} - -char *make_indent(const LocalOptions *options, size_t width); -char *get_indent_for_next_line(const LocalOptions *options, const StringView *line); -IndentInfo get_indent_info(const LocalOptions *options, const StringView *line); -size_t get_indent_width(const LocalOptions *options, const StringView *line); -size_t get_indent_level_bytes_left(const LocalOptions *options, BlockIter *cursor); -size_t get_indent_level_bytes_right(const LocalOptions *options, BlockIter *cursor); - -#endif diff --git a/examples/dte/load-save.c b/examples/dte/load-save.c deleted file mode 100644 index b3ea3fa..0000000 --- a/examples/dte/load-save.c +++ /dev/null @@ -1,505 +0,0 @@ -#include "compat.h" -#include <errno.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/mman.h> -#include <sys/stat.h> -#include <unistd.h> -#include "load-save.h" -#include "block.h" -#include "convert.h" -#include "encoding.h" -#include "error.h" -#include "util/debug.h" -#include "util/fd.h" -#include "util/list.h" -#include "util/log.h" -#include "util/path.h" -#include "util/str-util.h" -#include "util/time-util.h" -#include "util/xmalloc.h" -#include "util/xreadwrite.h" - -static void add_block(Buffer *buffer, Block *blk) -{ - buffer->nl += blk->nl; - list_add_before(&blk->node, &buffer->blocks); -} - -static Block *add_utf8_line ( - Buffer *buffer, - Block *blk, - const unsigned char *line, - size_t len -) { - size_t size = len + 1; - if (blk) { - size_t avail = blk->alloc - blk->size; - if (size <= avail) { - goto copy; - } - add_block(buffer, blk); - } - size = MAX(size, 8192); - blk = block_new(size); -copy: - memcpy(blk->data + blk->size, line, len); - blk->size += len; - blk->data[blk->size++] = '\n'; - blk->nl++; - return blk; -} - -static bool decode_and_add_blocks(Buffer *buffer, const unsigned char *buf, size_t size, bool utf8_bom) -{ - EncodingType bom_type = detect_encoding_from_bom(buf, size); - EncodingType enc_type = buffer->encoding.type; - if (enc_type == ENCODING_AUTODETECT) { - if (bom_type != UNKNOWN_ENCODING) { - BUG_ON(buffer->encoding.name); - Encoding e = encoding_from_type(bom_type); - if (conversion_supported_by_iconv(e.name, "UTF-8")) { - buffer_set_encoding(buffer, e, utf8_bom); - } else { - buffer_set_encoding(buffer, encoding_from_type(UTF8), utf8_bom); - } - } - } - - // Skip BOM only if it matches the specified file encoding - if (bom_type != UNKNOWN_ENCODING && bom_type == buffer->encoding.type) { - const ByteOrderMark *bom = get_bom_for_encoding(bom_type); - if (bom) { - const size_t bom_len = bom->len; - buf += bom_len; - size -= bom_len; - buffer->bom = true; - } - } - - FileDecoder *dec = new_file_decoder(buffer->encoding.name, buf, size); - if (!dec) { - return false; - } - - const char *line; - size_t len; - if (file_decoder_read_line(dec, &line, &len)) { - if (len && line[len - 1] == '\r') { - buffer->crlf_newlines = true; - len--; - } - Block *blk = add_utf8_line(buffer, NULL, line, len); - while (file_decoder_read_line(dec, &line, &len)) { - if (buffer->crlf_newlines && len && line[len - 1] == '\r') { - len--; - } - blk = add_utf8_line(buffer, blk, line, len); - } - if (blk) { - add_block(buffer, blk); - } - } - - if (buffer->encoding.type == ENCODING_AUTODETECT) { - const char *enc = file_decoder_get_encoding(dec); - buffer_set_encoding(buffer, encoding_from_name(enc ? enc : "UTF-8"), utf8_bom); - } - - free_file_decoder(dec); - return true; -} - -static void fixup_blocks(Buffer *buffer) -{ - if (list_empty(&buffer->blocks)) { - Block *blk = block_new(1); - list_add_before(&blk->node, &buffer->blocks); - } else { - // Incomplete lines are not allowed because they are special cases - // and cause lots of trouble - Block *blk = BLOCK(buffer->blocks.prev); - if (blk->size && blk->data[blk->size - 1] != '\n') { - if (blk->size == blk->alloc) { - blk->alloc = round_size_to_next_multiple(blk->size + 1, 64); - xrenew(blk->data, blk->alloc); - } - blk->data[blk->size++] = '\n'; - blk->nl++; - buffer->nl++; - } - } -} - -static int xmadvise_sequential(void *addr, size_t len) -{ -#if HAVE_POSIX_MADVISE - return posix_madvise(addr, len, POSIX_MADV_SEQUENTIAL); -#else - // "The posix_madvise() function shall have no effect on the semantics - // of access to memory in the specified range, although it may affect - // the performance of access". Ergo, doing nothing is a valid fallback. - (void)addr; - (void)len; - return 0; -#endif -} - -static bool update_file_info(FileInfo *info, const struct stat *st) -{ - *info = (FileInfo) { - .size = st->st_size, - .mode = st->st_mode, - .gid = st->st_gid, - .uid = st->st_uid, - .dev = st->st_dev, - .ino = st->st_ino, - .mtime = *get_stat_mtime(st), - }; - return true; -} - -static bool buffer_stat(FileInfo *info, const char *filename) -{ - struct stat st; - return !stat(filename, &st) && update_file_info(info, &st); -} - -static bool buffer_fstat(FileInfo *info, int fd) -{ - struct stat st; - return !fstat(fd, &st) && update_file_info(info, &st); -} - -bool read_blocks(Buffer *buffer, int fd, bool utf8_bom) -{ - const size_t map_size = 64 * 1024; - size_t size = buffer->file.size; - unsigned char *buf = NULL; - bool mapped = false; - bool ret = false; - - if (size >= map_size) { - // NOTE: size must be greater than 0 - buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); - if (buf != MAP_FAILED) { - xmadvise_sequential(buf, size); - mapped = true; - goto decode; - } - buf = NULL; - } - - if (likely(size > 0)) { - buf = malloc(size); - if (unlikely(!buf)) { - goto error; - } - ssize_t rc = xread_all(fd, buf, size); - if (unlikely(rc < 0)) { - goto error; - } - size = rc; - } else { - // st_size is zero for some files in /proc - size_t alloc = map_size; - BUG_ON(!IS_POWER_OF_2(alloc)); - buf = malloc(alloc); - if (unlikely(!buf)) { - goto error; - } - size_t pos = 0; - while (1) { - ssize_t rc = xread_all(fd, buf + pos, alloc - pos); - if (rc < 0) { - goto error; - } - if (rc == 0) { - break; - } - pos += rc; - if (pos == alloc) { - size_t new_alloc = alloc << 1; - if (unlikely(alloc >= new_alloc)) { - errno = EOVERFLOW; - goto error; - } - alloc = new_alloc; - char *new_buf = realloc(buf, alloc); - if (unlikely(!new_buf)) { - goto error; - } - buf = new_buf; - } - } - size = pos; - } - -decode: - ret = decode_and_add_blocks(buffer, buf, size, utf8_bom); - -error: - if (mapped) { - munmap(buf, size); - } else { - free(buf); - } - - if (ret) { - fixup_blocks(buffer); - } - - return ret; -} - -bool load_buffer(Buffer *buffer, const char *filename, const GlobalOptions *gopts, bool must_exist) -{ - int fd = xopen(filename, O_RDONLY | O_CLOEXEC, 0); - - if (fd < 0) { - if (errno != ENOENT) { - return error_msg("Error opening %s: %s", filename, strerror(errno)); - } - if (must_exist) { - return error_msg("File %s does not exist", filename); - } - fixup_blocks(buffer); - } else { - if (!buffer_fstat(&buffer->file, fd)) { - error_msg("fstat failed on %s: %s", filename, strerror(errno)); - goto error; - } - if (!S_ISREG(buffer->file.mode)) { - error_msg("Not a regular file %s", filename); - goto error; - } - if (unlikely(buffer->file.size < 0)) { - error_msg("Invalid file size: %jd", (intmax_t)buffer->file.size); - goto error; - } - if (buffer->file.size / 1024 / 1024 > gopts->filesize_limit) { - error_msg ( - "File size exceeds 'filesize-limit' option (%uMiB): %s", - gopts->filesize_limit, filename - ); - goto error; - } - if (!read_blocks(buffer, fd, gopts->utf8_bom)) { - error_msg("Error reading %s: %s", filename, strerror(errno)); - goto error; - } - xclose(fd); - } - - if (buffer->encoding.type == ENCODING_AUTODETECT) { - Encoding enc = encoding_from_type(UTF8); - buffer_set_encoding(buffer, enc, gopts->utf8_bom); - } - - return true; - -error: - xclose(fd); - return false; -} - -static mode_t get_umask(void) -{ - // Wonderful get-and-set API - mode_t old = umask(0); - umask(old); - return old; -} - -static bool write_buffer(Buffer *buffer, FileEncoder *enc, int fd, EncodingType bom_type) -{ - size_t size = 0; - const ByteOrderMark *bom = get_bom_for_encoding(bom_type); - if (bom) { - size = bom->len; - BUG_ON(size == 0); - if (xwrite_all(fd, bom->bytes, size) < 0) { - return error_msg_errno("write"); - } - } - - Block *blk; - block_for_each(blk, &buffer->blocks) { - ssize_t rc = file_encoder_write(enc, blk->data, blk->size); - if (rc < 0) { - return error_msg_errno("write"); - } - size += rc; - } - - size_t nr_errors = file_encoder_get_nr_errors(enc); - if (nr_errors > 0) { - // Any real error hides this message - error_msg ( - "Warning: %zu non-reversible character conversion%s; file saved", - nr_errors, - (nr_errors > 1) ? "s" : "" - ); - } - - // Need to truncate if writing to existing file - if (xftruncate(fd, size)) { - return error_msg_errno("ftruncate"); - } - - return true; -} - -static int tmp_file(const char *filename, const FileInfo *info, char *buf, size_t buflen) -{ - if (str_has_prefix(filename, "/tmp/")) { - // Don't use temporary file when saving file in /tmp because crontab - // command doesn't like the file to be replaced - return -1; - } - - const char *base = path_basename(filename); - const StringView dir = path_slice_dirname(filename); - const int dlen = (int)dir.length; - int n = snprintf(buf, buflen, "%.*s/.tmp.%s.XXXXXX", dlen, dir.data, base); - if (unlikely(n <= 0 || n >= buflen)) { - buf[0] = '\0'; - return -1; - } - - int fd = mkstemp(buf); - if (fd < 0) { - // No write permission to the directory? - buf[0] = '\0'; - return -1; - } - - if (!info->mode) { - // New file - if (xfchmod(fd, 0666 & ~get_umask()) != 0) { - LOG_WARNING("failed to set file mode: %s", strerror(errno)); - } - return fd; - } - - // Preserve ownership and mode of the original file if possible - if (xfchown(fd, info->uid, info->gid) != 0) { - LOG_WARNING("failed to preserve file ownership: %s", strerror(errno)); - } - if (xfchmod(fd, info->mode) != 0) { - LOG_WARNING("failed to preserve file mode: %s", strerror(errno)); - } - - return fd; -} - -static int xfsync(int fd) -{ -#if HAVE_FSYNC - retry: - if (fsync(fd) == 0) { - return 0; - } - - switch (errno) { - // EINVAL is ignored because it just means "operation not possible - // on this descriptor" rather than indicating an actual error - case EINVAL: - case ENOTSUP: - case ENOSYS: - return 0; - case EINTR: - goto retry; - } - - return -1; -#else - (void)fd; - return 0; -#endif -} - -bool save_buffer ( - Buffer *buffer, - const char *filename, - const Encoding *encoding, - bool crlf, - bool write_bom, - bool hardlinks -) { - char tmp[8192]; - tmp[0] = '\0'; - int fd = -1; - if (hardlinks) { - LOG_INFO("target file has hard links; writing in-place"); - } else { - // Try to use temporary file (safer) - fd = tmp_file(filename, &buffer->file, tmp, sizeof(tmp)); - } - - if (fd < 0) { - // Overwrite the original file directly (if it exists). - // Ownership is preserved automatically if the file exists. - mode_t mode = buffer->file.mode; - if (mode == 0) { - // New file - mode = 0666 & ~get_umask(); - } - fd = xopen(filename, O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC, mode); - if (fd < 0) { - return error_msg_errno("open"); - } - } - - FileEncoder *enc = new_file_encoder(encoding, crlf, fd); - if (unlikely(!enc)) { - // This should never happen because encoding is validated early - error_msg_errno("new_file_encoder"); - goto error; - } - - EncodingType bom_type = write_bom ? encoding->type : UNKNOWN_ENCODING; - if (!write_buffer(buffer, enc, fd, bom_type)) { - goto error; - } - - if (buffer->options.fsync && xfsync(fd) != 0) { - error_msg_errno("fsync"); - goto error; - } - - int r = xclose(fd); - fd = -1; - if (r != 0) { - error_msg_errno("close"); - goto error; - } - - if (tmp[0] && rename(tmp, filename)) { - error_msg_errno("rename"); - goto error; - } - - free_file_encoder(enc); - buffer_stat(&buffer->file, filename); - return true; - -error: - if (fd >= 0) { - xclose(fd); - } - if (enc) { - free_file_encoder(enc); - } - if (tmp[0]) { - unlink(tmp); - } else { - // Not using temporary file, therefore mtime may have changed. - // Update stat to avoid "File has been modified by someone else" - // error later when saving the file again. - buffer_stat(&buffer->file, filename); - } - return false; -} diff --git a/examples/dte/load-save.h b/examples/dte/load-save.h deleted file mode 100644 index e8c0a46..0000000 --- a/examples/dte/load-save.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef LOAD_SAVE_H -#define LOAD_SAVE_H - -#include <stdbool.h> -#include "buffer.h" -#include "encoding.h" -#include "options.h" -#include "util/macros.h" - -bool load_buffer(Buffer *buffer, const char *filename, const GlobalOptions *gopts, bool must_exist) WARN_UNUSED_RESULT; -bool save_buffer(Buffer *buffer, const char *filename, const Encoding *encoding, bool crlf, bool write_bom, bool hardlinks) WARN_UNUSED_RESULT; -bool read_blocks(Buffer *buffer, int fd, bool utf8_bom) WARN_UNUSED_RESULT; - -#endif diff --git a/examples/dte/lock.c b/examples/dte/lock.c deleted file mode 100644 index 74cf3a4..0000000 --- a/examples/dte/lock.c +++ /dev/null @@ -1,201 +0,0 @@ -#include <errno.h> -#include <signal.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> -#include <time.h> -#include <unistd.h> -#include "lock.h" -#include "error.h" -#include "util/debug.h" -#include "util/log.h" -#include "util/path.h" -#include "util/readfile.h" -#include "util/str-util.h" -#include "util/string-view.h" -#include "util/strtonum.h" -#include "util/xmalloc.h" -#include "util/xreadwrite.h" -#include "util/xsnprintf.h" - -// These are initialized during early startup and then never changed, -// so they're deemed an "acceptable" use of globals: -static const char *file_locks; -static const char *file_locks_lock; -static mode_t file_locks_mode = 0666; -static pid_t editor_pid; - -void init_file_locks_context(const char *fallback_dir, pid_t pid) -{ - BUG_ON(file_locks); - const char *dir = xgetenv("XDG_RUNTIME_DIR"); - if (!dir) { - LOG_INFO("$XDG_RUNTIME_DIR not set"); - dir = fallback_dir; - } else if (unlikely(!path_is_absolute(dir))) { - LOG_WARNING("$XDG_RUNTIME_DIR invalid (not an absolute path)"); - dir = fallback_dir; - } else { - // Set sticky bit (see XDG Base Directory Specification) - #ifdef S_ISVTX - file_locks_mode |= S_ISVTX; - #endif - } - - file_locks = path_join(dir, "dte-locks"); - file_locks_lock = path_join(dir, "dte-locks.lock"); - editor_pid = pid; - LOG_INFO("locks file: %s", file_locks); -} - -static bool process_exists(pid_t pid) -{ - return !kill(pid, 0); -} - -static pid_t rewrite_lock_file(char *buf, size_t *sizep, const char *filename) -{ - const size_t filename_len = strlen(filename); - size_t size = *sizep; - pid_t other_pid = 0; - - for (size_t pos = 0, bol = 0; pos < size; bol = pos) { - StringView line = buf_slice_next_line(buf, &pos, size); - uintmax_t num; - size_t numlen = buf_parse_uintmax(line.data, line.length, &num); - if (unlikely(numlen == 0 || num != (pid_t)num)) { - goto remove_line; - } - - strview_remove_prefix(&line, numlen); - if (unlikely(!strview_has_prefix(&line, " /"))) { - goto remove_line; - } - strview_remove_prefix(&line, 1); - - bool same = strview_equal_strn(&line, filename, filename_len); - pid_t pid = (pid_t)num; - if (pid == editor_pid) { - if (same) { - goto remove_line; - } - continue; - } else if (process_exists(pid)) { - if (same) { - other_pid = pid; - } - continue; - } - - remove_line: - memmove(buf + bol, buf + pos, size - pos); - size -= pos - bol; - pos = bol; - } - - *sizep = size; - return other_pid; -} - -static bool lock_or_unlock(const char *filename, bool lock) -{ - BUG_ON(!file_locks); - if (streq(filename, file_locks) || streq(filename, file_locks_lock)) { - return true; - } - - mode_t mode = file_locks_mode; - int tries = 0; - int wfd; - while (1) { - wfd = xopen(file_locks_lock, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, mode); - if (wfd >= 0) { - break; - } - - if (errno != EEXIST) { - return error_msg("Error creating %s: %s", file_locks_lock, strerror(errno)); - } - if (++tries == 3) { - if (unlink(file_locks_lock)) { - return error_msg ( - "Error removing stale lock file %s: %s", - file_locks_lock, - strerror(errno) - ); - } - error_msg("Stale lock file %s removed", file_locks_lock); - } else { - const struct timespec req = { - .tv_sec = 0, - .tv_nsec = 100 * 1000000, - }; - nanosleep(&req, NULL); - } - } - - char *buf = NULL; - ssize_t ssize = read_file(file_locks, &buf); - if (ssize < 0) { - if (errno != ENOENT) { - error_msg("Error reading %s: %s", file_locks, strerror(errno)); - goto error; - } - ssize = 0; - } - - size_t size = (size_t)ssize; - pid_t pid = rewrite_lock_file(buf, &size, filename); - if (lock) { - if (pid == 0) { - intmax_t p = (intmax_t)editor_pid; - size_t n = strlen(filename) + DECIMAL_STR_MAX(pid) + 4; - xrenew(buf, size + n); - size += xsnprintf(buf + size, n, "%jd %s\n", p, filename); - } else { - intmax_t p = (intmax_t)pid; - error_msg("File is locked (%s) by process %jd", file_locks, p); - } - } - - if (xwrite_all(wfd, buf, size) < 0) { - error_msg("Error writing %s: %s", file_locks_lock, strerror(errno)); - goto error; - } - - int r = xclose(wfd); - wfd = -1; - if (r != 0) { - error_msg("Error closing %s: %s", file_locks_lock, strerror(errno)); - goto error; - } - - if (rename(file_locks_lock, file_locks)) { - const char *err = strerror(errno); - error_msg("Renaming %s to %s: %s", file_locks_lock, file_locks, err); - goto error; - } - - free(buf); - return (pid == 0); - -error: - unlink(file_locks_lock); - free(buf); - if (wfd >= 0) { - xclose(wfd); - } - return false; -} - -bool lock_file(const char *filename) -{ - return lock_or_unlock(filename, true); -} - -void unlock_file(const char *filename) -{ - lock_or_unlock(filename, false); -} diff --git a/examples/dte/lock.h b/examples/dte/lock.h deleted file mode 100644 index 9d2a90a..0000000 --- a/examples/dte/lock.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef LOCK_H -#define LOCK_H - -#include <stdbool.h> -#include <sys/types.h> -#include "util/macros.h" - -void init_file_locks_context(const char *fallback_dir, pid_t pid); -bool lock_file(const char *filename) WARN_UNUSED_RESULT; -void unlock_file(const char *filename); - -#endif diff --git a/examples/dte/main.c b/examples/dte/main.c deleted file mode 100644 index 11025af..0000000 --- a/examples/dte/main.c +++ /dev/null @@ -1,575 +0,0 @@ -#include <errno.h> -#include <fcntl.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> -#include <sys/utsname.h> -#include <unistd.h> -#include "block.h" -#include "commands.h" -#include "compiler.h" -#include "config.h" -#include "editor.h" -#include "error.h" -#include "file-history.h" -#include "frame.h" -#include "history.h" -#include "load-save.h" -#include "move.h" -#include "screen.h" -#include "search.h" -#include "signals.h" -#include "syntax/state.h" -#include "syntax/syntax.h" -#include "tag.h" -#include "terminal/input.h" -#include "terminal/key.h" -#include "terminal/mode.h" -#include "terminal/output.h" -#include "terminal/terminal.h" -#include "util/debug.h" -#include "util/exitcode.h" -#include "util/fd.h" -#include "util/log.h" -#include "util/macros.h" -#include "util/path.h" -#include "util/ptr-array.h" -#include "util/strtonum.h" -#include "util/xmalloc.h" -#include "util/xreadwrite.h" -#include "util/xsnprintf.h" -#include "view.h" -#include "window.h" -#include "../build/version.h" - -static void term_cleanup(EditorState *e) -{ - set_fatal_error_cleanup_handler(NULL, NULL); - if (!e->child_controls_terminal) { - ui_end(e); - } -} - -static void cleanup_handler(void *userdata) -{ - term_cleanup(userdata); -} - -static ExitCode write_stdout(const char *str, size_t len) -{ - if (xwrite_all(STDOUT_FILENO, str, len) < 0) { - perror("write"); - return EX_IOERR; - } - return EX_OK; -} - -static ExitCode list_builtin_configs(void) -{ - String str = dump_builtin_configs(); - BUG_ON(!str.buffer); - ExitCode e = write_stdout(str.buffer, str.len); - string_free(&str); - return e; -} - -static ExitCode dump_builtin_config(const char *name) -{ - const BuiltinConfig *cfg = get_builtin_config(name); - if (!cfg) { - fprintf(stderr, "Error: no built-in config with name '%s'\n", name); - return EX_USAGE; - } - return write_stdout(cfg->text.data, cfg->text.length); -} - -static ExitCode lint_syntax(const char *filename) -{ - EditorState *e = init_editor_state(); - int err; - BUG_ON(e->status != EDITOR_INITIALIZING); - const Syntax *s = load_syntax_file(e, filename, CFG_MUST_EXIST, &err); - if (s) { - const size_t n = s->states.count; - const char *plural = (n == 1) ? "" : "s"; - printf("OK: loaded syntax '%s' with %zu state%s\n", s->name, n, plural); - } else if (err == EINVAL) { - error_msg("%s: no default syntax found", filename); - } - free_editor_state(e); - return get_nr_errors() ? EX_DATAERR : EX_OK; -} - -static ExitCode showkey_loop(const char *term_name, const char *colorterm) -{ - if (unlikely(!term_raw())) { - perror("tcsetattr"); - return EX_IOERR; - } - - Terminal term; - TermOutputBuffer *obuf = &term.obuf; - TermInputBuffer *ibuf = &term.ibuf; - term_init(&term, term_name, colorterm); - term_input_init(ibuf); - term_output_init(obuf); - term_enable_private_modes(&term); - term_add_literal(obuf, "Press any key combination, or use Ctrl+D to exit\r\n"); - term_output_flush(obuf); - - char keystr[KEYCODE_STR_MAX]; - for (bool loop = true; loop; ) { - KeyCode key = term_read_key(&term, 100); - switch (key) { - case KEY_NONE: - case KEY_IGNORE: - continue; - case KEY_BRACKETED_PASTE: - case KEY_DETECTED_PASTE: - term_discard_paste(ibuf, key == KEY_BRACKETED_PASTE); - continue; - case MOD_CTRL | 'd': - loop = false; - } - size_t keylen = keycode_to_string(key, keystr); - term_add_literal(obuf, " "); - term_add_bytes(obuf, keystr, keylen); - term_add_literal(obuf, "\r\n"); - term_output_flush(obuf); - } - - term_restore_private_modes(&term); - term_output_flush(obuf); - term_cooked(); - term_input_free(ibuf); - term_output_free(obuf); - return EX_OK; -} - -static ExitCode init_std_fds(int std_fds[2]) -{ - FILE *streams[3] = {stdin, stdout, stderr}; - for (int i = 0; i < ARRAYLEN(streams); i++) { - if (is_controlling_tty(i)) { - continue; - } - - if (i < STDERR_FILENO) { - // Try to create a duplicate fd for redirected stdin/stdout; to - // allow reading/writing after freopen(3) closes the original - int fd = fcntl(i, F_DUPFD_CLOEXEC, 3); - if (fd == -1 && errno != EBADF) { - perror("fcntl"); - return EX_OSERR; - } - std_fds[i] = fd; - } - - // Ensure standard streams are connected to the terminal during - // editor operation, regardless of how they were redirected - if (unlikely(!freopen("/dev/tty", i ? "w" : "r", streams[i]))) { - const char *err = strerror(errno); - fprintf(stderr, "Failed to open tty for fd %d: %s\n", i, err); - return EX_IOERR; - } - - int new_fd = fileno(streams[i]); - if (unlikely(new_fd != i)) { - // This should never happen in a single-threaded program. - // freopen() should call fclose() followed by open() and - // POSIX requires a successful call to open() to return the - // lowest available file descriptor. - fprintf(stderr, "freopen() changed fd from %d to %d\n", i, new_fd); - return EX_OSERR; - } - - if (unlikely(!is_controlling_tty(new_fd))) { - perror("tcgetpgrp"); - return EX_OSERR; - } - } - - return EX_OK; -} - -static Buffer *init_std_buffer(EditorState *e, int fds[2]) -{ - const char *name = NULL; - Buffer *buffer = NULL; - - if (fds[STDIN_FILENO] >= 3) { - Encoding enc = encoding_from_type(UTF8); - buffer = buffer_new(&e->buffers, &e->options, &enc); - if (read_blocks(buffer, fds[STDIN_FILENO], false)) { - name = "(stdin)"; - buffer->temporary = true; - } else { - error_msg("Unable to read redirected stdin"); - remove_and_free_buffer(&e->buffers, buffer); - buffer = NULL; - } - } - - if (fds[STDOUT_FILENO] >= 3) { - if (!buffer) { - buffer = open_empty_buffer(&e->buffers, &e->options); - name = "(stdout)"; - } else { - name = "(stdin|stdout)"; - } - buffer->stdout_buffer = true; - buffer->temporary = true; - } - - BUG_ON(!buffer != !name); - if (name) { - set_display_filename(buffer, xstrdup(name)); - } - - return buffer; -} - -static ExitCode init_logging(const char *filename, const char *req_level_str) -{ - if (!filename || filename[0] == '\0') { - return EX_OK; - } - - LogLevel req_level = log_level_from_str(req_level_str); - if (req_level == LOG_LEVEL_NONE) { - return EX_OK; - } - if (req_level == LOG_LEVEL_INVALID) { - fprintf(stderr, "Invalid $DTE_LOG_LEVEL value: '%s'\n", req_level_str); - return EX_USAGE; - } - - // https://no-color.org/ - const char *no_color = xgetenv("NO_COLOR"); - - LogLevel got_level = log_open(filename, req_level, !no_color); - if (got_level == LOG_LEVEL_NONE) { - const char *err = strerror(errno); - fprintf(stderr, "Failed to open $DTE_LOG (%s): %s\n", filename, err); - return EX_IOERR; - } - - const char *got_level_str = log_level_to_str(got_level); - if (got_level != req_level) { - const char *r = req_level_str; - const char *g = got_level_str; - LOG_WARNING("log level '%s' unavailable; falling back to '%s'", r, g); - } - - LOG_INFO("logging to '%s' (level: %s)", filename, got_level_str); - - if (no_color) { - LOG_INFO("log colors disabled ($NO_COLOR)"); - } - - struct utsname u; - if (likely(uname(&u) >= 0)) { - LOG_INFO("system: %s/%s %s", u.sysname, u.machine, u.release); - } else { - LOG_ERRNO("uname"); - } - return EX_OK; -} - -static void log_config_counts(const EditorState *e) -{ - if (!log_level_enabled(LOG_LEVEL_INFO)) { - return; - } - - size_t nbinds = 0; - for (size_t i = 0; i < ARRAYLEN(e->modes); i++) { - nbinds += e->modes[i].key_bindings.count; - } - - size_t nerrorfmts = 0; - for (HashMapIter it = hashmap_iter(&e->compilers); hashmap_next(&it); ) { - const Compiler *compiler = it.entry->value; - nerrorfmts += compiler->error_formats.count; - } - - LOG_INFO ( - "binds=%zu aliases=%zu hi=%zu ft=%zu option=%zu errorfmt=%zu(%zu)", - nbinds, - e->aliases.count, - e->colors.other.count + NR_BC, - e->filetypes.count, - e->file_options.count, - e->compilers.count, - nerrorfmts - ); -} - -static const char copyright[] = - "dte " VERSION "\n" - "(C) 2013-2023 Craig Barnes\n" - "(C) 2010-2015 Timo Hirvonen\n" - "This program is free software; you can redistribute and/or modify\n" - "it under the terms of the GNU General Public License version 2\n" - "<https://www.gnu.org/licenses/old-licenses/gpl-2.0.html>.\n" - "There is NO WARRANTY, to the extent permitted by law.\n"; - -static const char usage[] = - "Usage: %s [OPTIONS] [[+LINE] FILE]...\n\n" - "Options:\n" - " -c COMMAND Run COMMAND after editor starts\n" - " -t CTAG Jump to source location of CTAG\n" - " -r RCFILE Read user config from RCFILE instead of ~/.dte/rc\n" - " -s FILE Validate dte-syntax commands in FILE and exit\n" - " -b NAME Print built-in config matching NAME and exit\n" - " -B Print list of built-in config names and exit\n" - " -H Don't load or save history files\n" - " -R Don't read user config file\n" - " -K Start editor in \"showkey\" mode\n" - " -h Display help summary and exit\n" - " -V Display version number and exit\n" - "\n"; - -int main(int argc, char *argv[]) -{ - static const char optstring[] = "hBHKRVb:c:t:r:s:"; - const char *tag = NULL; - const char *rc = NULL; - const char *commands[8]; - size_t nr_commands = 0; - bool read_rc = true; - bool use_showkey = false; - bool load_and_save_history = true; - set_print_errors_to_stderr(true); - - for (int ch; (ch = getopt(argc, argv, optstring)) != -1; ) { - switch (ch) { - case 'c': - if (unlikely(nr_commands >= ARRAYLEN(commands))) { - fputs("Error: too many -c options used\n", stderr); - return EX_USAGE; - } - commands[nr_commands++] = optarg; - break; - case 't': - tag = optarg; - break; - case 'r': - rc = optarg; - break; - case 's': - return lint_syntax(optarg); - case 'R': - read_rc = false; - break; - case 'b': - return dump_builtin_config(optarg); - case 'B': - return list_builtin_configs(); - case 'H': - load_and_save_history = false; - break; - case 'K': - use_showkey = true; - goto loop_break; - case 'V': - return write_stdout(copyright, sizeof(copyright)); - case 'h': - printf(usage, (argv[0] && argv[0][0]) ? argv[0] : "dte"); - return EX_OK; - default: - return EX_USAGE; - } - } - -loop_break:; - - const char *term_name = xgetenv("TERM"); - if (!term_name) { - fputs("Error: $TERM not set\n", stderr); - // This is considered a "usage" error, because the program - // must be started from a properly configured terminal - return EX_USAGE; - } - - // This must be done before calling init_logging(), otherwise an - // invocation like e.g. `DTE_LOG=/dev/pts/2 dte 0<&-` could - // cause the logging fd to be opened as STDIN_FILENO - int std_fds[2] = {-1, -1}; - ExitCode r = init_std_fds(std_fds); - if (unlikely(r != EX_OK)) { - return r; - } - - r = init_logging(getenv("DTE_LOG"), getenv("DTE_LOG_LEVEL")); - if (unlikely(r != EX_OK)) { - return r; - } - - if (!term_mode_init()) { - perror("tcgetattr"); - return EX_IOERR; - } - - const char *colorterm = getenv("COLORTERM"); - if (use_showkey) { - return showkey_loop(term_name, colorterm); - } - - EditorState *e = init_editor_state(); - Terminal *term = &e->terminal; - term_init(term, term_name, colorterm); - - Buffer *std_buffer = init_std_buffer(e, std_fds); - bool have_stdout_buffer = std_buffer && std_buffer->stdout_buffer; - - // Create this early (needed if "lock-files" is true) - const char *cfgdir = e->user_config_dir; - BUG_ON(!cfgdir); - if (mkdir(cfgdir, 0755) != 0 && errno != EEXIST) { - error_msg("Error creating %s: %s", cfgdir, strerror(errno)); - load_and_save_history = false; - e->options.lock_files = false; - } - - term_save_title(term); - exec_builtin_rc(e); - - if (read_rc) { - ConfigFlags flags = CFG_NOFLAGS; - char buf[4096]; - if (rc) { - flags |= CFG_MUST_EXIST; - } else { - xsnprintf(buf, sizeof buf, "%s/%s", cfgdir, "rc"); - rc = buf; - } - LOG_INFO("loading configuration from %s", rc); - read_normal_config(e, rc, flags); - } - - log_config_counts(e); - update_all_syntax_colors(&e->syntaxes, &e->colors); - - Window *window = new_window(e); - e->window = window; - e->root_frame = new_root_frame(window); - - set_signal_handlers(); - set_fatal_error_cleanup_handler(cleanup_handler, e); - - if (load_and_save_history) { - file_history_load(&e->file_history, path_join(cfgdir, "file-history")); - history_load(&e->command_history, path_join(cfgdir, "command-history")); - history_load(&e->search_history, path_join(cfgdir, "search-history")); - if (e->search_history.last) { - search_set_regexp(&e->search, e->search_history.last->text); - } - } - - set_print_errors_to_stderr(false); - - // Initialize terminal but don't update screen yet. Also display - // "Press any key to continue" prompt if there were any errors - // during reading configuration files. - if (!term_raw()) { - perror("tcsetattr"); - return EX_IOERR; - } - if (get_nr_errors()) { - any_key(term, e->options.esc_timeout); - clear_error(); - } - - e->status = EDITOR_RUNNING; - - for (size_t i = optind, line = 0, col = 0; i < argc; i++) { - const char *str = argv[i]; - if (line == 0 && *str == '+' && str_to_filepos(str + 1, &line, &col)) { - continue; - } - View *view = window_open_buffer(window, str, false, NULL); - if (line == 0) { - continue; - } - set_view(view); - move_to_filepos(view, line, col); - line = 0; - } - - if (std_buffer) { - window_add_buffer(window, std_buffer); - } - - View *dview = NULL; - if (window->views.count == 0) { - // Open a default buffer, if none were opened for arguments - dview = window_open_empty_buffer(window); - BUG_ON(!dview); - BUG_ON(window->views.count != 1); - BUG_ON(dview != window->views.ptrs[0]); - } - - set_view(window->views.ptrs[0]); - ui_start(e); - - for (size_t i = 0; i < nr_commands; i++) { - handle_normal_command(e, commands[i], false); - } - - if (tag) { - StringView tag_sv = strview_from_cstring(tag); - if (tag_lookup(&e->tagfile, &tag_sv, NULL, &e->messages)) { - activate_current_message(e); - if (dview && nr_commands == 0 && window->views.count > 1) { - // Close default/empty buffer, if `-t` jumped to a tag - // and no commands were executed via `-c` - remove_view(dview); - dview = NULL; - } - } - } - - if (nr_commands > 0 || tag) { - normal_update(e); - } - - int exit_code = main_loop(e); - - term_restore_title(term); - ui_end(e); - term_output_flush(&term->obuf); - set_print_errors_to_stderr(true); - - // Unlock files and add to file history - remove_frame(e, e->root_frame); - - if (load_and_save_history) { - history_save(&e->command_history); - history_save(&e->search_history); - file_history_save(&e->file_history); - } - - if (have_stdout_buffer) { - int fd = std_fds[STDOUT_FILENO]; - Block *blk; - block_for_each(blk, &std_buffer->blocks) { - if (xwrite_all(fd, blk->data, blk->size) < 0) { - error_msg_errno("failed to write (stdout) buffer"); - if (exit_code == EDITOR_EXIT_OK) { - exit_code = EX_IOERR; - } - break; - } - } - free_blocks(std_buffer); - free(std_buffer); - } - - free_editor_state(e); - LOG_INFO("exiting with status %d", exit_code); - log_close(); - return exit_code; -} diff --git a/examples/dte/misc.c b/examples/dte/misc.c deleted file mode 100644 index 4ed640b..0000000 --- a/examples/dte/misc.c +++ /dev/null @@ -1,764 +0,0 @@ -#include <stdlib.h> -#include <string.h> -#include "misc.h" -#include "buffer.h" -#include "change.h" -#include "indent.h" -#include "move.h" -#include "options.h" -#include "regexp.h" -#include "selection.h" -#include "util/debug.h" -#include "util/macros.h" -#include "util/string.h" -#include "util/string-view.h" -#include "util/utf8.h" - -typedef struct { - String buf; - char *indent; - size_t indent_len; - size_t indent_width; - size_t cur_width; - size_t text_width; -} ParagraphFormatter; - -static bool line_has_opening_brace(StringView line) -{ - static regex_t re; - static bool compiled; - if (!compiled) { - // TODO: Reimplement without using regex - static const char pat[] = "\\{[ \t]*(//.*|/\\*.*\\*/[ \t]*)?$"; - regexp_compile_or_fatal_error(&re, pat, REG_NEWLINE | REG_NOSUB); - compiled = true; - } - - regmatch_t m; - return regexp_exec(&re, line.data, line.length, 0, &m, 0); -} - -static bool line_has_closing_brace(StringView line) -{ - strview_trim_left(&line); - return line.length > 0 && line.data[0] == '}'; -} - -/* - * Stupid { ... } block selector. - * - * Because braces can be inside strings or comments and writing real - * parser for many programming languages does not make sense the rules - * for selecting a block are made very simple. Line that matches \{\s*$ - * starts a block and line that matches ^\s*\} ends it. - */ -void select_block(View *view) -{ - BlockIter sbi, ebi, bi = view->cursor; - StringView line; - int level = 0; - - // If current line does not match \{\s*$ but matches ^\s*\} then - // cursor is likely at end of the block you want to select - fetch_this_line(&bi, &line); - if (!line_has_opening_brace(line) && line_has_closing_brace(line)) { - block_iter_prev_line(&bi); - } - - while (1) { - fetch_this_line(&bi, &line); - if (line_has_opening_brace(line)) { - if (level++ == 0) { - sbi = bi; - block_iter_next_line(&bi); - break; - } - } - if (line_has_closing_brace(line)) { - level--; - } - - if (!block_iter_prev_line(&bi)) { - return; - } - } - - while (1) { - fetch_this_line(&bi, &line); - if (line_has_closing_brace(line)) { - if (--level == 0) { - ebi = bi; - break; - } - } - if (line_has_opening_brace(line)) { - level++; - } - - if (!block_iter_next_line(&bi)) { - return; - } - } - - view->cursor = sbi; - view->sel_so = block_iter_get_offset(&ebi); - view->sel_eo = SEL_EO_RECALC; - view->selection = SELECT_LINES; - - mark_all_lines_changed(view->buffer); -} - -static int get_indent_of_matching_brace(const View *view) -{ - const LocalOptions *options = &view->buffer->options; - BlockIter bi = view->cursor; - StringView line; - int level = 0; - - while (block_iter_prev_line(&bi)) { - fetch_this_line(&bi, &line); - if (line_has_opening_brace(line)) { - if (level++ == 0) { - return get_indent_width(options, &line); - } - } - if (line_has_closing_brace(line)) { - level--; - } - } - - return -1; -} - -void unselect(View *view) -{ - view->select_mode = SELECT_NONE; - if (view->selection) { - view->selection = SELECT_NONE; - mark_all_lines_changed(view->buffer); - } -} - -void insert_text(View *view, const char *text, size_t size, bool move_after) -{ - size_t del_count = 0; - if (view->selection) { - del_count = prepare_selection(view); - unselect(view); - } - buffer_replace_bytes(view, del_count, text, size); - if (move_after) { - block_iter_skip_bytes(&view->cursor, size); - } -} - -void delete_ch(View *view) -{ - size_t size = 0; - if (view->selection) { - size = prepare_selection(view); - unselect(view); - } else { - const LocalOptions *options = &view->buffer->options; - begin_change(CHANGE_MERGE_DELETE); - if (options->emulate_tab) { - size = get_indent_level_bytes_right(options, &view->cursor); - } - if (size == 0) { - BlockIter bi = view->cursor; - size = block_iter_next_column(&bi); - } - } - buffer_delete_bytes(view, size); -} - -void erase(View *view) -{ - size_t size = 0; - if (view->selection) { - size = prepare_selection(view); - unselect(view); - } else { - const LocalOptions *options = &view->buffer->options; - begin_change(CHANGE_MERGE_ERASE); - if (options->emulate_tab) { - size = get_indent_level_bytes_left(options, &view->cursor); - block_iter_back_bytes(&view->cursor, size); - } - if (size == 0) { - CodePoint u; - size = block_iter_prev_char(&view->cursor, &u); - } - } - buffer_erase_bytes(view, size); -} - -// Go to beginning of whitespace (tabs and spaces) under cursor and -// return number of whitespace bytes after cursor after moving cursor -static size_t goto_beginning_of_whitespace(View *view) -{ - BlockIter bi = view->cursor; - size_t count = 0; - CodePoint u; - - // Count spaces and tabs at or after cursor - while (block_iter_next_char(&bi, &u)) { - if (u != '\t' && u != ' ') { - break; - } - count++; - } - - // Count spaces and tabs before cursor - while (block_iter_prev_char(&view->cursor, &u)) { - if (u != '\t' && u != ' ') { - block_iter_next_char(&view->cursor, &u); - break; - } - count++; - } - return count; -} - -static bool ws_only(const StringView *line) -{ - for (size_t i = 0, n = line->length; i < n; i++) { - char ch = line->data[i]; - if (ch != ' ' && ch != '\t') { - return false; - } - } - return true; -} - -// Non-empty line can be used to determine size of indentation for the next line -static bool find_non_empty_line_bwd(BlockIter *bi) -{ - block_iter_bol(bi); - do { - StringView line; - fill_line_ref(bi, &line); - if (!ws_only(&line)) { - return true; - } - } while (block_iter_prev_line(bi)); - return false; -} - -static void insert_nl(View *view) -{ - size_t del_count = 0; - size_t ins_count = 1; - char *ins = NULL; - - // Prepare deleted text (selection or whitespace around cursor) - if (view->selection) { - del_count = prepare_selection(view); - unselect(view); - } else { - // Trim whitespace around cursor - del_count = goto_beginning_of_whitespace(view); - } - - // Prepare inserted indentation - const LocalOptions *options = &view->buffer->options; - if (options->auto_indent) { - // Current line will be split at cursor position - BlockIter bi = view->cursor; - size_t len = block_iter_bol(&bi); - StringView line; - fill_line_ref(&bi, &line); - line.length = len; - if (ws_only(&line)) { - // This line is (or will become) white space only; find previous, - // non whitespace only line - if (block_iter_prev_line(&bi) && find_non_empty_line_bwd(&bi)) { - fill_line_ref(&bi, &line); - ins = get_indent_for_next_line(options, &line); - } - } else { - ins = get_indent_for_next_line(options, &line); - } - } - - begin_change(CHANGE_MERGE_NONE); - if (ins) { - // Add newline before indent - ins_count = strlen(ins); - memmove(ins + 1, ins, ins_count); - ins[0] = '\n'; - ins_count++; - - buffer_replace_bytes(view, del_count, ins, ins_count); - free(ins); - } else { - buffer_replace_bytes(view, del_count, "\n", ins_count); - } - end_change(); - - // Move after inserted text - block_iter_skip_bytes(&view->cursor, ins_count); -} - -void insert_ch(View *view, CodePoint ch) -{ - if (ch == '\n') { - insert_nl(view); - return; - } - - const Buffer *buffer = view->buffer; - const LocalOptions *options = &buffer->options; - char buf[8]; - char *ins = buf; - char *alloc = NULL; - size_t del_count = 0; - size_t ins_count = 0; - - if (view->selection) { - // Prepare deleted text (selection) - del_count = prepare_selection(view); - unselect(view); - } else if (options->overwrite) { - // Delete character under cursor unless we're at end of line - BlockIter bi = view->cursor; - del_count = block_iter_is_eol(&bi) ? 0 : block_iter_next_column(&bi); - } else if (ch == '}' && options->auto_indent && options->brace_indent) { - BlockIter bi = view->cursor; - StringView curlr; - block_iter_bol(&bi); - fill_line_ref(&bi, &curlr); - if (ws_only(&curlr)) { - int width = get_indent_of_matching_brace(view); - if (width >= 0) { - // Replace current (ws only) line with some indent + '}' - block_iter_bol(&view->cursor); - del_count = curlr.length; - if (width) { - alloc = make_indent(options, width); - ins = alloc; - ins_count = strlen(ins); - // '}' will be replace the terminating NUL - } - } - } - } - - // Prepare inserted text - if (ch == '\t' && options->expand_tab) { - ins_count = options->indent_width; - static_assert(sizeof(buf) >= INDENT_WIDTH_MAX); - memset(ins, ' ', ins_count); - } else { - u_set_char_raw(ins, &ins_count, ch); - } - - // Record change - begin_change(del_count ? CHANGE_MERGE_NONE : CHANGE_MERGE_INSERT); - buffer_replace_bytes(view, del_count, ins, ins_count); - end_change(); - free(alloc); - - // Move after inserted text - block_iter_skip_bytes(&view->cursor, ins_count); -} - -static void join_selection(View *view) -{ - size_t count = prepare_selection(view); - size_t len = 0, join = 0; - BlockIter bi; - CodePoint ch = 0; - - unselect(view); - bi = view->cursor; - - begin_change_chain(); - while (count > 0) { - if (!len) { - view->cursor = bi; - } - - count -= block_iter_next_char(&bi, &ch); - if (ch == '\t' || ch == ' ') { - len++; - } else if (ch == '\n') { - len++; - join++; - } else { - if (join) { - buffer_replace_bytes(view, len, " ", 1); - // Skip the space we inserted and the char we read last - block_iter_next_char(&view->cursor, &ch); - block_iter_next_char(&view->cursor, &ch); - bi = view->cursor; - } - len = 0; - join = 0; - } - } - - // Don't replace last \n that is at end of the selection - if (join && ch == '\n') { - join--; - len--; - } - - if (join) { - if (ch == '\n') { - // Don't add space to end of line - buffer_delete_bytes(view, len); - } else { - buffer_replace_bytes(view, len, " ", 1); - } - } - end_change_chain(view); -} - -void join_lines(View *view) -{ - BlockIter bi = view->cursor; - - if (view->selection) { - join_selection(view); - return; - } - - if (!block_iter_next_line(&bi)) { - return; - } - if (block_iter_is_eof(&bi)) { - return; - } - - BlockIter next = bi; - CodePoint u; - size_t count = 1; - block_iter_prev_char(&bi, &u); - while (block_iter_prev_char(&bi, &u)) { - if (u != '\t' && u != ' ') { - block_iter_next_char(&bi, &u); - break; - } - count++; - } - while (block_iter_next_char(&next, &u)) { - if (u != '\t' && u != ' ') { - break; - } - count++; - } - - view->cursor = bi; - if (u == '\n') { - buffer_delete_bytes(view, count); - } else { - buffer_replace_bytes(view, count, " ", 1); - } -} - -void clear_lines(View *view, bool auto_indent) -{ - char *indent = NULL; - if (auto_indent) { - BlockIter bi = view->cursor; - if (block_iter_prev_line(&bi) && find_non_empty_line_bwd(&bi)) { - StringView line; - fill_line_ref(&bi, &line); - indent = get_indent_for_next_line(&view->buffer->options, &line); - } - } - - size_t del_count = 0; - if (view->selection) { - view->selection = SELECT_LINES; - del_count = prepare_selection(view); - unselect(view); - // Don't delete last newline - if (del_count) { - del_count--; - } - } else { - block_iter_eol(&view->cursor); - del_count = block_iter_bol(&view->cursor); - } - - if (!indent && !del_count) { - return; - } - - size_t ins_count = indent ? strlen(indent) : 0; - buffer_replace_bytes(view, del_count, indent, ins_count); - free(indent); - block_iter_skip_bytes(&view->cursor, ins_count); -} - -void delete_lines(View *view) -{ - long x = view_get_preferred_x(view); - size_t del_count; - if (view->selection) { - view->selection = SELECT_LINES; - del_count = prepare_selection(view); - unselect(view); - } else { - block_iter_bol(&view->cursor); - BlockIter tmp = view->cursor; - del_count = block_iter_eat_line(&tmp); - } - buffer_delete_bytes(view, del_count); - move_to_preferred_x(view, x); -} - -void new_line(View *view, bool above) -{ - if (above && block_iter_prev_line(&view->cursor) == 0) { - // Already on first line; insert newline at bof - block_iter_bol(&view->cursor); - buffer_insert_bytes(view, "\n", 1); - return; - } - - const LocalOptions *options = &view->buffer->options; - char *ins = NULL; - block_iter_eol(&view->cursor); - - if (options->auto_indent) { - BlockIter bi = view->cursor; - if (find_non_empty_line_bwd(&bi)) { - StringView line; - fill_line_ref(&bi, &line); - ins = get_indent_for_next_line(options, &line); - } - } - - size_t ins_count; - if (ins) { - ins_count = strlen(ins); - memmove(ins + 1, ins, ins_count); - ins[0] = '\n'; - ins_count++; - buffer_insert_bytes(view, ins, ins_count); - free(ins); - } else { - ins_count = 1; - buffer_insert_bytes(view, "\n", 1); - } - - block_iter_skip_bytes(&view->cursor, ins_count); -} - -static void add_word(ParagraphFormatter *pf, const char *word, size_t len) -{ - size_t i = 0; - size_t word_width = 0; - while (i < len) { - word_width += u_char_width(u_get_char(word, len, &i)); - } - - if (pf->cur_width && pf->cur_width + 1 + word_width > pf->text_width) { - string_append_byte(&pf->buf, '\n'); - pf->cur_width = 0; - } - - if (pf->cur_width == 0) { - if (pf->indent_len) { - string_append_buf(&pf->buf, pf->indent, pf->indent_len); - } - pf->cur_width = pf->indent_width; - } else { - string_append_byte(&pf->buf, ' '); - pf->cur_width++; - } - - string_append_buf(&pf->buf, word, len); - pf->cur_width += word_width; -} - -static bool is_paragraph_separator(const StringView *line) -{ - StringView trimmed = *line; - strview_trim(&trimmed); - - return - trimmed.length == 0 - // TODO: make this configurable - || strview_equal_cstring(&trimmed, "/*") - || strview_equal_cstring(&trimmed, "*/") - ; -} - -static bool in_paragraph(const LocalOptions *options, const StringView *line, size_t indent_width) -{ - if (get_indent_width(options, line) != indent_width) { - return false; - } - return !is_paragraph_separator(line); -} - -static size_t paragraph_size(View *view) -{ - const LocalOptions *options = &view->buffer->options; - BlockIter bi = view->cursor; - StringView line; - block_iter_bol(&bi); - fill_line_ref(&bi, &line); - if (is_paragraph_separator(&line)) { - // Not in paragraph - return 0; - } - size_t indent_width = get_indent_width(options, &line); - - // Go to beginning of paragraph - while (block_iter_prev_line(&bi)) { - fill_line_ref(&bi, &line); - if (!in_paragraph(options, &line, indent_width)) { - block_iter_eat_line(&bi); - break; - } - } - view->cursor = bi; - - // Get size of paragraph - size_t size = 0; - do { - size_t bytes = block_iter_eat_line(&bi); - if (!bytes) { - break; - } - size += bytes; - fill_line_ref(&bi, &line); - } while (in_paragraph(options, &line, indent_width)); - return size; -} - -void format_paragraph(View *view, size_t text_width) -{ - size_t len; - if (view->selection) { - view->selection = SELECT_LINES; - len = prepare_selection(view); - } else { - len = paragraph_size(view); - } - if (!len) { - return; - } - - const LocalOptions *options = &view->buffer->options; - char *sel = block_iter_get_bytes(&view->cursor, len); - StringView sv = string_view(sel, len); - size_t indent_width = get_indent_width(options, &sv); - char *indent = make_indent(options, indent_width); - - ParagraphFormatter pf = { - .buf = STRING_INIT, - .indent = indent, - .indent_len = indent ? strlen(indent) : 0, - .indent_width = indent_width, - .cur_width = 0, - .text_width = text_width - }; - - for (size_t i = 0; true; ) { - while (i < len) { - size_t tmp = i; - if (!u_is_breakable_whitespace(u_get_char(sel, len, &tmp))) { - break; - } - i = tmp; - } - if (i == len) { - break; - } - - size_t start = i; - while (i < len) { - size_t tmp = i; - if (u_is_breakable_whitespace(u_get_char(sel, len, &tmp))) { - break; - } - i = tmp; - } - - add_word(&pf, sel + start, i - start); - } - - if (pf.buf.len) { - string_append_byte(&pf.buf, '\n'); - } - buffer_replace_bytes(view, len, pf.buf.buffer, pf.buf.len); - if (pf.buf.len) { - block_iter_skip_bytes(&view->cursor, pf.buf.len - 1); - } - string_free(&pf.buf); - free(pf.indent); - free(sel); - - unselect(view); -} - -void change_case(View *view, char mode) -{ - bool was_selecting = false; - bool move = true; - size_t text_len; - if (view->selection) { - SelectionInfo info; - init_selection(view, &info); - view->cursor = info.si; - text_len = info.eo - info.so; - unselect(view); - was_selecting = true; - move = !info.swapped; - } else { - CodePoint u; - if (!block_iter_get_char(&view->cursor, &u)) { - return; - } - text_len = u_char_size(u); - } - - String dst = string_new(text_len); - char *src = block_iter_get_bytes(&view->cursor, text_len); - size_t i = 0; - switch (mode) { - case 'l': - while (i < text_len) { - CodePoint u = u_to_lower(u_get_char(src, text_len, &i)); - string_append_codepoint(&dst, u); - } - break; - case 'u': - while (i < text_len) { - CodePoint u = u_to_upper(u_get_char(src, text_len, &i)); - string_append_codepoint(&dst, u); - } - break; - case 't': - while (i < text_len) { - CodePoint u = u_get_char(src, text_len, &i); - u = u_is_upper(u) ? u_to_lower(u) : u_to_upper(u); - string_append_codepoint(&dst, u); - } - break; - default: - BUG("unhandled case mode"); - } - - buffer_replace_bytes(view, text_len, dst.buffer, dst.len); - free(src); - - if (move && dst.len > 0) { - if (was_selecting) { - // Move cursor back to where it was - size_t idx = dst.len; - u_prev_char(dst.buffer, &idx); - block_iter_skip_bytes(&view->cursor, idx); - } else { - block_iter_skip_bytes(&view->cursor, dst.len); - } - } - - string_free(&dst); -} diff --git a/examples/dte/misc.h b/examples/dte/misc.h deleted file mode 100644 index 055748f..0000000 --- a/examples/dte/misc.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef MISC_H -#define MISC_H - -#include <stdbool.h> -#include <stddef.h> -#include "util/unicode.h" -#include "view.h" - -void select_block(View *view); -void unselect(View *view); -void insert_text(View *view, const char *text, size_t size, bool move_after); -void delete_ch(View *view); -void erase(View *view); -void insert_ch(View *view, CodePoint ch); -void join_lines(View *view); -void clear_lines(View *view, bool auto_indent); -void delete_lines(View *view); -void new_line(View *view, bool above); -void format_paragraph(View *view, size_t text_width); -void change_case(View *view, char mode); - -#endif diff --git a/examples/dte/mode.c b/examples/dte/mode.c deleted file mode 100644 index fe90b6a..0000000 --- a/examples/dte/mode.c +++ /dev/null @@ -1,74 +0,0 @@ -#include "mode.h" -#include "bind.h" -#include "change.h" -#include "cmdline.h" -#include "command/macro.h" -#include "completion.h" -#include "misc.h" -#include "shift.h" -#include "terminal/input.h" -#include "util/debug.h" -#include "util/unicode.h" -#include "view.h" - -static bool normal_mode_keypress(EditorState *e, KeyCode key) -{ - View *view = e->view; - KeyCode shift = key & MOD_SHIFT; - if ((key & ~shift) == KEY_TAB && view->selection == SELECT_LINES) { - // In line selections, Tab/S-Tab behave like `shift -- 1/-1` - shift_lines(view, shift ? -1 : 1); - return true; - } - - if (u_is_unicode(key)) { - insert_ch(view, key); - macro_insert_char_hook(&e->macro, key); - return true; - } - - return handle_binding(e, INPUT_NORMAL, key); -} - -static bool insert_paste(EditorState *e, bool bracketed) -{ - String str = term_read_paste(&e->terminal.ibuf, bracketed); - if (e->input_mode == INPUT_NORMAL) { - begin_change(CHANGE_MERGE_NONE); - insert_text(e->view, str.buffer, str.len, true); - end_change(); - macro_insert_text_hook(&e->macro, str.buffer, str.len); - } else { - CommandLine *c = &e->cmdline; - string_replace_byte(&str, '\n', ' '); - string_insert_buf(&c->buf, c->pos, str.buffer, str.len); - c->pos += str.len; - c->search_pos = NULL; - } - string_free(&str); - return true; -} - -bool handle_input(EditorState *e, KeyCode key) -{ - if (key == KEY_DETECTED_PASTE || key == KEY_BRACKETED_PASTE) { - return insert_paste(e, key == KEY_BRACKETED_PASTE); - } - - InputMode mode = e->input_mode; - if (mode == INPUT_NORMAL) { - return normal_mode_keypress(e, key); - } - - BUG_ON(!(mode == INPUT_COMMAND || mode == INPUT_SEARCH)); - if (!u_is_unicode(key) || key == KEY_TAB || key == KEY_ENTER) { - return handle_binding(e, mode, key); - } - - CommandLine *c = &e->cmdline; - c->pos += string_insert_codepoint(&c->buf, c->pos, key); - if (mode == INPUT_COMMAND) { - reset_completion(c); - } - return true; -} diff --git a/examples/dte/mode.h b/examples/dte/mode.h deleted file mode 100644 index 40d4a6b..0000000 --- a/examples/dte/mode.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef MODE_H -#define MODE_H - -#include <stdbool.h> -#include "editor.h" -#include "terminal/key.h" -#include "util/macros.h" - -bool handle_input(EditorState *e, KeyCode key) NONNULL_ARGS; - -#endif diff --git a/examples/dte/move.c b/examples/dte/move.c deleted file mode 100644 index 72414b3..0000000 --- a/examples/dte/move.c +++ /dev/null @@ -1,311 +0,0 @@ -#include "move.h" -#include "buffer.h" -#include "indent.h" -#include "util/ascii.h" -#include "util/debug.h" -#include "util/macros.h" -#include "util/utf8.h" - -typedef enum { - CT_SPACE, - CT_NEWLINE, - CT_WORD, - CT_OTHER, -} CharTypeEnum; - -void move_to_preferred_x(View *view, long preferred_x) -{ - const LocalOptions *options = &view->buffer->options; - StringView line; - view->preferred_x = preferred_x; - block_iter_bol(&view->cursor); - fill_line_ref(&view->cursor, &line); - - if (options->emulate_tab && view->preferred_x < line.length) { - const size_t iw = options->indent_width; - const size_t ilevel = indent_level(view->preferred_x, iw); - for (size_t i = 0; i < line.length && line.data[i] == ' '; i++) { - if (i + 1 == (ilevel + 1) * iw) { - // Force cursor to beginning of the indentation level - view->cursor.offset += ilevel * iw; - return; - } - } - } - - const unsigned int tw = options->tab_width; - unsigned long x = 0; - size_t i = 0; - while (x < view->preferred_x && i < line.length) { - CodePoint u = line.data[i++]; - if (likely(u < 0x80)) { - if (likely(!ascii_iscntrl(u))) { - x++; - } else if (u == '\t') { - x = next_indent_width(x, tw); - } else if (u == '\n') { - break; - } else { - x += 2; - } - } else { - const size_t next = i; - i--; - u = u_get_nonascii(line.data, line.length, &i); - x += u_char_width(u); - if (x > view->preferred_x) { - i = next; - break; - } - } - } - if (x > view->preferred_x) { - i--; - } - view->cursor.offset += i; - - // If cursor stopped on a zero-width char, move to the next spacing char - CodePoint u; - if (block_iter_get_char(&view->cursor, &u) && u_is_zero_width(u)) { - block_iter_next_column(&view->cursor); - } -} - -void move_cursor_left(View *view) -{ - const LocalOptions *options = &view->buffer->options; - if (options->emulate_tab) { - size_t size = get_indent_level_bytes_left(options, &view->cursor); - if (size) { - block_iter_back_bytes(&view->cursor, size); - view_reset_preferred_x(view); - return; - } - } - block_iter_prev_column(&view->cursor); - view_reset_preferred_x(view); -} - -void move_cursor_right(View *view) -{ - const LocalOptions *options = &view->buffer->options; - if (options->emulate_tab) { - size_t size = get_indent_level_bytes_right(options, &view->cursor); - if (size) { - block_iter_skip_bytes(&view->cursor, size); - view_reset_preferred_x(view); - return; - } - } - block_iter_next_column(&view->cursor); - view_reset_preferred_x(view); -} - -void move_bol(View *view) -{ - block_iter_bol(&view->cursor); - view_reset_preferred_x(view); -} - -void move_bol_smart(View *view, SmartBolFlags flags) -{ - if (flags == 0) { - move_bol(view); - return; - } - - BUG_ON(!(flags & BOL_SMART)); - bool fwd = false; - StringView line; - size_t cursor_offset = fetch_this_line(&view->cursor, &line); - - if (cursor_offset == 0) { - // Already at bol - if (!(flags & BOL_SMART_TOGGLE)) { - goto out; - } - fwd = true; - } - - size_t indent = ascii_blank_prefix_length(line.data, line.length); - if (fwd) { - block_iter_skip_bytes(&view->cursor, indent); - } else { - size_t co = cursor_offset; - size_t move = (co > indent) ? co - indent : co; - block_iter_back_bytes(&view->cursor, move); - } - -out: - view_reset_preferred_x(view); -} - -void move_eol(View *view) -{ - block_iter_eol(&view->cursor); - view_reset_preferred_x(view); -} - -void move_up(View *view, long count) -{ - const long x = view_get_preferred_x(view); - while (count > 0) { - if (!block_iter_prev_line(&view->cursor)) { - break; - } - count--; - } - move_to_preferred_x(view, x); -} - -void move_down(View *view, long count) -{ - const long x = view_get_preferred_x(view); - while (count > 0) { - if (!block_iter_eat_line(&view->cursor)) { - break; - } - count--; - } - move_to_preferred_x(view, x); -} - -void move_bof(View *view) -{ - block_iter_bof(&view->cursor); - view_reset_preferred_x(view); -} - -void move_eof(View *view) -{ - block_iter_eof(&view->cursor); - view_reset_preferred_x(view); -} - -void move_to_line(View *view, size_t line) -{ - BUG_ON(line == 0); - view->center_on_scroll = true; - block_iter_goto_line(&view->cursor, line - 1); -} - -void move_to_column(View *view, size_t column) -{ - BUG_ON(column == 0); - block_iter_bol(&view->cursor); - while (column-- > 1) { - CodePoint u; - if (!block_iter_next_char(&view->cursor, &u)) { - break; - } - if (u == '\n') { - block_iter_prev_char(&view->cursor, &u); - break; - } - } - view_reset_preferred_x(view); -} - -void move_to_filepos(View *view, size_t line, size_t column) -{ - move_to_line(view, line); - BUG_ON(!block_iter_is_bol(&view->cursor)); - if (column != 1) { - move_to_column(view, column); - } - view_reset_preferred_x(view); -} - -static CharTypeEnum get_char_type(CodePoint u) -{ - if (u == '\n') { - return CT_NEWLINE; - } - if (u_is_breakable_whitespace(u)) { - return CT_SPACE; - } - if (u_is_word_char(u)) { - return CT_WORD; - } - return CT_OTHER; -} - -static bool get_current_char_type(BlockIter *bi, CharTypeEnum *type) -{ - CodePoint u; - if (!block_iter_get_char(bi, &u)) { - return false; - } - - *type = get_char_type(u); - return true; -} - -static size_t skip_fwd_char_type(BlockIter *bi, CharTypeEnum type) -{ - size_t count = 0; - CodePoint u; - while (block_iter_next_char(bi, &u)) { - if (get_char_type(u) != type) { - block_iter_prev_char(bi, &u); - break; - } - count += u_char_size(u); - } - return count; -} - -static size_t skip_bwd_char_type(BlockIter *bi, CharTypeEnum type) -{ - size_t count = 0; - CodePoint u; - while (block_iter_prev_char(bi, &u)) { - if (get_char_type(u) != type) { - block_iter_next_char(bi, &u); - break; - } - count += u_char_size(u); - } - return count; -} - -size_t word_fwd(BlockIter *bi, bool skip_non_word) -{ - size_t count = 0; - CharTypeEnum type; - - while (1) { - count += skip_fwd_char_type(bi, CT_SPACE); - if (!get_current_char_type(bi, &type)) { - return count; - } - - if ( - count - && (!skip_non_word || (type == CT_WORD || type == CT_NEWLINE)) - ) { - return count; - } - - count += skip_fwd_char_type(bi, type); - } -} - -size_t word_bwd(BlockIter *bi, bool skip_non_word) -{ - size_t count = 0; - CharTypeEnum type; - CodePoint u; - - do { - count += skip_bwd_char_type(bi, CT_SPACE); - if (!block_iter_prev_char(bi, &u)) { - return count; - } - - type = get_char_type(u); - count += u_char_size(u); - count += skip_bwd_char_type(bi, type); - } while (skip_non_word && type != CT_WORD && type != CT_NEWLINE); - return count; -} diff --git a/examples/dte/move.h b/examples/dte/move.h deleted file mode 100644 index 412a033..0000000 --- a/examples/dte/move.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef MOVE_H -#define MOVE_H - -#include <stdbool.h> -#include <stddef.h> -#include "block-iter.h" -#include "view.h" - -typedef enum { - BOL_SMART = 0x1, // Move to end of indent, before moving to bol (left moves only) - BOL_SMART_TOGGLE = 0x2, // Move to end of indent, if at bol (can move right) -} SmartBolFlags; - -void move_to_preferred_x(View *view, long preferred_x); -void move_cursor_left(View *view); -void move_cursor_right(View *view); -void move_bol(View *view); -void move_bol_smart(View *view, SmartBolFlags flags); -void move_eol(View *view); -void move_up(View *view, long count); -void move_down(View *view, long count); -void move_bof(View *view); -void move_eof(View *view); -void move_to_line(View *view, size_t line); -void move_to_column(View *view, size_t column); -void move_to_filepos(View *view, size_t line, size_t column); - -size_t word_fwd(BlockIter *bi, bool skip_non_word); -size_t word_bwd(BlockIter *bi, bool skip_non_word); - -#endif diff --git a/examples/dte/msg.c b/examples/dte/msg.c deleted file mode 100644 index 203347f..0000000 --- a/examples/dte/msg.c +++ /dev/null @@ -1,139 +0,0 @@ -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include "msg.h" -#include "editor.h" -#include "error.h" -#include "util/debug.h" -#include "util/numtostr.h" -#include "util/path.h" -#include "util/xmalloc.h" - -static void free_message(Message *m) -{ - if (m->loc) { - file_location_free(m->loc); - } - free(m); -} - -Message *new_message(const char *msg, size_t len) -{ - Message *m = xmalloc(sizeof(*m) + len + 1); - m->loc = NULL; - if (len) { - memcpy(m->msg, msg, len); - } - m->msg[len] = '\0'; - return m; -} - -void add_message(MessageArray *msgs, Message *m) -{ - ptr_array_append(&msgs->array, m); -} - -bool activate_current_message(EditorState *e) -{ - const MessageArray *msgs = &e->messages; - size_t count = msgs->array.count; - if (count == 0) { - return true; - } - - size_t pos = msgs->pos; - BUG_ON(pos >= count); - const Message *m = msgs->array.ptrs[pos]; - const FileLocation *loc = m->loc; - if (loc && loc->filename && !file_location_go(e->window, loc)) { - // Failed to jump to location; error message is visible - return false; - } - - if (count == 1) { - info_msg("%s", m->msg); - } else { - info_msg("[%zu/%zu] %s", pos + 1, count, m->msg); - } - - return true; -} - -bool activate_current_message_save(EditorState *e) -{ - const View *view = e->view; - const BlockIter save = view->cursor; - FileLocation *loc = get_current_file_location(view); - bool ok = activate_current_message(e); - - // Save position if file changed or cursor moved - view = e->view; - if (view->cursor.blk != save.blk || view->cursor.offset != save.offset) { - bookmark_push(&e->bookmarks, loc); - } else { - file_location_free(loc); - } - - return ok; -} - -void clear_messages(MessageArray *msgs) -{ - msgs->pos = 0; - ptr_array_free_cb(&msgs->array, FREE_FUNC(free_message)); -} - -String dump_messages(const MessageArray *messages) -{ - String buf = string_new(4096); - char cwd[8192]; - if (unlikely(!getcwd(cwd, sizeof cwd))) { - return buf; - } - - for (size_t i = 0, n = messages->array.count; i < n; i++) { - char *ptr = string_reserve_space(&buf, DECIMAL_STR_MAX(i)); - buf.len += buf_umax_to_str(i + 1, ptr); - string_append_literal(&buf, ": "); - - const Message *m = messages->array.ptrs[i]; - const FileLocation *loc = m->loc; - if (!loc || !loc->filename) { - goto append_msg; - } - - if (path_is_absolute(loc->filename)) { - char *rel = path_relative(loc->filename, cwd); - string_append_cstring(&buf, rel); - free(rel); - } else { - string_append_cstring(&buf, loc->filename); - } - - string_append_byte(&buf, ':'); - - if (loc->pattern) { - string_append_literal(&buf, " /"); - string_append_cstring(&buf, loc->pattern); - string_append_literal(&buf, "/\n"); - continue; - } - - if (loc->line != 0) { - string_append_cstring(&buf, ulong_to_str(loc->line)); - string_append_byte(&buf, ':'); - if (loc->column != 0) { - string_append_cstring(&buf, ulong_to_str(loc->column)); - string_append_byte(&buf, ':'); - } - } - - string_append_literal(&buf, " "); - - append_msg: - string_append_cstring(&buf, m->msg); - string_append_byte(&buf, '\n'); - } - - return buf; -} diff --git a/examples/dte/msg.h b/examples/dte/msg.h deleted file mode 100644 index b23fa25..0000000 --- a/examples/dte/msg.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef MSG_H -#define MSG_H - -#include <stdbool.h> -#include <stddef.h> -#include "bookmark.h" -#include "util/macros.h" -#include "util/ptr-array.h" -#include "util/string.h" - -typedef struct { - FileLocation *loc; - char msg[]; -} Message; - -typedef struct { - PointerArray array; - size_t pos; -} MessageArray; - -struct EditorState; - -Message *new_message(const char *msg, size_t len) RETURNS_NONNULL; -void add_message(MessageArray *msgs, Message *m) NONNULL_ARGS; -bool activate_current_message(struct EditorState *e) NONNULL_ARGS; -bool activate_current_message_save(struct EditorState *e) NONNULL_ARGS; -void clear_messages(MessageArray *msgs) NONNULL_ARGS; -String dump_messages(const MessageArray *messages) NONNULL_ARGS; - -#endif diff --git a/examples/dte/options.c b/examples/dte/options.c deleted file mode 100644 index 2fd4190..0000000 --- a/examples/dte/options.c +++ /dev/null @@ -1,987 +0,0 @@ -#include <stdint.h> -#include <stdlib.h> -#include <string.h> -#include "options.h" -#include "buffer.h" -#include "command/serialize.h" -#include "editor.h" -#include "error.h" -#include "file-option.h" -#include "filetype.h" -#include "screen.h" -#include "status.h" -#include "terminal/output.h" -#include "util/bsearch.h" -#include "util/debug.h" -#include "util/intern.h" -#include "util/numtostr.h" -#include "util/str-util.h" -#include "util/string-view.h" -#include "util/strtonum.h" -#include "util/xmalloc.h" - -typedef enum { - OPT_STR, - OPT_UINT, - OPT_ENUM, - OPT_BOOL, - OPT_FLAG, - OPT_REGEX, -} OptionType; - -typedef union { - const char *str_val; // OPT_STR, OPT_REGEX - unsigned int uint_val; // OPT_UINT, OPT_ENUM, OPT_FLAG - bool bool_val; // OPT_BOOL -} OptionValue; - -typedef union { - struct {bool (*validate)(const char *value);} str_opt; // OPT_STR (optional) - struct {unsigned int min, max;} uint_opt; // OPT_UINT - struct {const char *const *values;} enum_opt; // OPT_ENUM, OPT_FLAG, OPT_BOOL -} OptionConstraint; - -typedef struct { - const char name[22]; - bool local; - bool global; - unsigned int offset; - OptionType type; - OptionConstraint u; - void (*on_change)(EditorState *e, bool global); // Optional -} OptionDesc; - -#define STR_OPT(_name, OLG, _validate, _on_change) { \ - OLG \ - .name = _name, \ - .type = OPT_STR, \ - .u = {.str_opt = {.validate = _validate}}, \ - .on_change = _on_change, \ -} - -#define UINT_OPT(_name, OLG, _min, _max, _on_change) { \ - OLG \ - .name = _name, \ - .type = OPT_UINT, \ - .u = {.uint_opt = { \ - .min = _min, \ - .max = _max, \ - }}, \ - .on_change = _on_change, \ -} - -#define ENUM_OPT(_name, OLG, _values, _on_change) { \ - OLG \ - .name = _name, \ - .type = OPT_ENUM, \ - .u = {.enum_opt = {.values = _values}}, \ - .on_change = _on_change, \ -} - -#define FLAG_OPT(_name, OLG, _values, _on_change) { \ - OLG \ - .name = _name, \ - .type = OPT_FLAG, \ - .u = {.enum_opt = {.values = _values}}, \ - .on_change = _on_change, \ -} - -#define BOOL_OPT(_name, OLG, _on_change) { \ - OLG \ - .name = _name, \ - .type = OPT_BOOL, \ - .u = {.enum_opt = {.values = bool_enum}}, \ - .on_change = _on_change, \ -} - -#define REGEX_OPT(_name, OLG, _on_change) { \ - OLG \ - .name = _name, \ - .type = OPT_REGEX, \ - .on_change = _on_change, \ -} - -#define OLG(_offset, _local, _global) \ - .offset = _offset, \ - .local = _local, \ - .global = _global, \ - -#define L(member) OLG(offsetof(LocalOptions, member), true, false) -#define G(member) OLG(offsetof(GlobalOptions, member), false, true) -#define C(member) OLG(offsetof(CommonOptions, member), true, true) - -static void filetype_changed(EditorState *e, bool global) -{ - BUG_ON(!e->buffer); - BUG_ON(global); - set_file_options(e, e->buffer); - buffer_update_syntax(e, e->buffer); -} - -static void set_window_title_changed(EditorState *e, bool global) -{ - BUG_ON(!global); - Terminal *term = &e->terminal; - if (e->options.set_window_title) { - if (e->status == EDITOR_RUNNING) { - update_term_title(term, e->buffer, e->options.set_window_title); - } - } else { - term_restore_title(term); - term_save_title(term); - } -} - -static void syntax_changed(EditorState *e, bool global) -{ - if (e->buffer && !global) { - buffer_update_syntax(e, e->buffer); - } -} - -static void overwrite_changed(EditorState *e, bool global) -{ - if (!global) { - e->cursor_style_changed = true; - } -} - -static void redraw_buffer(EditorState *e, bool global) -{ - if (e->buffer && !global) { - mark_all_lines_changed(e->buffer); - } -} - -static void redraw_screen(EditorState *e, bool global) -{ - BUG_ON(!global); - mark_everything_changed(e); -} - -static bool validate_statusline_format(const char *value) -{ - size_t errpos = statusline_format_find_error(value); - if (likely(errpos == 0)) { - return true; - } - char ch = value[errpos]; - if (ch == '\0') { - return error_msg("Format character expected after '%%'"); - } - return error_msg("Invalid format character '%c'", ch); -} - -static bool validate_filetype(const char *value) -{ - if (!is_valid_filetype_name(value)) { - return error_msg("Invalid filetype name '%s'", value); - } - return true; -} - -static OptionValue str_get(const OptionDesc* UNUSED_ARG(desc), void *ptr) -{ - const char *const *strp = ptr; - return (OptionValue){.str_val = *strp}; -} - -static void str_set(const OptionDesc* UNUSED_ARG(d), void *ptr, OptionValue v) -{ - const char **strp = ptr; - *strp = str_intern(v.str_val); -} - -static bool str_parse(const OptionDesc *d, const char *str, OptionValue *v) -{ - bool valid = !d->u.str_opt.validate || d->u.str_opt.validate(str); - v->str_val = valid ? str : NULL; - return valid; -} - -static const char *str_string(const OptionDesc* UNUSED_ARG(d), OptionValue v) -{ - const char *s = v.str_val; - return s ? s : ""; -} - -static bool str_equals(const OptionDesc* UNUSED_ARG(d), void *ptr, OptionValue v) -{ - const char **strp = ptr; - return xstreq(*strp, v.str_val); -} - -static OptionValue re_get(const OptionDesc* UNUSED_ARG(desc), void *ptr) -{ - const InternedRegexp *const *irp = ptr; - return (OptionValue){.str_val = *irp ? (*irp)->str : NULL}; -} - -static void re_set(const OptionDesc* UNUSED_ARG(d), void *ptr, OptionValue v) -{ - const InternedRegexp **irp = ptr; - *irp = v.str_val ? regexp_intern(v.str_val) : NULL; -} - -static bool re_parse(const OptionDesc* UNUSED_ARG(d), const char *str, OptionValue *v) -{ - if (str[0] == '\0') { - v->str_val = NULL; - return true; - } - - bool valid = regexp_is_interned(str) || regexp_is_valid(str, REG_NEWLINE); - v->str_val = valid ? str : NULL; - return valid; -} - -static bool re_equals(const OptionDesc* UNUSED_ARG(d), void *ptr, OptionValue v) -{ - const InternedRegexp **irp = ptr; - return *irp ? xstreq((*irp)->str, v.str_val) : !v.str_val; -} - -static OptionValue uint_get(const OptionDesc* UNUSED_ARG(desc), void *ptr) -{ - const unsigned int *valp = ptr; - return (OptionValue){.uint_val = *valp}; -} - -static void uint_set(const OptionDesc* UNUSED_ARG(d), void *ptr, OptionValue v) -{ - unsigned int *valp = ptr; - *valp = v.uint_val; -} - -static bool uint_parse(const OptionDesc *d, const char *str, OptionValue *v) -{ - unsigned int val; - if (!str_to_uint(str, &val)) { - return error_msg("Integer value for %s expected", d->name); - } - - const unsigned int min = d->u.uint_opt.min; - const unsigned int max = d->u.uint_opt.max; - if (val < min || val > max) { - return error_msg("Value for %s must be in %u-%u range", d->name, min, max); - } - - v->uint_val = val; - return true; -} - -static const char *uint_string(const OptionDesc* UNUSED_ARG(desc), OptionValue value) -{ - return uint_to_str(value.uint_val); -} - -static bool uint_equals(const OptionDesc* UNUSED_ARG(desc), void *ptr, OptionValue value) -{ - const unsigned int *valp = ptr; - return *valp == value.uint_val; -} - -static OptionValue bool_get(const OptionDesc* UNUSED_ARG(d), void *ptr) -{ - const bool *valp = ptr; - return (OptionValue){.bool_val = *valp}; -} - -static void bool_set(const OptionDesc* UNUSED_ARG(d), void *ptr, OptionValue v) -{ - bool *valp = ptr; - *valp = v.bool_val; -} - -static bool bool_parse(const OptionDesc *d, const char *str, OptionValue *v) -{ - if (streq(str, "true")) { - v->bool_val = true; - return true; - } else if (streq(str, "false")) { - v->bool_val = false; - return true; - } - return error_msg("Invalid value for %s", d->name); -} - -static const char *bool_string(const OptionDesc* UNUSED_ARG(d), OptionValue v) -{ - return v.bool_val ? "true" : "false"; -} - -static bool bool_equals(const OptionDesc* UNUSED_ARG(d), void *ptr, OptionValue v) -{ - const bool *valp = ptr; - return *valp == v.bool_val; -} - -static bool enum_parse(const OptionDesc *d, const char *str, OptionValue *v) -{ - const char *const *values = d->u.enum_opt.values; - unsigned int i; - for (i = 0; values[i]; i++) { - if (streq(values[i], str)) { - v->uint_val = i; - return true; - } - } - - unsigned int val; - if (!str_to_uint(str, &val) || val >= i) { - return error_msg("Invalid value for %s", d->name); - } - - v->uint_val = val; - return true; -} - -static const char *enum_string(const OptionDesc *desc, OptionValue value) -{ - return desc->u.enum_opt.values[value.uint_val]; -} - -static bool flag_parse(const OptionDesc *d, const char *str, OptionValue *v) -{ - // "0" is allowed for compatibility and is the same as "" - if (str[0] == '0' && str[1] == '\0') { - v->uint_val = 0; - return true; - } - - const char *const *values = d->u.enum_opt.values; - unsigned int flags = 0; - - for (size_t pos = 0, len = strlen(str); pos < len; ) { - const StringView flag = get_delim(str, &pos, len, ','); - size_t i; - for (i = 0; values[i]; i++) { - if (strview_equal_cstring(&flag, values[i])) { - flags |= 1u << i; - break; - } - } - if (unlikely(!values[i])) { - int flen = (int)flag.length; - return error_msg("Invalid flag '%.*s' for %s", flen, flag.data, d->name); - } - } - - v->uint_val = flags; - return true; -} - -static const char *flag_string(const OptionDesc *desc, OptionValue value) -{ - static char buf[128]; - unsigned int flags = value.uint_val; - if (!flags) { - buf[0] = '0'; - buf[1] = '\0'; - return buf; - } - - char *ptr = buf; - const char *const *values = desc->u.enum_opt.values; - for (size_t i = 0, avail = sizeof(buf); values[i]; i++) { - if (flags & (1u << i)) { - char *next = memccpy(ptr, values[i], '\0', avail); - if (DEBUG >= 1) { - BUG_ON(!next); - avail -= (size_t)(next - ptr); - } - ptr = next; - ptr[-1] = ','; - } - } - - BUG_ON(ptr == buf); - ptr[-1] = '\0'; - return buf; -} - -static const struct { - OptionValue (*get)(const OptionDesc *desc, void *ptr); - void (*set)(const OptionDesc *desc, void *ptr, OptionValue value); - bool (*parse)(const OptionDesc *desc, const char *str, OptionValue *value); - const char *(*string)(const OptionDesc *desc, OptionValue value); - bool (*equals)(const OptionDesc *desc, void *ptr, OptionValue value); -} option_ops[] = { - [OPT_STR] = {str_get, str_set, str_parse, str_string, str_equals}, - [OPT_UINT] = {uint_get, uint_set, uint_parse, uint_string, uint_equals}, - [OPT_ENUM] = {uint_get, uint_set, enum_parse, enum_string, uint_equals}, - [OPT_BOOL] = {bool_get, bool_set, bool_parse, bool_string, bool_equals}, - [OPT_FLAG] = {uint_get, uint_set, flag_parse, flag_string, uint_equals}, - [OPT_REGEX] = {re_get, re_set, re_parse, str_string, re_equals}, -}; - -static const char *const bool_enum[] = {"false", "true", NULL}; -static const char *const newline_enum[] = {"unix", "dos", NULL}; -static const char *const tristate_enum[] = {"false", "true", "auto", NULL}; -static const char *const save_unmodified_enum[] = {"none", "touch", "full", NULL}; - -static const char *const detect_indent_values[] = { - "1", "2", "3", "4", "5", "6", "7", "8", - NULL -}; - -// Note: this must be kept in sync with WhitespaceErrorFlags -static const char *const ws_error_values[] = { - "space-indent", - "space-align", - "tab-indent", - "tab-after-indent", - "special", - "auto-indent", - "trailing", - "all-trailing", - NULL -}; - -static const OptionDesc option_desc[] = { - BOOL_OPT("auto-indent", C(auto_indent), NULL), - BOOL_OPT("brace-indent", L(brace_indent), NULL), - ENUM_OPT("case-sensitive-search", G(case_sensitive_search), tristate_enum, NULL), - FLAG_OPT("detect-indent", C(detect_indent), detect_indent_values, NULL), - BOOL_OPT("display-special", G(display_special), redraw_screen), - BOOL_OPT("editorconfig", C(editorconfig), NULL), - BOOL_OPT("emulate-tab", C(emulate_tab), NULL), - UINT_OPT("esc-timeout", G(esc_timeout), 0, 2000, NULL), - BOOL_OPT("expand-tab", C(expand_tab), redraw_buffer), - BOOL_OPT("file-history", C(file_history), NULL), - UINT_OPT("filesize-limit", G(filesize_limit), 0, 16000, NULL), - STR_OPT("filetype", L(filetype), validate_filetype, filetype_changed), - BOOL_OPT("fsync", C(fsync), NULL), - REGEX_OPT("indent-regex", L(indent_regex), NULL), - UINT_OPT("indent-width", C(indent_width), 1, INDENT_WIDTH_MAX, NULL), - BOOL_OPT("lock-files", G(lock_files), NULL), - ENUM_OPT("newline", G(crlf_newlines), newline_enum, NULL), - BOOL_OPT("optimize-true-color", G(optimize_true_color), redraw_screen), - BOOL_OPT("overwrite", C(overwrite), overwrite_changed), - ENUM_OPT("save-unmodified", C(save_unmodified), save_unmodified_enum, NULL), - UINT_OPT("scroll-margin", G(scroll_margin), 0, 100, redraw_screen), - BOOL_OPT("select-cursor-char", G(select_cursor_char), redraw_screen), - BOOL_OPT("set-window-title", G(set_window_title), set_window_title_changed), - BOOL_OPT("show-line-numbers", G(show_line_numbers), redraw_screen), - STR_OPT("statusline-left", G(statusline_left), validate_statusline_format, NULL), - STR_OPT("statusline-right", G(statusline_right), validate_statusline_format, NULL), - BOOL_OPT("syntax", C(syntax), syntax_changed), - BOOL_OPT("tab-bar", G(tab_bar), redraw_screen), - UINT_OPT("tab-width", C(tab_width), 1, TAB_WIDTH_MAX, redraw_buffer), - UINT_OPT("text-width", C(text_width), 1, TEXT_WIDTH_MAX, NULL), - BOOL_OPT("utf8-bom", G(utf8_bom), NULL), - FLAG_OPT("ws-error", C(ws_error), ws_error_values, redraw_buffer), -}; - -static char *local_ptr(const OptionDesc *desc, LocalOptions *opt) -{ - BUG_ON(!desc->local); - return (char*)opt + desc->offset; -} - -static char *global_ptr(const OptionDesc *desc, GlobalOptions *opt) -{ - BUG_ON(!desc->global); - return (char*)opt + desc->offset; -} - -static char *get_option_ptr(EditorState *e, const OptionDesc *d, bool global) -{ - return global ? global_ptr(d, &e->options) : local_ptr(d, &e->buffer->options); -} - -static inline size_t count_enum_values(const OptionDesc *desc) -{ - OptionType type = desc->type; - BUG_ON(type != OPT_ENUM && type != OPT_FLAG && type != OPT_BOOL); - const char *const *values = desc->u.enum_opt.values; - BUG_ON(!values); - return string_array_length((char**)values); -} - -UNITTEST { - static const struct { - size_t alignment; - size_t size; - } map[] = { - [OPT_STR] = {ALIGNOF(const char*), sizeof(const char*)}, - [OPT_UINT] = {ALIGNOF(unsigned int), sizeof(unsigned int)}, - [OPT_ENUM] = {ALIGNOF(unsigned int), sizeof(unsigned int)}, - [OPT_BOOL] = {ALIGNOF(bool), sizeof(bool)}, - [OPT_FLAG] = {ALIGNOF(unsigned int), sizeof(unsigned int)}, - [OPT_REGEX] = {ALIGNOF(const InternedRegexp*), sizeof(const InternedRegexp*)}, - }; - - GlobalOptions gopts = {.tab_bar = true}; - LocalOptions lopts = {.filetype = NULL}; - - for (size_t i = 0; i < ARRAYLEN(option_desc); i++) { - const OptionDesc *desc = &option_desc[i]; - const OptionType type = desc->type; - BUG_ON(type >= ARRAYLEN(map)); - size_t alignment = map[type].alignment; - size_t end = desc->offset + map[type].size; - if (desc->global) { - uintptr_t ptr_val = (uintptr_t)global_ptr(desc, &gopts); - BUG_ON(ptr_val % alignment != 0); - BUG_ON(end > sizeof(GlobalOptions)); - } - if (desc->local) { - uintptr_t ptr_val = (uintptr_t)local_ptr(desc, &lopts); - BUG_ON(ptr_val % alignment != 0); - BUG_ON(end > sizeof(LocalOptions)); - } - if (desc->global && desc->local) { - BUG_ON(end > sizeof(CommonOptions)); - } - if (type == OPT_UINT) { - BUG_ON(desc->u.uint_opt.max <= desc->u.uint_opt.min); - } else if (type == OPT_BOOL) { - BUG_ON(desc->u.enum_opt.values != bool_enum); - } else if (type == OPT_ENUM) { - BUG_ON(count_enum_values(desc) < 2); - } else if (type == OPT_FLAG) { - size_t nvals = count_enum_values(desc); - OptionValue val = {.uint_val = -1}; - BUG_ON(nvals < 2); - BUG_ON(nvals >= BITSIZE(val.uint_val)); - const char *str = flag_string(desc, val); - BUG_ON(!str); - BUG_ON(str[0] == '\0'); - if (!flag_parse(desc, str, &val)) { - BUG("flag_parse() failed for string: %s", str); - } - unsigned int mask = (1u << nvals) - 1; - if (val.uint_val != mask) { - BUG("values not equal: %u, %u", val.uint_val, mask); - } - } - } - - // Ensure option_desc[] is properly sorted - CHECK_BSEARCH_ARRAY(option_desc, name, strcmp); -} - -static OptionValue desc_get(const OptionDesc *desc, void *ptr) -{ - return option_ops[desc->type].get(desc, ptr); -} - -static void desc_set(EditorState *e, const OptionDesc *desc, void *ptr, bool global, OptionValue value) -{ - option_ops[desc->type].set(desc, ptr, value); - if (desc->on_change) { - desc->on_change(e, global); - } -} - -static bool desc_parse(const OptionDesc *desc, const char *str, OptionValue *value) -{ - return option_ops[desc->type].parse(desc, str, value); -} - -static const char *desc_string(const OptionDesc *desc, OptionValue value) -{ - return option_ops[desc->type].string(desc, value); -} - -static bool desc_equals(const OptionDesc *desc, void *ptr, OptionValue value) -{ - return option_ops[desc->type].equals(desc, ptr, value); -} - -static int option_cmp(const void *key, const void *elem) -{ - const char *name = key; - const OptionDesc *desc = elem; - return strcmp(name, desc->name); -} - -static const OptionDesc *find_option(const char *name) -{ - return BSEARCH(name, option_desc, option_cmp); -} - -static const OptionDesc *must_find_option(const char *name) -{ - const OptionDesc *desc = find_option(name); - if (!desc) { - error_msg("No such option %s", name); - } - return desc; -} - -static const OptionDesc *must_find_global_option(const char *name) -{ - const OptionDesc *desc = must_find_option(name); - if (desc && !desc->global) { - error_msg("Option %s is not global", name); - return NULL; - } - return desc; -} - -static bool do_set_option ( - EditorState *e, - const OptionDesc *desc, - const char *value, - bool local, - bool global -) { - if (local && !desc->local) { - return error_msg("Option %s is not local", desc->name); - } - if (global && !desc->global) { - return error_msg("Option %s is not global", desc->name); - } - - OptionValue val; - if (!desc_parse(desc, value, &val)) { - return false; - } - - if (!local && !global) { - // Set both by default - local = desc->local; - global = desc->global; - } - - if (local) { - desc_set(e, desc, local_ptr(desc, &e->buffer->options), false, val); - } - if (global) { - desc_set(e, desc, global_ptr(desc, &e->options), true, val); - } - - return true; -} - -bool set_option(EditorState *e, const char *name, const char *value, bool local, bool global) -{ - const OptionDesc *desc = must_find_option(name); - if (!desc) { - return false; - } - return do_set_option(e, desc, value, local, global); -} - -bool set_bool_option(EditorState *e, const char *name, bool local, bool global) -{ - const OptionDesc *desc = must_find_option(name); - if (!desc) { - return false; - } - if (desc->type != OPT_BOOL) { - return error_msg("Option %s is not boolean", desc->name); - } - return do_set_option(e, desc, "true", local, global); -} - -static const OptionDesc *find_toggle_option(const char *name, bool *global) -{ - if (*global) { - return must_find_global_option(name); - } - - // Toggle local value by default if option has both values - const OptionDesc *desc = must_find_option(name); - if (desc && !desc->local) { - *global = true; - } - return desc; -} - -bool toggle_option(EditorState *e, const char *name, bool global, bool verbose) -{ - const OptionDesc *desc = find_toggle_option(name, &global); - if (!desc) { - return false; - } - - char *ptr = get_option_ptr(e, desc, global); - OptionValue value = desc_get(desc, ptr); - OptionType type = desc->type; - if (type == OPT_ENUM) { - if (desc->u.enum_opt.values[value.uint_val + 1]) { - value.uint_val++; - } else { - value.uint_val = 0; - } - } else if (type == OPT_BOOL) { - value.bool_val = !value.bool_val; - } else { - return error_msg("Toggling %s requires arguments", name); - } - - desc_set(e, desc, ptr, global, value); - if (verbose) { - const char *prefix = (global && desc->local) ? "[global] " : ""; - const char *str = desc_string(desc, value); - info_msg("%s%s = %s", prefix, desc->name, str); - } - - return true; -} - -bool toggle_option_values ( - EditorState *e, - const char *name, - bool global, - bool verbose, - char **values, - size_t count -) { - const OptionDesc *desc = find_toggle_option(name, &global); - if (!desc) { - return false; - } - - BUG_ON(count == 0); - size_t current = 0; - bool error = false; - char *ptr = get_option_ptr(e, desc, global); - OptionValue *parsed_values = xnew(OptionValue, count); - - for (size_t i = 0; i < count; i++) { - if (desc_parse(desc, values[i], &parsed_values[i])) { - if (desc_equals(desc, ptr, parsed_values[i])) { - current = i + 1; - } - } else { - error = true; - } - } - - if (!error) { - size_t i = current % count; - desc_set(e, desc, ptr, global, parsed_values[i]); - if (verbose) { - const char *prefix = (global && desc->local) ? "[global] " : ""; - const char *str = desc_string(desc, parsed_values[i]); - info_msg("%s%s = %s", prefix, desc->name, str); - } - } - - free(parsed_values); - return !error; -} - -bool validate_local_options(char **strs) -{ - size_t invalid = 0; - for (size_t i = 0; strs[i]; i += 2) { - const char *name = strs[i]; - const char *value = strs[i + 1]; - const OptionDesc *desc = must_find_option(name); - if (unlikely(!desc)) { - invalid++; - } else if (unlikely(!desc->local)) { - error_msg("%s is not local", name); - invalid++; - } else if (unlikely(desc->on_change == filetype_changed)) { - error_msg("filetype cannot be set via option command"); - invalid++; - } else { - OptionValue val; - if (unlikely(!desc_parse(desc, value, &val))) { - invalid++; - } - } - } - return !invalid; -} - -#if DEBUG >= 1 -static void sanity_check_option_value(const OptionDesc *desc, OptionValue val) -{ - switch (desc->type) { - case OPT_STR: - BUG_ON(!val.str_val); - BUG_ON(val.str_val != str_intern(val.str_val)); - if (desc->u.str_opt.validate) { - BUG_ON(!desc->u.str_opt.validate(val.str_val)); - } - return; - case OPT_UINT: - BUG_ON(val.uint_val < desc->u.uint_opt.min); - BUG_ON(val.uint_val > desc->u.uint_opt.max); - return; - case OPT_ENUM: - BUG_ON(val.uint_val >= count_enum_values(desc)); - return; - case OPT_FLAG: { - size_t nvals = count_enum_values(desc); - BUG_ON(nvals >= 32); - unsigned int mask = (1u << nvals) - 1; - unsigned int uint_val = val.uint_val; - BUG_ON((uint_val & mask) != uint_val); - } - return; - case OPT_REGEX: - BUG_ON(val.str_val && val.str_val[0] == '\0'); - BUG_ON(val.str_val && !regexp_is_interned(val.str_val)); - return; - case OPT_BOOL: - return; - } - - BUG("unhandled option type"); -} - -static void sanity_check_options(const void *opts, bool global) -{ - for (size_t i = 0; i < ARRAYLEN(option_desc); i++) { - const OptionDesc *desc = &option_desc[i]; - BUG_ON(desc->type >= ARRAYLEN(option_ops)); - if ((desc->global && desc->local) || global == desc->global) { - OptionValue val = desc_get(desc, (char*)opts + desc->offset); - sanity_check_option_value(desc, val); - } - } -} - -void sanity_check_global_options(const GlobalOptions *gopts) -{ - sanity_check_options(gopts, true); -} - -void sanity_check_local_options(const LocalOptions *lopts) -{ - sanity_check_options(lopts, false); -} -#endif - -void collect_options(PointerArray *a, const char *prefix, bool local, bool global) -{ - for (size_t i = 0; i < ARRAYLEN(option_desc); i++) { - const OptionDesc *desc = &option_desc[i]; - if ((local && !desc->local) || (global && !desc->global)) { - continue; - } - if (str_has_prefix(desc->name, prefix)) { - ptr_array_append(a, xstrdup(desc->name)); - } - } -} - -// Collect options that can be set via the "option" command -void collect_auto_options(PointerArray *a, const char *prefix) -{ - for (size_t i = 0; i < ARRAYLEN(option_desc); i++) { - const OptionDesc *desc = &option_desc[i]; - if (!desc->local || desc->on_change == filetype_changed) { - continue; - } - if (str_has_prefix(desc->name, prefix)) { - ptr_array_append(a, xstrdup(desc->name)); - } - } -} - -void collect_toggleable_options(PointerArray *a, const char *prefix, bool global) -{ - for (size_t i = 0; i < ARRAYLEN(option_desc); i++) { - const OptionDesc *desc = &option_desc[i]; - if (global && !desc->global) { - continue; - } - OptionType type = desc->type; - bool toggleable = (type == OPT_ENUM || type == OPT_BOOL); - if (toggleable && str_has_prefix(desc->name, prefix)) { - ptr_array_append(a, xstrdup(desc->name)); - } - } -} - -void collect_option_values(EditorState *e, PointerArray *a, const char *option, const char *prefix) -{ - const OptionDesc *desc = find_option(option); - if (!desc) { - return; - } - - if (prefix[0] == '\0') { - char *ptr = get_option_ptr(e, desc, !desc->local); - OptionValue value = desc_get(desc, ptr); - ptr_array_append(a, xstrdup(desc_string(desc, value))); - return; - } - - OptionType type = desc->type; - if (type == OPT_STR || type == OPT_UINT || type == OPT_REGEX) { - return; - } - - const char *const *values = desc->u.enum_opt.values; - if (type == OPT_ENUM || type == OPT_BOOL) { - for (size_t i = 0; values[i]; i++) { - if (str_has_prefix(values[i], prefix)) { - ptr_array_append(a, xstrdup(values[i])); - } - } - return; - } - - BUG_ON(type != OPT_FLAG); - const char *comma = strrchr(prefix, ','); - size_t prefix_len = comma ? ++comma - prefix : 0; - for (size_t i = 0; values[i]; i++) { - const char *str = values[i]; - if (str_has_prefix(str, prefix + prefix_len)) { - size_t str_len = strlen(str); - char *completion = xmalloc(prefix_len + str_len + 1); - memcpy(completion, prefix, prefix_len); - memcpy(completion + prefix_len, str, str_len + 1); - ptr_array_append(a, completion); - } - } -} - -static void append_option(String *s, const OptionDesc *desc, void *ptr) -{ - const OptionValue value = desc_get(desc, ptr); - const char *value_str = desc_string(desc, value); - if (unlikely(value_str[0] == '-')) { - string_append_literal(s, "-- "); - } - string_append_cstring(s, desc->name); - string_append_byte(s, ' '); - string_append_escaped_arg(s, value_str, true); - string_append_byte(s, '\n'); -} - -String dump_options(GlobalOptions *gopts, LocalOptions *lopts) -{ - String buf = string_new(4096); - for (size_t i = 0; i < ARRAYLEN(option_desc); i++) { - const OptionDesc *desc = &option_desc[i]; - void *local = desc->local ? local_ptr(desc, lopts) : NULL; - void *global = desc->global ? global_ptr(desc, gopts) : NULL; - if (local && global) { - const OptionValue global_value = desc_get(desc, global); - if (desc_equals(desc, local, global_value)) { - string_append_literal(&buf, "set "); - append_option(&buf, desc, local); - } else { - string_append_literal(&buf, "set -g "); - append_option(&buf, desc, global); - string_append_literal(&buf, "set -l "); - append_option(&buf, desc, local); - } - } else { - string_append_literal(&buf, "set "); - append_option(&buf, desc, local ? local : global); - } - } - return buf; -} - -const char *get_option_value_string(EditorState *e, const char *name) -{ - const OptionDesc *desc = find_option(name); - if (!desc) { - return NULL; - } - char *ptr = get_option_ptr(e, desc, !desc->local); - return desc_string(desc, desc_get(desc, ptr)); -} diff --git a/examples/dte/options.h b/examples/dte/options.h deleted file mode 100644 index 1d0a129..0000000 --- a/examples/dte/options.h +++ /dev/null @@ -1,114 +0,0 @@ -#ifndef OPTIONS_H -#define OPTIONS_H - -#include <stdbool.h> -#include <stddef.h> -#include "regexp.h" -#include "util/macros.h" -#include "util/ptr-array.h" -#include "util/string.h" - -enum { - INDENT_WIDTH_MAX = 8, - TAB_WIDTH_MAX = 8, - TEXT_WIDTH_MAX = 1000, -}; - -// Note: this must be kept in sync with ws_error_values[] -typedef enum { - WSE_SPACE_INDENT = 1 << 0, // Spaces in indent (except WSE_SPACE_ALIGN) - WSE_SPACE_ALIGN = 1 << 1, // Less than tab-width spaces at end of indent - WSE_TAB_INDENT = 1 << 2, // Tab in indent - WSE_TAB_AFTER_INDENT = 1 << 3, // Tab anywhere but indent - WSE_SPECIAL = 1 << 4, // Special whitespace characters - WSE_AUTO_INDENT = 1 << 5, // expand-tab ? WSE_TAB_AFTER_INDENT | WSE_TAB_INDENT : WSE_SPACE_INDENT - WSE_TRAILING = 1 << 6, // Trailing whitespace - WSE_ALL_TRAILING = 1 << 7, // Like WSE_TRAILING, but including around cursor -} WhitespaceErrorFlags; - -// Note: this must be kept in sync with save_unmodified_enum[] -typedef enum { - SAVE_NONE, - SAVE_TOUCH, - SAVE_FULL, -} SaveUnmodifiedType; - -#define COMMON_OPTIONS \ - unsigned int detect_indent; \ - unsigned int indent_width; \ - unsigned int save_unmodified; \ - unsigned int tab_width; \ - unsigned int text_width; \ - unsigned int ws_error; \ - bool auto_indent; \ - bool editorconfig; \ - bool emulate_tab; \ - bool expand_tab; \ - bool file_history; \ - bool fsync; \ - bool overwrite; \ - bool syntax - -typedef struct { - COMMON_OPTIONS; -} CommonOptions; - -// Note: all members should be initialized in buffer_new() -typedef struct { - COMMON_OPTIONS; - // Only local - bool brace_indent; - const char *filetype; - const InternedRegexp *indent_regex; -} LocalOptions; - -typedef struct { - COMMON_OPTIONS; - // Only global - bool display_special; - bool lock_files; - bool optimize_true_color; - bool select_cursor_char; - bool set_window_title; - bool show_line_numbers; - bool tab_bar; - bool utf8_bom; // Default value for new files - unsigned int esc_timeout; - unsigned int filesize_limit; - unsigned int scroll_margin; - unsigned int crlf_newlines; // Default value for new files - unsigned int case_sensitive_search; // SearchCaseSensitivity - const char *statusline_left; - const char *statusline_right; -} GlobalOptions; - -#undef COMMON_OPTIONS - -static inline bool use_spaces_for_indent(const LocalOptions *opt) -{ - return opt->expand_tab || opt->indent_width != opt->tab_width; -} - -struct EditorState; - -bool set_option(struct EditorState *e, const char *name, const char *value, bool local, bool global); -bool set_bool_option(struct EditorState *e, const char *name, bool local, bool global); -bool toggle_option(struct EditorState *e, const char *name, bool global, bool verbose); -bool toggle_option_values(struct EditorState *e, const char *name, bool global, bool verbose, char **values, size_t count); -bool validate_local_options(char **strs); -void collect_options(PointerArray *a, const char *prefix, bool local, bool global); -void collect_auto_options(PointerArray *a, const char *prefix); -void collect_toggleable_options(PointerArray *a, const char *prefix, bool global); -void collect_option_values(struct EditorState *e, PointerArray *a, const char *option, const char *prefix); -String dump_options(GlobalOptions *gopts, LocalOptions *lopts); -const char *get_option_value_string(struct EditorState *e, const char *name); - -#if DEBUG >= 1 - void sanity_check_global_options(const GlobalOptions *opts); - void sanity_check_local_options(const LocalOptions *lopts); -#else - static inline void sanity_check_global_options(const GlobalOptions* UNUSED_ARG(gopts)) {} - static inline void sanity_check_local_options(const LocalOptions* UNUSED_ARG(lopts)) {} -#endif - -#endif diff --git a/examples/dte/regexp.c b/examples/dte/regexp.c deleted file mode 100644 index dc4eb0f..0000000 --- a/examples/dte/regexp.c +++ /dev/null @@ -1,151 +0,0 @@ -#include <errno.h> -#include <stdlib.h> -#include "regexp.h" -#include "error.h" -#include "util/debug.h" -#include "util/hashmap.h" -#include "util/str-util.h" -#include "util/xmalloc.h" -#include "util/xsnprintf.h" - -static HashMap interned_regexps; - -bool regexp_error_msg(const regex_t *re, const char *pattern, int err) -{ - char msg[1024]; - regerror(err, re, msg, sizeof(msg)); - return error_msg("%s: %s", msg, pattern); -} - -bool regexp_compile_internal(regex_t *re, const char *pattern, int flags) -{ - int err = regcomp(re, pattern, flags); - if (err) { - return regexp_error_msg(re, pattern, err); - } - return true; -} - -void regexp_compile_or_fatal_error(regex_t *re, const char *pattern, int flags) -{ - // Note: DEFAULT_REGEX_FLAGS isn't used here because this function - // is only used for compiling built-in patterns, where we explicitly - // avoid using "enhanced" features - int err = regcomp(re, pattern, flags | REG_EXTENDED); - if (unlikely(err)) { - char msg[1024]; - regerror(err, re, msg, sizeof(msg)); - fatal_error(msg, EINVAL); - } -} - -bool regexp_exec ( - const regex_t *re, - const char *buf, - size_t size, - size_t nmatch, - regmatch_t *pmatch, - int flags -) { - // "If REG_STARTEND is specified, pmatch must point to at least one - // regmatch_t (even if nmatch is 0 or REG_NOSUB was specified), to - // hold the input offsets for REG_STARTEND." - // -- https://man.openbsd.org/regex.3 - BUG_ON(!pmatch); - -// ASan's __interceptor_regexec() doesn't support REG_STARTEND -#if defined(REG_STARTEND) && !defined(ASAN_ENABLED) && !defined(MSAN_ENABLED) - pmatch[0].rm_so = 0; - pmatch[0].rm_eo = size; - return !regexec(re, buf, nmatch, pmatch, flags | REG_STARTEND); -#else - // Buffer must be null-terminated if REG_STARTEND isn't supported - char *tmp = xstrcut(buf, size); - int ret = !regexec(re, tmp, nmatch, pmatch, flags); - free(tmp); - return ret; -#endif -} - -// Check which word boundary tokens are supported by regcomp(3) -// (if any) and initialize `rwbt` with them for later use -bool regexp_init_word_boundary_tokens(RegexpWordBoundaryTokens *rwbt) -{ - static const char text[] = "SSfooEE SSfoo fooEE foo SSfooEE"; - const regoff_t match_start = 20, match_end = 23; - static const RegexpWordBoundaryTokens pairs[] = { - {"\\<", "\\>"}, - {"[[:<:]]", "[[:>:]]"}, - {"\\b", "\\b"}, - }; - - BUG_ON(ARRAYLEN(text) <= match_end); - BUG_ON(!mem_equal(text + match_start - 1, " foo ", 5)); - - for (size_t i = 0; i < ARRAYLEN(pairs); i++) { - const char *start = pairs[i].start; - const char *end = pairs[i].end; - char patt[32]; - xsnprintf(patt, sizeof(patt), "%s(foo)%s", start, end); - regex_t re; - if (regcomp(&re, patt, DEFAULT_REGEX_FLAGS) != 0) { - continue; - } - regmatch_t m[2]; - bool match = !regexec(&re, text, ARRAYLEN(m), m, 0); - regfree(&re); - if (match && m[0].rm_so == match_start && m[0].rm_eo == match_end) { - *rwbt = pairs[i]; - return true; - } - } - - return false; -} - -void free_cached_regexp(CachedRegexp *cr) -{ - regfree(&cr->re); - free(cr); -} - -const InternedRegexp *regexp_intern(const char *pattern) -{ - if (pattern[0] == '\0') { - return NULL; - } - - InternedRegexp *ir = hashmap_get(&interned_regexps, pattern); - if (ir) { - return ir; - } - - ir = xnew(InternedRegexp, 1); - int err = regcomp(&ir->re, pattern, DEFAULT_REGEX_FLAGS | REG_NEWLINE | REG_NOSUB); - if (unlikely(err)) { - regexp_error_msg(&ir->re, pattern, err); - free(ir); - return NULL; - } - - ir->str = xstrdup(pattern); - return hashmap_insert(&interned_regexps, ir->str, ir); -} - -bool regexp_is_interned(const char *pattern) -{ - return !!hashmap_find(&interned_regexps, pattern); -} - -// Note: this does NOT free InternedRegexp::str, because it points at the -// same string as HashMapEntry::key and is already freed by hashmap_free() -static void free_interned_regexp(InternedRegexp *ir) -{ - regfree(&ir->re); - free(ir); -} - -void free_interned_regexps(void) -{ - hashmap_free(&interned_regexps, (FreeFunction)free_interned_regexp); -} diff --git a/examples/dte/regexp.h b/examples/dte/regexp.h deleted file mode 100644 index 50fdabb..0000000 --- a/examples/dte/regexp.h +++ /dev/null @@ -1,85 +0,0 @@ -#ifndef REGEXP_H -#define REGEXP_H - -#include <regex.h> -#include <stdbool.h> -#include <stddef.h> -#include "util/macros.h" - -enum { -#ifdef REG_ENHANCED - // The REG_ENHANCED flag enables various extensions on macOS - // (see "enhanced features" in re_format(7)). Most of these - // extensions are enabled by default on Linux (in both glibc - // and musl) without the need for any extra flags. - DEFAULT_REGEX_FLAGS = REG_EXTENDED | REG_ENHANCED, -#else - // POSIX Extended Regular Expressions (ERE) are used almost - // everywhere in this codebase, except where Basic Regular - // Expressions (BRE) are explicitly called for (most notably - // in search_tag(), which is used for ctags patterns). - DEFAULT_REGEX_FLAGS = REG_EXTENDED, -#endif -}; - -typedef struct { - regex_t re; - char str[]; -} CachedRegexp; - -typedef struct { - char *str; - regex_t re; -} InternedRegexp; - -// Platform-specific patterns for matching word boundaries, as detected -// and initialized by regexp_init_word_boundary_tokens() -typedef struct { - char start[8]; - char end[8]; -} RegexpWordBoundaryTokens; - -bool regexp_compile_internal(regex_t *re, const char *pattern, int flags) WARN_UNUSED_RESULT; - -WARN_UNUSED_RESULT -static inline bool regexp_compile(regex_t *re, const char *pattern, int flags) -{ - return regexp_compile_internal(re, pattern, flags | DEFAULT_REGEX_FLAGS); -} - -WARN_UNUSED_RESULT -static inline bool regexp_compile_basic(regex_t *re, const char *pattern, int flags) -{ - return regexp_compile_internal(re, pattern, flags); -} - -WARN_UNUSED_RESULT -static inline bool regexp_is_valid(const char *pattern, int flags) -{ - regex_t re; - if (!regexp_compile(&re, pattern, flags | REG_NOSUB)) { - return false; - } - regfree(&re); - return true; -} - -void regexp_compile_or_fatal_error(regex_t *re, const char *pattern, int flags); -bool regexp_init_word_boundary_tokens(RegexpWordBoundaryTokens *rwbt); -bool regexp_error_msg(const regex_t *re, const char *pattern, int err); -void free_cached_regexp(CachedRegexp *cr); - -const InternedRegexp *regexp_intern(const char *pattern); -bool regexp_is_interned(const char *pattern); -void free_interned_regexps(void); - -bool regexp_exec ( - const regex_t *re, - const char *buf, - size_t size, - size_t nmatch, - regmatch_t *pmatch, - int flags -) WARN_UNUSED_RESULT; - -#endif diff --git a/examples/dte/replace.c b/examples/dte/replace.c deleted file mode 100644 index 028d474..0000000 --- a/examples/dte/replace.c +++ /dev/null @@ -1,256 +0,0 @@ -#include <stdlib.h> -#include "replace.h" -#include "buffer.h" -#include "change.h" -#include "editor.h" -#include "error.h" -#include "regexp.h" -#include "screen.h" -#include "selection.h" -#include "util/debug.h" -#include "util/string.h" -#include "util/xmalloc.h" -#include "view.h" -#include "window.h" - -static void build_replacement ( - String *buf, - const char *line, - const char *format, - const regmatch_t *matches -) { - for (size_t i = 0; format[i]; ) { - char ch = format[i++]; - size_t match_idx; - if (ch == '\\') { - if (unlikely(format[i] == '\0')) { - break; - } - ch = format[i++]; - if (ch < '1' || ch > '9') { - string_append_byte(buf, ch); - continue; - } - match_idx = ch - '0'; - } else if (ch == '&') { - match_idx = 0; - } else { - string_append_byte(buf, ch); - continue; - } - const regmatch_t *match = &matches[match_idx]; - regoff_t len = match->rm_eo - match->rm_so; - if (len > 0) { - string_append_buf(buf, line + match->rm_so, (size_t)len); - } - } -} - -/* - * s/abc/x - * - * string to match against - * ------------------------------------------- - * "foo abc bar abc baz" "foo abc bar abc baz" - * "foo x bar abc baz" " bar abc baz" - */ -static unsigned int replace_on_line ( - View *view, - StringView *line, - regex_t *re, - const char *format, - BlockIter *bi, - ReplaceFlags *flagsp -) { - const unsigned char *buf = line->data; - unsigned char *alloc = NULL; - EditorState *e = view->window->editor; - ReplaceFlags flags = *flagsp; - regmatch_t matches[32]; - size_t pos = 0; - int eflags = 0; - unsigned int nr = 0; - - while (regexp_exec ( - re, - buf + pos, - line->length - pos, - ARRAYLEN(matches), - matches, - eflags - )) { - regoff_t match_len = matches[0].rm_eo - matches[0].rm_so; - bool skip = false; - - // Move cursor to beginning of the text to replace - block_iter_skip_bytes(bi, matches[0].rm_so); - view->cursor = *bi; - - if (flags & REPLACE_CONFIRM) { - switch (status_prompt(e, "Replace? [Y/n/a/q]", "ynaq")) { - case 'y': - break; - case 'n': - skip = true; - break; - case 'a': - flags &= ~REPLACE_CONFIRM; - *flagsp = flags; - - // Record rest of the changes as one chain - begin_change_chain(); - break; - case 'q': - case 0: - *flagsp = flags | REPLACE_CANCEL; - goto out; - } - } - - if (skip) { - // Move cursor after the matched text - block_iter_skip_bytes(&view->cursor, match_len); - } else { - String b = STRING_INIT; - build_replacement(&b, buf + pos, format, matches); - - // line ref is invalidated by modification - if (buf == line->data && line->length != 0) { - BUG_ON(alloc); - alloc = xmemdup(buf, line->length); - buf = alloc; - } - - buffer_replace_bytes(view, match_len, b.buffer, b.len); - nr++; - - // Update selection length - if (view->selection) { - view->sel_eo += b.len; - view->sel_eo -= match_len; - } - - // Move cursor after the replaced text - block_iter_skip_bytes(&view->cursor, b.len); - string_free(&b); - } - *bi = view->cursor; - - if (!match_len) { - break; - } - - if (!(flags & REPLACE_GLOBAL)) { - break; - } - - pos += matches[0].rm_so + match_len; - - // Don't match beginning of line again - eflags = REG_NOTBOL; - } - -out: - free(alloc); - return nr; -} - -bool reg_replace(View *view, const char *pattern, const char *format, ReplaceFlags flags) -{ - if (unlikely(pattern[0] == '\0')) { - return error_msg("Search pattern must contain at least 1 character"); - } - - int re_flags = REG_NEWLINE; - re_flags |= (flags & REPLACE_IGNORE_CASE) ? REG_ICASE : 0; - re_flags |= (flags & REPLACE_BASIC) ? 0 : DEFAULT_REGEX_FLAGS; - - regex_t re; - if (unlikely(!regexp_compile_internal(&re, pattern, re_flags))) { - return false; - } - - BlockIter bi = block_iter(view->buffer); - size_t nr_bytes; - bool swapped = false; - if (view->selection) { - SelectionInfo info; - init_selection(view, &info); - view->cursor = info.si; - view->sel_so = info.so; - view->sel_eo = info.eo; - swapped = info.swapped; - bi = view->cursor; - nr_bytes = info.eo - info.so; - } else { - BlockIter eof = bi; - block_iter_eof(&eof); - nr_bytes = block_iter_get_offset(&eof); - } - - // Record multiple changes as one chain only when replacing all - if (!(flags & REPLACE_CONFIRM)) { - begin_change_chain(); - } - - unsigned int nr_substitutions = 0; - size_t nr_lines = 0; - while (1) { - StringView line; - fill_line_ref(&bi, &line); - - // Number of bytes to process - size_t count = line.length; - if (line.length > nr_bytes) { - // End of selection is not full line - line.length = nr_bytes; - } - - unsigned int nr = replace_on_line(view, &line, &re, format, &bi, &flags); - if (nr) { - nr_substitutions += nr; - nr_lines++; - } - - if (flags & REPLACE_CANCEL || count + 1 >= nr_bytes) { - break; - } - - nr_bytes -= count + 1; - block_iter_next_line(&bi); - } - - if (!(flags & REPLACE_CONFIRM)) { - end_change_chain(view); - } - - regfree(&re); - - if (nr_substitutions) { - info_msg ( - "%u substitution%s on %zu line%s", - nr_substitutions, - (nr_substitutions > 1) ? "s" : "", - nr_lines, - (nr_lines > 1) ? "s" : "" - ); - } else if (!(flags & REPLACE_CANCEL)) { - error_msg("Pattern '%s' not found", pattern); - } - - if (view->selection) { - // Undo what init_selection() did - if (view->sel_eo) { - view->sel_eo--; - } - if (swapped) { - ssize_t tmp = view->sel_so; - view->sel_so = view->sel_eo; - view->sel_eo = tmp; - } - block_iter_goto_offset(&view->cursor, view->sel_eo); - view->sel_eo = SEL_EO_RECALC; - } - - return (nr_substitutions > 0); -} diff --git a/examples/dte/replace.h b/examples/dte/replace.h deleted file mode 100644 index 783a46f..0000000 --- a/examples/dte/replace.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef REPLACE_H -#define REPLACE_H - -#include <stdbool.h> -#include "util/macros.h" -#include "view.h" - -typedef enum { - REPLACE_CONFIRM = 1 << 0, - REPLACE_GLOBAL = 1 << 1, - REPLACE_IGNORE_CASE = 1 << 2, - REPLACE_BASIC = 1 << 3, - REPLACE_CANCEL = 1 << 4, -} ReplaceFlags; - -bool reg_replace(View *view, const char *pattern, const char *format, ReplaceFlags flags) NONNULL_ARGS; - -#endif diff --git a/examples/dte/screen-cmdline.c b/examples/dte/screen-cmdline.c deleted file mode 100644 index 58d04c4..0000000 --- a/examples/dte/screen-cmdline.c +++ /dev/null @@ -1,91 +0,0 @@ -#include "screen.h" -#include "error.h" -#include "search.h" - -static void print_message(Terminal *term, const ColorScheme *colors, const char *msg, bool is_error) -{ - BuiltinColorEnum c = BC_COMMANDLINE; - if (msg[0]) { - c = is_error ? BC_ERRORMSG : BC_INFOMSG; - } - - TermOutputBuffer *obuf = &term->obuf; - set_builtin_color(term, colors, c); - - for (size_t i = 0; msg[i]; ) { - CodePoint u = u_get_char(msg, i + 4, &i); - if (!term_put_char(obuf, u)) { - break; - } - } -} - -void show_message(Terminal *term, const ColorScheme *colors, const char *msg, bool is_error) -{ - term_output_reset(term, 0, term->width, 0); - term_move_cursor(&term->obuf, 0, term->height - 1); - print_message(term, colors, msg, is_error); - term_clear_eol(term); -} - -static size_t print_command(Terminal *term, const ColorScheme *colors, const CommandLine *cmdline, char prefix) -{ - const String *buf = &cmdline->buf; - TermOutputBuffer *obuf = &term->obuf; - - // Width of characters up to and including cursor position - size_t w = 1; // ":" (prefix) - - for (size_t i = 0; i <= cmdline->pos && i < buf->len; ) { - CodePoint u = u_get_char(buf->buffer, buf->len, &i); - w += u_char_width(u); - } - if (cmdline->pos == buf->len) { - w++; - } - if (w > term->width) { - obuf->scroll_x = w - term->width; - } - - set_builtin_color(term, colors, BC_COMMANDLINE); - term_put_char(obuf, prefix); - - size_t x = obuf->x - obuf->scroll_x; - for (size_t i = 0; i < buf->len; ) { - BUG_ON(obuf->x > obuf->scroll_x + obuf->width); - CodePoint u = u_get_char(buf->buffer, buf->len, &i); - if (!term_put_char(obuf, u)) { - break; - } - if (i <= cmdline->pos) { - x = obuf->x - obuf->scroll_x; - } - } - - return x; -} - -void update_command_line(EditorState *e) -{ - Terminal *term = &e->terminal; - char prefix = ':'; - term_output_reset(term, 0, term->width, 0); - term_move_cursor(&term->obuf, 0, term->height - 1); - switch (e->input_mode) { - case INPUT_NORMAL: { - bool msg_is_error; - const char *msg = get_msg(&msg_is_error); - print_message(term, &e->colors, msg, msg_is_error); - break; - } - case INPUT_SEARCH: - prefix = e->search.reverse ? '?' : '/'; - // Fallthrough - case INPUT_COMMAND: - e->cmdline_x = print_command(term, &e->colors, &e->cmdline, prefix); - break; - default: - BUG("unhandled input mode"); - } - term_clear_eol(term); -} diff --git a/examples/dte/screen-prompt.c b/examples/dte/screen-prompt.c deleted file mode 100644 index b5bbe7d..0000000 --- a/examples/dte/screen-prompt.c +++ /dev/null @@ -1,134 +0,0 @@ -#include "screen.h" -#include "signals.h" -#include "terminal/input.h" - -static char get_choice(Terminal *term, const char *choices, unsigned int esc_timeout) -{ - KeyCode key = term_read_key(term, esc_timeout); - if (key == KEY_NONE) { - return 0; - } - - switch (key) { - case KEY_BRACKETED_PASTE: - case KEY_DETECTED_PASTE: - term_discard_paste(&term->ibuf, key == KEY_BRACKETED_PASTE); - return 0; - case MOD_CTRL | 'c': - case MOD_CTRL | 'g': - case MOD_CTRL | '[': - return 0x18; // Cancel - case KEY_ENTER: - return choices[0]; // Default - } - - if (key < 128) { - char ch = ascii_tolower(key); - if (strchr(choices, ch)) { - return ch; - } - } - return 0; -} - -static void show_dialog ( - EditorState *e, - const TermColor *text_color, - const char *question -) { - Terminal *term = &e->terminal; - unsigned int question_width = u_str_width(question); - unsigned int min_width = question_width + 2; - if (term->height < 12 || term->width < min_width) { - return; - } - - unsigned int height = term->height / 4; - unsigned int mid = term->height / 2; - unsigned int top = mid - (height / 2); - unsigned int bot = top + height; - unsigned int width = MAX(term->width / 2, min_width); - unsigned int x = (term->width - width) / 2; - - // The "underline" and "strikethrough" attributes should only apply - // to the text, not the whole dialog background: - TermColor dialog_color = *text_color; - TermOutputBuffer *obuf = &term->obuf; - dialog_color.attr &= ~(ATTR_UNDERLINE | ATTR_STRIKETHROUGH); - set_color(term, &e->colors, &dialog_color); - - for (unsigned int y = top; y < bot; y++) { - term_output_reset(term, x, width, 0); - term_move_cursor(obuf, x, y); - if (y == mid) { - term_set_bytes(term, ' ', (width - question_width) / 2); - set_color(term, &e->colors, text_color); - term_add_str(obuf, question); - set_color(term, &e->colors, &dialog_color); - } - term_clear_eol(term); - } -} - -char dialog_prompt(EditorState *e, const char *question, const char *choices) -{ - const TermColor *color = &e->colors.builtin[BC_DIALOG]; - Terminal *term = &e->terminal; - TermOutputBuffer *obuf = &term->obuf; - - normal_update(e); - term_hide_cursor(term); - show_dialog(e, color, question); - show_message(term, &e->colors, question, false); - term_output_flush(obuf); - - unsigned int esc_timeout = e->options.esc_timeout; - char choice; - while ((choice = get_choice(term, choices, esc_timeout)) == 0) { - if (!resized) { - continue; - } - ui_resize(e); - term_hide_cursor(term); - show_dialog(e, color, question); - show_message(term, &e->colors, question, false); - term_output_flush(obuf); - } - - mark_everything_changed(e); - return (choice >= 'a') ? choice : 0; -} - -char status_prompt(EditorState *e, const char *question, const char *choices) -{ - // update_buffer_windows() assumes these have been called for current view - view_update_cursor_x(e->view); - view_update_cursor_y(e->view); - view_update(e->view, e->options.scroll_margin); - - // Set changed_line_min and changed_line_max before calling update_range() - mark_all_lines_changed(e->buffer); - - Terminal *term = &e->terminal; - start_update(term); - update_term_title(term, e->buffer, e->options.set_window_title); - update_buffer_windows(e, e->buffer); - show_message(term, &e->colors, question, false); - end_update(e); - - unsigned int esc_timeout = e->options.esc_timeout; - char choice; - while ((choice = get_choice(term, choices, esc_timeout)) == 0) { - if (!resized) { - continue; - } - ui_resize(e); - term_hide_cursor(term); - show_message(term, &e->colors, question, false); - restore_cursor(e); - term_show_cursor(term); - term_output_flush(&term->obuf); - } - - return (choice >= 'a') ? choice : 0; -} diff --git a/examples/dte/screen-status.c b/examples/dte/screen-status.c deleted file mode 100644 index c7ec837..0000000 --- a/examples/dte/screen-status.c +++ /dev/null @@ -1,46 +0,0 @@ -#include "screen.h" -#include "status.h" - -void update_status_line(const Window *window) -{ - EditorState *e = window->editor; - const GlobalOptions *opts = &e->options; - InputMode mode = e->input_mode; - char lbuf[512], rbuf[512]; - sf_format(window, opts, mode, lbuf, sizeof lbuf, opts->statusline_left); - sf_format(window, opts, mode, rbuf, sizeof rbuf, opts->statusline_right); - - const ColorScheme *colors = &e->colors; - Terminal *term = &e->terminal; - TermOutputBuffer *obuf = &term->obuf; - size_t lw = u_str_width(lbuf); - size_t rw = u_str_width(rbuf); - int w = window->w; - static_assert_compatible_types(w, window->w); - term_output_reset(term, window->x, w, 0); - term_move_cursor(obuf, window->x, window->y + window->h - 1); - set_builtin_color(term, colors, BC_STATUSLINE); - - if (lw + rw <= w) { - // Both fit - term_add_str(obuf, lbuf); - term_set_bytes(term, ' ', w - lw - rw); - term_add_str(obuf, rbuf); - } else if (lw <= w && rw <= w) { - // Both would fit separately, draw overlapping - term_add_str(obuf, lbuf); - obuf->x = w - rw; - term_move_cursor(obuf, window->x + w - rw, window->y + window->h - 1); - term_add_str(obuf, rbuf); - } else if (lw <= w) { - // Left fits - term_add_str(obuf, lbuf); - term_clear_eol(term); - } else if (rw <= w) { - // Right fits - term_set_bytes(term, ' ', w - rw); - term_add_str(obuf, rbuf); - } else { - term_clear_eol(term); - } -} diff --git a/examples/dte/screen-tabbar.c b/examples/dte/screen-tabbar.c deleted file mode 100644 index 57de14e..0000000 --- a/examples/dte/screen-tabbar.c +++ /dev/null @@ -1,176 +0,0 @@ -#include "screen.h" -#include "util/numtostr.h" -#include "util/strtonum.h" - -static size_t tab_title_width(size_t tab_number, const char *filename) -{ - return 3 + size_str_width(tab_number) + u_str_width(filename); -} - -static void update_tab_title_width(View *view, size_t tab_number) -{ - size_t w = tab_title_width(tab_number, buffer_filename(view->buffer)); - view->tt_width = w; - view->tt_truncated_width = w; -} - -static void update_first_tab_idx(Window *window) -{ - size_t max_first_idx = window->views.count; - for (size_t w = 0; max_first_idx > 0; max_first_idx--) { - const View *view = window->views.ptrs[max_first_idx - 1]; - w += view->tt_truncated_width; - if (w > window->w) { - break; - } - } - - size_t min_first_idx = window->views.count; - for (size_t w = 0; min_first_idx > 0; min_first_idx--) { - const View *view = window->views.ptrs[min_first_idx - 1]; - if (w || view == window->view) { - w += view->tt_truncated_width; - } - if (w > window->w) { - break; - } - } - - size_t idx = CLAMP(window->first_tab_idx, min_first_idx, max_first_idx); - window->first_tab_idx = idx; -} - -static void calculate_tabbar(Window *window) -{ - int total_w = 0; - for (size_t i = 0, n = window->views.count; i < n; i++) { - View *view = window->views.ptrs[i]; - if (view == window->view) { - // Make sure current tab is visible - window->first_tab_idx = MIN(i, window->first_tab_idx); - } - update_tab_title_width(view, i + 1); - total_w += view->tt_width; - } - - if (total_w <= window->w) { - // All tabs fit without truncating - window->first_tab_idx = 0; - return; - } - - // Truncate all wide tabs - total_w = 0; - int truncated_count = 0; - for (size_t i = 0, n = window->views.count; i < n; i++) { - View *view = window->views.ptrs[i]; - int truncated_w = 20; - if (view->tt_width > truncated_w) { - view->tt_truncated_width = truncated_w; - total_w += truncated_w; - truncated_count++; - } else { - total_w += view->tt_width; - } - } - - if (total_w > window->w) { - // Not all tabs fit even after truncating wide tabs - update_first_tab_idx(window); - return; - } - - // All tabs fit after truncating wide tabs - int extra = window->w - total_w; - - // Divide extra space between truncated tabs - while (extra > 0) { - BUG_ON(truncated_count == 0); - int extra_avg = extra / truncated_count; - int extra_mod = extra % truncated_count; - - for (size_t i = 0, n = window->views.count; i < n; i++) { - View *view = window->views.ptrs[i]; - int add = view->tt_width - view->tt_truncated_width; - if (add == 0) { - continue; - } - - int avail = extra_avg; - if (extra_mod) { - // This is needed for equal divide - if (extra_avg == 0) { - avail++; - extra_mod--; - } - } - if (add > avail) { - add = avail; - } else { - truncated_count--; - } - - view->tt_truncated_width += add; - extra -= add; - } - } - - window->first_tab_idx = 0; -} - -static void print_tab_title(Terminal *term, const ColorScheme *colors, const View *view, size_t idx) -{ - const char *filename = buffer_filename(view->buffer); - int skip = view->tt_width - view->tt_truncated_width; - if (skip > 0) { - filename += u_skip_chars(filename, &skip); - } - - const char *tab_number = uint_to_str((unsigned int)idx + 1); - TermOutputBuffer *obuf = &term->obuf; - bool is_active_tab = (view == view->window->view); - bool is_modified = buffer_modified(view->buffer); - bool left_overflow = (obuf->x == 0 && idx > 0); - - set_builtin_color(term, colors, is_active_tab ? BC_ACTIVETAB : BC_INACTIVETAB); - term_put_char(obuf, left_overflow ? '<' : ' '); - term_add_str(obuf, tab_number); - term_put_char(obuf, is_modified ? '+' : ':'); - term_add_str(obuf, filename); - - size_t ntabs = view->window->views.count; - bool right_overflow = (obuf->x == (obuf->width - 1) && idx < (ntabs - 1)); - term_put_char(obuf, right_overflow ? '>' : ' '); -} - -void print_tabbar(Terminal *term, const ColorScheme *colors, Window *window) -{ - TermOutputBuffer *obuf = &term->obuf; - term_output_reset(term, window->x, window->w, 0); - term_move_cursor(obuf, window->x, window->y); - calculate_tabbar(window); - - size_t i = window->first_tab_idx; - size_t n = window->views.count; - for (; i < n; i++) { - const View *view = window->views.ptrs[i]; - if (obuf->x + view->tt_truncated_width > window->w) { - break; - } - print_tab_title(term, colors, view, i); - } - - set_builtin_color(term, colors, BC_TABBAR); - - if (i == n) { - term_clear_eol(term); - return; - } - - while (obuf->x < obuf->width - 1) { - term_put_char(obuf, ' '); - } - if (obuf->x == obuf->width - 1) { - term_put_char(obuf, '>'); - } -} diff --git a/examples/dte/screen-view.c b/examples/dte/screen-view.c deleted file mode 100644 index 0f8d72c..0000000 --- a/examples/dte/screen-view.c +++ /dev/null @@ -1,427 +0,0 @@ -#include "screen.h" -#include "indent.h" -#include "selection.h" -#include "syntax/highlight.h" -#include "util/ascii.h" -#include "util/debug.h" -#include "util/str-util.h" -#include "util/utf8.h" - -typedef struct { - const View *view; - size_t line_nr; - size_t offset; - ssize_t sel_so; - ssize_t sel_eo; - - const unsigned char *line; - size_t size; - size_t pos; - size_t indent_size; - size_t trailing_ws_offset; - const TermColor **colors; -} LineInfo; - -// Like mask_color() but can change bg color only if it has not been changed yet -static void mask_color2(const ColorScheme *colors, TermColor *color, const TermColor *over) -{ - int32_t default_bg = colors->builtin[BC_DEFAULT].bg; - if (over->bg != COLOR_KEEP && (color->bg == default_bg || color->bg < 0)) { - color->bg = over->bg; - } - - if (over->fg != COLOR_KEEP) { - color->fg = over->fg; - } - - if (!(over->attr & ATTR_KEEP)) { - color->attr = over->attr; - } -} - -static void mask_selection_and_current_line ( - const ColorScheme *colors, - const LineInfo *info, - TermColor *color -) { - if (info->offset >= info->sel_so && info->offset < info->sel_eo) { - mask_color(color, &colors->builtin[BC_SELECTION]); - } else if (info->line_nr == info->view->cy) { - mask_color2(colors, color, &colors->builtin[BC_CURRENTLINE]); - } -} - -static bool is_non_text(CodePoint u, bool display_special) -{ - if (u == '\t') { - return display_special; - } - return u < 0x20 || u == 0x7F || u_is_unprintable(u); -} - -static WhitespaceErrorFlags get_ws_error(const LocalOptions *opts) -{ - WhitespaceErrorFlags taberrs = WSE_TAB_INDENT | WSE_TAB_AFTER_INDENT; - WhitespaceErrorFlags extra = opts->expand_tab ? taberrs : WSE_SPACE_INDENT; - return opts->ws_error | ((opts->ws_error & WSE_AUTO_INDENT) ? extra : 0); -} - -static bool whitespace_error(const LineInfo *info, CodePoint u, size_t i) -{ - const View *view = info->view; - WhitespaceErrorFlags flags = get_ws_error(&view->buffer->options); - WhitespaceErrorFlags trailing = flags & (WSE_TRAILING | WSE_ALL_TRAILING); - if (i >= info->trailing_ws_offset && trailing) { - // Trailing whitespace - if ( - // Cursor is not on this line - info->line_nr != view->cy - // or is positioned before any trailing whitespace - || view->cx < info->trailing_ws_offset - // or user explicitly wants trailing space under cursor highlighted - || flags & WSE_ALL_TRAILING - ) { - return true; - } - } - - bool in_indent = (i < info->indent_size); - if (u == '\t') { - WhitespaceErrorFlags mask = in_indent ? WSE_TAB_INDENT : WSE_TAB_AFTER_INDENT; - return (flags & mask) != 0; - } - if (!in_indent) { - // All checks below here only apply to indentation - return false; - } - - const char *line = info->line; - size_t pos = i; - size_t count = 0; - while (pos > 0 && line[pos - 1] == ' ') { - pos--; - } - while (pos < info->size && line[pos] == ' ') { - pos++; - count++; - } - - WhitespaceErrorFlags mask; - if (count >= view->buffer->options.tab_width) { - // Spaces used instead of tab - mask = WSE_SPACE_INDENT; - } else if (pos < info->size && line[pos] == '\t') { - // Space before tab - mask = WSE_SPACE_INDENT; - } else { - // Less than tab width spaces at end of indentation - mask = WSE_SPACE_ALIGN; - } - return (flags & mask) != 0; -} - -static CodePoint screen_next_char(EditorState *e, LineInfo *info) -{ - size_t count, pos = info->pos; - CodePoint u = info->line[pos]; - TermColor color; - bool ws_error = false; - - if (likely(u < 0x80)) { - info->pos++; - count = 1; - if (u == '\t' || u == ' ') { - ws_error = whitespace_error(info, u, pos); - } - } else { - u = u_get_nonascii(info->line, info->size, &info->pos); - count = info->pos - pos; - - if ( - u_is_special_whitespace(u) // Highly annoying no-break space etc. - && (info->view->buffer->options.ws_error & WSE_SPECIAL) - ) { - ws_error = true; - } - } - - if (info->colors && info->colors[pos]) { - color = *info->colors[pos]; - } else { - color = e->colors.builtin[BC_DEFAULT]; - } - if (is_non_text(u, e->options.display_special)) { - mask_color(&color, &e->colors.builtin[BC_NONTEXT]); - } - if (ws_error) { - mask_color(&color, &e->colors.builtin[BC_WSERROR]); - } - mask_selection_and_current_line(&e->colors, info, &color); - set_color(&e->terminal, &e->colors, &color); - - info->offset += count; - return u; -} - -static void screen_skip_char(TermOutputBuffer *obuf, LineInfo *info) -{ - CodePoint u = info->line[info->pos++]; - info->offset++; - if (likely(u < 0x80)) { - if (likely(!ascii_iscntrl(u))) { - obuf->x++; - } else if (u == '\t' && obuf->tab_mode != TAB_CONTROL) { - obuf->x = next_indent_width(obuf->x, obuf->tab_width); - } else { - // Control - obuf->x += 2; - } - } else { - size_t pos = info->pos; - info->pos--; - u = u_get_nonascii(info->line, info->size, &info->pos); - obuf->x += u_char_width(u); - info->offset += info->pos - pos; - } -} - -static bool is_notice(const char *word, size_t len) -{ - switch (len) { - case 3: return mem_equal(word, "XXX", 3); - case 4: return mem_equal(word, "TODO", 4); - case 5: return mem_equal(word, "FIXME", 5); - } - return false; -} - -// Highlight certain words inside comments -static void hl_words(Terminal *term, const ColorScheme *colors, const LineInfo *info) -{ - const TermColor *cc = find_color(colors, "comment"); - const TermColor *nc = find_color(colors, "notice"); - - if (!info->colors || !cc || !nc) { - return; - } - - size_t i = info->pos; - if (i >= info->size) { - return; - } - - // Go to beginning of partially visible word inside comment - while (i > 0 && info->colors[i] == cc && is_word_byte(info->line[i])) { - i--; - } - - // This should be more than enough. I'm too lazy to iterate characters - // instead of bytes and calculate text width. - const size_t max = info->pos + term->width * 4 + 8; - - size_t si; - while (i < info->size) { - if (info->colors[i] != cc || !is_word_byte(info->line[i])) { - if (i > max) { - break; - } - i++; - } else { - // Beginning of a word inside comment - si = i++; - while ( - i < info->size && info->colors[i] == cc - && is_word_byte(info->line[i]) - ) { - i++; - } - if (is_notice(info->line + si, i - si)) { - for (size_t j = si; j < i; j++) { - info->colors[j] = nc; - } - } - } - } -} - -static void line_info_init ( - LineInfo *info, - const View *view, - const BlockIter *bi, - size_t line_nr -) { - *info = (LineInfo) { - .view = view, - .line_nr = line_nr, - .offset = block_iter_get_offset(bi), - }; - - if (!view->selection) { - info->sel_so = -1; - info->sel_eo = -1; - } else if (view->sel_eo != SEL_EO_RECALC) { - // Already calculated - info->sel_so = view->sel_so; - info->sel_eo = view->sel_eo; - BUG_ON(info->sel_so > info->sel_eo); - } else { - SelectionInfo sel; - init_selection(view, &sel); - info->sel_so = sel.so; - info->sel_eo = sel.eo; - } -} - -static void line_info_set_line ( - LineInfo *info, - const StringView *line, - const TermColor **colors -) { - BUG_ON(line->length == 0); - BUG_ON(line->data[line->length - 1] != '\n'); - - info->line = line->data; - info->size = line->length - 1; - info->pos = 0; - info->colors = colors; - - { - size_t i, n; - for (i = 0, n = info->size; i < n; i++) { - char ch = info->line[i]; - if (ch != '\t' && ch != ' ') { - break; - } - } - info->indent_size = i; - } - - static_assert_compatible_types(info->trailing_ws_offset, size_t); - info->trailing_ws_offset = SIZE_MAX; - for (ssize_t i = info->size - 1; i >= 0; i--) { - char ch = info->line[i]; - if (ch != '\t' && ch != ' ') { - break; - } - info->trailing_ws_offset = i; - } -} - -static void print_line(EditorState *e, LineInfo *info) -{ - // Screen might be scrolled horizontally. Skip most invisible - // characters using screen_skip_char(), which is much faster than - // buf_skip(screen_next_char(info)). - // - // There can be a wide character (tab, control code etc.) that is - // partially visible and can't be skipped using screen_skip_char(). - Terminal *term = &e->terminal; - TermOutputBuffer *obuf = &term->obuf; - while (obuf->x + 8 < obuf->scroll_x && info->pos < info->size) { - screen_skip_char(obuf, info); - } - - const ColorScheme *colors = &e->colors; - hl_words(term, colors, info); - - while (info->pos < info->size) { - BUG_ON(obuf->x > obuf->scroll_x + obuf->width); - CodePoint u = screen_next_char(e, info); - if (!term_put_char(obuf, u)) { - // +1 for newline - info->offset += info->size - info->pos + 1; - return; - } - } - - TermColor color; - if (e->options.display_special && obuf->x >= obuf->scroll_x) { - // Syntax highlighter highlights \n but use default color anyway - color = colors->builtin[BC_DEFAULT]; - mask_color(&color, &colors->builtin[BC_NONTEXT]); - mask_selection_and_current_line(colors, info, &color); - set_color(term, colors, &color); - term_put_char(obuf, '$'); - } - - color = colors->builtin[BC_DEFAULT]; - mask_selection_and_current_line(colors, info, &color); - set_color(term, colors, &color); - info->offset++; - term_clear_eol(term); -} - -void update_range(EditorState *e, const View *view, long y1, long y2) -{ - const int edit_x = view->window->edit_x; - const int edit_y = view->window->edit_y; - const int edit_w = view->window->edit_w; - const int edit_h = view->window->edit_h; - - Terminal *term = &e->terminal; - TermOutputBuffer *obuf = &term->obuf; - term_output_reset(term, edit_x, edit_w, view->vx); - obuf->tab_width = view->buffer->options.tab_width; - obuf->tab_mode = e->options.display_special ? TAB_SPECIAL : TAB_NORMAL; - - BlockIter bi = view->cursor; - for (long i = 0, n = view->cy - y1; i < n; i++) { - block_iter_prev_line(&bi); - } - for (long i = 0, n = y1 - view->cy; i < n; i++) { - block_iter_eat_line(&bi); - } - block_iter_bol(&bi); - - LineInfo info; - line_info_init(&info, view, &bi, y1); - - y1 -= view->vy; - y2 -= view->vy; - - bool got_line = !block_iter_is_eof(&bi); - hl_fill_start_states(view->buffer, &e->colors, info.line_nr); - long i; - for (i = y1; got_line && i < y2; i++) { - obuf->x = 0; - term_move_cursor(obuf, edit_x, edit_y + i); - - StringView line; - fill_line_nl_ref(&bi, &line); - bool next_changed; - const TermColor **colors = hl_line(view->buffer, &e->colors, &line, info.line_nr, &next_changed); - line_info_set_line(&info, &line, colors); - print_line(e, &info); - - got_line = !!block_iter_next_line(&bi); - info.line_nr++; - - if (next_changed && i + 1 == y2 && y2 < edit_h) { - // More lines need to be updated not because their contents have - // changed but because their highlight state has - y2++; - } - } - - if (i < y2 && info.line_nr == view->cy) { - // Dummy empty line is shown only if cursor is on it - TermColor color = e->colors.builtin[BC_DEFAULT]; - - obuf->x = 0; - mask_color2(&e->colors, &color, &e->colors.builtin[BC_CURRENTLINE]); - set_color(term, &e->colors, &color); - - term_move_cursor(obuf, edit_x, edit_y + i++); - term_clear_eol(term); - } - - if (i < y2) { - set_builtin_color(term, &e->colors, BC_NOLINE); - } - for (; i < y2; i++) { - obuf->x = 0; - term_move_cursor(obuf, edit_x, edit_y + i); - term_put_char(obuf, '~'); - term_clear_eol(term); - } -} diff --git a/examples/dte/screen-window.c b/examples/dte/screen-window.c deleted file mode 100644 index aa3a96f..0000000 --- a/examples/dte/screen-window.c +++ /dev/null @@ -1,130 +0,0 @@ -#include "screen.h" - -static void print_separator(Window *window, void *ud) -{ - Terminal *term = ud; - TermOutputBuffer *obuf = &term->obuf; - if (window->x + window->w == term->width) { - return; - } - for (int y = 0, h = window->h; y < h; y++) { - term_move_cursor(obuf, window->x + window->w, window->y + y); - term_add_byte(obuf, '|'); - } -} - -static void update_separators(Terminal *term, const ColorScheme *colors, const Frame *frame) -{ - set_builtin_color(term, colors, BC_STATUSLINE); - frame_for_each_window(frame, print_separator, term); -} - -static void update_line_numbers(Terminal *term, const ColorScheme *colors, Window *window, bool force) -{ - const View *view = window->view; - size_t lines = view->buffer->nl; - int x = window->x; - - calculate_line_numbers(window); - long first = view->vy + 1; - long last = MIN(view->vy + window->edit_h, lines); - - if ( - !force - && window->line_numbers.first == first - && window->line_numbers.last == last - ) { - return; - } - - window->line_numbers.first = first; - window->line_numbers.last = last; - - TermOutputBuffer *obuf = &term->obuf; - char buf[DECIMAL_STR_MAX(unsigned long) + 1]; - size_t width = window->line_numbers.width; - BUG_ON(width > sizeof(buf)); - BUG_ON(width < LINE_NUMBERS_MIN_WIDTH); - term_output_reset(term, window->x, window->w, 0); - set_builtin_color(term, colors, BC_LINENUMBER); - - for (int y = 0, h = window->edit_h, edit_y = window->edit_y; y < h; y++) { - unsigned long line = view->vy + y + 1; - memset(buf, ' ', width); - if (line <= lines) { - size_t i = width - 2; - do { - buf[i--] = (line % 10) + '0'; - } while (line /= 10); - } - term_move_cursor(obuf, x, edit_y + y); - term_add_bytes(obuf, buf, width); - } -} - -static void update_window_full(Window *window, void* UNUSED_ARG(data)) -{ - EditorState *e = window->editor; - View *view = window->view; - view_update_cursor_x(view); - view_update_cursor_y(view); - view_update(view, e->options.scroll_margin); - if (e->options.tab_bar) { - print_tabbar(&e->terminal, &e->colors, window); - } - if (e->options.show_line_numbers) { - update_line_numbers(&e->terminal, &e->colors, window, true); - } - update_range(e, view, view->vy, view->vy + window->edit_h); - update_status_line(window); -} - -void update_all_windows(EditorState *e) -{ - update_window_sizes(&e->terminal, e->root_frame); - frame_for_each_window(e->root_frame, update_window_full, NULL); - update_separators(&e->terminal, &e->colors, e->root_frame); -} - -static void update_window(EditorState *e, Window *window) -{ - if (e->options.tab_bar && window->update_tabbar) { - print_tabbar(&e->terminal, &e->colors, window); - } - - const View *view = window->view; - if (e->options.show_line_numbers) { - // Force updating lines numbers if all lines changed - bool force = (view->buffer->changed_line_max == LONG_MAX); - update_line_numbers(&e->terminal, &e->colors, window, force); - } - - long y1 = MAX(view->buffer->changed_line_min, view->vy); - long y2 = MIN(view->buffer->changed_line_max, view->vy + window->edit_h - 1); - update_range(e, view, y1, y2 + 1); - update_status_line(window); -} - -// Update all visible views containing this buffer -void update_buffer_windows(EditorState *e, const Buffer *buffer) -{ - const View *current_view = e->view; - for (size_t i = 0, n = buffer->views.count; i < n; i++) { - View *view = buffer->views.ptrs[i]; - if (view != view->window->view) { - // Not visible - continue; - } - if (view != current_view) { - // Restore cursor - view->cursor.blk = BLOCK(view->buffer->blocks.next); - block_iter_goto_offset(&view->cursor, view->saved_cursor_offset); - - // These have already been updated for current view - view_update_cursor_x(view); - view_update_cursor_y(view); - view_update(view, e->options.scroll_margin); - } - update_window(e, view->window); - } -} diff --git a/examples/dte/screen.c b/examples/dte/screen.c deleted file mode 100644 index 22bbf69..0000000 --- a/examples/dte/screen.c +++ /dev/null @@ -1,211 +0,0 @@ -#include <string.h> -#include "screen.h" -#include "frame.h" -#include "terminal/cursor.h" -#include "terminal/ioctl.h" -#include "util/log.h" - -void set_color(Terminal *term, const ColorScheme *colors, const TermColor *color) -{ - TermColor tmp = *color; - // NOTE: -2 (keep) is treated as -1 (default) - if (tmp.fg < 0) { - tmp.fg = colors->builtin[BC_DEFAULT].fg; - } - if (tmp.bg < 0) { - tmp.bg = colors->builtin[BC_DEFAULT].bg; - } - if (same_color(&tmp, &term->obuf.color)) { - return; - } - term_set_color(term, &tmp); -} - -void set_builtin_color(Terminal *term, const ColorScheme *colors, BuiltinColorEnum c) -{ - set_color(term, colors, &colors->builtin[c]); -} - -static void update_cursor_style(EditorState *e) -{ - CursorInputMode mode; - switch (e->input_mode) { - case INPUT_NORMAL: - if (e->buffer->options.overwrite) { - mode = CURSOR_MODE_OVERWRITE; - } else { - mode = CURSOR_MODE_INSERT; - } - break; - case INPUT_COMMAND: - case INPUT_SEARCH: - mode = CURSOR_MODE_CMDLINE; - break; - default: - BUG("unhandled input mode"); - return; - } - - TermCursorStyle style = e->cursor_styles[mode]; - TermCursorStyle def = e->cursor_styles[CURSOR_MODE_DEFAULT]; - if (style.type == CURSOR_KEEP) { - style.type = def.type; - } - if (style.color == COLOR_KEEP) { - style.color = def.color; - } - - e->cursor_style_changed = false; - if (!same_cursor(&style, &e->terminal.obuf.cursor_style)) { - term_set_cursor_style(&e->terminal, style); - } -} - -void update_term_title(Terminal *term, const Buffer *buffer, bool set_window_title) -{ - if (!set_window_title || !(term->features & TFLAG_SET_WINDOW_TITLE)) { - return; - } - - // FIXME: title must not contain control characters - TermOutputBuffer *obuf = &term->obuf; - const char *filename = buffer_filename(buffer); - term_add_literal(obuf, "\033]2;"); - term_add_bytes(obuf, filename, strlen(filename)); - term_add_byte(obuf, ' '); - term_add_byte(obuf, buffer_modified(buffer) ? '+' : '-'); - term_add_literal(obuf, " dte\033\\"); -} - -void mask_color(TermColor *color, const TermColor *over) -{ - if (over->fg != COLOR_KEEP) { - color->fg = over->fg; - } - if (over->bg != COLOR_KEEP) { - color->bg = over->bg; - } - if (!(over->attr & ATTR_KEEP)) { - color->attr = over->attr; - } -} - -void restore_cursor(EditorState *e) -{ - unsigned int x, y; - switch (e->input_mode) { - case INPUT_NORMAL: - x = e->window->edit_x + e->view->cx_display - e->view->vx; - y = e->window->edit_y + e->view->cy - e->view->vy; - break; - case INPUT_COMMAND: - case INPUT_SEARCH: - x = e->cmdline_x; - y = e->terminal.height - 1; - break; - default: - BUG("unhandled input mode"); - } - term_move_cursor(&e->terminal.obuf, x, y); -} - -static void clear_update_tabbar(Window *window, void* UNUSED_ARG(data)) -{ - window->update_tabbar = false; -} - -void end_update(EditorState *e) -{ - Terminal *term = &e->terminal; - restore_cursor(e); - term_show_cursor(term); - term_end_sync_update(term); - term_output_flush(&term->obuf); - - e->buffer->changed_line_min = LONG_MAX; - e->buffer->changed_line_max = -1; - frame_for_each_window(e->root_frame, clear_update_tabbar, NULL); -} - -void start_update(Terminal *term) -{ - term_begin_sync_update(term); - term_hide_cursor(term); -} - -void update_window_sizes(Terminal *term, Frame *frame) -{ - set_frame_size(frame, term->width, term->height - 1); - update_window_coordinates(frame); -} - -void update_screen_size(Terminal *term, Frame *root_frame) -{ - unsigned int width, height; - if (!term_get_size(&width, &height)) { - return; - } - - // TODO: remove minimum width/height and instead make update_screen() - // do something sensible when the terminal dimensions are tiny - term->width = MAX(width, 3); - term->height = MAX(height, 3); - - update_window_sizes(term, root_frame); - LOG_INFO("terminal size: %ux%u", width, height); -} - -NOINLINE -void normal_update(EditorState *e) -{ - Terminal *term = &e->terminal; - start_update(term); - update_term_title(term, e->buffer, e->options.set_window_title); - update_all_windows(e); - update_command_line(e); - update_cursor_style(e); - end_update(e); -} - -void update_screen(EditorState *e, const ScreenState *s) -{ - if (e->everything_changed) { - e->everything_changed = false; - normal_update(e); - return; - } - - Buffer *buffer = e->buffer; - View *view = e->view; - view_update_cursor_x(view); - view_update_cursor_y(view); - view_update(view, e->options.scroll_margin); - - if (s->id == buffer->id) { - if (s->vx != view->vx || s->vy != view->vy) { - mark_all_lines_changed(buffer); - } else { - // Because of trailing whitespace highlighting and highlighting - // current line in different color, the lines cy (old cursor y) and - // view->cy need to be updated. Always update at least current line. - buffer_mark_lines_changed(buffer, s->cy, view->cy); - } - if (s->is_modified != buffer_modified(buffer)) { - mark_buffer_tabbars_changed(buffer); - } - } else { - e->window->update_tabbar = true; - mark_all_lines_changed(buffer); - } - - start_update(&e->terminal); - if (e->window->update_tabbar) { - update_term_title(&e->terminal, e->buffer, e->options.set_window_title); - } - update_buffer_windows(e, buffer); - update_command_line(e); - if (e->cursor_style_changed) { - update_cursor_style(e); - } - end_update(e); -} diff --git a/examples/dte/screen.h b/examples/dte/screen.h deleted file mode 100644 index a3041c0..0000000 --- a/examples/dte/screen.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef SCREEN_H -#define SCREEN_H - -#include <stdbool.h> -#include <stddef.h> -#include "buffer.h" -#include "editor.h" -#include "syntax/color.h" -#include "terminal/output.h" -#include "terminal/terminal.h" -#include "util/debug.h" -#include "util/macros.h" -#include "util/utf8.h" -#include "view.h" -#include "window.h" - -typedef struct { - bool is_modified; - unsigned long id; - long cy; - long vx; - long vy; -} ScreenState; - -// screen.c -void update_screen(EditorState *e, const ScreenState *s); -void update_term_title(Terminal *term, const Buffer *buffer, bool set_window_title); -void update_window_sizes(Terminal *term, Frame *frame); -void update_screen_size(Terminal *term, Frame *root_frame); -void set_color(Terminal *term, const ColorScheme *colors, const TermColor *color); -void set_builtin_color(Terminal *term, const ColorScheme *colors, BuiltinColorEnum c); -void mask_color(TermColor *color, const TermColor *over); -void start_update(Terminal *term); -void end_update(EditorState *e); -void normal_update(EditorState *e); -void restore_cursor(EditorState *e); - -// screen-cmdline.c -void update_command_line(EditorState *e); -void show_message(Terminal *term, const ColorScheme *colors, const char *msg, bool is_error); - -// screen-tabbar.c -void print_tabbar(Terminal *term, const ColorScheme *colors, Window *window); - -// screen-status.c -void update_status_line(const Window *window); - -// screen-view.c -void update_range(EditorState *e, const View *view, long y1, long y2); - -// screen-window.c -void update_all_windows(EditorState *e); -void update_buffer_windows(EditorState *e, const Buffer *buffer); - -// screen-prompt.c -char status_prompt(EditorState *e, const char *question, const char *choices) NONNULL_ARGS; -char dialog_prompt(EditorState *e, const char *question, const char *choices) NONNULL_ARGS; - -#endif diff --git a/examples/dte/search.c b/examples/dte/search.c deleted file mode 100644 index 80ad5c3..0000000 --- a/examples/dte/search.c +++ /dev/null @@ -1,244 +0,0 @@ -#include <stdlib.h> -#include "search.h" -#include "block-iter.h" -#include "buffer.h" -#include "error.h" -#include "regexp.h" -#include "util/ascii.h" -#include "util/xmalloc.h" - -static bool do_search_fwd(View *view, regex_t *regex, BlockIter *bi, bool skip) -{ - int flags = block_iter_is_bol(bi) ? 0 : REG_NOTBOL; - - do { - if (block_iter_is_eof(bi)) { - return false; - } - - regmatch_t match; - StringView line; - fill_line_ref(bi, &line); - - // NOTE: If this is the first iteration then line.data contains - // partial line (text starting from the cursor position) and - // if match.rm_so is 0 then match is at beginning of the text - // which is same as the cursor position. - if (regexp_exec(regex, line.data, line.length, 1, &match, flags)) { - if (skip && match.rm_so == 0) { - // Ignore match at current cursor position - regoff_t count = match.rm_eo; - if (count == 0) { - // It is safe to skip one byte because every line - // has one extra byte (newline) that is not in line.data - count = 1; - } - block_iter_skip_bytes(bi, (size_t)count); - return do_search_fwd(view, regex, bi, false); - } - - block_iter_skip_bytes(bi, match.rm_so); - view->cursor = *bi; - view->center_on_scroll = true; - view_reset_preferred_x(view); - return true; - } - - skip = false; // Not at cursor position any more - flags = 0; - } while (block_iter_next_line(bi)); - - return false; -} - -static bool do_search_bwd(View *view, regex_t *regex, BlockIter *bi, ssize_t cx, bool skip) -{ - if (block_iter_is_eof(bi)) { - goto next; - } - - do { - regmatch_t match; - StringView line; - int flags = 0; - regoff_t offset = -1; - regoff_t pos = 0; - - fill_line_ref(bi, &line); - while ( - pos <= line.length - && regexp_exec(regex, line.data + pos, line.length - pos, 1, &match, flags) - ) { - flags = REG_NOTBOL; - if (cx >= 0) { - if (pos + match.rm_so >= cx) { - // Ignore match at or after cursor - break; - } - if (skip && pos + match.rm_eo > cx) { - // Search -rw should not find word under cursor - break; - } - } - - // This might be what we want (last match before cursor) - offset = pos + match.rm_so; - pos += match.rm_eo; - - if (match.rm_so == match.rm_eo) { - // Zero length match - break; - } - } - - if (offset >= 0) { - block_iter_skip_bytes(bi, offset); - view->cursor = *bi; - view->center_on_scroll = true; - view_reset_preferred_x(view); - return true; - } - - next: - cx = -1; - } while (block_iter_prev_line(bi)); - - return false; -} - -bool search_tag(View *view, const char *pattern) -{ - regex_t regex; - if (!regexp_compile_basic(®ex, pattern, REG_NEWLINE)) { - return false; - } - - BlockIter bi = block_iter(view->buffer); - bool found = do_search_fwd(view, ®ex, &bi, false); - regfree(®ex); - - if (!found) { - // Don't center view to cursor unnecessarily - view->force_center = false; - return error_msg("Tag not found"); - } - - view->center_on_scroll = true; - return true; -} - -static void free_regex(SearchState *search) -{ - if (search->re_flags) { - regfree(&search->regex); - search->re_flags = 0; - } -} - -static bool has_upper(const char *str) -{ - for (size_t i = 0; str[i]; i++) { - if (ascii_isupper(str[i])) { - return true; - } - } - return false; -} - -static bool update_regex(SearchState *search, SearchCaseSensitivity cs) -{ - int re_flags = REG_NEWLINE; - switch (cs) { - case CSS_TRUE: - break; - case CSS_FALSE: - re_flags |= REG_ICASE; - break; - case CSS_AUTO: - if (!has_upper(search->pattern)) { - re_flags |= REG_ICASE; - } - break; - default: - BUG("unhandled case sensitivity value"); - } - - if (re_flags == search->re_flags) { - return true; - } - - free_regex(search); - - search->re_flags = re_flags; - if (regexp_compile(&search->regex, search->pattern, search->re_flags)) { - return true; - } - - free_regex(search); - return false; -} - -void search_free_regexp(SearchState *search) -{ - free_regex(search); - free(search->pattern); -} - -void search_set_regexp(SearchState *search, const char *pattern) -{ - search_free_regexp(search); - search->pattern = xstrdup(pattern); -} - -static bool do_search_next(View *view, SearchState *search, SearchCaseSensitivity cs, bool skip) -{ - if (!search->pattern) { - return error_msg("No previous search pattern"); - } - if (!update_regex(search, cs)) { - return false; - } - - BlockIter bi = view->cursor; - regex_t *regex = &search->regex; - if (!search->reverse) { - if (do_search_fwd(view, regex, &bi, true)) { - return true; - } - block_iter_bof(&bi); - if (do_search_fwd(view, regex, &bi, false)) { - info_msg("Continuing at top"); - return true; - } - } else { - size_t cursor_x = block_iter_bol(&bi); - if (do_search_bwd(view, regex, &bi, cursor_x, skip)) { - return true; - } - block_iter_eof(&bi); - if (do_search_bwd(view, regex, &bi, -1, false)) { - info_msg("Continuing at bottom"); - return true; - } - } - - return error_msg("Pattern '%s' not found", search->pattern); -} - -bool search_prev(View *view, SearchState *search, SearchCaseSensitivity cs) -{ - toggle_search_direction(search); - bool r = search_next(view, search, cs); - toggle_search_direction(search); - return r; -} - -bool search_next(View *view, SearchState *search, SearchCaseSensitivity cs) -{ - return do_search_next(view, search, cs, false); -} - -bool search_next_word(View *view, SearchState *search, SearchCaseSensitivity cs) -{ - return do_search_next(view, search, cs, true); -} diff --git a/examples/dte/search.h b/examples/dte/search.h deleted file mode 100644 index 94d3a57..0000000 --- a/examples/dte/search.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef SEARCH_H -#define SEARCH_H - -#include <regex.h> -#include <stdbool.h> -#include "util/macros.h" -#include "view.h" - -typedef enum { - CSS_FALSE, - CSS_TRUE, - CSS_AUTO, -} SearchCaseSensitivity; - -typedef struct { - regex_t regex; - char *pattern; - int re_flags; // If zero, regex hasn't been compiled - bool reverse; -} SearchState; - -static inline void toggle_search_direction(SearchState *search) -{ - search->reverse ^= 1; -} - -bool search_tag(View *view, const char *pattern) NONNULL_ARGS WARN_UNUSED_RESULT; -void search_set_regexp(SearchState *search, const char *pattern) NONNULL_ARGS; -void search_free_regexp(SearchState *search) NONNULL_ARGS; -bool search_prev(View *view, SearchState *search, SearchCaseSensitivity cs) NONNULL_ARGS WARN_UNUSED_RESULT; -bool search_next(View *view, SearchState *search, SearchCaseSensitivity cs) NONNULL_ARGS WARN_UNUSED_RESULT; -bool search_next_word(View *view, SearchState *search, SearchCaseSensitivity cs) NONNULL_ARGS WARN_UNUSED_RESULT; - -#endif diff --git a/examples/dte/selection.c b/examples/dte/selection.c deleted file mode 100644 index 5ddc67c..0000000 --- a/examples/dte/selection.c +++ /dev/null @@ -1,110 +0,0 @@ -#include "selection.h" -#include "editor.h" -#include "util/unicode.h" - -static bool include_cursor_char_in_selection(const View *view) -{ - const EditorState *e = view->window->editor; - if (!e->options.select_cursor_char) { - return false; - } - - bool overwrite = view->buffer->options.overwrite; - CursorInputMode mode = overwrite ? CURSOR_MODE_OVERWRITE : CURSOR_MODE_INSERT; - TermCursorType type = e->cursor_styles[mode].type; - if (type == CURSOR_KEEP) { - type = e->cursor_styles[CURSOR_MODE_DEFAULT].type; - } - - // If "select-cursor-char" option is true, include character under cursor - // in selections for any cursor type except bars (where it makes no sense - // to do so) - return !(type == CURSOR_STEADY_BAR || type == CURSOR_BLINKING_BAR); -} - -void init_selection(const View *view, SelectionInfo *info) -{ - info->so = view->sel_so; - info->eo = block_iter_get_offset(&view->cursor); - info->si = view->cursor; - block_iter_goto_offset(&info->si, info->so); - info->swapped = false; - if (info->so > info->eo) { - size_t o = info->so; - info->so = info->eo; - info->eo = o; - info->si = view->cursor; - info->swapped = true; - } - - BlockIter ei = info->si; - block_iter_skip_bytes(&ei, info->eo - info->so); - if (block_iter_is_eof(&ei)) { - if (info->so == info->eo) { - return; - } - CodePoint u; - info->eo -= block_iter_prev_char(&ei, &u); - } - - if (view->selection == SELECT_LINES) { - info->so -= block_iter_bol(&info->si); - info->eo += block_iter_eat_line(&ei); - } else { - if (include_cursor_char_in_selection(view)) { - info->eo += block_iter_next_column(&ei); - } - } -} - -size_t prepare_selection(View *view) -{ - SelectionInfo info; - init_selection(view, &info); - view->cursor = info.si; - return info.eo - info.so; -} - -char *view_get_selection(View *view, size_t *size) -{ - if (view->selection == SELECT_NONE) { - *size = 0; - return NULL; - } - - BlockIter save = view->cursor; - *size = prepare_selection(view); - char *buf = block_iter_get_bytes(&view->cursor, *size); - view->cursor = save; - return buf; -} - -size_t get_nr_selected_lines(const SelectionInfo *info) -{ - BlockIter bi = info->si; - size_t pos = info->so; - CodePoint u = 0; - size_t nr_lines = 1; - - while (pos < info->eo) { - if (u == '\n') { - nr_lines++; - } - pos += block_iter_next_char(&bi, &u); - } - return nr_lines; -} - -size_t get_nr_selected_chars(const SelectionInfo *info) -{ - BlockIter bi = info->si; - size_t pos = info->so; - CodePoint u; - size_t nr_chars = 0; - - while (pos < info->eo) { - nr_chars++; - pos += block_iter_next_char(&bi, &u); - } - return nr_chars; -} diff --git a/examples/dte/selection.h b/examples/dte/selection.h deleted file mode 100644 index ddd60c5..0000000 --- a/examples/dte/selection.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef SELECTION_H -#define SELECTION_H - -#include <stdbool.h> -#include <stddef.h> -#include "block-iter.h" -#include "view.h" - -typedef struct { - BlockIter si; - size_t so; - size_t eo; - bool swapped; -} SelectionInfo; - -void init_selection(const View *view, SelectionInfo *info); -size_t prepare_selection(View *view); -char *view_get_selection(View *view, size_t *size); -size_t get_nr_selected_lines(const SelectionInfo *info); -size_t get_nr_selected_chars(const SelectionInfo *info); - -#endif diff --git a/examples/dte/shift.c b/examples/dte/shift.c deleted file mode 100644 index 6276d3c..0000000 --- a/examples/dte/shift.c +++ /dev/null @@ -1,147 +0,0 @@ -#include <stddef.h> -#include <stdlib.h> -#include <string.h> -#include "shift.h" -#include "block-iter.h" -#include "buffer.h" -#include "change.h" -#include "indent.h" -#include "move.h" -#include "options.h" -#include "selection.h" -#include "util/debug.h" -#include "util/macros.h" -#include "util/xmalloc.h" - -static char *alloc_indent(const LocalOptions *options, size_t count, size_t *sizep) -{ - bool use_spaces = use_spaces_for_indent(options); - size_t size = use_spaces ? count * options->indent_width : count; - *sizep = size; - return memset(xmalloc(size), use_spaces ? ' ' : '\t', size); -} - -static void shift_right(View *view, size_t nr_lines, size_t count) -{ - const LocalOptions *options = &view->buffer->options; - size_t indent_size; - char *indent = alloc_indent(options, count, &indent_size); - - for (size_t i = 0; true; ) { - StringView line; - fetch_this_line(&view->cursor, &line); - IndentInfo info = get_indent_info(options, &line); - if (info.wsonly) { - if (info.bytes) { - // Remove indentation - buffer_delete_bytes(view, info.bytes); - } - } else if (info.sane) { - // Insert whitespace - buffer_insert_bytes(view, indent, indent_size); - } else { - // Replace whole indentation with sane one - size_t size; - char *buf = alloc_indent(options, info.level + count, &size); - buffer_replace_bytes(view, info.bytes, buf, size); - free(buf); - } - if (++i == nr_lines) { - break; - } - block_iter_eat_line(&view->cursor); - } - - free(indent); -} - -static void shift_left(View *view, size_t nr_lines, size_t count) -{ - const LocalOptions *options = &view->buffer->options; - const size_t indent_width = options->indent_width; - const bool space_indent = use_spaces_for_indent(options); - - for (size_t i = 0; true; ) { - StringView line; - fetch_this_line(&view->cursor, &line); - IndentInfo info = get_indent_info(options, &line); - if (info.wsonly) { - if (info.bytes) { - // Remove indentation - buffer_delete_bytes(view, info.bytes); - } - } else if (info.level && info.sane) { - size_t n = MIN(count, info.level); - if (space_indent) { - n *= indent_width; - } - buffer_delete_bytes(view, n); - } else if (info.bytes) { - // Replace whole indentation with sane one - if (info.level > count) { - size_t size; - char *buf = alloc_indent(options, info.level - count, &size); - buffer_replace_bytes(view, info.bytes, buf, size); - free(buf); - } else { - buffer_delete_bytes(view, info.bytes); - } - } - if (++i == nr_lines) { - break; - } - block_iter_eat_line(&view->cursor); - } -} - -static void do_shift_lines(View *view, int count, size_t nr_lines) -{ - begin_change_chain(); - block_iter_bol(&view->cursor); - if (count > 0) { - shift_right(view, nr_lines, count); - } else { - shift_left(view, nr_lines, -count); - } - end_change_chain(view); -} - -void shift_lines(View *view, int count) -{ - unsigned int width = view->buffer->options.indent_width; - BUG_ON(width > INDENT_WIDTH_MAX); - BUG_ON(count == 0); - - long x = view_get_preferred_x(view) + (count * width); - x = MAX(x, 0); - - if (view->selection == SELECT_NONE) { - do_shift_lines(view, count, 1); - goto out; - } - - SelectionInfo info; - view->selection = SELECT_LINES; - init_selection(view, &info); - view->cursor = info.si; - size_t nr_lines = get_nr_selected_lines(&info); - do_shift_lines(view, count, nr_lines); - if (info.swapped) { - // Cursor should be at beginning of selection - block_iter_bol(&view->cursor); - view->sel_so = block_iter_get_offset(&view->cursor); - while (--nr_lines) { - block_iter_prev_line(&view->cursor); - } - } else { - BlockIter save = view->cursor; - while (--nr_lines) { - block_iter_prev_line(&view->cursor); - } - view->sel_so = block_iter_get_offset(&view->cursor); - view->cursor = save; - } - -out: - move_to_preferred_x(view, x); -} diff --git a/examples/dte/shift.h b/examples/dte/shift.h deleted file mode 100644 index 92da552..0000000 --- a/examples/dte/shift.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef SHIFT_H -#define SHIFT_H - -#include "view.h" - -void shift_lines(View *view, int count); - -#endif diff --git a/examples/dte/show.c b/examples/dte/show.c deleted file mode 100644 index 69a6acf..0000000 --- a/examples/dte/show.c +++ /dev/null @@ -1,558 +0,0 @@ -#include <stdint.h> -#include <stdlib.h> -#include <string.h> -#include "show.h" -#include "bind.h" -#include "buffer.h" -#include "change.h" -#include "cmdline.h" -#include "command/alias.h" -#include "command/macro.h" -#include "command/serialize.h" -#include "commands.h" -#include "compiler.h" -#include "completion.h" -#include "config.h" -#include "edit.h" -#include "encoding.h" -#include "error.h" -#include "file-option.h" -#include "filetype.h" -#include "frame.h" -#include "msg.h" -#include "options.h" -#include "syntax/color.h" -#include "terminal/cursor.h" -#include "terminal/key.h" -#include "terminal/style.h" -#include "util/array.h" -#include "util/bsearch.h" -#include "util/debug.h" -#include "util/intern.h" -#include "util/str-util.h" -#include "util/unicode.h" -#include "util/xmalloc.h" -#include "util/xsnprintf.h" -#include "view.h" -#include "window.h" - -extern char **environ; - -typedef enum { - DTERC = 0x1, // Use "dte" filetype (and syntax highlighter) - LASTLINE = 0x2, // Move cursor to last line (e.g. most recent history entry) - MSGLINE = 0x4, // Move cursor to line containing current message -} ShowHandlerFlags; - -typedef struct { - const char name[11]; - uint8_t flags; // ShowHandlerFlags - String (*dump)(EditorState *e); - bool (*show)(EditorState *e, const char *name, bool cmdline); - void (*complete_arg)(EditorState *e, PointerArray *a, const char *prefix); -} ShowHandler; - -static void open_temporary_buffer ( - EditorState *e, - const char *text, - size_t text_len, - const char *cmd, - const char *cmd_arg, - ShowHandlerFlags flags -) { - View *view = window_open_new_file(e->window); - Buffer *buffer = view->buffer; - buffer->temporary = true; - do_insert(view, text, text_len); - set_display_filename(buffer, xasprintf("(%s %s)", cmd, cmd_arg)); - buffer_set_encoding(buffer, encoding_from_type(UTF8), e->options.utf8_bom); - - if (flags & LASTLINE) { - block_iter_eof(&view->cursor); - block_iter_prev_line(&view->cursor); - } else if ((flags & MSGLINE) && e->messages.array.count > 0) { - block_iter_goto_line(&view->cursor, e->messages.pos); - } - - if (flags & DTERC) { - buffer->options.filetype = str_intern("dte"); - set_file_options(e, buffer); - buffer_update_syntax(e, buffer); - } -} - -static bool show_normal_alias(EditorState *e, const char *alias_name, bool cflag) -{ - const char *cmd_str = find_alias(&e->aliases, alias_name); - if (!cmd_str) { - if (find_normal_command(alias_name)) { - info_msg("%s is a built-in command, not an alias", alias_name); - } else { - info_msg("%s is not a known alias", alias_name); - } - return true; - } - - if (cflag) { - set_input_mode(e, INPUT_COMMAND); - cmdline_set_text(&e->cmdline, cmd_str); - } else { - info_msg("%s is aliased to: %s", alias_name, cmd_str); - } - - return true; -} - -static bool show_binding(EditorState *e, const char *keystr, bool cflag) -{ - KeyCode key; - if (!parse_key_string(&key, keystr)) { - return error_msg("invalid key string: %s", keystr); - } - - // Use canonical key string in printed messages - char buf[KEYCODE_STR_MAX]; - size_t len = keycode_to_string(key, buf); - BUG_ON(len == 0); - keystr = buf; - - if (u_is_unicode(key)) { - return error_msg("%s is not a bindable key", keystr); - } - - const CachedCommand *b = lookup_binding(&e->modes[INPUT_NORMAL].key_bindings, key); - if (!b) { - info_msg("%s is not bound to a command", keystr); - return true; - } - - if (cflag) { - set_input_mode(e, INPUT_COMMAND); - cmdline_set_text(&e->cmdline, b->cmd_str); - } else { - info_msg("%s is bound to: %s", keystr, b->cmd_str); - } - - return true; -} - -static bool show_color(EditorState *e, const char *color_name, bool cflag) -{ - const TermColor *hl = find_color(&e->colors, color_name); - if (!hl) { - info_msg("no color entry with name '%s'", color_name); - return true; - } - - if (cflag) { - CommandLine *c = &e->cmdline; - set_input_mode(e, INPUT_COMMAND); - cmdline_clear(c); - string_append_hl_color(&c->buf, color_name, hl); - c->pos = c->buf.len; - } else { - const char *color_str = term_color_to_string(hl); - info_msg("color '%s' is set to: %s", color_name, color_str); - } - - return true; -} - -static bool show_cursor(EditorState *e, const char *mode_str, bool cflag) -{ - CursorInputMode mode = cursor_mode_from_str(mode_str); - if (mode >= NR_CURSOR_MODES) { - return error_msg("no cursor entry for '%s'", mode_str); - } - - TermCursorStyle style = e->cursor_styles[mode]; - const char *type = cursor_type_to_str(style.type); - const char *color = cursor_color_to_str(style.color); - if (cflag) { - char buf[64]; - xsnprintf(buf, sizeof buf, "cursor %s %s %s", mode_str, type, color); - set_input_mode(e, INPUT_COMMAND); - cmdline_set_text(&e->cmdline, buf); - } else { - info_msg("cursor '%s' is set to: %s %s", mode_str, type, color); - } - - return true; -} - -static bool show_env(EditorState *e, const char *name, bool cflag) -{ - const char *value = getenv(name); - if (!value) { - info_msg("no environment variable with name '%s'", name); - return true; - } - - if (cflag) { - set_input_mode(e, INPUT_COMMAND); - cmdline_set_text(&e->cmdline, value); - } else { - info_msg("$%s is set to: %s", name, value); - } - - return true; -} - -static String dump_env(EditorState* UNUSED_ARG(e)) -{ - String buf = string_new(4096); - for (size_t i = 0; environ[i]; i++) { - string_append_cstring(&buf, environ[i]); - string_append_byte(&buf, '\n'); - } - return buf; -} - -static String dump_setenv(EditorState* UNUSED_ARG(e)) -{ - String buf = string_new(4096); - for (size_t i = 0; environ[i]; i++) { - const char *str = environ[i]; - const char *delim = strchr(str, '='); - if (unlikely(!delim || delim == str)) { - continue; - } - string_append_literal(&buf, "setenv "); - if (unlikely(str[0] == '-' || delim[1] == '-')) { - string_append_literal(&buf, "-- "); - } - const StringView name = string_view(str, delim - str); - string_append_escaped_arg_sv(&buf, name, true); - string_append_byte(&buf, ' '); - string_append_escaped_arg(&buf, delim + 1, true); - string_append_byte(&buf, '\n'); - } - return buf; -} - -static bool show_builtin(EditorState *e, const char *name, bool cflag) -{ - const BuiltinConfig *cfg = get_builtin_config(name); - if (!cfg) { - return error_msg("no built-in config with name '%s'", name); - } - - const StringView sv = cfg->text; - if (cflag) { - buffer_insert_bytes(e->view, sv.data, sv.length); - } else { - open_temporary_buffer(e, sv.data, sv.length, "builtin", name, DTERC); - } - - return true; -} - -static bool show_compiler(EditorState *e, const char *name, bool cflag) -{ - const Compiler *compiler = find_compiler(&e->compilers, name); - if (!compiler) { - info_msg("no errorfmt entry found for '%s'", name); - return true; - } - - String str = string_new(512); - dump_compiler(compiler, name, &str); - if (cflag) { - buffer_insert_bytes(e->view, str.buffer, str.len); - } else { - open_temporary_buffer(e, str.buffer, str.len, "errorfmt", name, DTERC); - } - - string_free(&str); - return true; -} - -static bool show_option(EditorState *e, const char *name, bool cflag) -{ - const char *value = get_option_value_string(e, name); - if (!value) { - return error_msg("invalid option name: %s", name); - } - - if (cflag) { - set_input_mode(e, INPUT_COMMAND); - cmdline_set_text(&e->cmdline, value); - } else { - info_msg("%s is set to: %s", name, value); - } - - return true; -} - -static void collect_all_options(EditorState* UNUSED_ARG(e), PointerArray *a, const char *prefix) -{ - collect_options(a, prefix, false, false); -} - -static void do_collect_cursor_modes(EditorState* UNUSED_ARG(e), PointerArray *a, const char *prefix) -{ - collect_cursor_modes(a, prefix); -} - -static void do_collect_builtin_configs(EditorState* UNUSED_ARG(e), PointerArray *a, const char *prefix) -{ - collect_builtin_configs(a, prefix); -} - -static void do_collect_builtin_includes(EditorState* UNUSED_ARG(e), PointerArray *a, const char *prefix) -{ - collect_builtin_includes(a, prefix); -} - -static bool show_wsplit(EditorState *e, const char *name, bool cflag) -{ - if (!streq(name, "this")) { - return error_msg("invalid window: %s", name); - } - - const Window *w = e->window; - char buf[(4 * DECIMAL_STR_MAX(w->x)) + 4]; - xsnprintf(buf, sizeof buf, "%d,%d %dx%d", w->x, w->y, w->w, w->h); - - if (cflag) { - set_input_mode(e, INPUT_COMMAND); - cmdline_set_text(&e->cmdline, buf); - } else { - info_msg("current window dimensions: %s", buf); - } - - return true; -} - -static String do_history_dump(const History *history) -{ - const size_t nr_entries = history->entries.count; - const size_t size = round_size_to_next_multiple(16 * nr_entries, 4096); - String buf = string_new(size); - size_t n = 0; - for (HistoryEntry *e = history->first; e; e = e->next, n++) { - string_append_cstring(&buf, e->text); - string_append_byte(&buf, '\n'); - } - BUG_ON(n != nr_entries); - return buf; -} - -String dump_command_history(EditorState *e) -{ - return do_history_dump(&e->command_history); -} - -String dump_search_history(EditorState *e) -{ - return do_history_dump(&e->search_history); -} - -typedef struct { - const char *name; - const char *value; -} CommandAlias; - -static int alias_cmp(const void *ap, const void *bp) -{ - const CommandAlias *a = ap; - const CommandAlias *b = bp; - return strcmp(a->name, b->name); -} - -String dump_normal_aliases(EditorState *e) -{ - const size_t count = e->aliases.count; - if (unlikely(count == 0)) { - return string_new(0); - } - - // Clone the contents of the HashMap as an array of name/value pairs - CommandAlias *array = xnew(CommandAlias, count); - size_t n = 0; - for (HashMapIter it = hashmap_iter(&e->aliases); hashmap_next(&it); ) { - array[n++] = (CommandAlias) { - .name = it.entry->key, - .value = it.entry->value, - }; - } - - // Sort the array - BUG_ON(n != count); - qsort(array, count, sizeof(array[0]), alias_cmp); - - // Serialize the aliases in sorted order - String buf = string_new(4096); - for (size_t i = 0; i < count; i++) { - const char *name = array[i].name; - string_append_literal(&buf, "alias "); - if (unlikely(name[0] == '-')) { - string_append_literal(&buf, "-- "); - } - string_append_escaped_arg(&buf, name, true); - string_append_byte(&buf, ' '); - string_append_escaped_arg(&buf, array[i].value, true); - string_append_byte(&buf, '\n'); - } - - free(array); - return buf; -} - -String dump_all_bindings(EditorState *e) -{ - static const char flags[][4] = { - [INPUT_NORMAL] = "", - [INPUT_COMMAND] = "-c ", - [INPUT_SEARCH] = "-s ", - }; - - static_assert(ARRAYLEN(flags) == ARRAYLEN(e->modes)); - String buf = string_new(4096); - for (InputMode i = 0, n = ARRAYLEN(e->modes); i < n; i++) { - const IntMap *bindings = &e->modes[i].key_bindings; - if (dump_bindings(bindings, flags[i], &buf) && i != n - 1) { - string_append_byte(&buf, '\n'); - } - } - return buf; -} - -String dump_frames(EditorState *e) -{ - String str = string_new(4096); - dump_frame(e->root_frame, 0, &str); - return str; -} - -String dump_compilers(EditorState *e) -{ - String buf = string_new(4096); - for (HashMapIter it = hashmap_iter(&e->compilers); hashmap_next(&it); ) { - const char *name = it.entry->key; - const Compiler *c = it.entry->value; - dump_compiler(c, name, &buf); - string_append_byte(&buf, '\n'); - } - return buf; -} - -String dump_cursors(EditorState *e) -{ - String buf = string_new(128); - for (CursorInputMode m = 0; m < ARRAYLEN(e->cursor_styles); m++) { - const TermCursorStyle *style = &e->cursor_styles[m]; - string_append_literal(&buf, "cursor "); - string_append_cstring(&buf, cursor_mode_to_str(m)); - string_append_byte(&buf, ' '); - string_append_cstring(&buf, cursor_type_to_str(style->type)); - string_append_byte(&buf, ' '); - string_append_cstring(&buf, cursor_color_to_str(style->color)); - string_append_byte(&buf, '\n'); - } - return buf; -} - -// Dump option values only -String do_dump_options(EditorState *e) -{ - return dump_options(&e->options, &e->buffer->options); -} - -// Dump option values and FileOption entries -String dump_options_and_fileopts(EditorState *e) -{ - String str = do_dump_options(e); - string_append_literal(&str, "\n\n"); - dump_file_options(&e->file_options, &str); - return str; -} - -String do_dump_builtin_configs(EditorState* UNUSED_ARG(e)) -{ - return dump_builtin_configs(); -} - -String do_dump_hl_colors(EditorState *e) -{ - return dump_hl_colors(&e->colors); -} - -String do_dump_filetypes(EditorState *e) -{ - return dump_filetypes(&e->filetypes); -} - -static String do_dump_messages(EditorState *e) -{ - return dump_messages(&e->messages); -} - -static String do_dump_macro(EditorState *e) -{ - return dump_macro(&e->macro); -} - -static String do_dump_buffer(EditorState *e) -{ - return dump_buffer(e->buffer); -} - -static const ShowHandler show_handlers[] = { - {"alias", DTERC, dump_normal_aliases, show_normal_alias, collect_normal_aliases}, - {"bind", DTERC, dump_all_bindings, show_binding, collect_bound_normal_keys}, - {"buffer", 0, do_dump_buffer, NULL, NULL}, - {"builtin", 0, do_dump_builtin_configs, show_builtin, do_collect_builtin_configs}, - {"color", DTERC, do_dump_hl_colors, show_color, collect_hl_colors}, - {"command", DTERC | LASTLINE, dump_command_history, NULL, NULL}, - {"cursor", DTERC, dump_cursors, show_cursor, do_collect_cursor_modes}, - {"env", 0, dump_env, show_env, collect_env}, - {"errorfmt", DTERC, dump_compilers, show_compiler, collect_compilers}, - {"ft", DTERC, do_dump_filetypes, NULL, NULL}, - {"hi", DTERC, do_dump_hl_colors, show_color, collect_hl_colors}, - {"include", 0, do_dump_builtin_configs, show_builtin, do_collect_builtin_includes}, - {"macro", DTERC, do_dump_macro, NULL, NULL}, - {"msg", MSGLINE, do_dump_messages, NULL, NULL}, - {"option", DTERC, dump_options_and_fileopts, show_option, collect_all_options}, - {"search", LASTLINE, dump_search_history, NULL, NULL}, - {"set", DTERC, do_dump_options, show_option, collect_all_options}, - {"setenv", DTERC, dump_setenv, show_env, collect_env}, - {"wsplit", 0, dump_frames, show_wsplit, NULL}, -}; - -UNITTEST { - CHECK_BSEARCH_ARRAY(show_handlers, name, strcmp); -} - -bool show(EditorState *e, const char *type, const char *key, bool cflag) -{ - const ShowHandler *handler = BSEARCH(type, show_handlers, vstrcmp); - if (!handler) { - return error_msg("invalid argument: '%s'", type); - } - - if (key) { - if (!handler->show) { - return error_msg("'show %s' doesn't take extra arguments", type); - } - return handler->show(e, key, cflag); - } - - String str = handler->dump(e); - open_temporary_buffer(e, str.buffer, str.len, "show", type, handler->flags); - string_free(&str); - return true; -} - -void collect_show_subcommands(PointerArray *a, const char *prefix) -{ - COLLECT_STRING_FIELDS(show_handlers, name, a, prefix); -} - -void collect_show_subcommand_args(EditorState *e, PointerArray *a, const char *name, const char *arg_prefix) -{ - const ShowHandler *handler = BSEARCH(name, show_handlers, vstrcmp); - if (handler && handler->complete_arg) { - handler->complete_arg(e, a, arg_prefix); - } -} diff --git a/examples/dte/show.h b/examples/dte/show.h deleted file mode 100644 index 8736c50..0000000 --- a/examples/dte/show.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef SHOW_H -#define SHOW_H - -#include <stdbool.h> -#include "editor.h" -#include "util/macros.h" -#include "util/ptr-array.h" -#include "util/string.h" - -bool show(EditorState *e, const char *type, const char *key, bool cflag) NONNULL_ARG(1, 2) WARN_UNUSED_RESULT; -void collect_show_subcommands(PointerArray *a, const char *prefix) NONNULL_ARGS; -void collect_show_subcommand_args(EditorState *e, PointerArray *a, const char *name, const char *arg_prefix) NONNULL_ARGS; - -String dump_all_bindings(EditorState *e); -String dump_command_history(EditorState *e); -String dump_compilers(EditorState *e); -String dump_cursors(EditorState *e); -String dump_frames(EditorState *e); -String dump_normal_aliases(EditorState *e); -String dump_options_and_fileopts(EditorState *e); -String dump_search_history(EditorState *e); -String do_dump_builtin_configs(EditorState *e); -String do_dump_filetypes(EditorState *e); -String do_dump_hl_colors(EditorState *e); -String do_dump_options(EditorState *e); - -#endif diff --git a/examples/dte/signals.c b/examples/dte/signals.c deleted file mode 100644 index e1a7155..0000000 --- a/examples/dte/signals.c +++ /dev/null @@ -1,169 +0,0 @@ -#include "compat.h" -#include <errno.h> -#include <string.h> -#include <unistd.h> -#include "signals.h" -#include "util/debug.h" -#include "util/exitcode.h" -#include "util/log.h" -#include "util/macros.h" - -volatile sig_atomic_t resized = 0; - -static const int ignored_signals[] = { - SIGINT, // Terminal interrupt (see: VINTR in termios(3)) - SIGQUIT, // Terminal quit (see: VQUIT in termios(3)) - SIGTSTP, // Terminal stop (see: VSUSP in termios(3)) - SIGXFSZ, // File size limit exceeded (see: RLIMIT_FSIZE in getrlimit(3)) - SIGPIPE, // Broken pipe (see: EPIPE error in write(3)) - SIGUSR1, // User signal 1 (terminates by default, for no good reason) - SIGUSR2, // User signal 2 (as above) -}; - -static const int default_signals[] = { - SIGABRT, // Terminate (cleanup already done) - SIGCHLD, // Ignore (see: wait(3)) - SIGURG, // Ignore - SIGTTIN, // Stop - SIGTTOU, // Stop - SIGCONT, // Continue -}; - -static const int fatal_signals[] = { - SIGBUS, - SIGFPE, - SIGILL, - SIGSEGV, - SIGSYS, - SIGTRAP, - SIGXCPU, - SIGALRM, - SIGVTALRM, - SIGHUP, - SIGTERM, -#ifdef SIGPROF - SIGPROF, -#endif -#ifdef SIGEMT - SIGEMT, -#endif -}; - -void handle_sigwinch(int UNUSED_ARG(signum)) -{ - resized = 1; -} - -static noreturn COLD void handle_fatal_signal(int signum) -{ - LOG_CRITICAL("received signal %d (%s)", signum, strsignal(signum)); - - // If `signum` is SIGHUP, there's no point in trying to clean up the - // state of the (disconnected) terminal - if (signum != SIGHUP) { - fatal_error_cleanup(); - } - - // Restore and unblock `signum` and then re-raise it, to ensure the - // termination status (as seen by e.g. waitpid(3) in the parent) is - // set appropriately - struct sigaction sa = {.sa_handler = SIG_DFL}; - if ( - sigemptyset(&sa.sa_mask) == 0 - && sigaction(signum, &sa, NULL) == 0 - && sigaddset(&sa.sa_mask, signum) == 0 - && sigprocmask(SIG_UNBLOCK, &sa.sa_mask, NULL) == 0 - ) { - raise(signum); - } - - // This is here just to make extra certain the handler never returns. - // If everything is working correctly, this code should be unreachable. - raise(SIGKILL); - _exit(EX_OSERR); -} - -// strsignal(3) is fine in situations where a signal is being reported -// as terminating a process, but it tends to be confusing in most other -// circumstances, where the signal name (not description) is usually -// clearer -static const char *signum_to_str(int signum) -{ -#if HAVE_SIG2STR - static char buf[SIG2STR_MAX + 3]; - if (sig2str(signum, buf + 3) == 0) { - return memcpy(buf, "SIG", 3); - } -#elif HAVE_SIGABBREV_NP - static char buf[16]; - const char *abbr = sigabbrev_np(signum); - if (abbr && memccpy(buf + 3, abbr, '\0', sizeof(buf) - 3)) { - return memcpy(buf, "SIG", 3); - } -#endif - - const char *str = strsignal(signum); - return likely(str) ? str : "??"; -} - -static void do_sigaction(int sig, const struct sigaction *action) -{ - struct sigaction old_action; - if (unlikely(sigaction(sig, action, &old_action) != 0)) { - const char *err = strerror(errno); - LOG_ERROR("failed to set disposition for signal %d: %s", sig, err); - return; - } - if (unlikely(old_action.sa_handler == SIG_IGN)) { - const char *str = signum_to_str(sig); - LOG_WARNING("ignored signal was inherited: %d (%s)", sig, str); - } -} - -/* - * "A program that uses these functions should be written to catch all - * signals and take other appropriate actions to ensure that when the - * program terminates, whether planned or not, the terminal device's - * state is restored to its original state." - * - * (https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetattr.html) - */ -void set_signal_handlers(void) -{ - struct sigaction action = {.sa_handler = handle_fatal_signal}; - sigfillset(&action.sa_mask); - for (size_t i = 0; i < ARRAYLEN(fatal_signals); i++) { - do_sigaction(fatal_signals[i], &action); - } - - // "The default actions for the realtime signals in the range SIGRTMIN - // to SIGRTMAX shall be to terminate the process abnormally." - // (POSIX.1-2017 §2.4.3) -#if defined(SIGRTMIN) && defined(SIGRTMAX) - for (int s = SIGRTMIN, max = SIGRTMAX; s <= max; s++) { - do_sigaction(s, &action); - } -#endif - - action.sa_handler = SIG_IGN; - for (size_t i = 0; i < ARRAYLEN(ignored_signals); i++) { - do_sigaction(ignored_signals[i], &action); - } - - action.sa_handler = SIG_DFL; - for (size_t i = 0; i < ARRAYLEN(default_signals); i++) { - do_sigaction(default_signals[i], &action); - } - -#if defined(SIGWINCH) - LOG_INFO("setting SIGWINCH handler"); - action.sa_handler = handle_sigwinch; - do_sigaction(SIGWINCH, &action); -#endif - - // Set signal mask explicitly, to avoid any possibility of - // inheriting blocked signals - sigset_t mask; - sigemptyset(&mask); - sigprocmask(SIG_SETMASK, &mask, NULL); -} diff --git a/examples/dte/signals.h b/examples/dte/signals.h deleted file mode 100644 index de0859a..0000000 --- a/examples/dte/signals.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef SIGNALS_H -#define SIGNALS_H - -#include <signal.h> - -extern volatile sig_atomic_t resized; - -void set_signal_handlers(void); -void handle_sigwinch(int signum); - -#endif diff --git a/examples/dte/spawn.c b/examples/dte/spawn.c deleted file mode 100644 index 0d9e0d6..0000000 --- a/examples/dte/spawn.c +++ /dev/null @@ -1,396 +0,0 @@ -#include <errno.h> -#include <poll.h> -#include <stddef.h> -#include <stdio.h> -#include <string.h> -#include <unistd.h> -#include "spawn.h" -#include "error.h" -#include "regexp.h" -#include "terminal/mode.h" -#include "util/debug.h" -#include "util/fd.h" -#include "util/fork-exec.h" -#include "util/ptr-array.h" -#include "util/str-util.h" -#include "util/strtonum.h" -#include "util/xmalloc.h" -#include "util/xreadwrite.h" -#include "util/xstdio.h" - -static void handle_error_msg(const Compiler *c, MessageArray *msgs, char *str) -{ - if (str[0] == '\0' || str[0] == '\n') { - return; - } - - size_t str_len = str_replace_byte(str, '\t', ' '); - if (str[str_len - 1] == '\n') { - str[--str_len] = '\0'; - } - - for (size_t i = 0, n = c->error_formats.count; i < n; i++) { - const ErrorFormat *p = c->error_formats.ptrs[i]; - regmatch_t m[ERRORFMT_CAPTURE_MAX]; - if (!regexp_exec(&p->re, str, str_len, ARRAYLEN(m), m, 0)) { - continue; - } - if (p->ignore) { - return; - } - - int8_t mi = p->capture_index[ERRFMT_MESSAGE]; - if (m[mi].rm_so < 0) { - mi = 0; - } - - Message *msg = new_message(str + m[mi].rm_so, m[mi].rm_eo - m[mi].rm_so); - msg->loc = xnew0(FileLocation, 1); - - int8_t fi = p->capture_index[ERRFMT_FILE]; - if (fi >= 0 && m[fi].rm_so >= 0) { - msg->loc->filename = xstrslice(str, m[fi].rm_so, m[fi].rm_eo); - - unsigned long *const ptrs[] = { - [ERRFMT_LINE] = &msg->loc->line, - [ERRFMT_COLUMN] = &msg->loc->column, - }; - - static_assert(ARRAYLEN(ptrs) == 3); - static_assert(ERRFMT_LINE == 1); - static_assert(ERRFMT_COLUMN == 2); - - for (size_t j = ERRFMT_LINE; j < ARRAYLEN(ptrs); j++) { - int8_t ci = p->capture_index[j]; - if (ci >= 0 && m[ci].rm_so >= 0) { - size_t len = m[ci].rm_eo - m[ci].rm_so; - unsigned long val; - if (len == buf_parse_ulong(str + m[ci].rm_so, len, &val)) { - *ptrs[j] = val; - } - } - } - } - - add_message(msgs, msg); - return; - } - - add_message(msgs, new_message(str, str_len)); -} - -static void read_errors(const Compiler *c, MessageArray *msgs, int fd, bool quiet) -{ - FILE *f = fdopen(fd, "r"); - if (unlikely(!f)) { - return; - } - char line[4096]; - while (xfgets(line, sizeof(line), f)) { - if (!quiet) { - xfputs(line, stderr); - } - handle_error_msg(c, msgs, line); - } - fclose(f); -} - -static void handle_piped_data(int f[3], SpawnContext *ctx) -{ - BUG_ON(f[0] < 0 && f[1] < 0 && f[2] < 0); - BUG_ON(f[0] >= 0 && f[0] <= 2); - BUG_ON(f[1] >= 0 && f[1] <= 2); - BUG_ON(f[2] >= 0 && f[2] <= 2); - - if (ctx->input.length == 0) { - xclose(f[0]); - f[0] = -1; - if (f[1] < 0 && f[2] < 0) { - return; - } - } - - struct pollfd fds[] = { - {.fd = f[0], .events = POLLOUT}, - {.fd = f[1], .events = POLLIN}, - {.fd = f[2], .events = POLLIN}, - }; - - size_t wlen = 0; - while (1) { - if (unlikely(poll(fds, ARRAYLEN(fds), -1) < 0)) { - if (errno == EINTR) { - continue; - } - error_msg_errno("poll"); - return; - } - - for (size_t i = 0; i < ARRAYLEN(ctx->outputs); i++) { - struct pollfd *pfd = fds + i + 1; - if (pfd->revents & POLLIN) { - String *output = &ctx->outputs[i]; - char *buf = string_reserve_space(output, 4096); - ssize_t rc = xread(pfd->fd, buf, output->alloc - output->len); - if (unlikely(rc < 0)) { - error_msg_errno("read"); - return; - } - if (rc == 0) { // EOF - if (xclose(pfd->fd)) { - error_msg_errno("close"); - return; - } - pfd->fd = -1; - continue; - } - output->len += rc; - } - } - - if (fds[0].revents & POLLOUT) { - ssize_t rc = xwrite(fds[0].fd, ctx->input.data + wlen, ctx->input.length - wlen); - if (unlikely(rc < 0)) { - error_msg_errno("write"); - return; - } - wlen += (size_t) rc; - if (wlen == ctx->input.length) { - if (xclose(fds[0].fd)) { - error_msg_errno("close"); - return; - } - fds[0].fd = -1; - } - } - - size_t active_fds = ARRAYLEN(fds); - for (size_t i = 0; i < ARRAYLEN(fds); i++) { - int rev = fds[i].revents; - if (fds[i].fd < 0 || rev & POLLNVAL) { - fds[i].fd = -1; - active_fds--; - continue; - } - if (rev & POLLERR || (rev & (POLLHUP | POLLIN)) == POLLHUP) { - if (xclose(fds[i].fd)) { - error_msg_errno("close"); - } - fds[i].fd = -1; - active_fds--; - } - } - if (active_fds == 0) { - return; - } - } -} - -static int open_dev_null(int flags) -{ - int fd = xopen("/dev/null", flags | O_CLOEXEC, 0); - if (unlikely(fd < 0)) { - error_msg_errno("Error opening /dev/null"); - } - return fd; -} - -static int handle_child_error(pid_t pid) -{ - int ret = wait_child(pid); - if (ret < 0) { - error_msg_errno("waitpid"); - } else if (ret >= 256) { - int sig = ret >> 8; - const char *str = strsignal(sig); - error_msg("Child received signal %d (%s)", sig, str ? str : "??"); - } else if (ret) { - error_msg("Child returned %d", ret); - } - return ret; -} - -static void yield_terminal(EditorState *e, bool quiet) -{ - if (quiet) { - term_raw_isig(); - } else { - e->child_controls_terminal = true; - ui_end(e); - } -} - -static void resume_terminal(EditorState *e, bool quiet, bool prompt) -{ - term_raw(); - if (!quiet && e->child_controls_terminal) { - if (prompt) { - any_key(&e->terminal, e->options.esc_timeout); - } - ui_start(e); - e->child_controls_terminal = false; - } -} - -static void exec_error(const char *argv0) -{ - error_msg("Unable to exec '%s': %s", argv0, strerror(errno)); -} - -bool spawn_compiler(SpawnContext *ctx, const Compiler *c, MessageArray *msgs) -{ - BUG_ON(!ctx->editor); - BUG_ON(!ctx->argv[0]); - - int fd[3]; - fd[0] = open_dev_null(O_RDONLY); - if (fd[0] < 0) { - return false; - } - - int dev_null = open_dev_null(O_WRONLY); - if (dev_null < 0) { - xclose(fd[0]); - return false; - } - - int p[2]; - if (xpipe2(p, O_CLOEXEC) != 0) { - error_msg_errno("pipe"); - xclose(dev_null); - xclose(fd[0]); - return false; - } - - SpawnFlags flags = ctx->flags; - bool read_stdout = !!(flags & SPAWN_READ_STDOUT); - bool quiet = !!(flags & SPAWN_QUIET); - bool prompt = !!(flags & SPAWN_PROMPT); - if (read_stdout) { - fd[1] = p[1]; - fd[2] = quiet ? dev_null : 2; - } else { - fd[1] = quiet ? dev_null : 1; - fd[2] = p[1]; - } - - yield_terminal(ctx->editor, quiet); - pid_t pid = fork_exec(ctx->argv, NULL, fd, quiet); - if (pid == -1) { - exec_error(ctx->argv[0]); - xclose(p[1]); - prompt = false; - } else { - // Must close write end of the pipe before read_errors() or - // the read end never gets EOF! - xclose(p[1]); - read_errors(c, msgs, p[0], quiet); - handle_child_error(pid); - } - resume_terminal(ctx->editor, quiet, prompt); - - xclose(p[0]); - xclose(dev_null); - xclose(fd[0]); - return (pid != -1); -} - -// Close fd only if valid (positive) and not stdin/stdout/stderr -static int safe_xclose(int fd) -{ - return (fd > STDERR_FILENO) ? xclose(fd) : 0; -} - -static void safe_xclose_all(int fds[], size_t nr_fds) -{ - for (size_t i = 0; i < nr_fds; i++) { - safe_xclose(fds[i]); - fds[i] = -1; - } -} - -UNITTEST { - int fds[] = {-2, -3, -4}; - safe_xclose_all(fds, 2); - BUG_ON(fds[0] != -1); - BUG_ON(fds[1] != -1); - BUG_ON(fds[2] != -4); - safe_xclose_all(fds, 3); - BUG_ON(fds[2] != -1); -} - -int spawn(SpawnContext *ctx) -{ - BUG_ON(!ctx->editor); - BUG_ON(!ctx->argv[0]); - - int child_fds[3] = {-1, -1, -1}; - int parent_fds[3] = {-1, -1, -1}; - bool quiet = !!(ctx->flags & SPAWN_QUIET); - size_t nr_pipes = 0; - - for (size_t i = 0; i < ARRAYLEN(child_fds); i++) { - switch (ctx->actions[i]) { - case SPAWN_TTY: - if (!quiet) { - child_fds[i] = i; - break; - } - // Fallthrough - case SPAWN_NULL: - child_fds[i] = open_dev_null(O_RDWR); - if (child_fds[i] < 0) { - goto error_close; - } - break; - case SPAWN_PIPE: { - int p[2]; - if (xpipe2(p, O_CLOEXEC) != 0) { - error_msg_errno("pipe"); - goto error_close; - } - BUG_ON(p[0] <= STDERR_FILENO); - BUG_ON(p[1] <= STDERR_FILENO); - child_fds[i] = i ? p[1] : p[0]; - parent_fds[i] = i ? p[0] : p[1]; - if (!fd_set_nonblock(parent_fds[i], true)) { - error_msg_errno("fcntl"); - goto error_close; - } - nr_pipes++; - break; - } - default: - BUG("unhandled action type"); - goto error_close; - } - } - - yield_terminal(ctx->editor, quiet); - pid_t pid = fork_exec(ctx->argv, ctx->env, child_fds, quiet); - if (pid == -1) { - exec_error(ctx->argv[0]); - goto error_resume; - } - - safe_xclose_all(child_fds, ARRAYLEN(child_fds)); - if (nr_pipes > 0) { - handle_piped_data(parent_fds, ctx); - } - - safe_xclose_all(parent_fds, ARRAYLEN(parent_fds)); - int err = wait_child(pid); - if (err < 0) { - error_msg_errno("waitpid"); - } - - resume_terminal(ctx->editor, quiet, !!(ctx->flags & SPAWN_PROMPT)); - return err; - -error_resume: - resume_terminal(ctx->editor, quiet, false); -error_close: - safe_xclose_all(child_fds, ARRAYLEN(child_fds)); - safe_xclose_all(parent_fds, ARRAYLEN(parent_fds)); - return -1; -} diff --git a/examples/dte/spawn.h b/examples/dte/spawn.h deleted file mode 100644 index 659f2cd..0000000 --- a/examples/dte/spawn.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef SPAWN_H -#define SPAWN_H - -#include <stdbool.h> -#include "compiler.h" -#include "editor.h" -#include "msg.h" -#include "util/macros.h" -#include "util/string.h" -#include "util/string-view.h" - -typedef enum { - SPAWN_QUIET = 1 << 0, // Interpret SPAWN_TTY as SPAWN_NULL and don't yield terminal to child - SPAWN_PROMPT = 1 << 1, // Show "press any key to continue" prompt - SPAWN_READ_STDOUT = 1 << 2, // Read errors from stdout instead of stderr -} SpawnFlags; - -typedef enum { - SPAWN_NULL, - SPAWN_TTY, - SPAWN_PIPE, -} SpawnAction; - -typedef struct { - EditorState *editor; - const char **argv; - const char **env; - StringView input; - String outputs[2]; // For stdout/stderr - SpawnFlags flags; - SpawnAction actions[3]; -} SpawnContext; - -int spawn(SpawnContext *ctx) NONNULL_ARGS WARN_UNUSED_RESULT; -bool spawn_compiler(SpawnContext *ctx, const Compiler *c, MessageArray *msgs) NONNULL_ARGS WARN_UNUSED_RESULT; - -#endif diff --git a/examples/dte/status.c b/examples/dte/status.c deleted file mode 100644 index 228b1c6..0000000 --- a/examples/dte/status.c +++ /dev/null @@ -1,337 +0,0 @@ -#include <stdbool.h> -#include <stdint.h> -#include <string.h> -#include "status.h" -#include "search.h" -#include "selection.h" -#include "util/debug.h" -#include "util/macros.h" -#include "util/numtostr.h" -#include "util/utf8.h" -#include "util/xsnprintf.h" - -typedef struct { - char *buf; - size_t size; - size_t pos; - size_t separator; - const Window *window; - const GlobalOptions *opts; - InputMode input_mode; -} Formatter; - -typedef enum { - STATUS_INVALID = 0, - STATUS_ESCAPED_PERCENT, - STATUS_ENCODING, - STATUS_MISC, - STATUS_IS_CRLF, - STATUS_SEPARATOR_LONG, - STATUS_CURSOR_COL_BYTES, - STATUS_TOTAL_ROWS, - STATUS_BOM, - STATUS_FILENAME, - STATUS_MODIFIED, - STATUS_LINE_ENDING, - STATUS_OVERWRITE, - STATUS_SCROLL_POSITION, - STATUS_READONLY, - STATUS_SEPARATOR, - STATUS_FILETYPE, - STATUS_UNICODE, - STATUS_CURSOR_COL, - STATUS_CURSOR_ROW, -} FormatSpecifierType; - -static FormatSpecifierType lookup_format_specifier(unsigned char ch) -{ - switch (ch) { - case '%': return STATUS_ESCAPED_PERCENT; - case 'E': return STATUS_ENCODING; - case 'M': return STATUS_MISC; - case 'N': return STATUS_IS_CRLF; - case 'S': return STATUS_SEPARATOR_LONG; - case 'X': return STATUS_CURSOR_COL_BYTES; - case 'Y': return STATUS_TOTAL_ROWS; - case 'b': return STATUS_BOM; - case 'f': return STATUS_FILENAME; - case 'm': return STATUS_MODIFIED; - case 'n': return STATUS_LINE_ENDING; - case 'o': return STATUS_OVERWRITE; - case 'p': return STATUS_SCROLL_POSITION; - case 'r': return STATUS_READONLY; - case 's': return STATUS_SEPARATOR; - case 't': return STATUS_FILETYPE; - case 'u': return STATUS_UNICODE; - case 'x': return STATUS_CURSOR_COL; - case 'y': return STATUS_CURSOR_ROW; - } - return STATUS_INVALID; -} - -#define add_status_literal(f, s) add_status_bytes(f, s, STRLEN(s)) - -static void add_ch(Formatter *f, char ch) -{ - f->buf[f->pos++] = ch; -} - -static void add_separator(Formatter *f) -{ - while (f->separator && f->pos < f->size) { - add_ch(f, ' '); - f->separator--; - } -} - -static void add_status_str(Formatter *f, const char *str) -{ - BUG_ON(!str); - if (unlikely(!str[0])) { - return; - } - add_separator(f); - size_t idx = 0; - while (f->pos < f->size && str[idx]) { - u_set_char(f->buf, &f->pos, u_str_get_char(str, &idx)); - } -} - -static void add_status_bytes(Formatter *f, const char *str, size_t len) -{ - if (unlikely(len == 0)) { - return; - } - add_separator(f); - if (f->pos >= f->size) { - return; - } - const size_t avail = f->size - f->pos; - len = MIN(len, avail); - memcpy(f->buf + f->pos, str, len); - f->pos += len; -} - -PRINTF(2) -static void add_status_format(Formatter *f, const char *format, ...) -{ - char buf[1024]; - va_list ap; - va_start(ap, format); - size_t len = xvsnprintf(buf, sizeof(buf), format, ap); - va_end(ap); - add_status_bytes(f, buf, len); -} - -static void add_status_umax(Formatter *f, uintmax_t x) -{ - char buf[DECIMAL_STR_MAX(x)]; - size_t len = buf_umax_to_str(x, buf); - add_status_bytes(f, buf, len); -} - -static void add_status_pos(Formatter *f) -{ - size_t lines = f->window->view->buffer->nl; - int h = f->window->edit_h; - long pos = f->window->view->vy; - if (lines <= h) { - if (pos) { - add_status_literal(f, "Bot"); - } else { - add_status_literal(f, "All"); - } - } else if (pos == 0) { - add_status_literal(f, "Top"); - } else if (pos + h - 1 >= lines) { - add_status_literal(f, "Bot"); - } else { - unsigned int d = lines - (h - 1); - unsigned int percent = (pos * 100 + d / 2) / d; - BUG_ON(percent > 100); - char buf[4]; - size_t len = buf_uint_to_str(percent, buf); - buf[len++] = '%'; - add_status_bytes(f, buf, len); - } -} - -static void add_misc_status(Formatter *f) -{ - static const struct { - const char str[24]; - size_t len; - } css_strs[] = { - [CSS_FALSE] = {STRN("[case-sensitive = false]")}, - [CSS_TRUE] = {STRN("[case-sensitive = true]")}, - [CSS_AUTO] = {STRN("[case-sensitive = auto]")}, - }; - - if (f->input_mode == INPUT_SEARCH) { - SearchCaseSensitivity css = f->opts->case_sensitive_search; - BUG_ON(css >= ARRAYLEN(css_strs)); - add_status_bytes(f, css_strs[css].str, css_strs[css].len); - return; - } - - const View *view = f->window->view; - if (view->selection == SELECT_NONE) { - return; - } - - SelectionInfo si; - init_selection(view, &si); - bool is_lines = (view->selection == SELECT_LINES); - size_t n = is_lines ? get_nr_selected_lines(&si) : get_nr_selected_chars(&si); - const char *unit = is_lines ? "line" : "char"; - const char *plural = unlikely(n == 1) ? "" : "s"; - add_status_format(f, "[%zu %s%s]", n, unit, plural); -} - -void sf_format ( - const Window *window, - const GlobalOptions *opts, - InputMode mode, - char *buf, // NOLINT(readability-non-const-parameter) - size_t size, - const char *format -) { - BUG_ON(size < 16); - Formatter f = { - .window = window, - .opts = opts, - .input_mode = mode, - .buf = buf, - .size = size - 5, // Max length of char and terminating NUL - }; - - const View *view = window->view; - const Buffer *buffer = view->buffer; - CodePoint u; - - while (f.pos < f.size && *format) { - unsigned char ch = *format++; - if (ch != '%') { - add_separator(&f); - add_ch(&f, ch); - continue; - } - - switch (lookup_format_specifier(*format++)) { - case STATUS_BOM: - if (buffer->bom) { - add_status_literal(&f, "BOM"); - } - break; - case STATUS_FILENAME: - add_status_str(&f, buffer_filename(buffer)); - break; - case STATUS_MODIFIED: - if (buffer_modified(buffer)) { - add_separator(&f); - add_ch(&f, '*'); - } - break; - case STATUS_READONLY: - if (buffer->readonly) { - add_status_literal(&f, "RO"); - } else if (buffer->temporary) { - add_status_literal(&f, "TMP"); - } - break; - case STATUS_CURSOR_ROW: - add_status_umax(&f, view->cy + 1); - break; - case STATUS_TOTAL_ROWS: - add_status_umax(&f, buffer->nl); - break; - case STATUS_CURSOR_COL: - add_status_umax(&f, view->cx_display + 1); - break; - case STATUS_CURSOR_COL_BYTES: - add_status_umax(&f, view->cx_char + 1); - if (view->cx_display != view->cx_char) { - add_ch(&f, '-'); - add_status_umax(&f, view->cx_display + 1); - } - break; - case STATUS_SCROLL_POSITION: - add_status_pos(&f); - break; - case STATUS_ENCODING: - add_status_str(&f, buffer->encoding.name); - break; - case STATUS_MISC: - add_misc_status(&f); - break; - case STATUS_IS_CRLF: - if (buffer->crlf_newlines) { - add_status_literal(&f, "CRLF"); - } - break; - case STATUS_LINE_ENDING: - if (buffer->crlf_newlines) { - add_status_literal(&f, "CRLF"); - } else { - add_status_literal(&f, "LF"); - } - break; - case STATUS_OVERWRITE: - if (buffer->options.overwrite) { - add_status_literal(&f, "OVR"); - } else { - add_status_literal(&f, "INS"); - } - break; - case STATUS_SEPARATOR_LONG: - f.separator = 3; - break; - case STATUS_SEPARATOR: - f.separator = 1; - break; - case STATUS_FILETYPE: - add_status_str(&f, buffer->options.filetype); - break; - case STATUS_UNICODE: - if (unlikely(!block_iter_get_char(&view->cursor, &u))) { - break; - } - if (u_is_unicode(u)) { - char str[STRLEN("U+10FFFF") + 1]; - str[0] = 'U'; - str[1] = '+'; - size_t ndigits = buf_umax_to_hex_str(u, str + 2, 4); - add_status_bytes(&f, str, 2 + ndigits); - } else { - add_status_literal(&f, "Invalid"); - } - break; - case STATUS_ESCAPED_PERCENT: - add_separator(&f); - add_ch(&f, '%'); - break; - case STATUS_INVALID: - default: - BUG("should be unreachable, due to validate_statusline_format()"); - } - } - - f.buf[f.pos] = '\0'; -} - -// Returns the offset of the first invalid format specifier, or 0 if -// the whole format string is valid. It's safe to use 0 to indicate -// "no errors", since it's not possible for there to be an error at -// the very start of the string. -size_t statusline_format_find_error(const char *str) -{ - for (size_t i = 0; str[i]; ) { - if (str[i++] != '%') { - continue; - } - if (lookup_format_specifier(str[i++]) == STATUS_INVALID) { - return i - 1; - } - } - return 0; -} diff --git a/examples/dte/status.h b/examples/dte/status.h deleted file mode 100644 index df84f0f..0000000 --- a/examples/dte/status.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef STATUS_H -#define STATUS_H - -#include <stddef.h> -#include "editor.h" -#include "options.h" -#include "window.h" - -size_t statusline_format_find_error(const char *str); - -void sf_format ( - const Window *window, - const GlobalOptions *opts, - InputMode mode, - char *buf, - size_t size, - const char *format -); - -#endif diff --git a/examples/dte/tag.c b/examples/dte/tag.c deleted file mode 100644 index bd030c0..0000000 --- a/examples/dte/tag.c +++ /dev/null @@ -1,322 +0,0 @@ -#include <errno.h> -#include <string.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> -#include "tag.h" -#include "error.h" -#include "util/debug.h" -#include "util/log.h" -#include "util/path.h" -#include "util/str-util.h" -#include "util/xmalloc.h" -#include "util/xreadwrite.h" - -static const char *current_filename; // For sorting tags - -static int visibility_cmp(const Tag *a, const Tag *b) -{ - bool a_this_file = false; - bool b_this_file = false; - - if (!a->local && !b->local) { - return 0; - } - - // Is tag visibility limited to the current file? - if (a->local) { - a_this_file = current_filename && strview_equal_cstring(&a->filename, current_filename); - } - if (b->local) { - b_this_file = current_filename && strview_equal_cstring(&b->filename, current_filename); - } - - // Tags local to other file than current are not interesting - if (a->local && !a_this_file) { - // a is not interesting - if (b->local && !b_this_file) { - // b is equally uninteresting - return 0; - } - // b is more interesting, sort it before a - return 1; - } - if (b->local && !b_this_file) { - // b is not interesting - return -1; - } - - // Both are NOT UNinteresting - - if (a->local && a_this_file) { - if (b->local && b_this_file) { - return 0; - } - // a is more interesting because it is local symbol - return -1; - } - if (b->local && b_this_file) { - // b is more interesting because it is local symbol - return 1; - } - return 0; -} - -static int kind_cmp(const Tag *a, const Tag *b) -{ - if (a->kind == b->kind) { - return 0; - } - - // Struct member (m) is not very interesting - if (a->kind == 'm') { - return 1; - } - if (b->kind == 'm') { - return -1; - } - - // Global variable (v) is not very interesting - if (a->kind == 'v') { - return 1; - } - if (b->kind == 'v') { - return -1; - } - - // Struct (s), union (u) - return 0; -} - -static int tag_cmp(const void *ap, const void *bp) -{ - const Tag *const *a = ap; - const Tag *const *b = bp; - int r = visibility_cmp(*a, *b); - return r ? r : kind_cmp(*a, *b); -} - -// Find "tags" file from directory path and its parent directories -static int open_tag_file(char *path) -{ - static const char tags[] = "tags"; - while (*path) { - size_t len = strlen(path); - char *slash = strrchr(path, '/'); - if (slash != path + len - 1) { - path[len++] = '/'; - } - memcpy(path + len, tags, sizeof(tags)); - int fd = xopen(path, O_RDONLY | O_CLOEXEC, 0); - if (fd >= 0) { - return fd; - } - if (errno != ENOENT) { - return -1; - } - *slash = '\0'; - } - errno = ENOENT; - return -1; -} - -static bool tag_file_changed ( - const TagFile *tf, - const char *filename, - const struct stat *st -) { - return tf->mtime != st->st_mtime || !streq(tf->filename, filename); -} - -// Note: does not free `tf` itself -void tag_file_free(TagFile *tf) -{ - free(tf->filename); - free(tf->buf); - *tf = (TagFile){.filename = NULL}; -} - -static bool load_tag_file(TagFile *tf) -{ - char path[4096]; - if (unlikely(!getcwd(path, sizeof(path) - STRLEN("/tags")))) { - LOG_ERRNO("getcwd"); - return false; - } - - int fd = open_tag_file(path); - if (fd < 0) { - return false; - } - - struct stat st; - if (unlikely(fstat(fd, &st) != 0)) { - LOG_ERRNO("fstat"); - xclose(fd); - return false; - } - - if (unlikely(st.st_size <= 0)) { - xclose(fd); - return false; - } - - if (tf->filename) { - if (!tag_file_changed(tf, path, &st)) { - xclose(fd); - return true; - } - tag_file_free(tf); - BUG_ON(tf->filename); - } - - char *buf = malloc(st.st_size); - if (unlikely(!buf)) { - LOG_ERRNO("malloc"); - xclose(fd); - return false; - } - - ssize_t size = xread_all(fd, buf, st.st_size); - xclose(fd); - if (size < 0) { - free(buf); - return false; - } - - *tf = (TagFile) { - .filename = xstrdup(path), - .buf = buf, - .size = size, - .mtime = st.st_mtime, - }; - - return true; -} - -static void free_tags_cb(Tag *t) -{ - free_tag(t); - free(t); -} - -static void free_tags(PointerArray *tags) -{ - ptr_array_free_cb(tags, FREE_FUNC(free_tags_cb)); -} - -// Both parameters must be absolute and clean -static const char *path_slice_relative(const char *filename, const StringView dir) -{ - if (strncmp(filename, dir.data, dir.length) != 0) { - // Filename doesn't start with dir - return NULL; - } - switch (filename[dir.length]) { - case '\0': // Equal strings - return "."; - case '/': - return filename + dir.length + 1; - } - return NULL; -} - -static void tag_file_find_tags ( - const TagFile *tf, - const char *filename, - const StringView *name, - PointerArray *tags -) { - Tag *t = xnew(Tag, 1); - size_t pos = 0; - while (next_tag(tf->buf, tf->size, &pos, name, true, t)) { - ptr_array_append(tags, t); - t = xnew(Tag, 1); - } - free(t); - - if (!filename) { - current_filename = NULL; - } else { - StringView dir = path_slice_dirname(tf->filename); - current_filename = path_slice_relative(filename, dir); - } - ptr_array_sort(tags, tag_cmp); - current_filename = NULL; -} - -// Note: this moves ownership of tag->pattern to the generated Message -// and assigns NULL to the old pointer -void add_message_for_tag(MessageArray *messages, Tag *tag, const StringView *dir) -{ - BUG_ON(dir->length == 0); - BUG_ON(dir->data[0] != '/'); - - static const char prefix[] = "Tag "; - size_t prefix_len = sizeof(prefix) - 1; - size_t msg_len = prefix_len + tag->name.length; - Message *m = xmalloc(sizeof(*m) + msg_len + 1); - - memcpy(m->msg, prefix, prefix_len); - memcpy(m->msg + prefix_len, tag->name.data, tag->name.length); - m->msg[msg_len] = '\0'; - - m->loc = xnew0(FileLocation, 1); - m->loc->filename = path_join_sv(dir, &tag->filename, false); - - if (tag->pattern) { - m->loc->pattern = tag->pattern; // Message takes ownership - tag->pattern = NULL; - } else { - m->loc->line = tag->lineno; - } - - add_message(messages, m); -} - -size_t tag_lookup(TagFile *tf, const StringView *name, const char *filename, MessageArray *messages) -{ - clear_messages(messages); - if (!load_tag_file(tf)) { - error_msg("No tags file"); - return 0; - } - - // Filename helps to find correct tags - PointerArray tags = PTR_ARRAY_INIT; - tag_file_find_tags(tf, filename, name, &tags); - - size_t ntags = tags.count; - if (ntags == 0) { - error_msg("Tag '%.*s' not found", (int)name->length, name->data); - return 0; - } - - StringView tf_dir = path_slice_dirname(tf->filename); - for (size_t i = 0; i < ntags; i++) { - Tag *tag = tags.ptrs[i]; - add_message_for_tag(messages, tag, &tf_dir); - } - - free_tags(&tags); - return ntags; -} - -void collect_tags(TagFile *tf, PointerArray *a, const StringView *prefix) -{ - if (!load_tag_file(tf)) { - return; - } - - Tag t; - size_t pos = 0; - StringView prev = STRING_VIEW_INIT; - while (next_tag(tf->buf, tf->size, &pos, prefix, false, &t)) { - BUG_ON(t.name.length == 0); - if (prev.length == 0 || !strview_equal(&t.name, &prev)) { - ptr_array_append(a, xstrcut(t.name.data, t.name.length)); - prev = t.name; - } - free_tag(&t); - } -} diff --git a/examples/dte/tag.h b/examples/dte/tag.h deleted file mode 100644 index eddc04a..0000000 --- a/examples/dte/tag.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef TAG_H -#define TAG_H - -#include <sys/types.h> -#include "ctags.h" -#include "msg.h" -#include "util/macros.h" -#include "util/ptr-array.h" -#include "util/string-view.h" - -typedef struct { - char *filename; - char *buf; - size_t size; - time_t mtime; -} TagFile; - -void add_message_for_tag(MessageArray *messages, Tag *tag, const StringView *dir) NONNULL_ARGS; -size_t tag_lookup(TagFile *tf, const StringView *name, const char *filename, MessageArray *messages) NONNULL_ARG(1, 2, 4); -void collect_tags(TagFile *tf, PointerArray *a, const StringView *prefix) NONNULL_ARGS; -void tag_file_free(TagFile *tf) NONNULL_ARGS; - -#endif diff --git a/examples/dte/vars.c b/examples/dte/vars.c deleted file mode 100644 index 5155ca2..0000000 --- a/examples/dte/vars.c +++ /dev/null @@ -1,113 +0,0 @@ -#include <stddef.h> -#include <string.h> -#include <unistd.h> -#include "vars.h" -#include "buffer.h" -#include "editor.h" -#include "selection.h" -#include "util/array.h" -#include "util/bsearch.h" -#include "util/numtostr.h" -#include "util/path.h" -#include "util/xmalloc.h" -#include "view.h" - -typedef struct { - char name[12]; - char *(*expand)(const EditorState *e); -} BuiltinVar; - -static char *expand_dte_home(const EditorState *e) -{ - return xstrdup(e->user_config_dir); -} - -static char *expand_file(const EditorState *e) -{ - if (!e->buffer || !e->buffer->abs_filename) { - return NULL; - } - return xstrdup(e->buffer->abs_filename); -} - -static char *expand_file_dir(const EditorState *e) -{ - if (!e->buffer || !e->buffer->abs_filename) { - return NULL; - } - return path_dirname(e->buffer->abs_filename); -} - -static char *expand_rfile(const EditorState *e) -{ - if (!e->buffer || !e->buffer->abs_filename) { - return NULL; - } - char buf[8192]; - const char *cwd = getcwd(buf, sizeof buf); - const char *abs = e->buffer->abs_filename; - return likely(cwd) ? path_relative(abs, cwd) : xstrdup(abs); -} - -static char *expand_filetype(const EditorState *e) -{ - return e->buffer ? xstrdup(e->buffer->options.filetype) : NULL; -} - -static char *expand_colno(const EditorState *e) -{ - return e->view ? xstrdup(umax_to_str(e->view->cx_display + 1)) : NULL; -} - -static char *expand_lineno(const EditorState *e) -{ - return e->view ? xstrdup(umax_to_str(e->view->cy + 1)) : NULL; -} - -static char *expand_word(const EditorState *e) -{ - if (!e->view) { - return NULL; - } - - size_t size; - char *selection = view_get_selection(e->view, &size); - if (selection) { - xrenew(selection, size + 1); - selection[size] = '\0'; - return selection; - } - - StringView word = view_get_word_under_cursor(e->view); - return word.length ? xstrcut(word.data, word.length) : NULL; -} - -static const BuiltinVar normal_vars[] = { - {"COLNO", expand_colno}, - {"DTE_HOME", expand_dte_home}, - {"FILE", expand_file}, - {"FILEDIR", expand_file_dir}, - {"FILETYPE", expand_filetype}, - {"LINENO", expand_lineno}, - {"RFILE", expand_rfile}, - {"WORD", expand_word}, -}; - -UNITTEST { - CHECK_BSEARCH_ARRAY(normal_vars, name, strcmp); -} - -bool expand_normal_var(const char *name, char **value, const void *userdata) -{ - const BuiltinVar *var = BSEARCH(name, normal_vars, vstrcmp); - if (!var) { - return false; - } - *value = var->expand(userdata); - return true; -} - -void collect_normal_vars(PointerArray *a, const char *prefix) -{ - COLLECT_STRING_FIELDS(normal_vars, name, a, prefix); -} diff --git a/examples/dte/vars.h b/examples/dte/vars.h deleted file mode 100644 index 02d53be..0000000 --- a/examples/dte/vars.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef VARS_H -#define VARS_H - -#include <stdbool.h> -#include "util/macros.h" -#include "util/ptr-array.h" - -bool expand_normal_var(const char *name, char **value, const void *userdata) NONNULL_ARGS; -void collect_normal_vars(PointerArray *a, const char *prefix) NONNULL_ARGS; - -#endif diff --git a/examples/dte/view.c b/examples/dte/view.c deleted file mode 100644 index cc259c0..0000000 --- a/examples/dte/view.c +++ /dev/null @@ -1,178 +0,0 @@ -#include "view.h" -#include "buffer.h" -#include "indent.h" -#include "util/ascii.h" -#include "util/debug.h" -#include "util/str-util.h" -#include "util/utf8.h" -#include "window.h" - -void view_update_cursor_y(View *view) -{ - Buffer *buffer = view->buffer; - Block *blk; - size_t nl = 0; - block_for_each(blk, &buffer->blocks) { - if (blk == view->cursor.blk) { - nl += count_nl(blk->data, view->cursor.offset); - view->cy = nl; - return; - } - nl += blk->nl; - } - BUG("unreachable"); -} - -void view_update_cursor_x(View *view) -{ - StringView line; - const unsigned int tw = view->buffer->options.tab_width; - const size_t cx = fetch_this_line(&view->cursor, &line); - long cx_char = 0; - long w = 0; - - for (size_t idx = 0; idx < cx; cx_char++) { - CodePoint u = line.data[idx++]; - if (likely(u < 0x80)) { - if (likely(!ascii_iscntrl(u))) { - w++; - } else if (u == '\t') { - w = next_indent_width(w, tw); - } else { - w += 2; - } - } else { - idx--; - u = u_get_nonascii(line.data, line.length, &idx); - w += u_char_width(u); - } - } - - view->cx = cx; - view->cx_char = cx_char; - view->cx_display = w; -} - -static bool view_is_cursor_visible(const View *v) -{ - return v->cy < v->vy || v->cy > v->vy + v->window->edit_h - 1; -} - -static void view_center_to_cursor(View *v) -{ - size_t lines = v->buffer->nl; - Window *window = v->window; - unsigned int hh = window->edit_h / 2; - - if (window->edit_h >= lines || v->cy < hh) { - v->vy = 0; - return; - } - - v->vy = v->cy - hh; - if (v->vy + window->edit_h > lines) { - // -1 makes one ~ line visible so that you know where the EOF is - v->vy -= v->vy + window->edit_h - lines - 1; - } -} - -static void view_update_vx(View *v) -{ - Window *window = v->window; - unsigned int c = 8; - - if (v->cx_display - v->vx >= window->edit_w) { - v->vx = (v->cx_display - window->edit_w + c) / c * c; - } - if (v->cx_display < v->vx) { - v->vx = v->cx_display / c * c; - } -} - -static void view_update_vy(View *v, unsigned int scroll_margin) -{ - Window *window = v->window; - int margin = window_get_scroll_margin(window, scroll_margin); - long max_y = v->vy + window->edit_h - 1 - margin; - - if (v->cy < v->vy + margin) { - v->vy = MAX(v->cy - margin, 0); - } else if (v->cy > max_y) { - v->vy += v->cy - max_y; - max_y = v->buffer->nl - window->edit_h + 1; - if (v->vy > max_y && max_y >= 0) { - v->vy = max_y; - } - } -} - -void view_update(View *v, unsigned int scroll_margin) -{ - view_update_vx(v); - if (v->force_center || (v->center_on_scroll && view_is_cursor_visible(v))) { - view_center_to_cursor(v); - } else { - view_update_vy(v, scroll_margin); - } - v->force_center = false; - v->center_on_scroll = false; -} - -long view_get_preferred_x(View *v) -{ - if (v->preferred_x < 0) { - view_update_cursor_x(v); - v->preferred_x = v->cx_display; - } - return v->preferred_x; -} - -bool view_can_close(const View *view) -{ - const Buffer *buffer = view->buffer; - return !buffer_modified(buffer) || buffer->views.count > 1; -} - -StringView view_do_get_word_under_cursor(const View *view, size_t *offset_in_line) -{ - StringView line; - size_t si = fetch_this_line(&view->cursor, &line); - while (si < line.length) { - size_t i = si; - if (u_is_word_char(u_get_char(line.data, line.length, &i))) { - break; - } - si = i; - } - - if (si == line.length) { - *offset_in_line = 0; - return string_view(NULL, 0); - } - - size_t ei = si; - while (si > 0) { - size_t i = si; - if (!u_is_word_char(u_prev_char(line.data, &i))) { - break; - } - si = i; - } - - while (ei < line.length) { - size_t i = ei; - if (!u_is_word_char(u_get_char(line.data, line.length, &i))) { - break; - } - ei = i; - } - - *offset_in_line = si; - return string_view(line.data + si, ei - si); -} - -StringView view_get_word_under_cursor(const View *view) -{ - size_t offset_in_line; - return view_do_get_word_under_cursor(view, &offset_in_line); -} diff --git a/examples/dte/view.h b/examples/dte/view.h deleted file mode 100644 index c7bb254..0000000 --- a/examples/dte/view.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef VIEW_H -#define VIEW_H - -#include <limits.h> -#include <stdbool.h> -#include <sys/types.h> -#include "block-iter.h" -#include "util/macros.h" -#include "util/string-view.h" - -typedef enum { - SELECT_NONE, - SELECT_CHARS, - SELECT_LINES, -} SelectionType; - -// A view into a Buffer, with its own cursor position and selection. -// Visually speaking, each tab in a Window corresponds to a View. -typedef struct View { - struct Buffer *buffer; - struct Window *window; - BlockIter cursor; - long cx, cy; // Cursor position - long cx_display; // Visual cursor x (char widths: wide 2, tab 1-8, control 2, invalid char 4) - long cx_char; // Cursor x in characters (invalid UTF-8 character (byte) is 1 char) - long vx, vy; // Top left corner - long preferred_x; // Preferred cursor x (preferred value for cx_display) - int tt_width; // Tab title width - int tt_truncated_width; - bool center_on_scroll; // Center view to cursor if scrolled - bool force_center; // Force centering view to cursor - - SelectionType selection; - SelectionType select_mode; - ssize_t sel_so; // Cursor offset when selection was started - ssize_t sel_eo; // See `SEL_EO_RECALC` below - - // Used to save cursor state when multiple views share same buffer - bool restore_cursor; - size_t saved_cursor_offset; -} View; - -// If View::sel_eo is set to this value it means the offset must -// be calculated from the cursor iterator. Otherwise the offset -// is precalculated and may not be the same as the cursor position -// (see search/replace code). -#define SEL_EO_RECALC SSIZE_MAX - -static inline void view_reset_preferred_x(View *view) -{ - view->preferred_x = -1; -} - -void view_update_cursor_y(View *view) NONNULL_ARGS; -void view_update_cursor_x(View *view) NONNULL_ARGS; -void view_update(View *view, unsigned int scroll_margin) NONNULL_ARGS; -long view_get_preferred_x(View *view) NONNULL_ARGS; -bool view_can_close(const View *view) NONNULL_ARGS; -StringView view_do_get_word_under_cursor(const View *view, size_t *offset_in_line) NONNULL_ARGS; -StringView view_get_word_under_cursor(const View *view) NONNULL_ARGS; - -#endif diff --git a/examples/dte/window.c b/examples/dte/window.c deleted file mode 100644 index 6b0c5cb..0000000 --- a/examples/dte/window.c +++ /dev/null @@ -1,507 +0,0 @@ -#include <errno.h> -#include <stdlib.h> -#include <unistd.h> -#include "window.h" -#include "editor.h" -#include "error.h" -#include "file-history.h" -#include "load-save.h" -#include "lock.h" -#include "move.h" -#include "util/path.h" -#include "util/str-util.h" -#include "util/strtonum.h" -#include "util/xmalloc.h" - -Window *new_window(EditorState *e) -{ - Window *window = xnew0(Window, 1); - window->editor = e; - return window; -} - -View *window_add_buffer(Window *window, Buffer *buffer) -{ - // We rely on this being 0, for implicit initialization of - // View::selection and View::select_mode - static_assert(SELECT_NONE == 0); - - View *view = xnew(View, 1); - *view = (View) { - .buffer = buffer, - .window = window, - .cursor = { - .blk = BLOCK(buffer->blocks.next), - .head = &buffer->blocks, - } - }; - - ptr_array_append(&buffer->views, view); - ptr_array_append(&window->views, view); - window->update_tabbar = true; - return view; -} - -View *window_open_empty_buffer(Window *window) -{ - EditorState *e = window->editor; - return window_add_buffer(window, open_empty_buffer(&e->buffers, &e->options)); -} - -View *window_open_buffer ( - Window *window, - const char *filename, - bool must_exist, - const Encoding *encoding -) { - if (unlikely(filename[0] == '\0')) { - error_msg("Empty filename not allowed"); - return NULL; - } - - EditorState *e = window->editor; - bool dir_missing = false; - char *absolute = path_absolute(filename); - if (absolute) { - // Already open? - Buffer *buffer = find_buffer(&e->buffers, absolute); - if (buffer) { - if (!streq(absolute, buffer->abs_filename)) { - const char *bufname = buffer_filename(buffer); - char *s = short_filename(absolute, &e->home_dir); - info_msg("%s and %s are the same file", s, bufname); - free(s); - } - free(absolute); - return window_get_view(window, buffer); - } - } else { - // Let load_buffer() create error message - dir_missing = (errno == ENOENT); - } - - /* - /proc/$PID/fd/ contains symbolic links to files that have been opened - by process $PID. Some of the files may have been deleted but can still - be opened using the symbolic link but not by using the absolute path. - - # create file - mkdir /tmp/x - echo foo > /tmp/x/file - - # in another shell: keep the file open - tail -f /tmp/x/file - - # make the absolute path unavailable - rm /tmp/x/file - rmdir /tmp/x - - # this should still succeed - dte /proc/$(pidof tail)/fd/3 - */ - - Buffer *buffer = buffer_new(&e->buffers, &e->options, encoding); - if (!load_buffer(buffer, filename, &e->options, must_exist)) { - remove_and_free_buffer(&e->buffers, buffer); - free(absolute); - return NULL; - } - if (unlikely(buffer->file.mode == 0 && dir_missing)) { - // New file in non-existing directory; this is usually a mistake - error_msg("Error opening %s: Directory does not exist", filename); - remove_and_free_buffer(&e->buffers, buffer); - free(absolute); - return NULL; - } - - if (absolute) { - buffer->abs_filename = absolute; - } else { - // FIXME: obviously wrong - buffer->abs_filename = xstrdup(filename); - } - update_short_filename(buffer, &e->home_dir); - - if (e->options.lock_files) { - if (!lock_file(buffer->abs_filename)) { - buffer->readonly = true; - } else { - buffer->locked = true; - } - } - - if (buffer->file.mode != 0 && !buffer->readonly && access(filename, W_OK)) { - error_msg("No write permission to %s, marking read-only", filename); - buffer->readonly = true; - } - - return window_add_buffer(window, buffer); -} - -View *window_get_view(Window *window, Buffer *buffer) -{ - View *view = window_find_view(window, buffer); - if (!view) { - // Open the buffer in other window to this window - view = window_add_buffer(window, buffer); - view->cursor = ((View*)buffer->views.ptrs[0])->cursor; - } - return view; -} - -View *window_find_view(Window *window, Buffer *buffer) -{ - for (size_t i = 0, n = buffer->views.count; i < n; i++) { - View *view = buffer->views.ptrs[i]; - if (view->window == window) { - return view; - } - } - // Buffer isn't open in this window - return NULL; -} - -View *window_find_unclosable_view(Window *window) -{ - // Check active view first - if (window->view && !view_can_close(window->view)) { - return window->view; - } - for (size_t i = 0, n = window->views.count; i < n; i++) { - View *view = window->views.ptrs[i]; - if (!view_can_close(view)) { - return view; - } - } - return NULL; -} - -static void window_remove_views(Window *window) -{ - while (window->views.count > 0) { - View *view = window->views.ptrs[window->views.count - 1]; - remove_view(view); - } -} - -// NOTE: window->frame isn't removed -void window_free(Window *window) -{ - window_remove_views(window); - free(window->views.ptrs); - window->frame = NULL; - free(window); -} - -// Remove view from view->window and view->buffer->views and free it -size_t remove_view(View *view) -{ - Window *window = view->window; - EditorState *e = window->editor; - if (view == window->prev_view) { - window->prev_view = NULL; - } - if (view == e->view) { - e->view = NULL; - e->buffer = NULL; - } - - size_t idx = ptr_array_idx(&window->views, view); - BUG_ON(idx >= window->views.count); - ptr_array_remove_idx(&window->views, idx); - window->update_tabbar = true; - - Buffer *buffer = view->buffer; - ptr_array_remove(&buffer->views, view); - if (buffer->views.count == 0) { - if (buffer->options.file_history && buffer->abs_filename) { - FileHistory *hist = &e->file_history; - file_history_add(hist, view->cy + 1, view->cx_char + 1, buffer->abs_filename); - } - remove_and_free_buffer(&e->buffers, buffer); - } - - free(view); - return idx; -} - -void window_close_current_view(Window *window) -{ - size_t idx = remove_view(window->view); - if (window->prev_view) { - window->view = window->prev_view; - window->prev_view = NULL; - return; - } - if (window->views.count == 0) { - window_open_empty_buffer(window); - } - if (window->views.count == idx) { - idx--; - } - window->view = window->views.ptrs[idx]; -} - -static void restore_cursor_from_history(const FileHistory *hist, View *view) -{ - unsigned long row, col; - if (file_history_find(hist, view->buffer->abs_filename, &row, &col)) { - move_to_filepos(view, row, col); - } -} - -void set_view(View *view) -{ - EditorState *e = view->window->editor; - if (e->view == view) { - return; - } - - // Forget previous view when changing view using any other command but open - if (e->window) { - e->window->prev_view = NULL; - } - - e->view = view; - e->buffer = view->buffer; - e->window = view->window; - e->window->view = view; - - if (!view->buffer->setup) { - buffer_setup(e, view->buffer); - if (view->buffer->options.file_history && view->buffer->abs_filename) { - restore_cursor_from_history(&e->file_history, view); - } - } - - // view.cursor can be invalid if same buffer was modified from another view - if (view->restore_cursor) { - view->cursor.blk = BLOCK(view->buffer->blocks.next); - block_iter_goto_offset(&view->cursor, view->saved_cursor_offset); - view->restore_cursor = false; - view->saved_cursor_offset = 0; - } - - // Save cursor states of views sharing same buffer - for (size_t i = 0, n = view->buffer->views.count; i < n; i++) { - View *other = view->buffer->views.ptrs[i]; - if (other != view) { - other->saved_cursor_offset = block_iter_get_offset(&other->cursor); - other->restore_cursor = true; - } - } -} - -View *window_open_new_file(Window *window) -{ - View *prev = window->view; - View *view = window_open_empty_buffer(window); - set_view(view); - window->prev_view = prev; - return view; -} - -static bool buffer_is_empty_and_untouched(const Buffer *b) -{ - return !b->abs_filename && b->change_head.nr_prev == 0 && !b->display_filename; -} - -// If window contains only one untouched buffer it'll be closed after -// opening another file. This is done because closing the last buffer -// causes an empty buffer to be opened (windows must contain at least -// one buffer). -static bool is_useless_empty_view(const View *v) -{ - return v && v->window->views.count == 1 && buffer_is_empty_and_untouched(v->buffer); -} - -View *window_open_file(Window *window, const char *filename, const Encoding *encoding) -{ - View *prev = window->view; - bool useless = is_useless_empty_view(prev); - View *view = window_open_buffer(window, filename, false, encoding); - if (view) { - set_view(view); - if (useless) { - remove_view(prev); - } else { - window->prev_view = prev; - } - } - return view; -} - -// Open multiple files in window and return the first opened View -View *window_open_files(Window *window, char **filenames, const Encoding *encoding) -{ - View *empty = window->view; - bool useless = is_useless_empty_view(empty); - View *first = NULL; - - for (size_t i = 0; filenames[i]; i++) { - View *view = window_open_buffer(window, filenames[i], false, encoding); - if (view && !first) { - set_view(view); - first = view; - } - } - - if (useless && window->view != empty) { - remove_view(empty); - } - - return first; -} - -void mark_buffer_tabbars_changed(Buffer *buffer) -{ - for (size_t i = 0, n = buffer->views.count; i < n; i++) { - View *view = buffer->views.ptrs[i]; - view->window->update_tabbar = true; - } -} - -static int line_numbers_width(const Window *window, const GlobalOptions *options) -{ - if (!options->show_line_numbers || !window->view) { - return 0; - } - size_t width = size_str_width(window->view->buffer->nl) + 1; - return MAX(width, LINE_NUMBERS_MIN_WIDTH); -} - -static int edit_x_offset(const Window *window, const GlobalOptions *options) -{ - return line_numbers_width(window, options); -} - -static int edit_y_offset(const GlobalOptions *options) -{ - return options->tab_bar ? 1 : 0; -} - -static void set_edit_size(Window *window, const GlobalOptions *options) -{ - int xo = edit_x_offset(window, options); - int yo = edit_y_offset(options); - - window->edit_w = window->w - xo; - window->edit_h = window->h - yo - 1; // statusline - window->edit_x = window->x + xo; -} - -void calculate_line_numbers(Window *window) -{ - const GlobalOptions *options = &window->editor->options; - int w = line_numbers_width(window, options); - if (w != window->line_numbers.width) { - window->line_numbers.width = w; - window->line_numbers.first = 0; - window->line_numbers.last = 0; - mark_all_lines_changed(window->view->buffer); - } - set_edit_size(window, options); -} - -void set_window_coordinates(Window *window, int x, int y) -{ - const GlobalOptions *options = &window->editor->options; - window->x = x; - window->y = y; - window->edit_x = x + edit_x_offset(window, options); - window->edit_y = y + edit_y_offset(options); -} - -void set_window_size(Window *window, int w, int h) -{ - window->w = w; - window->h = h; - calculate_line_numbers(window); -} - -int window_get_scroll_margin(const Window *window, unsigned int scroll_margin) -{ - int max = (window->edit_h - 1) / 2; - BUG_ON(max < 0); - return MIN(max, scroll_margin); -} - -void frame_for_each_window(const Frame *frame, void (*func)(Window*, void*), void *data) -{ - if (frame->window) { - func(frame->window, data); - return; - } - for (size_t i = 0, n = frame->frames.count; i < n; i++) { - frame_for_each_window(frame->frames.ptrs[i], func, data); - } -} - -typedef struct { - const Window *const target; // Window to search for (set at init.) - Window *first; // Window passed in first callback invocation - Window *last; // Window passed in last callback invocation - Window *prev; // Window immediately before target (if any) - Window *next; // Window immediately after target (if any) - bool found; // Set to true when target is found -} WindowCallbackData; - -static void find_prev_and_next(Window *window, void *ud) -{ - WindowCallbackData *data = ud; - data->last = window; - if (data->found) { - if (!data->next) { - data->next = window; - } - return; - } - if (!data->first) { - data->first = window; - } - if (window == data->target) { - data->found = true; - return; - } - data->prev = window; -} - -Window *prev_window(Window *window) -{ - WindowCallbackData data = {.target = window}; - frame_for_each_window(window->editor->root_frame, find_prev_and_next, &data); - BUG_ON(!data.found); - return data.prev ? data.prev : data.last; -} - -Window *next_window(Window *window) -{ - WindowCallbackData data = {.target = window}; - frame_for_each_window(window->editor->root_frame, find_prev_and_next, &data); - BUG_ON(!data.found); - return data.next ? data.next : data.first; -} - -void window_close(Window *window) -{ - EditorState *e = window->editor; - if (!window->frame->parent) { - // Don't close last window - window_remove_views(window); - set_view(window_open_empty_buffer(window)); - return; - } - - WindowCallbackData data = {.target = window}; - frame_for_each_window(e->root_frame, find_prev_and_next, &data); - BUG_ON(!data.found); - Window *next_or_prev = data.next ? data.next : data.prev; - BUG_ON(!next_or_prev); - - remove_frame(e, window->frame); - e->window = NULL; - set_view(next_or_prev->view); - - mark_everything_changed(e); - debug_frame(e->root_frame); -} diff --git a/examples/dte/window.h b/examples/dte/window.h deleted file mode 100644 index 57378b1..0000000 --- a/examples/dte/window.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef WINDOW_H -#define WINDOW_H - -#include <stdbool.h> -#include <stddef.h> -#include "buffer.h" -#include "encoding.h" -#include "frame.h" -#include "util/macros.h" -#include "util/ptr-array.h" -#include "view.h" - -enum { - // Minimum width of line numbers bar (including padding) - LINE_NUMBERS_MIN_WIDTH = 5 -}; - -// A sub-division of the screen, similar to a window in a tiling window -// manager. There can be multiple Views associated with each Window, but -// only one is visible at a time. Each tab displayed in the tab bar -// corresponds to a View and the editable text area corresponds to the -// Buffer of the *current* View (Window::view::buffer). -typedef struct Window { - struct EditorState *editor; - PointerArray views; - Frame *frame; - View *view; // Current view - View *prev_view; // Previous view, if set - int x, y; // Coordinates for top left of window - int w, h; // Width and height of window (including tabbar and status) - int edit_x, edit_y; // Top left of editable area - int edit_w, edit_h; // Width and height of editable area - size_t first_tab_idx; - bool update_tabbar; - struct { - int width; - long first; - long last; - } line_numbers; -} Window; - -struct EditorState; - -Window *new_window(struct EditorState *e) NONNULL_ARGS_AND_RETURN; -View *window_add_buffer(Window *window, Buffer *buffer); -View *window_open_empty_buffer(Window *window); -View *window_open_buffer(Window *window, const char *filename, bool must_exist, const Encoding *encoding); -View *window_get_view(Window *window, Buffer *buffer); -View *window_find_view(Window *window, Buffer *buffer); -View *window_find_unclosable_view(Window *window); -void window_free(Window *window); -size_t remove_view(View *view); -void window_close(Window *window); -void window_close_current_view(Window *window); -void set_view(View *view); -View *window_open_new_file(Window *window); -View *window_open_file(Window *window, const char *filename, const Encoding *encoding); -View *window_open_files(Window *window, char **filenames, const Encoding *encoding); -void mark_buffer_tabbars_changed(Buffer *buffer); -void calculate_line_numbers(Window *window); -void set_window_coordinates(Window *window, int x, int y); -void set_window_size(Window *window, int w, int h); -int window_get_scroll_margin(const Window *window, unsigned int scroll_margin); -void frame_for_each_window(const Frame *frame, void (*func)(Window*, void*), void *data); -Window *prev_window(Window *window); -Window *next_window(Window *window); - -#endif |
