Add support for workspace read requests - #1825
Conversation
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
|
@microsoft-github-policy-service agree |
a97888f to
d0e4f81
Compare
Dirk Bäumer (dbaeumer)
left a comment
There was a problem hiding this comment.
Thanks. Very nice PR.
| /** | ||
| * Whether the file is a symbolic link. | ||
| */ | ||
| isSymlink: boolean; |
There was a problem hiding this comment.
Can we make this a flag property with a bit wise implementation. Makes it easier to expand in the future.
There was a problem hiding this comment.
I thought about combining it with the type property, similar to how its done in vscode, but decided against it. Is this what you had in mind?
There was a problem hiding this comment.
Mark Sujew (@msujew) no, I like that they are separate but I would rename isSymlink to flags and have a SymLink flag. If we have more flags in the future it is easier to extend.
There was a problem hiding this comment.
Ok, I believe that's what I've done in b356b60 already 👍
| /** | ||
| * Whether the entry is a symbolic link. | ||
| */ | ||
| isSymlink: boolean; |
|
What if we read a file that is binary? I have some library files and we need to obfuscate the source for end-users. const fileRead: FileSystemReadFileSignature = async (uri, encoding) => {
try {
const bytes = await vscode.workspace.fs.readFile(uri);
const decoder = new TextDecoder(encoding || 'utf-8');
return decoder.decode(bytes);
} catch {
return null;
}
};Does it make sense when I currently am already testing with this pull request and all works fine. But I have used middleware to overwrite the implementation. (Which is also fine for me) |
Martijn Bakker (@Bakker-Martijn) WDYT about Dirk Bäumer (@dbaeumer) do you have an opinion on this? |
|
Mark Sujew (@msujew) I had the same thought process. But this is not allowed: https://developer.mozilla.org/en-US/docs/Web/API/Encoding_API/Encodings I believe the I also notices the size increase. Which is not ideal... Just wanted to point this out. It also depends if binary files will be read by other languages servers or not (perhaps I am one of the few). Can imagine not implementing this, having the middleware as fallback is also OK for me. --Edit: |
Yes, exactly. Essentially just special casing the client code and documenting this into the protocol. I.e. servers can request binary file content via |
|
That would be perfect for my use-case. I think that is an excellent idea :) |
|
I think returning export enum ReadFileParamKind {
Text = 'text,
Binary = 'binary'
}
export interface TextReadFileParams {
kind: ReadFileParamKind.Text;
/**
* A URI for the location of the file.
*/
uri: DocumentUri;
/**
* The encoding of the file content. If not specified, the content is assumed to be UTF-8.
*/
encoding?: string;
}
export interface BinaryReadFileParams {
kind: ReadFileParamKind.Binary;
/**
* A URI for the location of the file.
*/
uri: DocumentUri;
}
export type ReadFileParams = TextReadFileParams | BinaryReadFileParams; |
Yep, much better. I've replaced the enum with string literal types, since that works better with the meta model generator script. Since there was no base64 conversion yet, I've tried my best with what was available. That should probably do. |
|
Mark Sujew (@msujew) the |
There was a problem hiding this comment.
🟡 Changes recommended
Filesystem access lacks a security boundary, and capability paths and numeric protocol types are currently inconsistent.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Implements the read-only portion of language-server-protocol#1264, allowing servers to access client-side filesystem data.
Changes:
- Adds stat, file-read, and directory-read protocol requests.
- Implements client/server feature plumbing and conversion.
- Adds testbed commands demonstrating the requests.
File summaries
| File | Description |
|---|---|
client/src/common/client.ts |
Registers filesystem support and middleware. |
client/src/common/codeConverter.ts |
Converts VS Code filesystem metadata. |
client/src/common/fileSystem.ts |
Handles filesystem requests client-side. |
protocol/metaModel.json |
Models the new protocol API. |
protocol/src/common/protocol.fileSystem.ts |
Defines filesystem requests and types. |
protocol/src/common/protocol.ts |
Exposes filesystem capabilities and types. |
server/src/common/fileSystem.ts |
Adds server-side request methods. |
server/src/common/server.ts |
Integrates filesystem methods into workspaces. |
testbed/client/src/extension.ts |
Adds demonstration commands. |
testbed/package.json |
Contributes testbed command metadata. |
testbed/server/src/server.ts |
Exercises filesystem requests. |
Review details
Suppressed comments (5)
protocol/metaModel.json:1197
- The declared capability is
workspace.fileSystem.readDirectory, notworkspace.fileOperations.readDirectory. Correcting this prevents generated consumers from checking an unadvertised field.
"clientCapability": "workspace.fileOperations.readDirectory",
protocol/metaModel.json:1222
- The declared capability is
workspace.fileSystem.readFile, notworkspace.fileOperations.readFile. Leaving this path unchanged makes generated capability detection disagree with the client implementation.
"clientCapability": "workspace.fileOperations.readFile",
protocol/src/common/protocol.fileSystem.ts:58
- Using
numberhere produces an LSPintegerin the metamodel, limiting file sizes to 2^31−1 bytes. Files larger than 2 GiB cannot be represented by the declared protocol type; use an unrestricted numeric base type such asdecimaland regenerate the metamodel.
size: number;
protocol/metaModel.json:4656
- Normal epoch-millisecond
mtimevalues exceed the LSPintegerrange. This field needs the unrestricteddecimalbase type instead.
"name": "integer"
protocol/metaModel.json:4664
- An LSP
integertops out at 2^31−1, so this model cannot describe files larger than 2 GiB even though VS Code'sFileStat.sizecan. Use thedecimalbase type to carry the full numeric value.
"name": "integer"
- Files reviewed: 11/11 changed files
- Comments generated: 10
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| const paramsUri = this._client.protocol2CodeConverter.asUri(params.uri); | ||
| const fileRead: FileSystemReadFileSignature = async (kind, uri, encoding) => { | ||
| try { | ||
| const bytes = await vscode.workspace.fs.readFile(uri); |
| FoldingRangeProviderMiddleware & DeclarationMiddleware & SelectionRangeProviderMiddleware & CallHierarchyMiddleware & SemanticTokensMiddleware & | ||
| LinkedEditingRangeMiddleware & TypeHierarchyMiddleware & InlineValueMiddleware & InlayHintsMiddleware & NotebookDocumentMiddleware & DiagnosticProviderMiddleware & | ||
| InlineCompletionMiddleware & TextDocumentContentMiddleware & GeneralMiddleware; | ||
| InlineCompletionMiddleware & TextDocumentContentMiddleware & FileSystemMiddleware & GeneralMiddleware; |
| ] | ||
| }, | ||
| "messageDirection": "serverToClient", | ||
| "clientCapability": "workspace.fileOperations.fileStat", |
| "name": "ctime", | ||
| "type": { | ||
| "kind": "base", | ||
| "name": "integer" |
| ctime: number; | ||
| /** | ||
| * The modification timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC. | ||
| */ | ||
| mtime: number; |
| export const messageDirection: MessageDirection = MessageDirection.serverToClient; | ||
| export const type = new ProtocolRequestType<StatParams, FileStat | null, never, void, void>(method); | ||
| export type HandlerSignature = RequestHandler<StatParams, FileStat | null, void>; | ||
| export const capabilities = CM.create('workspace.fileOperations.fileStat', undefined); |
| export const messageDirection: MessageDirection = MessageDirection.serverToClient; | ||
| export const type = new ProtocolRequestType<ReadDirectoryParams, DirectoryEntry[] | null, never, void, void>(method); | ||
| export type HandlerSignature = RequestHandler<ReadDirectoryParams, DirectoryEntry[] | null, void>; | ||
| export const capabilities = CM.create('workspace.fileOperations.readDirectory', undefined); |
| export const messageDirection: MessageDirection = MessageDirection.serverToClient; | ||
| export const type = new ProtocolRequestType<ReadFileParams, ReadFileResult | null, never, void, void>(method); | ||
| export type HandlerSignature = RequestHandler<ReadFileParams, ReadFileResult | null, void>; | ||
| export const capabilities = CM.create('workspace.fileOperations.readFile', undefined); |
| FileStat, StatParams, StatRequest, DirectoryEntry, FileType, FileFlags, ReadDirectoryParams, ReadDirectoryRequest, ReadFileParams, ReadFileRequest, ReadFileResult, | ||
| ReadFileParamKind, TextReadFileParams, BinaryReadFileParams, |
| return result; | ||
| } | ||
|
|
||
| function asFileType(value: code.FileType): { |
Related to microsoft/language-server-protocol#1264 (does not fully resolve it, since this PR does not include features to write into a file system - only read from it).
Adds support for the server to read files/directories/stat info from the client.
As indicated by microsoft/language-server-protocol#1264 (comment), I also thought it'd be best to start with read-only access to the file system. However, the new interfaces/types should be extendable enough to also add write requests if required later on.
Some questions/considerations:
null. Should it return a response error instead?FileSystemClientCapabilities. Does it make sense to expose each individual request type as an opt-in flag? Or is it enough to provide aread?: booleanflag (maybe extend this with awrite?: booleanflag later on)?FileType.unknownvalue results from the fact that vscode offersvscode.FileType.unknownas a possible value to return forFileStat.type. Should this be included in the protocol, or should stats/directory entries with this type simply be omitted?