summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2022-12-23 13:00:51 -0500
committerpommicket <pommicket@gmail.com>2022-12-23 13:00:51 -0500
commite4774c14963bf51840bbf940c1181fe7fc7c3046 (patch)
tree5d784e37a2d3f18e05e42c795ba7220262a33231
parentbc21923ba75a46693f6470f9bff903e0df46ac15 (diff)
trigger characters
-rw-r--r--autocomplete.c2
-rw-r--r--json.c54
-rw-r--r--lsp-parse.c80
-rw-r--r--lsp.c1
-rw-r--r--lsp.h2
-rw-r--r--main.c31
6 files changed, 122 insertions, 48 deletions
diff --git a/autocomplete.c b/autocomplete.c
index e0de9fd..a71e45a 100644
--- a/autocomplete.c
+++ b/autocomplete.c
@@ -171,7 +171,7 @@ static void autocomplete_process_lsp_response(Ted *ted, const LSPResponse *respo
// open autocomplete, or just do the completion if there's only one suggestion
static void autocomplete_open(Ted *ted) {
Autocomplete *ac = &ted->autocomplete;
-
+ if (ac->open) return;
if (!ted->active_buffer) return;
TextBuffer *buffer = ted->active_buffer;
if (!buffer->filename) return;
diff --git a/json.c b/json.c
index 61c8103..1174490 100644
--- a/json.c
+++ b/json.c
@@ -81,6 +81,7 @@ const char *json_type_to_str(JSONValueType type) {
}
static bool json_parse_value(JSON *json, u32 *p_index, JSONValue *val);
+void json_debug_print_value(const JSON *json, JSONValue value);
// defining this instead of using isspace seems to be faster
// probably because isspace depends on the locale.
@@ -88,7 +89,33 @@ static inline bool json_is_space(char c) {
return c == ' ' || c == '\n' || c == '\r' || c == '\t';
}
-static void json_debug_print_value(const JSON *json, JSONValue value) {
+void json_debug_print_array(const JSON *json, JSONArray array) {
+ printf("[");
+ for (u32 i = 0; i < array.len; ++i) {
+ json_debug_print_value(json, json->values[array.elements + i]);
+ printf(", ");
+ }
+ printf("]");
+}
+
+void json_debug_print_object(const JSON *json, JSONObject obj) {
+ printf("{");
+ for (u32 i = 0; i < obj.len; ++i) {
+ json_debug_print_value(json, json->values[obj.items + i]);
+ printf(": ");
+ json_debug_print_value(json, json->values[obj.items + obj.len + i]);
+ printf(", ");
+ }
+ printf("}");
+}
+
+void json_debug_print_string(const JSON *json, JSONString string) {
+ printf("\"%.*s\"",
+ (int)string.len,
+ json->text + string.pos);
+}
+
+void json_debug_print_value(const JSON *json, JSONValue value) {
switch (value.type) {
case JSON_UNDEFINED: printf("undefined"); break;
case JSON_NULL: printf("null"); break;
@@ -96,30 +123,13 @@ static void json_debug_print_value(const JSON *json, JSONValue value) {
case JSON_TRUE: printf("true"); break;
case JSON_NUMBER: printf("%g", value.val.number); break;
case JSON_STRING: {
- JSONString string = value.val.string;
- printf("\"%.*s\"",
- (int)string.len,
- json->text + string.pos);
+ json_debug_print_string(json, value.val.string);
} break;
case JSON_ARRAY: {
- JSONArray array = value.val.array;
- printf("[");
- for (u32 i = 0; i < array.len; ++i) {
- json_debug_print_value(json, json->values[array.elements + i]);
- printf(", ");
- }
- printf("]");
- } break;
+ json_debug_print_array(json, value.val.array);
+ } break;
case JSON_OBJECT: {
- JSONObject obj = value.val.object;
- printf("{");
- for (u32 i = 0; i < obj.len; ++i) {
- json_debug_print_value(json, json->values[obj.items + i]);
- printf(": ");
- json_debug_print_value(json, json->values[obj.items + obj.len + i]);
- printf(", ");
- }
- printf("}");
+ json_debug_print_object(json, value.val.object);
} break;
}
}
diff --git a/lsp-parse.c b/lsp-parse.c
index 05fc898..0c75acf 100644
--- a/lsp-parse.c
+++ b/lsp-parse.c
@@ -77,6 +77,35 @@ static bool parse_range(LSP *lsp, const JSON *json, JSONValue range_value, LSPRa
&& parse_position(lsp, json, end, &range->end);
}
+
+static void parse_capabilities(LSP *lsp, const JSON *json, JSONObject capabilities) {
+ JSONValue completion_value = json_object_get(json, capabilities, "completionProvider");
+ if (completion_value.type == JSON_OBJECT) {
+ lsp->provides_completion = true;
+ JSONObject completion = completion_value.val.object;
+
+ JSONArray trigger_chars = json_object_get_array(json, completion, "triggerCharacters");
+ for (u32 i = 0; i < trigger_chars.len; ++i) {
+ char character[8] = {0};
+ json_string_get(json,
+ json_array_get_string(json, trigger_chars, i),
+ character,
+ sizeof character);
+ if (*character) {
+ char32_t c = 0;
+ unicode_utf8_to_utf32(&c, character, strlen(character));
+ // the fact that they're called "trigger characters" makes
+ // me think multi-character triggers aren't allowed
+ // even though that would be nice in some languages,
+ // e.g. "::"
+ if (c) {
+ arr_add(lsp->trigger_chars, c);
+ }
+ }
+ }
+ }
+}
+
static bool parse_completion(LSP *lsp, const JSON *json, LSPResponse *response) {
// deal with textDocument/completion response.
// result: CompletionItem[] | CompletionList | null
@@ -273,7 +302,6 @@ static bool parse_server2client_request(LSP *lsp, JSON *json, LSPRequest *reques
return false;
}
-
static void process_message(LSP *lsp, JSON *json) {
#if 0
@@ -318,8 +346,22 @@ static void process_message(LSP *lsp, JSON *json) {
JSONValue result = json_get(json, "result");
if (result.type != JSON_UNDEFINED) {
- if (response_to.type == LSP_REQUEST_INITIALIZE) {
+ LSPResponse response = {0};
+ bool add_to_messages = false;
+ response.request = response_to;
+ switch (response_to.type) {
+ case LSP_REQUEST_COMPLETION:
+ add_to_messages = parse_completion(lsp, json, &response);
+ break;
+ case LSP_REQUEST_INITIALIZE: {
// it's the response to our initialize request!
+
+ if (result.type == JSON_OBJECT) {
+ // read server capabilities
+ JSONObject capabilities = json_object_get_object(json, result.val.object, "capabilities");
+ parse_capabilities(lsp, json, capabilities);
+ }
+
// let's send back an "initialized" request (notification) because apparently
// that's something we need to do.
LSPRequest initialized = {
@@ -329,28 +371,20 @@ static void process_message(LSP *lsp, JSON *json) {
write_request(lsp, &initialized);
// we can now send requests which have nothing to do with initialization
lsp->initialized = true;
+ } break;
+ default:
+ // it's some response we don't care about
+ break;
+ }
+ if (add_to_messages) {
+ SDL_LockMutex(lsp->messages_mutex);
+ LSPMessage *message = arr_addp(lsp->messages);
+ message->type = LSP_RESPONSE;
+ message->u.response = response;
+ SDL_UnlockMutex(lsp->messages_mutex);
+ response_to.type = 0; // don't free
} else {
- LSPResponse response = {0};
- bool success = false;
- response.request = response_to;
- switch (response_to.type) {
- case LSP_REQUEST_COMPLETION:
- success = parse_completion(lsp, json, &response);
- break;
- default:
- // it's some response we don't care about
- break;
- }
- if (success) {
- SDL_LockMutex(lsp->messages_mutex);
- LSPMessage *message = arr_addp(lsp->messages);
- message->type = LSP_RESPONSE;
- message->u.response = response;
- SDL_UnlockMutex(lsp->messages_mutex);
- response_to.type = 0; // don't free
- } else {
- lsp_response_free(&response);
- }
+ lsp_response_free(&response);
}
} else if (json_has(json, "method")) {
LSPRequest request = {0};
diff --git a/lsp.c b/lsp.c
index fedc085..087d757 100644
--- a/lsp.c
+++ b/lsp.c
@@ -315,6 +315,7 @@ void lsp_free(LSP *lsp) {
lsp_message_free(message);
}
arr_free(lsp->messages);
+ arr_free(lsp->trigger_chars);
}
void lsp_document_changed(LSP *lsp, const char *document, LSPDocumentChangeEvent change) {
diff --git a/lsp.h b/lsp.h
index cee1505..1c6076a 100644
--- a/lsp.h
+++ b/lsp.h
@@ -268,6 +268,8 @@ typedef struct LSP {
SDL_Thread *communication_thread;
SDL_sem *quit_sem;
char *received_data; // dynamic array
+ bool provides_completion; // can this LSP server handle completion requests?
+ char32_t *trigger_chars; // dynamic array of "trigger characters"
SDL_mutex *error_mutex;
char error[256];
} LSP;
diff --git a/main.c b/main.c
index f9f8605..26195f5 100644
--- a/main.c
+++ b/main.c
@@ -1,11 +1,16 @@
/*
@TODO:
-- trigger characters (with setting)
+- fix unicode_utf8_to_utf32 to handle bad UTF-8 (i.e. continuation bytes which aren't actually continuation bytes)
+- provide completion context?
+- dont do completion if provides_completion = false
- scroll through completions
- only show "Loading..." if it's taking some time (prevent flash)
- LSP setting
- figure out workspace
- make sure "save as" works
+- more LSP stuff:
+ - go to definition using LSP
+ - find usages
- rename buffer->filename to buffer->path
- make buffer->path NULL for untitled buffers & fix resulting mess
- run everything through valgrind ideally with leak checking
@@ -810,8 +815,30 @@ int main(int argc, char **argv) {
case SDL_TEXTINPUT: {
char *text = event.text.text;
if (buffer
- && (key_modifier & ~KEY_MODIFIER_SHIFT) == 0) // unfortunately, some key combinations like ctrl+minus still register as a "-" text input event
+ // unfortunately, some key combinations like ctrl+minus still register as a "-" text input event
+ && (key_modifier & ~KEY_MODIFIER_SHIFT) == 0) {
+ // insert the text
buffer_insert_utf8_at_cursor(buffer, text);
+ // check for trigger character
+ LSP *lsp = buffer_lsp(buffer);
+ Settings *settings = buffer_settings(buffer);
+ if (lsp && settings->trigger_characters) {
+ u32 last_code_point = (u32)strlen(text) - 1;
+ while (last_code_point > 0 &&
+ unicode_is_continuation_byte((u8)text[last_code_point]))
+ --last_code_point;
+ char32_t last_char = 0;
+ unicode_utf8_to_utf32(&last_char, &text[last_code_point],
+ strlen(text) - last_code_point);
+ arr_foreach_ptr(lsp->trigger_chars, char32_t, c) {
+ if (*c == last_char) {
+ autocomplete_open(ted);
+ break;
+ }
+ }
+ }
+
+ }
} break;
}
}