summaryrefslogtreecommitdiff
path: root/examples/dte/exec.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/dte/exec.c')
-rw-r--r--examples/dte/exec.c366
1 files changed, 366 insertions, 0 deletions
diff --git a/examples/dte/exec.c b/examples/dte/exec.c
new file mode 100644
index 0000000..416a2ef
--- /dev/null
+++ b/examples/dte/exec.c
@@ -0,0 +1,366 @@
+#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;
+}