diff options
-rw-r--r-- | autocomplete.c | 145 | ||||
-rw-r--r-- | buffer.c | 6 | ||||
-rw-r--r-- | build.c | 1 | ||||
-rw-r--r-- | command.c | 35 | ||||
-rw-r--r-- | main.c | 4 | ||||
-rw-r--r-- | menu.c | 1 | ||||
-rw-r--r-- | ted.h | 2 |
7 files changed, 161 insertions, 33 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; + } +} @@ -2148,6 +2148,12 @@ u32 buffer_last_rendered_line(TextBuffer *buffer) { // returns true if the buffer "used" this event bool buffer_handle_click(Ted *ted, TextBuffer *buffer, v2 click, u8 times) { BufferPos buffer_pos; + if (ted->autocomplete) { + 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 + } if (buffer_pixels_to_pos(buffer, click, &buffer_pos)) { // user clicked on buffer if (!ted->menu || buffer->is_line_buffer) { @@ -11,7 +11,6 @@ static void build_stop(Ted *ted) { process_kill(&ted->build_process); ted->building = false; ted->build_shown = false; - *ted->build_dir = '\0'; build_clear(ted); } @@ -205,38 +205,9 @@ void command_execute(Ted *ted, Command c, i64 argument) { } break; - case CMD_AUTOCOMPLETE: { - 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)); - 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: { - char *str = completions[0] + 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); - } break; - case 2: - // open autocomplete selection menu - ted->autocomplete = true; - break; - default: assert(0); break; - } - free(completions[0]); - free(completions[1]); - free(start); - } - } break; - + case CMD_AUTOCOMPLETE: + autocomplete_open(ted); + break; case CMD_UNDO: if (buffer) buffer_undo(buffer, argument); break; @@ -74,6 +74,7 @@ bool tag_goto(Ted *ted, char const *tag); #include "tags.c" #include "build.c" #include "menu.c" +#include "autocomplete.c" #include "command.c" #include "config.c" #include "session.c" @@ -805,6 +806,9 @@ int main(int argc, char **argv) { float y1 = padding; node_frame(ted, node, rect4(x1, y1, x2, y)); + if (ted->autocomplete) { + autocomplete_frame(ted); + } } else { 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, colors[COLOR_TEXT_SECONDARY], ANCHOR_MIDDLE); @@ -44,6 +44,7 @@ static void menu_open(Ted *ted, Menu menu) { if (ted->menu) menu_close(ted); if (ted->find) find_close(ted); + ted->autocomplete = false; ted->menu = menu; TextBuffer *prev_buf = ted->prev_active_buffer = ted->active_buffer; if (prev_buf) @@ -280,6 +280,8 @@ typedef struct Ted { bool building; // is the build process running? bool autocomplete; // is the autocomplete window open? + Rect autocomplete_rect; // rectangle where the autocomplete menu is (needed to avoid interpreting autocomplete clicks as other clicks) + FILE *log; BuildError *build_errors; // dynamic array of build errors |