summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2023-09-08 17:17:35 -0400
committerpommicket <pommicket@gmail.com>2023-09-08 17:18:51 -0400
commit100859239a28c2709bb3e2cdce347300a2b763e2 (patch)
treeb45486fe7c2a3318d9702adbe17ce17b672a78d1
parent49f22fb75ae7ec5ffa98532c060d81e18d71575c (diff)
LSP over TCP initial draft
-rw-r--r--buffer.c3
-rw-r--r--colors.h1
-rw-r--r--config.c1
-rw-r--r--lsp-parse.c4
-rw-r--r--lsp-write.c5
-rw-r--r--lsp.c78
-rw-r--r--lsp.h17
-rw-r--r--main.c6
-rw-r--r--os-posix.c117
-rw-r--r--os.h37
-rw-r--r--ted-internal.h1
-rw-r--r--ted.c8
-rw-r--r--ted.cfg3
13 files changed, 214 insertions, 67 deletions
diff --git a/buffer.c b/buffer.c
index 7c0e481..549a99e 100644
--- a/buffer.c
+++ b/buffer.c
@@ -3374,7 +3374,8 @@ void buffer_render(TextBuffer *buffer, Rect r) {
hover_diagnostic = diagnostic;
if (diagnostic->url && ted_clicked_in_rect(ted, rect))
open_with_default_application(diagnostic->url);
- ted->cursor = ted->cursor_hand;
+ if (diagnostic->url)
+ ted->cursor = ted->cursor_hand;
}
}
// set color
diff --git a/colors.h b/colors.h
index d026619..6535240 100644
--- a/colors.h
+++ b/colors.h
@@ -10,6 +10,7 @@
typedef enum {
COLOR_UNKNOWN,
+ /// main text color
COLOR_TEXT,
COLOR_TEXT_SECONDARY,
COLOR_BG,
diff --git a/config.c b/config.c
index c17306a..4f80044 100644
--- a/config.c
+++ b/config.c
@@ -138,6 +138,7 @@ static const SettingU16 settings_u16[] = {
{"max-menu-width", &settings_zero.max_menu_width, 10, U16_MAX, false},
{"error-display-time", &settings_zero.error_display_time, 0, U16_MAX, false},
{"framerate-cap", &settings_zero.framerate_cap, 3, 1000, false},
+ {"lsp-port", &settings_zero.lsp_port, 0, 65535, true},
};
static const SettingU32 settings_u32[] = {
{"max-file-size", &settings_zero.max_file_size, 100, 2000000000, false},
diff --git a/lsp-parse.c b/lsp-parse.c
index df87a13..70301d3 100644
--- a/lsp-parse.c
+++ b/lsp-parse.c
@@ -802,6 +802,10 @@ static bool parse_server2client_request(LSP *lsp, JSON *json, LSPRequest *reques
} else if (streq(method, "textDocument/publishDiagnostics")) {
request->type = LSP_REQUEST_PUBLISH_DIAGNOSTICS;
return parse_publish_diagnostics(lsp, json, request);
+ } else if (streq(method, "gdscript_client/changeWorkspace")) {
+ // i ignore you (this is just a notification)
+ } else if (streq(method, "gdscript/capabilities")) {
+ // i ignore you (this is just a notification)
} else {
debug_println("Unrecognized request method: %s", method);
}
diff --git a/lsp-write.c b/lsp-write.c
index fd80799..9722a7b 100644
--- a/lsp-write.c
+++ b/lsp-write.c
@@ -334,7 +334,10 @@ static void message_writer_write_and_free(LSP *lsp, JSONWriter *o) {
fprintf(lsp->log, "LSP MESSAGE FROM CLIENT TO SERVER\n%s\n\n", content + header_size);
}
- process_write(lsp->process, content, strlen(content));
+ if (lsp->socket)
+ socket_write(lsp->socket, content, strlen(content));
+ else
+ process_write(lsp->process, content, strlen(content));
str_builder_free(&builder);
}
diff --git a/lsp.c b/lsp.c
index ec9d3d7..113091b 100644
--- a/lsp.c
+++ b/lsp.c
@@ -347,7 +347,7 @@ const char *lsp_request_string(const LSPRequest *request, LSPString string) {
// returns false if the process exited
static bool lsp_receive(LSP *lsp, size_t max_size) {
- {
+ if (lsp->process) {
// read stderr. if all goes well, we shouldn't get anything over stderr.
char stderr_buf[1024] = {0};
for (size_t i = 0; i < (max_size + sizeof stderr_buf) / sizeof stderr_buf; ++i) {
@@ -365,7 +365,7 @@ static bool lsp_receive(LSP *lsp, size_t max_size) {
}
}
- {
+ if (lsp->process) {
// check process status
ProcessExitInfo info = {0};
int status = process_check_status(&lsp->process, &info);
@@ -397,7 +397,9 @@ static bool lsp_receive(LSP *lsp, size_t max_size) {
size_t received_so_far = arr_len(lsp->received_data);
arr_reserve(lsp->received_data, received_so_far + max_size + 1);
- long long bytes_read = process_read(lsp->process, lsp->received_data + received_so_far, max_size);
+ long long bytes_read = lsp->socket
+ ? socket_read(lsp->socket, lsp->received_data + received_so_far, max_size)
+ : process_read(lsp->process, lsp->received_data + received_so_far, max_size);
if (bytes_read <= 0) {
// no data
return true;
@@ -492,6 +494,22 @@ static bool lsp_send(LSP *lsp) {
// This writes requests and reads (and parses) responses.
static int lsp_communication_thread(void *data) {
LSP *lsp = data;
+
+ if (lsp->port) {
+ lsp->socket = socket_connect_tcp(NULL, lsp->port);
+ const char *error = socket_get_error(lsp->socket);
+ if (*error) {
+ lsp_set_error(lsp, "%s", error);
+ return 0;
+ }
+ }
+
+ LSPRequest initialize = {
+ .type = LSP_REQUEST_INITIALIZE
+ };
+ initialize.id = get_request_id();
+ write_request(lsp, &initialize);
+
while (1) {
bool quit = lsp_send(lsp);
if (quit) break;
@@ -575,7 +593,7 @@ const char *lsp_document_path(LSP *lsp, LSPDocumentID document) {
return path;
}
-LSP *lsp_create(const char *root_dir, const char *command, const char *configuration, FILE *log) {
+LSP *lsp_create(const char *root_dir, const char *command, u16 port, const char *configuration, FILE *log) {
LSP *lsp = calloc(1, sizeof *lsp);
if (!lsp) return NULL;
if (!request_id_mutex)
@@ -584,10 +602,11 @@ LSP *lsp_create(const char *root_dir, const char *command, const char *configura
static LSPID curr_id = 1;
lsp->id = curr_id++;
lsp->log = log;
-
+ lsp->port = port;
+
#if DEBUG
- printf("Starting up LSP %p (ID %u) `%s` in %s\n",
- (void *)lsp, (unsigned)lsp->id, command, root_dir);
+ printf("Starting up LSP %p (ID %u) `%s` (port %u) in %s\n",
+ (void *)lsp, (unsigned)lsp->id, command ? command : "(no command)", port, root_dir);
#endif
str_hash_table_create(&lsp->document_ids, sizeof(u32));
@@ -606,33 +625,28 @@ LSP *lsp_create(const char *root_dir, const char *command, const char *configura
arr_add(lsp->workspace_folders, lsp_document_id(lsp, root_dir));
lsp->workspace_folders_mutex = SDL_CreateMutex();
- ProcessSettings settings = {
- .separate_stderr = true,
- .working_directory = root_dir,
- };
- lsp->process = process_run_ex(command, &settings);
- const char *error = process_geterr(lsp->process);
- if (error) {
- // don't show an error box if the server is not installed
- #if _WIN32
- if (strstr(error, " 2)")) {
- if (lsp->log) fprintf(lsp->log, "Couldn't start LSP server %s: file not found.", command);
- } else
- #endif
- lsp_set_error(lsp, "Couldn't start LSP server: %s", error);
- lsp->exited = true;
- process_kill(&lsp->process);
- } else {
-
- LSPRequest initialize = {
- .type = LSP_REQUEST_INITIALIZE
+ if (command) {
+ ProcessSettings settings = {
+ .separate_stderr = true,
+ .working_directory = root_dir,
};
- initialize.id = get_request_id();
- // immediately send the request rather than queueing it.
- // this is a small request, so it shouldn't be a problem.
- write_request(lsp, &initialize);
- lsp->communication_thread = SDL_CreateThread(lsp_communication_thread, "LSP communicate", lsp);
+ lsp->process = process_run_ex(command, &settings);
+ const char *error = lsp->process ? process_geterr(lsp->process) : NULL;
+ if (error) {
+ // don't show an error box if the server is not installed
+ #if _WIN32
+ if (strstr(error, " 2)")) {
+ if (lsp->log) fprintf(lsp->log, "Couldn't start LSP server %s: file not found.", command);
+ } else
+ #endif
+ lsp_set_error(lsp, "Couldn't start LSP server: %s", error);
+ lsp->exited = true;
+ process_kill(&lsp->process);
+ return lsp;
+ }
}
+
+ lsp->communication_thread = SDL_CreateThread(lsp_communication_thread, "LSP communicate", lsp);
return lsp;
}
diff --git a/lsp.h b/lsp.h
index 1d9d8ee..5545c61 100644
--- a/lsp.h
+++ b/lsp.h
@@ -590,9 +590,20 @@ typedef struct LSP {
// thread-safety: set once in lsp_create, then only used by communication thread
FILE *log;
- // The server process
+ // 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
Process *process;
+ // Socket for communicating with server. Maybe be NULL if communication is done over stdio.
+ //
+ // thread-safety: TODO
+ // at least one of `process` and `socket` must be non-null
+ Socket *socket;
+ // port used for communication
+ //
+ // this will be zero iff communication is done over stdio
+ // thread-safety: only set once in lsp_create
+ u16 port;
LSPMutex document_mutex;
// for our purposes, folders are "documents"
@@ -672,8 +683,8 @@ LSPString lsp_response_add_string(LSPResponse *response, const char *string);
bool lsp_string_is_empty(LSPString string);
/// Start up an LSP server.
///
-/// configuration and log can be NULL.
-LSP *lsp_create(const char *root_dir, const char *command, const char *configuration, FILE *log);
+/// `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)
diff --git a/main.c b/main.c
index 12d7d63..c070760 100644
--- a/main.c
+++ b/main.c
@@ -1,6 +1,10 @@
/*
TODO:
-- LSP over TCP (needed for godot)
+- highlight not, super in godot (where do those appear in the godot docs?)
+- what's goin wrong with godot (test other servers over TCP)
+ - check for server stops running over TCP
+- automatically restart server
+- LSP textDocument/formatting and textDocument/rangeFormatting
FUTURE FEATURES:
- autodetect indentation (tabs vs spaces)
- custom file/build command associations
diff --git a/os-posix.c b/os-posix.c
index a620523..0c408a5 100644
--- a/os-posix.c
+++ b/os-posix.c
@@ -5,6 +5,9 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
+#include <sys/socket.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
#include <signal.h>
#include <unistd.h>
#include <dirent.h>
@@ -250,41 +253,31 @@ const char *process_geterr(Process *p) {
return *p->error ? p->error : NULL;
}
-long long process_write(Process *proc, const char *data, size_t size) {
- if (!proc) {
- assert(0);
- return -2;
- }
- if (!proc->stdin_pipe) { // check that process hasn't been killed
- strbuf_printf(proc->error, "Process terminated");
- return -2;
- }
+static long long write_fd(int fd, char *error, size_t error_size, const char *data, size_t size) {
if (size > LLONG_MAX) {
- strbuf_printf(proc->error, "Too much data to write.");
+ str_printf(error, error_size, "too much data to write");
return -2;
}
size_t so_far = 0;
while (so_far < size) {
- ssize_t bytes_written = write(proc->stdin_pipe, data + so_far, size - so_far);
+ ssize_t bytes_written = write(fd, data + so_far, size - so_far);
if (bytes_written >= 0) {
so_far += (size_t)bytes_written;
- } else if (errno == EAGAIN) {
+ } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
return (long long)so_far;
+ } else if (errno == EPIPE) {
+ return -1;
} else {
- strbuf_printf(proc->error, "%s", strerror(errno));
+ str_printf(error, error_size, "write failed: %s", strerror(errno));
return -2;
}
}
return (long long)size;
}
-static long long process_read_fd(Process *proc, int fd, char *data, size_t size) {
- if (!fd) { // check that process hasn't been killed
- strbuf_printf(proc->error, "Process terminated");
- return -2;
- }
+static long long read_fd(int fd, char *error, size_t error_size, char *data, size_t size) {
if (size > LLONG_MAX) {
- strbuf_printf(proc->error, "Too much data to read.");
+ str_printf(error, error_size, "Too much data to read.");
return -2;
}
size_t so_far = 0;
@@ -294,16 +287,38 @@ static long long process_read_fd(Process *proc, int fd, char *data, size_t size)
so_far += (size_t)bytes_read;
} else if (bytes_read == 0) {
return (long long)so_far;
- } else if (errno == EAGAIN) {
+ } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
return so_far == 0 ? -1 : (long long)so_far;
+ } else if (errno == EPIPE) {
+ return -1;
} else {
- strbuf_printf(proc->error, "%s", strerror(errno));
+ str_printf(error, error_size, "read failed: %s", strerror(errno));
return -2;
}
}
return (long long)size;
}
+long long process_write(Process *proc, const char *data, size_t size) {
+ if (!proc) {
+ assert(0);
+ return -2;
+ }
+ if (!proc->stdin_pipe) { // check that process hasn't been killed
+ strbuf_printf(proc->error, "Process terminated");
+ return -2;
+ }
+ return write_fd(proc->stdin_pipe, proc->error, sizeof proc->error, data, size);
+}
+
+static long long process_read_fd(Process *proc, int fd, char *data, size_t size) {
+ if (!fd) { // check that process hasn't been killed
+ strbuf_printf(proc->error, "Process terminated");
+ return -2;
+ }
+ return read_fd(fd, proc->error, sizeof proc->error, data, size);
+}
+
long long process_read(Process *proc, char *data, size_t size) {
if (!proc) {
assert(0);
@@ -412,3 +427,63 @@ bool open_with_default_application(const char *path) {
bool change_directory(const char *path) {
return chdir(path) == 0;
}
+
+struct Socket {
+ int fd;
+ char error[256];
+};
+
+Socket *socket_connect_tcp(const char *address, u16 port) {
+ Socket *s = calloc(1, sizeof *s);
+ if (!s) return NULL;
+
+ if (!address) address = "127.0.0.1";
+
+ int fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (fd < 0) {
+ strbuf_printf(s->error, "couldn't create socket (%s)", strerror(errno));
+ return s;
+ }
+ struct sockaddr_in addr = {
+ .sin_family = AF_INET,
+ .sin_port = htons(port),
+ .sin_addr = {0},
+ .sin_zero = {0}
+ };
+ if (inet_pton(AF_INET, address, &addr.sin_addr) <= 0) {
+ strbuf_printf(s->error, "invalid address");
+ return s;
+ }
+
+ if (connect(fd, &addr, sizeof addr) < 0) {
+ strbuf_printf(s->error, "couldn't connect to %u.%u.%u.%u:%u (%s)",
+ address[0], address[1], address[2], address[3], port,
+ strerror(errno));
+ }
+
+ set_nonblocking(fd);
+
+ s->fd = fd;
+ return s;
+}
+
+const char *socket_get_error(Socket *socket) {
+ return socket->error;
+}
+
+long long socket_read(Socket *s, char *data, size_t size) {
+ if (s->fd <= 0) {
+ strbuf_printf(s->error, "socket has been closed");
+ return -2;
+ }
+ return read_fd(s->fd, s->error, sizeof s->error, data, size);
+
+}
+
+long long socket_write(Socket *s, const char *data, size_t size) {
+ if (s->fd <= 0) {
+ strbuf_printf(s->error, "socket has been closed");
+ return -2;
+ }
+ return write_fd(s->fd, s->error, sizeof s->error, data, size);
+}
diff --git a/os.h b/os.h
index 5af1dca..a937b40 100644
--- a/os.h
+++ b/os.h
@@ -128,17 +128,17 @@ Process *process_run(const char *command);
const char *process_geterr(Process *process);
/// write to stdin
///
-/// returns -2 on error,
+/// \returns -2 on error,\n
+/// -1 if the read end of the pipe was closed,\n
/// or a non-negative number indicating the number of bytes written.
-/// Currently, this does a blocking write.
long long process_write(Process *process, const char *data, size_t size);
/// read from stdout+stderr.
///
-/// returns:\n
+/// \returns
/// -2 on error\n
/// -1 if no data is available right now\n
/// 0 on end of file\n
-/// or a positive number indicating the number of bytes read to data (at most size)\n
+/// or a positive number indicating the number of bytes read to `data` (at most `size`)\n
/// This does a nonblocking read.
long long process_read(Process *process, char *data, size_t size);
/// like \ref process_read, but reads stderr.
@@ -148,7 +148,7 @@ long long process_read(Process *process, char *data, size_t size);
long long process_read_stderr(Process *process, char *data, size_t size);
/// Checks if the process has exited.
///
-/// Returns:\n
+/// \returns
/// -1 if the process returned a non-zero exit code, or got a signal.\n
/// 1 if the process exited successfully\n
/// 0 if the process hasn't exited.\n
@@ -162,5 +162,32 @@ int process_check_status(Process **process, ProcessExitInfo *info);
void process_kill(Process **process);
+typedef struct Socket Socket;
+
+/// create TCP socket with address and port.
+///
+/// currently only supports IPv4.
+/// if you pass `NULL` for `address`, `127.0.0.1` will be used.
+Socket *socket_connect_tcp(const char *address, u16 port);
+/// get last error from socket
+///
+/// returns `""` if there is no error.
+const char *socket_get_error(Socket *socket);
+/// read from socket.
+///
+/// \returns
+/// -2 on error\n
+/// -1 if no data is available right now\n
+/// 0 on end of file\n
+/// or a positive number indicating the number of bytes read to `data` (at most `size`)\n
+/// This does a nonblocking read.
+long long socket_read(Socket *socket, char *data, size_t size);
+/// write to socket
+///
+/// \returns -2 on error\n
+/// -1 if the read end of the socket was closed\n
+/// or a non-negative number indicating the number of bytes written.
+long long socket_write(Socket *socket, const char *data, size_t size);
+
#endif // OS_H_
diff --git a/ted-internal.h b/ted-internal.h
index 42592fb..a9ee175 100644
--- a/ted-internal.h
+++ b/ted-internal.h
@@ -111,6 +111,7 @@ struct Settings {
u16 text_size;
u16 max_menu_width;
u16 error_display_time;
+ u16 lsp_port;
bool auto_indent;
bool auto_add_newline;
bool syntax_highlighting;
diff --git a/ted.c b/ted.c
index ec04e0b..a21c317 100644
--- a/ted.c
+++ b/ted.c
@@ -245,7 +245,8 @@ 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 (!streq(lsp->command, settings->lsp)) continue;
+ if (lsp->command && !streq(lsp->command, settings->lsp)) continue;
+ if (lsp->port != settings->lsp_port) continue;
if (!lsp->initialized) {
// withhold judgement until this server initializes.
@@ -266,11 +267,12 @@ LSP *ted_get_lsp(Ted *ted, const char *path, Language language) {
}
if (i == TED_LSP_MAX)
return NULL; // why are there so many LSP open???
- if (*settings->lsp) {
+ if (*settings->lsp || settings->lsp_port) {
// 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_configuration, log);
+ ted->lsps[i] = lsp_create(root_dir, *settings->lsp ? settings->lsp : NULL,
+ settings->lsp_port, settings->lsp_configuration, log);
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 27ad0a3..b7a06dd 100644
--- a/ted.cfg
+++ b/ted.cfg
@@ -209,6 +209,9 @@ comment-end = " -->"
comment-start = "/* "
comment-end = " */"
+[GDScript.core]
+lsp-port = 6005
+
# phantom completions are just annoying if you're not actually programming
[Markdown.core]
phantom-completions = off