Skip to content

Commit 4408739

Browse files
committed
Fixes #704: Make FileOperationPattern more like document filters
1 parent 2c43702 commit 4408739

File tree

4 files changed

+84
-53
lines changed

4 files changed

+84
-53
lines changed

client-node-tests/src/integration.test.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -146,22 +146,22 @@ suite('Client integration', () => {
146146
},
147147
workspace: {
148148
fileOperations: {
149-
didCreate: { patterns: [{ glob: '**/created-static/**{/,/*.txt}' }] },
149+
didCreate: { filters: [{ scheme: 'file', pattern: { glob: '**/created-static/**{/,/*.txt}' } }] },
150150
didRename: {
151-
patterns: [
152-
{ glob: '**/renamed-static/**/', matches: 'folder' },
153-
{ glob: '**/renamed-static/**/*.txt', matches: 'file' }
151+
filters: [
152+
{ scheme: 'file', pattern: { glob: '**/renamed-static/**/', matches: 'folder' } },
153+
{ scheme: 'file', pattern: { glob: '**/renamed-static/**/*.txt', matches: 'file' } }
154154
]
155155
},
156-
didDelete: { patterns: [{ glob: '**/deleted-static/**{/,/*.txt}' }] },
157-
willCreate: { patterns: [{ glob: '**/created-static/**{/,/*.txt}' }] },
156+
didDelete: { filters: [{ scheme: 'file', pattern: { glob: '**/deleted-static/**{/,/*.txt}' } }] },
157+
willCreate: { filters: [{ scheme: 'file', pattern: { glob: '**/created-static/**{/,/*.txt}' } }] },
158158
willRename: {
159-
patterns: [
160-
{ glob: '**/renamed-static/**/', matches: 'folder' },
161-
{ glob: '**/renamed-static/**/*.txt', matches: 'file' }
159+
filters: [
160+
{ scheme: 'file', pattern: { glob: '**/renamed-static/**/', matches: 'folder' } },
161+
{ scheme: 'file', pattern: { glob: '**/renamed-static/**/*.txt', matches: 'file' } }
162162
]
163163
},
164-
willDelete: { patterns: [{ glob: '**/deleted-static/**{/,/*.txt}' }] },
164+
willDelete: { filters: [ {scheme: 'file', pattern: { glob: '**/deleted-static/**{/,/*.txt}' } }] },
165165
},
166166
},
167167
linkedEditingRangeProvider: false
@@ -728,7 +728,7 @@ suite('Client integration', () => {
728728
'/my/created-dynamic/file.txt',
729729
'/my/created-dynamic/file.js',
730730
'/my/created-dynamic/folder/',
731-
].map((p) => vscode.Uri.parse(p));
731+
].map((p) => vscode.Uri.file(p));
732732

733733
const renameFiles = [
734734
['/my/file.txt', '/my-new/file.txt'],
@@ -742,7 +742,7 @@ suite('Client integration', () => {
742742
['/my/renamed-dynamic/file.txt', '/my-new/renamed-dynamic/file.txt'],
743743
['/my/renamed-dynamic/file.js', '/my-new/renamed-dynamic/file.js'],
744744
['/my/renamed-dynamic/folder/', '/my-new/renamed-dynamic/folder/'],
745-
].map(([o, n]) => ({ oldUri: vscode.Uri.parse(o), newUri: vscode.Uri.parse(n) }));
745+
].map(([o, n]) => ({ oldUri: vscode.Uri.file(o), newUri: vscode.Uri.file(n) }));
746746

747747
const deleteFiles = [
748748
'/my/file.txt',
@@ -756,7 +756,7 @@ suite('Client integration', () => {
756756
'/my/deleted-dynamic/file.txt',
757757
'/my/deleted-dynamic/file.js',
758758
'/my/deleted-dynamic/folder/',
759-
].map((p) => vscode.Uri.parse(p));
759+
].map((p) => vscode.Uri.file(p));
760760

761761
test('Will Create Files', async () => {
762762
const feature = client.getFeature(WillCreateFilesRequest.method);

client-node-tests/src/servers/testServer.ts

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
DiagnosticTag, CompletionItemTag, TextDocumentSyncKind, MarkupKind, SignatureHelp, SignatureInformation, ParameterInformation,
1010
Location, Range, DocumentHighlight, DocumentHighlightKind, CodeAction, Command, TextEdit, Position, DocumentLink,
1111
ColorInformation, Color, ColorPresentation, FoldingRange, SelectionRange, SymbolKind, ProtocolRequestType, WorkDoneProgress,
12-
WorkDoneProgressCreateRequest, WillCreateFilesRequest, WillRenameFilesRequest, WillDeleteFilesRequest, DidDeleteFilesNotification, DidRenameFilesNotification, DidCreateFilesNotification
12+
WorkDoneProgressCreateRequest, WillCreateFilesRequest, WillRenameFilesRequest, WillDeleteFilesRequest, DidDeleteFilesNotification,
13+
DidRenameFilesNotification, DidCreateFilesNotification
1314
} from '../../../server/node';
1415

1516
import { URI } from 'vscode-uri';
@@ -91,28 +92,28 @@ connection.onInitialize((params: InitializeParams): any => {
9192
fileOperations: {
9293
// Static reg is folders + .txt files with operation kind in the path
9394
didCreate: {
94-
patterns: [{ glob: '**/created-static/**{/,/*.txt}' }]
95+
filters: [{ scheme: 'file', pattern: { glob: '**/created-static/**{/,/*.txt}' }}]
9596
},
9697
didRename: {
97-
patterns: [
98-
{ glob: '**/renamed-static/**/', matches: 'folder' },
99-
{ glob: '**/renamed-static/**/*.txt', matches: 'file' }
98+
filters: [
99+
{ scheme: 'file', pattern: { glob: '**/renamed-static/**/', matches: 'folder' } },
100+
{ scheme: 'file', pattern: { glob: '**/renamed-static/**/*.txt', matches: 'file' } }
100101
]
101102
},
102103
didDelete: {
103-
patterns: [{ glob: '**/deleted-static/**{/,/*.txt}' }]
104+
filters: [{ scheme: 'file', pattern: { glob: '**/deleted-static/**{/,/*.txt}' } }]
104105
},
105106
willCreate: {
106-
patterns: [{ glob: '**/created-static/**{/,/*.txt}' }]
107+
filters: [{ scheme: 'file', pattern: { glob: '**/created-static/**{/,/*.txt}' } }]
107108
},
108109
willRename: {
109-
patterns: [
110-
{ glob: '**/renamed-static/**/', matches: 'folder' },
111-
{ glob: '**/renamed-static/**/*.txt', matches: 'file' }
110+
filters: [
111+
{ scheme: 'file', pattern: { glob: '**/renamed-static/**/', matches: 'folder' } },
112+
{ scheme: 'file', pattern: { glob: '**/renamed-static/**/*.txt', matches: 'file' } }
112113
]
113114
},
114115
willDelete: {
115-
patterns: [{ glob: '**/deleted-static/**{/,/*.txt}' }]
116+
filters: [{ scheme: 'file', pattern: { glob: '**/deleted-static/**{/,/*.txt}' } }]
116117
},
117118
},
118119
},
@@ -124,31 +125,33 @@ connection.onInitialize((params: InitializeParams): any => {
124125
connection.onInitialized(() => {
125126
// Dynamic reg is folders + .js files with operation kind in the path
126127
connection.client.register(DidCreateFilesNotification.type, {
127-
patterns: [{ glob: '**/created-dynamic/**{/,/*.js}' }]
128+
filters: [{ scheme: 'file', pattern: { glob: '**/created-dynamic/**{/,/*.js}' } }]
128129
});
129130
connection.client.register(DidRenameFilesNotification.type, {
130-
patterns: [
131-
{ glob: '**/renamed-dynamic/**/', matches: 'folder' },
132-
{ glob: '**/renamed-dynamic/**/*.js', matches: 'file' }
131+
filters: [
132+
{ scheme: 'file', pattern: { glob: '**/renamed-dynamic/**/', matches: 'folder' } },
133+
{ scheme: 'file', pattern: { glob: '**/renamed-dynamic/**/*.js', matches: 'file' } }
133134
]
134135
});
135136
connection.client.register(DidDeleteFilesNotification.type, {
136-
patterns: [{ glob: '**/deleted-dynamic/**{/,/*.js}' }]
137+
filters: [ { scheme: 'file', pattern: { glob: '**/deleted-dynamic/**{/,/*.js}' } }]
137138
});
138139
connection.client.register(WillCreateFilesRequest.type, {
139-
patterns: [{ glob: '**/created-dynamic/**{/,/*.js}' }]
140+
filters: [{ scheme: 'file', pattern: { glob: '**/created-dynamic/**{/,/*.js}' } }]
140141
});
141142
connection.client.register(WillRenameFilesRequest.type, {
142-
patterns: [
143-
{ glob: '**/renamed-dynamic/**/', matches: 'folder' },
144-
{ glob: '**/renamed-dynamic/**/*.js', matches: 'file' }
143+
filters: [
144+
{ scheme: 'file', pattern: { glob: '**/renamed-dynamic/**/', matches: 'folder' } },
145+
{ scheme: 'file', pattern: { glob: '**/renamed-dynamic/**/*.js', matches: 'file' } }
145146
]
146147
});
147148
connection.client.register(WillDeleteFilesRequest.type, {
148-
patterns: [{ glob: '**/deleted-dynamic/**{/,/*.js}' }]
149+
filters: [{scheme: 'file', pattern: { glob: '**/deleted-dynamic/**{/,/*.js}' } }]
149150
});
150151
});
151152

153+
//const type: typeof SemanticTokensRegistrationType.type = WillDeleteFilesRequest.type;
154+
152155
connection.onDeclaration((params) => {
153156
assert.equal(params.position.line, 1);
154157
assert.equal(params.position.character, 1);

client/src/common/fileOperations.ts

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ abstract class FileOperationFeature<I, E extends Event<I>> implements DynamicFea
5151
private _clientCapability: keyof proto.FileOperationClientCapabilities;
5252
private _serverCapability: keyof proto.FileOperationOptions;
5353
private _listener: code.Disposable | undefined;
54-
private _globPatterns = new Map<string, Array<{ matcher: minimatch.IMinimatch, kind?: proto.FileOperationPatternKind }>>();
54+
private _filters = new Map<string, Array<{ scheme?: string, matcher: minimatch.IMinimatch, kind?: proto.FileOperationPatternKind }>>();
5555

5656
constructor(client: BaseLanguageClient, event: code.Event<E>,
5757
registrationType: proto.RegistrationType<proto.FileOperationRegistrationOptions>,
@@ -79,11 +79,11 @@ abstract class FileOperationFeature<I, E extends Event<I>> implements DynamicFea
7979
public initialize(capabilities: proto.ServerCapabilities): void {
8080
const options = capabilities.workspace?.fileOperations;
8181
const capability = options !== undefined ? access(options, this._serverCapability) : undefined;
82-
if (capability?.patterns !== undefined) {
82+
if (capability?.filters !== undefined) {
8383
try {
8484
this.register({
8585
id: UUID.generateUuid(),
86-
registerOptions: { patterns: capability.patterns }
86+
registerOptions: { filters: capability.filters }
8787
});
8888
} catch (e) {
8989
this._client.warn(`Ignoring invalid glob pattern for ${this._serverCapability} registration: ${e}`);
@@ -95,28 +95,28 @@ abstract class FileOperationFeature<I, E extends Event<I>> implements DynamicFea
9595
if (!this._listener) {
9696
this._listener = this._event(this.send, this);
9797
}
98-
const regularExpressions = data.registerOptions.patterns.map((rule) => {
99-
const matcher = new minimatch.Minimatch(rule.glob, FileOperationFeature.asMinimatchOptions(rule.options));
98+
const minimatchFilter = data.registerOptions.filters.map((filter) => {
99+
const matcher = new minimatch.Minimatch(filter.pattern.glob, FileOperationFeature.asMinimatchOptions(filter.pattern.options));
100100
if (!matcher.makeRe()) {
101-
throw new Error(`Invalid pattern ${rule.glob}!`);
101+
throw new Error(`Invalid pattern ${filter.pattern.glob}!`);
102102
}
103-
return { matcher, kind: rule.matches };
103+
return { scheme: filter.scheme, matcher, kind: filter.pattern.matches };
104104
});
105-
this._globPatterns.set(data.id, regularExpressions);
105+
this._filters.set(data.id, minimatchFilter);
106106
}
107107

108108
public abstract send(data: E): Promise<void>;
109109

110110
public unregister(id: string): void {
111-
this._globPatterns.delete(id);
112-
if (this._globPatterns.size === 0 && this._listener) {
111+
this._filters.delete(id);
112+
if (this._filters.size === 0 && this._listener) {
113113
this._listener.dispose();
114114
this._listener = undefined;
115115
}
116116
}
117117

118118
public dispose(): void {
119-
this._globPatterns.clear();
119+
this._filters.clear();
120120
if (this._listener) {
121121
this._listener.dispose();
122122
this._listener = undefined;
@@ -131,11 +131,14 @@ abstract class FileOperationFeature<I, E extends Event<I>> implements DynamicFea
131131
// Use fsPath to make this consistent with file system watchers but help
132132
// minimatch to use '/' instead of `\\` if present.
133133
const path = uri.fsPath.replace(/\\/g, '/');
134-
for (const globs of this._globPatterns.values()) {
135-
for (const pattern of globs) {
136-
if (pattern.matcher.match(path)) {
134+
for (const filters of this._filters.values()) {
135+
for (const filter of filters) {
136+
if (filter.scheme !== undefined && filter.scheme !== uri.scheme) {
137+
continue;
138+
}
139+
if (filter.matcher.match(path)) {
137140
// The pattern matches. If kind is undefined then everything is ok
138-
if (pattern.kind === undefined) {
141+
if (filter.kind === undefined) {
139142
return true;
140143
}
141144
const fileType = await FileOperationFeature.getFileType(uri);
@@ -145,12 +148,12 @@ abstract class FileOperationFeature<I, E extends Event<I>> implements DynamicFea
145148
this._client.error(`Failed to determine file type for ${uri.toString()}.`);
146149
return true;
147150
}
148-
if ((fileType === code.FileType.File && pattern.kind === proto.FileOperationPatternKind.file) || (fileType === code.FileType.Directory && pattern.kind === proto.FileOperationPatternKind.folder)) {
151+
if ((fileType === code.FileType.File && filter.kind === proto.FileOperationPatternKind.file) || (fileType === code.FileType.Directory && filter.kind === proto.FileOperationPatternKind.folder)) {
149152
return true;
150153
}
151-
} else if (pattern.kind === proto.FileOperationPatternKind.folder) {
154+
} else if (filter.kind === proto.FileOperationPatternKind.folder) {
152155
const fileType = await FileOperationFeature.getFileType(uri);
153-
if (fileType === code.FileType.Directory && pattern.matcher.match(`${path}/`)) {
156+
if (fileType === code.FileType.Directory && filter.matcher.match(`${path}/`)) {
154157
return true;
155158
}
156159
}

protocol/src/common/protocol.fileOperations.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ export interface FileOperationOptions {
5151
* @since 3.16.0 - proposed state
5252
*/
5353
export interface FileOperationRegistrationOptions {
54-
patterns: FileOperationPattern[];
54+
55+
/**
56+
* The actual filters.
57+
*/
58+
filters: FileOperationFilter[];
5559
}
5660

5761
/**
@@ -61,6 +65,7 @@ export interface FileOperationRegistrationOptions {
6165
* @since 3.16.0 - proposed state
6266
*/
6367
export namespace FileOperationPatternKind {
68+
6469
/**
6570
* The pattern matches a file only.
6671
*/
@@ -94,6 +99,7 @@ export interface FileOperationPatternOptions {
9499
* @since 3.16.0 - proposed state
95100
*/
96101
interface FileOperationPattern {
102+
97103
/**
98104
* The glob pattern to match. Glob patterns can have the following syntax:
99105
* - `*` to match one or more characters in a path segment
@@ -118,6 +124,25 @@ interface FileOperationPattern {
118124
options?: FileOperationPatternOptions;
119125
}
120126

127+
/**
128+
* A filter to describe in which file operation requests or notifications
129+
* the server is interested in.
130+
*
131+
* @since 3.16.0
132+
*/
133+
export interface FileOperationFilter {
134+
135+
/**
136+
* A Uri like `file` or `untitled`.
137+
*/
138+
scheme?: string;
139+
140+
/**
141+
* The actual file operation pattern.
142+
*/
143+
pattern: FileOperationPattern;
144+
}
145+
121146
/**
122147
* Capabilities relating to events from file operations by the user in the client.
123148
*

0 commit comments

Comments
 (0)