[gnome-builder/1074-vala-language-server-add-support-using-gvls-2: 3/3] GVls: add plugin for Vala development



commit b22482f86d0e36ce2d7c5ea075d8d090b7b79775
Author: Daniel Espinosa Ortiz <esodan gmail com>
Date:   Mon Dec 16 18:28:12 2019 -0600

    GVls: add plugin for Vala development
    
    Replace vala-pack

 meson_options.txt               |   3 +-
 src/plugins/gvls/gvls.plugin    |  14 +++
 src/plugins/gvls/gvls_plugin.py | 231 ++++++++++++++++++++++++++++++++++++++++
 src/plugins/gvls/meson.build    |  15 +++
 src/plugins/meson.build         |   2 +
 5 files changed, 264 insertions(+), 1 deletion(-)
---
diff --git a/meson_options.txt b/meson_options.txt
index 15fce38b1..0e131f2b5 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -45,6 +45,7 @@ option('plugin_gnome_code_assistance', type: 'boolean')
 option('plugin_go_langserv', type: 'boolean')
 option('plugin_gradle', type: 'boolean')
 option('plugin_grep', type: 'boolean')
+option('plugin_gvls', type: 'boolean')
 option('plugin_html_completion', type: 'boolean')
 option('plugin_html_preview', type: 'boolean')
 option('plugin_jedi', type: 'boolean')
@@ -71,7 +72,7 @@ option('plugin_stylelint', type: 'boolean')
 option('plugin_sysprof', type: 'boolean')
 option('plugin_sysroot', type: 'boolean')
 option('plugin_todo', type: 'boolean')
-option('plugin_vala', type: 'boolean')
+option('plugin_vala', type: 'boolean', value: false)
 option('plugin_vagrant', type: 'boolean', value: false)
 option('plugin_valgrind', type: 'boolean')
 option('plugin_waf', type: 'boolean')
