diff options
-rw-r--r-- | buffer.c | 3 | ||||
-rw-r--r-- | colors.h | 1 | ||||
-rw-r--r-- | config.c | 1 | ||||
-rw-r--r-- | lsp-parse.c | 4 | ||||
-rw-r--r-- | lsp-write.c | 5 | ||||
-rw-r--r-- | lsp.c | 78 | ||||
-rw-r--r-- | lsp.h | 17 | ||||
-rw-r--r-- | main.c | 6 | ||||
-rw-r--r-- | os-posix.c | 117 | ||||
-rw-r--r-- | os.h | 37 | ||||
-rw-r--r-- | ted-internal.h | 1 | ||||
-rw-r--r-- | ted.c | 8 | ||||
-rw-r--r-- | ted.cfg | 3 |
13 files changed, 214 insertions, 67 deletions
@@ -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 @@ -10,6 +10,7 @@ typedef enum { COLOR_UNKNOWN, + /// main text color COLOR_TEXT, COLOR_TEXT_SECONDARY, COLOR_BG, @@ -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); } @@ -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; } @@ -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) @@ -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 @@ -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); +} @@ -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; @@ -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) } @@ -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 |