1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
|
#define TED_PATH_MAX 256
#define TED_UNTITLED "Untitled" // what to call untitled buffers
#define TED_CFG "ted.cfg" // config filename
#define TEXT_SIZE_MIN 6
#define TEXT_SIZE_MAX 70
// max number of LSPs running at once
#define TED_LSP_MAX 200
// these all say "CPP" but really they're C/C++
enum {
SYNTAX_STATE_CPP_MULTI_LINE_COMMENT = 0x1u, // are we in a multi-line comment? (delineated by /* */)
SYNTAX_STATE_CPP_SINGLE_LINE_COMMENT = 0x2u, // if you add a \ to the end of a single-line comment, it is continued to the next line.
SYNTAX_STATE_CPP_PREPROCESSOR = 0x4u, // similar to above
SYNTAX_STATE_CPP_STRING = 0x8u,
SYNTAX_STATE_CPP_RAW_STRING = 0x10u,
};
enum {
SYNTAX_STATE_RUST_COMMENT_DEPTH_MASK = 0xfu, // in rust, /* */ comments can nest.
SYNTAX_STATE_RUST_COMMENT_DEPTH_MUL = 0x1u,
SYNTAX_STATE_RUST_COMMENT_DEPTH_BITS = 4, // number of bits we allocate for the comment depth.
SYNTAX_STATE_RUST_STRING = 0x10u,
SYNTAX_STATE_RUST_STRING_IS_RAW = 0x20u,
};
enum {
SYNTAX_STATE_PYTHON_STRING = 0x01u, // multiline strings (''' and """)
SYNTAX_STATE_PYTHON_STRING_DBL_QUOTED = 0x02u, // is this a """ string, as opposed to a ''' string?
};
enum {
SYNTAX_STATE_TEX_DOLLAR = 0x01u, // inside math $ ... $
SYNTAX_STATE_TEX_DOLLARDOLLAR = 0x02u, // inside math $$ ... $$
SYNTAX_STATE_TEX_VERBATIM = 0x04u, // inside \begin{verbatim} ... \end{verbatim}
};
enum {
SYNTAX_STATE_MARKDOWN_CODE = 0x01u, // inside ``` ``` code section
};
enum {
SYNTAX_STATE_HTML_COMMENT = 0x01u
};
enum {
SYNTAX_STATE_JAVASCRIPT_TEMPLATE_STRING = 0x01u,
SYNTAX_STATE_JAVASCRIPT_MULTILINE_COMMENT = 0x02u,
};
enum {
SYNTAX_STATE_JAVA_MULTILINE_COMMENT = 0x01u
};
enum {
SYNTAX_STATE_GO_RAW_STRING = 0x01u, // backtick-enclosed string
SYNTAX_STATE_GO_MULTILINE_COMMENT = 0x02u
};
enum {
SYNTAX_STATE_TED_CFG_STRING = 0x01u,
};
typedef u8 SyntaxState;
ENUM_U8 {
SYNTAX_NORMAL,
SYNTAX_KEYWORD,
SYNTAX_BUILTIN,
SYNTAX_COMMENT,
SYNTAX_PREPROCESSOR,
SYNTAX_STRING,
SYNTAX_CHARACTER,
SYNTAX_CONSTANT,
} ENUM_U8_END(SyntaxCharType);
#define SYNTAX_MATH SYNTAX_STRING // for tex
#define SYNTAX_CODE SYNTAX_PREPROCESSOR // for markdown
#define SYNTAX_LINK SYNTAX_CONSTANT // for markdown
#define SCANCODE_MOUSE_X1 (SDL_NUM_SCANCODES)
#define SCANCODE_MOUSE_X2 (SDL_NUM_SCANCODES+1)
#define SCANCODE_COUNT (SDL_NUM_SCANCODES+2)
// a "key combo" is some subset of {control, shift, alt} + some key.
#define KEY_COMBO_COUNT (SCANCODE_COUNT << 3)
#define KEY_MODIFIER_CTRL_BIT 0
#define KEY_MODIFIER_SHIFT_BIT 1
#define KEY_MODIFIER_ALT_BIT 2
#define KEY_MODIFIER_CTRL ((u32)1<<KEY_MODIFIER_CTRL_BIT)
#define KEY_MODIFIER_SHIFT ((u32)1<<KEY_MODIFIER_SHIFT_BIT)
#define KEY_MODIFIER_ALT ((u32)1<<KEY_MODIFIER_ALT_BIT)
// ctrl+alt+c is encoded as SDL_SCANCODE_C << 3 | KEY_MODIFIER_CTRL | KEY_MODIFIER_ALT
typedef struct KeyAction {
u32 line_number; // config line number where this was set
Command command; // this will be 0 (COMMAND_UNKNOWN) if there's no action for the key
i64 argument;
} KeyAction;
// a SettingsContext is a context where a specific set of settings are applied.
// this corresponds to [PATH//LANGUAGE.(section)] in config files
typedef struct {
Language language; // these settings apply to this language.
char *path; // these settings apply to all paths which start with this string, or all paths if path=NULL
} SettingsContext;
// need to use refcounting for this because of Settings.
// => we copy parent settings to children
// e.g.
// [core]
// bg-texture = "blablabla.png"
// [Javascript.core]
// some random shit
// the main Settings' bg_texture will get copied to javascript's Settings,
// so we need to be extra careful about when we delete textures.
typedef struct {
u32 ref_count;
u32 texture;
} GlRcTexture;
// shader-array-buffer combo.
typedef struct {
u32 ref_count;
u32 shader;
u32 array;
u32 buffer;
} GlRcSAB;
typedef struct {
// NOTE: to add more options to ted, add fields here,
// and change the options_<type> global constant in config.c
SettingsContext context;
float cursor_blink_time_on, cursor_blink_time_off;
u32 colors[COLOR_COUNT];
u32 max_file_size;
u32 max_file_size_view_only;
u16 text_size;
u16 max_menu_width;
u16 error_display_time;
bool auto_indent;
bool auto_add_newline;
bool syntax_highlighting;
bool line_numbers;
bool auto_reload;
bool auto_reload_config;
bool restore_session;
bool regenerate_tags_if_not_found;
bool indent_with_spaces;
bool trigger_characters;
bool identifier_trigger_characters;
bool signature_help_enabled;
bool lsp_enabled;
bool hover_enabled;
u8 tab_width;
u8 cursor_width;
u8 undo_save_time;
u8 border_thickness;
u8 padding;
u8 scrolloff;
u8 tags_max_depth;
GlRcSAB *bg_shader;
GlRcTexture *bg_texture;
char bg_shader_text[4096];
char bg_shader_image[TED_PATH_MAX];
char root_identifiers[4096];
char lsp[512];
char build_default_command[256];
// [i] = comma-separated string of file extensions for language i, or NULL for none
char *language_extensions[LANG_COUNT];
KeyAction key_actions[KEY_COMBO_COUNT];
} Settings;
// a position in the buffer
typedef struct {
u32 line;
u32 index; // index of character in line (not the same as column, since a tab is settings->tab_width columns)
} BufferPos;
typedef struct {
SyntaxState syntax;
u32 len;
char32_t *str;
} Line;
typedef enum {
SECTION_NONE,
SECTION_CORE,
SECTION_KEYBOARD,
SECTION_COLORS,
SECTION_EXTENSIONS
} ConfigSection;
// this structure is used temporarily when loading settings
// it's needed because we want more specific contexts to be dealt with last.
typedef struct {
int index; // index in order of which part was read first.
SettingsContext context;
ConfigSection section;
char *file;
u32 line;
char *text;
u32 settings; // index into ted->all_settings. only used in config_parse
} ConfigPart;
// this refers to replacing prev_len characters (found in prev_text) at pos with new_len characters
typedef struct {
bool chain; // should this + the next edit be treated as one?
BufferPos pos;
u32 new_len;
u32 prev_len;
char32_t *prev_text;
double time; // time at start of edit (i.e. the time just before the edit), in seconds since epoch
} BufferEdit;
typedef struct {
char *filename; // NULL if this buffer doesn't correspond to a file (e.g. line buffers)
struct Ted *ted; // we keep a back-pointer to the ted instance so we don't have to pass it in to every buffer function
double scroll_x, scroll_y; // number of characters scrolled in the x/y direction
struct timespec last_write_time; // last write time to filename.
i16 manual_language; // 1 + the language the buffer has been manually set to, or 0 if it hasn't been manually set to anything
BufferPos cursor_pos;
BufferPos selection_pos; // if selection is true, the text between selection_pos and cursor_pos is selected.
bool is_line_buffer; // "line buffers" are buffers which can only have one line of text (used for inputs)
bool selection;
bool store_undo_events; // set to false to disable undo events
// This is set to true whenever a change is made to the buffer, and never set to false by buffer_ functions.
// (Distinct from buffer_unsaved_changes)
bool modified;
bool will_chain_edits;
bool chaining_edits; // are we chaining undo events together?
bool view_only;
bool line_buffer_submitted; // (line buffers only) set to true when submitted. you have to reset it to false.
// If set to true, buffer will be scrolled to the cursor position next frame.
// This is to fix the problem that x1,y1,x2,y2 are not updated until the buffer is rendered.
bool center_cursor_next_frame;
float x1, y1, x2, y2;
u32 nlines;
u32 lines_capacity;
// which LSP this document is open in (this is a LSPID)
u32 lsp_opened_in;
u32 undo_history_write_pos; // where in the undo history was the last write? used by buffer_unsaved_changes
u32 first_line_on_screen, last_line_on_screen; // which lines are on screen? updated when buffer_render is called.
// to cache syntax highlighting properly, it is important to keep track of the
// first and last line modified since last frame.
u32 frame_earliest_line_modified;
u32 frame_latest_line_modified;
Line *lines;
char error[256];
BufferEdit *undo_history; // dynamic array of undo history
BufferEdit *redo_history; // dynamic array of redo history
} TextBuffer;
ENUM_U16 {
MENU_NONE,
MENU_OPEN,
MENU_SAVE_AS,
MENU_WARN_UNSAVED, // warn about unsaved changes
MENU_ASK_RELOAD, // prompt about whether to reload file which has ben changed by another program
MENU_GOTO_DEFINITION,
MENU_GOTO_LINE,
MENU_COMMAND_SELECTOR,
MENU_SHELL, // run a shell command
} ENUM_U16_END(Menu);
typedef struct {
char const *name;
u32 color;
} SelectorEntry;
typedef struct {
SelectorEntry *entries;
u32 n_entries;
Rect bounds;
u32 cursor; // index where the selector thing is
float scroll;
bool enable_cursor;
} Selector;
// file entries for file selectors
typedef struct {
char *name; // just the file name
char *path; // full path
FsType type;
} FileEntry;
typedef struct {
Selector sel;
Rect bounds;
u32 n_entries;
FileEntry *entries;
char cwd[TED_PATH_MAX];
bool create_menu; // this is for creating files, not opening files
} FileSelector;
// a node is a collection of tabs OR a split of two nodes
typedef struct Node {
u16 *tabs; // dynamic array of indices into ted->buffers, or NULL if this is a split
float split_pos; // number from 0 to 1 indicating where the split is.
u16 active_tab; // index of active tab in tabs.
bool split_vertical; // is the split vertical? if false, this split looks like a|b
u16 split_a; // split left/upper half; index into ted->nodes
u16 split_b; // split right/lower half
} Node;
#define TED_MAX_BUFFERS 256
#define TED_MAX_NODES 256
// max tabs per node
#define TED_MAX_TABS 100
// max strings in config file
#define TED_MAX_STRINGS 1000
typedef struct {
BufferPos start;
BufferPos end;
} FindResult;
typedef struct {
char *filename;
BufferPos pos;
u32 build_output_line; // which line in the build output corresponds to this error
} BuildError;
// LSPSymbolKinds are translated to these. this is a much coarser categorization
typedef enum {
SYMBOL_OTHER,
SYMBOL_FUNCTION,
SYMBOL_FIELD,
SYMBOL_TYPE,
SYMBOL_VARIABLE,
SYMBOL_CONSTANT,
SYMBOL_KEYWORD
} SymbolKind;
typedef struct {
char *label;
char *filter;
char *text;
char *detail; // this can be NULL!
char *documentation; // this can be NULL!
bool deprecated;
SymbolKind kind;
} Autocompletion;
enum {
// autocomplete was triggered by :autocomplete command
TRIGGER_INVOKED = 0x12000,
// autocomplete list needs to be updated because more characters were typed
TRIGGER_INCOMPLETE = 0x12001,
};
typedef struct {
bool open; // is the autocomplete window open?
bool waiting_for_lsp;
bool is_list_complete; // should the completions array be updated when more characters are typed?
// what trigger caused the last request for completions:
// either a character code (for trigger characters),
// or one of the TRIGGER_* constants above
uint32_t trigger;
// when we sent the request to the LSP for completions
// (this is used to figure out when we should display "Loading...")
struct timespec lsp_request_time;
Autocompletion *completions; // dynamic array of all completions
u32 *suggested; // dynamic array of completions to be suggested (indices into completions)
BufferPos last_pos; // position of cursor last time completions were generated. if this changes, we need to recompute completions.
i32 cursor; // which completion is currently selected (index into suggested)
i32 scroll;
Rect rect; // rectangle where the autocomplete menu is (needed to avoid interpreting autocomplete clicks as other clicks)
} Autocomplete;
typedef struct {
// displayed normal
char *label_pre;
// displayed bold
char *label_active;
// displayed normal
char *label_post;
} Signature;
#define SIGNATURE_HELP_MAX 5
// "signature help" (LSP) is thing that shows the current parameter, etc.
typedef struct {
// should we resend a signature help request this frame?
bool retrigger;
// if signature_count = 0, signature help is closed
u16 signature_count;
Signature signatures[SIGNATURE_HELP_MAX];
} SignatureHelp;
typedef struct {
// is some hover info being displayed?
bool open;
// text to display
char *text;
// where the hover data is coming from.
// we use this to check if we need to refresh it.
LSPDocumentPosition requested_position;
LSPID requested_lsp;
BufferPos range_start;
BufferPos range_end;
} Hover;
typedef struct {
// LSPID and ID of the last request which was sent out.
// used to process responses in chronological order (= ID order).
// if we got a response for the last request, or no requests have been made,
// last_request_lsp is set to 0.
LSPID last_request_lsp;
u32 last_request_id;
struct timespec last_request_time;
} Definitions;
typedef struct Ted {
struct LSP *lsps[TED_LSP_MAX + 1];
// current time, as of the start of this frame
struct timespec frame_time;
SDL_Window *window;
Font *font_bold;
Font *font;
TextBuffer *active_buffer;
// buffer we are currently drag-to-selecting in, if any
TextBuffer *drag_buffer;
// while a menu or something is open, there is no active buffer. when the menu is closed,
// the old active buffer needs to be restored. that's what this stores.
TextBuffer *prev_active_buffer;
Node *active_node;
Settings *all_settings; // dynamic array of Settings. use Settings.context to figure out which one to use.
Settings *default_settings;
float window_width, window_height;
u32 key_modifier; // which of shift, alt, ctrl are down right now.
v2 mouse_pos;
u32 mouse_state;
u8 nmouse_clicks[4]; // nmouse_clicks[i] = length of mouse_clicks[i]
v2 mouse_clicks[4][32]; // mouse_clicks[SDL_BUTTON_RIGHT], for example, is all the right mouse-clicks that have happened this frame
// number of times mouse was clicked at each position
u8 mouse_click_times[4][32];
u8 nmouse_releases[4];
v2 mouse_releases[4][32];
int scroll_total_x, scroll_total_y; // total amount scrolled in the x and y direction this frame
Menu menu;
FileSelector file_selector;
Selector tag_selector; // for "go to definition of..." menu
Selector command_selector;
TextBuffer line_buffer; // general-purpose line buffer for inputs -- used for menus
TextBuffer find_buffer; // use for "find" term in find/find+replace
TextBuffer replace_buffer; // "replace" for find+replace
TextBuffer build_buffer; // buffer for build output (view only)
TextBuffer argument_buffer; // used for command selector
double error_time; // time error box was opened (in seconds -- see time_get_seconds)
double cursor_error_time; // time which the cursor error animation started (cursor turns red, e.g. when there's no autocomplete suggestion)
bool search_start_cwd; // should start_cwd be searched for files? set to true if the executable isn't "installed"
char start_cwd[TED_PATH_MAX];
bool quit; // if set to true, the window will close next frame. NOTE: this doesn't check for unsaved changes!!
bool find; // is the find or find+replace menu open?
bool replace; // is the find+replace menu open?
bool find_regex, find_case_sensitive; // find options
u32 find_flags; // flags used last time search term was compiled
pcre2_code *find_code;
pcre2_match_data *find_match_data;
FindResult *find_results;
bool find_invalid_pattern; // invalid regex?
Command warn_unsaved; // if non-zero, the user is trying to execute this command, but there are unsaved changes
bool build_shown; // are we showing the build output?
bool building; // is the build process running?
Autocomplete autocomplete;
SignatureHelp signature_help;
Hover hover;
Definitions definitions;
FILE *log;
BuildError *build_errors; // dynamic array of build errors
u32 build_error; // build error we are currently "on"
// used by menus to keep track of the scroll position so we can return to it.
v2d prev_active_buffer_scroll;
SDL_Cursor *cursor_arrow, *cursor_ibeam, *cursor_wait,
*cursor_resize_h, *cursor_resize_v, *cursor_hand, *cursor_move;
SDL_Cursor *cursor; // which cursor to use this frame (NULL for no cursor)
// node containing tab user is dragging around, NULL if user is not dragging a tab
Node *dragging_tab_node;
// index in dragging_tab_node->tabs
u16 dragging_tab_idx;
v2 dragging_tab_origin; // where the tab is being dragged from (i.e. mouse pos at start of drag action)
// if not NULL, points to the node whose split the user is currently resizing.
Node *resizing_split;
char **tag_selector_entries; // an array of all tags (see tag_selector_open)
char **shell_history; // dynamic array of history of commands run with :shell (UTF-8)
u32 shell_history_pos; // for keeping track of where we are in the shell history.
// points to a selector if any is open, otherwise NULL.
Selector *selector_open;
float build_output_height; // what % of the screen the build output takes up
bool resizing_build_output;
double last_save_time; // last time a save command was executed. used for bg-shaders.
Process build_process;
// When we read the stdout from the build process, the tail end of the read could be an
// incomplete UTF-8 code point. This is where we store that "tail end" until more
// data is available. (This is up to 3 bytes, null terminated)
char build_incomplete_codepoint[4];
char **build_queue; // allows execution of multiple commands -- needed for tags generation
char warn_unsaved_names[TED_PATH_MAX]; // comma-separated list of files with unsaved changes (only applicable if warn_unsaved != 0)
char warn_overwrite[TED_PATH_MAX]; // file name user is trying to overwrite
char ask_reload[TED_PATH_MAX]; // file name which we want to reload
char local_data_dir[TED_PATH_MAX];
char global_data_dir[TED_PATH_MAX];
char home[TED_PATH_MAX];
char cwd[TED_PATH_MAX]; // current working directory
char build_dir[TED_PATH_MAX]; // directory where we run the build command
char tags_dir[TED_PATH_MAX]; // where we are reading tags from
bool nodes_used[TED_MAX_NODES];
Node nodes[TED_MAX_NODES];
// NOTE: the buffer at index 0 is reserved as a "null buffer" and should not be used.
bool buffers_used[TED_MAX_BUFFERS];
TextBuffer buffers[TED_MAX_BUFFERS];
// config file strings
u32 nstrings;
char *strings[TED_MAX_STRINGS];
char window_title[256];
char error[512];
char error_shown[512]; // error display in box on screen
} Ted;
char *buffer_contents_utf8_alloc(TextBuffer *buffer);
Command command_from_str(char const *str);
void command_execute(Ted *ted, Command c, i64 argument);
void ted_switch_to_buffer(Ted *ted, TextBuffer *buffer);
// the settings of the active buffer, or the default settings if there is no active buffer
Settings *ted_active_settings(Ted *ted);
Settings *ted_get_settings(Ted *ted, const char *path, Language lang);
void ted_load_configs(Ted *ted, bool reloading);
struct LSP *ted_get_lsp(Ted *ted, const char *path, Language lang);
struct LSP *ted_active_lsp(Ted *ted);
struct LSP *ted_get_lsp_by_id(Ted *ted, u32 id);
static TextBuffer *find_search_buffer(Ted *ted);
// first, we read all config files, then we parse them.
// this is because we want less specific settings (e.g. settings applied
// to all languages instead of one particular language) to be applied first,
// then more specific settings are based off of those.
// EXAMPLE:
// ---config file 1---
// [Javascript.core]
// syntax-highlighting = off
// (inherits tab-width = 4)
// [CSS.core]
// tab-width = 2 (overrides tab-width = 4)
// ---config file 2---
// [core]
// tab-width = 4
void config_read(Ted *ted, ConfigPart **parts, const char *filename);
void config_parse(Ted *ted, ConfigPart **parts);
void config_free(Ted *ted);
char *settings_get_root_dir(Settings *settings, const char *path);
void menu_open(Ted *ted, Menu menu);
void menu_close(Ted *ted);
void find_update(Ted *ted, bool force);
void autocomplete_close(Ted *ted);
void signature_help_retrigger(Ted *ted);
// go to the definition of `name`.
// if `lsp` is NULL, tags will be used.
// Note: the document position is required for LSP requests because of overloading (where the name
// alone isn't sufficient)
void definition_goto(Ted *ted, LSP *lsp, const char *name, LSPDocumentPosition pos);
|