summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--buffer.c4
-rw-r--r--config.c1
-rw-r--r--development.md2
-rw-r--r--ide-hover.c4
-rw-r--r--lsp.c112
-rw-r--r--lsp.h196
-rw-r--r--main.c9
-rw-r--r--ted-internal.h1
-rw-r--r--ted.c25
-rw-r--r--ted.cfg9
-rw-r--r--ted.h3
11 files changed, 255 insertions, 111 deletions
diff --git a/buffer.c b/buffer.c
index ac2b532..eb6d438 100644
--- a/buffer.c
+++ b/buffer.c
@@ -516,7 +516,7 @@ static void buffer_send_lsp_did_open(TextBuffer *buffer, LSP *lsp) {
open->document = lsp_document_id(lsp, buffer->path);
open->language = buffer_language(buffer);
lsp_send_request(lsp, &request);
- buffer->lsp_opened_in = lsp->id;
+ buffer->lsp_opened_in = lsp_get_id(lsp);
}
LSP *buffer_lsp(TextBuffer *buffer) {
@@ -1911,7 +1911,7 @@ static void buffer_send_lsp_did_change(LSP *lsp, TextBuffer *buffer, BufferPos p
range.end = buffer_pos_to_lsp_position(buffer, pos_end);
const char *document = buffer->path;
- if (lsp->capabilities.incremental_sync_support) {
+ if (lsp_has_incremental_sync_support(lsp)) {
LSPRequest request = {.type = LSP_REQUEST_DID_CHANGE};
LSPDocumentChangeEvent change = {
.range = range,
diff --git a/config.c b/config.c
index 4f80044..8f8982a 100644
--- a/config.c
+++ b/config.c
@@ -149,6 +149,7 @@ static const SettingFloat settings_float[] = {
{"cursor-blink-time-off", &settings_zero.cursor_blink_time_off, 0, 1000, true},
{"hover-time", &settings_zero.hover_time, 0, INFINITY, true},
{"ctrl-scroll-adjust-text-size", &settings_zero.ctrl_scroll_adjust_text_size, -10, 10, true},
+ {"lsp-delay", &settings_zero.lsp_delay, 0, 100, true},
};
static const SettingString settings_string[] = {
{"build-default-command", settings_zero.build_default_command, sizeof settings_zero.build_default_command, true},
diff --git a/development.md b/development.md
index fc23ee5..e3e0519 100644
--- a/development.md
+++ b/development.md
@@ -96,7 +96,7 @@ When you add a source file to ted, make sure you:
## Adding settings
-Find the `Settings` struct in `ted.h` and add the new member.
+Find the `Settings` struct in `ted-internal.h` and add the new member.
Then go to `config.c` and edit the `settings_<type>` array.
## Adding commands
diff --git a/ide-hover.c b/ide-hover.c
index 59498f7..ff5fb19 100644
--- a/ide-hover.c
+++ b/ide-hover.c
@@ -90,7 +90,7 @@ void hover_process_lsp_response(Ted *ted, const LSPResponse *response) {
if (hover->text // we already have hover text
&& (
- lsp->id != hover->last_request.lsp // this request is from a different LSP
+ lsp_get_id(lsp) != hover->last_request.lsp // this request is from a different LSP
|| !lsp_document_position_eq(response->request.data.hover.position, pos) // this request is for a different position
)) {
// this is a stale request. ignore it
@@ -141,7 +141,7 @@ void hover_frame(Ted *ted, double dt) {
LSPDocumentPosition pos={0};
LSP *lsp=0;
if (get_hover_position(ted, &pos, &buffer, &lsp)) {
- if (lsp->id != hover->last_request.lsp
+ if (lsp_get_id(lsp) != hover->last_request.lsp
|| !lsp_document_position_eq(pos, hover->requested_position)) {
// refresh hover
hover_send_request(ted);
diff --git a/lsp.c b/lsp.c
index c828bf1..54a28d6 100644
--- a/lsp.c
+++ b/lsp.c
@@ -6,6 +6,10 @@
static LSPMutex request_id_mutex;
+u32 lsp_get_id(const LSP *lsp) {
+ return lsp->id;
+}
+
// it's nice to have request IDs be totally unique, including across LSP servers.
static LSPRequestID get_request_id(void) {
// it's important that this never returns 0, since that's reserved for "no ID"
@@ -479,11 +483,13 @@ static bool lsp_receive(LSP *lsp, size_t max_size) {
return true;
}
-// send requests.
+/// send requests.
+///
+/// returns `false` if we should quit.
static bool lsp_send(LSP *lsp) {
if (!lsp->initialized) {
// don't send anything before the server is initialized.
- return false;
+ return true;
}
LSPMessage *messages = NULL;
@@ -504,22 +510,46 @@ static bool lsp_send(LSP *lsp) {
SDL_UnlockMutex(lsp->messages_mutex);
- bool quit = false;
+ bool alive = true;
for (size_t i = 0; i < n_messages; ++i) {
LSPMessage *m = &messages[i];
- if (quit) {
- lsp_message_free(m);
- } else {
+ bool send = alive;
+ if (send && m->type == LSP_REQUEST
+ && i + 1 < n_messages
+ && messages[i + 1].type == LSP_REQUEST) {
+ const LSPRequest *r = &m->request;
+ const LSPRequest *next = &messages[i + 1].request;
+ if (r->type == LSP_REQUEST_DID_CHANGE
+ && next->type == LSP_REQUEST_DID_CHANGE
+ && arr_len(r->data.change.changes) == 1
+ && arr_len(next->data.change.changes) == 1
+ && !r->data.change.changes[0].use_range
+ && !next->data.change.changes[1].use_range
+ && r->data.change.document == next->data.change.document) {
+ // we don't need to send this request, since it's made
+ // irrelevant by the next request.
+ // (specifically, they're both full-document-content
+ // didChange notifications)
+ // this helps godot's language server a lot
+ // since it's super slow because it tries to publish diagnostics
+ // on every change.
+ send = false;
+ }
+ }
+
+ if (send) {
write_message(lsp, m);
+ } else {
+ lsp_message_free(m);
}
if (SDL_SemTryWait(lsp->quit_sem) == 0) {
- quit = true;
+ alive = false;
}
}
free(messages);
- return quit;
+ return alive;
}
@@ -543,10 +573,20 @@ static int lsp_communication_thread(void *data) {
initialize.id = get_request_id();
write_request(lsp, &initialize);
+ const double send_delay = lsp->send_delay;
+ double last_send = -DBL_MAX;
while (1) {
- bool quit = lsp_send(lsp);
- if (quit) break;
-
+ bool send = true;
+ if (send_delay > 0) {
+ double t = time_get_seconds();
+ if (t - last_send > send_delay) {
+ last_send = t;
+ } else {
+ send = false;
+ }
+ }
+ if (send && !lsp_send(lsp))
+ break;
if (!lsp_receive(lsp, (size_t)10<<20))
break;
if (SDL_SemWaitTimeout(lsp->quit_sem, 5) == 0)
@@ -569,13 +609,11 @@ static int lsp_communication_thread(void *data) {
.type = LSP_REQUEST_EXIT,
.data = {{0}}
};
+ // just spam these things
+ // we're supposed to be nice and wait for the shutdown
+ // response, but who gives a fuck
write_request(lsp, &shutdown);
- // i give you ONE MILLISECOND to send your fucking shutdown response
- time_sleep_ms(1);
write_request(lsp, &exit);
- // i give you ONE MILLISECOND to terminate
- // I WILL KILL YOU IF IT TAKES ANY LONGER
- time_sleep_ms(1);
#if 0
char buf[1024]={0};
@@ -626,16 +664,22 @@ const char *lsp_document_path(LSP *lsp, LSPDocumentID document) {
return path;
}
-LSP *lsp_create(const char *root_dir, const char *command, u16 port, const char *configuration, FILE *log) {
+LSP *lsp_create(const LSPSetup *setup) {
LSP *lsp = calloc(1, sizeof *lsp);
if (!lsp) return NULL;
if (!request_id_mutex)
request_id_mutex = SDL_CreateMutex();
+ const char *const command = setup->command;
+ const u16 port = setup->port;
+ const char *const root_dir = setup->root_dir;
+ const char *const configuration = setup->configuration;
+
static LSPID curr_id = 1;
lsp->id = curr_id++;
- lsp->log = log;
+ lsp->log = setup->log;
lsp->port = port;
+ lsp->send_delay = setup->send_delay;
#if DEBUG
printf("Starting up LSP %p (ID %u) `%s` (port %u) in %s\n",
@@ -811,6 +855,18 @@ LSPDocumentPosition lsp_location_end_position(LSPLocation location) {
};
}
+const uint32_t *lsp_completion_trigger_chars(LSP *lsp) {
+ return lsp->completion_trigger_chars;
+}
+
+const uint32_t *lsp_signature_help_trigger_chars(LSP *lsp) {
+ return lsp->signature_help_trigger_chars;
+}
+
+const uint32_t *lsp_signature_help_retrigger_chars(LSP *lsp) {
+ return lsp->signature_help_retrigger_chars;
+}
+
bool lsp_covers_path(LSP *lsp, const char *path) {
bool ret = false;
SDL_LockMutex(lsp->workspace_folders_mutex);
@@ -856,6 +912,26 @@ void lsp_cancel_request(LSP *lsp, LSPRequestID id) {
}
}
+bool lsp_has_exited(LSP *lsp) {
+ return lsp->exited;
+}
+
+bool lsp_is_initialized(LSP *lsp) {
+ return lsp->initialized;
+}
+
+bool lsp_has_incremental_sync_support(LSP *lsp) {
+ return lsp->capabilities.incremental_sync_support;
+}
+
+const char *lsp_get_command(LSP *lsp) {
+ return lsp->command;
+}
+
+u16 lsp_get_port(LSP *lsp) {
+ return lsp->port;
+}
+
void lsp_quit(void) {
if (request_id_mutex) {
SDL_DestroyMutex(request_id_mutex);
diff --git a/lsp.h b/lsp.h
index 8361f60..2e16045 100644
--- a/lsp.h
+++ b/lsp.h
@@ -616,21 +616,126 @@ typedef struct {
bool range_formatting_support;
} LSPCapabilities;
-typedef struct LSP {
+typedef struct LSP LSP;
+
+/// arguments to \ref lsp_create, but in `struct` form because
+/// there are so many of them.
+typedef struct {
+ /// root directory
+ const char *root_dir;
+ /// command to run to start server (set to `NULL` if LSP is assumed to already be running)
+ const char *command;
+ /// port which server is listening on (set to 0 for LSP over stdio)
+ u16 port;
+ /// configuration JSON
+ const char *configuration;
+ /// log file, or `NULL` to disable logging
+ FILE *log;
+ /// see `LSP::send_delay`
+ double send_delay;
+} LSPSetup;
+
+/// Start up an LSP server.
+LSP *lsp_create(const LSPSetup *setup);
+/// get unique ID associated with this server.
+u32 lsp_get_id(const LSP *lsp);
+/// has the server been initialized?
+bool lsp_is_initialized(LSP *lsp);
+/// \returns the \ref LSPSetup::command value passed into \ref lsp_create
+const char *lsp_get_command(LSP *lsp);
+/// \returns the \ref LSPSetup::port value passed into \ref lsp_create
+u16 lsp_get_port(LSP *lsp);
+/// has the server exited?
+bool lsp_has_exited(LSP *lsp);
+/// Assiociate `id` with the LSP language identifier `lsp_identifier` (see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#-textdocumentitem-)
+void lsp_register_language(u64 id, const char *lsp_identifier);
+// returns true if there's an error.
+// returns false and sets error to "" if there's no error.
+// if clear = true, the error will be cleared.
+// you can set error = NULL, error_size = 0, clear = true to just clear the error
+bool lsp_get_error(LSP *lsp, char *error, size_t error_size, bool clear);
+void lsp_message_free(LSPMessage *message);
+u32 lsp_document_id(LSP *lsp, const char *path);
+// returned pointer lives as long as lsp.
+const char *lsp_document_path(LSP *lsp, LSPDocumentID id);
+// returns the ID of the sent request, or (LSPServerRequestID){0} if the request is not supported by the LSP
+// don't free the contents of this request (even on failure)! let me handle it!
+LSPServerRequestID lsp_send_request(LSP *lsp, LSPRequest *request);
+// send a $/cancelRequest notification
+// if id = 0, nothing will happen.
+void lsp_cancel_request(LSP *lsp, LSPRequestID id);
+// don't free the contents of this response! let me handle it!
+void lsp_send_response(LSP *lsp, LSPResponse *response);
+const char *lsp_response_string(const LSPResponse *response, LSPString string);
+const char *lsp_request_string(const LSPRequest *request, LSPString string);
+/// low-level API for allocating message strings.
+///
+/// sets `*string` to the LSPString, and returns a pointer which you can write the string to.
+/// the returned pointer will be zeroed up to and including [len].
+char *lsp_message_alloc_string(LSPMessageBase *message, size_t len, LSPString *string);
+LSPString lsp_message_add_string32(LSPMessageBase *message, String32 string);
+LSPString lsp_request_add_string(LSPRequest *request, const char *string);
+LSPString lsp_response_add_string(LSPResponse *response, const char *string);
+bool lsp_string_is_empty(LSPString string);
+// try to add a new "workspace folder" to the lsp.
+// IMPORTANT: only call this if lsp->initialized is true
+// (if not we don't yet know whether the server supports workspace folders)
+// returns true on success or if new_root_dir is already contained in a workspace folder for this LSP.
+// if this fails (i.e. if the LSP does not have workspace support), create a new LSP
+// with root directory `new_root_dir`.
+bool lsp_try_add_root_dir(LSP *lsp, const char *new_root_dir);
+// is this path in the LSP's workspace folders?
+bool lsp_covers_path(LSP *lsp, const char *path);
+// get next message from server
+bool lsp_next_message(LSP *lsp, LSPMessage *message);
+/// returns `-1` if `a` comes before `b`, 0 if `a` and `b` are equal, and `1` if `a` comes after `b`
+int lsp_position_cmp(LSPPosition a, LSPPosition b);
+/// returns `true` if `a` and `b` are equal
+bool lsp_position_eq(LSPPosition a, LSPPosition b);
+/// returns `true` if `a` and `b` overlap
+bool lsp_ranges_overlap(LSPRange a, LSPRange b);
+bool lsp_document_position_eq(LSPDocumentPosition a, LSPDocumentPosition b);
+/// does this server support incremental synchronization
+///
+/// see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_synchronization
+/// for more info.
+bool lsp_has_incremental_sync_support(LSP *lsp);
+/// get dynamic array of completion trigger characters.
+const uint32_t *lsp_completion_trigger_chars(LSP *lsp);
+/// get dynamic array of signature help trigger characters.
+const uint32_t *lsp_signature_help_trigger_chars(LSP *lsp);
+/// get dynamic array of signature help retrigger characters.
+const uint32_t *lsp_signature_help_retrigger_chars(LSP *lsp);
+// get the start of location's range as a LSPDocumentPosition
+LSPDocumentPosition lsp_location_start_position(LSPLocation location);
+// get the end of location's range as a LSPDocumentPosition
+LSPDocumentPosition lsp_location_end_position(LSPLocation location);
+void lsp_free(LSP *lsp);
+// call this to free any global resources used by lsp*.c
+// not strictly necessary, but prevents valgrind errors & stuff.
+// make sure you call lsp_free on every LSP you create before calling this.
+void lsp_quit(void);
+
+#endif // LSP_H_
+
+#if defined LSP_INTERNAL && !defined LSP_INTERNAL_H_
+#define LSP_INTERNAL_H_
+
+struct LSP {
// thread safety is important here!
// every member should either be indented to indicate which mutex controls it,
// or have a comment explaining why it doesn't need one
// A unique ID number for this LSP.
- // thread-safety: only set once in lsp_create.
+ // thread-safety: only set once in \ref lsp_create.
LSPID id;
- // thread-safety: set once in lsp_create, then only used by communication thread
+ // thread-safety: set once in \ref lsp_create, then only used by communication thread
FILE *log;
// The server process. May be NULL if the process isn't started by ted.
//
- // thread-safety: created in lsp_create, then only accessed by the communication thread
+ // thread-safety: created in \ref lsp_create, then only accessed by the communication thread
Process *process;
// Socket for communicating with server. Maybe be NULL if communication is done over stdio.
//
@@ -640,9 +745,17 @@ typedef struct LSP {
// port used for communication
//
// this will be zero iff communication is done over stdio
- // thread-safety: only set once in lsp_create
+ // thread-safety: only set once in \ref lsp_create
u16 port;
+ /// delay before sending requests.
+ ///
+ /// this exists for servers which don't support `$/cancelRequest`
+ /// to avoid flooding them with requests which they can't keep up with.
+ ///
+ /// thread-safety: only set once in \ref lsp_create
+ double send_delay;
+
LSPMutex document_mutex;
// for our purposes, folders are "documents"
// the spec kinda does this too: WorkspaceFolder has a `uri: DocumentUri` member.
@@ -688,74 +801,7 @@ typedef struct LSP {
LSPDocumentID *workspace_folders;
LSPMutex error_mutex;
char error[512];
-} LSP;
-
-/// Assiociate `id` with the LSP language identifier `lsp_identifier` (see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#-textdocumentitem-)
-void lsp_register_language(u64 id, const char *lsp_identifier);
-// returns true if there's an error.
-// returns false and sets error to "" if there's no error.
-// if clear = true, the error will be cleared.
-// you can set error = NULL, error_size = 0, clear = true to just clear the error
-bool lsp_get_error(LSP *lsp, char *error, size_t error_size, bool clear);
-void lsp_message_free(LSPMessage *message);
-u32 lsp_document_id(LSP *lsp, const char *path);
-// returned pointer lives as long as lsp.
-const char *lsp_document_path(LSP *lsp, LSPDocumentID id);
-// returns the ID of the sent request, or (LSPServerRequestID){0} if the request is not supported by the LSP
-// don't free the contents of this request (even on failure)! let me handle it!
-LSPServerRequestID lsp_send_request(LSP *lsp, LSPRequest *request);
-// send a $/cancelRequest notification
-// if id = 0, nothing will happen.
-void lsp_cancel_request(LSP *lsp, LSPRequestID id);
-// don't free the contents of this response! let me handle it!
-void lsp_send_response(LSP *lsp, LSPResponse *response);
-const char *lsp_response_string(const LSPResponse *response, LSPString string);
-const char *lsp_request_string(const LSPRequest *request, LSPString string);
-/// low-level API for allocating message strings.
-///
-/// sets `*string` to the LSPString, and returns a pointer which you can write the string to.
-/// the returned pointer will be zeroed up to and including [len].
-char *lsp_message_alloc_string(LSPMessageBase *message, size_t len, LSPString *string);
-LSPString lsp_message_add_string32(LSPMessageBase *message, String32 string);
-LSPString lsp_request_add_string(LSPRequest *request, const char *string);
-LSPString lsp_response_add_string(LSPResponse *response, const char *string);
-bool lsp_string_is_empty(LSPString string);
-/// Start up an LSP server.
-///
-/// `command`, `port`, `configuration` and `log` can be 0 as long as `port` and `command` are not both 0.
-LSP *lsp_create(const char *root_dir, const char *command, u16 port, const char *configuration, FILE *log);
-// try to add a new "workspace folder" to the lsp.
-// IMPORTANT: only call this if lsp->initialized is true
-// (if not we don't yet know whether the server supports workspace folders)
-// returns true on success or if new_root_dir is already contained in a workspace folder for this LSP.
-// if this fails (i.e. if the LSP does not have workspace support), create a new LSP
-// with root directory `new_root_dir`.
-bool lsp_try_add_root_dir(LSP *lsp, const char *new_root_dir);
-// is this path in the LSP's workspace folders?
-bool lsp_covers_path(LSP *lsp, const char *path);
-// get next message from server
-bool lsp_next_message(LSP *lsp, LSPMessage *message);
-/// returns `-1` if `a` comes before `b`, 0 if `a` and `b` are equal, and `1` if `a` comes after `b`
-int lsp_position_cmp(LSPPosition a, LSPPosition b);
-/// returns `true` if `a` and `b` are equal
-bool lsp_position_eq(LSPPosition a, LSPPosition b);
-/// returns `true` if `a` and `b` overlap
-bool lsp_ranges_overlap(LSPRange a, LSPRange b);
-bool lsp_document_position_eq(LSPDocumentPosition a, LSPDocumentPosition b);
-// get the start of location's range as a LSPDocumentPosition
-LSPDocumentPosition lsp_location_start_position(LSPLocation location);
-// get the end of location's range as a LSPDocumentPosition
-LSPDocumentPosition lsp_location_end_position(LSPLocation location);
-void lsp_free(LSP *lsp);
-// call this to free any global resources used by lsp*.c
-// not strictly necessary, but prevents valgrind errors & stuff.
-// make sure you call lsp_free on every LSP you create before calling this.
-void lsp_quit(void);
-
-#endif // LSP_H_
-
-#if defined LSP_INTERNAL && !defined LSP_INTERNAL_H_
-#define LSP_INTERNAL_H_
+};
#include "sdl-inc.h"
@@ -871,9 +917,9 @@ LSPString lsp_request_add_json_string(LSPRequest *request, const JSON *json, JSO
void lsp_write_quit(void);
/// print server-to-client communication
-#define LSP_SHOW_S2C 0
+#define LSP_SHOW_S2C 1
/// print client-to-server communication
-#define LSP_SHOW_C2S 0
+#define LSP_SHOW_C2S 1
#endif // LSP_INTERNAL
diff --git a/main.c b/main.c
index 5deabac..f12ee19 100644
--- a/main.c
+++ b/main.c
@@ -1,9 +1,8 @@
/*
TODO:
-- figure out how to deal with godot language server being so slow
- one comparatively solution is to wait x seconds before sending a batch of requests in the communication thread
- (this gives us time to cancel the irrelevant requests before they get sent to the server,
- and we can remove stale full-sync didChange requests)
+- on the last line of a buffer, shift+up end backspace acts weird
+- restart LSP server automatically?
+
FUTURE FEATURES:
- autodetect indentation (tabs vs spaces)
- custom file/build command associations
@@ -764,7 +763,7 @@ int main(int argc, char **argv) {
char32_t last_char = 0;
unicode_utf8_to_utf32(&last_char, &text[last_code_point],
strlen(text) - last_code_point);
- arr_foreach_ptr(lsp->completion_trigger_chars, char32_t, c) {
+ arr_foreach_ptr(lsp_completion_trigger_chars(lsp), const char32_t, c) {
if (*c == last_char) {
autocomplete_open(ted, last_char);
break;
diff --git a/ted-internal.h b/ted-internal.h
index 498516b..0efa6b6 100644
--- a/ted-internal.h
+++ b/ted-internal.h
@@ -104,6 +104,7 @@ struct Settings {
float cursor_blink_time_on, cursor_blink_time_off;
float hover_time;
float ctrl_scroll_adjust_text_size;
+ float lsp_delay;
u32 max_file_size;
u32 max_file_size_view_only;
u16 framerate_cap;
diff --git a/ted.c b/ted.c
index 5167900..3c09f01 100644
--- a/ted.c
+++ b/ted.c
@@ -230,8 +230,8 @@ LSP *ted_get_lsp_by_id(Ted *ted, LSPID id) {
if (id == 0) return NULL;
for (int i = 0; ted->lsps[i]; ++i) {
LSP *lsp = ted->lsps[i];
- if (lsp->id == id)
- return lsp->exited ? NULL : lsp;
+ if (lsp_get_id(lsp) == id)
+ return lsp_has_exited(lsp) ? NULL : lsp;
}
return NULL;
}
@@ -245,16 +245,18 @@ LSP *ted_get_lsp(Ted *ted, const char *path, Language language) {
for (i = 0; i < TED_LSP_MAX; ++i) {
LSP *lsp = ted->lsps[i];
if (!lsp) break;
- if (lsp->command && !streq(lsp->command, settings->lsp)) continue;
- if (lsp->port != settings->lsp_port) continue;
+ const char *const lsp_command = lsp_get_command(lsp);
+ const u16 lsp_port = lsp_get_port(lsp);
+ if (lsp_command && !streq(lsp_command, settings->lsp)) continue;
+ if (lsp_port != settings->lsp_port) continue;
- if (!lsp->initialized) {
+ if (!lsp_is_initialized(lsp)) {
// withhold judgement until this server initializes.
// we shouldn't call lsp_try_add_root_dir yet because it doesn't know
// if the server supports workspaceFolders.
return NULL;
}
- if (lsp_covers_path(lsp, path) && lsp->exited) {
+ if (lsp_covers_path(lsp, path) && lsp_has_exited(lsp)) {
// this server died. give up.
return NULL;
}
@@ -271,8 +273,15 @@ LSP *ted_get_lsp(Ted *ted, const char *path, Language language) {
// start up this LSP
FILE *log = settings->lsp_log ? ted->log : NULL;
char *root_dir = settings_get_root_dir(settings, path);
- ted->lsps[i] = lsp_create(root_dir, *settings->lsp ? settings->lsp : NULL,
- settings->lsp_port, settings->lsp_configuration, log);
+ LSPSetup setup = {
+ .root_dir = root_dir,
+ .command = *settings->lsp ? settings->lsp : NULL,
+ .port = settings->lsp_port,
+ .configuration = settings->lsp_configuration,
+ .log = log,
+ .send_delay = settings->lsp_delay,
+ };
+ ted->lsps[i] = lsp_create(&setup);
free(root_dir);
// don't actually return it yet, since it's still initializing (see above)
}
diff --git a/ted.cfg b/ted.cfg
index dd36a1f..8fd7be3 100644
--- a/ted.cfg
+++ b/ted.cfg
@@ -211,6 +211,15 @@ comment-end = " */"
[GDScript.core]
lsp-port = 6005
+# this delay is needed because godot's language server is currently kinda shitty
+# and slow so we want to avoid overwhelming it with requests
+# (specifically this sets up a delay between you typing
+# and ted sending what you typed to godot)
+lsp-delay = 0.5
+# phantom completion/signature help use a lot of requests --- let's not overwhelm godot
+# (turn these back on if you want but you may have to increase lsp-delay)
+phantom-completions = off
+signature-help = off
# phantom completions are just annoying if you're not actually programming
[Markdown.core]
diff --git a/ted.h b/ted.h
index 984ab70..67272b9 100644
--- a/ted.h
+++ b/ted.h
@@ -134,6 +134,9 @@ typedef struct Selector Selector;
/// a selector menu for files (e.g. the "open" menu)
typedef struct FileSelector FileSelector;
+/// LSP server
+struct LSP;
+
/// an entry in a \ref Selector
///
/// only `name` needs to be filled in; everything else can be zeroed.