Skip to content

Global symbol search #31

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 34 commits into from
Sep 30, 2016
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
6d97d52
Implemented workspace symbol search
sunverwerth Sep 17, 2016
1efb09b
Fixed missing TextEdit using declaration
sunverwerth Sep 18, 2016
d597eee
Fixed generating uri when parsing next file.
sunverwerth Sep 18, 2016
a34426f
Cleaned up code. Fixed tests
sunverwerth Sep 18, 2016
1865ce8
Fixed PHPDoc for LanguageServer::initialize()
sunverwerth Sep 19, 2016
6b28cb6
Moved utility functions to utils.php
sunverwerth Sep 19, 2016
ca0fcce
Added tests for pathToUri and findFilesRecursive
sunverwerth Sep 19, 2016
1a006de
Added command line argument for socket communication
sunverwerth Sep 22, 2016
e6b48c7
Fixed local variable detection and containerName generation in Symbol…
sunverwerth Sep 22, 2016
4d6774f
Fixed formatting in ProtocolStreamReader
sunverwerth Sep 22, 2016
28d6cc6
Store text content in PHPDocument, removed stmts, regenerate on demand
sunverwerth Sep 22, 2016
05fb3cf
Fixed local variable detection and containerName generation in Symbol…
sunverwerth Sep 22, 2016
0b15abc
Added Tests for Project and Workspace
sunverwerth Sep 28, 2016
6688e9e
Added test for didChange event
sunverwerth Sep 28, 2016
1e66859
Modified lexer error handling
sunverwerth Sep 28, 2016
21b55ef
Removed file that shouldn't have been committed.
sunverwerth Sep 28, 2016
6c098bf
Updated sabre/event dependency to 4.0.0
sunverwerth Sep 28, 2016
9036ae8
Updated readme.md to show tcp option
sunverwerth Sep 28, 2016
5f8e37b
make input stream non-blocking
sunverwerth Sep 28, 2016
b99fb94
Correct code style
felixfbecker Sep 29, 2016
d1fd4bc
Use triple equals
felixfbecker Sep 29, 2016
fa9e540
Revert change in SymbolFinder
felixfbecker Sep 29, 2016
38c8cac
Optimize processFile() a bit
felixfbecker Sep 29, 2016
12931ae
Use MessageType enum instead of number literal
felixfbecker Sep 29, 2016
365e128
Merge branch 'master' into workspace_symbols
felixfbecker Sep 29, 2016
7633bb5
Add missing space
felixfbecker Sep 29, 2016
dbfb7b3
Fixed ProtocolStreamWriter for nonblocking connection.
sunverwerth Sep 29, 2016
3aad065
Merge branch 'workspace_symbols' of https://github.com/MadHed/php-lan…
sunverwerth Sep 29, 2016
1fb1722
Suppress fwrite() notice when not all bytes could be written.
sunverwerth Sep 29, 2016
b554041
Fix another code style issue
felixfbecker Sep 29, 2016
ffea1d0
Throw Exceotion instead of Error
sunverwerth Sep 29, 2016
c7c7e45
Added ProtocolStreamWriter test
sunverwerth Sep 29, 2016
9f6cf32
Correct workspace/symbol documentation
felixfbecker Sep 30, 2016
a9c8f9c
Improve exception in ProtocolStreamWriter::write()
felixfbecker Sep 30, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,15 @@ to install dependencies.
Run the tests with

vendor/bin/phpunit --bootstrap vendor/autoload.php tests

## Command line arguments

--tcp host:port

Causes the server to use a tcp connection for communicating with the language client instead of using STDIN/STDOUT.
The server will try to connect to the specified address.

Example:

php bin/php-language-server.php --tcp 127.0.0.1:12345

21 changes: 20 additions & 1 deletion bin/php-language-server.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

ini_set('memory_limit', '-1');

use LanguageServer\{LanguageServer, ProtocolStreamReader, ProtocolStreamWriter};
use Sabre\Event\Loop;

Expand All @@ -10,6 +12,23 @@
}
}

