Repository navigation
Add {will,did}{Create,Rename,Delete}Files - #687
Conversation
Copied as suggested here: microsoft/language-server-protocol#1074 (comment)
… and blank segments
|
Dirk Bäumer (@dbaeumer) I've implemented a test for I made the test server register statically + dynamically (slightly different paths) so both can be tested together (I don't have middleware tests yet, but they should be easy). Any preferences for how to test the Did{Create,Rename,Delete} notifications went through? (They don't have a response like the Will{...} requests. Thanks! Edit: It might not be very obvious from the test, but the server creates an edit for a dummy reference file which just includes all the paths it received. Seemed like the easiest way for the client to verify the server got the expected event (as well as verifying the edit it passed through to |
|
|
||
| public initialize(capabilities: ServerCapabilities, _: DocumentSelector): void { | ||
| let syncOptions = capabilities.files; | ||
| if (syncOptions?.willCreate?.globPattern) { |
There was a problem hiding this comment.
I started (for new code) to make falsify checks explicit since I trip over undefined and 0 quite often (espeically for number and string). So this should be syncOptions?.willCreate?.globPattern !== undefined. And do we need to check the syntax of the glob pattern?
There was a problem hiding this comment.
I see now the comment in register
There was a problem hiding this comment.
Regarding errors: during initialization we should check the pattern, and if not valid ignore the capability and add something to the output channel.
If an invalid pattern is received during dynamic registration we should throw. However we should always throw new Error and never a string.
There was a problem hiding this comment.
Done both of these. We throw a custom error type in registration so it can be caught by static registration and ignored/logged instead. Let me know if this should done differently!
|
Regarding the test question: you can also throw an error on the server side which will propagate back to the client and will make the send call fail. Normally this will make the test fail. |
I think this would prevent checking that calling |
| } | ||
| } | ||
|
|
||
| class InvalidGlobError extends Error { |
There was a problem hiding this comment.
I would leave this out and simply throw new Error. The client will not catch it anyways and the server will not care :-)
There was a problem hiding this comment.
Done! Though this does mean if another error occurs (that isn't converting the glob to a regex) inside the registration method, it may warn with a misleading message (and would also ignore the error). There's not a lot of code there to go wrong, but thought it worth mentioning.
|
Dirk Bäumer (@dbaeumer) any other feedback on this, or is it worth me implementing the other requests too? (they're mostly copy/pastes of this, though with the |
|
Danny Tuppeny (@DanTup) to be able to merge this we should have implementation for all notifications and requests. I will add my comments to the one implementation we have and the the rest should be copy paste. |
|
Dirk Bäumer (@dbaeumer) sgtm! I always planed to implement them all before anything is merged - I just wanted to check you're happy with this one before creating the others. Thanks! |
| codeLens?: CodeLensWorkspaceClientCapabilities; | ||
| } | ||
|
|
||
| export interface FileClientCapabilities { |
There was a problem hiding this comment.
Now that I think about the name we should may be call this FileManipulation to make it clear that these are not file events in the classical sense of file watching. We should also add a sentence to make this clear.
There was a problem hiding this comment.
You used FileOperations further down which is a good name.
There was a problem hiding this comment.
Done!
| * | ||
| * Since 3.16.0 | ||
| */ | ||
| files?: FileClientCapabilities |
There was a problem hiding this comment.
And then we can name this fileOperations as well. Same on the server side.
There was a problem hiding this comment.
Done!
(Also pushed changes to the LSP spec PR to reflect the same)
| * | ||
| * @param handler The corresponding handler. | ||
| */ | ||
| onWillCreateFiles(handler: RequestHandler<CreateFilesParams, WorkspaceEdit | undefined | null, void>): void; |
There was a problem hiding this comment.
I moved away from putting the handlers on the connection namespace since it makes it hard to understand this. Since we already have a window namespace and that namespace is use for UI specific requests and notification we should add these to the window namespace. This makes it also clear that this has nothing to do with normal file events since those would be on the workspace namespace. All window stuff is UI related.
There was a problem hiding this comment.
This also allows you to implement this on the server side as a feature and not directly in the server since the feature can be added to the RemoteWindow type here https://github.com/microsoft/vscode-languageserver-node/blob/master/server/src/common/server.ts#L470 and to the implementation here: https://github.com/microsoft/vscode-languageserver-node/blob/master/server/src/common/server.ts#L512
I can help with this if it is not clear how to do this.
There was a problem hiding this comment.
I started doing this, but I've gotten confused. The example I was copying (ShowDocumentFeature) is a server->client request. The ones I'm implementing come from the client. So I don't have a server implementation that's shared, the implementation needs to come from the specific server implementation.
I've pushed my changes so far, there are two places I'm stuck:
- What the (test) server implementation should look like now (this code doesn't compile):
- What should the Feature look like (when the server code has no default implementation unless it's being contributed by the implementing server):
There was a problem hiding this comment.
You can copy from an example that implements a language request. It would work the same for the window namespace. A corresponding example is here: https://github.com/microsoft/vscode-languageserver-node/blob/master/server/src/common/onTypeRename.ts#L6
The code for the test server is correct assuming that you have it working for the window namespace. Is your code checked in. Then I can tweak it and push to your repository if you give me write access.
There was a problem hiding this comment.
Aha, I see! I've gotten further (and pushed my changes), though it seems like I'm missing a this.attachWorkDoneProgress from _RemoteWindowImpl (it's on _Languages already). What's the correct way to add this? (I can copy/paste from _Languages but I'm not sure if that's right - I'm also unsure if we should have progress here and if so whether I need some tweaks to the spec?).
I've given your access to my fork btw, so feel free to make changes if that's easier than describing. Thanks!
There was a problem hiding this comment.
OK. I did the changes and pushed them. Did some additional code cleanup One thing I didn't do to not surprise you is to rename protocol.window.fileOperations.ts to protocol.fileOperations. I usually don't put the namespace into the name. A also created a fileOperations.ts file in the client.
Regarding this.attachWorkDoneProgress since the request should be fast we shouldn't allow progress. So you use a normal RequestHandler.
There was a problem hiding this comment.
Great - thanks! I'll rename that file and fill in the rest of the notifications and tests.
|
Danny Tuppeny (@DanTup) thanks for providing the PR. Regarding pre-release: yes, either today or tomorrow. I still need to do some polish and I wanted to better understand the minimatch directory issue to see if we need to spec more. |
|
Great! Do shout if anything isn't clear or you want me to make changes. I went with the second option above (we suffix Thanks! |
|
Danny Tuppeny (@DanTup) I played a litte with minimatch and I think we don't need to add the slash to the pattern since we have the pattern kind. I tested that for example: let matcher = new Minimatch('a/**');
matcher.match('a/folder1/folder2/folder3');evaluates to true. So I would suggest that we do the following:
This means we still need to do that stat but we don't need to tweak the paths. Is there something you can think of that doesn't make this work? |
|
The filter method would then look like this: protected async filter(event: E, prop: (i: I) => code.Uri): Promise<E> {
// (Asynchronously) map each file onto a boolean of whether it matches
// any of the globs.
const fileMatches = await Promise.all(event.files.map(async (item) => {
const uri = prop(item);
const path = uri.fsPath;
for (const globs of this._globPatterns.values()) {
for (const pattern of globs) {
if (pattern.matcher.match(path)) {
// The pattern matches. If kind is undefined then everything is ok
if (pattern.kind === undefined) {
return true;
}
const fileType = await FileOperationFeature.getFileType(uri);
if (fileType === undefined || (fileType === code.FileType.File && pattern.kind === FileOperationPatternKind.file) || (fileType === code.FileType.Directory && pattern.kind === FileOperationPatternKind.folder)) {
return true;
}
}
}
}
return false;
}));
// Filter the files to those that matched.
const files = event.files.filter((_, index) => fileMatches[index]);
return { ...event, files };
} |
|
Tests are still green with that change. One thing we can debate is whether a failure of determining the file type should drop the event or sent it. Currently it is sent. I have pushed the changes. Let me know if you see any problems with them. |
The case I was trying to cover was if the pattern ended with a // true
console.log(new Minimatch('a/**').match('a/folder1/folder2/folder3'));
// false
console.log(new Minimatch('a/**/').match('a/folder1/folder2/folder3'));
// true
console.log(new Minimatch('a/**/').match('a/folder1/folder2/folder3/'));The middle one fails there, as the pattern has a trailing slash. I don't know whether that's reasonable or not, but some of my previous example patterns were like: If it's ok to say there shouldn't be trailing slashes on the patterns for directories (we can't enforce this easily ourselves if we're not doing the brace expansion), then I think the changes are fine. It would make sense to highlight this in the spec if so, to avoid catching people out.
I have no strong feeling on this, though since the server can easily drop it if it can't do anything with it (but can't handle what it doesn't get), maybe sending it makes most sense. Maybe worth being explicit about this in the spec whichever way you think is best? |
|
OK. I do think we should cover |
|
Here is what I suggest we do: if the matching fails and kind is folder then we stat and if it is a directory we add a '/' to the end and rematch. The reasoning behind this is that the regular expression matching is a lot faster than stating the URI. Does that makes sense? |
|
See cd9b8e4 |
|
I can't think of any examples where that wouldn't work, so seems sensible to me. The only question I have is whether minimatch cares that the trailing slash is forwards but the rest of the path might be backslashes now we're using |
|
Minimatch actually doesn't care. But let me check again. I used fsPath since we do use that in all other cases, even with document selector patterns. So this makes it at least consistent. |
Great, all sounds good to me then :-) |
|
It does take care of it but it depends on the platform :-). Only under Windows it treats |
|
Oh, I see. Can't we switch back to |
|
Using Uri.path is IMO confusing since VS Code always uses fsPath for patterns. This would result in strange behavior expecially under Windows with drive letters. I did some more testing and it is actually OK. Under Windows: let matcher = new Minimatch('a/**/')
> matcher.match('a\\folder\\folder\\')
true
> matcher.match('a\\folder\\folder/')
true
>Which is OK. But what I will do after checking with what we do elsewhere is to replace |
|
Done. |
Fair - I was thinking patterns would always be relative since they're implicitly constrained by the workspace, but that probably isn't a valid assumption. Oh, one final thing - minimatch seems to be case-sensitive by default. I think we should document this, or state that they're case-insensitive and set // true
console.log(new Minimatch('a/**').match('a/folder1/folder2/folder3'));
// false
console.log(new Minimatch('A/**').match('a/folder1/folder2/folder3'));
// true
console.log(new Minimatch('A/**', { nocase: true }).match('a/folder1/folder2/folder3'));Let me know if you want me to do any of that. Thanks! |
|
Actually, that is a good point. We should add an options property to Something like interface FileOperationPatternOptions {
ignoreCase: boolean;
}Does that make sense to you ? |
|
Yep! I can update the spec/client tomorrow if you'd like. Should the options and/or |
|
I made it optional. Looks like this export interface FileOperationPatternOptions {
/**
* The pattern should be match ignoring casing.
*/
ignoreCase?: boolean;
}Default should be false as with all optional values. |
|
I update the spec as well. |
|
I added a comment here about a typo + making the default clear from the description: But otherwise, all looks good to me :-) |
|
Published new next versions. |
|
Thanks! I rebuilt everything from the latest version of the spec in the Dart server, and my renames are all working as expected! 😀 Looking at the stuff in the v3.16 milestone here and for LSP, I presume there aren't any significant changes planned before it's finalised? I'd like to start preparing PRs for the server changes (for this + semantic tokens) but have been holding off until the spec was less likely to change. Thanks again for your help + patience with this! |
|
Thanks a lot for all your help with this. I don't plan to add any new features for 3.16. As soon as VS Code 1.52 ships I will release a new LSP version. |
|
Perfect, thanks! Out of interest - is there anything specific in VS Code 1.52 that is required for v3.16? I'm wondering whether I should be increasing the minimum version of VS Code when I switch to this latest client or not - I haven't noticed any issues using it with v1.51, but my testing has been quite focused on the changes in this PR. |
|
The new version of LSP will require 1.52 because of the linked range editing support. |
|
Ah, I see - I'll bump my minimum version once VS code releases then too. Thanks! |
This is still very much WIP, but opening to get some feedback/guidance.Implementation for microsoft/language-server-protocol#1074.