[gnome-builder] intelephense: Add intelephense lsp client implementation



commit fe1045aa5a219c38e05419e23952a71d0d4a9ae1
Author: Peter Maatman <blackwolf12333 gmail com>
Date:   Thu Jan 14 15:43:47 2021 +0100

    intelephense: Add intelephense lsp client implementation
    
    This introduces an lsp client implementation for the intelephense
    (https://intelephense.com/) language server for PHP.
    
    The plugin supports most of the functionality in the language server,
    but not everything is accessible without a licence key for intelephense.
    There is currently no way to provide this license key except modifying the
    plugin itself.

 meson_options.txt                            |   1 +
 src/plugins/intelephense/intelephense.plugin |  16 +++
 src/plugins/intelephense/intelephense.py     | 174 +++++++++++++++++++++++++++
 src/plugins/intelephense/meson.build         |  13 ++
 src/plugins/meson.build                      |   2 +
 5 files changed, 206 insertions(+)
---
diff --git a/meson_options.txt b/meson_options.txt
index 53f189304..6f160a564 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -52,6 +52,7 @@ option('plugin_grep', type: 'boolean')
 option('plugin_gvls', type: 'boolean')
 option('plugin_html_completion', type: 'boolean')
 option('plugin_html_preview', type: 'boolean')
+option('plugin_intelephense', type: 'boolean')
 option('plugin_jedi_language_server', type: 'boolean')
 option('plugin_jhbuild', type: 'boolean')
 option('plugin_make', type: 'boolean')
diff --git a/src/plugins/intelephense/intelephense.plugin b/src/plugins/intelephense/intelephense.plugin
new file mode 100644
index 000000000..45a118a17
--- /dev/null
+++ b/src/plugins/intelephense/intelephense.plugin
@@ -0,0 +1,16 @@
+[Plugin]
+Builtin=true
+Name=Intelephense LSP plugin
+Description=Provides a Language Server Protocol implementation for PHP.
+Loader=python3
+Module=intelephense
+Author=Peter Maatman <blackwolf12333 gmail com>
+X-Builder-ABI=@PACKAGE_ABI@
+X-Completion-Provider-Languages=php
+X-Symbol-Resolver-Languages=php
+X-Diagnostic-Provider-Languages=php
+X-Highlighter-Languages=php
+X-Hover-Provider-Languages=php
+X-Rename-Provider-Languages=php
+X-Formatter-Languages=php
+X-Code-Action-Languages=php
diff --git a/src/plugins/intelephense/intelephense.py b/src/plugins/intelephense/intelephense.py
new file mode 100644
index 000000000..364ef6982
--- /dev/null
+++ b/src/plugins/intelephense/intelephense.py
@@ -0,0 +1,174 @@
+#!/usr/bin/env python3
+
+import os
+import gi
+
+from gi.repository import GLib
+from gi.repository import Gio
+from gi.repository import GObject
+from gi.repository import Ide
+
+DEV_MODE = os.getenv('DEV_MODE') and True or False
+
+class PhpService(Ide.Object):
+    _client = None
+    _has_started = False
+    _supervisor = None
+    _context = None
+    notif = None
+
+    @classmethod
+    def from_context(klass, context):
+        return context.ensure_child_typed(PhpService)
+
+    @GObject.Property(type=Ide.LspClient)
+    def client(self):
+        return self._client
+
+    @client.setter
+    def client(self, value):
+        self._client = value
+        self.notify('client')
+
+    def do_stop(self):
+        if self._supervisor:
+            supervisor, self._supervisor = self._supervisor, None
+            supervisor.stop()
+
+    def _on_load_configuration(self, data):
+        try:
+            d = GLib.Variant('a{sv}', {
+                "intelephense": GLib.Variant('a{sv}', {
+                    "files": GLib.Variant('a{sv}', {
+                        "associations": GLib.Variant('as', ["*.php", "*.phtml"]),
+                        "exclude": GLib.Variant('as', [])
+                    }),
+                    "completion": GLib.Variant('a{sv}', {
+                        "insertUseDeclaration": GLib.Variant('b', True),
+                        "fullyQualifyGlobalConstantsAndFunctions": GLib.Variant('b', False),
+                        "triggerParameterHints": GLib.Variant('b', True),
+                        "maxItems": GLib.Variant('i', 100)
+                    }),
+                    "format": GLib.Variant('a{sv}', {
+                        "enable": GLib.Variant('b', True)
+                    })
+                })
+            })
+
+            return d
+        except Error as e:
+            Ide.critical ('On Load Configuration Error: {}'.format(e.message))
+            return GLib.Variant ('a{sv}', {})
+
+    def _on_notification(self, client, name, data):
+        if name == "indexingStarted":
+            if self.notif is not None:
+                self.notif.withdraw()
+
+            self.notif = Ide.Notification(
+                id='org.gnome.builder.intelephense.indexing',
+                title="Intelephense",
+                body=_('Indexing php code...'),
+                has_progress=True,
+                progress_is_imprecise=True,
+                progress=0.0)
+            self.notif.attach(self._context)
+        elif name == "indexingEnded":
+            if self.notif is not None:
+                self.notif.withdraw()
+
+    def _get_runtime(self):
+        config_manager = Ide.ConfigManager.from_context(self._context)
+        config = config_manager.get_current()
+        return config.get_runtime()
+
+    def _ensure_started(self):
+        # To avoid starting the process unconditionally at startup, 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
+
+            launcher = self._create_launcher()
+            launcher.set_clear_env(False)
+
+            # Locate the directory of the project and run intelephense from there
+            workdir = self.get_context().ref_workdir()
+            launcher.set_cwd(workdir.get_path())
+
+            launcher.push_argv("intelephense")
+            launcher.push_argv("--stdio")
+
+            # 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._ls_spawned)
+            self._supervisor.set_launcher(launcher)
+            self._supervisor.start()
+
+    def _ls_spawned(self, supervisor, subprocess):
+        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._client.connect('load-configuration', self._on_load_configuration)
+        self._client.connect('notification', self._on_notification)
+        self.append(self._client)
+        self._client.add_language('php')
+        self._client.start()
+        self.notify('client')
+
+    def _create_launcher(self):
+        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)
+        return launcher
+
+    @classmethod
+    def bind_client(klass, provider):
+        context = provider.get_context()
+        self = PhpService.from_context(context)
+        self._context = context
+        self._ensure_started()
+        self.bind_property('client', provider, 'client', GObject.BindingFlags.SYNC_CREATE)
+
+class PhpLspSymbolResolver(Ide.LspSymbolResolver, Ide.SymbolResolver):
+    def do_load(self):
+        PhpService.bind_client(self)
+
+class PhpLspHoverProvider(Ide.LspHoverProvider):
+    def do_prepare(self):
+        self.props.category = 'PHP'
+        self.props.priority = 300
+        PhpService.bind_client(self)
+
+class PhpLspFormatter(Ide.LspFormatter, Ide.Formatter):
+    def do_load(self):
+        PhpService.bind_client(self)
+
+class PhpLspDiagnosticProvider(Ide.LspDiagnosticProvider, Ide.DiagnosticProvider):
+   def do_load(self):
+       PhpService.bind_client(self)
+
+class PhpLspCompletionProvider(Ide.LspCompletionProvider, Ide.CompletionProvider):
+    def do_load(self, context):
+        PhpService.bind_client(self)
+
+class PhpLspHighlighter(Ide.LspHighlighter, Ide.Highlighter):
+    def do_load(self):
+        PhpService.bind_client(self)
+
+class PhpLspRenameProvider(Ide.LspRenameProvider, Ide.RenameProvider):
+    def do_load(self):
+        PhpService.bind_client(self)
+
+class PhpLspCodeActionProvider(Ide.LspCodeActionProvider, Ide.CodeActionProvider):
+    def do_load(self):
+        PhpService.bind_client(self)
diff --git a/src/plugins/intelephense/meson.build b/src/plugins/intelephense/meson.build
new file mode 100644
index 000000000..d04fe8cb4
--- /dev/null
+++ b/src/plugins/intelephense/meson.build
@@ -0,0 +1,13 @@
+if get_option('plugin_intelephense')
+
+install_data('intelephense.py', install_dir: plugindir)
+
+configure_file(
+          input: 'intelephense.plugin',
+         output: 'intelephense.plugin',
+  configuration: config_h,
+        install: true,
+    install_dir: plugindir,
+)
+
+endif
diff --git a/src/plugins/meson.build b/src/plugins/meson.build
index 6ca14f9cf..5d8f36969 100644
--- a/src/plugins/meson.build
+++ b/src/plugins/meson.build
@@ -88,6 +88,7 @@ subdir('gvls')
 subdir('history')
 subdir('html-completion')
 subdir('html-preview')
+subdir('intelephense')
 subdir('jedi-language-server')
 subdir('jhbuild')
 subdir('line-spacing')
@@ -182,6 +183,7 @@ status += [
   'Grep .................. : @0@'.format(get_option('plugin_grep')),
   'HTML Completion ....... : @0@'.format(get_option('plugin_html_completion')),
   'HTML Preview .......... : @0@'.format(get_option('plugin_html_preview')),
+  'Intelephense .......... : @0@'.format(get_option('plugin_intelephense')),
   'Jedi Language Server... : @0@'.format(get_option('plugin_jedi_language_server')),
   'JHBuild ............... : @0@'.format(get_option('plugin_jhbuild')),
   'Make .................. : @0@'.format(get_option('plugin_make')),


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