Skip to content
This repository has been archived by the owner on Sep 6, 2021. It is now read-only.

Php Tooling Extensions Using LSP Framework #14671

Merged
merged 21 commits into from
Apr 3, 2019
Merged

Php Tooling Extensions Using LSP Framework #14671

merged 21 commits into from
Apr 3, 2019

Conversation

niteskum
Copy link
Collaborator

@niteskum niteskum commented Mar 20, 2019

Language Server Client Extension using the https://github.com/felixfbecker/php-language-server

Requirements:

Installation: npm install

Set the following preferences in

brackets.json

to configure the extension:

"php": {
    "enablePhpTooling": true,             // Enable/Disable PHP Tooling
    "executablePath": "php",              //Path of the php 7 executable
    "memoryLimit": "4095M",               //Memory limit for the Language Server Process
    "validateOnType": "false"             //Get validation results on Type or on Save
}

@niteskum
Copy link
Collaborator Author

niteskum commented Mar 20, 2019

This PR is Using LSP Framework which are being reviewed in separate PR #14606 .

@shubhsnov @swmitra @subhashjha333 please review

}

var serverOptions = function (){
return new Promise(function (resolve, reject) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: The indentation seems off here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

indentation is correct

var LanguageClient = require(global.LanguageClientInfo.languageClientPath).LanguageClient,
net = require("net"),
cp = require("child_process"),
execa = require("execa"),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are you introducing a new node_module? (execa)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes execa and semver

confParams["validate_executablePath"] ||
(process.platform === 'win32' ? 'php.exe' : 'php');

memoryLimit = confParams["memoryLimit"] || 'default';
Copy link
Collaborator

Choose a reason for hiding this comment

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

Keep the default preferences in Brackets itself, and send from there, instead of using a 'default' string.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

});
// Listen on random port
server.listen(0, '127.0.0.1', function () {
var pathToPHP = __dirname + "/vendor/bin/php-language-server.php";
Copy link
Collaborator

Choose a reason for hiding this comment

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

Make sure this path works for Win and Mac both.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

var clientFilePath = ExtensionUtils.getModulePath(module, "client.js"),
clientName = "PhpClient",
_client = null,
phpConfig = {},
Copy link
Collaborator

Choose a reason for hiding this comment

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

See my comment above. Have defaults set here, which are overridden when the user sets the preferences.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed


PreferencesManager.definePreference("php", "object", {
enablePhpTooling: true,
showDiagnosisOnType: false,
Copy link
Collaborator

Choose a reason for hiding this comment

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

showDiagnosisOnType => validateOn{Type, Save} . since we are using validate elsewhere as well.

Copy link
Collaborator

Choose a reason for hiding this comment

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

?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

var newPhpConfig = PreferencesManager.get("php");

if((newPhpConfig["executablePath"] !== phpConfig["executablePath"])
|| (newPhpConfig["enablePhpTooling"] !== phpConfig["enablePhpTooling"])) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

What about validation_executablePath?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

removed validation_executablePath , as it is not required.

}

function startPhpServer() {
if(_client && phpConfig["enablePhpTooling"]) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Two suggestions:

  • Decouple the validation logic from this function. You can do it on App Ready and then prompt the UI on opening a PHP file the first time. Save that to state.

  • I know it seems like a nice optimization to start the server on first open of PHP file, but it doesn't seem like valid enforcement if the extension itself enforces it. Since this is a reference implementation, we'd want to provide the developer with a way where they can defer the activation of the server if they want from the brackets core itself. This is a nice optimization which other LSP extension can leverage as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think we should always validate Server config before starting.
otherwise if server start fails for any reason (e.g user set wrong executable Path), we wont be able to notify user about failure.

Copy link
Collaborator

@shubhsnov shubhsnov Mar 21, 2019

Choose a reason for hiding this comment

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

It's up to you. I find the current way a bit clumsy. Also, I don't think you clearly understood what I meant.

@shubhsnov
Copy link
Collaborator

Another thing to note: To test the validation logic against PHP5 and PHP7. Both should give different results, based on syntax.

@shubhsnov
Copy link
Collaborator

@niteskum Please add support for PHP static code hinting as part of this on priority, for completeness.

@shubhsnov
Copy link
Collaborator

shubhsnov commented Mar 21, 2019

@niteskum Some other required features for reference impl:
Currently, there are two ways to handle the project root change, either restart or send folder change event. Tap into the server capability which is received on client start to figure out whether the server can handle the folder change event, and then decide dynamically which way to go.

Same for completion resolve.

@niteskum
Copy link
Collaborator Author

@shubhsnov I can see all static code hints and Parameter hints comes from LSP Server except below few variables.
$GLOBALS
$_SERVER
$_GET
$_POST
$_FILES
$_REQUEST
$_SESSION
$_ENV
$_COOKIE
$php_errormsg
$HTTP_RAW_POST_DATA
$http_response_header
$argc
$argv
$this

are we sure we have to include this in code hints.??, There might be some reason lsp server is not giving these variables as hints.

if ((newPhpConfig["executablePath"] !== phpConfig["executablePath"])
|| (newPhpConfig["enablePhpTooling"] !== phpConfig["enablePhpTooling"])) {
phpConfig = newPhpConfig;
startPhpServer();
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we need to differentiate between starting the server and restarting the server. You seem to be just starting the server again and again. I don't see any stop anywhere. Somebody can always change a valid path to another valid path.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

var handleProjectOpen = function (event, directory) {
_client.stop()
.done(function () {
setTimeout(function () {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This timeout is not required. It was for test purpose only,

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It does not work without timeout function, Can you check from core side.
Better provide a restart function so Extension Developer need not to worry about these things


PreferencesManager.definePreference("php", "object", {
enablePhpTooling: true,
showDiagnosisOnType: false,
Copy link
Collaborator

Choose a reason for hiding this comment

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

?

@shubhsnov
Copy link
Collaborator

@shubhsnov I can see all static code hints and Parameter hints comes from LSP Server except below few variables.
$GLOBALS
$_SERVER
$_GET
$_POST
$_FILES
$_REQUEST
$_SESSION
$_ENV
$_COOKIE
$php_errormsg
$HTTP_RAW_POST_DATA
$http_response_header
$argc
$argv
$this

are we sure we have to include this in code hints.??, There might be some reason lsp server is not giving these variables as hints.

Get this resolved. My take is that most of these appear to quite common globals. It would be good to have.

@niteskum
Copy link
Collaborator Author

@shubhsnov Regarding FolderChange Event my understanding is "workspace/didChangeWorkspaceFolders" event is for workspace folder structure change like (folder added or removed from workspace ), not for the workspace project root folder change.

correct me if my understanding is wrong.

@shubhsnov
Copy link
Collaborator

@niteskum

@shubhsnov Regarding FolderChange Event my understanding is "workspace/didChangeWorkspaceFolders" event is for workspace folder structure change like (folder added or removed from workspace ), not for the workspace project root folder change.

correct me if my understanding is wrong.

Quoting from the actual specification here :
https://microsoft.github.io/language-server-protocol/specification#workspace_workspaceFolders

Many tools support more than one root folder per workspace. Examples for this are VS Code’s multi-root support, Atom’s project folder support or Sublime’s project support. If a client workspace consists of multiple roots then a server typically needs to know about this. The protocol up to now assumes one root folder which is announced to the server by the rootUri property of the InitializeParams. If the client supports workspace folders and announces them via the corresponding workspaceFolders client capability, the InitializeParams contain an additional property workspaceFolders with the configured workspace folders when the server starts.

In Brackets we only have one project root, so workspace and project are synonymous. This way we can piggyback on the client capability "workspaceFolders", to change project root. I've done so in the default capabilities. A change in the project root can thus be translated as an addition of a folder and removal of a folder from a workspace which only has one folder.

So quoting again:

The workspace/didChangeWorkspaceFolders notification is sent from the client to the server to inform the server about workspace folder configuration changes.

I believe the reason we have both "rootUri" and "workspaceFolders" in the specification is because of backward compatibility. Earlier the servers didn't support multiple workspace folders, and separate clients had to be created per workspace folder manually in the extension.

return true;
};

CodeHintsProvider.prototype.getHints = function (implicitChar) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think we need this file. You can create override the getHInts function of the DefaultProvider object since that is the only place is where you need to change anything. See my comments below.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

see my comment below

};

function registerToolingProviders() {
var chProvider = new CodeHintsProvider(_client),
Copy link
Collaborator

Choose a reason for hiding this comment

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

do this instead:

var chProvider = new DefaultProviders.CodeHintsProvider(_client),
chProvider.getHints = yourOwnGetHintsFunction;

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't see this gives us any benefits, I understand that I have changes only in getHints function but still I have to rewrite all Util functions which are defined in default code Hints provider and I have to access "this" object, If i use "this" variable in normal functions that will look weird. this seems to be an hack code not recommended coding practise.
If you are going to externalise all util functions, then I can reuse your util functions from default provider.

@niteskum
Copy link
Collaborator Author

@niteskum

@shubhsnov Regarding FolderChange Event my understanding is "workspace/didChangeWorkspaceFolders" event is for workspace folder structure change like (folder added or removed from workspace ), not for the workspace project root folder change.
correct me if my understanding is wrong.

Quoting from the actual specification here :
https://microsoft.github.io/language-server-protocol/specification#workspace_workspaceFolders

Many tools support more than one root folder per workspace. Examples for this are VS Code’s multi-root support, Atom’s project folder support or Sublime’s project support. If a client workspace consists of multiple roots then a server typically needs to know about this. The protocol up to now assumes one root folder which is announced to the server by the rootUri property of the InitializeParams. If the client supports workspace folders and announces them via the corresponding workspaceFolders client capability, the InitializeParams contain an additional property workspaceFolders with the configured workspace folders when the server starts.

In Brackets we only have one project root, so workspace and project are synonymous. This way we can piggyback on the client capability "workspaceFolders", to change project root. I've done so in the default capabilities. A change in the project root can thus be translated as an addition of a folder and removal of a folder from a workspace which only has one folder.

So quoting again:

The workspace/didChangeWorkspaceFolders notification is sent from the client to the server to inform the server about workspace folder configuration changes.

I believe the reason we have both "rootUri" and "workspaceFolders" in the specification is because of backward compatibility. Earlier the servers didn't support multiple workspace folders, and separate clients had to be created per workspace folder manually in the extension.

Php server does not support WorkspaceFolders change Notification. so this can not be tested with Php client. so I am not using this in PHP client. we should not ship any feature without having tested.

instead I have used servercapabilities.definitionProvider in PHP client this flag is true if Language Server support "go to definition". PHP Server support so i have used this PHP client.

@shubhsnov
Copy link
Collaborator

shubhsnov commented Mar 22, 2019

Php server does not support WorkspaceFolders change Notification. so this can not be tested with Php client. so I am not using this in PHP client. we should not ship any feature without having tested.

instead I have used servercapabilities.definitionProvider in PHP client this flag is true if Language Server support "go to definition". PHP Server support so i have used this PHP client.

@niteskum Don't worry, I have added tests for the same already, by creating a sample LSP server, which supports the event. What you need to do here is just check the existence of the server capability(ServerCapabilities/workspace/workspaceFolders). If it's available then use the "didChangeWorkspaceFolder" event, otherwise do an explicit restart.

// There is a bug in Php Language Server, Php Language Server does not provide superGlobals
// Variables as completion. so these variables are being explicity put in response objects
// below code should be removed if php server fix this bug.
for(var key in phpSuperGlobalVariables) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think we should add the static hints here always, otherwise we'll show hints even when they are not required. It should only come when implicitChar is '$', or otherwise filtered by the current token's prefix in case the token starts with '$'.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

it will shown only if prefix is '$'. in below code "filterWithQueryAndMatcher" will filter it.

@niteskum
Copy link
Collaborator Author

Php server does not support WorkspaceFolders change Notification. so this can not be tested with Php client. so I am not using this in PHP client. we should not ship any feature without having tested.
instead I have used servercapabilities.definitionProvider in PHP client this flag is true if Language Server support "go to definition". PHP Server support so i have used this PHP client.

@niteskum Don't worry, I have added tests for the same already, by creating a sample LSP server, which supports the event. What you need to do here is just check the existence of the server capability(ServerCapabilities/workspace/workspaceFolders). If it's available then use the "didChangeWorkspaceFolder" event, otherwise do an explicit restart.

If we are going to use this event , then in initialize function we should also send workspaceFolders which is root_path, this require a change in core side in initialize function.

@niteskum
Copy link
Collaborator Author

Another thing to note: To test the validation logic against PHP5 and PHP7. Both should give different results, based on syntax.

For LSP server to work we need PHP 7, with PHP 5 LSP server will not work

@niteskum
Copy link
Collaborator Author

Php server does not support WorkspaceFolders change Notification. so this can not be tested with Php client. so I am not using this in PHP client. we should not ship any feature without having tested.
instead I have used servercapabilities.definitionProvider in PHP client this flag is true if Language Server support "go to definition". PHP Server support so i have used this PHP client.

@niteskum Don't worry, I have added tests for the same already, by creating a sample LSP server, which supports the event. What you need to do here is just check the existence of the server capability(ServerCapabilities/workspace/workspaceFolders). If it's available then use the "didChangeWorkspaceFolder" event, otherwise do an explicit restart.

If we are going to use this event , then in initialize function we should also send workspaceFolders which is root_path, this require a change in core side in initialize function.

fixed

"description": "Contains an array of all the arguments passed to the script when running from the command line.",
"type": "array"
},
"$this": {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add the "parent" and "self" keyword as well.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also __destruct.


function registerToolingProviders() {
var chProvider = new CodeHintsProvider(_client),
phProvider = new DefaultProviders.ParameterHintsProvider(_client),
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We need servercapabilities in client

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sure.

return matchResults;
}

CodeHintsProvider.prototype.hasHints = function (editor, implicitChar) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'll add the ability to add static hints in the Default Provider itself.


function registerToolingProviders() {
var chProvider = new CodeHintsProvider(_client),
phProvider = new DefaultProviders.ParameterHintsProvider(_client),
Copy link
Collaborator

Choose a reason for hiding this comment

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

@niteskum Please override the hasParameterHints function to handle bug. Use TokenUtils to check if the previous token is of type "variable", and return true only then.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@shubhsnov what is the bug?

}

function restart(project) {
var result = $.Deferred();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Try using _client.restart directly.

@shubhsnov
Copy link
Collaborator

@niteskum There is a bug with the php server, where it provides the parameter hints just outside the function after the closing parentheses ')'. Can you take a look at this and see if we can do a targeted fix from Brackets side.

@shubhsnov
Copy link
Collaborator

shubhsnov commented Mar 28, 2019

LGTM 👍 💯

@swmitra
Copy link
Collaborator

swmitra commented Apr 2, 2019

Please resolve the conflicts @niteskum.

@niteskum
Copy link
Collaborator Author

niteskum commented Apr 2, 2019

Please resolve the conflicts @niteskum.

resolved conflicts in strings.js

@shubhsnov
Copy link
Collaborator

@gavankar I think the Travis CI env configuration should also be updated, for PHP 7 and composer 1.8.4. The CI is failing due to this and will continue to fail otherwise.

gavankar and others added 2 commits April 3, 2019 13:34
php-Tooling npm package requires felixfbecker/language-server php
package, which can only be installed on php7. this should fix the break
in brackets travis build
Switch to php7 in travis before dependency installation in travis
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants