summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2022-12-09 15:57:24 -0500
committerpommicket <pommicket@gmail.com>2022-12-09 15:57:24 -0500
commit2dce45002b9d4d93cc2db15b1697d13c1709fb92 (patch)
tree808efd41be71afe286a1d8bf83480ca1ff7df75d
parent27c9af7de5f47c33217be02107e31061366f481f (diff)
more lsp
-rw-r--r--base.h5
-rw-r--r--json.c101
-rw-r--r--lsp.c362
-rw-r--r--main.c37
-rw-r--r--test.rs1
-rw-r--r--util.c81
6 files changed, 457 insertions, 130 deletions
diff --git a/base.h b/base.h
index 5fa2b15..5ba03bc 100644
--- a/base.h
+++ b/base.h
@@ -116,6 +116,11 @@ typedef unsigned long long ullong;
#else
#define ATTRIBUTE_PRINTF(fmt_idx, arg_idx)
#endif
+#if _MSC_VER > 1400
+#define PRINTF_FORMAT_STRING _Printf_format_string_
+#else
+#define PRINTF_FORMAT_STRING
+#endif
#define Status bool WarnUnusedResult // false = error, true = success
diff --git a/json.c b/json.c
index 4835eb9..1ee29a1 100644
--- a/json.c
+++ b/json.c
@@ -25,7 +25,7 @@ typedef struct {
u32 elements;
} JSONArray;
-enum {
+typedef enum {
// note: json doesn't actually include undefined.
// this is only for returning things from json_get etc.
JSON_UNDEFINED,
@@ -36,10 +36,10 @@ enum {
JSON_STRING,
JSON_OBJECT,
JSON_ARRAY
-};
+} JSONValueType;
struct JSONValue {
- u8 type;
+ JSONValueType type;
union {
double number;
JSONString string;
@@ -59,6 +59,26 @@ typedef struct {
#define SKIP_WHITESPACE while (json_is_space(text[index])) ++index;
+const char *json_type_to_str(JSONValueType type) {
+ switch (type) {
+ case JSON_UNDEFINED:
+ return "undefined";
+ case JSON_NULL:
+ return "null";
+ case JSON_STRING:
+ return "string";
+ case JSON_NUMBER:
+ return "number";
+ case JSON_FALSE:
+ return "false";
+ case JSON_TRUE:
+ return "true";
+ case JSON_ARRAY:
+ return "array";
+ case JSON_OBJECT:
+ return "object";
+ }
+}
static bool json_parse_value(JSON *json, u32 *p_index, JSONValue *val);
@@ -446,10 +466,16 @@ JSONValue json_get(const JSON *json, const char *path) {
return curr_value;
}
+// equivalent to json_get(json, path).type != JSON_UNDEFINED, but more readable
+bool json_has(const JSON *json, const char *path) {
+ JSONValue value = json_get(json, path);
+ return value.type != JSON_UNDEFINED;
+}
+
// 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.
-static void json_string_get(const JSON *json, const JSONString *string, char *buf, size_t buf_sz) {
+void json_string_get(const JSON *json, const JSONString *string, char *buf, size_t buf_sz) {
const char *text = json->text;
if (buf_sz == 0) {
assert(0);
@@ -496,6 +522,15 @@ static void json_string_get(const JSON *json, const JSONString *string, char *bu
*buf = '\0';
}
+// returns a malloc'd null-terminated string.
+static char *json_string_get_alloc(const JSON *json, const 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);
+ return buf;
+}
+
#if __unix__
static void json_test_time_large(const char *filename) {
@@ -555,23 +590,67 @@ static void json_test_time_small(void) {
}
#endif
-static void json_debug_print(const JSON *json) {
+void json_debug_print(const JSON *json) {
printf("%u values (capacity %u, text length %zu)\n",
arr_len(json->values), arr_cap(json->values), strlen(json->text));
json_debug_print_value(json, &json->values[0]);
}
// e.g. converts "Hello\nworld" to "Hello\\nworld"
-// the return value is the # of bytes in the escaped string.
-static size_t json_escape_to(char *out, size_t out_sz, const char *in) {
- (void)out;(void)out_sz;(void)in;
- // @TODO
- abort();
+// if out_sz is at least 2 * strlen(str) + 1, the string will fit.
+void json_escape_to(char *out, size_t out_sz, const char *in) {
+ char *end = out + out_sz;
+ assert(out_sz);
+
+ --end; // leave room for null terminator
+
+ for (; *in; ++in) {
+ if (out + 1 > end) {
+ break;
+ }
+ char esc = '\0';
+ switch (*in) {
+ case '\0': goto brk;
+ case '\n':
+ esc = 'n';
+ goto escape;
+ case '\\':
+ esc = '\\';
+ goto escape;
+ case '"':
+ esc = '"';
+ goto escape;
+ case '\t':
+ esc = 't';
+ goto escape;
+ case '\r':
+ esc = 'r';
+ goto escape;
+ case '\f':
+ esc = 'f';
+ goto escape;
+ case '\b':
+ esc = 'b';
+ goto escape;
+ escape:
+ if (out + 2 > end)
+ goto brk;
+ *out++ = '\\';
+ *out++ = esc;
+ break;
+ default:
+ *out = *in;
+ ++out;
+ break;
+ }
+ }
+ brk:
+ *out = '\0';
}
// e.g. converts "Hello\nworld" to "Hello\\nworld"
// the resulting string should be free'd.
-static char *json_escape(const char *str) {
+char *json_escape(const char *str) {
size_t out_sz = 2 * strlen(str) + 1;
char *out = calloc(1, out_sz);
json_escape_to(out, out_sz, str);
diff --git a/lsp.c b/lsp.c
index 8b62853..bff1ed0 100644
--- a/lsp.c
+++ b/lsp.c
@@ -1,36 +1,86 @@
+// @TODO: documentation
+// @TODO : make sure offsets are utf-16!
// @TODO: maximum queue size for requests/responses just in case?
typedef enum {
+ LSP_REQUEST,
+ LSP_RESPONSE
+} LSPMessageType;
+
+typedef enum {
LSP_NONE,
+
+ // client-to-server
LSP_INITIALIZE,
LSP_INITIALIZED,
LSP_OPEN,
LSP_COMPLETION,
LSP_SHUTDOWN,
- LSP_EXIT
+ LSP_EXIT,
+
+ // server-to-client
+ LSP_SHOW_MESSAGE,
+ LSP_LOG_MESSAGE
} LSPRequestType;
typedef struct {
// buffer language
Language language;
- // these will be free'd.
+ // freed by lsp_request_free
char *filename;
+ // freed by lsp_request_free
char *file_contents;
} LSPRequestOpen;
+typedef enum {
+ ERROR = 1,
+ WARNING = 2,
+ INFO = 3,
+ LOG = 4
+} LSPWindowMessageType;
+
+typedef struct {
+ LSPWindowMessageType type;
+ // freed by lsp_request_free
+ char *message;
+} LSPRequestMessage;
+
+typedef struct {
+ // freed by lsp_request_free
+ char *path;
+ u32 line;
+ u32 character;
+} LSPDocumentPosition;
+
+typedef struct {
+ LSPDocumentPosition position;
+} LSPRequestCompletion;
+
typedef struct {
LSPRequestType type;
union {
LSPRequestOpen open;
+ LSPRequestCompletion completion;
+ // for LSP_SHOW_MESSAGE and LSP_LOG_MESSAGE
+ LSPRequestMessage message;
} data;
} LSPRequest;
typedef struct {
+ LSPMessageType type;
+ union {
+ LSPRequest request;
+ JSON response;
+ } u;
+} LSPMessage;
+
+typedef struct {
Process process;
u64 request_id;
- JSON *responses;
- SDL_mutex *responses_mutex;
- LSPRequest *requests;
+ LSPMessage *messages;
+ SDL_mutex *messages_mutex;
+ LSPRequest *requests_client2server;
+ LSPRequest *requests_server2client;
SDL_mutex *requests_mutex;
bool initialized; // has the response to the initialize request been sent?
SDL_Thread *communication_thread;
@@ -62,17 +112,6 @@ bool lsp_get_error(LSP *lsp, char *error, size_t error_size, bool clear) {
SDL_UnlockMutex(lsp->error_mutex);\
} while (0)
-static void write_request_content(LSP *lsp, const char *content) {
- char header[128];
- size_t content_size = strlen(content);
- strbuf_printf(header, "Content-Length: %zu\r\n\r\n", content_size);
- #if 0
- printf("\x1b[1m%s%s\x1b[0m\n", header, content);
- #endif
- process_write(&lsp->process, header, strlen(header));
- process_write(&lsp->process, content, content_size);
-}
-
static const char *lsp_language_id(Language lang) {
switch (lang) {
case LANG_CONFIG:
@@ -112,67 +151,98 @@ static const char *lsp_language_id(Language lang) {
static u64 write_request(LSP *lsp, const LSPRequest *request) {
unsigned long long id = lsp->request_id++;
+ StrBuilder builder = str_builder_new();
+
+ u32 max_header_size = 64;
+ // this is where our header will go
+ str_builder_append_null(&builder, max_header_size);
+
+ str_builder_append(&builder, "{\"jsonrpc\":\"2.0\",");
+
switch (request->type) {
case LSP_NONE:
+ // these are server-to-client-only requests
+ case LSP_SHOW_MESSAGE:
+ case LSP_LOG_MESSAGE:
assert(0);
break;
case LSP_INITIALIZE: {
- char content[1024];
- strbuf_printf(content,
- "{\"jsonrpc\":\"2.0\",\"id\":%llu,\"method\":\"initialize\",\"params\":{"
+ str_builder_appendf(&builder,
+ "\"id\":%llu,\"method\":\"initialize\",\"params\":{"
"\"processId\":%d,"
"\"capabilities\":{}"
- "}}", id, process_get_id());
- write_request_content(lsp, content);
- } break;
- case LSP_INITIALIZED: {
- write_request_content(lsp, "{\"jsonrpc\":\"2.0\",\"method\":\"initialized\"}");
+ "}", id, process_get_id());
} break;
+ case LSP_INITIALIZED:
+ str_builder_append(&builder, "\"method\":\"initialized\"");
+ break;
case LSP_OPEN: {
const LSPRequestOpen *open = &request->data.open;
char *escaped_filename = json_escape(open->filename);
- char *did_open = a_sprintf(
- "{\"jsonrpc\":\"2.0\",\"id\":%llu,\"method\":\"textDocument/open\",\"params\":{"
+ char *escaped_text = json_escape(open->file_contents);
+
+ str_builder_appendf(&builder,
+ "\"id\":%llu,\"method\":\"textDocument/open\",\"params\":{"
"textDocument:{"
"uri:\"file://%s\","
"languageId:\"%s\","
"version:1,"
- "text:\"",
- id, escaped_filename, lsp_language_id(open->language));
+ "text:\"%s\"}}",
+ id,
+ escaped_filename,
+ lsp_language_id(open->language),
+ escaped_text);
+ free(escaped_text);
free(escaped_filename);
-
- size_t did_open_sz = strlen(did_open) + 2 * strlen(open->file_contents) + 16;
- did_open = realloc(did_open, did_open_sz);
-
- size_t n = json_escape_to(did_open + strlen(did_open),
- did_open_sz - 10 - strlen(did_open),
- open->file_contents);
- char *p = did_open + n;
- sprintf(p, "\"}}}");
-
- free(did_open);
-
- write_request_content(lsp, did_open);
} break;
case LSP_COMPLETION: {
- char content[1024];
- // no params needed
- strbuf_printf(content,
- "{\"jsonrpc\":\"2.0\",\"id\":%llu,\"method\":\"textDocument/completion\",\"params\":{"
- "}}", id);
- write_request_content(lsp, content);
- } break;
- case LSP_SHUTDOWN: {
- char content[1024];
- strbuf_printf(content,
- "{\"jsonrpc\":\"2.0\",\"id\":%llu,\"method\":\"shutdown\"}", id);
- write_request_content(lsp, content);
- } break;
- case LSP_EXIT: {
- write_request_content(lsp, "{\"jsonrpc\":\"2.0\",\"method\":\"exit\"}");
+ 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"
+ "}"
+ "}",
+ 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);
+ break;
+ case LSP_EXIT:
+ str_builder_appendf(&builder, "\"method\":\"exit\"");
+ break;
}
+ str_builder_append(&builder, "}");
+
+ // this is kind of hacky but it lets us send the whole request with one write call.
+ // probably not *actually* needed. i thought it would help fix an error but it didn't.
+ size_t content_length = str_builder_len(&builder) - max_header_size;
+ char content_length_str[32];
+ sprintf(content_length_str, "%zu", content_length);
+ size_t header_size = strlen("Content-Length: \r\n\r\n") + strlen(content_length_str);
+ char *header = &builder.str[max_header_size - header_size];
+ strcpy(header, "Content-Length: ");
+ strcat(header, content_length_str);
+ // we specifically DON'T want a null byte
+ memcpy(header + strlen(header), "\r\n\r\n", 4);
+
+ char *content = header;
+ #if 1
+ printf("\x1b[1m%s\x1b[0m\n",content);
+ #endif
+
+ // @TODO: does write always write the full amount? probably not. this should be fixed.
+ process_write(&lsp->process, content, strlen(content));
+
+ str_builder_free(&builder);
+
return (u64)id;
}
@@ -195,55 +265,121 @@ static bool has_response(const char *data, size_t data_len, u64 *p_offset, u64 *
void lsp_send_request(LSP *lsp, const LSPRequest *request) {
SDL_LockMutex(lsp->requests_mutex);
- arr_add(lsp->requests, *request);
+ arr_add(lsp->requests_client2server, *request);
SDL_UnlockMutex(lsp->requests_mutex);
}
-static void process_response(LSP *lsp, const JSON *json) {
+static bool parse_server2client_request(LSP *lsp, JSON *json, LSPRequest *request) {
+ JSONValue method = json_get(json, "method");
+ if (method.type != JSON_STRING) {
+ lsp_set_error(lsp, "Bad type for request method: %s", json_type_to_str(method.type));
+ }
+
+ char str[64] = {0};
+ json_string_get(json, &method.val.string, str, sizeof str);
+
+ if (streq(str, "window/showMessage")) {
+ request->type = LSP_SHOW_MESSAGE;
+ goto window_message;
+ } else if (streq(str, "window/logMessage")) {
+ request->type = LSP_LOG_MESSAGE;
+ window_message:;
+ JSONValue type = json_get(json, "params.type");
+ JSONValue message = json_get(json, "params.message");
+ if (type.type != JSON_NUMBER) {
+ lsp_set_error(lsp, "Expected MessageType, got %s", json_type_to_str(type.type));
+ return false;
+ }
+ if (message.type != JSON_STRING) {
+ lsp_set_error(lsp, "Expected message string, got %s", json_type_to_str(message.type));
+ return false;
+ }
+
+ int mtype = (int)type.val.number;
+ if (mtype < 1 || mtype > 4) {
+ lsp_set_error(lsp, "Bad MessageType: %g", type.val.number);
+ return false;
+ }
+
+ LSPRequestMessage *m = &request->data.message;
+ m->type = (LSPWindowMessageType)mtype;
+ m->message = json_string_get_alloc(json, &message.val.string);
+ return true;
+ } else if (str_has_prefix(str, "$/")) {
+ // we can safely ignore this
+ } else {
+ lsp_set_error(lsp, "Unrecognized request method: %s", str);
+ }
+ return false;
+}
+
+static void process_message(LSP *lsp, JSON *json) {
#if 1
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);;
+ lsp_set_error(lsp, "%s", err);
+ goto ret;
+ }
JSONValue result = json_get(json, "result");
- JSONValue id = json_get(json, "id");
+ if (result.type != JSON_UNDEFINED) {
+ JSONValue id = json_get(json, "id");
- if (result.type == JSON_UNDEFINED || id.type != JSON_NUMBER) {
- // uh oh
- 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);;
- lsp_set_error(lsp, "%s", err);
+ if (id.type != JSON_NUMBER) {
+ // what
+ lsp_set_error(lsp, "Response with no ID.");
+ goto ret;
+ }
+ if (id.val.number == 0) {
+ // it's the response to our initialize request!
+ // let's send back an "initialized" request (notification) because apparently
+ // that's something we need to do.
+ LSPRequest initialized = {
+ .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);
+ // we can now send requests which have nothing to do with initialization
+ lsp->initialized = true;
} else {
- lsp_set_error(lsp, "Server error (no message)");
+ 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;
+ }
+ } else if (json_has(json, "method")) {
+ LSPRequest request = {0};
+ if (parse_server2client_request(lsp, json, &request)) {
+ SDL_LockMutex(lsp->messages_mutex);
+ LSPMessage *message = arr_addp(lsp->messages);
+ message->type = LSP_REQUEST;
+ message->u.request = request;
+ SDL_UnlockMutex(lsp->messages_mutex);
}
- } else if (id.val.number == 0) {
- // it's the response to our initialize request!
- // let's send back an "initialized" request (notification) because apparently
- // that's something we need to do.
- LSPRequest initialized = {
- .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);
- // we can now send requests which have nothing to do with initialization
- lsp->initialized = true;
} else {
- SDL_LockMutex(lsp->responses_mutex);
- arr_add(lsp->responses, *json);
- SDL_UnlockMutex(lsp->responses_mutex);
+ lsp_set_error(lsp, "Bad message from server (no result, no method).");
}
+ ret:
+ if (!keep_json)
+ json_free(json);
}
-// receive responses from LSP, up to max_size bytes.
+// receive responses/requests/notifications from LSP, up to max_size bytes.
static void lsp_receive(LSP *lsp, size_t max_size) {
{
@@ -283,9 +419,8 @@ static void lsp_receive(LSP *lsp, size_t max_size) {
if (json_parse(&json, copy)) {
assert(json.text == copy);
json.is_text_copied = true;
- process_response(lsp, &json);
+ process_message(lsp, &json);
} else {
-
lsp_set_error(lsp, "couldn't parse response JSON: %s", json.error);
json_free(&json);
}
@@ -298,22 +433,44 @@ static void lsp_receive(LSP *lsp, size_t max_size) {
}
}
-static void free_request(LSPRequest *r) {
+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_COMPLETION:
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;
}
}
@@ -326,10 +483,10 @@ static bool lsp_send(LSP *lsp) {
LSPRequest *requests = NULL;
SDL_LockMutex(lsp->requests_mutex);
- size_t n_requests = arr_len(lsp->requests);
+ size_t n_requests = arr_len(lsp->requests_client2server);
requests = calloc(n_requests, sizeof *requests);
- memcpy(requests, lsp->requests, n_requests * sizeof *requests);
- arr_clear(lsp->requests);
+ memcpy(requests, lsp->requests_client2server, n_requests * sizeof *requests);
+ arr_clear(lsp->requests_client2server);
SDL_UnlockMutex(lsp->requests_mutex);
bool quit = false;
@@ -340,7 +497,7 @@ static bool lsp_send(LSP *lsp) {
// whatever.
write_request(lsp, r);
}
- free_request(r);
+ lsp_request_free(r);
if (SDL_SemTryWait(lsp->quit_sem) == 0) {
quit = true;
@@ -416,27 +573,32 @@ bool lsp_create(LSP *lsp, const char *analyzer_command) {
write_request(lsp, &initialize);
lsp->quit_sem = SDL_CreateSemaphore(0);
- lsp->responses_mutex = SDL_CreateMutex();
+ lsp->messages_mutex = SDL_CreateMutex();
lsp->communication_thread = SDL_CreateThread(lsp_communication_thread, "LSP communicate", lsp);
return true;
}
-bool lsp_next_response(LSP *lsp, JSON *json) {
+bool lsp_next_message(LSP *lsp, LSPMessage *message) {
bool any = false;
- SDL_LockMutex(lsp->responses_mutex);
- if (arr_len(lsp->responses)) {
- *json = lsp->responses[0];
- arr_remove(lsp->responses, 0);
+ SDL_LockMutex(lsp->messages_mutex);
+ if (arr_len(lsp->messages)) {
+ *message = lsp->messages[0];
+ arr_remove(lsp->messages, 0);
any = true;
}
- SDL_UnlockMutex(lsp->responses_mutex);
+ SDL_UnlockMutex(lsp->messages_mutex);
return any;
}
void lsp_free(LSP *lsp) {
SDL_SemPost(lsp->quit_sem);
SDL_WaitThread(lsp->communication_thread, NULL);
+ SDL_DestroyMutex(lsp->messages_mutex);
SDL_DestroySemaphore(lsp->quit_sem);
process_kill(&lsp->process);
arr_free(lsp->received_data);
+ arr_foreach_ptr(lsp->messages, LSPMessage, message) {
+ lsp_message_free(message);
+ }
+ arr_free(lsp->messages);
}
diff --git a/main.c b/main.c
index 82a0ef1..ebe9729 100644
--- a/main.c
+++ b/main.c
@@ -45,7 +45,9 @@ no_warn_end
#endif
#include "unicode.h"
+#include "arr.c"
#include "util.c"
+
#if _WIN32
#include "filesystem-win.c"
#elif __unix__
@@ -53,7 +55,6 @@ no_warn_end
#else
#error "Unrecognized operating system."
#endif
-#include "arr.c"
#include "math.c"
#if _WIN32
@@ -294,13 +295,37 @@ int main(int argc, char **argv) {
}
usleep(500000);//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,
+ }
+ };
lsp_send_request(&lsp, &test_req);
while (1) {
- JSON response = {0};
- if (lsp_next_response(&lsp, &response)) {
- json_debug_print(&response);
- printf("\n");
- break;
+ LSPMessage message = {0};
+ if (lsp_next_message(&lsp, &message)) {
+ if (message.type == LSP_RESPONSE) {
+ json_debug_print(&message.u.response);
+ printf("\n");
+ } else if (message.type == LSP_REQUEST) {
+ const LSPRequest *request = &message.u.request;
+ switch (request->type) {
+ case LSP_SHOW_MESSAGE: {
+ const LSPRequestMessage *m = &request->data.message;
+ // @TODO actually show
+ printf("Show (%d): %s\n", m->type, m->message);
+ } break;
+ case LSP_LOG_MESSAGE: {
+ const LSPRequestMessage *m = &request->data.message;
+ // @TODO actually log
+ printf("Log (%d): %s\n", m->type, m->message);
+ } break;
+ default: break;
+ }
+ }
+ lsp_message_free(&message);
}
char error[256];
if (lsp_get_error(&lsp, error, sizeof error, true)) {
diff --git a/test.rs b/test.rs
index 6c852d9..40c2438 100644
--- a/test.rs
+++ b/test.rs
@@ -19,6 +19,7 @@ fn main() -> Result<()> {
line.pop();
lines.push(line);
}
+let x = lines.
for line in lines {
println!("{}", line);
}
diff --git a/util.c b/util.c
index fc73b1b..32411a7 100644
--- a/util.c
+++ b/util.c
@@ -97,19 +97,6 @@ static char *str_dup(char const *src) {
#define strbuf_catf(str, ...) assert(sizeof str != 4 && sizeof str != 8), \
str_catf(str, sizeof str, __VA_ARGS__)
-#if __unix__
-static char *a_sprintf(const char *fmt, ...) ATTRIBUTE_PRINTF(1, 2);
-static char *a_sprintf(const char *fmt, ...) {
- va_list args;
- va_start(args, fmt);
- char *str = NULL;
- vasprintf(&str, fmt, args);
- return str;
-}
-#else
-#error "@TODO"
-#endif
-
// on 16-bit systems, this is 16383. on 32/64-bit systems, this is 1073741823
// it is unusual to have a string that long.
#define STRLEN_SAFE_MAX (UINT_MAX >> 2)
@@ -372,3 +359,71 @@ static bool copy_file(char const *src, char const *dst) {
}
return success;
}
+
+typedef struct {
+ // dynamic array, including a null byte.
+ char *str;
+} StrBuilder;
+
+void str_builder_create(StrBuilder *builder) {
+ memset(builder, 0, sizeof *builder);
+ arr_add(builder->str, 0);
+}
+
+StrBuilder str_builder_new(void) {
+ StrBuilder ret = {0};
+ str_builder_create(&ret);
+ return ret;
+}
+
+void str_builder_free(StrBuilder *builder) {
+ arr_free(builder->str);
+}
+
+void str_builder_clear(StrBuilder *builder) {
+ str_builder_free(builder);
+ str_builder_create(builder);
+}
+
+void str_builder_append(StrBuilder *builder, const char *s) {
+ assert(builder->str);
+
+ size_t s_len = strlen(s);
+ size_t prev_size = arr_len(builder->str);
+ size_t prev_len = prev_size - 1; // null terminator
+ // note: this zeroes the newly created elements, so we have a new null terminator
+ arr_set_len(builder->str, prev_size + s_len);
+ // -1 for null terminator
+ memcpy(builder->str + prev_len, s, s_len);
+}
+
+void str_builder_appendf(StrBuilder *builder, PRINTF_FORMAT_STRING const char *fmt, ...) ATTRIBUTE_PRINTF(2, 3);
+void str_builder_appendf(StrBuilder *builder, const char *fmt, ...) {
+ // idk if you can always just pass NULL to vsnprintf
+ va_list args;
+ char fakebuf[2] = {0};
+ va_start(args, fmt);
+ int ret = vsnprintf(fakebuf, 1, fmt, args);
+ va_end(args);
+
+ if (ret < 0) return; // bad format or something
+ u32 n = (u32)ret;
+
+ size_t prev_size = arr_len(builder->str);
+ size_t prev_len = prev_size - 1; // null terminator
+ arr_set_len(builder->str, prev_size + n);
+ va_start(args, fmt);
+ vsnprintf(builder->str + prev_len, n + 1, fmt, args);
+ va_end(args);
+}
+
+// append n null bytes.
+void str_builder_append_null(StrBuilder *builder, size_t n) {
+ arr_set_len(builder->str, arr_len(builder->str) + n);
+}
+
+u32 str_builder_len(StrBuilder *builder) {
+ assert(builder->str);
+ return arr_len(builder->str) - 1;
+}
+