Last active
September 1, 2021 21:45
-
-
Save ilhooq/7d179dcde9b4f2dfcda8709ceea4d879 to your computer and use it in GitHub Desktop.
A gnome-builder plugin to provide PHP Language Server integration with Phpactor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[Plugin] | |
Authors=Sylvain PHILIP <[email protected]> | |
Copyright=Copyright © 2021 Sylvain PHILIP | |
Description=Provides auto-completion for PHP, diagnostics, and other IDE features, working with Phpactor | |
Loader=python3 | |
Module=phpls_plugin | |
Name=PHP Language Server Integration | |
X-Completion-Provider-Languages=php | |
X-Diagnostic-Provider-Languages=php | |
X-Formatter-Languages=php | |
X-Highlighter-Languages=php | |
X-Hover-Provider-Languages=php | |
X-Rename-Provider-Languages=php | |
X-Symbol-Resolver-Languages=php | |
X-Builder-ABI=3.36 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# Copyright 2016 Christian Hergert <[email protected]> | |
# Copyright 2021 PHILIP Sylvain <[email protected]> | |
# | |
# 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 PHP Language Server implemented in | |
Phpactor. | |
To debug communication between builder and phpactor : | |
JSONRPC_DEBUG=1 gnome-builder -vvvv | |
""" | |
import gi | |
import os | |
import sys | |
from gi.repository import GLib | |
from gi.repository import Gio | |
from gi.repository import GObject | |
from gi.repository import Ide | |
DEV_MODE = False | |
class LSPService(Ide.Object): | |
_client = None | |
_has_started = False | |
_supervisor = None | |
_monitor = None | |
@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): | |
""" | |
Stops the PHP Language Server upon request to shutdown the | |
LSPService. | |
""" | |
if self._supervisor is not None: | |
supervisor, self._supervisor = self._supervisor, None | |
supervisor.stop() | |
def _ensure_started(self): | |
""" | |
Start the service which provides communication with the | |
PHP 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 LSPService to access the php components they need. | |
""" | |
# To avoid starting the `phpactor` 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 PHP language server | |
launcher = self._create_launcher() | |
launcher.set_clear_env(False) | |
# Locate the directory of the project and run rls from there. | |
workdir = self.get_context().ref_workdir() | |
launcher.set_cwd(workdir.get_path()) | |
launcher.push_argv("phpactor") | |
launcher.push_argv("language-server") | |
launcher.push_argv("-n") | |
if DEV_MODE: | |
launcher.push_argv("-vvv") | |
# 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._lsp_spawned) | |
self._supervisor.set_launcher(launcher) | |
self._supervisor.start() | |
def _lsp_spawned(self, supervisor, subprocess): | |
""" | |
This callback is executed when the `rls` 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('php') | |
self._client.start() | |
self.notify('client') | |
def _create_launcher(self): | |
""" | |
Creates a launcher to be used by the PHP service. | |
""" | |
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 `rls` process has crashed. | |
""" | |
context = provider.get_context() | |
self = context.ensure_child_typed(LSPService) | |
self._ensure_started() | |
self.bind_property('client', provider, 'client', GObject.BindingFlags.SYNC_CREATE) | |
class PhplsDiagnosticProvider(Ide.LspDiagnosticProvider, Ide.DiagnosticProvider): | |
def do_load(self): | |
LSPService.bind_client(self) | |
class PhplsCompletionProvider(Ide.LspCompletionProvider, Ide.CompletionProvider): | |
def do_load(self, context): | |
LSPService.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 PhplsRenameProvider(Ide.LspRenameProvider, Ide.RenameProvider): | |
def do_load(self): | |
LSPService.bind_client(self) | |
class PhplsSymbolResolver(Ide.LspSymbolResolver, Ide.SymbolResolver): | |
def do_load(self): | |
LSPService.bind_client(self) | |
class PhplsHighlighter(Ide.LspHighlighter, Ide.Highlighter): | |
def do_load(self): | |
LSPService.bind_client(self) | |
class PhplsFormatter(Ide.LspFormatter, Ide.Formatter): | |
def do_load(self): | |
LSPService.bind_client(self) | |
class PhplsHoverProvider(Ide.LspHoverProvider, Ide.HoverProvider): | |
def do_prepare(self): | |
self.props.category = 'Php' | |
self.props.priority = 200 | |
LSPService.bind_client(self) | |
Just for info… On Ubuntu 18.04, with the default GNOME builder 3.28.1, I get this error :
AttributeError: 'gi.repository.Ide' object has no attribute 'LspClient'
Anyway, thanks for this gist… Really inspiring !
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
These files must be copied in
$HOME/.local/share/gnome-builder/plugins