summaryrefslogtreecommitdiff
path: root/examples/dte
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2026-01-21 22:52:54 +0100
committerMitja Felicijan <mitja.felicijan@gmail.com>2026-01-21 22:52:54 +0100
commitdcacc00e3750300617ba6e16eb346713f91a783a (patch)
tree38e2d4fb5ed9d119711d4295c6eda4b014af73fd /examples/dte
parent58dac10aeb8f5a041c46bddbeaf4c7966a99b998 (diff)
downloadcrep-dcacc00e3750300617ba6e16eb346713f91a783a.tar.gz
Remove testing data
Diffstat (limited to 'examples/dte')
-rw-r--r--examples/dte/README.md19
-rw-r--r--examples/dte/bind.c115
-rw-r--r--examples/dte/bind.h20
-rw-r--r--examples/dte/block-iter.c343
-rw-r--r--examples/dte/block-iter.h78
-rw-r--r--examples/dte/block.c19
-rw-r--r--examples/dte/block.h39
-rw-r--r--examples/dte/bookmark.c103
-rw-r--r--examples/dte/bookmark.h24
-rw-r--r--examples/dte/buffer.c480
-rw-r--r--examples/dte/buffer.h103
-rw-r--r--examples/dte/change.c417
-rw-r--r--examples/dte/change.h39
-rw-r--r--examples/dte/cmdline.c540
-rw-r--r--examples/dte/cmdline.h40
-rw-r--r--examples/dte/commands.c2594
-rw-r--r--examples/dte/commands.h22
-rw-r--r--examples/dte/compat.c35
-rw-r--r--examples/dte/compat.h8
-rw-r--r--examples/dte/compiler.c151
-rw-r--r--examples/dte/compiler.h49
-rw-r--r--examples/dte/completion.c879
-rw-r--r--examples/dte/completion.h21
-rw-r--r--examples/dte/config.c185
-rw-r--r--examples/dte/config.h42
-rw-r--r--examples/dte/convert.c581
-rw-r--r--examples/dte/convert.h24
-rw-r--r--examples/dte/copy.c74
-rw-r--r--examples/dte/copy.h26
-rw-r--r--examples/dte/ctags.c157
-rw-r--r--examples/dte/ctags.h31
-rw-r--r--examples/dte/edit.c394
-rw-r--r--examples/dte/edit.h13
-rw-r--r--examples/dte/editor.c321
-rw-r--r--examples/dte/editor.h121
-rw-r--r--examples/dte/encoding.c132
-rw-r--r--examples/dte/encoding.h46
-rw-r--r--examples/dte/error.c95
-rw-r--r--examples/dte/error.h15
-rw-r--r--examples/dte/exec.c366
-rw-r--r--examples/dte/exec.h37
-rw-r--r--examples/dte/file-history.c153
-rw-r--r--examples/dte/file-history.h29
-rw-r--r--examples/dte/file-option.c193
-rw-r--r--examples/dte/file-option.h32
-rw-r--r--examples/dte/filetype.c333
-rw-r--r--examples/dte/filetype.h35
-rw-r--r--examples/dte/frame.c496
-rw-r--r--examples/dte/frame.h48
-rw-r--r--examples/dte/history.c146
-rw-r--r--examples/dte/history.h35
-rw-r--r--examples/dte/indent.c193
-rw-r--r--examples/dte/indent.h58
-rw-r--r--examples/dte/load-save.c505
-rw-r--r--examples/dte/load-save.h14
-rw-r--r--examples/dte/lock.c201
-rw-r--r--examples/dte/lock.h12
-rw-r--r--examples/dte/main.c575
-rw-r--r--examples/dte/misc.c764
-rw-r--r--examples/dte/misc.h22
-rw-r--r--examples/dte/mode.c74
-rw-r--r--examples/dte/mode.h11
-rw-r--r--examples/dte/move.c311
-rw-r--r--examples/dte/move.h31
-rw-r--r--examples/dte/msg.c139
-rw-r--r--examples/dte/msg.h30
-rw-r--r--examples/dte/options.c987
-rw-r--r--examples/dte/options.h114
-rw-r--r--examples/dte/regexp.c151
-rw-r--r--examples/dte/regexp.h85
-rw-r--r--examples/dte/replace.c256
-rw-r--r--examples/dte/replace.h18
-rw-r--r--examples/dte/screen-cmdline.c91
-rw-r--r--examples/dte/screen-prompt.c134
-rw-r--r--examples/dte/screen-status.c46
-rw-r--r--examples/dte/screen-tabbar.c176
-rw-r--r--examples/dte/screen-view.c427
-rw-r--r--examples/dte/screen-window.c130
-rw-r--r--examples/dte/screen.c211
-rw-r--r--examples/dte/screen.h59
-rw-r--r--examples/dte/search.c244
-rw-r--r--examples/dte/search.h34
-rw-r--r--examples/dte/selection.c110
-rw-r--r--examples/dte/selection.h22
-rw-r--r--examples/dte/shift.c147
-rw-r--r--examples/dte/shift.h8
-rw-r--r--examples/dte/show.c558
-rw-r--r--examples/dte/show.h27
-rw-r--r--examples/dte/signals.c169
-rw-r--r--examples/dte/signals.h11
-rw-r--r--examples/dte/spawn.c396
-rw-r--r--examples/dte/spawn.h37
-rw-r--r--examples/dte/status.c337
-rw-r--r--examples/dte/status.h20
-rw-r--r--examples/dte/tag.c322
-rw-r--r--examples/dte/tag.h23
-rw-r--r--examples/dte/vars.c113
-rw-r--r--examples/dte/vars.h11
-rw-r--r--examples/dte/view.c178
-rw-r--r--examples/dte/view.h62
-rw-r--r--examples/dte/window.c507
-rw-r--r--examples/dte/window.h68
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, &times[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(&regex, pattern, REG_NEWLINE)) {
- return false;
- }
-
- BlockIter bi = block_iter(view->buffer);
- bool found = do_search_fwd(view, &regex, &bi, false);
- regfree(&regex);
-
- 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