From e8ab8aa2b83cad79abefe67ed2d4edeb51f33083 Mon Sep 17 00:00:00 2001 From: Michal Niewrzal Date: Wed, 23 Nov 2016 18:38:57 +0100 Subject: [PATCH 1/4] Make processId optional for initialization (#178) --- src/LanguageServer.php | 4 ++-- tests/LanguageServerTest.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/LanguageServer.php b/src/LanguageServer.php index a2c1f52..43f0544 100644 --- a/src/LanguageServer.php +++ b/src/LanguageServer.php @@ -107,12 +107,12 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher /** * The initialize request is sent as the first request from the client to the server. * - * @param int $processId The process Id of the parent process that started the server. * @param ClientCapabilities $capabilities The capabilities provided by the client (editor) * @param string|null $rootPath The rootPath of the workspace. Is null if no folder is open. + * @param int|null $processId The process Id of the parent process that started the server. Is null if the process has not been started by another process. If the parent process is not alive then the server should exit (see exit notification) its process. * @return InitializeResult */ - public function initialize(int $processId, ClientCapabilities $capabilities, string $rootPath = null): InitializeResult + public function initialize(ClientCapabilities $capabilities, string $rootPath = null, int $processId = null): InitializeResult { $this->rootPath = $rootPath; $this->clientCapabilities = $capabilities; diff --git a/tests/LanguageServerTest.php b/tests/LanguageServerTest.php index 272c458..2e0fb4e 100644 --- a/tests/LanguageServerTest.php +++ b/tests/LanguageServerTest.php @@ -68,7 +68,7 @@ class LanguageServerTest extends TestCase }); $server = new LanguageServer($input, $output); $capabilities = new ClientCapabilities; - $server->initialize(getmypid(), $capabilities, realpath(__DIR__ . '/../fixtures')); + $server->initialize($capabilities, realpath(__DIR__ . '/../fixtures'), getmypid()); $promise->wait(); } @@ -116,7 +116,7 @@ class LanguageServerTest extends TestCase $capabilities = new ClientCapabilities; $capabilities->xfilesProvider = true; $capabilities->xcontentProvider = true; - $server->initialize(getmypid(), $capabilities, $rootPath); + $server->initialize($capabilities, $rootPath, getmypid()); $promise->wait(); $this->assertTrue($filesCalled); $this->assertTrue($contentCalled); From ea92b224cdae17d42bdff821a3b534e8b41a71a6 Mon Sep 17 00:00:00 2001 From: Michal Niewrzal Date: Tue, 29 Nov 2016 13:10:02 +0100 Subject: [PATCH 2/4] Symbols throws error for empty php file (#187) Closes #186 --- src/PhpDocument.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpDocument.php b/src/PhpDocument.php index e24b9ac..575a5e6 100644 --- a/src/PhpDocument.php +++ b/src/PhpDocument.php @@ -325,7 +325,7 @@ class PhpDocument */ public function getDefinitions() { - return $this->definitions; + return $this->definitions ?? []; } /** From 48e01670609e4d828fbaeae2f058d79b6054077d Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Tue, 29 Nov 2016 19:32:17 +0100 Subject: [PATCH 3/4] Support to run as TCP server & fork a child process for every connection (#183) --- README.md | 10 ++++++ bin/php-language-server.php | 70 ++++++++++++++++++++++++++++++------- 2 files changed, 68 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index dae264d..de9167b 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,16 @@ Example: php bin/php-language-server.php --tcp=127.0.0.1:12345 +#### `--tcp-server=host:port` (optional) +Causes the server to use a tcp connection for communicating with the language client instead of using STDIN/STDOUT. +The server will listen on the given address for a connection. +If PCNTL is available, will fork a child process for every connection. +If not, will only accept one connection and the connection cannot be reestablished once closed, spawn a new process instead. + +Example: + + php bin/php-language-server.php --tcp-server=127.0.0.1:12345 + #### `--memory-limit=integer` (optional) Sets memory limit for language server. Equivalent to [memory-limit](http://php.net/manual/en/ini.core.php#ini.memory-limit) php.ini directive. diff --git a/bin/php-language-server.php b/bin/php-language-server.php index cad4215..7cb3178 100644 --- a/bin/php-language-server.php +++ b/bin/php-language-server.php @@ -3,7 +3,7 @@ use LanguageServer\{LanguageServer, ProtocolStreamReader, ProtocolStreamWriter}; use Sabre\Event\Loop; -$options = getopt('', ['tcp::', 'memory-limit::']); +$options = getopt('', ['tcp::', 'tcp-server::', 'memory-limit::']); ini_set('memory_limit', $options['memory-limit'] ?? -1); @@ -31,21 +31,67 @@ set_exception_handler(function (\Throwable $e) { @cli_set_process_title('PHP Language Server'); if (!empty($options['tcp'])) { + // Connect to a TCP server $address = $options['tcp']; $socket = stream_socket_client('tcp://' . $address, $errno, $errstr); if ($socket === false) { - fwrite(STDERR, "Could not connect to language client. Error $errno\n"); - fwrite(STDERR, "$errstr\n"); + fwrite(STDERR, "Could not connect to language client. Error $errno\n$errstr"); exit(1); } - $inputStream = $outputStream = $socket; + stream_set_blocking($socket, false); + $ls = new LanguageServer( + new ProtocolStreamReader($socket), + new ProtocolStreamWriter($socket) + ); + Loop\run(); +} else if (!empty($options['tcp-server'])) { + // Run a TCP Server + $address = $options['tcp-server']; + $tcpServer = stream_socket_server('tcp://' . $address, $errno, $errstr); + if ($tcpServer === false) { + fwrite(STDERR, "Could not listen on $address. Error $errno\n$errstr"); + exit(1); + } + fwrite(STDOUT, "Server listening on $address\n"); + if (!extension_loaded('pcntl')) { + fwrite(STDERR, "PCNTL is not available. Only a single connection will be accepted\n"); + } + while ($socket = stream_socket_accept($tcpServer, -1)) { + fwrite(STDOUT, "Connection accepted\n"); + stream_set_blocking($socket, false); + if (extension_loaded('pcntl')) { + // If PCNTL is available, fork a child process for the connection + // An exit notification will only terminate the child process + $pid = pcntl_fork(); + if ($pid === -1) { + fwrite(STDERR, "Could not fork\n"); + exit(1); + } else if ($pid === 0) { + // Child process + $ls = new LanguageServer( + new ProtocolStreamReader($socket), + new ProtocolStreamWriter($socket) + ); + Loop\run(); + // Just for safety + exit(0); + } + } else { + // If PCNTL is not available, we only accept one connection. + // An exit notification will terminate the server + $ls = new LanguageServer( + new ProtocolStreamReader($socket), + new ProtocolStreamWriter($socket) + ); + Loop\run(); + } + } } else { - $inputStream = STDIN; - $outputStream = STDOUT; + // Use STDIO + stream_set_blocking(STDIN, false); + $ls = new LanguageServer( + new ProtocolStreamReader(STDIN), + new ProtocolStreamWriter(STDOUT) + ); + Loop\run(); } - -stream_set_blocking($inputStream, false); - -$server = new LanguageServer(new ProtocolStreamReader($inputStream), new ProtocolStreamWriter($outputStream)); - -Loop\run(); From 5077b1a87a40dbae0934406e413bc1f14fc9498d Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Tue, 29 Nov 2016 21:08:54 +0100 Subject: [PATCH 4/4] Add Dockerfile (#185) * Add Dockerfile * Add .dockerignore * Publish to docker hub on every release --- .dockerignore | 9 +++++++++ .travis.yml | 9 +++++++++ Dockerfile | 27 +++++++++++++++++++++++++++ php.ini | 5 +++++ 4 files changed, 50 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 php.ini diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a839c69 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +.DS_Store +.vscode/ +.idea/ +.git/ +tests/ +fixtures/ +coverage/ +coverage.xml +images/ diff --git a/.travis.yml b/.travis.yml index caefbec..0199d52 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,9 @@ language: php php: - '7.0' +services: + - docker + cache: directories: - vendor @@ -16,3 +19,9 @@ script: after_success: - bash <(curl -s https://codecov.io/bash) + - | + if [[ $TRAVIS_TAG == v* ]]; then + docker build -t felixfbecker/php-language-server:${TRAVIS_TAG:1} . + docker login -e="$DOCKER_EMAIL" -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" + docker push felixfbecker/php-language-server:${TRAVIS_TAG:1} + fi diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f4291db --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ + +# Running this container will start a language server that listens for TCP connections on port 2088 +# Every connection will be run in a forked child process + +# Please note that before building the image, you have to install dependencies with `composer install` + +FROM php:7-cli +MAINTAINER Felix Becker + +RUN apt-get update \ + # Needed for CodeSniffer + && apt-get install -y libxml2 libxml2-dev \ + && rm -rf /var/lib/apt/lists/* + +RUN docker-php-ext-configure pcntl --enable-pcntl +RUN docker-php-ext-install pcntl +COPY ./php.ini /usr/local/etc/php/conf.d/ + +COPY ./ /srv/phpls + +WORKDIR /srv/phpls + +EXPOSE 2088 + +CMD ["--tcp-server=0:2088"] + +ENTRYPOINT ["php", "bin/php-language-server.php"] diff --git a/php.ini b/php.ini new file mode 100644 index 0000000..334e2c5 --- /dev/null +++ b/php.ini @@ -0,0 +1,5 @@ + +# php.ini for Docker + +error_reporting = E_ALL +display_errors = stderr