summaryrefslogtreecommitdiff
path: root/autocomplete.c
diff options
context:
space:
mode:
Diffstat (limited to 'autocomplete.c')
-rw-r--r--autocomplete.c145
1 files changed, 145 insertions, 0 deletions
diff --git a/autocomplete.c b/autocomplete.c
new file mode 100644
index 0000000..da4ade3
--- /dev/null
+++ b/autocomplete.c
@@ -0,0 +1,145 @@
+// 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;
+ }
+}
+
+// do the actual completion
+static void autocomplete_complete(Ted *ted, char *start, TextBuffer *buffer, char *completion) {
+ char *str = completion + strlen(start);
+ buffer_start_edit_chain(buffer); // don't merge with other edits
+ buffer_insert_utf8_at_cursor(buffer, str);
+ buffer_end_edit_chain(buffer);
+ ted->autocomplete = false;
+}
+
+// open autocomplete, or just do the completion if there's only one suggestion
+static void autocomplete_open(Ted *ted) {
+ char *start;
+ TextBuffer *buffer;
+ ted->cursor_error_time = 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);
+ }
+}
+
+#define AUTOCOMPLETE_NCOMPLETIONS 10 // max # of completions to show
+static void autocomplete_frame(Ted *ted) {
+ Settings const *settings = &ted->settings;
+ u32 const *colors = settings->colors;
+ float const padding = settings->padding;
+
+ char *start;
+ TextBuffer *buffer;
+ if (autocomplete_get(ted, &start, &buffer)) {
+ Font *font = ted->font;
+ float char_height = text_font_char_height(font);
+
+ 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;
+ }
+
+
+ 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]);
+ }
+
+ // 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 cursor 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;
+ ted->autocomplete_rect = r;
+ }
+
+ 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]);
+ }
+ }
+ }
+
+ 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;
+ }
+}