$server = new LanguageServer(new ProtocolStreamReader(STDIN), new ProtocolStreamWriter(STDOUT));
if (count($argv) >= 3 && $argv[1] === '--tcp') {
$address = $argv[2];
$socket = stream_socket_client('tcp://' . $address, $errno, $errstr);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means the IDE has to open a socket on its side, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. extension.js opens a listen socket at 127.0.0.1:random_port and immediately stops listening when the language server connects.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get the second part of your sentence, why does it have to stop listening? I though it would then solely communicate through the socket?

Copy link
Contributor Author

@sunverwerth sunverwerth Sep 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only the listen socket is closed, the established connection keeps going. It just won't allow any additional connections after the first one.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah okay so the server should only accept a single connection

if ($socket === false) {
fwrite(STDERR, "Could not connect to language client. Error $errno\n");
fwrite(STDERR, "$errstr\n");
exit(1);
}
$inputStream = $outputStream = $socket;
}
else {
$inputStream = STDIN;
$outputStream = STDOUT;
}

stream_set_blocking($inputStream, false);

$server = new LanguageServer(new ProtocolStreamReader($inputStream), new ProtocolStreamWriter($outputStream));

Loop\run();
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,16 @@
"php": ">=7.0",
"nikic/php-parser": "^3.0.0beta1",
"phpdocumentor/reflection-docblock": "^3.0",
"sabre/event": "^3.0",
"sabre/event": "^4.0",
"felixfbecker/advanced-json-rpc": "^1.2"
},
"minimum-stability": "dev",
"prefer-stable": true,
"autoload": {
"psr-4": {
"LanguageServer\\": "src/"
}
},
"files" : ["src/utils.php"]
},
"autoload-dev": {
"psr-4": {
Expand Down
1 change: 1 addition & 0 deletions fixtures/recursive/a.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A
1 change: 1 addition & 0 deletions fixtures/recursive/search/b.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
B
1 change: 1 addition & 0 deletions fixtures/recursive/search/here/c.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Peeakboo!
60 changes: 60 additions & 0 deletions src/Client/Window.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php
declare(strict_types = 1);

namespace LanguageServer\Client;

use AdvancedJsonRpc\Notification as NotificationBody;
use PhpParser\{Error, Comment, Node, ParserFactory, NodeTraverser, Lexer};
use PhpParser\NodeVisitor\NameResolver;
use LanguageServer\ProtocolWriter;
use LanguageServer\Protocol\{TextDocumentItem, TextDocumentIdentifier, VersionedTextDocumentIdentifier, Message};

/**
* Provides method handlers for all window/* methods
*/
class Window
{
/**
* @var ProtocolWriter
*/
private $protocolWriter;

public function __construct(ProtocolWriter $protocolWriter)
{
$this->protocolWriter = $protocolWriter;
}

/**
* The show message notification is sent from a server to a client to ask the client to display a particular message in the user interface.
*
* @param int $type
* @param string $message
*/
public function showMessage(int $type, string $message)
{
$this->protocolWriter->write(new Message(new NotificationBody(
'window/showMessage',
(object)[
'type' => $type,
'message' => $message
]
)));
}

/**
* The log message notification is sent from the server to the client to ask the client to log a particular message.
*
* @param int $type
* @param string $message
*/
public function logMessage(int $type, string $message)
{
$this->protocolWriter->write(new Message(new NotificationBody(
'window/logMessage',
(object)[
'type' => $type,
'message' => $message
]
)));
}
}
9 changes: 9 additions & 0 deletions src/LanguageClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
namespace LanguageServer;

use LanguageServer\Client\TextDocument;
use LanguageServer\Client\Window;

class LanguageClient
{
Expand All @@ -14,11 +15,19 @@ class LanguageClient
*/
public $textDocument;

/**
* Handles window/* methods
*
* @var Client\Window
*/
public $window;

private $protocolWriter;

public function __construct(ProtocolWriter $writer)
{
$this->protocolWriter = $writer;
$this->textDocument = new TextDocument($writer);
$this->window = new Window($writer);
}
}
73 changes: 67 additions & 6 deletions src/LanguageServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@
namespace LanguageServer;

use LanguageServer\Server\TextDocument;
use LanguageServer\Protocol\{ServerCapabilities, ClientCapabilities, TextDocumentSyncKind, Message};
use LanguageServer\Protocol\InitializeResult;
use LanguageServer\Protocol\{
ServerCapabilities,
ClientCapabilities,
TextDocumentSyncKind,
Message,
MessageType,
InitializeResult
};
use AdvancedJsonRpc\{Dispatcher, ResponseError, Response as ResponseBody, Request as RequestBody};
use Sabre\Event\Loop;

class LanguageServer extends \AdvancedJsonRpc\Dispatcher
{
Expand All @@ -16,16 +23,24 @@ class LanguageServer extends \AdvancedJsonRpc\Dispatcher
*/
public $textDocument;

/**
* Handles workspace/* method calls
*
* @var Server\Workspace
*/
public $workspace;

public $telemetry;
public $window;
public $workspace;
public $completionItem;
public $codeLens;

private $protocolReader;
private $protocolWriter;
private $client;

private $project;

public function __construct(ProtocolReader $reader, ProtocolWriter $writer)
{
parent::__construct($this, '/');
Expand Down Expand Up @@ -56,24 +71,35 @@ public function __construct(ProtocolReader $reader, ProtocolWriter $writer)
});
$this->protocolWriter = $writer;
$this->client = new LanguageClient($writer);
$this->textDocument = new Server\TextDocument($this->client);

$this->project = new Project($this->client);

$this->textDocument = new Server\TextDocument($this->project, $this->client);
$this->workspace = new Server\Workspace($this->project, $this->client);
}

/**
* The initialize request is sent as the first request from the client to the server.
*
* @param string $rootPath The rootPath of the workspace. Is null if no folder is open.
* @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.
* @return InitializeResult
*/
public function initialize(string $rootPath, int $processId, ClientCapabilities $capabilities): InitializeResult
public function initialize(int $processId, ClientCapabilities $capabilities, string $rootPath = null): InitializeResult
{
// start building project index
if ($rootPath) {
$this->indexProject($rootPath);
}

$serverCapabilities = new ServerCapabilities();
// Ask the client to return always full documents (because we need to rebuild the AST from scratch)
$serverCapabilities->textDocumentSync = TextDocumentSyncKind::FULL;
// Support "Find all symbols"
$serverCapabilities->documentSymbolProvider = true;
// Support "Find all symbols in workspace"
$serverCapabilities->workspaceSymbolProvider = true;
// Support "Format Code"
$serverCapabilities->documentFormattingProvider = true;
return new InitializeResult($serverCapabilities);
Expand All @@ -100,4 +126,39 @@ public function exit()
{
exit(0);
}

/**
* Parses workspace files, one at a time.
*
* @param string $rootPath The rootPath of the workspace.
* @return void
*/
private function indexProject(string $rootPath)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you cannot pass null if you type hint it as string. It needs to be string $rootPath = null then.

Copy link
Contributor Author

@sunverwerth sunverwerth Sep 18, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we check this at the calling site? Indexing a null path doesn't make sense, after all.
I would then adjust the param

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you're right. I just went by the @param desc

{
$fileList = findFilesRecursive($rootPath, '/^.+\.php$/i');
$numTotalFiles = count($fileList);

$startTime = microtime(true);
$fileNum = 0;

$processFile = function() use (&$fileList, &$fileNum, &$processFile, $rootPath, $numTotalFiles, $startTime) {
if ($fileNum < $numTotalFiles) {
$file = $fileList[$fileNum];
$uri = pathToUri($file);
$fileNum++;
$shortName = substr($file, strlen($rootPath) + 1);
$this->client->window->logMessage(MessageType::INFO, "Parsing file $fileNum/$numTotalFiles: $shortName.");

$this->project->getDocument($uri)->updateContent(file_get_contents($file));

Loop\setTimeout($processFile, 0);
} else {
$duration = (int)(microtime(true) - $startTime);
$mem = (int)(memory_get_usage(true) / (1024 * 1024));
$this->client->window->logMessage(MessageType::INFO, "All PHP files parsed in $duration seconds. $mem MiB allocated.");
}
};

Loop\setTimeout($processFile, 0);
}
}
Loading