Skip to content

Instantly share code, notes, and snippets.

@ilhooq
Last active September 1, 2021 21:45
Show Gist options
  • Save ilhooq/7d179dcde9b4f2dfcda8709ceea4d879 to your computer and use it in GitHub Desktop.
Save ilhooq/7d179dcde9b4f2dfcda8709ceea4d879 to your computer and use it in GitHub Desktop.
A gnome-builder plugin to provide PHP Language Server integration with Phpactor
[Plugin]
Authors=Sylvain PHILIP <contact@sphilip.com>
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
#!/usr/bin/env python
# Copyright 2016 Christian Hergert <chergert@redhat.com>
# Copyright 2021 PHILIP Sylvain <contact@sphilip.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 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)
@ilhooq
Copy link
Author

ilhooq commented Feb 13, 2021

These files must be copied in $HOME/.local/share/gnome-builder/plugins

@charlycoste
Copy link

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