mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 09:08:48 +01:00
[html] embedded content documents updating, refactoring
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { workspace, Uri, EventEmitter, Disposable, TextDocument } from 'vscode';
|
||||
import { LanguageClient, RequestType } from 'vscode-languageclient';
|
||||
|
||||
|
||||
interface EmbeddedContentParams {
|
||||
uri: string;
|
||||
embeddedLanguageId: string;
|
||||
}
|
||||
|
||||
interface EmbeddedContent {
|
||||
content: string;
|
||||
version: number;
|
||||
}
|
||||
|
||||
namespace EmbeddedContentRequest {
|
||||
export const type: RequestType<EmbeddedContentParams, EmbeddedContent, any> = { get method() { return 'embedded/content'; } };
|
||||
}
|
||||
|
||||
export interface EmbeddedDocuments extends Disposable {
|
||||
getVirtualDocumentUri: (parentDocumentUri: string, embeddedLanguageId: string) => Uri;
|
||||
openVirtualDocument: (embeddedContentUri: Uri, expectedVersion: number) => Thenable<TextDocument>;
|
||||
}
|
||||
|
||||
|
||||
export function initializeEmbeddedContentDocuments(embeddedScheme: string, client: LanguageClient): EmbeddedDocuments {
|
||||
let toDispose: Disposable[] = [];
|
||||
|
||||
let embeddedContentChanged = new EventEmitter<Uri>();
|
||||
|
||||
// remember all open virtual documents with the version of the content
|
||||
let openVirtualDocuments: { [uri: string]: number } = {};
|
||||
|
||||
// documents are closed after a time out or when collected.
|
||||
toDispose.push(workspace.onDidCloseTextDocument(d => {
|
||||
if (d.uri.scheme === embeddedScheme) {
|
||||
delete openVirtualDocuments[d.uri.toString()];
|
||||
}
|
||||
}));
|
||||
|
||||
// virtual document provider
|
||||
toDispose.push(workspace.registerTextDocumentContentProvider(embeddedScheme, {
|
||||
provideTextDocumentContent: uri => {
|
||||
if (uri.scheme === embeddedScheme) {
|
||||
let contentRequestParms = { uri: getParentDocumentUri(uri), embeddedLanguageId: getEmbeddedLanguageId(uri) };
|
||||
return client.sendRequest(EmbeddedContentRequest.type, contentRequestParms).then(content => {
|
||||
if (content) {
|
||||
openVirtualDocuments[uri.toString()] = content.version;
|
||||
return content.content;
|
||||
} else {
|
||||
delete openVirtualDocuments[uri.toString()];
|
||||
return '';
|
||||
}
|
||||
});
|
||||
}
|
||||
return '';
|
||||
},
|
||||
onDidChange: embeddedContentChanged.event
|
||||
}));
|
||||
|
||||
function getVirtualDocumentUri(parentDocumentUri: string, embeddedLanguageId: string) {
|
||||
return Uri.parse(embeddedScheme + '://' + embeddedLanguageId + '/' + encodeURIComponent(parentDocumentUri) + '.' + embeddedLanguageId);
|
||||
};
|
||||
|
||||
function getParentDocumentUri(virtualDocumentUri: Uri): string {
|
||||
let languageId = virtualDocumentUri.authority;
|
||||
let path = virtualDocumentUri.path.substring(1, virtualDocumentUri.path.length - languageId.length - 1); // remove leading '/' and new file extension
|
||||
return decodeURIComponent(path);
|
||||
};
|
||||
|
||||
function getEmbeddedLanguageId(virtualDocumentUri: Uri): string {
|
||||
return virtualDocumentUri.authority;
|
||||
}
|
||||
|
||||
function ensureContentUpdated(virtualURI: Uri, expectedVersion: number) {
|
||||
let virtualURIString = virtualURI.toString();
|
||||
let virtualDocVersion = openVirtualDocuments[virtualURIString];
|
||||
if (isDefined(virtualDocVersion) && virtualDocVersion !== expectedVersion) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let subscription = workspace.onDidChangeTextDocument(d => {
|
||||
if (d.document.uri.toString() === virtualURIString) {
|
||||
subscription.dispose();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
embeddedContentChanged.fire(virtualURI);
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
function openVirtualDocument(virtualURI: Uri, expectedVersion: number): Thenable<TextDocument> {
|
||||
return ensureContentUpdated(virtualURI, expectedVersion).then(_ => {
|
||||
return workspace.openTextDocument(virtualURI).then(document => {
|
||||
if (expectedVersion === openVirtualDocuments[virtualURI.toString()]) {
|
||||
return document;
|
||||
}
|
||||
return void 0;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
getVirtualDocumentUri,
|
||||
openVirtualDocument,
|
||||
dispose: Disposable.from(...toDispose).dispose
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
function isDefined(o: any) {
|
||||
return typeof o !== 'undefined';
|
||||
}
|
||||
@@ -6,16 +6,18 @@
|
||||
|
||||
import * as path from 'path';
|
||||
|
||||
import { languages, workspace, ExtensionContext, IndentAction, commands, Uri, CompletionList, EventEmitter } from 'vscode';
|
||||
import { languages, workspace, ExtensionContext, IndentAction, commands, CompletionList } from 'vscode';
|
||||
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, Position, RequestType, Protocol2Code, Code2Protocol } from 'vscode-languageclient';
|
||||
import { CompletionList as LSCompletionList } from 'vscode-languageserver-types';
|
||||
import { EMPTY_ELEMENTS } from './htmlEmptyTagsShared';
|
||||
import { initializeEmbeddedContentDocuments } from './embeddedContentDocuments';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
let localize = nls.loadMessageBundle();
|
||||
|
||||
interface EmbeddedCompletionParams {
|
||||
uri: string;
|
||||
version: number;
|
||||
embeddedLanguageId: string;
|
||||
position: Position;
|
||||
}
|
||||
@@ -24,15 +26,6 @@ namespace EmbeddedCompletionRequest {
|
||||
export const type: RequestType<EmbeddedCompletionParams, LSCompletionList, any> = { get method() { return 'embedded/completion'; } };
|
||||
}
|
||||
|
||||
interface EmbeddedContentParams {
|
||||
uri: string;
|
||||
embeddedLanguageId: string;
|
||||
}
|
||||
|
||||
namespace EmbeddedContentRequest {
|
||||
export const type: RequestType<EmbeddedContentParams, string, any> = { get method() { return 'embedded/content'; } };
|
||||
}
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
|
||||
// The server is implemented in node
|
||||
@@ -63,50 +56,36 @@ export function activate(context: ExtensionContext) {
|
||||
// Create the language client and start the client.
|
||||
let client = new LanguageClient('html', localize('htmlserver.name', 'HTML Language Server'), serverOptions, clientOptions);
|
||||
|
||||
let embeddedContentChanged = new EventEmitter<Uri>();
|
||||
let embeddedDocuments = initializeEmbeddedContentDocuments('html-embedded', client);
|
||||
context.subscriptions.push(embeddedDocuments);
|
||||
|
||||
client.onRequest(EmbeddedCompletionRequest.type, params => {
|
||||
let position = Protocol2Code.asPosition(params.position);
|
||||
let virtualURI = Uri.parse('html-embedded://' + params.embeddedLanguageId + '/' + encodeURIComponent(params.uri) + '.' + params.embeddedLanguageId);
|
||||
let virtualDocumentURI = embeddedDocuments.getVirtualDocumentUri(params.uri, params.embeddedLanguageId);
|
||||
|
||||
embeddedContentChanged.fire(virtualURI);
|
||||
return workspace.openTextDocument(virtualURI).then(_ => {
|
||||
return commands.executeCommand<CompletionList>('vscode.executeCompletionItemProvider', virtualURI, position).then(completionList => {
|
||||
if (completionList) {
|
||||
return {
|
||||
isIncomplete: completionList.isIncomplete,
|
||||
items: completionList.items.map(Code2Protocol.asCompletionItem)
|
||||
};
|
||||
}
|
||||
return { isIncomplete: true, items: [] };
|
||||
}, error => {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}, error => {
|
||||
return Promise.reject(error);
|
||||
return embeddedDocuments.openVirtualDocument(virtualDocumentURI, params.version).then(document => {
|
||||
if (document) {
|
||||
return commands.executeCommand<CompletionList>('vscode.executeCompletionItemProvider', virtualDocumentURI, position).then(completionList => {
|
||||
if (completionList) {
|
||||
return {
|
||||
isIncomplete: completionList.isIncomplete,
|
||||
items: completionList.items.map(Code2Protocol.asCompletionItem)
|
||||
};
|
||||
}
|
||||
return { isIncomplete: true, items: [] };
|
||||
});
|
||||
}
|
||||
return { isIncomplete: true, items: [] };
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
let disposable = client.start();
|
||||
|
||||
// Push the disposable to the context's subscriptions so that the
|
||||
// client can be deactivated on extension deactivation
|
||||
context.subscriptions.push(disposable);
|
||||
|
||||
|
||||
context.subscriptions.push(workspace.registerTextDocumentContentProvider('html-embedded', {
|
||||
provideTextDocumentContent: (uri, ct) => {
|
||||
if (uri.scheme === 'html-embedded') {
|
||||
let languageId = uri.authority;
|
||||
let path = uri.path.substring(1, uri.path.length - languageId.length - 1); // remove leading '/' and new file extension
|
||||
let documentURI = decodeURIComponent(path);
|
||||
return client.sendRequest(EmbeddedContentRequest.type, { uri: documentURI, embeddedLanguageId: languageId });
|
||||
}
|
||||
return '';
|
||||
},
|
||||
onDidChange: embeddedContentChanged.event
|
||||
}));
|
||||
|
||||
languages.setLanguageConfiguration('html', {
|
||||
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\$\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\s]+)/g,
|
||||
onEnterRules: [
|
||||
|
||||
@@ -18,6 +18,7 @@ nls.config(process.env['VSCODE_NLS_CONFIG']);
|
||||
|
||||
interface EmbeddedCompletionParams {
|
||||
uri: string;
|
||||
version: number;
|
||||
embeddedLanguageId: string;
|
||||
position: Position;
|
||||
}
|
||||
@@ -31,8 +32,13 @@ interface EmbeddedContentParams {
|
||||
embeddedLanguageId: string;
|
||||
}
|
||||
|
||||
interface EmbeddedContent {
|
||||
content: string;
|
||||
version: number;
|
||||
}
|
||||
|
||||
namespace EmbeddedContentRequest {
|
||||
export const type: RequestType<EmbeddedContentParams, string, any> = { get method() { return 'embedded/content'; } };
|
||||
export const type: RequestType<EmbeddedContentParams, EmbeddedContent, any> = { get method() { return 'embedded/content'; } };
|
||||
}
|
||||
|
||||
// Create a connection for the server
|
||||
@@ -103,7 +109,7 @@ connection.onCompletion(textDocumentPosition => {
|
||||
if (list.items.length === 0) {
|
||||
let embeddedLanguageId = getEmbeddedLanguageAtPosition(languageService, document, htmlDocument, textDocumentPosition.position);
|
||||
if (embeddedLanguageId) {
|
||||
return connection.sendRequest(EmbeddedCompletionRequest.type, { uri: document.uri, embeddedLanguageId, position: textDocumentPosition.position });
|
||||
return connection.sendRequest(EmbeddedCompletionRequest.type, { uri: document.uri, version: document.version, embeddedLanguageId, position: textDocumentPosition.position });
|
||||
}
|
||||
}
|
||||
return list;
|
||||
@@ -113,7 +119,7 @@ connection.onRequest(EmbeddedContentRequest.type, parms => {
|
||||
let document = documents.get(parms.uri);
|
||||
if (document) {
|
||||
let htmlDocument = htmlDocuments.get(document);
|
||||
return getEmbeddedContent(languageService, document, htmlDocument, parms.embeddedLanguageId);
|
||||
return { content: getEmbeddedContent(languageService, document, htmlDocument, parms.embeddedLanguageId), version: document.version };
|
||||
}
|
||||
return void 0;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user