Closed ArkadiuszMichalski closed 1 month ago
Thank you very much for taking the time to document how a node based server can be used. I will, with your permission, work it into the tips and tricks section.
With Rename and Ask for help
, which is basically lsp actions, there are variations of the returned structures
which are not yet fully implemented. But having servers that use them makes it easier to implement them.
I enjoy programming my plugins, but unfortunately the time I can spend on it is very limited at the moment. I hope that I can work on it for 4-5 hours a day during my vacation then it should be a whole step closer to be a fully working lsp client.
Thank you very much for taking the time to document how a node based server can be used. I will, with your permission, work it into the tips and tricks section.
No problem, I prefer to discuss here rather than on the forum, because it's always closer to the plugin. It's easier to search/read such discussions and tips later.
I enjoy programming my plugins, but unfortunately the time I can spend on it is very limited at the moment. I hope that I can work on it for 4-5 hours a day during my vacation then it should be a whole step closer to be a fully working lsp client.
There is no rush, it's not easy either, from what I see each LSP server has different coverage (and other differences, like configuration) so in the future it will probably be necessary to fine-tune everything a bit for specific ones (at least the most popular ones that the Notepad++ community would prefer).
If I ask a lot of questions here or open a new issue, there is no need for a quick response, I just write down what I can observe so that I don't forget about it later. When you have a moment, you can come back to the topic.
At the moment I have one important question, can you tell me if the LSP protocol supports any filtration for the folders/files it operates on? What I mean is that if we have a Project
folder, can we somehow attach a folder from another location to it, like SomeLibrary
? And the opposite situation, can we exclude some other folder from the Project
folder, e.g. modules
? In short:
Somelibrary << include this folder
Project << this is root of project
modules << exclude this folder
...
If the LSP protocol itself does not provide this, then we will probably need to analyze each server separately and check its options.
I prefer to discuss here
I also think it makes more sense to discuss plugin related stuff here.
in the future it will probably be necessary to fine-tune
Yes, this is necessary because it is possible to have custom lsp messages per server, which I currently ignore and this will probably not be available for the first officially released version. First the basic functionality has to work reliably.
can you tell me if the LSP protocol supports any filtration for the folders/files it operates on?
Yes, Workspace folders.
can we exclude some other folder
but no, if you mean that such a folder is a subdirectory of the project root directory.
Currently, the content of the FoldersAsWorkspaces(FAW) dialog is used to inform the servers about the directories in use. When the buffer is activated, I check whether the content has changed and send the notification Workspace/didChangeWorkspaceFolders accordingly. This means that only FAWs root nodes are taken into account.
The problem I have with FAW is that it is a performance bottleneck with language servers that compile sources, as it monitors and updates the directories. Also, it would be really helpful if I could reuse a dialog like this to show a diagnostic counter next to the folders, so you can see immediately that there are problems without having to open the diagnostic dialog. So I'm thinking about giving the lsp client its own "Projects/FAW" dialog.
@Ekopalypse But how all roots in FAW are treated, as the same project or separate? I mean will the suggestions, searches etc. work separately for each root or together?
In the case of tsserver we can control on which files it operates by using the config file. I tried this and indeed the files
property works:
{
"[files]": [
"file1.js",
"file2.js",
]
}
Unfortunately, filtering by using include
/exclude
doesn't work for me. It's possible that I'm doing something wrong.
In general, some additional plugin configuration through a file placed in the project folder would be useful for those who don't use FAW (because it's an additional panel, and we not always have much space on the screen). But I'll open a separate issue for this problem.
But how all roots in FAW are treated
How the root directories are interpreted by the respective servers is beyond the client's knowledge. The client sends the information about which root directory(s) are available in the initialize request. This takes place in 3 places, either via
Now it is up to the respective server to use this knowledge to search for the corresponding language-specific configuration files, if available/used. The rust-analyzer would now search for a cargo.toml in one of these directories, clangd would probably look for the compile_commands.json etc.
But the current way, using FAW, also has the disadvantage that a root directory, which for example only makes sense for the typescript server, would also be made known to clangd. Unfortunately, I don't see how this can be solved easily.
In general, some additional plugin configuration through a file ...
But how is this supposed to work? If we take the following project as an example,
root
- sub_dir
- sub_sub_dir
- file1
- file2
- sub_dir2
- sub_sub_dir2
- file3
- file4
If file4 is now opened, how does the LspClient know where to find this configuration file? It could of course go from sub_sub_dir2 upwards and search for it, but what if sub_dir2 was already an independent project? In Rust, for example, it is common for it to look like this
root
- sub_dir
- sub_sub_dir
- file1.rs
- file2.rs
lib.rs
cargo.toml
- sub_dir2
- sub_sub_dir2
- file3.rs
- file4.rs
lib.rs
cargo.toml
cargo.toml
This means that rust-analyzer only needs the cargo.tml from sub_dir2 to be able to use file4.rs. Of course, this makes compiling much faster than if it were to compile the complete project.
It would be conceivable, at least for servers that use such configuration files, to define them in the NppLspClient.toml file. So that the client knows that for the rust-analyzer a cargo.toml would have to be searched for, but even that will not be 100% satisfactory, that it can ALSO be that in the above example a cargo.toml exists in the sub_dir2 directory but the cargo.toml from the root directory is still required for the compilation to be successful.
@Ekopalypse I will open a new issue for this separate config file because it's probably a bigger problem.
Returning to JS I got what I asked for. I had to use jsconfig.json
instead of tsconfig.json
file with below content:
{
"include": [
"**/*.js",
"D:/outerjs/test3.js"
],
"exclude": [
"innerjs/*"
],
}
We still can use tsconfig.json
file but something needs to be added:
{
"include": [
"**/*.js",
"D:/outerjs/test3.js"
],
"exclude": [
"innerjs/*"
],
"compilerOptions": {
"allowJs": true,
}
}
Thanks to this I can point to files that should be analyzed (even outside the root), and add some exclusions inside the root. That's what I cared about the most.
I don't need to open/activate all nested files for LSP commands to work, just one from root is enough. The only problem is when I start the server from a file nested in root a little deeper, it still somehow can't find things above, I have to activate some file directly from root. At the moment it's acceptable, unless I find a solution.
If anyone uses JS/TS let me know what the problem is. In short, when we run the server on a file that is 1 or more level nested below the root, it does not search for things above unless we go to a file that is directly in the root. jsconfig.json
or tsconfig.json
is located in root. It looks like the search for these config files is not going up (manual).
Edit: This LSP doesn't support workspaceFolders so I don't know if this doesn't that cause my problem.
The only problem is when I start the server from a file nested in root a little deeper
FAW is currently required at this point. The user must have specified the root directory of the project. If you open a file that cannot be found in one of the root directories of FAW, the file directory is used in the initialization request.
@Ekopalypse
The user must have specified the root directory of the project.
Some configuration (separate file/command or something else for discussion) would be useful for things like this.
Regardless of this, I noticed one interesting thing for JS:
dirA
jsconfig.json
fileA1.js
dirA2
fileA2.js
dirB
fileB1.js
fileB2.js
fileA2.js
is active and we run server then it't not recognized command from fileA1.js
(we need switch to this file, then it will probably process the file jsconfig.json
). So in this case it doesn't loop over all parent folders of fileA2.js
.When fileB2.js
is active and we run server then command from << it looks like i was wrong here (i probably had a config file defined that's why it worked), without such config file we need to activate a given file so that its stuff is recognized in other files.fileB1.js
is recognized (that's interesting because if we did opposite, the commands from fileB2.js
would not be available in fileB1.js
until we activated file fileB2.js
). Well, this seems to be the default behavior within the folder dirB
.
But that's not the point, what's more interesting is that if now we switch to fileA2.js
it read jsconfig.json
because all the commands from above folders are recognized inside file fileA2.js
. So in this case it loop over all parent folders of fileA2.js
. It's strange, why wasn't this done in the first case? Was the initialization done differently or is something else important here? I thought it would just stick on dirA2
(like in first case).
It's a bit confusing because sometimes it iterates up through folders and other times it doesn't. It's difficult to use configuration files in such a situation (we must remember about such variations).
You ran this test with dirA and dirB as root folders in FAW? If the server supports workspace folders, I would have thought it would recognize the jsconfig.json file and know what to do. Do you have a simple demo I can play with?
You ran this test with dirA and dirB as root folders in FAW?
No, I run it without FAW, this LSP doesn't support FAW. But it's more specific because it supports single file mode.
It seems to me that when we run it for the first time (without FAW) you set root to the current folder and server will then read the config file from that folder if it exists (but it doesn't try to go higher if it doesn't exist). Which would correspond to this description from the manual:
By invoking tsc with no input files and a --project (or just -p) command line option that specifies the path of a directory containing a tsconfig.json file, or a path to a valid .json file containing the configurations.
Then, when we switch to some other file from a folder higher up (or a completely separate folder), it probably treats it as if there was no root specified, and then it looks for the configuration file iterating upwards. Which would correspond to this description from the manual:
By invoking tsc with no input files, in which case the compiler searches for the tsconfig.json file starting in the current directory and continuing up the parent directory chain.
What's interesting is that when we have separate folders with configuration files, it internally treats them as separate projects, it doesn't get lost in navigation through definitions, etc., everything worked as if I had separate projects, or at least that's what I think, because I only tested it on simple functions/variables.
Do you have a simple demo I can play with?
If you want to play with it I will create a folder with such cases in various subfolders. I'll just mention that I don't use commands like import/require etc. for modules. I just treat files as if they lived in one scope and all of them should have access to everything (as if it was one file). Because when we use module import it processes them immediately and everything below is recognized (then even jsconfig.json
is unnecessary here).
Next infos: https://code.visualstudio.com/docs/languages/jsconfig
No, I run it without FAW, this LSP ...
Just to be clear: FAW is not directly related to the workspace folder feature of the lsp protocol. Although I also use it for this purpose, the other main reason to use it is that a main project root directory can be determined by the lsp client. So the problem with opening fileA2.js
and trying to see functions from both fileA1.js
and fileB1.js
or fileB2.js
should not have occurred. (As long as the jsconfig.json has been setup correctly).
To open fileB*.js
and see definitions from fileA*.js
, in this scenario, it would need the support of the workspace folders from the server.
@ArkadiuszMichalski - reported renaming and code action issues should be fixed in the released version v.0.0.27-alpha.
After some short testing Lsp for C/C++ I started looking something for JavaScript. From what I have seen the only solution is: typescript-language-server which is a bridge for tsserver (something from Microsoft used in their tools, unfortunately it still does not support LSP directly https://github.com/microsoft/TypeScript/issues/39459).
How to install?
If you already have
Node
available inPath
and want to have these tools also globally, then call:npm install -g typescript-language-server typescript
Then config for Lsp should looks like:For people who don't have/don't use
Node
and want to check it quickly locally: 2.1 DownloadNode
(withNPM
) from here https://nodejs.org/dist/latest/ (.7z
or.zip
). 2.2 Unpack it for example toNode
folder. 2.3 InsideNode
folder run:npm install -g typescript-language-server typescript
Inside
Node
andNode\node_modules
new files should appear. Then config for Lsp should looks like:Of course,
Node
is a virtual name, it should be the full path to this folder, like:etc., depending on where you unpacked
Node
.After a quick tests, I can see that the most important commands work:
Goto definition
Find references
Symbol windows
Format document
andFormat selected text
Not work:
Rename
<< but the same I have for C/C++Ask for help
<< it shows something there and we can select, but after that it does not perform any operation.Poorly work:
Nice, I didn't think I'd see contextual jumping in Notepad++ for JavaScript (tools based on ctags can't do that). Great job, I hope you keep developing it, in Notepad++ we miss these new solutions from other editors when working on code (with larger code they become more and more necessary).