summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2022-12-20 15:43:24 -0500
committerpommicket <pommicket@gmail.com>2022-12-20 15:43:24 -0500
commitd3375f02cfed99ed40c28631e62654af23817729 (patch)
tree575072b13497c3e2f6ba73f3d9feb90214ad6c70
parent9dc4d70511862004661cd5b0c02adc500c35f42e (diff)
start completion requests
-rw-r--r--autocomplete.c294
-rw-r--r--buffer.c8
-rw-r--r--command.c2
-rw-r--r--main.c8
-rw-r--r--menu.c2
-rw-r--r--ted.c2
-rw-r--r--ted.h8
7 files changed, 178 insertions, 146 deletions
diff --git a/autocomplete.c b/autocomplete.c
index 23523bd..ed6687a 100644
--- a/autocomplete.c
+++ b/autocomplete.c
@@ -1,168 +1,186 @@
+#define TAGS_MAX_COMPLETIONS 200 // max # of tag completions to scroll through
+#define AUTOCOMPLETE_NCOMPLETIONS 10 // max # of completions to show at once
-#define AUTOCOMPLETE_NCOMPLETIONS 10 // max # of completions to show
-
-// get the thing to be completed (and what buffer it's in)
-// returns false if this is a read only buffer or something
-// call free() on *startp when you're done with it
-static Status autocomplete_get(Ted *ted, char **startp, TextBuffer **bufferp) {
- TextBuffer *buffer = ted->active_buffer;
- if (buffer && !buffer->view_only && !buffer->is_line_buffer) {
- buffer->selection = false;
- if (is_word(buffer_char_after_cursor(buffer)))
- buffer_cursor_move_right_words(buffer, 1);
- else
- buffer_scroll_to_cursor(buffer);
- char *start = str32_to_utf8_cstr(buffer_word_at_cursor(buffer));
- if (*start) {
- *startp = start;
- *bufferp = buffer;
- return true;
- } else {
- // no word at cursor
- free(start);
- return false;
- }
- } else {
- return false;
- }
+static void autocomplete_clear_completions(Ted *ted) {
+ arr_foreach_ptr(ted->autocompletions, Autocompletion, completion) {
+ free(completion->label);
+ free(completion->text);
+ }
+ arr_clear(ted->autocompletions);
}
// do the actual completion
-static void autocomplete_complete(Ted *ted, char *start, TextBuffer *buffer, char *completion) {
- char *str = completion + strlen(start);
+static void autocomplete_complete(Ted *ted, Autocompletion *completion) {
+ TextBuffer *buffer = ted->active_buffer;
buffer_start_edit_chain(buffer); // don't merge with other edits
- buffer_insert_utf8_at_cursor(buffer, str);
+ if (is_word(buffer_char_before_cursor(buffer)))
+ buffer_backspace_words_at_cursor(buffer, 1); // delete whatever text was already typed
+ buffer_insert_utf8_at_cursor(buffer, completion->text);
buffer_end_edit_chain(buffer);
- ted->autocomplete = false;
+ autocomplete_close(ted);
}
static void autocomplete_select_cursor_completion(Ted *ted) {
- char *start; TextBuffer *buffer;
- if (autocomplete_get(ted, &start, &buffer)) {
- char *completions[AUTOCOMPLETE_NCOMPLETIONS];
- size_t ncompletions = tags_beginning_with(ted, start, completions, arr_count(completions));
+ if (ted->autocomplete) {
+ size_t ncompletions = arr_len(ted->autocompletions);
if (ncompletions) {
i64 cursor = mod_i64(ted->autocomplete_cursor, (i64)ncompletions);
- autocomplete_complete(ted, start, buffer, completions[cursor]);
- for (size_t i = 0; i < ncompletions; ++i)
- free(completions[i]);
+ autocomplete_complete(ted, &ted->autocompletions[cursor]);
+ autocomplete_close(ted);
+ }
+ }
+}
+
+
+void autocomplete_close(Ted *ted) {
+ if (ted->autocomplete) {
+ ted->autocomplete = false;
+ autocomplete_clear_completions(ted);
+ }
+}
+
+static void autocomplete_find_completions(Ted *ted) {
+ TextBuffer *buffer = ted->active_buffer;
+ BufferPos pos = buffer->cursor_pos;
+ if (buffer_pos_eq(pos, ted->autocomplete_pos))
+ return; // no need to update completions.
+ ted->autocomplete_pos = pos;
+
+ LSP *lsp = buffer_lsp(buffer);
+ if (lsp) {
+ LSPRequest request = {
+ .type = LSP_REQUEST_COMPLETION
+ };
+ request.data.completion = (LSPRequestCompletion) {
+ .position = {
+ .document = str_dup(buffer->filename),
+ .pos = buffer_pos_to_lsp(buffer, pos)
+ }
+ };
+ lsp_send_request(lsp, &request);
+ } else {
+ // tag completion
+ autocomplete_clear_completions(ted);
+
+ char *word_at_cursor = str32_to_utf8_cstr(buffer_word_at_cursor(buffer));
+ char **completions = calloc(TAGS_MAX_COMPLETIONS, sizeof *completions);
+ size_t ncompletions = tags_beginning_with(ted, word_at_cursor, completions, TAGS_MAX_COMPLETIONS);
+ free(word_at_cursor);
+
+ arr_set_len(ted->autocompletions, ncompletions);
+
+ for (size_t i = 0; i < ncompletions; ++i) {
+ ted->autocompletions[i].label = completions[i];
+ ted->autocompletions[i].text = str_dup(completions[i]);
}
- free(start);
+ free(completions);
}
}
// open autocomplete, or just do the completion if there's only one suggestion
static void autocomplete_open(Ted *ted) {
- char *start;
- TextBuffer *buffer;
+ if (!ted->active_buffer) return;
+ TextBuffer *buffer = ted->active_buffer;
+ if (!buffer->filename) return;
+ if (buffer->view_only) return;
+
ted->cursor_error_time = 0;
+ ted->autocomplete_pos = (BufferPos){0,0};
ted->autocomplete_cursor = 0;
- if (autocomplete_get(ted, &start, &buffer)) {
- char *completions[2] = {0};
- size_t ncompletions = tags_beginning_with(ted, start, completions, 2);
- switch (ncompletions) {
- case 0:
- ted->cursor_error_time = time_get_seconds();
- break;
- case 1:
- autocomplete_complete(ted, start, buffer, completions[0]);
- break;
- case 2:
- // open autocomplete selection menu
- ted->autocomplete = true;
- break;
- default: assert(0); break;
- }
- free(completions[0]);
- free(completions[1]);
- free(start);
+ ted->autocompletions = NULL;
+ autocomplete_find_completions(ted);
+ switch (arr_len(ted->autocompletions)) {
+ case 0:
+ ted->cursor_error_time = time_get_seconds();
+ autocomplete_close(ted);
+ break;
+ case 1:
+ autocomplete_complete(ted, &ted->autocompletions[0]);
+ // (^ this calls autocomplete_close)
+ break;
+ default:
+ // open autocomplete menu
+ ted->autocomplete = true;
+ break;
}
}
static void autocomplete_frame(Ted *ted) {
+ TextBuffer *buffer = ted->active_buffer;
+ Font *font = ted->font;
+ float char_height = text_font_char_height(font);
+ Settings const *settings = buffer_settings(buffer);
+ u32 const *colors = settings->colors;
+ float const padding = settings->padding;
+
+ autocomplete_find_completions(ted);
+
+ size_t ncompletions = arr_len(ted->autocompletions);
+ if (ncompletions > AUTOCOMPLETE_NCOMPLETIONS)
+ ncompletions = AUTOCOMPLETE_NCOMPLETIONS;
+ float menu_width = 400, menu_height = (float)ncompletions * char_height + 2 * padding;
- char *start;
- TextBuffer *buffer;
- if (autocomplete_get(ted, &start, &buffer)) {
- Font *font = ted->font;
- float char_height = text_font_char_height(font);
- Settings const *settings = buffer_settings(buffer);
- u32 const *colors = settings->colors;
- float const padding = settings->padding;
+ if (ncompletions == 0) {
+ // no completions. close menu.
+ autocomplete_close(ted);
+ return;
+ }
- char *completions[AUTOCOMPLETE_NCOMPLETIONS];
- size_t ncompletions = tags_beginning_with(ted, start, completions, arr_count(completions));
- float menu_width = 400, menu_height = (float)ncompletions * char_height + 2 * padding;
-
- if (ncompletions == 0) {
- // no completions. close menu.
- ted->autocomplete = false;
- return;
- }
-
- ted->autocomplete_cursor = (i32)mod_i64(ted->autocomplete_cursor, (i64)ncompletions);
-
- v2 cursor_pos = buffer_pos_to_pixels(buffer, buffer->cursor_pos);
- bool open_up = cursor_pos.y > 0.5f * (buffer->y1 + buffer->y2); // should the completion menu open upwards?
- bool open_left = cursor_pos.x > 0.5f * (buffer->x1 + buffer->x2);
- float x = cursor_pos.x, start_y = cursor_pos.y;
- if (open_left) x -= menu_width;
- if (open_up)
- start_y -= menu_height;
- else
- start_y += char_height; // put menu below cursor
- {
- Rect menu_rect = rect(V2(x, start_y), V2(menu_width, menu_height));
- gl_geometry_rect(menu_rect, colors[COLOR_MENU_BG]);
- //gl_geometry_rect_border(menu_rect, 1, colors[COLOR_BORDER]);
- ted->autocomplete_rect = menu_rect;
- }
-
- // vertical padding
- start_y += padding;
- menu_height -= 2 * padding;
-
- u16 cursor_entry = (u16)((ted->mouse_pos.y - start_y) / char_height);
- if (cursor_entry < ncompletions) {
- // highlight moused over entry
- Rect r = rect(V2(x, start_y + cursor_entry * char_height), V2(menu_width, char_height));
- gl_geometry_rect(r, colors[COLOR_MENU_HL]);
- ted->cursor = ted->cursor_hand;
- }
- { // highlight cursor entry
- Rect r = rect(V2(x, start_y + (float)ted->autocomplete_cursor * char_height), V2(menu_width, char_height));
- gl_geometry_rect(r, colors[COLOR_MENU_HL]);
- }
-
- for (uint i = 0; i < ted->nmouse_clicks[SDL_BUTTON_LEFT]; ++i) {
- v2 click = ted->mouse_clicks[SDL_BUTTON_LEFT][i];
- if (rect_contains_point(ted->autocomplete_rect, click)) {
- u16 entry = (u16)((click.y - start_y) / char_height);
- if (entry < ncompletions) {
- // entry was clicked on! use this completion.
- autocomplete_complete(ted, start, buffer, completions[entry]);
- }
+ ted->autocomplete_cursor = (i32)mod_i64(ted->autocomplete_cursor, (i64)ncompletions);
+
+ v2 cursor_pos = buffer_pos_to_pixels(buffer, buffer->cursor_pos);
+ bool open_up = cursor_pos.y > 0.5f * (buffer->y1 + buffer->y2); // should the completion menu open upwards?
+ bool open_left = cursor_pos.x > 0.5f * (buffer->x1 + buffer->x2);
+ float x = cursor_pos.x, start_y = cursor_pos.y;
+ if (open_left) x -= menu_width;
+ if (open_up)
+ start_y -= menu_height;
+ else
+ start_y += char_height; // put menu below cursor
+ {
+ Rect menu_rect = rect(V2(x, start_y), V2(menu_width, menu_height));
+ gl_geometry_rect(menu_rect, colors[COLOR_MENU_BG]);
+ //gl_geometry_rect_border(menu_rect, 1, colors[COLOR_BORDER]);
+ ted->autocomplete_rect = menu_rect;
+ }
+
+ // vertical padding
+ start_y += padding;
+ menu_height -= 2 * padding;
+
+ u16 cursor_entry = (u16)((ted->mouse_pos.y - start_y) / char_height);
+ if (cursor_entry < ncompletions) {
+ // highlight moused over entry
+ Rect r = rect(V2(x, start_y + cursor_entry * char_height), V2(menu_width, char_height));
+ gl_geometry_rect(r, colors[COLOR_MENU_HL]);
+ ted->cursor = ted->cursor_hand;
+ }
+ { // highlight cursor entry
+ Rect r = rect(V2(x, start_y + (float)ted->autocomplete_cursor * char_height), V2(menu_width, char_height));
+ gl_geometry_rect(r, colors[COLOR_MENU_HL]);
+ }
+
+ for (uint i = 0; i < ted->nmouse_clicks[SDL_BUTTON_LEFT]; ++i) {
+ v2 click = ted->mouse_clicks[SDL_BUTTON_LEFT][i];
+ if (rect_contains_point(ted->autocomplete_rect, click)) {
+ u16 entry = (u16)((click.y - start_y) / char_height);
+ if (entry < ncompletions) {
+ // entry was clicked on! use this completion.
+ autocomplete_complete(ted, &ted->autocompletions[entry]);
}
}
-
- float y = start_y;
- TextRenderState state = text_render_state_default;
- state.min_x = x + padding; state.min_y = y; state.max_x = x + menu_width - padding; state.max_y = y + menu_height;
- rgba_u32_to_floats(colors[COLOR_TEXT], state.color);
- for (size_t i = 0; i < ncompletions; ++i) {
- state.x = x + padding; state.y = y;
- text_utf8_with_state(font, &state, completions[i]);
- y += char_height;
- }
-
- gl_geometry_draw();
- text_render(font);
-
- for (size_t i = 0; i < ncompletions; ++i)
- free(completions[i]);
+ }
- free(start);
- } else {
- ted->autocomplete = false;
+ float y = start_y;
+ TextRenderState state = text_render_state_default;
+ state.min_x = x + padding; state.min_y = y; state.max_x = x + menu_width - padding; state.max_y = y + menu_height;
+ rgba_u32_to_floats(colors[COLOR_TEXT], state.color);
+ for (size_t i = 0; i < ncompletions; ++i) {
+ state.x = x + padding; state.y = y;
+ text_utf8_with_state(font, &state, ted->autocompletions[i].label);
+ y += char_height;
}
+
+ gl_geometry_draw();
+ text_render(font);
}
diff --git a/buffer.c b/buffer.c
index 6ee76d9..b947b4b 100644
--- a/buffer.c
+++ b/buffer.c
@@ -287,6 +287,8 @@ Language buffer_language(TextBuffer *buffer) {
}
LSP *buffer_lsp(TextBuffer *buffer) {
+ if (!buffer_is_named_file(buffer))
+ return NULL;
return ted_get_lsp(buffer->ted, buffer_language(buffer));
}
@@ -1386,7 +1388,7 @@ static Status buffer_insert_lines(TextBuffer *buffer, u32 where, u32 number) {
}
// LSP uses UTF-16 indices because Microsoft fucking loves UTF-16 and won't let it die
-static LSPPosition buffer_pos_to_lsp(TextBuffer *buffer, BufferPos pos) {
+LSPPosition buffer_pos_to_lsp(TextBuffer *buffer, BufferPos pos) {
LSPPosition lsp_pos = {
.line = pos.line
};
@@ -2111,7 +2113,7 @@ Status buffer_load_file(TextBuffer *buffer, char const *filename) {
buffer_seterr(buffer, "File too big (size: %zu).", file_size);
success = false;
} else {
- u8 *file_contents = buffer_calloc(buffer, 1, file_size);
+ u8 *file_contents = buffer_calloc(buffer, 1, file_size + 1);
lines_capacity = 4;
lines = buffer_calloc(buffer, lines_capacity, sizeof *buffer->lines); // initial lines
nlines = 1;
@@ -2345,7 +2347,7 @@ bool buffer_handle_click(Ted *ted, TextBuffer *buffer, v2 click, u8 times) {
if (rect_contains_point(ted->autocomplete_rect, click))
return false; // don't look at clicks in the autocomplete menu
else
- ted->autocomplete = false; // close autocomplete menu if user clicks outside of it
+ autocomplete_close(ted); // close autocomplete menu if user clicks outside of it
}
if (buffer_pixels_to_pos(buffer, click, &buffer_pos)) {
// user clicked on buffer
diff --git a/command.c b/command.c
index ce447ed..b6f3195 100644
--- a/command.c
+++ b/command.c
@@ -377,7 +377,7 @@ void command_execute(Ted *ted, Command c, i64 argument) {
// dismiss error box
*ted->error_shown = '\0';
} else if (ted->autocomplete) {
- ted->autocomplete = false;
+ autocomplete_close(ted);
} else if (ted->menu) {
menu_escape(ted);
} else {
diff --git a/main.c b/main.c
index ae4b688..06b52c2 100644
--- a/main.c
+++ b/main.c
@@ -1,10 +1,14 @@
-/* o
+/*
@TODO:
+- make sure autocomplete is closed when you switch buffers
+- scroll through completions
+- figure out under which circumstances backspace should close completions
- rename buffer->filename to buffer->path
- rust-analyzer bug reports:
- bad json can give "Unexpected error: client exited without proper shutdown sequence"
- rust-analyzer should wait until cargo metadata/check is done before sending initialize response
FUTURE FEATURES:
+- robust find (results shouldn't move around when you type things)
- configurable max buffer size + max view-only buffer size
- :set-build-command, don't let ../Cargo.toml override ./Makefile
- add numlock as a key modifier
@@ -977,7 +981,7 @@ int main(int argc, char **argv) {
autocomplete_frame(ted);
}
} else {
- ted->autocomplete = false;
+ autocomplete_close(ted);
text_utf8_anchored(font, "Press Ctrl+O to open a file or Ctrl+N to create a new one.",
window_width * 0.5f, window_height * 0.5f, ted_color(ted, COLOR_TEXT_SECONDARY), ANCHOR_MIDDLE);
text_render(font);
diff --git a/menu.c b/menu.c
index a41da08..2e04d49 100644
--- a/menu.c
+++ b/menu.c
@@ -50,7 +50,7 @@ void menu_open(Ted *ted, Menu menu) {
if (ted->menu)
menu_close_with_next(ted, menu);
if (ted->find) find_close(ted);
- ted->autocomplete = false;
+ autocomplete_close(ted);
ted->menu = menu;
TextBuffer *prev_buf = ted->prev_active_buffer = ted->active_buffer;
if (prev_buf)
diff --git a/ted.c b/ted.c
index ed5cb00..2ce39fe 100644
--- a/ted.c
+++ b/ted.c
@@ -139,7 +139,7 @@ static void ted_load_fonts(Ted *ted) {
void ted_switch_to_buffer(Ted *ted, TextBuffer *buffer) {
TextBuffer *search_buffer = find_search_buffer(ted);
ted->active_buffer = buffer;
- ted->autocomplete = false;
+ autocomplete_close(ted);
if (buffer != search_buffer) {
if (ted->find)
find_update(ted, true); // make sure find results are for this file
diff --git a/ted.h b/ted.h
index 3d2bbc6..df50f8b 100644
--- a/ted.h
+++ b/ted.h
@@ -352,6 +352,11 @@ typedef struct {
u32 build_output_line; // which line in the build output corresponds to this error
} BuildError;
+typedef struct {
+ char *label;
+ char *text;
+} Autocompletion;
+
typedef struct Ted {
struct LSP *test_lsp; // @TODO: something better
@@ -405,6 +410,8 @@ typedef struct Ted {
bool building; // is the build process running?
bool autocomplete; // is the autocomplete window open?
+ Autocompletion *autocompletions; // dynamic array of suggestions
+ BufferPos autocomplete_pos; // position of cursor last time completions were generated. if this changes, we need to recompute completions.
i32 autocomplete_cursor; // which completion is currently selected
Rect autocomplete_rect; // rectangle where the autocomplete menu is (needed to avoid interpreting autocomplete clicks as other clicks)
@@ -468,6 +475,7 @@ typedef struct Ted {
char error_shown[512]; // error display in box on screen
} Ted;
+void autocomplete_close(Ted *ted);
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