summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeo Tenenbaum <pommicket@gmail.com>2021-03-03 19:17:48 -0500
committerLeo Tenenbaum <pommicket@gmail.com>2021-03-03 19:17:48 -0500
commite3eb333ae2b07467e14dd9e2f845889f75f01a16 (patch)
tree64599a9b11f757a5e480c9f94b3ddedb57678d4e
parent74cae07c859e68876ee98b99e1c1761d4c205484 (diff)
start autocomplete
-rw-r--r--buffer.c35
-rw-r--r--colors.h6
-rw-r--r--command.c35
-rw-r--r--command.h5
-rw-r--r--math.c83
-rw-r--r--tags.c13
-rw-r--r--ted.cfg6
-rw-r--r--ted.h4
8 files changed, 169 insertions, 18 deletions
diff --git a/buffer.c b/buffer.c
index 21922ee..7c5c9db 100644
--- a/buffer.c
+++ b/buffer.c
@@ -2437,17 +2437,6 @@ void buffer_render(TextBuffer *buffer, Rect r) {
text_render(font);
if (ted->active_buffer == buffer) {
- // render cursor
- float time_on = settings->cursor_blink_time_on;
- float time_off = settings->cursor_blink_time_off;
- bool is_on = true;
- if (time_off > 0) {
- double absolute_time = time_get_seconds();
- float period = time_on + time_off;
- // time in period
- double t = fmod(absolute_time, period);
- is_on = t < time_on; // are we in the "on" part of the period?
- }
// highlight matching brackets
char32_t cursor_bracket = buffer_char_before_cursor(buffer);
@@ -2477,11 +2466,33 @@ void buffer_render(TextBuffer *buffer, Rect r) {
}
}
+ // render cursor
+ float time_on = settings->cursor_blink_time_on;
+ float time_off = settings->cursor_blink_time_off;
+ double error_animation_duration = 1.0;
+ double error_animation_dt = time_get_seconds() - ted->cursor_error_time;
+ bool error_animation = ted->cursor_error_time > 0 && error_animation_dt < error_animation_duration;
+
+ bool is_on = true;
+ if (!error_animation && time_off > 0) {
+ double absolute_time = time_get_seconds();
+ float period = time_on + time_off;
+ // time in period
+ double t = fmod(absolute_time, period);
+ is_on = t < time_on; // are we in the "on" part of the period?
+ }
if (is_on) {
if (buffer_clip_rect(buffer, &cursor_rect)) {
// draw cursor
- gl_geometry_rect(cursor_rect, colors[buffer->view_only ? COLOR_VIEW_ONLY_CURSOR : COLOR_CURSOR]);
+ u32 color = colors[COLOR_CURSOR];
+ if (buffer->view_only)
+ color = colors[COLOR_VIEW_ONLY_CURSOR];
+ if (error_animation) {
+ color = color_interpolate(maxf(0, 2 * ((float)(error_animation_dt / error_animation_duration) - 0.5f)), colors[COLOR_CURSOR_ERROR], colors[COLOR_CURSOR]);
+ }
+
+ gl_geometry_rect(cursor_rect, color);
}
}
gl_geometry_draw();
diff --git a/colors.h b/colors.h
index e1f033c..f45ed01 100644
--- a/colors.h
+++ b/colors.h
@@ -6,6 +6,7 @@ ENUM_U16 {
COLOR_BG,
COLOR_HL,
COLOR_CURSOR,
+ COLOR_CURSOR_ERROR,
COLOR_CURSOR_LINE_BG,
COLOR_SELECTION_BG,
COLOR_VIEW_ONLY_CURSOR,
@@ -49,13 +50,14 @@ typedef struct {
char const *name;
} ColorName;
-static ColorName const color_names[COLOR_COUNT] = {
+static ColorName const color_names[] = {
{COLOR_UNKNOWN, "unknown"},
{COLOR_TEXT, "text"},
{COLOR_TEXT_SECONDARY, "text-secondary"},
{COLOR_BG, "bg"},
{COLOR_HL, "hl"},
{COLOR_CURSOR, "cursor"},
+ {COLOR_CURSOR_ERROR, "cursor-error"},
{COLOR_CURSOR_LINE_BG, "cursor-line-bg"},
{COLOR_VIEW_ONLY_CURSOR, "view-only-cursor"},
{COLOR_VIEW_ONLY_SELECTION_BG, "view-only-selection-bg"},
@@ -88,6 +90,8 @@ static ColorName const color_names[COLOR_COUNT] = {
{COLOR_LINE_NUMBERS_SEPARATOR, "line-numbers-separator"},
};
+static_assert_if_possible(arr_count(color_names) == COLOR_COUNT)
+
static ColorSetting color_setting_from_str(char const *str) {
// @OPTIMIZE: sort color_names, binary search
for (int i = 0; i < COLOR_COUNT; ++i) {
diff --git a/command.c b/command.c
index 727b2c6..f0dd1ec 100644
--- a/command.c
+++ b/command.c
@@ -204,6 +204,39 @@ 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_UNDO:
if (buffer) buffer_undo(buffer, argument);
break;
@@ -301,6 +334,8 @@ void command_execute(Ted *ted, Command c, i64 argument) {
if (*ted->error_shown) {
// dismiss error box
*ted->error_shown = '\0';
+ } else if (ted->autocomplete) {
+ ted->autocomplete = false;
} else if (ted->menu) {
menu_escape(ted);
} else if (ted->find) {
diff --git a/command.h b/command.h
index 41bab31..1c9be20 100644
--- a/command.h
+++ b/command.h
@@ -50,7 +50,9 @@ ENUM_U16 {
CMD_COMMAND_SELECTOR,
CMD_OPEN_CONFIG,
CMD_QUIT,
-
+
+ CMD_AUTOCOMPLETE,
+
CMD_COPY,
CMD_CUT,
CMD_PASTE,
@@ -138,6 +140,7 @@ static CommandName const command_names[] = {
{"copy", CMD_COPY},
{"cut", CMD_CUT},
{"paste", CMD_PASTE},
+ {"autocomplete", CMD_AUTOCOMPLETE},
{"find", CMD_FIND},
{"find-replace", CMD_FIND_REPLACE},
{"tab-close", CMD_TAB_CLOSE},
diff --git a/math.c b/math.c
index 7d16a52..75ec497 100644
--- a/math.c
+++ b/math.c
@@ -639,6 +639,13 @@ static v4 rgba_u32_to_v4(u32 rgba) {
return V4(c[0], c[1], c[2], c[3]);
}
+static u32 rgba_v4_to_u32(v4 color) {
+ return (u32)(color.x * 255) << 24
+ | (u32)(color.y * 255) << 16
+ | (u32)(color.z * 255) << 8
+ | (u32)(color.w * 255);
+}
+
// returns average of red green and blue components of color
static float rgba_brightness(u32 color) {
u8 r = (u8)(color >> 24), g = (u8)(color >> 16), b = (u8)(color >> 8);
@@ -740,3 +747,79 @@ static Rect rect_shrink(Rect r, float amount) {
r.size.y = maxf(r.size.y, 0);
return r;
}
+
+static v4 color_rgba_to_hsva(v4 rgba) {
+ float R = rgba.x, G = rgba.y, B = rgba.z, A = rgba.w;
+ float M = maxf(R, maxf(G, B));
+ float m = minf(R, minf(G, B));
+ float C = M - m;
+ float H = 0;
+ if (C == 0)
+ H = 0;
+ if (M == R)
+ H = fmodf((G - B) / C, 6);
+ else if (M == G)
+ H = (B - R) / C + 2;
+ else if (M == B)
+ H = (R - G) / C + 4;
+ H *= 60;
+ float V = M;
+ float S = V == 0 ? 0 : C / V;
+ return V4(H, S, V, A);
+}
+
+static v4 color_hsva_to_rgba(v4 hsva) {
+ float H = hsva.x, S = hsva.y, V = hsva.z, A = hsva.w;
+ H /= 60;
+ float C = S * V;
+ float X = C * (1 - fabsf(fmodf(H, 2) - 1));
+ float R, G, B;
+ if (H <= 1)
+ R=C, G=X, B=0;
+ else if (H <= 2)
+ R=X, G=C, B=0;
+ else if (H <= 3)
+ R=0, G=C, B=X;
+ else if (H <= 4)
+ R=0, G=X, B=C;
+ else if (H <= 5)
+ R=X, G=0, B=C;
+ else
+ R=C, G=0, B=X;
+
+ float m = V-C;
+ R += m;
+ G += m;
+ B += m;
+ return V4(R, G, B, A);
+}
+
+static u32 color_interpolate(float x, u32 color1, u32 color2) {
+ x = x * x * (3 - 2*x); // hermite interpolation
+
+ v4 c1 = rgba_u32_to_v4(color1), c2 = rgba_u32_to_v4(color2);
+ // to make it interpolate more nicely, convert to hsv, interpolate in that space, then convert back
+ c1 = color_rgba_to_hsva(c1);
+ c2 = color_rgba_to_hsva(c2);
+ float h1 = c1.x, s1 = c1.y, v1 = c1.z, a1 = c1.w;
+ float h2 = c2.x, s2 = c2.y, v2 = c2.z, a2 = c2.w;
+
+ float s_out = lerpf(x, s1, s2);
+ float v_out = lerpf(x, v1, v2);
+ float a_out = lerpf(x, a1, a2);
+
+ float h_out;
+ // because hue is on a circle, we need to make sure we take the shorter route around the circle
+ if (fabsf(h1 - h2) < 180) {
+ h_out = lerpf(x, h1, h2);
+ } else if (h1 > h2) {
+ h_out = lerpf(x, h1, h2 + 360);
+ } else {
+ h_out = lerpf(x, h1 + 360, h2);
+ }
+ h_out = fmodf(h_out, 360);
+
+ v4 c_out = V4(h_out, s_out, v_out, a_out);
+ c_out = color_hsva_to_rgba(c_out);
+ return rgba_v4_to_u32(c_out);
+}
diff --git a/tags.c b/tags.c
index a7b87e8..ad95d4c 100644
--- a/tags.c
+++ b/tags.c
@@ -42,6 +42,7 @@ static int tag_try(FILE *fp, char const *tag) {
}
// finds all tags beginning with the given prefix, returning them into *out, writing at most out_size entries.
+// you may pass NULL for out, in which case just the number of matching tags is returned (still maxing out at out_size)
// each element in out should be freed when you're done with them
size_t tags_beginning_with(Ted *ted, char const *prefix, char **out, size_t out_size) {
assert(out_size);
@@ -77,14 +78,20 @@ size_t tags_beginning_with(Ted *ted, char const *prefix, char **out, size_t out_
size_t nmatches = 0;
size_t prefix_len = strlen(prefix);
bool done = false;
+ char prev_match[1024];
+
while (!done && fgets(line, sizeof line, file)) {
switch (strncmp(line, prefix, prefix_len)) {
case 0: {
char *tag = strn_dup(line, strcspn(line, "\t"));
- if (nmatches == 0 || !streq(tag, out[nmatches - 1])) // don't include duplicate tags
- out[nmatches++] = tag;
- else
+ if (nmatches == 0 || !streq(tag, prev_match)) { // don't include duplicate tags
+ strbuf_cpy(prev_match, tag);
+ if (out) out[nmatches] = tag;
+ else free(tag);
+ ++nmatches;
+ } else {
free(tag);
+ }
if (nmatches >= out_size) done = true;
} break;
case +1:
diff --git a/ted.cfg b/ted.cfg
index d824b92..ff8ff87 100644
--- a/ted.cfg
+++ b/ted.cfg
@@ -86,6 +86,9 @@ Ctrl+s = :save
Ctrl+Alt+Shift+s = :save-all
Ctrl+Shift+s = :save-as
Ctrl+q = :quit
+
+Ctrl+Space = :autocomplete
+
Ctrl+z = :undo
Ctrl+Shift+z = :redo
Ctrl+f = :find
@@ -129,6 +132,7 @@ Ctrl+/ = :split-horizontal
Ctrl+Shift+/ = :split-vertical
# unsplit
Ctrl+j = :split-join
+# switch to the other side of a split
Ctrl+tab = :split-swap
Escape = :escape
@@ -140,6 +144,8 @@ active-tab-hl = #a77a
selected-tab-hl = #7777
cursor-line-bg = #fff2
cursor = #3ff
+# used as cursor color when you do autocomplete and there are no suggestions
+cursor-error = #f00
# color to highlight matching brackets with
matching-bracket-hl = #fda8
selection-bg = #36aa
diff --git a/ted.h b/ted.h
index ea1afdd..7b0af48 100644
--- a/ted.h
+++ b/ted.h
@@ -263,6 +263,7 @@ typedef struct Ted {
TextBuffer build_buffer; // buffer for build output (view only)
TextBuffer argument_buffer; // used for command selector
double error_time; // time error box was opened (in seconds -- see time_get_seconds)
+ double cursor_error_time; // time which the cursor error animation started (cursor turns red, e.g. when there's no autocomplete suggestion)
KeyAction key_actions[KEY_COMBO_COUNT];
bool search_cwd; // should the working directory be searched for files? set to true if the executable isn't "installed"
bool quit; // if set to true, the window will close next frame. NOTE: this doesn't check for unsaved changes!!
@@ -277,7 +278,8 @@ typedef struct Ted {
Command warn_unsaved; // if non-zero, the user is trying to execute this command, but there are unsaved changes
bool build_shown; // are we showing the build output?
bool building; // is the build process running?
-
+ bool autocomplete; // is the autocomplete window open?
+
FILE *log;
BuildError *build_errors; // dynamic array of build errors