summaryrefslogtreecommitdiff
path: root/autocomplete.c
diff options
context:
space:
mode:
Diffstat (limited to 'autocomplete.c')
-rw-r--r--autocomplete.c294
1 files changed, 156 insertions, 138 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);
}