1
0
Fork 0

Merge branch 'master' into completion

pull/165/head
Felix Becker 2016-11-30 01:01:56 +01:00 committed by GitHub
commit 471d4703c5
9 changed files with 123 additions and 17 deletions

9
.dockerignore Normal file
View File

@ -0,0 +1,9 @@
.DS_Store
.vscode/
.idea/
.git/
tests/
fixtures/
coverage/
coverage.xml
images/

View File

@ -3,6 +3,9 @@ language: php
php: php:
- '7.0' - '7.0'
services:
- docker
cache: cache:
directories: directories:
- vendor - vendor
@ -16,3 +19,9 @@ script:
after_success: after_success:
- bash <(curl -s https://codecov.io/bash) - 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

27
Dockerfile Normal file
View File

@ -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 <felix.b@outlook.com>
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"]

View File

@ -148,6 +148,16 @@ Example:
php bin/php-language-server.php --tcp=127.0.0.1:12345 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) #### `--memory-limit=integer` (optional)
Sets memory limit for language server. Sets memory limit for language server.
Equivalent to [memory-limit](http://php.net/manual/en/ini.core.php#ini.memory-limit) php.ini directive. Equivalent to [memory-limit](http://php.net/manual/en/ini.core.php#ini.memory-limit) php.ini directive.

View File

@ -3,7 +3,7 @@
use LanguageServer\{LanguageServer, ProtocolStreamReader, ProtocolStreamWriter}; use LanguageServer\{LanguageServer, ProtocolStreamReader, ProtocolStreamWriter};
use Sabre\Event\Loop; use Sabre\Event\Loop;
$options = getopt('', ['tcp::', 'memory-limit::']); $options = getopt('', ['tcp::', 'tcp-server::', 'memory-limit::']);
ini_set('memory_limit', $options['memory-limit'] ?? -1); 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'); @cli_set_process_title('PHP Language Server');
if (!empty($options['tcp'])) { if (!empty($options['tcp'])) {
// Connect to a TCP server
$address = $options['tcp']; $address = $options['tcp'];
$socket = stream_socket_client('tcp://' . $address, $errno, $errstr); $socket = stream_socket_client('tcp://' . $address, $errno, $errstr);
if ($socket === false) { if ($socket === false) {
fwrite(STDERR, "Could not connect to language client. Error $errno\n"); fwrite(STDERR, "Could not connect to language client. Error $errno\n$errstr");
fwrite(STDERR, "$errstr\n");
exit(1); 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 { } else {
$inputStream = STDIN; // Use STDIO
$outputStream = STDOUT; 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();

5
php.ini Normal file
View File

@ -0,0 +1,5 @@
# php.ini for Docker
error_reporting = E_ALL
display_errors = stderr

View File

@ -108,12 +108,12 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
/** /**
* The initialize request is sent as the first request from the client to the server. * 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 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 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 * @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->rootPath = $rootPath;
$this->clientCapabilities = $capabilities; $this->clientCapabilities = $capabilities;

View File

@ -344,7 +344,7 @@ class PhpDocument
*/ */
public function getDefinitions() public function getDefinitions()
{ {
return $this->definitions; return $this->definitions ?? [];
} }
/** /**

View File

@ -71,7 +71,7 @@ class LanguageServerTest extends TestCase
}); });
$server = new LanguageServer($input, $output); $server = new LanguageServer($input, $output);
$capabilities = new ClientCapabilities; $capabilities = new ClientCapabilities;
$server->initialize(getmypid(), $capabilities, realpath(__DIR__ . '/../fixtures')); $server->initialize($capabilities, realpath(__DIR__ . '/../fixtures'), getmypid());
$promise->wait(); $promise->wait();
} }
@ -119,7 +119,7 @@ class LanguageServerTest extends TestCase
$capabilities = new ClientCapabilities; $capabilities = new ClientCapabilities;
$capabilities->xfilesProvider = true; $capabilities->xfilesProvider = true;
$capabilities->xcontentProvider = true; $capabilities->xcontentProvider = true;
$server->initialize(getmypid(), $capabilities, $rootPath); $server->initialize($capabilities, $rootPath, getmypid());
$promise->wait(); $promise->wait();
$this->assertTrue($filesCalled); $this->assertTrue($filesCalled);
$this->assertTrue($contentCalled); $this->assertTrue($contentCalled);