summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeo Tenenbaum <pommicket@gmail.com>2021-03-03 20:14:51 -0500
committerLeo Tenenbaum <pommicket@gmail.com>2021-03-03 20:14:51 -0500
commit6ef4720bba8fcb90bcbdca5d7766b76816fc58c5 (patch)
tree41a0d02884f182193ff41ccb23b7f66adf35404b
parente3eb333ae2b07467e14dd9e2f845889f75f01a16 (diff)
more autocomplete (already partially working!)
-rw-r--r--autocomplete.c145
-rw-r--r--buffer.c6
-rw-r--r--build.c1
-rw-r--r--command.c35
-rw-r--r--main.c4
-rw-r--r--menu.c1
-rw-r--r--ted.h2
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;
+ }
+}
diff --git a/buffer.c b/buffer.c
index 7c5c9db..51bdfbe 100644
--- a/buffer.c
+++ b/buffer.c
@@ -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) {
diff --git a/build.c b/build.c
index 72cc59c..be0763e 100644
--- a/build.c
+++ b/build.c
@@ -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);
}
diff --git a/command.c b/command.c
index f0dd1ec..f246f64 100644
--- a/command.c
+++ b/command.c
@@ -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;
diff --git a/main.c b/main.c
index 0a78fe2..4a70160 100644
--- a/main.c
+++ b/main.c
@@ -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);
diff --git a/menu.c b/menu.c
index 24a9af8..975ce15 100644
--- a/menu.c
+++ b/menu.c
@@ -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)
diff --git a/ted.h b/ted.h
index 7b0af48..baad978 100644
--- a/ted.h
+++ b/ted.h
@@ -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