791 lines
23 KiB
C
791 lines
23 KiB
C
|
/* ide-lsp-service.c
|
||
|
*
|
||
|
* Copyright 2021 James Westman <james@jwestman.net>
|
||
|
*
|
||
|
* This program is free software: you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License as published by
|
||
|
* the Free Software Foundation, either version 3 of the License, or
|
||
|
* (at your option) any later version.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*
|
||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||
|
*/
|
||
|
|
||
|
#define G_LOG_DOMAIN "ide-lsp-service"
|
||
|
|
||
|
#include "config.h"
|
||
|
|
||
|
#include "ide-lsp-service.h"
|
||
|
|
||
|
/**
|
||
|
* SECTION:ide-lsp-service
|
||
|
* @title: IdeLspService
|
||
|
* @short_description: Service integration for LSPs
|
||
|
*
|
||
|
* Since: 42.0
|
||
|
*/
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
IdeSubprocessSupervisor *supervisor;
|
||
|
IdeLspClient *client;
|
||
|
char *program;
|
||
|
char **search_path;
|
||
|
guint has_started : 1;
|
||
|
guint inherit_stderr : 1;
|
||
|
} IdeLspServicePrivate;
|
||
|
|
||
|
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (IdeLspService, ide_lsp_service, IDE_TYPE_OBJECT)
|
||
|
|
||
|
enum {
|
||
|
PROP_0,
|
||
|
PROP_CLIENT,
|
||
|
PROP_INHERIT_STDERR,
|
||
|
PROP_PROGRAM,
|
||
|
PROP_SEARCH_PATH,
|
||
|
PROP_SUPERVISOR,
|
||
|
N_PROPS
|
||
|
};
|
||
|
|
||
|
enum {
|
||
|
CREATE_LAUNCHER,
|
||
|
N_SIGNALS
|
||
|
};
|
||
|
|
||
|
static GParamSpec *properties [N_PROPS];
|
||
|
static guint signals [N_SIGNALS];
|
||
|
|
||
|
static void
|
||
|
ide_lsp_service_stop (IdeLspService *self)
|
||
|
{
|
||
|
IdeLspServicePrivate *priv = ide_lsp_service_get_instance_private (self);
|
||
|
gboolean notify_client = FALSE;
|
||
|
gboolean notify_supervisor = FALSE;
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
||
|
g_return_if_fail (IDE_IS_LSP_SERVICE (self));
|
||
|
|
||
|
if (priv->has_started)
|
||
|
g_debug ("Stopping LSP client %s", G_OBJECT_TYPE_NAME (self));
|
||
|
|
||
|
if (priv->client != NULL)
|
||
|
{
|
||
|
ide_lsp_client_stop (priv->client);
|
||
|
ide_object_destroy (IDE_OBJECT (priv->client));
|
||
|
priv->client = NULL;
|
||
|
notify_client = TRUE;
|
||
|
}
|
||
|
|
||
|
if (priv->supervisor != NULL)
|
||
|
{
|
||
|
ide_subprocess_supervisor_stop (priv->supervisor);
|
||
|
g_clear_object (&priv->supervisor);
|
||
|
notify_supervisor = TRUE;
|
||
|
}
|
||
|
|
||
|
priv->has_started = FALSE;
|
||
|
|
||
|
if (notify_client)
|
||
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CLIENT]);
|
||
|
|
||
|
if (notify_supervisor)
|
||
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SUPERVISOR]);
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
ide_lsp_service_get_property (GObject *object,
|
||
|
guint prop_id,
|
||
|
GValue *value,
|
||
|
GParamSpec *pspec)
|
||
|
{
|
||
|
IdeLspService *self = IDE_LSP_SERVICE (object);
|
||
|
IdeLspServicePrivate *priv = ide_lsp_service_get_instance_private (self);
|
||
|
|
||
|
switch (prop_id)
|
||
|
{
|
||
|
case PROP_CLIENT:
|
||
|
g_value_set_object (value, priv->client);
|
||
|
break;
|
||
|
|
||
|
case PROP_PROGRAM:
|
||
|
g_value_set_string (value, priv->program);
|
||
|
break;
|
||
|
|
||
|
case PROP_SEARCH_PATH:
|
||
|
g_value_set_boxed (value, priv->search_path);
|
||
|
break;
|
||
|
|
||
|
case PROP_SUPERVISOR:
|
||
|
g_value_set_object (value, priv->supervisor);
|
||
|
break;
|
||
|
|
||
|
case PROP_INHERIT_STDERR:
|
||
|
g_value_set_boolean (value, priv->inherit_stderr);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_lsp_service_set_property (GObject *object,
|
||
|
guint prop_id,
|
||
|
const GValue *value,
|
||
|
GParamSpec *pspec)
|
||
|
{
|
||
|
IdeLspService *self = IDE_LSP_SERVICE (object);
|
||
|
|
||
|
switch (prop_id)
|
||
|
{
|
||
|
case PROP_INHERIT_STDERR:
|
||
|
ide_lsp_service_set_inherit_stderr (self, g_value_get_boolean (value));
|
||
|
break;
|
||
|
|
||
|
case PROP_PROGRAM:
|
||
|
ide_lsp_service_set_program (self, g_value_get_string (value));
|
||
|
break;
|
||
|
|
||
|
case PROP_SEARCH_PATH:
|
||
|
ide_lsp_service_set_search_path (self, g_value_get_boxed (value));
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_lsp_service_destroy (IdeObject *object)
|
||
|
{
|
||
|
IdeLspService *self = (IdeLspService *)object;
|
||
|
IdeLspServicePrivate *priv = ide_lsp_service_get_instance_private (self);
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
ide_lsp_service_stop (self);
|
||
|
|
||
|
g_clear_object (&priv->supervisor);
|
||
|
g_clear_object (&priv->client);
|
||
|
g_clear_pointer (&priv->program, g_free);
|
||
|
g_clear_pointer (&priv->search_path, g_strfreev);
|
||
|
|
||
|
IDE_OBJECT_CLASS (ide_lsp_service_parent_class)->destroy (object);
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
static IdeSubprocessLauncher *
|
||
|
ide_lsp_service_real_create_launcher (IdeLspService *self,
|
||
|
IdePipeline *pipeline,
|
||
|
GSubprocessFlags flags)
|
||
|
{
|
||
|
IdeLspServicePrivate *priv = ide_lsp_service_get_instance_private (self);
|
||
|
g_autoptr(IdeSubprocessLauncher) launcher = NULL;
|
||
|
g_autoptr(IdeContext) context = NULL;
|
||
|
const char *srcdir;
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_assert (IDE_IS_LSP_SERVICE (self));
|
||
|
g_assert (IDE_IS_PIPELINE (pipeline));
|
||
|
|
||
|
if (priv->program == NULL)
|
||
|
IDE_RETURN (NULL);
|
||
|
|
||
|
context = ide_object_ref_context (IDE_OBJECT (self));
|
||
|
srcdir = ide_pipeline_get_srcdir (pipeline);
|
||
|
|
||
|
/* First try in the build environment */
|
||
|
if (ide_pipeline_contains_program_in_path (pipeline, priv->program, NULL))
|
||
|
{
|
||
|
if ((launcher = ide_pipeline_create_launcher (pipeline, NULL)))
|
||
|
{
|
||
|
ide_subprocess_launcher_set_flags (launcher, flags);
|
||
|
ide_subprocess_launcher_push_argv (launcher, priv->program);
|
||
|
ide_subprocess_launcher_set_cwd (launcher, srcdir);
|
||
|
IDE_RETURN (g_steal_pointer (&launcher));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Then try on the host if we find it there */
|
||
|
if (launcher == NULL)
|
||
|
{
|
||
|
IdeRuntimeManager *runtime_manager = ide_runtime_manager_from_context (context);
|
||
|
IdeRuntime *host = ide_runtime_manager_get_runtime (runtime_manager, "host");
|
||
|
|
||
|
if (ide_runtime_contains_program_in_path (host, priv->program, NULL))
|
||
|
{
|
||
|
if ((launcher = ide_runtime_create_launcher (host, NULL)))
|
||
|
{
|
||
|
ide_subprocess_launcher_set_flags (launcher, flags);
|
||
|
ide_subprocess_launcher_push_argv (launcher, priv->program);
|
||
|
ide_subprocess_launcher_set_cwd (launcher, srcdir);
|
||
|
IDE_RETURN (g_steal_pointer (&launcher));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* If we didn't find it in the host, we might have an alternate
|
||
|
* search path we can try.
|
||
|
*/
|
||
|
if (priv->search_path)
|
||
|
{
|
||
|
for (guint i = 0; priv->search_path[i]; i++)
|
||
|
{
|
||
|
g_autofree char *path = g_build_filename (priv->search_path[i], priv->program, NULL);
|
||
|
|
||
|
if (g_file_test (path, G_FILE_TEST_IS_EXECUTABLE))
|
||
|
{
|
||
|
if ((launcher = ide_runtime_create_launcher (host, NULL)))
|
||
|
{
|
||
|
ide_subprocess_launcher_push_argv (launcher, path);
|
||
|
ide_subprocess_launcher_set_flags (launcher, flags);
|
||
|
ide_subprocess_launcher_set_cwd (launcher, srcdir);
|
||
|
IDE_RETURN (g_steal_pointer (&launcher));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Finally fallback to Builder's execution runtime */
|
||
|
if (launcher == NULL)
|
||
|
{
|
||
|
g_autofree char *path = NULL;
|
||
|
|
||
|
if ((path = g_find_program_in_path (priv->program)))
|
||
|
{
|
||
|
launcher = ide_subprocess_launcher_new (flags);
|
||
|
ide_subprocess_launcher_push_argv (launcher, path);
|
||
|
ide_subprocess_launcher_set_cwd (launcher, srcdir);
|
||
|
IDE_RETURN (g_steal_pointer (&launcher));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
IDE_RETURN (NULL);
|
||
|
}
|
||
|
|
||
|
G_NORETURN static void
|
||
|
ide_lsp_service_real_configure_client (IdeLspService *self,
|
||
|
IdeLspClient *client)
|
||
|
{
|
||
|
g_assert (IDE_IS_LSP_SERVICE (self));
|
||
|
g_assert (IDE_IS_LSP_CLIENT (client));
|
||
|
|
||
|
g_assert_not_reached ();
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_lsp_service_real_configure_launcher (IdeLspService *self,
|
||
|
IdePipeline *pipeline,
|
||
|
IdeSubprocessLauncher *launcher)
|
||
|
{
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_assert (IDE_IS_LSP_SERVICE (self));
|
||
|
g_assert (!pipeline || IDE_IS_PIPELINE (pipeline));
|
||
|
g_assert (IDE_IS_SUBPROCESS_LAUNCHER (launcher));
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_lsp_service_real_configure_supervisor (IdeLspService *self,
|
||
|
IdeSubprocessSupervisor *client)
|
||
|
{
|
||
|
IDE_ENTRY;
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_lsp_service_class_init (IdeLspServiceClass *klass)
|
||
|
{
|
||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||
|
IdeObjectClass *ide_object_class = IDE_OBJECT_CLASS (klass);
|
||
|
IdeLspServiceClass *service_class = IDE_LSP_SERVICE_CLASS (klass);
|
||
|
|
||
|
object_class->get_property = ide_lsp_service_get_property;
|
||
|
object_class->set_property = ide_lsp_service_set_property;
|
||
|
|
||
|
ide_object_class->destroy = ide_lsp_service_destroy;
|
||
|
|
||
|
service_class->create_launcher = ide_lsp_service_real_create_launcher;
|
||
|
service_class->configure_client = ide_lsp_service_real_configure_client;
|
||
|
service_class->configure_launcher = ide_lsp_service_real_configure_launcher;
|
||
|
service_class->configure_supervisor = ide_lsp_service_real_configure_supervisor;
|
||
|
|
||
|
/**
|
||
|
* IdeLspService:client:
|
||
|
*
|
||
|
* The [class@LspClient] provided by the service, or %NULL if it has not been started yet.
|
||
|
*/
|
||
|
properties[PROP_CLIENT] =
|
||
|
g_param_spec_object ("client",
|
||
|
"Client",
|
||
|
"Client",
|
||
|
IDE_TYPE_LSP_CLIENT,
|
||
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
|
||
|
|
||
|
/**
|
||
|
* IdeLspService:program:
|
||
|
*
|
||
|
* The "program" property contains the name of the executable to
|
||
|
* launch. If this is set, the create-launcher signal will use it
|
||
|
* to locate and execute the program if found.
|
||
|
*/
|
||
|
properties [PROP_PROGRAM] =
|
||
|
g_param_spec_string ("program",
|
||
|
"Program",
|
||
|
"The program executable name",
|
||
|
NULL,
|
||
|
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
|
||
|
|
||
|
/**
|
||
|
* IdeLspService:search-path:
|
||
|
*
|
||
|
* An alternate search path to locate the program on the host.
|
||
|
*/
|
||
|
properties [PROP_SEARCH_PATH] =
|
||
|
g_param_spec_boxed ("search-path",
|
||
|
"Search Path",
|
||
|
"Search Path",
|
||
|
G_TYPE_STRV,
|
||
|
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
|
||
|
|
||
|
/**
|
||
|
* IdeLspService:supervisor:
|
||
|
*
|
||
|
* The [class@SubprocessSupervisor] that manages the language server process, or %NULL if the
|
||
|
* service is not running.
|
||
|
*/
|
||
|
properties[PROP_SUPERVISOR] =
|
||
|
g_param_spec_object ("supervisor",
|
||
|
"Supervisor",
|
||
|
"Supervisor",
|
||
|
IDE_TYPE_LSP_CLIENT,
|
||
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
|
||
|
|
||
|
/**
|
||
|
* IdeLspService:inherit-stderr:
|
||
|
*
|
||
|
* If inherit-stderr is enabled, the language server process's stderr is passed through to Builder's.
|
||
|
*/
|
||
|
properties[PROP_INHERIT_STDERR] =
|
||
|
g_param_spec_boolean ("inherit-stderr",
|
||
|
"Inherit stderr",
|
||
|
"Inherit stderr",
|
||
|
FALSE,
|
||
|
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||
|
|
||
|
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||
|
|
||
|
/**
|
||
|
* IdeLspService::create-launcher:
|
||
|
* @self: an [class@LspService]
|
||
|
* @pipeline: a loaded [class@Pipeline]
|
||
|
* @flags: [flags@Gio.SubprocessFlags] to use for the launcher
|
||
|
*
|
||
|
* Creates the launcher to be used for the LSP.
|
||
|
*
|
||
|
* If you want to use a launcher on the host, this would be a good
|
||
|
* place to determine that.
|
||
|
*
|
||
|
* Returns: (transfer full) (nullable): a [class@SubprocessLauncher] or %NULL
|
||
|
*/
|
||
|
signals [CREATE_LAUNCHER] =
|
||
|
g_signal_new ("create-launcher",
|
||
|
G_TYPE_FROM_CLASS (klass),
|
||
|
G_SIGNAL_RUN_LAST,
|
||
|
G_STRUCT_OFFSET (IdeLspServiceClass, create_launcher),
|
||
|
g_signal_accumulator_first_wins, NULL,
|
||
|
NULL,
|
||
|
IDE_TYPE_SUBPROCESS_LAUNCHER,
|
||
|
2,
|
||
|
IDE_TYPE_PIPELINE,
|
||
|
G_TYPE_SUBPROCESS_FLAGS);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_lsp_service_init (IdeLspService *self)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_lsp_service_get_inherit_stderr:
|
||
|
* @self: a [class@LspService]
|
||
|
*
|
||
|
* Gets whether the language server process's stderr output should be passed to Builder's.
|
||
|
*
|
||
|
* Returns: %TRUE if the subprocess inherits stderr, otherwise %FALSE
|
||
|
*/
|
||
|
gboolean
|
||
|
ide_lsp_service_get_inherit_stderr (IdeLspService *self)
|
||
|
{
|
||
|
IdeLspServicePrivate *priv = ide_lsp_service_get_instance_private (self);
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
|
||
|
g_return_val_if_fail (IDE_IS_LSP_SERVICE (self), FALSE);
|
||
|
|
||
|
return priv->inherit_stderr;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_lsp_service_set_inherit_stderr:
|
||
|
* @self: a [class@LspService]
|
||
|
* @inherit_stderr: %TRUE to enable stderr, %FALSE to disable it
|
||
|
*
|
||
|
* Gets whether the language server process's stderr output should be passed to Builder's.
|
||
|
*/
|
||
|
void
|
||
|
ide_lsp_service_set_inherit_stderr (IdeLspService *self,
|
||
|
gboolean inherit_stderr)
|
||
|
{
|
||
|
IdeLspServicePrivate *priv = ide_lsp_service_get_instance_private (self);
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
||
|
g_return_if_fail (IDE_IS_LSP_SERVICE (self));
|
||
|
|
||
|
inherit_stderr = !!inherit_stderr;
|
||
|
|
||
|
if (priv->inherit_stderr != inherit_stderr)
|
||
|
{
|
||
|
priv->inherit_stderr = inherit_stderr;
|
||
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INHERIT_STDERR]);
|
||
|
}
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
on_supervisor_spawned_cb (IdeLspService *self,
|
||
|
IdeSubprocess *subprocess,
|
||
|
IdeSubprocessSupervisor *supervisor)
|
||
|
{
|
||
|
IdeLspServicePrivate *priv = ide_lsp_service_get_instance_private (self);
|
||
|
IdeLspServiceClass *klass;
|
||
|
g_autoptr(GIOStream) iostream = NULL;
|
||
|
g_autoptr(IdeLspClient) client = NULL;
|
||
|
g_autoptr(GInputStream) to_stdout = NULL;
|
||
|
g_autoptr(GOutputStream) to_stdin = NULL;
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_assert (IDE_IS_MAIN_THREAD ());
|
||
|
g_assert (IDE_IS_LSP_SERVICE (self));
|
||
|
g_assert (IDE_IS_SUBPROCESS (subprocess));
|
||
|
g_assert (IDE_IS_SUBPROCESS_SUPERVISOR (supervisor));
|
||
|
|
||
|
klass = IDE_LSP_SERVICE_GET_CLASS (self);
|
||
|
|
||
|
to_stdin = ide_subprocess_get_stdin_pipe (subprocess);
|
||
|
to_stdout = ide_subprocess_get_stdout_pipe (subprocess);
|
||
|
iostream = g_simple_io_stream_new (to_stdout, to_stdin);
|
||
|
|
||
|
if (priv->client != NULL)
|
||
|
{
|
||
|
ide_lsp_client_stop (priv->client);
|
||
|
ide_object_destroy (IDE_OBJECT (priv->client));
|
||
|
}
|
||
|
|
||
|
client = ide_lsp_client_new (iostream);
|
||
|
ide_object_append (IDE_OBJECT (self), IDE_OBJECT (client));
|
||
|
|
||
|
klass->configure_client (self, client);
|
||
|
|
||
|
ide_lsp_client_start (client);
|
||
|
|
||
|
priv->client = g_steal_pointer (&client);
|
||
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CLIENT]);
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ensure_started (IdeLspService *self,
|
||
|
IdeContext *context)
|
||
|
{
|
||
|
IdeLspServicePrivate *priv = ide_lsp_service_get_instance_private (self);
|
||
|
g_autoptr(IdeSubprocessLauncher) launcher = NULL;
|
||
|
g_autoptr(IdeSubprocessSupervisor) supervisor = NULL;
|
||
|
IdeBuildManager *build_manager;
|
||
|
IdeLspServiceClass *klass;
|
||
|
IdePipeline *pipeline = NULL;
|
||
|
GSubprocessFlags flags;
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_assert (IDE_IS_MAIN_THREAD ());
|
||
|
g_assert (IDE_IS_LSP_SERVICE (self));
|
||
|
g_assert (IDE_IS_CONTEXT (context));
|
||
|
|
||
|
if (priv->has_started)
|
||
|
IDE_EXIT;
|
||
|
|
||
|
g_assert (priv->supervisor == NULL);
|
||
|
g_assert (priv->client == NULL);
|
||
|
|
||
|
klass = IDE_LSP_SERVICE_GET_CLASS (self);
|
||
|
build_manager = ide_build_manager_from_context (context);
|
||
|
pipeline = ide_build_manager_get_pipeline (build_manager);
|
||
|
|
||
|
/* Delay until pipeline is ready */
|
||
|
if (!ide_pipeline_is_ready (pipeline))
|
||
|
IDE_EXIT;
|
||
|
|
||
|
flags = G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE;
|
||
|
if (!priv->inherit_stderr)
|
||
|
flags |= G_SUBPROCESS_FLAGS_STDERR_SILENCE;
|
||
|
|
||
|
/* Allow subclasses to control launcher creation */
|
||
|
g_signal_emit (self, signals [CREATE_LAUNCHER], 0, pipeline, flags, &launcher);
|
||
|
if (launcher == NULL)
|
||
|
IDE_EXIT;
|
||
|
|
||
|
klass->configure_launcher (self, pipeline, launcher);
|
||
|
|
||
|
supervisor = ide_subprocess_supervisor_new ();
|
||
|
ide_subprocess_supervisor_set_launcher (supervisor, launcher);
|
||
|
g_signal_connect_object (supervisor,
|
||
|
"spawned",
|
||
|
G_CALLBACK (on_supervisor_spawned_cb),
|
||
|
self,
|
||
|
G_CONNECT_SWAPPED);
|
||
|
|
||
|
priv->has_started = TRUE;
|
||
|
|
||
|
klass->configure_supervisor (self, supervisor);
|
||
|
ide_subprocess_supervisor_start (supervisor);
|
||
|
priv->supervisor = g_steal_pointer (&supervisor);
|
||
|
|
||
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SUPERVISOR]);
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_lsp_service_restart:
|
||
|
* @self: a [class@LspService]
|
||
|
*
|
||
|
* Restarts the service and its associated process.
|
||
|
*/
|
||
|
void
|
||
|
ide_lsp_service_restart (IdeLspService *self)
|
||
|
{
|
||
|
IdeContext *context;
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
||
|
g_return_if_fail (IDE_IS_LSP_SERVICE (self));
|
||
|
g_return_if_fail (!ide_object_in_destruction (IDE_OBJECT (self)));
|
||
|
|
||
|
ide_lsp_service_stop (self);
|
||
|
|
||
|
if ((context = ide_object_get_context (IDE_OBJECT (self))))
|
||
|
ensure_started (self, context);
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
on_pipeline_loaded_cb (IdeLspService *self,
|
||
|
IdePipeline *pipeline)
|
||
|
{
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_assert (IDE_IS_LSP_SERVICE (self));
|
||
|
g_assert (IDE_IS_PIPELINE (pipeline));
|
||
|
|
||
|
ide_lsp_service_restart (self);
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
on_notify_pipeline_cb (IdeLspService *self,
|
||
|
GParamSpec *pspec,
|
||
|
IdeBuildManager *build_manager)
|
||
|
{
|
||
|
IdePipeline *pipeline;
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_assert (IDE_IS_LSP_SERVICE (self));
|
||
|
g_assert (IDE_IS_BUILD_MANAGER (build_manager));
|
||
|
|
||
|
ide_lsp_service_stop (self);
|
||
|
|
||
|
if ((pipeline = ide_build_manager_get_pipeline (build_manager)))
|
||
|
{
|
||
|
if (!ide_pipeline_is_ready (pipeline))
|
||
|
g_signal_connect_object (pipeline,
|
||
|
"loaded",
|
||
|
G_CALLBACK (on_pipeline_loaded_cb),
|
||
|
self,
|
||
|
G_CONNECT_SWAPPED);
|
||
|
else
|
||
|
ide_lsp_service_restart (self);
|
||
|
}
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_lsp_service_class_bind_client_internal (IdeLspServiceClass *klass,
|
||
|
IdeObject *provider,
|
||
|
gboolean autostart)
|
||
|
{
|
||
|
IdeContext *context;
|
||
|
GParamSpec *pspec;
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
||
|
g_return_if_fail (IDE_IS_LSP_SERVICE_CLASS (klass));
|
||
|
g_return_if_fail (IDE_IS_OBJECT (provider));
|
||
|
|
||
|
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (provider), "client");
|
||
|
g_return_if_fail (pspec != NULL && g_type_is_a (pspec->value_type, IDE_TYPE_LSP_CLIENT));
|
||
|
|
||
|
context = ide_object_get_context (provider);
|
||
|
g_return_if_fail (IDE_IS_CONTEXT (context));
|
||
|
|
||
|
/* If the context has a project (ie: not editor mode), then we
|
||
|
* want to track changes to the pipeline so we can reload the
|
||
|
* language server automatically.
|
||
|
*/
|
||
|
if (ide_context_has_project (context))
|
||
|
{
|
||
|
IdeLspServicePrivate *priv;
|
||
|
IdeBuildManager *build_manager = ide_build_manager_from_context (context);
|
||
|
g_autoptr(IdeLspService) service = NULL;
|
||
|
gboolean do_notify = FALSE;
|
||
|
|
||
|
if (!(service = ide_object_get_child_typed (IDE_OBJECT (context), G_OBJECT_CLASS_TYPE (klass))))
|
||
|
{
|
||
|
service = ide_object_ensure_child_typed (IDE_OBJECT (context), G_OBJECT_CLASS_TYPE (klass));
|
||
|
g_signal_connect_object (build_manager,
|
||
|
"notify::pipeline",
|
||
|
G_CALLBACK (on_notify_pipeline_cb),
|
||
|
service,
|
||
|
G_CONNECT_SWAPPED);
|
||
|
do_notify = TRUE;
|
||
|
}
|
||
|
|
||
|
priv = ide_lsp_service_get_instance_private (service);
|
||
|
do_notify |= (autostart && !priv->has_started);
|
||
|
|
||
|
if (do_notify)
|
||
|
on_notify_pipeline_cb (service, NULL, build_manager);
|
||
|
|
||
|
g_object_bind_property (service, "client", provider, "client", G_BINDING_SYNC_CREATE);
|
||
|
}
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_lsp_service_class_bind_client:
|
||
|
* @klass: a [class@LspService] class structure
|
||
|
* @provider: an [class@Object]
|
||
|
*
|
||
|
* Binds the "client" property of @property to its context's instance of
|
||
|
* @klass. If the language server is not running yet, it will be started.
|
||
|
*/
|
||
|
void
|
||
|
ide_lsp_service_class_bind_client (IdeLspServiceClass *klass,
|
||
|
IdeObject *provider)
|
||
|
{
|
||
|
ide_lsp_service_class_bind_client_internal (klass, provider, TRUE);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_lsp_service_class_bind_client_lazy:
|
||
|
* @klass: a [class@LspService] class structure
|
||
|
* @provider: an [class@Object]
|
||
|
*
|
||
|
* Like ide_lsp_service_bind_client() but will not immediately spawn
|
||
|
* the language server.
|
||
|
*/
|
||
|
void
|
||
|
ide_lsp_service_class_bind_client_lazy (IdeLspServiceClass *klass,
|
||
|
IdeObject *provider)
|
||
|
{
|
||
|
ide_lsp_service_class_bind_client_internal (klass, provider, FALSE);
|
||
|
}
|
||
|
|
||
|
const char *
|
||
|
ide_lsp_service_get_program (IdeLspService *self)
|
||
|
{
|
||
|
IdeLspServicePrivate *priv = ide_lsp_service_get_instance_private (self);
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_LSP_SERVICE (self), NULL);
|
||
|
|
||
|
return priv->program;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ide_lsp_service_set_program (IdeLspService *self,
|
||
|
const char *program)
|
||
|
{
|
||
|
IdeLspServicePrivate *priv = ide_lsp_service_get_instance_private (self);
|
||
|
|
||
|
g_return_if_fail (IDE_IS_LSP_SERVICE (self));
|
||
|
|
||
|
if (g_strcmp0 (program, priv->program) != 0)
|
||
|
{
|
||
|
g_free (priv->program);
|
||
|
priv->program = g_strdup (program);
|
||
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PROGRAM]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const char * const *
|
||
|
ide_lsp_service_get_search_path (IdeLspService *self)
|
||
|
{
|
||
|
IdeLspServicePrivate *priv = ide_lsp_service_get_instance_private (self);
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_LSP_SERVICE (self), NULL);
|
||
|
|
||
|
return (const char * const *)priv->search_path;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_lsp_service_set_search_path:
|
||
|
* @self: a #IdeLspService
|
||
|
* @search_path: (array zero-terminated=1) (element-type utf8) (nullable):
|
||
|
* a search path to apply when searching the host or %NULL.
|
||
|
*
|
||
|
* Sets an alternate search path to use when discovering programs on
|
||
|
* the host system.
|
||
|
*/
|
||
|
void
|
||
|
ide_lsp_service_set_search_path (IdeLspService *self,
|
||
|
const char * const *search_path)
|
||
|
{
|
||
|
IdeLspServicePrivate *priv = ide_lsp_service_get_instance_private (self);
|
||
|
|
||
|
g_return_if_fail (IDE_IS_LSP_SERVICE (self));
|
||
|
|
||
|
if ((const char * const *)priv->search_path == search_path)
|
||
|
return;
|
||
|
|
||
|
g_strfreev (priv->search_path);
|
||
|
priv->search_path = g_strdupv ((char **)search_path);
|
||
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SEARCH_PATH]);
|
||
|
}
|