Path: blob/main/src/vs/workbench/api/common/extHostFileSystem.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { URI, UriComponents } from '../../../base/common/uri.js';6import { MainContext, IMainContext, ExtHostFileSystemShape, MainThreadFileSystemShape, IFileChangeDto } from './extHost.protocol.js';7import type * as vscode from 'vscode';8import * as files from '../../../platform/files/common/files.js';9import { IDisposable, toDisposable } from '../../../base/common/lifecycle.js';10import { FileChangeType } from './extHostTypes.js';11import * as typeConverter from './extHostTypeConverters.js';12import { ExtHostLanguageFeatures } from './extHostLanguageFeatures.js';13import { State, StateMachine, LinkComputer, Edge } from '../../../editor/common/languages/linkComputer.js';14import { commonPrefixLength } from '../../../base/common/strings.js';15import { CharCode } from '../../../base/common/charCode.js';16import { VSBuffer } from '../../../base/common/buffer.js';17import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js';18import { checkProposedApiEnabled } from '../../services/extensions/common/extensions.js';19import { IMarkdownString, isMarkdownString } from '../../../base/common/htmlContent.js';2021class FsLinkProvider {2223private _schemes: string[] = [];24private _stateMachine?: StateMachine;2526add(scheme: string): void {27this._stateMachine = undefined;28this._schemes.push(scheme);29}3031delete(scheme: string): void {32const idx = this._schemes.indexOf(scheme);33if (idx >= 0) {34this._schemes.splice(idx, 1);35this._stateMachine = undefined;36}37}3839private _initStateMachine(): void {40if (!this._stateMachine) {4142// sort and compute common prefix with previous scheme43// then build state transitions based on the data44const schemes = this._schemes.sort();45const edges: Edge[] = [];46let prevScheme: string | undefined;47let prevState: State;48let lastState = State.LastKnownState;49let nextState = State.LastKnownState;50for (const scheme of schemes) {5152// skip the common prefix of the prev scheme53// and continue with its last state54let pos = !prevScheme ? 0 : commonPrefixLength(prevScheme, scheme);55if (pos === 0) {56prevState = State.Start;57} else {58prevState = nextState;59}6061for (; pos < scheme.length; pos++) {62// keep creating new (next) states until the63// end (and the BeforeColon-state) is reached64if (pos + 1 === scheme.length) {65// Save the last state here, because we need to continue for the next scheme66lastState = nextState;67nextState = State.BeforeColon;68} else {69nextState += 1;70}71edges.push([prevState, scheme.toUpperCase().charCodeAt(pos), nextState]);72edges.push([prevState, scheme.toLowerCase().charCodeAt(pos), nextState]);73prevState = nextState;74}7576prevScheme = scheme;77// Restore the last state78nextState = lastState;79}8081// all link must match this pattern `<scheme>:/<more>`82edges.push([State.BeforeColon, CharCode.Colon, State.AfterColon]);83edges.push([State.AfterColon, CharCode.Slash, State.End]);8485this._stateMachine = new StateMachine(edges);86}87}8889provideDocumentLinks(document: vscode.TextDocument): vscode.ProviderResult<vscode.DocumentLink[]> {90this._initStateMachine();9192const result: vscode.DocumentLink[] = [];93const links = LinkComputer.computeLinks({94getLineContent(lineNumber: number): string {95return document.lineAt(lineNumber - 1).text;96},97getLineCount(): number {98return document.lineCount;99}100}, this._stateMachine);101102for (const link of links) {103const docLink = typeConverter.DocumentLink.to(link);104if (docLink.target) {105result.push(docLink);106}107}108return result;109}110}111112export class ExtHostFileSystem implements ExtHostFileSystemShape {113114private readonly _proxy: MainThreadFileSystemShape;115private readonly _linkProvider = new FsLinkProvider();116private readonly _fsProvider = new Map<number, vscode.FileSystemProvider>();117private readonly _registeredSchemes = new Set<string>();118private readonly _watches = new Map<number, IDisposable>();119120private _linkProviderRegistration?: IDisposable;121private _handlePool: number = 0;122123constructor(mainContext: IMainContext, private _extHostLanguageFeatures: ExtHostLanguageFeatures) {124this._proxy = mainContext.getProxy(MainContext.MainThreadFileSystem);125}126127dispose(): void {128this._linkProviderRegistration?.dispose();129}130131registerFileSystemProvider(extension: IExtensionDescription, scheme: string, provider: vscode.FileSystemProvider, options: { isCaseSensitive?: boolean; isReadonly?: boolean | vscode.MarkdownString } = {}) {132133// validate the given provider is complete134ExtHostFileSystem._validateFileSystemProvider(provider);135136if (this._registeredSchemes.has(scheme)) {137throw new Error(`a provider for the scheme '${scheme}' is already registered`);138}139140//141if (!this._linkProviderRegistration) {142this._linkProviderRegistration = this._extHostLanguageFeatures.registerDocumentLinkProvider(extension, '*', this._linkProvider);143}144145const handle = this._handlePool++;146this._linkProvider.add(scheme);147this._registeredSchemes.add(scheme);148this._fsProvider.set(handle, provider);149150let capabilities = files.FileSystemProviderCapabilities.FileReadWrite;151if (options.isCaseSensitive) {152capabilities += files.FileSystemProviderCapabilities.PathCaseSensitive;153}154if (options.isReadonly) {155capabilities += files.FileSystemProviderCapabilities.Readonly;156}157if (typeof provider.copy === 'function') {158capabilities += files.FileSystemProviderCapabilities.FileFolderCopy;159}160if (typeof provider.open === 'function' && typeof provider.close === 'function'161&& typeof provider.read === 'function' && typeof provider.write === 'function'162) {163checkProposedApiEnabled(extension, 'fsChunks');164capabilities += files.FileSystemProviderCapabilities.FileOpenReadWriteClose;165}166167let readOnlyMessage: IMarkdownString | undefined;168if (options.isReadonly && isMarkdownString(options.isReadonly) && options.isReadonly.value !== '') {169readOnlyMessage = {170value: options.isReadonly.value,171isTrusted: options.isReadonly.isTrusted,172supportThemeIcons: options.isReadonly.supportThemeIcons,173supportHtml: options.isReadonly.supportHtml,174baseUri: options.isReadonly.baseUri,175uris: options.isReadonly.uris176};177}178179this._proxy.$registerFileSystemProvider(handle, scheme, capabilities, readOnlyMessage).catch(err => {180console.error(`FAILED to register filesystem provider of ${extension.identifier.value}-extension for the scheme ${scheme}`);181console.error(err);182});183184const subscription = provider.onDidChangeFile(event => {185const mapped: IFileChangeDto[] = [];186for (const e of event) {187const { uri: resource, type } = e;188if (resource.scheme !== scheme) {189// dropping events for wrong scheme190continue;191}192let newType: files.FileChangeType | undefined;193switch (type) {194case FileChangeType.Changed:195newType = files.FileChangeType.UPDATED;196break;197case FileChangeType.Created:198newType = files.FileChangeType.ADDED;199break;200case FileChangeType.Deleted:201newType = files.FileChangeType.DELETED;202break;203default:204throw new Error('Unknown FileChangeType');205}206mapped.push({ resource, type: newType });207}208this._proxy.$onFileSystemChange(handle, mapped);209});210211return toDisposable(() => {212subscription.dispose();213this._linkProvider.delete(scheme);214this._registeredSchemes.delete(scheme);215this._fsProvider.delete(handle);216this._proxy.$unregisterProvider(handle);217});218}219220private static _validateFileSystemProvider(provider: vscode.FileSystemProvider) {221if (!provider) {222throw new Error('MISSING provider');223}224if (typeof provider.watch !== 'function') {225throw new Error('Provider does NOT implement watch');226}227if (typeof provider.stat !== 'function') {228throw new Error('Provider does NOT implement stat');229}230if (typeof provider.readDirectory !== 'function') {231throw new Error('Provider does NOT implement readDirectory');232}233if (typeof provider.createDirectory !== 'function') {234throw new Error('Provider does NOT implement createDirectory');235}236if (typeof provider.readFile !== 'function') {237throw new Error('Provider does NOT implement readFile');238}239if (typeof provider.writeFile !== 'function') {240throw new Error('Provider does NOT implement writeFile');241}242if (typeof provider.delete !== 'function') {243throw new Error('Provider does NOT implement delete');244}245if (typeof provider.rename !== 'function') {246throw new Error('Provider does NOT implement rename');247}248}249250private static _asIStat(stat: vscode.FileStat): files.IStat {251const { type, ctime, mtime, size, permissions } = stat;252return { type, ctime, mtime, size, permissions };253}254255$stat(handle: number, resource: UriComponents): Promise<files.IStat> {256return Promise.resolve(this._getFsProvider(handle).stat(URI.revive(resource))).then(stat => ExtHostFileSystem._asIStat(stat));257}258259$readdir(handle: number, resource: UriComponents): Promise<[string, files.FileType][]> {260return Promise.resolve(this._getFsProvider(handle).readDirectory(URI.revive(resource)));261}262263$readFile(handle: number, resource: UriComponents): Promise<VSBuffer> {264return Promise.resolve(this._getFsProvider(handle).readFile(URI.revive(resource))).then(data => VSBuffer.wrap(data));265}266267$writeFile(handle: number, resource: UriComponents, content: VSBuffer, opts: files.IFileWriteOptions): Promise<void> {268return Promise.resolve(this._getFsProvider(handle).writeFile(URI.revive(resource), content.buffer, opts));269}270271$delete(handle: number, resource: UriComponents, opts: files.IFileDeleteOptions): Promise<void> {272return Promise.resolve(this._getFsProvider(handle).delete(URI.revive(resource), opts));273}274275$rename(handle: number, oldUri: UriComponents, newUri: UriComponents, opts: files.IFileOverwriteOptions): Promise<void> {276return Promise.resolve(this._getFsProvider(handle).rename(URI.revive(oldUri), URI.revive(newUri), opts));277}278279$copy(handle: number, oldUri: UriComponents, newUri: UriComponents, opts: files.IFileOverwriteOptions): Promise<void> {280const provider = this._getFsProvider(handle);281if (!provider.copy) {282throw new Error('FileSystemProvider does not implement "copy"');283}284return Promise.resolve(provider.copy(URI.revive(oldUri), URI.revive(newUri), opts));285}286287$mkdir(handle: number, resource: UriComponents): Promise<void> {288return Promise.resolve(this._getFsProvider(handle).createDirectory(URI.revive(resource)));289}290291$watch(handle: number, session: number, resource: UriComponents, opts: files.IWatchOptions): void {292const subscription = this._getFsProvider(handle).watch(URI.revive(resource), opts);293this._watches.set(session, subscription);294}295296$unwatch(_handle: number, session: number): void {297const subscription = this._watches.get(session);298if (subscription) {299subscription.dispose();300this._watches.delete(session);301}302}303304$open(handle: number, resource: UriComponents, opts: files.IFileOpenOptions): Promise<number> {305const provider = this._getFsProvider(handle);306if (!provider.open) {307throw new Error('FileSystemProvider does not implement "open"');308}309return Promise.resolve(provider.open(URI.revive(resource), opts));310}311312$close(handle: number, fd: number): Promise<void> {313const provider = this._getFsProvider(handle);314if (!provider.close) {315throw new Error('FileSystemProvider does not implement "close"');316}317return Promise.resolve(provider.close(fd));318}319320$read(handle: number, fd: number, pos: number, length: number): Promise<VSBuffer> {321const provider = this._getFsProvider(handle);322if (!provider.read) {323throw new Error('FileSystemProvider does not implement "read"');324}325const data = VSBuffer.alloc(length);326return Promise.resolve(provider.read(fd, pos, data.buffer, 0, length)).then(read => {327return data.slice(0, read); // don't send zeros328});329}330331$write(handle: number, fd: number, pos: number, data: VSBuffer): Promise<number> {332const provider = this._getFsProvider(handle);333if (!provider.write) {334throw new Error('FileSystemProvider does not implement "write"');335}336return Promise.resolve(provider.write(fd, pos, data.buffer, 0, data.byteLength));337}338339private _getFsProvider(handle: number): vscode.FileSystemProvider {340const provider = this._fsProvider.get(handle);341if (!provider) {342const err = new Error();343err.name = 'ENOPRO';344err.message = `no provider`;345throw err;346}347return provider;348}349}350351352