diff --git a/src/plugins/gvls/gvls.plugin b/src/plugins/gvls/gvls.plugin
new file mode 100644
index 000000000..1770cc6f8
--- /dev/null
+++ b/src/plugins/gvls/gvls.plugin
@@ -0,0 +1,14 @@
+# gvls.plugin
+[Plugin]
+Author=Daniel Espinosa
+Name=GNOME Vala Language Server
+Description=GNOME Builder plugin for GVLS using LSP
+Copyright=Copyright © 2019 Daniel Espinosa
+Builtin=true
+Loader=python3
+Module=gvls_plugin
+X-Builder-ABI=@PACKAGE_ABI@
+X-Completion-Provider-Languages=vala
+X-Highlighter-Languages=vala
+X-Symbol-Resolver-Languages=vala
+X-Diagnostic-Provider-Languages=vala
diff --git a/src/plugins/gvls/gvls_plugin.py b/src/plugins/gvls/gvls_plugin.py
new file mode 100644
index 000000000..39abbf589
--- /dev/null
+++ b/src/plugins/gvls/gvls_plugin.py
@@ -0,0 +1,231 @@
+#!/usr/bin/env python
+
+# gvls_plugin.py
+#
+# Copyright 2016 Christian Hergert <chergert redhat com>
+# Copyright 2019 Daniel Espinosa <esodan gmail com>
+#
+# 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/>.
+
+"""
+This plugin provides integration with the Vala Language Server.
+It builds off the generic language service components in libide
+by bridging them to our supervised Vala Language Server.
+"""
+
+import gi
+import os
+
+from gi.repository import GLib
+from gi.repository import Gio
+from gi.repository import GObject
+from gi.repository import Ide
+from gi.repository import Gdk
+from gi.repository import Gtk
+from gi.repository import GtkSource
+
+DEV_MODE = True
+
+class GVlsService(Ide.Object):
+    _client = None
+    _has_started = False
+    _supervisor = None
+    _monitor = None
+
+    @classmethod
+    def from_context(klass, context):
+        return context.ensure_child_typed(GVlsService)
+
+    @GObject.Property(type=Ide.LspClient)
+    def client(self):
+        return self._client
+
+    @client.setter
+    def client(self, value):
+        self._client = value
+        ## Construct GVls Configuration response at `initialize`
+        self._client.connect('load-configuration', self._on_load_configuration)
+        self.notify('client')
+
+    def do_parent_set(self, parent):
+        """
+        No useful for VLS
+        """
+        if parent is None:
+            return
+
+        context = self.get_context()
+        workdir = context.ref_workdir()
+
+
+    def do_stop(self):
+        """
+        Stops the Vala Language Server upon request to shutdown the
+        GVlsService.
+        """
+        if self._client is not None:
+            print ("Shutting down server")
+            _client.stop()
+            _client.destroy()
+
+        if self._supervisor is not None:
+            supervisor, self._supervisor = self._supervisor, None
+            supervisor.stop()
+
+    def _ensure_started(self):
+        """
+        Start the Vala service which provides communication with the
+        Vala Language Server. We supervise our own instance of the
+        language server and restart it as necessary using the
+        Ide.SubprocessSupervisor.
+
+        Various extension points (diagnostics, symbol providers, etc) use
+        the GVlsService to access the rust components they need.
+        """
+        # To avoid starting the `gvls` process unconditionally at startup,
+        # we lazily start it when the first provider tries to bind a client
+        # to its :client property.
+        if not self._has_started:
+            self._has_started = True
+
+            # Setup a launcher to spawn the rust language server
+            launcher = self._create_launcher()
+            launcher.set_clear_env(False)
+            # Locate the directory of the project and run gvls from there.
+            workdir = self.get_context().ref_workdir()
+            launcher.set_cwd(workdir.get_path())
+
+            # If org.gnome.gvls.stdio.Server is installed by GVls
+            path = 'org.gnome.gvls.stdio.Server'
+
+            # Setup our Argv. We want to communicate over STDIN/STDOUT,
+            # so it does not require any command line options.
+            launcher.push_argv(path)
+
+            # Spawn our peer process and monitor it for
+            # crashes. We may need to restart it occasionally.
+            self._supervisor = Ide.SubprocessSupervisor()
+            self._supervisor.connect('spawned', self._gvls_spawned)
+            self._supervisor.set_launcher(launcher)
+            self._supervisor.start()
+
+    def _on_load_configuration(self):
+        try:
+            conf = GLib.VariantBuilder.new (GLib.VariantType.new ('a{sv}'))
+            conf.add_value(GLib.Variant.new_dict_entry ('initialized', GLib.Variant.new_boolean (True)))
+            conf.add_value(GLib.Variant.new_dict_entry ('initialized', GLib.Variant.new_boolean (True)))
+            conf.add_value(GLib.Variant.new_dict_entry ('defaultNamespaces', GLib.Variant.new_boolean 
(True)))
+            conf.add_value(GLib.Variant.new_dict_entry ('defaultVapiDirs', GLib.Variant.new_boolean (True)))
+            conf.add_value(GLib.Variant.new_dict_entry ('scanWorkspace', GLib.Variant.new_boolean (True)))
+            conf.add_value(GLib.Variant.new_dict_entry ('addUsingNamespaces', GLib.Variant.new_boolean 
(True)))
+
+            pkgs = GLib.VariantBuilder(GLib.VariantType.new ('av'))
+            # Get from build environment
+            # pkgs.add ('v', GLib.Variant.string ())
+            pkgs.end()
+
+            vargs = GLib.VariantBuilder (GLib.VariantType.new ('av'))
+            # Get from build environment
+            # vargs.add ('v', GLib.Variant.string ())
+            vargs.end()
+
+            opts = GLib.VariantBuilder.new (GLib.VariantType.new ('av'))
+            # Get from build environment
+            # vargs.add ('v', GLib.Variant.string ())
+            opts.end()
+
+            conf.add (GLib.Variant.new_dict_entry ('packages', pkgs))
+            conf.add (GLib.Variant.new_dict_entry ('valaArgs', vargs))
+            conf.add (GLib.Variant.new_dict_entry ('options', opts))
+
+            conf.end ()
+            return conf
+        except Exception as e:
+            print(e)
+
+
+    def _gvls_spawned(self, supervisor, subprocess):
+        """
+        This callback is executed when the `org.gnome.gvls.stdio.Server` process is spawned.
+        We can use the stdin/stdout to create a channel for our
+        LspClient.
+        """
+        stdin = subprocess.get_stdin_pipe()
+        stdout = subprocess.get_stdout_pipe()
+        io_stream = Gio.SimpleIOStream.new(stdout, stdin)
+
+        if self._client:
+            self._client.stop()
+            self._client.destroy()
+
+        self._client = Ide.LspClient.new(io_stream)
+        self.append(self._client)
+        self._client.add_language('vala')
+        self._client.start()
+        self.notify('client')
+
+    def _create_launcher(self):
+        """
+        Creates a launcher to be used by the vala service.
+
+        In the future, we might be able to rely on the runtime for
+        the tooling. Maybe even the program if flatpak-builder has
+        prebuilt our dependencies.
+        """
+        flags = Gio.SubprocessFlags.STDIN_PIPE | Gio.SubprocessFlags.STDOUT_PIPE
+        if not DEV_MODE:
+            flags |= Gio.SubprocessFlags.STDERR_SILENCE
+        launcher = Ide.SubprocessLauncher()
+        launcher.set_flags(flags)
+        launcher.set_cwd(GLib.get_home_dir())
+        launcher.set_run_on_host(True)
+        return launcher
+
+    @classmethod
+    def bind_client(klass, provider):
+        """
+        This helper tracks changes to our client as it might happen when
+        our `org.gnome.gvls.Server` process has crashed.
+        """
+        context = provider.get_context()
+        self = GVlsService.from_context(context)
+        self._ensure_started()
+        self.bind_property('client', provider, 'client', GObject.BindingFlags.SYNC_CREATE)
+
+#class GVlsDiagnosticProvider(Ide.LspDiagnosticProvider):
+#    def do_load(self):
+#        GVlsService.bind_client(self)
+
+class GVlsCompletionProvider(Ide.LspCompletionProvider):
+    def do_load(self, context):
+        GVlsService.bind_client(self)
+
+    def do_get_priority(self, context):
+        # This provider only activates when it is very likely that we
+        # want the results. So use high priority (negative is better).
+        return -1000
+
+class GVlsHighlighter(Ide.LspHighlighter):
+    def do_load(self):
+        GVlsService.bind_client(self)
+
+class GVlsSymbolResolver(Ide.LspSymbolResolver):
+    def do_load(self):
+        GVlsService.bind_client(self)
+
+
+class GVlsDiagnosticProvider(Ide.LspDiagnosticProvider):
+    def do_load(self):
+        GVlsService.bind_client(self)
+
diff --git a/src/plugins/gvls/meson.build b/src/plugins/gvls/meson.build
new file mode 100644
index 000000000..fe25a54af
--- /dev/null
+++ b/src/plugins/gvls/meson.build
@@ -0,0 +1,15 @@
+if get_option('plugin_gvls')
+
+install_data('gvls_plugin.py', install_dir: plugindir)
+
+configure_file(
+          input: 'gvls.plugin',
+         output: 'gvls.plugin',
+  configuration: config_h,
+        install: true,
+    install_dir: plugindir,
+)
+
+gvls_stdio_server = find_program('org.gnome.gvls.stdio.Server', required: false)
+
+endif
diff --git a/src/plugins/meson.build b/src/plugins/meson.build
index 9cf9c9120..dd3dc631c 100644
--- a/src/plugins/meson.build
+++ b/src/plugins/meson.build
@@ -125,6 +125,7 @@ subdir('vim')
 subdir('waf')
 subdir('words')
 subdir('xml-pack')
+subdir('gvls')
 
 plugins = static_library('plugins', plugins_sources,
          dependencies: plugins_deps,
@@ -186,6 +187,7 @@ status += [
   'Sysprof ............... : @0@'.format(get_option('plugin_sysprof')),
   'Sysroot ............... : @0@'.format(get_option('plugin_sysroot')),
   'Todo .................. : @0@'.format(get_option('plugin_todo')),
+  'Vala Language Server... : @0@'.format(get_option('plugin_gvls')),
   'Vala Pack ............. : @0@'.format(get_option('plugin_vala')),
   'Vagrant ............... : @0@'.format(get_option('plugin_vagrant')),
   'Valgrind .............. : @0@'.format(get_option('plugin_valgrind')),


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]