Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions client/src/common/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ import { InlineCompletionItemFeature, InlineCompletionMiddleware } from './inlin
import { TextDocumentContentFeature, type TextDocumentContentMiddleware, type TextDocumentContentProviderShape } from './textDocumentContent';
import { FileSystemWatcherFeature } from './fileSystemWatcher';
import { ProgressFeature } from './progress';
import { FileSystemFeature, FileSystemMiddleware } from './fileSystem';

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

export type WorkspaceMiddleware = _WorkspaceMiddleware & ConfigurationMiddleware & DidChangeConfigurationMiddleware & WorkspaceFolderMiddleware & FileOperationsMiddleware;
export type WorkspaceMiddleware = _WorkspaceMiddleware & ConfigurationMiddleware & DidChangeConfigurationMiddleware & WorkspaceFolderMiddleware & FileOperationsMiddleware & FileSystemMiddleware;

interface _WindowMiddleware {
showDocument?: ShowDocumentRequest.MiddlewareSignature;
Expand Down Expand Up @@ -342,7 +343,7 @@ DocumentHighlightMiddleware & DocumentSymbolMiddleware & WorkspaceSymbolMiddlewa
ColorProviderMiddleware & CodeActionMiddleware & CodeLensMiddleware & FormattingMiddleware & RenameMiddleware & DocumentLinkMiddleware & ExecuteCommandMiddleware &
FoldingRangeProviderMiddleware & DeclarationMiddleware & SelectionRangeProviderMiddleware & CallHierarchyMiddleware & SemanticTokensMiddleware &
LinkedEditingRangeMiddleware & TypeHierarchyMiddleware & InlineValueMiddleware & InlayHintsMiddleware & NotebookDocumentMiddleware & DiagnosticProviderMiddleware &
InlineCompletionMiddleware & TextDocumentContentMiddleware & GeneralMiddleware;
InlineCompletionMiddleware & TextDocumentContentMiddleware & FileSystemMiddleware & GeneralMiddleware;

export type LanguageClientOptions = {
documentSelector?: DocumentSelector | string[];
Expand Down Expand Up @@ -2085,6 +2086,7 @@ export abstract class BaseLanguageClient implements FeatureClient<Middleware, La
this.registerFeature(new NotebookDocumentSyncFeature(this));
this.registerFeature(new InlineCompletionItemFeature(this));
this.registerFeature(new TextDocumentContentFeature(this));
this.registerFeature(new FileSystemFeature(this));
}

public registerProposedFeatures() {
Expand Down
46 changes: 45 additions & 1 deletion client/src/common/codeConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ export interface Converter {

asInlineCompletionParams(document: code.TextDocument, position: code.Position, context: code.InlineCompletionContext): proto.InlineCompletionParams;
asInlineCompletionContext(context: code.InlineCompletionContext): proto.InlineCompletionContext;

asDirectoryEntry(item: [string, code.FileType]): proto.DirectoryEntry;
asFileStat(item: code.FileStat): proto.FileStat;
}

export interface URIConverter {
Expand Down Expand Up @@ -977,6 +980,45 @@ export function createConverter(uriConverter?: URIConverter): Converter {
return result;
}

function asFileType(value: code.FileType): {
type: proto.FileType;
isSymlink: boolean;
} {
let type: proto.FileType = proto.FileType.unknown;
if (value & code.FileType.Directory) {
type = proto.FileType.directory;
} else if (value & code.FileType.File) {
type = proto.FileType.file;
}
const isSymlink = (value & code.FileType.SymbolicLink) !== 0;
return {
type,
isSymlink
};
}

function asDirectoryEntry(item: [string, code.FileType]): proto.DirectoryEntry {
const { type, isSymlink } = asFileType(item[1]);
const result: proto.DirectoryEntry = {
name: item[0],
type,
isSymlink
};
return result;
}

function asFileStat(item: code.FileStat): proto.FileStat {
const { type, isSymlink } = asFileType(item.type);
const result: proto.FileStat = {
type,
isSymlink,
ctime: item.ctime,
mtime: item.mtime,
size: item.size
};
return result;
}

return {
asUri,
asTextDocumentIdentifier,
Expand Down Expand Up @@ -1031,6 +1073,8 @@ export function createConverter(uriConverter?: URIConverter): Converter {
asInlayHint,
asWorkspaceSymbol,
asInlineCompletionParams,
asInlineCompletionContext
asInlineCompletionContext,
asDirectoryEntry,
asFileStat,
};
}
138 changes: 138 additions & 0 deletions client/src/common/fileSystem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */

import * as vscode from 'vscode';

import {
ClientCapabilities, StatRequest, ReadFileRequest, ReadDirectoryRequest
} from 'vscode-languageserver-protocol';

import { StaticFeature, FeatureClient, FeatureState } from './features';

export interface FileSystemReadFileSignature {
(this: void, uri: vscode.Uri, encoding?: string): Promise<string | null>;
}

export interface FileSystemStatSignature {
(this: void, uri: vscode.Uri): Promise<vscode.FileStat | null>;
}

export interface FileSystemReadDirectorySignature {
(this: void, uri: vscode.Uri): Promise<[string, vscode.FileType][] | null>;
}

/**
* File system middleware.
*
* @since 3.19.0
*/
export interface FileSystemMiddleware {
fs?: {
stat?: (this: void, uri: vscode.Uri, next: FileSystemStatSignature) => vscode.ProviderResult<vscode.FileStat | null>;
readFile?: (this: void, uri: vscode.Uri, encoding: string | undefined, next: FileSystemReadFileSignature) => vscode.ProviderResult<string | null>;
readDirectory?: (this: void, uri: vscode.Uri, next: FileSystemReadDirectorySignature) => vscode.ProviderResult<[string, vscode.FileType][] | null>;
};
}

interface WorkspaceFileSystemMiddleware {
workspace?: FileSystemMiddleware;
}

// TextDecoder is available in all supported environments, but we can't use it directly
// because that would require us to use the dom or webworker lib, which we don't want to do.
// So we declare it here to make TypeScript happy.
declare class TextDecoder {
constructor(label?: string, options?: { fatal?: boolean; ignoreBOM?: boolean });
decode(input?: Uint8Array): string;
}

/**
* file system feature. From server to client.
*/
export class FileSystemFeature implements StaticFeature {

private readonly _client: FeatureClient<WorkspaceFileSystemMiddleware>;

constructor(client: FeatureClient<WorkspaceFileSystemMiddleware>) {
this._client = client;
}

getState(): FeatureState {
return { kind: 'static' };
}

public fillClientCapabilities(capabilities: ClientCapabilities): void {
capabilities.workspace ??= {};
capabilities.workspace.fileSystem ??= {};
capabilities.workspace.fileSystem.stat = true;
capabilities.workspace.fileSystem.readFile = true;
capabilities.workspace.fileSystem.readDirectory = true;
}

public initialize(): void {
const client = this._client;
client.onRequest(StatRequest.type, async (params) => {
const paramsUri = this._client.protocol2CodeConverter.asUri(params.uri);
const fileStat: FileSystemStatSignature = async (uri) => {
try {
const vstat = await vscode.workspace.fs.stat(uri);
return vstat;
} catch {
return null;
}
};
const middleware = client.middleware.workspace;
const result = await (middleware?.fs?.stat
? middleware.fs.stat(paramsUri, fileStat)
: fileStat(paramsUri));
return result
? this._client.code2ProtocolConverter.asFileStat(result)
: null;
});
client.onRequest(ReadFileRequest.type, async (params) => {
const paramsUri = this._client.protocol2CodeConverter.asUri(params.uri);
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;
}
};
const middleware = client.middleware.workspace;
const result = await (middleware?.fs?.readFile
? middleware.fs.readFile(paramsUri, params.encoding, fileRead)
: fileRead(paramsUri, params.encoding));
if (result === undefined || result === null) {
return null;
}
return { text: result };
});
client.onRequest(ReadDirectoryRequest.type, async (params) => {
const paramsUri = this._client.protocol2CodeConverter.asUri(params.uri);
const directoryRead: FileSystemReadDirectorySignature = async (uri) => {
try {
const entries = await vscode.workspace.fs.readDirectory(uri);
return entries;
} catch {
return null;
}
};
const middleware = client.middleware.workspace;
const result = await (middleware?.fs?.readDirectory
? middleware.fs.readDirectory(paramsUri, directoryRead)
: directoryRead(paramsUri));
if (result === undefined || result === null) {
return null;
}
const entries = result.map(this._client.code2ProtocolConverter.asDirectoryEntry);
return entries;
});
}

public clear(): void {
}
}
Loading