From b074018c3eb51f1ef5c159826ff3d43ffd1b2f0b Mon Sep 17 00:00:00 2001 From: Jan Kretschmer Date: Mon, 1 Nov 2021 14:50:32 +0100 Subject: [PATCH 1/4] sketch for virtual document support for --- .../client/src/customData.ts | 26 +++++++++++++++++-- .../client/src/htmlClient.ts | 4 ++- .../client/src/requests.ts | 20 +++++++++----- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/extensions/html-language-features/client/src/customData.ts b/extensions/html-language-features/client/src/customData.ts index ecf964056e5..cc78f809548 100644 --- a/extensions/html-language-features/client/src/customData.ts +++ b/extensions/html-language-features/client/src/customData.ts @@ -26,6 +26,13 @@ export function getCustomDataSource(toDispose: Disposable[]) { } })); + toDispose.push(workspace.onDidChangeTextDocument(e => { + const path = e.document.uri.toString(); + if (pathsInExtensions.indexOf(path) || pathsInWorkspace.indexOf(path)) { + onChange.fire(); + } + })); + return { get uris() { return pathsInWorkspace.concat(pathsInExtensions); @@ -50,7 +57,14 @@ function getCustomDataPathsInAllWorkspaces(): string[] { if (Array.isArray(paths)) { for (const path of paths) { if (typeof path === 'string') { - dataPaths.push(resolvePath(rootFolder, path).toString()); + const uri = Uri.parse(path); + if (uri.scheme === 'file') { + // only resolve file paths relative to extension + dataPaths.push(resolvePath(rootFolder, path).toString()); + } else { + // others schemes + dataPaths.push(path); + } } } } @@ -80,7 +94,15 @@ function getCustomDataPathsFromAllExtensions(): string[] { const customData = extension.packageJSON?.contributes?.html?.customData; if (Array.isArray(customData)) { for (const rp of customData) { - dataPaths.push(joinPath(extension.extensionUri, rp).toString()); + const uri = Uri.parse(rp); + if (uri.scheme === 'file') { + // only resolve file paths relative to extension + dataPaths.push(joinPath(extension.extensionUri, rp).toString()); + } else { + // others schemes + dataPaths.push(rp); + } + } } } diff --git a/extensions/html-language-features/client/src/htmlClient.ts b/extensions/html-language-features/client/src/htmlClient.ts index 42e75a297e5..3e7ca38be7e 100644 --- a/extensions/html-language-features/client/src/htmlClient.ts +++ b/extensions/html-language-features/client/src/htmlClient.ts @@ -16,7 +16,7 @@ import { DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, TextDocumentIdentifier, RequestType0, Range as LspRange, NotificationType, CommonLanguageClient } from 'vscode-languageclient'; import { activateTagClosing } from './tagClosing'; -import { RequestService } from './requests'; +import { RequestService, serveFileSystemRequests } from './requests'; import { getCustomDataSource } from './customData'; namespace CustomDataChangedNotification { @@ -120,6 +120,8 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua toDispose.push(disposable); client.onReady().then(() => { + serveFileSystemRequests(client, runtime, context.subscriptions); + client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); customDataSource.onDidChange(() => { client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); diff --git a/extensions/html-language-features/client/src/requests.ts b/extensions/html-language-features/client/src/requests.ts index f127c88562f..30c75595b7e 100644 --- a/extensions/html-language-features/client/src/requests.ts +++ b/extensions/html-language-features/client/src/requests.ts @@ -18,30 +18,36 @@ export namespace FsReadDirRequest { export const type: RequestType = new RequestType('fs/readDir'); } -export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime) { - client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string; }) => { +export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime, subscriptions: { dispose(): any }[]) { + subscriptions.push(client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string; }) => { const uri = Uri.parse(param.uri); if (uri.scheme === 'file' && runtime.fs) { return runtime.fs.getContent(param.uri); } + return workspace.fs.readFile(uri).then(buffer => { return new runtime.TextDecoder(param.encoding).decode(buffer); + }, () => { + // this path also considers TextDocumentContentProvider + return workspace.openTextDocument(uri).then(doc => { + return doc.getText(); + }); }); - }); - client.onRequest(FsReadDirRequest.type, (uriString: string) => { + })); + subscriptions.push(client.onRequest(FsReadDirRequest.type, (uriString: string) => { const uri = Uri.parse(uriString); if (uri.scheme === 'file' && runtime.fs) { return runtime.fs.readDirectory(uriString); } return workspace.fs.readDirectory(uri); - }); - client.onRequest(FsStatRequest.type, (uriString: string) => { + })); + subscriptions.push(client.onRequest(FsStatRequest.type, (uriString: string) => { const uri = Uri.parse(uriString); if (uri.scheme === 'file' && runtime.fs) { return runtime.fs.stat(uriString); } return workspace.fs.stat(uri); - }); + })); } export enum FileType { From 8779aaf2ae298cdd04565bf533eb7b3ee4008173 Mon Sep 17 00:00:00 2001 From: Jan Kretschmer Date: Sat, 20 Nov 2021 00:16:06 +0100 Subject: [PATCH 2/4] use set to store and lookup paths of interest --- .../client/src/customData.ts | 22 +++++++++---------- .../client/src/requests.ts | 19 ++++++++-------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/extensions/html-language-features/client/src/customData.ts b/extensions/html-language-features/client/src/customData.ts index cc78f809548..248ad18672c 100644 --- a/extensions/html-language-features/client/src/customData.ts +++ b/extensions/html-language-features/client/src/customData.ts @@ -14,7 +14,7 @@ export function getCustomDataSource(toDispose: Disposable[]) { toDispose.push(extensions.onDidChange(_ => { const newPathsInExtensions = getCustomDataPathsFromAllExtensions(); - if (newPathsInExtensions.length !== pathsInExtensions.length || !newPathsInExtensions.every((val, idx) => val === pathsInExtensions[idx])) { + if (pathsInExtensions.size !== newPathsInExtensions.size || ![...pathsInExtensions].every(path => newPathsInExtensions.has(path))) { pathsInExtensions = newPathsInExtensions; onChange.fire(); } @@ -28,14 +28,14 @@ export function getCustomDataSource(toDispose: Disposable[]) { toDispose.push(workspace.onDidChangeTextDocument(e => { const path = e.document.uri.toString(); - if (pathsInExtensions.indexOf(path) || pathsInWorkspace.indexOf(path)) { + if (pathsInExtensions.has(path) || pathsInWorkspace.has(path)) { onChange.fire(); } })); return { get uris() { - return pathsInWorkspace.concat(pathsInExtensions); + return [...pathsInWorkspace].concat([...pathsInExtensions]); }, get onDidChange() { return onChange.event; @@ -44,10 +44,10 @@ export function getCustomDataSource(toDispose: Disposable[]) { } -function getCustomDataPathsInAllWorkspaces(): string[] { +function getCustomDataPathsInAllWorkspaces(): Set { const workspaceFolders = workspace.workspaceFolders; - const dataPaths: string[] = []; + const dataPaths = new Set(); if (!workspaceFolders) { return dataPaths; @@ -60,10 +60,10 @@ function getCustomDataPathsInAllWorkspaces(): string[] { const uri = Uri.parse(path); if (uri.scheme === 'file') { // only resolve file paths relative to extension - dataPaths.push(resolvePath(rootFolder, path).toString()); + dataPaths.add(resolvePath(rootFolder, path).toString()); } else { // others schemes - dataPaths.push(path); + dataPaths.add(path); } } } @@ -88,8 +88,8 @@ function getCustomDataPathsInAllWorkspaces(): string[] { return dataPaths; } -function getCustomDataPathsFromAllExtensions(): string[] { - const dataPaths: string[] = []; +function getCustomDataPathsFromAllExtensions(): Set { + const dataPaths = new Set(); for (const extension of extensions.all) { const customData = extension.packageJSON?.contributes?.html?.customData; if (Array.isArray(customData)) { @@ -97,10 +97,10 @@ function getCustomDataPathsFromAllExtensions(): string[] { const uri = Uri.parse(rp); if (uri.scheme === 'file') { // only resolve file paths relative to extension - dataPaths.push(joinPath(extension.extensionUri, rp).toString()); + dataPaths.add(joinPath(extension.extensionUri, rp).toString()); } else { // others schemes - dataPaths.push(rp); + dataPaths.add(rp); } } diff --git a/extensions/html-language-features/client/src/requests.ts b/extensions/html-language-features/client/src/requests.ts index 30c75595b7e..e5bc857dabd 100644 --- a/extensions/html-language-features/client/src/requests.ts +++ b/extensions/html-language-features/client/src/requests.ts @@ -21,18 +21,19 @@ export namespace FsReadDirRequest { export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime, subscriptions: { dispose(): any }[]) { subscriptions.push(client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string; }) => { const uri = Uri.parse(param.uri); - if (uri.scheme === 'file' && runtime.fs) { - return runtime.fs.getContent(param.uri); - } - - return workspace.fs.readFile(uri).then(buffer => { - return new runtime.TextDecoder(param.encoding).decode(buffer); - }, () => { - // this path also considers TextDocumentContentProvider + if (uri.scheme === 'file') { + if (runtime.fs) { + return runtime.fs.getContent(param.uri); + } else { + return workspace.fs.readFile(uri).then(buffer => { + return new runtime.TextDecoder(param.encoding).decode(buffer); + }); + } + } else { return workspace.openTextDocument(uri).then(doc => { return doc.getText(); }); - }); + } })); subscriptions.push(client.onRequest(FsReadDirRequest.type, (uriString: string) => { const uri = Uri.parse(uriString); From bb89815cfb253af45a9cab30b9da7b89efa67ff1 Mon Sep 17 00:00:00 2001 From: Jan Kretschmer Date: Wed, 24 Nov 2021 22:07:31 +0100 Subject: [PATCH 3/4] use regex, not Uri.parse, to detect custom scheme --- .../client/src/customData.ts | 13 +++++------ .../client/src/requests.ts | 22 ++++++++++--------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/extensions/html-language-features/client/src/customData.ts b/extensions/html-language-features/client/src/customData.ts index 248ad18672c..146fef2f3ee 100644 --- a/extensions/html-language-features/client/src/customData.ts +++ b/extensions/html-language-features/client/src/customData.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { workspace, extensions, Uri, EventEmitter, Disposable } from 'vscode'; -import { resolvePath, joinPath } from './requests'; +import { resolvePath, joinPath, uriScheme } from './requests'; + export function getCustomDataSource(toDispose: Disposable[]) { let pathsInWorkspace = getCustomDataPathsInAllWorkspaces(); @@ -57,8 +58,7 @@ function getCustomDataPathsInAllWorkspaces(): Set { if (Array.isArray(paths)) { for (const path of paths) { if (typeof path === 'string') { - const uri = Uri.parse(path); - if (uri.scheme === 'file') { + if (!uriScheme.test(path)) { // only resolve file paths relative to extension dataPaths.add(resolvePath(rootFolder, path).toString()); } else { @@ -94,12 +94,11 @@ function getCustomDataPathsFromAllExtensions(): Set { const customData = extension.packageJSON?.contributes?.html?.customData; if (Array.isArray(customData)) { for (const rp of customData) { - const uri = Uri.parse(rp); - if (uri.scheme === 'file') { - // only resolve file paths relative to extension + if (!uriScheme.test(rp)) { + // no schame -> resolve relative to extension dataPaths.add(joinPath(extension.extensionUri, rp).toString()); } else { - // others schemes + // actual schemes dataPaths.add(rp); } diff --git a/extensions/html-language-features/client/src/requests.ts b/extensions/html-language-features/client/src/requests.ts index e5bc857dabd..aa75e6b615f 100644 --- a/extensions/html-language-features/client/src/requests.ts +++ b/extensions/html-language-features/client/src/requests.ts @@ -7,6 +7,8 @@ import { Uri, workspace } from 'vscode'; import { RequestType, CommonLanguageClient } from 'vscode-languageclient'; import { Runtime } from './htmlClient'; +export const uriScheme = /^(?\w[\w\d+.-]*):/; + export namespace FsContentRequest { export const type: RequestType<{ uri: string; encoding?: string; }, string, any> = new RequestType('fs/content'); } @@ -20,34 +22,34 @@ export namespace FsReadDirRequest { export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime, subscriptions: { dispose(): any }[]) { subscriptions.push(client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string; }) => { - const uri = Uri.parse(param.uri); - if (uri.scheme === 'file') { + const uri = param.uri.match(uriScheme); + if (uri?.groups?.scheme === 'file') { if (runtime.fs) { return runtime.fs.getContent(param.uri); } else { - return workspace.fs.readFile(uri).then(buffer => { + return workspace.fs.readFile(Uri.parse(param.uri)).then(buffer => { return new runtime.TextDecoder(param.encoding).decode(buffer); }); } } else { - return workspace.openTextDocument(uri).then(doc => { + return workspace.openTextDocument(Uri.parse(param.uri)).then(doc => { return doc.getText(); }); } })); subscriptions.push(client.onRequest(FsReadDirRequest.type, (uriString: string) => { - const uri = Uri.parse(uriString); - if (uri.scheme === 'file' && runtime.fs) { + const uri = uriString.match(uriScheme); + if (uri?.groups?.scheme === 'file' && runtime.fs) { return runtime.fs.readDirectory(uriString); } - return workspace.fs.readDirectory(uri); + return workspace.fs.readDirectory(Uri.parse(uriString)); })); subscriptions.push(client.onRequest(FsStatRequest.type, (uriString: string) => { - const uri = Uri.parse(uriString); - if (uri.scheme === 'file' && runtime.fs) { + const uri = uriString.match(uriScheme); + if (uri?.groups?.scheme === 'file' && runtime.fs) { return runtime.fs.stat(uriString); } - return workspace.fs.stat(uri); + return workspace.fs.stat(Uri.parse(uriString)); })); } From f1455eabedfea1ae02bd68351b1c910896551371 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 26 Nov 2021 11:45:08 +0100 Subject: [PATCH 4/4] revert request changes & polish --- .../client/src/customData.ts | 36 ++++++++------- .../client/src/htmlClient.ts | 2 +- .../client/src/requests.ts | 45 ++++++++----------- 3 files changed, 40 insertions(+), 43 deletions(-) diff --git a/extensions/html-language-features/client/src/customData.ts b/extensions/html-language-features/client/src/customData.ts index 146fef2f3ee..02c5b0d0a7d 100644 --- a/extensions/html-language-features/client/src/customData.ts +++ b/extensions/html-language-features/client/src/customData.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { workspace, extensions, Uri, EventEmitter, Disposable } from 'vscode'; -import { resolvePath, joinPath, uriScheme } from './requests'; +import { resolvePath, joinPath } from './requests'; export function getCustomDataSource(toDispose: Disposable[]) { @@ -44,6 +44,10 @@ export function getCustomDataSource(toDispose: Disposable[]) { }; } +function isURI(uriOrPath: string) { + return /^(?\w[\w\d+.-]*):/.test(uriOrPath); +} + function getCustomDataPathsInAllWorkspaces(): Set { const workspaceFolders = workspace.workspaceFolders; @@ -54,16 +58,16 @@ function getCustomDataPathsInAllWorkspaces(): Set { return dataPaths; } - const collect = (paths: string[] | undefined, rootFolder: Uri) => { - if (Array.isArray(paths)) { - for (const path of paths) { - if (typeof path === 'string') { - if (!uriScheme.test(path)) { - // only resolve file paths relative to extension - dataPaths.add(resolvePath(rootFolder, path).toString()); + const collect = (uriOrPaths: string[] | undefined, rootFolder: Uri) => { + if (Array.isArray(uriOrPaths)) { + for (const uriOrPath of uriOrPaths) { + if (typeof uriOrPath === 'string') { + if (!isURI(uriOrPath)) { + // path in the workspace + dataPaths.add(resolvePath(rootFolder, uriOrPath).toString()); } else { - // others schemes - dataPaths.add(path); + // external uri + dataPaths.add(uriOrPath); } } } @@ -93,13 +97,13 @@ function getCustomDataPathsFromAllExtensions(): Set { for (const extension of extensions.all) { const customData = extension.packageJSON?.contributes?.html?.customData; if (Array.isArray(customData)) { - for (const rp of customData) { - if (!uriScheme.test(rp)) { - // no schame -> resolve relative to extension - dataPaths.add(joinPath(extension.extensionUri, rp).toString()); + for (const uriOrPath of customData) { + if (!isURI(uriOrPath)) { + // relative path in an extension + dataPaths.add(joinPath(extension.extensionUri, uriOrPath).toString()); } else { - // actual schemes - dataPaths.add(rp); + // external uri + dataPaths.add(uriOrPath); } } diff --git a/extensions/html-language-features/client/src/htmlClient.ts b/extensions/html-language-features/client/src/htmlClient.ts index 3e7ca38be7e..bfb241c2250 100644 --- a/extensions/html-language-features/client/src/htmlClient.ts +++ b/extensions/html-language-features/client/src/htmlClient.ts @@ -120,7 +120,7 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua toDispose.push(disposable); client.onReady().then(() => { - serveFileSystemRequests(client, runtime, context.subscriptions); + toDispose.push(serveFileSystemRequests(client, runtime)); client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); customDataSource.onDidChange(() => { diff --git a/extensions/html-language-features/client/src/requests.ts b/extensions/html-language-features/client/src/requests.ts index aa75e6b615f..867ad6fc7d4 100644 --- a/extensions/html-language-features/client/src/requests.ts +++ b/extensions/html-language-features/client/src/requests.ts @@ -3,12 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Uri, workspace } from 'vscode'; +import { Uri, workspace, Disposable } from 'vscode'; import { RequestType, CommonLanguageClient } from 'vscode-languageclient'; import { Runtime } from './htmlClient'; -export const uriScheme = /^(?\w[\w\d+.-]*):/; - export namespace FsContentRequest { export const type: RequestType<{ uri: string; encoding?: string; }, string, any> = new RequestType('fs/content'); } @@ -20,37 +18,32 @@ export namespace FsReadDirRequest { export const type: RequestType = new RequestType('fs/readDir'); } -export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime, subscriptions: { dispose(): any }[]) { - subscriptions.push(client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string; }) => { - const uri = param.uri.match(uriScheme); - if (uri?.groups?.scheme === 'file') { - if (runtime.fs) { - return runtime.fs.getContent(param.uri); - } else { - return workspace.fs.readFile(Uri.parse(param.uri)).then(buffer => { - return new runtime.TextDecoder(param.encoding).decode(buffer); - }); - } - } else { - return workspace.openTextDocument(Uri.parse(param.uri)).then(doc => { - return doc.getText(); - }); +export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime): Disposable { + const disposables = []; + disposables.push(client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string; }) => { + const uri = Uri.parse(param.uri); + if (uri.scheme === 'file' && runtime.fs) { + return runtime.fs.getContent(param.uri); } + return workspace.fs.readFile(uri).then(buffer => { + return new runtime.TextDecoder(param.encoding).decode(buffer); + }); })); - subscriptions.push(client.onRequest(FsReadDirRequest.type, (uriString: string) => { - const uri = uriString.match(uriScheme); - if (uri?.groups?.scheme === 'file' && runtime.fs) { + disposables.push(client.onRequest(FsReadDirRequest.type, (uriString: string) => { + const uri = Uri.parse(uriString); + if (uri.scheme === 'file' && runtime.fs) { return runtime.fs.readDirectory(uriString); } - return workspace.fs.readDirectory(Uri.parse(uriString)); + return workspace.fs.readDirectory(uri); })); - subscriptions.push(client.onRequest(FsStatRequest.type, (uriString: string) => { - const uri = uriString.match(uriScheme); - if (uri?.groups?.scheme === 'file' && runtime.fs) { + disposables.push(client.onRequest(FsStatRequest.type, (uriString: string) => { + const uri = Uri.parse(uriString); + if (uri.scheme === 'file' && runtime.fs) { return runtime.fs.stat(uriString); } - return workspace.fs.stat(Uri.parse(uriString)); + return workspace.fs.stat(uri); })); + return Disposable.from(...disposables); } export enum FileType {