From 62aea26c345c74ddee40712eb72eb290a1bfd902 Mon Sep 17 00:00:00 2001 From: pommicket Date: Wed, 7 Dec 2022 20:22:42 -0500 Subject: more lsp stuff --- arr.c | 25 ++--------- lsp.c | 129 +++++++++++++++++++++++++++++++++++++++++--------------- main.c | 6 ++- process-posix.c | 62 +++++++++++++++++++++------ process-win.c | 2 + process.h | 6 +++ 6 files changed, 161 insertions(+), 69 deletions(-) diff --git a/arr.c b/arr.c index 05117b1..2ca394c 100644 --- a/arr.c +++ b/arr.c @@ -1,27 +1,5 @@ #ifndef ARR_C_ #define ARR_C_ -/* -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. -For more information, please refer to -*/ // functions in this file suffixed with _ are not meant to be used outside here, unless you // know what you're doing @@ -29,6 +7,9 @@ For more information, please refer to // IMPORTANT NOTE: If you are using this with structures containing `long double`s, do // #define ARR_LONG_DOUBLE // before including this file +// ( otherwise the long doubles will not be aligned. +// this does mean that arrays waste 8 bytes of memory. +// which isnt important unless you're making a lot of arrays.) #include typedef union { diff --git a/lsp.c b/lsp.c index afe8831..bea5334 100644 --- a/lsp.c +++ b/lsp.c @@ -1,8 +1,11 @@ // @TODO: maximum queue size for requests/responses just in case? typedef enum { + LSP_NONE, LSP_INITIALIZE, + LSP_INITIALIZED, LSP_OPEN, + LSP_COMPLETION, } LSPRequestType; typedef struct { @@ -22,11 +25,12 @@ typedef struct { typedef struct { Process process; + u64 request_id; JSON *responses; SDL_mutex *responses_mutex; LSPRequest *requests; SDL_mutex *requests_mutex; - + bool initialized; // has the response to the initialize request been sent? SDL_Thread *communication_thread; SDL_sem *quit_sem; char *received_data; // dynamic array @@ -38,6 +42,9 @@ 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); } @@ -75,10 +82,13 @@ static const char *lsp_language_id(Language lang) { } static void write_request(LSP *lsp, const LSPRequest *request) { - static unsigned long long id; - ++id; + unsigned long long id = lsp->request_id++; + switch (request->type) { + case LSP_NONE: + assert(0); + break; case LSP_INITIALIZE: { char content[1024]; strbuf_printf(content, @@ -88,6 +98,13 @@ static void write_request(LSP *lsp, const LSPRequest *request) { "}}", id, process_get_id()); write_request_content(lsp, content); } break; + case LSP_INITIALIZED: { + char content[1024]; + strbuf_printf(content, + "{\"jsonrpc\":\"2.0\",\"id\":%llu,\"method\":\"initialized\",\"params\":{" + "}}", id); + write_request_content(lsp, content); + } break; case LSP_OPEN: { const LSPRequestOpen *open = &request->data.open; char *escaped_filename = json_escape(open->filename); @@ -114,9 +131,14 @@ static void write_request(LSP *lsp, const LSPRequest *request) { write_request_content(lsp, did_open); } break; - default: - // @TODO - abort(); + case LSP_COMPLETION: { + char content[1024]; + // no params needed + strbuf_printf(content, + "{\"jsonrpc\":\"2.0\",\"id\":%llu,\"method\":\"initialize\",\"params\":{" + "}}", id); + write_request_content(lsp, content); + } break; } } @@ -145,6 +167,22 @@ void lsp_send_request(LSP *lsp, const LSPRequest *request) { // receive responses from LSP, up to max_size bytes. static void lsp_receive(LSP *lsp, size_t max_size) { + + { + // 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) { + ssize_t nstderr = process_read_stderr(&lsp->process, stderr_buf, sizeof stderr_buf - 1); + if (nstderr > 0) { + // uh oh + stderr_buf[nstderr] = '\0'; + fprintf(stderr, "\x1b[1m\x1b[93m%s\x1b[0m", stderr_buf); + } else { + break; + } + } + } + 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); @@ -156,6 +194,9 @@ 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 + printf("\x1b[3m%s\x1b[0m\n",lsp->received_data); + #endif u64 response_offset=0, response_size=0; while (has_response(lsp->received_data, received_so_far, &response_offset, &response_size)) { @@ -191,7 +232,12 @@ static void lsp_receive(LSP *lsp, size_t max_size) { static void free_request(LSPRequest *r) { switch (r->type) { + case LSP_NONE: + assert(0); + break; case LSP_INITIALIZE: + case LSP_INITIALIZED: + case LSP_COMPLETION: break; case LSP_OPEN: { LSPRequestOpen *open = &r->data.open; @@ -201,38 +247,51 @@ static void free_request(LSPRequest *r) { } } +// send requests. +static bool lsp_send(LSP *lsp) { + if (!lsp->initialized) { + // don't send anything before the server is initialized. + return false; + } + + LSPRequest *requests = NULL; + SDL_LockMutex(lsp->requests_mutex); + size_t n_requests = arr_len(lsp->requests); + requests = calloc(n_requests, sizeof *requests); + memcpy(requests, lsp->requests, n_requests * sizeof *requests); + SDL_UnlockMutex(lsp->requests_mutex); + + bool quit = false; + for (size_t i = 0; i < n_requests; ++i) { + LSPRequest *r = &requests[i]; + if (!quit) { + // this could slow down lsp_free if there's a gigantic request. + // whatever. + write_request(lsp, r); + } + free_request(r); + + if (SDL_SemTryWait(lsp->quit_sem) == 0) { + quit = true; + // important that we don't break here so all the requests get freed. + } + } + + free(requests); + return quit; +} + + // Do any necessary communication with the LSP. // This writes requests and reads (and parses) responses. static int lsp_communication_thread(void *data) { LSP *lsp = data; while (1) { - LSPRequest *requests = NULL; - SDL_LockMutex(lsp->requests_mutex); - while (arr_len(lsp->requests)) { - arr_add(requests, lsp->requests[0]); - arr_remove(lsp->requests, 0); - } - SDL_UnlockMutex(lsp->requests_mutex); - - bool quit = false; - arr_foreach_ptr(requests, LSPRequest, r) { - if (!quit) { - // this could slow down lsp_free if there's a gigantic request. - // whatever. - write_request(lsp, r); - } - free_request(r); - - if (SDL_SemTryWait(lsp->quit_sem) == 0) { - quit = true; - // important that we don't break here so all the requests get freed. - } - } - - arr_free(requests); + bool quit = lsp_send(lsp); + if (quit) break; lsp_receive(lsp, (size_t)10<<20); - if (quit || SDL_SemWaitTimeout(lsp->quit_sem, 5) == 0) + if (SDL_SemWaitTimeout(lsp->quit_sem, 5) == 0) break; } return 0; @@ -241,13 +300,17 @@ static int lsp_communication_thread(void *data) { bool lsp_create(LSP *lsp, const char *analyzer_command) { ProcessSettings settings = { .stdin_blocking = true, - .stdout_blocking = false + .stdout_blocking = false, + .stderr_blocking = false, + .separate_stderr = true, }; process_run_ex(&lsp->process, analyzer_command, &settings); LSPRequest initialize = { .type = LSP_INITIALIZE }; - lsp_send_request(lsp, &initialize); + // 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->quit_sem = SDL_CreateSemaphore(0); lsp->responses_mutex = SDL_CreateMutex(); diff --git a/main.c b/main.c index 2792bd9..81e5199 100644 --- a/main.c +++ b/main.c @@ -292,13 +292,15 @@ int main(int argc, char **argv) { printf("lsp_create: %s\n",lsp.error); exit(1); } +// LSPRequest test_req = {LSP_COMPLETION}; +// lsp_send_request(&lsp, &test_req); while (1) { JSON response = {0}; if (lsp_next_response(&lsp, &response)) { json_debug_print(&response); - break; + printf("\n"); } - usleep(100000); + usleep(10000); } lsp_free(&lsp); exit(0); diff --git a/process-posix.c b/process-posix.c index 1463487..c8ea376 100644 --- a/process-posix.c +++ b/process-posix.c @@ -7,6 +7,8 @@ struct Process { pid_t pid; int stdout_pipe; + // only applicable if separate_stderr was specified. + int stderr_pipe; int stdin_pipe; char error[64]; }; @@ -15,10 +17,14 @@ int process_get_id(void) { return getpid(); } +static void set_nonblocking(int fd) { + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); +} + bool process_run_ex(Process *proc, const char *command, const ProcessSettings *settings) { memset(proc, 0, sizeof *proc); - int stdin_pipe[2] = {0}, stdout_pipe[2] = {0}; + int stdin_pipe[2] = {0}, stdout_pipe[2] = {0}, stderr_pipe[2] = {0}; if (pipe(stdin_pipe) != 0) { strbuf_printf(proc->error, "%s", strerror(errno)); return false; @@ -29,6 +35,16 @@ bool process_run_ex(Process *proc, const char *command, const ProcessSettings *s close(stdin_pipe[1]); return false; } + if (settings->separate_stderr) { + if (pipe(stderr_pipe) != 0) { + strbuf_printf(proc->error, "%s", strerror(errno)); + close(stdin_pipe[0]); + close(stdin_pipe[1]); + close(stdout_pipe[0]); + close(stdout_pipe[1]); + return false; + } + } bool success = false; pid_t pid = fork(); @@ -40,13 +56,20 @@ bool process_run_ex(Process *proc, const char *command, const ProcessSettings *s setpgid(0, 0); // pipe stuff dup2(stdout_pipe[1], STDOUT_FILENO); - dup2(stdout_pipe[1], STDERR_FILENO); + if (stderr_pipe[1]) + dup2(stderr_pipe[1], STDERR_FILENO); + else + dup2(stdout_pipe[1], STDERR_FILENO); dup2(stdin_pipe[0], STDIN_FILENO); // don't need these file descriptors anymore - close(stdout_pipe[0]); - close(stdout_pipe[1]); close(stdin_pipe[0]); close(stdin_pipe[1]); + close(stdout_pipe[0]); + close(stdout_pipe[1]); + if (stderr_pipe[0]) { + close(stderr_pipe[0]); + close(stderr_pipe[1]); + } char *program = "/bin/sh"; char *argv[] = {program, "-c", (char *)command, NULL}; @@ -57,18 +80,24 @@ bool process_run_ex(Process *proc, const char *command, const ProcessSettings *s } else if (pid > 0) { // parent process - // we're reading from (the child's) stdout and writing to stdin, + // we're reading from (the child's) stdout/stderr and writing to stdin, // so we don't need the write end of the stdout pipe or the // read end of the stdin pipe. close(stdout_pipe[1]); + if (stderr_pipe[1]) + close(stderr_pipe[1]); close(stdin_pipe[0]); // set pipes to non-blocking if (!settings->stdout_blocking) - fcntl(stdout_pipe[0], F_SETFL, fcntl(stdout_pipe[0], F_GETFL) | O_NONBLOCK); + set_nonblocking(stdout_pipe[0]); + if (stderr_pipe[0] && !settings->stderr_blocking) + set_nonblocking(stderr_pipe[0]); if (!settings->stdin_blocking) - fcntl(stdin_pipe[1], F_SETFL, fcntl(stdin_pipe[1], F_GETFL) | O_NONBLOCK); + set_nonblocking(stdin_pipe[1]); proc->pid = pid; proc->stdout_pipe = stdout_pipe[0]; + if (stderr_pipe[0]) + proc->stderr_pipe = stderr_pipe[0]; proc->stdin_pipe = stdin_pipe[1]; success = true; } @@ -98,9 +127,9 @@ long long process_write(Process *proc, const char *data, size_t size) { } } -long long process_read(Process *proc, char *data, size_t size) { - assert(proc->stdout_pipe); - ssize_t bytes_read = read(proc->stdout_pipe, data, size); +static long long process_read_fd(Process *proc, int fd, char *data, size_t size) { + assert(fd); + ssize_t bytes_read = read(fd, data, size); if (bytes_read >= 0) { return (long long)bytes_read; } else if (errno == EAGAIN) { @@ -109,14 +138,23 @@ long long process_read(Process *proc, char *data, size_t size) { strbuf_printf(proc->error, "%s", strerror(errno)); return -2; } +} + +long long process_read(Process *proc, char *data, size_t size) { + return process_read_fd(proc, proc->stdout_pipe, data, size); +} +long long process_read_stderr(Process *proc, char *data, size_t size) { + return process_read_fd(proc, proc->stderr_pipe, data, size); } static void process_close_pipes(Process *proc) { - close(proc->stdout_pipe); close(proc->stdin_pipe); - proc->stdout_pipe = 0; + close(proc->stdout_pipe); + close(proc->stderr_pipe); proc->stdin_pipe = 0; + proc->stdout_pipe = 0; + proc->stderr_pipe = 0; } void process_kill(Process *proc) { diff --git a/process-win.c b/process-win.c index a5f05c0..8ea7b91 100644 --- a/process-win.c +++ b/process-win.c @@ -1,3 +1,5 @@ +#error "@TODO : implement process_write, separate_stderr" + #include "process.h" struct Process { diff --git a/process.h b/process.h index 854899b..2d5a10c 100644 --- a/process.h +++ b/process.h @@ -8,6 +8,8 @@ typedef struct Process Process; typedef struct { bool stdin_blocking; bool stdout_blocking; + bool separate_stderr; + bool stderr_blocking; // not applicable if separate_stderr is false. } ProcessSettings; // get process ID of this process @@ -31,6 +33,10 @@ long long process_write(Process *process, const char *data, size_t size); // 0 on end of file // or a positive number indicating the number of bytes read to data (at most size) long long process_read(Process *process, char *data, size_t size); +// like process_read, but reads stderr. +// this function ALWAYS RETURNS -2 if separate_stderr is not specified in the ProcessSettings. +// if separate_stderr is false, then both stdout and stderr will be sent via process_read. +long long process_read_stderr(Process *process, char *data, size_t size); // Checks if the process has exited. Returns: // -1 if the process returned a non-zero exit code, or got a signal. // 1 if the process exited successfully -- cgit v1.2.3