From 911edf40ac0dfc1ff78d36242b0cdeee1f1b9162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Thu, 19 Dec 2024 15:20:10 +0100 Subject: [PATCH] Sync with geany-lsp --- lsp/src/lsp-autocomplete.c | 2 + lsp/src/lsp-server.c | 28 +-- lsp/src/spawn/lspunixinputstream.c | 36 --- lsp/src/spawn/lspunixoutputstream.c | 36 --- lsp/src/spawn/spawn.c | 338 +++++++++++++++++++++++++++- lsp/src/spawn/spawn.h | 8 +- 6 files changed, 359 insertions(+), 89 deletions(-) diff --git a/lsp/src/lsp-autocomplete.c b/lsp/src/lsp-autocomplete.c index 73899800c..fa503b958 100644 --- a/lsp/src/lsp-autocomplete.c +++ b/lsp/src/lsp-autocomplete.c @@ -165,6 +165,8 @@ static guint get_ident_prefixlen(const gchar *word_chars, GeanyDocument *doc, gi if (!strchr(word_chars, c)) break; } + else + break; num++; pos = new_pos; } diff --git a/lsp/src/lsp-server.c b/lsp/src/lsp-server.c index 0f481781c..83337ab69 100644 --- a/lsp/src/lsp-server.c +++ b/lsp/src/lsp-server.c @@ -595,7 +595,8 @@ static void initialize_cb(GVariant *return_value, GError *error, gpointer user_d } else { - msgwin_status_add(_("LSP initialize request failed for LSP server, terminating %s"), s->config.cmd); + msgwin_status_add(_("Initialize request failed for LSP server %s: %s"), s->config.cmd, error->message); + msgwin_status_add(_("Force terminating %s"), s->config.cmd); // force exit the server - since the handshake didn't perform, the // server may be in some strange state and normal "exit" may not work @@ -808,6 +809,15 @@ static GKeyFile *read_keyfile(const gchar *config_file) } +static void stderr_cb(GString *string, GIOCondition condition, gpointer data) +{ + LspServer *srv = data; + + if (srv->config.show_server_stderr) + fprintf(stderr, "%s", string->str); +} + + static void start_lsp_server(LspServer *server) { GInputStream *input_stream; @@ -815,9 +825,7 @@ static void start_lsp_server(LspServer *server) GError *error = NULL; gint stdin_fd = -1; gint stdout_fd = -1; - gint stderr_fd = -1; gboolean success; - GSource *source; GString *cmd = g_string_new(server->config.cmd); #ifdef G_OS_UNIX @@ -832,11 +840,10 @@ static void start_lsp_server(LspServer *server) msgwin_status_add(_("Starting LSP server %s"), cmd->str); - success = lsp_spawn_async_with_pipes(NULL, cmd->str, NULL, - server->config.env, &server->pid, - &stdin_fd, &stdout_fd, - server->config.show_server_stderr ? NULL : &stderr_fd, - &error); + success = lsp_spawn_with_pipes_and_stderr_callback(NULL, cmd->str, NULL, + server->config.env, + &stdin_fd, &stdout_fd, stderr_cb, server, 0, + process_stopped, server, &server->pid, &error); if (!success) { @@ -847,11 +854,6 @@ static void start_lsp_server(LspServer *server) return; } - source = g_child_watch_source_new(server->pid); - g_source_set_callback(source, (GSourceFunc) (void(*)(void)) (GChildWatchFunc) process_stopped, server, NULL); - g_source_attach(source, NULL); - g_source_unref(source); - #ifdef G_OS_UNIX input_stream = g_unix_input_stream_new(stdout_fd, TRUE); output_stream = g_unix_output_stream_new(stdin_fd, TRUE); diff --git a/lsp/src/spawn/lspunixinputstream.c b/lsp/src/spawn/lspunixinputstream.c index 3fe0ff8c1..b7b6aea95 100644 --- a/lsp/src/spawn/lspunixinputstream.c +++ b/lsp/src/spawn/lspunixinputstream.c @@ -296,47 +296,13 @@ lsp_unix_input_stream_read (GInputStream *stream, { LspUnixInputStream *unix_stream; gssize res = -1; - GPollFD poll_fds[2]; - int nfds; - int poll_ret; unix_stream = LSP_UNIX_INPUT_STREAM (stream); - poll_fds[0].fd = unix_stream->priv->fd; - poll_fds[0].events = G_IO_IN; - if (unix_stream->priv->can_poll && - g_cancellable_make_pollfd (cancellable, &poll_fds[1])) - nfds = 2; - else - nfds = 1; - while (1) { int errsv; - poll_fds[0].revents = poll_fds[1].revents = 0; - do - { - poll_ret = g_poll (poll_fds, nfds, -1); - errsv = errno; - } - while (poll_ret == -1 && errsv == EINTR); - - if (poll_ret == -1) - { - g_set_error (error, G_IO_ERROR, - g_io_error_from_errno (errsv), - "Error reading from file descriptor: %s", - g_strerror (errsv)); - break; - } - - if (g_cancellable_set_error_if_cancelled (cancellable, error)) - break; - - if (!poll_fds[0].revents) - continue; - res = read (unix_stream->priv->fd, buffer, count); if (res == -1) { @@ -354,8 +320,6 @@ lsp_unix_input_stream_read (GInputStream *stream, break; } - if (nfds == 2) - g_cancellable_release_fd (cancellable); return res; } diff --git a/lsp/src/spawn/lspunixoutputstream.c b/lsp/src/spawn/lspunixoutputstream.c index b798eefa9..47ae96fae 100644 --- a/lsp/src/spawn/lspunixoutputstream.c +++ b/lsp/src/spawn/lspunixoutputstream.c @@ -282,47 +282,13 @@ lsp_unix_output_stream_write (GOutputStream *stream, { LspUnixOutputStream *unix_stream; gssize res = -1; - GPollFD poll_fds[2]; - int nfds = 0; - int poll_ret; unix_stream = LSP_UNIX_OUTPUT_STREAM (stream); - poll_fds[0].fd = unix_stream->priv->fd; - poll_fds[0].events = G_IO_OUT; - nfds++; - - if (unix_stream->priv->can_poll && - g_cancellable_make_pollfd (cancellable, &poll_fds[1])) - nfds++; - while (1) { int errsv; - poll_fds[0].revents = poll_fds[1].revents = 0; - do - { - poll_ret = g_poll (poll_fds, nfds, -1); - errsv = errno; - } - while (poll_ret == -1 && errsv == EINTR); - - if (poll_ret == -1) - { - g_set_error (error, G_IO_ERROR, - g_io_error_from_errno (errsv), - "Error writing to file descriptor: %s", - g_strerror (errsv)); - break; - } - - if (g_cancellable_set_error_if_cancelled (cancellable, error)) - break; - - if (!poll_fds[0].revents) - continue; - res = write (unix_stream->priv->fd, buffer, count); errsv = errno; if (res == -1) @@ -339,8 +305,6 @@ lsp_unix_output_stream_write (GOutputStream *stream, break; } - if (nfds == 2) - g_cancellable_release_fd (cancellable); return res; } diff --git a/lsp/src/spawn/spawn.c b/lsp/src/spawn/spawn.c index a9818722b..d3325d45c 100644 --- a/lsp/src/spawn/spawn.c +++ b/lsp/src/spawn/spawn.c @@ -369,7 +369,7 @@ static void spawn_append_argument(GString *command, const char *text) * * @return @c TRUE on success, @c FALSE on error. */ -gboolean lsp_spawn_async_with_pipes(const gchar *working_directory, const gchar *command_line, +static gboolean spawn_async_with_pipes(const gchar *working_directory, const gchar *command_line, gchar **argv, gchar **envp, GPid *child_pid, gint *stdin_fd, gint *stdout_fd, gint *stderr_fd, GError **error) { @@ -642,3 +642,339 @@ gboolean lsp_spawn_async_with_pipes(const gchar *working_directory, const gchar return spawned; #endif /* G_OS_WIN32 */ } + + +/* + * Spawn with callbacks - general event sequence: + * + * - Launch the child. + * - Setup any I/O callbacks and a child watch callback. + * - On sync execution, run a main loop. + * - Wait for the child to terminate. + * - Check for active I/O sources. If any, add a timeout source to watch them, they should + * become inactive real soon now that the child is dead. Otherwise, finalize immediately. + * - In the timeout source: check for active I/O sources and finalize if none. + */ + +typedef struct _SpawnChannelData +{ + GIOChannel *channel; /* NULL if not created / already destroyed */ + union + { + GIOFunc write; + SpawnReadFunc read; + } cb; + gpointer cb_data; + /* stdout/stderr only */ + GString *buffer; /* NULL if recursive */ + GString *line_buffer; /* NULL if char buffered */ + gsize max_length; + /* stdout/stderr: fix continuous empty G_IO_IN-s for recursive channels */ + guint empty_gio_ins; +} SpawnChannelData; + +#define SPAWN_CHANNEL_GIO_WATCH(sc) ((sc)->empty_gio_ins < 200) + + +static void spawn_destroy_common(SpawnChannelData *sc) +{ + g_io_channel_shutdown(sc->channel, FALSE, NULL); + + if (sc->buffer) + g_string_free(sc->buffer, TRUE); + + if (sc->line_buffer) + g_string_free(sc->line_buffer, TRUE); +} + + +static void spawn_timeout_destroy_cb(gpointer data) +{ + SpawnChannelData *sc = (SpawnChannelData *) data; + + spawn_destroy_common(sc); + g_io_channel_unref(sc->channel); + sc->channel = NULL; +} + + +static void spawn_destroy_cb(gpointer data) +{ + SpawnChannelData *sc = (SpawnChannelData *) data; + + if (SPAWN_CHANNEL_GIO_WATCH(sc)) + { + spawn_destroy_common(sc); + sc->channel = NULL; + } +} + + +static gboolean spawn_read_cb(GIOChannel *channel, GIOCondition condition, gpointer data); + +static gboolean spawn_timeout_read_cb(gpointer data) +{ + SpawnChannelData *sc = (SpawnChannelData *) data; + + /* The solution for continuous empty G_IO_IN-s is to generate them outselves. :) */ + return spawn_read_cb(sc->channel, G_IO_IN, data); +} + +#define SPAWN_IO_FAILURE (G_IO_ERR | G_IO_HUP | G_IO_NVAL) /* always used together */ + +static gboolean spawn_read_cb(GIOChannel *channel, GIOCondition condition, gpointer data) +{ + SpawnChannelData *sc = (SpawnChannelData *) data; + GString *line_buffer = sc->line_buffer; + GString *buffer = sc->buffer ? sc->buffer : g_string_sized_new(sc->max_length); + GIOCondition input_cond = condition & (G_IO_IN | G_IO_PRI); + GIOCondition failure_cond = condition & SPAWN_IO_FAILURE; + GIOStatus status = G_IO_STATUS_NORMAL; + /* + * - Normally, read only once. With IO watches, our data processing must be immediate, + * which may give the child time to emit more data, and a read loop may combine it into + * large stdout and stderr portions. Under Windows, looping blocks. + * - On failure, read in a loop. It won't block now, there will be no more data, and the + * IO watch is not guaranteed to be called again (under Windows this is the last call). + * - When using timeout callbacks, read in a loop. Otherwise, the input processing will + * be limited to (1/0.050 * DEFAULT_IO_LENGTH) KB/sec, which is pretty low. + */ + if (input_cond) + { + gsize chars_read; + + if (line_buffer) + { + gsize n = line_buffer->len; + + while ((status = g_io_channel_read_chars(channel, line_buffer->str + n, + DEFAULT_IO_LENGTH, &chars_read, NULL)) == G_IO_STATUS_NORMAL) + { + g_string_set_size(line_buffer, n + chars_read); + + while (n < line_buffer->len) + { + gsize line_len = 0; + + if (n == sc->max_length) + line_len = n; + else if (strchr("\n", line_buffer->str[n])) /* '\n' or '\0' */ + line_len = n + 1; + else if (n < line_buffer->len - 1 && line_buffer->str[n] == '\r') + line_len = n + 1 + (line_buffer->str[n + 1] == '\n'); + + if (!line_len) + n++; + else + { + g_string_append_len(buffer, line_buffer->str, line_len); + g_string_erase(line_buffer, 0, line_len); + /* input only, failures are reported separately below */ + sc->cb.read(buffer, input_cond, sc->cb_data); + g_string_truncate(buffer, 0); + n = 0; + } + } + + if (SPAWN_CHANNEL_GIO_WATCH(sc) && !failure_cond) + break; + } + } + else + { + while ((status = g_io_channel_read_chars(channel, buffer->str, sc->max_length, + &chars_read, NULL)) == G_IO_STATUS_NORMAL) + { + g_string_set_size(buffer, chars_read); + /* input only, failures are reported separately below */ + sc->cb.read(buffer, input_cond, sc->cb_data); + + if (SPAWN_CHANNEL_GIO_WATCH(sc) && !failure_cond) + break; + } + } + + /* Under OSX, after child death, the read watches receive input conditions instead + of error conditions, so we convert the termination statuses into conditions. + Should not hurt the other OS. */ + if (status == G_IO_STATUS_ERROR) + failure_cond |= G_IO_ERR; + else if (status == G_IO_STATUS_EOF) + failure_cond |= G_IO_HUP; + } + + if (failure_cond) /* we must signal the callback */ + { + if (line_buffer && line_buffer->len) /* flush the line buffer */ + { + g_string_append_len(buffer, line_buffer->str, line_buffer->len); + /* all data may be from a previous call */ + if (!input_cond) + input_cond = G_IO_IN; + } + else + { + input_cond = 0; + g_string_truncate(buffer, 0); + } + + sc->cb.read(buffer, input_cond | failure_cond, sc->cb_data); + } + /* Check for continuous activations with G_IO_IN | G_IO_PRI, without any + data to read and without errors. If detected, switch to timeout source. */ + else if (SPAWN_CHANNEL_GIO_WATCH(sc) && status == G_IO_STATUS_AGAIN) + { + sc->empty_gio_ins++; + + if (!SPAWN_CHANNEL_GIO_WATCH(sc)) + { + GSource *old_source = g_main_current_source(); + GSource *new_source = g_timeout_source_new(50); + +// geany_debug("Switching spawn source %s ((GSource*)%p on (GIOChannel*)%p) to a timeout source", +// g_source_get_name(old_source), (gpointer) old_source, (gpointer)sc->channel); + + g_io_channel_ref(sc->channel); + g_source_set_can_recurse(new_source, g_source_get_can_recurse(old_source)); + g_source_set_callback(new_source, spawn_timeout_read_cb, data, spawn_timeout_destroy_cb); + g_source_attach(new_source, g_source_get_context(old_source)); + g_source_unref(new_source); + failure_cond |= G_IO_ERR; + } + } + + if (buffer != sc->buffer) + g_string_free(buffer, TRUE); + + return !failure_cond; +} + + +typedef struct _SpawnWatcherData +{ + SpawnChannelData sc; /* stderr */ + GChildWatchFunc exit_cb; + gpointer exit_data; + GPid pid; + gint exit_status; + GMainContext *main_context; /* NULL if async execution */ + GMainLoop *main_loop; /* NULL if async execution */ +} SpawnWatcherData; + + +static void spawn_finalize(SpawnWatcherData *sw) +{ + if (sw->exit_cb) + sw->exit_cb(sw->pid, sw->exit_status, sw->exit_data); + + if (sw->main_loop) + { + g_main_loop_quit(sw->main_loop); + g_main_loop_unref(sw->main_loop); + } + + g_spawn_close_pid(sw->pid); + g_slice_free(SpawnWatcherData, sw); +} + + +static gboolean spawn_timeout_watch_cb(gpointer data) +{ + SpawnWatcherData *sw = (SpawnWatcherData *) data; + + if (sw->sc.channel) + return TRUE; + + spawn_finalize(sw); + return FALSE; +} + + +static void spawn_watch_cb(GPid pid, gint status, gpointer data) +{ + SpawnWatcherData *sw = (SpawnWatcherData *) data; + + sw->pid = pid; + sw->exit_status = status; + + if (sw->sc.channel) + { + GSource *source = g_timeout_source_new(50); + + g_source_set_callback(source, spawn_timeout_watch_cb, data, NULL); + g_source_attach(source, sw->main_context); + g_source_unref(source); + return; + } + + spawn_finalize(sw); +} + + +gboolean lsp_spawn_with_pipes_and_stderr_callback(const gchar *working_directory, const gchar *command_line, + gchar **argv, gchar **envp, gint *stdin_fd, gint *stdout_fd, + SpawnReadFunc stderr_cb, gpointer stderr_data, gsize stderr_max_length, + GChildWatchFunc exit_cb, gpointer exit_data, GPid *child_pid, GError **error) +{ + GPid pid; + int pipe = -1; + + if (spawn_async_with_pipes(working_directory, command_line, argv, envp, &pid, + stdin_fd, stdout_fd, stderr_cb ? &pipe : NULL, error)) + { + SpawnWatcherData *sw = g_slice_new0(SpawnWatcherData); + GSource *source; + SpawnChannelData *sc = &sw->sc; + GIOCondition condition; + GIOFunc callback; + + sw->main_context = NULL; + + if (child_pid) + *child_pid = pid; + + if (pipe != -1) + { + #ifdef G_OS_WIN32 + sc->channel = g_io_channel_win32_new_fd(pipe); + #else + sc->channel = g_io_channel_unix_new(pipe); + g_io_channel_set_flags(sc->channel, G_IO_FLAG_NONBLOCK, NULL); + #endif + g_io_channel_set_encoding(sc->channel, NULL, NULL); + /* we have our own buffers, and GIO buffering blocks under Windows */ + g_io_channel_set_buffered(sc->channel, FALSE); + sc->cb_data = stderr_data; + + condition = G_IO_IN | G_IO_PRI | SPAWN_IO_FAILURE; + callback = spawn_read_cb; + + { + sc->cb.read = stderr_cb; + sc->max_length = stderr_max_length ? stderr_max_length : DEFAULT_IO_LENGTH; + } + + sc->empty_gio_ins = 0; + + source = g_io_create_watch(sc->channel, condition); + g_io_channel_unref(sc->channel); + + sc->buffer = g_string_sized_new(sc->max_length); + + g_source_set_callback(source, (GSourceFunc) (void(*)(void)) callback, sc, spawn_destroy_cb); + g_source_attach(source, sw->main_context); + g_source_unref(source); + + sw->exit_cb = exit_cb; + sw->exit_data = exit_data; + source = g_child_watch_source_new(pid); + g_source_set_callback(source, (GSourceFunc) (void(*)(void)) (GChildWatchFunc) spawn_watch_cb, sw, NULL); + g_source_attach(source, sw->main_context); + g_source_unref(source); + } + + return TRUE; + } + + return FALSE; +} diff --git a/lsp/src/spawn/spawn.h b/lsp/src/spawn/spawn.h index 18eabdc08..84fa285e3 100644 --- a/lsp/src/spawn/spawn.h +++ b/lsp/src/spawn/spawn.h @@ -23,12 +23,14 @@ #define LSP_SPAWN_H 1 #include +#include G_BEGIN_DECLS -gboolean lsp_spawn_async_with_pipes(const gchar *working_directory, const gchar *command_line, - gchar **argv, gchar **envp, GPid *child_pid, gint *stdin_fd, gint *stdout_fd, - gint *stderr_fd, GError **error); +gboolean lsp_spawn_with_pipes_and_stderr_callback(const gchar *working_directory, const gchar *command_line, + gchar **argv, gchar **envp, gint *stdin_fd, gint *stdout_fd, + SpawnReadFunc stderr_cb, gpointer stderr_data, gsize stderr_max_length, + GChildWatchFunc exit_cb, gpointer exit_data, GPid *child_pid, GError **error); G_END_DECLS