summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2022-12-09 20:50:41 -0500
committerpommicket <pommicket@gmail.com>2022-12-09 20:50:41 -0500
commit81be6e2805a82a6f9fbe3c867144efdf58a1667b (patch)
tree8d3d055e8ae2d1a0eab0067dabea6b09ab7d2f4e
parent2dce45002b9d4d93cc2db15b1697d13c1709fb92 (diff)
getting some completions
-rw-r--r--Makefile2
-rw-r--r--json.c15
-rw-r--r--lsp.c313
-rw-r--r--main.c14
4 files changed, 251 insertions, 93 deletions
diff --git a/Makefile b/Makefile
index 9a0e2fa..eb73aa5 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@ ALL_CFLAGS=$(CFLAGS) -Wall -Wextra -Wshadow -Wconversion -Wpedantic -pedantic -s
-Wno-unused-function -Wno-fixed-enum-extension -Wimplicit-fallthrough -Wno-format-truncation -Wno-unknown-warning-option \
-Ipcre2
LIBS=-lSDL2 -lGL -lm libpcre2-32.a
-DEBUG_CFLAGS=$(ALL_CFLAGS) -DDEBUG -O0 -g
+DEBUG_CFLAGS=$(ALL_CFLAGS) -Wno-unused-parameter -DDEBUG -O0 -g
RELEASE_CFLAGS=$(ALL_CFLAGS) -O3
PROFILE_CFLAGS=$(ALL_CFLAGS) -O3 -g -DPROFILE=1
# if you change the directories below, ted won't work.
diff --git a/json.c b/json.c
index 1ee29a1..09ef6e9 100644
--- a/json.c
+++ b/json.c
@@ -444,6 +444,13 @@ JSONValue json_object_get(const JSON *json, const JSONObject *object, const char
return (JSONValue){0};
}
+JSONValue json_array_get(const JSON *json, const JSONArray *array, u64 i) {
+ if (i < array->len) {
+ return json->values[array->elements + i];
+ }
+ return (JSONValue){0};
+}
+
// e.g. if json is { "a" : { "b": 3 }}, then json_get(json, "a.b") = 3.
// returns undefined if there is no such property
JSONValue json_get(const JSON *json, const char *path) {
@@ -475,14 +482,14 @@ bool json_has(const JSON *json, const char *path) {
// turn a json string into a null terminated string.
// this won't be nice if the json string includes \u0000 but that's rare.
// if buf_sz > string->len, the string will fit.
-void json_string_get(const JSON *json, const JSONString *string, char *buf, size_t buf_sz) {
+void json_string_get(const JSON *json, JSONString string, char *buf, size_t buf_sz) {
const char *text = json->text;
if (buf_sz == 0) {
assert(0);
return;
}
char *buf_end = buf + buf_sz - 1;
- for (u32 i = string->pos, end = string->pos + string->len; i < end && buf < buf_end; ++i) {
+ for (u32 i = string.pos, end = string.pos + string.len; i < end && buf < buf_end; ++i) {
if (text[i] != '\\') {
*buf++ = text[i];
} else {
@@ -523,8 +530,8 @@ void json_string_get(const JSON *json, const JSONString *string, char *buf, size
}
// returns a malloc'd null-terminated string.
-static char *json_string_get_alloc(const JSON *json, const JSONString *string) {
- u32 n = string->len + 1;
+static char *json_string_get_alloc(const JSON *json, JSONString string) {
+ u32 n = string.len + 1;
if (n == 0) --n; // extreme edge case
char *buf = calloc(1, n);
json_string_get(json, string, buf, n);
diff --git a/lsp.c b/lsp.c
index bff1ed0..94978ec 100644
--- a/lsp.c
+++ b/lsp.c
@@ -1,6 +1,8 @@
-// @TODO: documentation
-// @TODO : make sure offsets are utf-16!
-// @TODO: maximum queue size for requests/responses just in case?
+// @TODO:
+// - make json_object_get take value not pointer
+// - documentation
+// - make sure offsets are utf-16!
+// - maximum queue size for requests/responses just in case?
typedef enum {
LSP_REQUEST,
@@ -66,11 +68,45 @@ typedef struct {
} data;
} LSPRequest;
+// info we want to keep track of about a request,
+// so we can deal with the response appropriately.
+typedef struct {
+ u64 id;
+ LSPRequestType type;
+} LSPRequestTrackedInfo;
+
+typedef struct {
+ u32 offset;
+} LSPResponseString;
+
+typedef struct {
+ LSPResponseString label;
+ // note: the items are sorted here in this file.
+ LSPResponseString sort_by;
+} LSPCompletionItem;
+
+typedef struct {
+ // dynamic array
+ LSPCompletionItem *items;
+} LSPResponseCompletion;
+
+typedef LSPRequestType LSPResponseType;
+typedef struct {
+ LSPResponseType type;
+ // LSP responses tend to have a lot of strings.
+ // to avoid doing a ton of allocations+frees,
+ // they're all stored here.
+ char *string_data;
+ union {
+ LSPResponseCompletion completion;
+ } data;
+} LSPResponse;
+
typedef struct {
LSPMessageType type;
union {
LSPRequest request;
- JSON response;
+ LSPResponse response;
} u;
} LSPMessage;
@@ -81,6 +117,8 @@ typedef struct {
SDL_mutex *messages_mutex;
LSPRequest *requests_client2server;
LSPRequest *requests_server2client;
+ // only applicable for client-to-server requests
+ LSPRequestTrackedInfo *requests_tracked_info;
SDL_mutex *requests_mutex;
bool initialized; // has the response to the initialize request been sent?
SDL_Thread *communication_thread;
@@ -144,12 +182,65 @@ static const char *lsp_language_id(Language lang) {
return "text";
}
+
+static void lsp_position_free(LSPDocumentPosition *position) {
+ free(position->path);
+}
+
+static void lsp_request_free(LSPRequest *r) {
+ switch (r->type) {
+ case LSP_NONE:
+ assert(0);
+ break;
+ case LSP_INITIALIZE:
+ case LSP_INITIALIZED:
+ case LSP_SHUTDOWN:
+ case LSP_EXIT:
+ break;
+ case LSP_COMPLETION: {
+ LSPRequestCompletion *completion = &r->data.completion;
+ lsp_position_free(&completion->position);
+ } break;
+ case LSP_OPEN: {
+ LSPRequestOpen *open = &r->data.open;
+ free(open->filename);
+ free(open->file_contents);
+ } break;
+ case LSP_SHOW_MESSAGE:
+ case LSP_LOG_MESSAGE:
+ free(r->data.message.message);
+ break;
+ }
+}
+
+static void lsp_response_free(LSPResponse *r) {
+ arr_free(r->string_data);
+ switch (r->type) {
+ case LSP_COMPLETION:
+ arr_free(r->data.completion.items);
+ break;
+ default:
+ break;
+ }
+}
+
+void lsp_message_free(LSPMessage *message) {
+ switch (message->type) {
+ case LSP_REQUEST:
+ lsp_request_free(&message->u.request);
+ break;
+ case LSP_RESPONSE:
+ lsp_response_free(&message->u.response);
+ break;
+ }
+ memset(message, 0, sizeof *message);
+}
+
// technically there are "requests" and "notifications"
// notifications are different in that they don't have IDs and don't return responses.
-// the distinction isn't super important to us though.
+// this function handles both.
// returns the ID of the request
-static u64 write_request(LSP *lsp, const LSPRequest *request) {
- unsigned long long id = lsp->request_id++;
+static void write_request(LSP *lsp, const LSPRequest *request) {
StrBuilder builder = str_builder_new();
@@ -159,6 +250,20 @@ static u64 write_request(LSP *lsp, const LSPRequest *request) {
str_builder_append(&builder, "{\"jsonrpc\":\"2.0\",");
+ bool is_notification = request->type == LSP_INITIALIZED
+ || request->type == LSP_EXIT;
+ if (!is_notification) {
+ unsigned long long id = lsp->request_id++;
+ str_builder_appendf(&builder, "\"id\":%llu,", id);
+ LSPRequestTrackedInfo info = {
+ .id = id,
+ .type = request->type
+ };
+ SDL_LockMutex(lsp->requests_mutex);
+ arr_add(lsp->requests_tracked_info, info);
+ SDL_UnlockMutex(lsp->requests_mutex);
+ }
+
switch (request->type) {
case LSP_NONE:
// these are server-to-client-only requests
@@ -168,10 +273,10 @@ static u64 write_request(LSP *lsp, const LSPRequest *request) {
break;
case LSP_INITIALIZE: {
str_builder_appendf(&builder,
- "\"id\":%llu,\"method\":\"initialize\",\"params\":{"
+ "\"method\":\"initialize\",\"params\":{"
"\"processId\":%d,"
"\"capabilities\":{}"
- "}", id, process_get_id());
+ "}", process_get_id());
} break;
case LSP_INITIALIZED:
str_builder_append(&builder, "\"method\":\"initialized\"");
@@ -182,13 +287,12 @@ static u64 write_request(LSP *lsp, const LSPRequest *request) {
char *escaped_text = json_escape(open->file_contents);
str_builder_appendf(&builder,
- "\"id\":%llu,\"method\":\"textDocument/open\",\"params\":{"
+ "\"method\":\"textDocument/open\",\"params\":{"
"textDocument:{"
"uri:\"file://%s\","
"languageId:\"%s\","
"version:1,"
"text:\"%s\"}}",
- id,
escaped_filename,
lsp_language_id(open->language),
escaped_text);
@@ -198,24 +302,23 @@ static u64 write_request(LSP *lsp, const LSPRequest *request) {
case LSP_COMPLETION: {
const LSPRequestCompletion *completion = &request->data.completion;
char *escaped_path = json_escape(completion->position.path);
- str_builder_appendf(&builder,"\"id\":%llu,\"method\":\"textDocument/completion\",\"params\":{"
- "textDocument:\"%s\","
- "position:{"
- "line:%lu,"
- "character:%lu"
+ str_builder_appendf(&builder,"\"method\":\"textDocument/completion\",\"params\":{"
+ "\"textDocument\":{\"uri\":\"file://%s\"},"
+ "\"position\":{"
+ "\"line\":%lu,"
+ "\"character\":%lu"
"}"
"}",
- id,
escaped_path,
(ulong)completion->position.line,
(ulong)completion->position.character);
free(escaped_path);
} break;
case LSP_SHUTDOWN:
- str_builder_appendf(&builder, "\"id\":%llu,\"method\":\"shutdown\"", id);
+ str_builder_append(&builder, "\"method\":\"shutdown\"");
break;
case LSP_EXIT:
- str_builder_appendf(&builder, "\"method\":\"exit\"");
+ str_builder_append(&builder, "\"method\":\"exit\"");
break;
}
@@ -242,8 +345,6 @@ static u64 write_request(LSP *lsp, const LSPRequest *request) {
process_write(&lsp->process, content, strlen(content));
str_builder_free(&builder);
-
- return (u64)id;
}
// figure out if data begins with a complete LSP response.
@@ -276,7 +377,7 @@ static bool parse_server2client_request(LSP *lsp, JSON *json, LSPRequest *reques
}
char str[64] = {0};
- json_string_get(json, &method.val.string, str, sizeof str);
+ json_string_get(json, method.val.string, str, sizeof str);
if (streq(str, "window/showMessage")) {
request->type = LSP_SHOW_MESSAGE;
@@ -303,7 +404,7 @@ static bool parse_server2client_request(LSP *lsp, JSON *json, LSPRequest *reques
LSPRequestMessage *m = &request->data.message;
m->type = (LSPWindowMessageType)mtype;
- m->message = json_string_get_alloc(json, &message.val.string);
+ m->message = json_string_get_alloc(json, message.val.string);
return true;
} else if (str_has_prefix(str, "$/")) {
// we can safely ignore this
@@ -313,19 +414,87 @@ static bool parse_server2client_request(LSP *lsp, JSON *json, LSPRequest *reques
return false;
}
+const char *lsp_response_string(const LSPResponse *response, LSPResponseString string) {
+ assert(string.offset < arr_len(response->string_data));
+ return &response->string_data[string.offset];
+}
+
+static LSPResponseString lsp_response_add_json_string(LSPResponse *response, const JSON *json, JSONString string) {
+ u32 offset = arr_len(response->string_data);
+ arr_set_len(response->string_data, offset + string.len + 1);
+ json_string_get(json, string, response->string_data + offset, string.len + 1);
+ return (LSPResponseString){
+ .offset = offset
+ };
+}
+
+static bool parse_completion(LSP *lsp, const JSON *json, LSPResponse *response) {
+ // deal with textDocument/completion response.
+ // result: CompletionItem[] | CompletionList | null
+ response->type = LSP_COMPLETION;
+ LSPResponseCompletion *completion = &response->data.completion;
+
+ JSONValue result = json_get(json, "result");
+ JSONValue items_value = {0};
+ switch (result.type) {
+ case JSON_NULL:
+ // no completions
+ return true;
+ case JSON_ARRAY:
+ items_value = result;
+ break;
+ case JSON_OBJECT:
+ items_value = json_object_get(json, &result.val.object, "items");
+ break;
+ default:
+ lsp_set_error(lsp, "Weird result type for textDocument/completion response: %s.", json_type_to_str(result.type));
+ break;
+ }
+
+ if (items_value.type != JSON_ARRAY) {
+ lsp_set_error(lsp, "Expected array for completion list, got %s", json_type_to_str(items_value.type));
+ return false;
+ }
+
+ JSONArray items = items_value.val.array;
+
+ arr_set_len(completion->items, items.len);
+
+ for (u32 i = 0; i < items.len; ++i) {
+ LSPCompletionItem *item = &completion->items[i];
+
+ JSONValue item_value = json_array_get(json, &items, i);
+ if (item_value.type != JSON_OBJECT) {
+ lsp_set_error(lsp, "Expected array for completion list, got %s", json_type_to_str(items_value.type));
+ return false;
+ }
+ JSONObject item_object = item_value.val.object;
+ JSONValue label_value = json_object_get(json, &item_object, "label");
+ if (label_value.type != JSON_STRING) {
+ lsp_set_error(lsp, "Expected string for completion label, got %s", json_type_to_str(label_value.type));
+ return false;
+ }
+ JSONString label = label_value.val.string;
+ item->label = lsp_response_add_json_string(response, json, label);
+ printf("%s\n",lsp_response_string(response,item->label));//@TODO
+ }
+
+ return true;
+}
+
+
static void process_message(LSP *lsp, JSON *json) {
- #if 1
+ #if 0
printf("\x1b[3m");
json_debug_print(json);
printf("\x1b[0m\n");
#endif
- bool keep_json = false;
-
+
JSONValue error = json_get(json, "error.message");
if (error.type == JSON_STRING) {
char err[256] = {0};
- json_string_get(json, &error.val.string, err, sizeof err);;
+ json_string_get(json, error.val.string, err, sizeof err);;
lsp_set_error(lsp, "%s", err);
goto ret;
}
@@ -347,19 +516,42 @@ static void process_message(LSP *lsp, JSON *json) {
.type = LSP_INITIALIZED,
.data = {0},
};
- u64 initialized_id = write_request(lsp, &initialized);
- // this should be the second request.
- (void)initialized_id;
- assert(initialized_id == 1);
+ write_request(lsp, &initialized);
// we can now send requests which have nothing to do with initialization
lsp->initialized = true;
} else {
- SDL_LockMutex(lsp->messages_mutex);
- LSPMessage *message = arr_addp(lsp->messages);
- message->type = LSP_RESPONSE;
- message->u.response = *json;
- SDL_UnlockMutex(lsp->messages_mutex);
- keep_json = true;
+ u64 id_no = (u64)id.val.number;
+ LSPRequestTrackedInfo tracked_info = {0};
+ SDL_LockMutex(lsp->requests_mutex);
+ arr_foreach_ptr(lsp->requests_tracked_info, LSPRequestTrackedInfo, info) {
+ if (info->id == id_no) {
+ // hey its the thing
+ tracked_info = *info;
+ arr_remove(lsp->requests_tracked_info, (u32)(info - lsp->requests_tracked_info));
+ break;
+ }
+ }
+ SDL_UnlockMutex(lsp->requests_mutex);
+
+ LSPResponse response = {0};
+ bool success = false;
+ switch (tracked_info.type) {
+ case LSP_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);
+ } else {
+ lsp_response_free(&response);
+ }
}
} else if (json_has(json, "method")) {
LSPRequest request = {0};
@@ -374,8 +566,7 @@ static void process_message(LSP *lsp, JSON *json) {
lsp_set_error(lsp, "Bad message from server (no result, no method).");
}
ret:
- if (!keep_json)
- json_free(json);
+ json_free(json);
}
@@ -408,7 +599,7 @@ static void lsp_receive(LSP *lsp, size_t max_size) {
// kind of a hack. this is needed because arr_set_len zeroes the data.
arr_hdr_(lsp->received_data)->len = (u32)received_so_far;
lsp->received_data[received_so_far] = '\0';// null terminate
- #if 0
+ #if 1
printf("\x1b[3m%s\x1b[0m\n",lsp->received_data);
#endif
@@ -433,47 +624,6 @@ static void lsp_receive(LSP *lsp, size_t max_size) {
}
}
-static void lsp_position_free(LSPDocumentPosition *position) {
- free(position->path);
-}
-
-static void lsp_request_free(LSPRequest *r) {
- switch (r->type) {
- case LSP_NONE:
- assert(0);
- break;
- case LSP_INITIALIZE:
- case LSP_INITIALIZED:
- case LSP_SHUTDOWN:
- case LSP_EXIT:
- break;
- case LSP_COMPLETION: {
- LSPRequestCompletion *completion = &r->data.completion;
- lsp_position_free(&completion->position);
- } break;
- case LSP_OPEN: {
- LSPRequestOpen *open = &r->data.open;
- free(open->filename);
- free(open->file_contents);
- } break;
- case LSP_SHOW_MESSAGE:
- case LSP_LOG_MESSAGE:
- free(r->data.message.message);
- break;
- }
-}
-
-void lsp_message_free(LSPMessage *message) {
- switch (message->type) {
- case LSP_REQUEST:
- lsp_request_free(&message->u.request);
- break;
- case LSP_RESPONSE:
- json_free(&message->u.response);
- break;
- }
-}
-
// send requests.
static bool lsp_send(LSP *lsp) {
if (!lsp->initialized) {
@@ -574,6 +724,7 @@ bool lsp_create(LSP *lsp, const char *analyzer_command) {
lsp->quit_sem = SDL_CreateSemaphore(0);
lsp->messages_mutex = SDL_CreateMutex();
+ lsp->requests_mutex = SDL_CreateMutex();
lsp->communication_thread = SDL_CreateThread(lsp_communication_thread, "LSP communicate", lsp);
return true;
}
diff --git a/main.c b/main.c
index ebe9729..28fb1d6 100644
--- a/main.c
+++ b/main.c
@@ -289,26 +289,26 @@ int main(int argc, char **argv) {
// @TODO TEMPORARY
{
LSP lsp={0};
+ chdir("/p/test-lsp");
if (!lsp_create(&lsp, "rust-analyzer")) {
printf("lsp_create: %s\n",lsp.error);
exit(1);
}
- usleep(500000);//if we don't do this we get "waiting for cargo metadata or cargo check"
+ usleep(1000000);//if we don't do this we get "waiting for cargo metadata or cargo check"
LSPRequest test_req = {.type = LSP_COMPLETION};
test_req.data.completion = (LSPRequestCompletion){
.position = {
- .path = str_dup("/p/ted/test.rs"),
- .line = 21,
- .character = 14,
+ .path = str_dup("/p/test-lsp/src/main.rs"),
+ .line = 2,
+ .character = 2,
}
};
lsp_send_request(&lsp, &test_req);
while (1) {
LSPMessage message = {0};
- if (lsp_next_message(&lsp, &message)) {
+ while (lsp_next_message(&lsp, &message)) {
if (message.type == LSP_RESPONSE) {
- json_debug_print(&message.u.response);
- printf("\n");
+ printf("response type %u\n",message.u.response.type);
} else if (message.type == LSP_REQUEST) {
const LSPRequest *request = &message.u.request;
switch (request->type) {