summaryrefslogtreecommitdiff
path: root/examples/dte/editor.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/dte/editor.c')
-rw-r--r--examples/dte/editor.c321
1 files changed, 321 insertions, 0 deletions
diff --git a/examples/dte/editor.c b/examples/dte/editor.c
new file mode 100644
index 0000000..aa88d6e
--- /dev/null
+++ b/examples/dte/editor.c
@@ -0,0 +1,321 @@
1#include "compat.h"
2#include <errno.h>
3#include <langinfo.h>
4#include <locale.h>
5#include <stdint.h>
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9#include <unistd.h>
10#include "editor.h"
11#include "bind.h"
12#include "bookmark.h"
13#include "command/macro.h"
14#include "commands.h"
15#include "compiler.h"
16#include "encoding.h"
17#include "error.h"
18#include "file-option.h"
19#include "filetype.h"
20#include "lock.h"
21#include "mode.h"
22#include "regexp.h"
23#include "screen.h"
24#include "search.h"
25#include "signals.h"
26#include "syntax/syntax.h"
27#include "tag.h"
28#include "terminal/input.h"
29#include "terminal/mode.h"
30#include "terminal/output.h"
31#include "terminal/style.h"
32#include "util/ascii.h"
33#include "util/debug.h"
34#include "util/exitcode.h"
35#include "util/intern.h"
36#include "util/log.h"
37#include "util/utf8.h"
38#include "util/xmalloc.h"
39#include "util/xstdio.h"
40#include "window.h"
41#include "../build/version.h"
42
43static void set_and_check_locale(void)
44{
45 const char *default_locale = setlocale(LC_CTYPE, "");
46 if (likely(default_locale)) {
47 const char *codeset = nl_langinfo(CODESET);
48 LOG_INFO("locale: %s (codeset: %s)", default_locale, codeset);
49 if (likely(lookup_encoding(codeset) == UTF8)) {
50 return;
51 }
52 } else {
53 LOG_ERROR("failed to set default locale");
54 }
55
56 static const char fallbacks[][12] = {"C.UTF-8", "en_US.UTF-8"};
57 const char *fallback = NULL;
58 for (size_t i = 0; i < ARRAYLEN(fallbacks) && !fallback; i++) {
59 fallback = setlocale(LC_CTYPE, fallbacks[i]);
60 }
61 if (fallback) {
62 LOG_INFO("using fallback locale for LC_CTYPE: %s", fallback);
63 return;
64 }
65
66 LOG_ERROR("no UTF-8 fallback locales found");
67 fputs("setlocale() failed\n", stderr);
68 exit(EX_CONFIG);
69}
70
71EditorState *init_editor_state(void)
72{
73 EditorState *e = xnew(EditorState, 1);
74 *e = (EditorState) {
75 .status = EDITOR_INITIALIZING,
76 .input_mode = INPUT_NORMAL,
77 .version = VERSION,
78 .command_history = {
79 .max_entries = 512,
80 },
81 .search_history = {
82 .max_entries = 128,
83 },
84 .cursor_styles = {
85 [CURSOR_MODE_DEFAULT] = {.type = CURSOR_DEFAULT, .color = COLOR_DEFAULT},
86 [CURSOR_MODE_INSERT] = {.type = CURSOR_KEEP, .color = COLOR_KEEP},
87 [CURSOR_MODE_OVERWRITE] = {.type = CURSOR_KEEP, .color = COLOR_KEEP},
88 [CURSOR_MODE_CMDLINE] = {.type = CURSOR_KEEP, .color = COLOR_KEEP},
89 },
90 .modes = {
91 [INPUT_NORMAL] = {.cmds = &normal_commands},
92 [INPUT_COMMAND] = {.cmds = &cmd_mode_commands},
93 [INPUT_SEARCH] = {.cmds = &search_mode_commands},
94 },
95 .options = {
96 .auto_indent = true,
97 .detect_indent = 0,
98 .editorconfig = false,
99 .emulate_tab = false,
100 .expand_tab = false,
101 .file_history = true,
102 .indent_width = 8,
103 .overwrite = false,
104 .save_unmodified = SAVE_FULL,
105 .syntax = true,
106 .tab_width = 8,
107 .text_width = 72,
108 .ws_error = WSE_SPECIAL,
109
110 // Global-only options
111 .case_sensitive_search = CSS_TRUE,
112 .crlf_newlines = false,
113 .display_special = false,
114 .esc_timeout = 100,
115 .filesize_limit = 250,
116 .lock_files = true,
117 .optimize_true_color = true,
118 .scroll_margin = 0,
119 .select_cursor_char = true,
120 .set_window_title = false,
121 .show_line_numbers = false,
122 .statusline_left = str_intern(" %f%s%m%s%r%s%M"),
123 .statusline_right = str_intern(" %y,%X %u %o %E%s%b%s%n %t %p "),
124 .tab_bar = true,
125 .utf8_bom = false,
126 }
127 };
128
129 sanity_check_global_options(&e->options);
130
131 for (size_t i = 0; i < ARRAYLEN(e->modes); i++) {
132 const CommandSet *cmds = e->modes[i].cmds;
133 BUG_ON(!cmds);
134 BUG_ON(!cmds->lookup);
135 }
136
137 const char *home = getenv("HOME");
138 const char *dte_home = getenv("DTE_HOME");
139 e->home_dir = strview_intern(home ? home : "");
140 if (dte_home) {
141 e->user_config_dir = xstrdup(dte_home);
142 } else {
143 e->user_config_dir = xasprintf("%s/.dte", e->home_dir.data);
144 }
145
146 LOG_INFO("dte version: " VERSION);
147 LOG_INFO("features:%s", feature_string);
148
149 pid_t pid = getpid();
150 bool leader = pid == getsid(0);
151 e->session_leader = leader;
152 LOG_INFO("pid: %jd%s", (intmax_t)pid, leader ? " (session leader)" : "");
153
154 pid_t pgid = getpgrp();
155 if (pgid != pid) {
156 LOG_INFO("pgid: %jd", (intmax_t)pgid);
157 }
158
159 set_and_check_locale();
160 init_file_locks_context(e->user_config_dir, pid);
161
162 // Allow child processes to detect that they're running under dte
163 if (unlikely(setenv("DTE_VERSION", VERSION, true) != 0)) {
164 fatal_error("setenv", errno);
165 }
166
167 RegexpWordBoundaryTokens *wb = &e->regexp_word_tokens;
168 if (regexp_init_word_boundary_tokens(wb)) {
169 LOG_INFO("regex word boundary tokens detected: %s %s", wb->start, wb->end);
170 } else {
171 LOG_WARNING("no regex word boundary tokens detected");
172 }
173
174 term_input_init(&e->terminal.ibuf);
175 term_output_init(&e->terminal.obuf);
176 hashmap_init(&e->aliases, 32);
177 intmap_init(&e->modes[INPUT_NORMAL].key_bindings, 150);
178 intmap_init(&e->modes[INPUT_COMMAND].key_bindings, 40);
179 intmap_init(&e->modes[INPUT_SEARCH].key_bindings, 40);
180 return e;
181}
182
183void free_editor_state(EditorState *e)
184{
185 free(e->clipboard.buf);
186 free_file_options(&e->file_options);
187 free_filetypes(&e->filetypes);
188 free_syntaxes(&e->syntaxes);
189 file_history_free(&e->file_history);
190 history_free(&e->command_history);
191 history_free(&e->search_history);
192 search_free_regexp(&e->search);
193 term_output_free(&e->terminal.obuf);
194 term_input_free(&e->terminal.ibuf);
195 cmdline_free(&e->cmdline);
196 clear_messages(&e->messages);
197 free_macro(&e->macro);
198 tag_file_free(&e->tagfile);
199
200 ptr_array_free_cb(&e->bookmarks, FREE_FUNC(file_location_free));
201 ptr_array_free_cb(&e->buffers, FREE_FUNC(free_buffer));
202 hashmap_free(&e->compilers, FREE_FUNC(free_compiler));
203 hashmap_free(&e->colors.other, free);
204 hashmap_free(&e->aliases, free);
205
206 for (size_t i = 0; i < ARRAYLEN(e->modes); i++) {
207 free_bindings(&e->modes[i].key_bindings);
208 }
209
210 free_interned_strings();
211 free_interned_regexps();
212
213 // TODO: intern this (so that it's freed by free_intern_pool())
214 free((void*)e->user_config_dir);
215
216 free(e);
217}
218
219static void sanity_check(const View *view)
220{
221#if DEBUG >= 1
222 const Block *blk;
223 block_for_each(blk, &view->buffer->blocks) {
224 if (blk == view->cursor.blk) {
225 BUG_ON(view->cursor.offset > view->cursor.blk->size);
226 return;
227 }
228 }
229 BUG("cursor not seen");
230#else
231 (void)view;
232#endif
233}
234
235void any_key(Terminal *term, unsigned int esc_timeout)
236{
237 KeyCode key;
238 xfputs("Press any key to continue\r\n", stderr);
239 while ((key = term_read_key(term, esc_timeout)) == KEY_NONE) {
240 ;
241 }
242 bool bracketed_paste = key == KEY_BRACKETED_PASTE;
243 if (bracketed_paste || key == KEY_DETECTED_PASTE) {
244 term_discard_paste(&term->ibuf, bracketed_paste);
245 }
246}
247
248NOINLINE
249void ui_resize(EditorState *e)
250{
251 if (e->status == EDITOR_INITIALIZING) {
252 return;
253 }
254 resized = 0;
255 update_screen_size(&e->terminal, e->root_frame);
256 normal_update(e);
257}
258
259void ui_start(EditorState *e)
260{
261 if (e->status == EDITOR_INITIALIZING) {
262 return;
263 }
264
265 // Note: the order of these calls is important - Kitty saves/restores
266 // some terminal state when switching buffers, so switching to the
267 // alternate screen buffer needs to happen before modes are enabled
268 term_use_alt_screen_buffer(&e->terminal);
269 term_enable_private_modes(&e->terminal);
270
271 ui_resize(e);
272}
273
274void ui_end(EditorState *e)
275{
276 if (e->status == EDITOR_INITIALIZING) {
277 return;
278 }
279 Terminal *term = &e->terminal;
280 TermOutputBuffer *obuf = &term->obuf;
281 term_clear_screen(obuf);
282 term_move_cursor(obuf, 0, term->height - 1);
283 term_restore_cursor_style(term);
284 term_show_cursor(term);
285 term_restore_private_modes(term);
286 term_use_normal_screen_buffer(term);
287 term_end_sync_update(term);
288 term_output_flush(obuf);
289 term_cooked();
290}
291
292int main_loop(EditorState *e)
293{
294 while (e->status == EDITOR_RUNNING) {
295 if (unlikely(resized)) {
296 LOG_INFO("SIGWINCH received");
297 ui_resize(e);
298 }
299
300 KeyCode key = term_read_key(&e->terminal, e->options.esc_timeout);
301 if (unlikely(key == KEY_NONE)) {
302 continue;
303 }
304
305 const ScreenState s = {
306 .is_modified = buffer_modified(e->buffer),
307 .id = e->buffer->id,
308 .cy = e->view->cy,
309 .vx = e->view->vx,
310 .vy = e->view->vy
311 };
312
313 clear_error();
314 handle_input(e, key);
315 sanity_check(e->view);
316 update_screen(e, &s);
317 }
318
319 BUG_ON(e->status < 0 || e->status > EDITOR_EXIT_MAX);
320 return e->status;
321}