summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--autocomplete.c90
-rw-r--r--command.c2
-rw-r--r--json.c14
-rw-r--r--lsp-parse.c3
-rw-r--r--lsp.h1
-rw-r--r--main.c1
-rw-r--r--ted.h18
7 files changed, 93 insertions, 36 deletions
diff --git a/autocomplete.c b/autocomplete.c
index 8f3df10..2955183 100644
--- a/autocomplete.c
+++ b/autocomplete.c
@@ -99,39 +99,65 @@ static bool autocomplete_using_lsp(Ted *ted) {
static void autocomplete_no_suggestions(Ted *ted) {
Autocomplete *ac = &ted->autocomplete;
- if (ac->trigger_char == 0)
+ if (ac->trigger == TRIGGER_INVOKED)
ted->cursor_error_time = time_get_seconds();
autocomplete_close(ted);
}
-static void autocomplete_find_completions(Ted *ted, char32_t trigger_char) {
+static void autocomplete_send_completion_request(Ted *ted, TextBuffer *buffer, BufferPos pos, uint32_t trigger) {
+ LSP *lsp = buffer_lsp(buffer);
+ Autocomplete *ac = &ted->autocomplete;
+
+ LSPRequest request = {
+ .type = LSP_REQUEST_COMPLETION
+ };
+
+ LSPCompletionTriggerKind lsp_trigger = LSP_TRIGGER_CHARACTER;
+ switch (trigger) {
+ case TRIGGER_INVOKED: lsp_trigger = LSP_TRIGGER_INVOKED; break;
+ case TRIGGER_INCOMPLETE: lsp_trigger = LSP_TRIGGER_INCOMPLETE; break;
+ }
+
+ request.data.completion = (LSPRequestCompletion) {
+ .position = {
+ .document = lsp_document_id(lsp, buffer->filename),
+ .pos = buffer_pos_to_lsp(buffer, pos)
+ },
+ .context = {
+ .trigger_kind = lsp_trigger,
+ .trigger_character = {0},
+ }
+ };
+ if (trigger < UNICODE_CODE_POINTS)
+ unicode_utf32_to_utf8(request.data.completion.context.trigger_character, trigger);
+ lsp_send_request(lsp, &request);
+ ac->waiting_for_lsp = true;
+ ac->lsp_request_time = ted->frame_time;
+ // *technically sepaking* this can mess things up if a complete
+ // list arrives only after the user has typed some stuff
+ // (in that case we'll send a TriggerKind = incomplete request even though it makes no sense).
+ // but i don't think any servers will have a problem with that.
+ ac->is_list_complete = false;
+}
+
+static void autocomplete_find_completions(Ted *ted, uint32_t trigger) {
Autocomplete *ac = &ted->autocomplete;
TextBuffer *buffer = ted->active_buffer;
BufferPos pos = buffer->cursor_pos;
if (buffer_pos_eq(pos, ac->last_pos))
return; // no need to update completions.
- ac->trigger_char = trigger_char;
+ ac->trigger = trigger;
ac->last_pos = pos;
LSP *lsp = buffer_lsp(buffer);
if (lsp) {
- LSPRequest request = {
- .type = LSP_REQUEST_COMPLETION
- };
- bool invoked = ac->trigger_char == 0 || is_word(ac->trigger_char);
- request.data.completion = (LSPRequestCompletion) {
- .position = {
- .document = lsp_document_id(lsp, buffer->filename),
- .pos = buffer_pos_to_lsp(buffer, pos)
- },
- .context = {
- .trigger_kind = invoked ? LSP_TRIGGER_INVOKED : LSP_TRIGGER_CHARACTER,
- .trigger_character = {0},
- }
- };
- unicode_utf32_to_utf8(request.data.completion.context.trigger_character, ac->trigger_char);
- lsp_send_request(lsp, &request);
- ac->waiting_for_lsp = true;
+ if (ac->is_list_complete && trigger == TRIGGER_INCOMPLETE) {
+ // the list of completions we got from the LSP server is complete,
+ // so we just need to call autocomplete_update_suggested,
+ // we don't need to send a new request.
+ } else {
+ autocomplete_send_completion_request(ted, buffer, pos, trigger);
+ }
} else {
// tag completion
autocomplete_clear_completions(ted);
@@ -150,6 +176,10 @@ static void autocomplete_find_completions(Ted *ted, char32_t trigger_char) {
arr_add(ac->suggested, (u32)i);
}
free(completions);
+
+ // if we got the full list of tags beginning with `word_at_cursor`,
+ // then we don't need to call `tags_beginning_with` again.
+ ac->is_list_complete = ncompletions == TAGS_MAX_COMPLETIONS;
}
autocomplete_update_suggested(ted);
@@ -157,7 +187,6 @@ static void autocomplete_find_completions(Ted *ted, char32_t trigger_char) {
static void autocomplete_process_lsp_response(Ted *ted, const LSPResponse *response) {
Autocomplete *ac = &ted->autocomplete;
- bool was_waiting = ac->waiting_for_lsp;
ac->waiting_for_lsp = false;
if (!ac->open) {
// user hit escape or down or something before completions arrived.
@@ -186,6 +215,7 @@ static void autocomplete_process_lsp_response(Ted *ted, const LSPResponse *respo
ted_completion->documentation = *documentation ? str_dup(documentation) : NULL;
}
+ ac->is_list_complete = completion->is_complete;
}
autocomplete_update_suggested(ted);
switch (arr_len(ac->suggested)) {
@@ -193,15 +223,16 @@ static void autocomplete_process_lsp_response(Ted *ted, const LSPResponse *respo
autocomplete_no_suggestions(ted);
return;
case 1:
- // if we just finished loading suggestions, and there's only one suggestion, use it
- if (was_waiting)
+ // if autocomplete was invoked by Ctrl+Space, and there's only one completion, select it.
+ if (ac->trigger == TRIGGER_INVOKED)
autocomplete_complete(ted, ac->completions[ac->suggested[0]]);
return;
}
}
-// open autocomplete, or just do the completion if there's only one suggestion
-static void autocomplete_open(Ted *ted, char32_t trigger_character) {
+// open autocomplete
+// trigger should either be a character (e.g. '.') or one of the TRIGGER_* constants.
+static void autocomplete_open(Ted *ted, uint32_t trigger) {
Autocomplete *ac = &ted->autocomplete;
if (ac->open) return;
if (!ted->active_buffer) return;
@@ -212,8 +243,7 @@ static void autocomplete_open(Ted *ted, char32_t trigger_character) {
ted->cursor_error_time = 0;
ac->last_pos = (BufferPos){0,0};
ac->cursor = 0;
- ac->open_time = ted->frame_time;
- autocomplete_find_completions(ted, trigger_character);
+ autocomplete_find_completions(ted, trigger);
switch (arr_len(ac->completions)) {
case 0:
@@ -261,13 +291,13 @@ static void autocomplete_frame(Ted *ted) {
u32 const *colors = settings->colors;
float const padding = settings->padding;
- autocomplete_find_completions(ted, 0);
-
+ autocomplete_find_completions(ted, TRIGGER_INCOMPLETE);
+
size_t ncompletions = arr_len(ac->suggested);
if (ac->waiting_for_lsp && ncompletions == 0) {
struct timespec now = ted->frame_time;
- if (timespec_sub(now, ac->open_time) < 0.2) {
+ if (timespec_sub(now, ac->lsp_request_time) < 0.2) {
// don't show "Loading..." unless we've actually been loading for a bit of time
return;
}
diff --git a/command.c b/command.c
index 82b096b..47dabef 100644
--- a/command.c
+++ b/command.c
@@ -287,7 +287,7 @@ void command_execute(Ted *ted, Command c, i64 argument) {
if (ted->autocomplete.open)
autocomplete_next(ted);
else
- autocomplete_open(ted, 0);
+ autocomplete_open(ted, TRIGGER_INVOKED);
break;
case CMD_AUTOCOMPLETE_BACK:
if (ted->autocomplete.open)
diff --git a/json.c b/json.c
index 1174490..2dfb368 100644
--- a/json.c
+++ b/json.c
@@ -478,6 +478,20 @@ double json_array_get_number(const JSON *json, JSONArray array, size_t i) {
return json_force_number(json_array_get(json, array, i));
}
+bool json_force_bool(JSONValue x, bool default_value) {
+ if (x.type == JSON_TRUE) return true;
+ if (x.type == JSON_FALSE) return false;
+ return default_value;
+}
+
+bool json_object_get_bool(const JSON *json, JSONObject object, const char *name, bool default_value) {
+ return json_force_bool(json_object_get(json, object, name), default_value);
+}
+
+bool json_array_get_bool(const JSON *json, JSONArray array, size_t i, bool default_value) {
+ return json_force_bool(json_array_get(json, array, i), default_value);
+}
+
// returns (JSONString){0} (which is interpreted as an empty string) if `x` is not a string
JSONString json_force_string(JSONValue x) {
if (x.type == JSON_STRING) {
diff --git a/lsp-parse.c b/lsp-parse.c
index 84b44e2..b5a05d0 100644
--- a/lsp-parse.c
+++ b/lsp-parse.c
@@ -113,6 +113,8 @@ static bool parse_completion(LSP *lsp, const JSON *json, LSPResponse *response)
JSONValue result = json_get(json, "result");
JSONValue items_value = {0};
+ completion->is_complete = true; // default
+
switch (result.type) {
case JSON_NULL:
// no completions
@@ -122,6 +124,7 @@ static bool parse_completion(LSP *lsp, const JSON *json, LSPResponse *response)
break;
case JSON_OBJECT:
items_value = json_object_get(json, result.val.object, "items");
+ completion->is_complete = !json_object_get_bool(json, result.val.object, "isIncomplete", false);
break;
default:
lsp_set_error(lsp, "Weird result type for textDocument/completion response: %s.", json_type_to_str(result.type));
diff --git a/lsp.h b/lsp.h
index d68456a..4301636 100644
--- a/lsp.h
+++ b/lsp.h
@@ -220,6 +220,7 @@ typedef struct {
} LSPCompletionItem;
typedef struct {
+ bool is_complete;
// dynamic array
LSPCompletionItem *items;
} LSPResponseCompletion;
diff --git a/main.c b/main.c
index 0781490..03d8be5 100644
--- a/main.c
+++ b/main.c
@@ -1,6 +1,5 @@
/*
@TODO:
-- stop typing from completing when there's only one copmletion left
- in jdtls, opening an empty java file gives an exception. is this my fault?
- what's wrong with pylsp (try "f.w") in generate.py (might be fixed now)
- what's wrong with gopls?
diff --git a/ted.h b/ted.h
index 55a97eb..18defc4 100644
--- a/ted.h
+++ b/ted.h
@@ -379,16 +379,26 @@ typedef struct {
SymbolKind kind;
} Autocompletion;
+enum {
+ // autocomplete was triggered by :autocomplete command
+ TRIGGER_INVOKED = 0x12000,
+ // autocomplete list needs to be updated because more characters were typed
+ TRIGGER_INCOMPLETE = 0x12001,
+};
+
typedef struct {
bool open; // is the autocomplete window open?
bool waiting_for_lsp;
+ bool is_list_complete; // should the completions array be updated when more characters are typed?
- // which trigger character invoked this (0 if autocomplete was manually invoked)
- char32_t trigger_char;
+ // what trigger caused the last request for completions:
+ // either a character code (for trigger characters),
+ // or one of the TRIGGER_* constants above
+ uint32_t trigger;
- // when autocomplete menu was opened
+ // when we sent the request to the LSP for completions
// (this is used to figure out when we should display "Loading...")
- struct timespec open_time;
+ struct timespec lsp_request_time;
Autocompletion *completions; // dynamic array of all completions
u32 *suggested; // dynamic array of completions to be suggested (indices into completions)