summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2022-12-07 20:22:42 -0500
committerpommicket <pommicket@gmail.com>2022-12-07 20:22:42 -0500
commit62aea26c345c74ddee40712eb72eb290a1bfd902 (patch)
treeb6451e917533c9fc75f58c053ec87418871ab048
parent65b72b38ec6c3a51b33e5d32313b51df993b32e4 (diff)
more lsp stuff
-rw-r--r--arr.c25
-rw-r--r--lsp.c129
-rw-r--r--main.c6
-rw-r--r--process-posix.c62
-rw-r--r--process-win.c2
-rw-r--r--process.h6
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 <http://unlicense.org/>
-*/
// 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 <http://unlicense.org/>
// 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 <stddef.h>
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