Skip to content

Commit a97888f

Browse files
committed
Add support for workspace read requests
1 parent 5fa1a2a commit a97888f

11 files changed

Lines changed: 837 additions & 7 deletions

File tree

client/src/common/client.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ import { InlineCompletionItemFeature, InlineCompletionMiddleware } from './inlin
9595
import { TextDocumentContentFeature, type TextDocumentContentMiddleware, type TextDocumentContentProviderShape } from './textDocumentContent';
9696
import { FileSystemWatcherFeature } from './fileSystemWatcher';
9797
import { ProgressFeature } from './progress';
98+
import { FileSystemFeature, FileSystemMiddleware } from './fileSystem';
9899

99100
/**
100101
* Controls when the output channel is revealed.
@@ -291,7 +292,7 @@ type _WorkspaceMiddleware = {
291292
handleApplyEdit?: (this: void, params: ApplyWorkspaceEditParams, next: ApplyWorkspaceEditRequest.HandlerSignature) => HandlerResult<ApplyWorkspaceEditResult, void>;
292293
};
293294

294-
export type WorkspaceMiddleware = _WorkspaceMiddleware & ConfigurationMiddleware & DidChangeConfigurationMiddleware & WorkspaceFolderMiddleware & FileOperationsMiddleware;
295+
export type WorkspaceMiddleware = _WorkspaceMiddleware & ConfigurationMiddleware & DidChangeConfigurationMiddleware & WorkspaceFolderMiddleware & FileOperationsMiddleware & FileSystemMiddleware;
295296

296297
interface _WindowMiddleware {
297298
showDocument?: ShowDocumentRequest.MiddlewareSignature;
@@ -342,7 +343,7 @@ DocumentHighlightMiddleware & DocumentSymbolMiddleware & WorkspaceSymbolMiddlewa
342343
ColorProviderMiddleware & CodeActionMiddleware & CodeLensMiddleware & FormattingMiddleware & RenameMiddleware & DocumentLinkMiddleware & ExecuteCommandMiddleware &
343344
FoldingRangeProviderMiddleware & DeclarationMiddleware & SelectionRangeProviderMiddleware & CallHierarchyMiddleware & SemanticTokensMiddleware &
344345
LinkedEditingRangeMiddleware & TypeHierarchyMiddleware & InlineValueMiddleware & InlayHintsMiddleware & NotebookDocumentMiddleware & DiagnosticProviderMiddleware &
345-
InlineCompletionMiddleware & TextDocumentContentMiddleware & GeneralMiddleware;
346+
InlineCompletionMiddleware & TextDocumentContentMiddleware & FileSystemMiddleware & GeneralMiddleware;
346347

347348
export type LanguageClientOptions = {
348349
documentSelector?: DocumentSelector | string[];
@@ -2085,6 +2086,7 @@ export abstract class BaseLanguageClient implements FeatureClient<Middleware, La
20852086
this.registerFeature(new NotebookDocumentSyncFeature(this));
20862087
this.registerFeature(new InlineCompletionItemFeature(this));
20872088
this.registerFeature(new TextDocumentContentFeature(this));
2089+
this.registerFeature(new FileSystemFeature(this));
20882090
}
20892091

20902092
public registerProposedFeatures() {

client/src/common/codeConverter.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ export interface Converter {
145145

146146
asInlineCompletionParams(document: code.TextDocument, position: code.Position, context: code.InlineCompletionContext): proto.InlineCompletionParams;
147147
asInlineCompletionContext(context: code.InlineCompletionContext): proto.InlineCompletionContext;
148+
149+
asDirectoryEntry(item: [string, code.FileType]): proto.DirectoryEntry;
150+
asFileStat(item: code.FileStat): proto.FileStat;
148151
}
149152

150153
export interface URIConverter {
@@ -977,6 +980,45 @@ export function createConverter(uriConverter?: URIConverter): Converter {
977980
return result;
978981
}
979982

983+
function asFileType(value: code.FileType): {
984+
type: proto.FileType;
985+
isSymlink: boolean;
986+
} {
987+
let type: proto.FileType = proto.FileType.unknown;
988+
if (value & code.FileType.Directory) {
989+
type = proto.FileType.directory;
990+
} else if (value & code.FileType.File) {
991+
type = proto.FileType.file;
992+
}
993+
const isSymlink = (value & code.FileType.SymbolicLink) !== 0;
994+
return {
995+
type,
996+
isSymlink
997+
};
998+
}
999+
1000+
function asDirectoryEntry(item: [string, code.FileType]): proto.DirectoryEntry {
1001+
const { type, isSymlink } = asFileType(item[1]);
1002+
const result: proto.DirectoryEntry = {
1003+
name: item[0],
1004+
type,
1005+
isSymlink
1006+
};
1007+
return result;
1008+
}
1009+
1010+
function asFileStat(item: code.FileStat): proto.FileStat {
1011+
const { type, isSymlink } = asFileType(item.type);
1012+
const result: proto.FileStat = {
1013+
type,
1014+
isSymlink,
1015+
ctime: item.ctime,
1016+
mtime: item.mtime,
1017+
size: item.size
1018+
};
1019+
return result;
1020+
}
1021+
9801022
return {
9811023
asUri,
9821024
asTextDocumentIdentifier,
@@ -1031,6 +1073,8 @@ export function createConverter(uriConverter?: URIConverter): Converter {
10311073
asInlayHint,
10321074
asWorkspaceSymbol,
10331075
asInlineCompletionParams,
1034-
asInlineCompletionContext
1076+
asInlineCompletionContext,
1077+
asDirectoryEntry,
1078+
asFileStat,
10351079
};
10361080
}

client/src/common/fileSystem.ts

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/* --------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
* ------------------------------------------------------------------------------------------ */
5+
6+
import * as vscode from 'vscode';
7+
8+
import {
9+
ClientCapabilities, StatRequest, ReadFileRequest, ReadDirectoryRequest
10+
} from 'vscode-languageserver-protocol';
11+
12+
import { StaticFeature, FeatureClient, FeatureState } from './features';
13+
14+
export interface FileSystemReadFileSignature {
15+
(this: void, uri: vscode.Uri, encoding?: string): Promise<string | null>;
16+
}
17+
18+
export interface FileSystemStatSignature {
19+
(this: void, uri: vscode.Uri): Promise<vscode.FileStat | null>;
20+
}
21+
22+
export interface FileSystemReadDirectorySignature {
23+
(this: void, uri: vscode.Uri): Promise<[string, vscode.FileType][] | null>;
24+
}
25+
26+
/**
27+
* File system middleware.
28+
*
29+
* @since 3.19.0
30+
*/
31+
export interface FileSystemMiddleware {
32+
fs?: {
33+
stat?: (this: void, uri: vscode.Uri, next: FileSystemStatSignature) => vscode.ProviderResult<vscode.FileStat | null>;
34+
readFile?: (this: void, uri: vscode.Uri, encoding: string | undefined, next: FileSystemReadFileSignature) => vscode.ProviderResult<string | null>;
35+
readDirectory?: (this: void, uri: vscode.Uri, next: FileSystemReadDirectorySignature) => vscode.ProviderResult<[string, vscode.FileType][] | null>;
36+
};
37+
}
38+
39+
interface WorkspaceFileSystemMiddleware {
40+
workspace?: FileSystemMiddleware;
41+
}
42+
43+
// TextDecoder is available in all supported environments, but we can't use it directly
44+
// because that would require us to use the dom or webworker lib, which we don't want to do.
45+
// So we declare it here to make TypeScript happy.
46+
declare class TextDecoder {
47+
constructor(label?: string, options?: { fatal?: boolean; ignoreBOM?: boolean });
48+
decode(input?: Uint8Array): string;
49+
}
50+
51+
/**
52+
* file system feature. From server to client.
53+
*/
54+
export class FileSystemFeature implements StaticFeature {
55+
56+
private readonly _client: FeatureClient<WorkspaceFileSystemMiddleware>;
57+
58+
constructor(client: FeatureClient<WorkspaceFileSystemMiddleware>) {
59+
this._client = client;
60+
}
61+
62+
getState(): FeatureState {
63+
return { kind: 'static' };
64+
}
65+
66+
public fillClientCapabilities(capabilities: ClientCapabilities): void {
67+
capabilities.workspace ??= {};
68+
capabilities.workspace.fileSystem ??= {};
69+
capabilities.workspace.fileSystem.stat = true;
70+
capabilities.workspace.fileSystem.readFile = true;
71+
capabilities.workspace.fileSystem.readDirectory = true;
72+
}
73+
74+
public initialize(): void {
75+
const client = this._client;
76+
client.onRequest(StatRequest.type, async (params) => {
77+
const paramsUri = this._client.protocol2CodeConverter.asUri(params.uri);
78+
const fileStat: FileSystemStatSignature = async (uri) => {
79+
try {
80+
const vstat = await vscode.workspace.fs.stat(uri);
81+
return vstat;
82+
} catch {
83+
return null;
84+
}
85+
};
86+
const middleware = client.middleware.workspace;
87+
const result = await (middleware?.fs?.stat
88+
? middleware.fs.stat(paramsUri, fileStat)
89+
: fileStat(paramsUri));
90+
return result
91+
? this._client.code2ProtocolConverter.asFileStat(result)
92+
: null;
93+
});
94+
client.onRequest(ReadFileRequest.type, async (params) => {
95+
const paramsUri = this._client.protocol2CodeConverter.asUri(params.uri);
96+
const fileRead: FileSystemReadFileSignature = async (uri, encoding) => {
97+
try {
98+
const bytes = await vscode.workspace.fs.readFile(uri);
99+
const decoder = new TextDecoder(encoding || 'utf-8');
100+
return decoder.decode(bytes);
101+
} catch {
102+
return null;
103+
}
104+
};
105+
const middleware = client.middleware.workspace;
106+
const result = await (middleware?.fs?.readFile
107+
? middleware.fs.readFile(paramsUri, params.encoding, fileRead)
108+
: fileRead(paramsUri, params.encoding));
109+
if (result === undefined || result === null) {
110+
return null;
111+
}
112+
return { text: result };
113+
});
114+
client.onRequest(ReadDirectoryRequest.type, async (params) => {
115+
const paramsUri = this._client.protocol2CodeConverter.asUri(params.uri);
116+
const directoryRead: FileSystemReadDirectorySignature = async (uri) => {
117+
try {
118+
const entries = await vscode.workspace.fs.readDirectory(uri);
119+
return entries;
120+
} catch {
121+
return null;
122+
}
123+
};
124+
const middleware = client.middleware.workspace;
125+
const result = await (middleware?.fs?.readDirectory
126+
? middleware.fs.readDirectory(paramsUri, directoryRead)
127+
: directoryRead(paramsUri));
128+
if (result === undefined || result === null) {
129+
return null;
130+
}
131+
const entries = result.map(this._client.code2ProtocolConverter.asDirectoryEntry);
132+
return entries;
133+
});
134+
}
135+
136+
public clear(): void {
137+
}
138+
}

0 commit comments

Comments
 (0)