-
Notifications
You must be signed in to change notification settings - Fork 185
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
Changes from 28 commits
6d97d52
1efb09b
d597eee
a34426f
1865ce8
6b28cb6
ca0fcce
1a006de
e6b48c7
4d6774f
28d6cc6
05fb3cf
0b15abc
6688e9e
1e66859
21b55ef
6c098bf
9036ae8
5f8e37b
b99fb94
d1fd4bc
fa9e540
38c8cac
12931ae
365e128
7633bb5
dbfb7b3
3aad065
1fb1722
b554041
ffea1d0
c7c7e45
9f6cf32
a9c8f9c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
A |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
B |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Peeakboo! |
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 | ||
] | ||
))); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
{ | ||
|
@@ -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, '/'); | ||
|
@@ -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); | ||
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you cannot pass There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, you're right. I just went by the |
||
{ | ||
$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); | ||
} | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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