mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 21:09:43 +00:00
[html][css] diagnistics for embedded content
This commit is contained in:
parent
2ff7e262b6
commit
2df11a3136
5
extensions/css/server/npm-shrinkwrap.json
generated
5
extensions/css/server/npm-shrinkwrap.json
generated
|
@ -33,6 +33,11 @@
|
|||
"version": "1.0.7",
|
||||
"from": "vscode-nls@>=1.0.4 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-1.0.7.tgz"
|
||||
},
|
||||
"vscode-uri": {
|
||||
"version": "1.0.0",
|
||||
"from": "vscode-uri@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.0.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"vscode-css-languageservice": "^1.1.0",
|
||||
"vscode-languageserver": "^2.4.0-next.12"
|
||||
"vscode-languageserver": "^2.4.0-next.12",
|
||||
"vscode-uri": "^1.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"compile": "gulp compile-extension:css-server",
|
||||
|
|
|
@ -11,6 +11,8 @@ import {
|
|||
|
||||
import { getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService, Stylesheet } from 'vscode-css-languageservice';
|
||||
import { getLanguageModelCache } from './languageModelCache';
|
||||
import Uri from 'vscode-uri';
|
||||
import { isEmbeddedContentUri, getHostDocumentUri } from './embeddedContentUri';
|
||||
|
||||
namespace ColorSymbolRequest {
|
||||
export const type: RequestType<string, Range[], any> = { get method() { return 'css/colorSymbols'; } };
|
||||
|
@ -125,7 +127,9 @@ function validateTextDocument(textDocument: TextDocument): void {
|
|||
let stylesheet = stylesheets.get(textDocument);
|
||||
let diagnostics = getLanguageService(textDocument).doValidation(textDocument, stylesheet);
|
||||
// Send the computed diagnostics to VSCode.
|
||||
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
|
||||
let uri = Uri.parse(textDocument.uri);
|
||||
let diagnosticsTarget = isEmbeddedContentUri(uri) ? getHostDocumentUri(uri) : textDocument.uri;
|
||||
connection.sendDiagnostics({ uri: diagnosticsTarget, diagnostics });
|
||||
}
|
||||
|
||||
connection.onCompletion(textDocumentPosition => {
|
||||
|
|
27
extensions/css/server/src/embeddedContentUri.ts
Normal file
27
extensions/css/server/src/embeddedContentUri.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 Uri from 'vscode-uri';
|
||||
|
||||
export const EMBEDDED_CONTENT_SCHEME = 'embedded-content';
|
||||
|
||||
export function isEmbeddedContentUri(virtualDocumentUri: Uri): boolean {
|
||||
return virtualDocumentUri.scheme === EMBEDDED_CONTENT_SCHEME;
|
||||
}
|
||||
|
||||
export function getEmbeddedContentUri(parentDocumentUri: string, embeddedLanguageId: string): Uri {
|
||||
return Uri.parse(EMBEDDED_CONTENT_SCHEME + '://' + embeddedLanguageId + '/' + encodeURIComponent(parentDocumentUri) + '.' + embeddedLanguageId);
|
||||
};
|
||||
|
||||
export function getHostDocumentUri(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);
|
||||
};
|
||||
|
||||
export function getEmbeddedLanguageId(virtualDocumentUri: Uri): string {
|
||||
return virtualDocumentUri.authority;
|
||||
}
|
|
@ -5,8 +5,8 @@
|
|||
'use strict';
|
||||
|
||||
import { workspace, Uri, EventEmitter, Disposable, TextDocument } from 'vscode';
|
||||
import { LanguageClient, RequestType } from 'vscode-languageclient';
|
||||
|
||||
import { LanguageClient, RequestType, NotificationType } from 'vscode-languageclient';
|
||||
import { getEmbeddedContentUri, getEmbeddedLanguageId, getHostDocumentUri, isEmbeddedContentUri, EMBEDDED_CONTENT_SCHEME } from './embeddedContentUri';
|
||||
|
||||
interface EmbeddedContentParams {
|
||||
uri: string;
|
||||
|
@ -23,12 +23,21 @@ namespace EmbeddedContentRequest {
|
|||
}
|
||||
|
||||
export interface EmbeddedDocuments extends Disposable {
|
||||
getVirtualDocumentUri: (parentDocumentUri: string, embeddedLanguageId: string) => Uri;
|
||||
openVirtualDocument: (embeddedContentUri: Uri, expectedVersion: number) => Thenable<TextDocument>;
|
||||
getEmbeddedContentUri: (parentDocumentUri: string, embeddedLanguageId: string) => Uri;
|
||||
openEmbeddedContentDocument: (embeddedContentUri: Uri, expectedVersion: number) => Thenable<TextDocument>;
|
||||
}
|
||||
|
||||
interface EmbeddedContentChangedParams {
|
||||
uri: string;
|
||||
version: number;
|
||||
embeddedLanguageIds: string[];
|
||||
}
|
||||
|
||||
export function initializeEmbeddedContentDocuments(embeddedScheme: string, client: LanguageClient): EmbeddedDocuments {
|
||||
namespace EmbeddedContentChangedNotification {
|
||||
export const type: NotificationType<EmbeddedContentChangedParams> = { get method() { return 'embedded/contentchanged'; } };
|
||||
}
|
||||
|
||||
export function initializeEmbeddedContentDocuments(parentDocumentSelector: string[], embeddedLanguages: { [languageId: string]: boolean }, client: LanguageClient): EmbeddedDocuments {
|
||||
let toDispose: Disposable[] = [];
|
||||
|
||||
let embeddedContentChanged = new EventEmitter<Uri>();
|
||||
|
@ -38,16 +47,16 @@ export function initializeEmbeddedContentDocuments(embeddedScheme: string, clien
|
|||
|
||||
// documents are closed after a time out or when collected.
|
||||
toDispose.push(workspace.onDidCloseTextDocument(d => {
|
||||
if (d.uri.scheme === embeddedScheme) {
|
||||
if (isEmbeddedContentUri(d.uri)) {
|
||||
delete openVirtualDocuments[d.uri.toString()];
|
||||
}
|
||||
}));
|
||||
|
||||
// virtual document provider
|
||||
toDispose.push(workspace.registerTextDocumentContentProvider(embeddedScheme, {
|
||||
toDispose.push(workspace.registerTextDocumentContentProvider(EMBEDDED_CONTENT_SCHEME, {
|
||||
provideTextDocumentContent: uri => {
|
||||
if (uri.scheme === embeddedScheme) {
|
||||
let contentRequestParms = { uri: getParentDocumentUri(uri), embeddedLanguageId: getEmbeddedLanguageId(uri) };
|
||||
if (isEmbeddedContentUri(uri)) {
|
||||
let contentRequestParms = { uri: getHostDocumentUri(uri), embeddedLanguageId: getEmbeddedLanguageId(uri) };
|
||||
return client.sendRequest(EmbeddedContentRequest.type, contentRequestParms).then(content => {
|
||||
if (content) {
|
||||
openVirtualDocuments[uri.toString()] = content.version;
|
||||
|
@ -63,19 +72,16 @@ export function initializeEmbeddedContentDocuments(embeddedScheme: string, clien
|
|||
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;
|
||||
}
|
||||
// diagnostics for embedded contents
|
||||
client.onNotification(EmbeddedContentChangedNotification.type, p => {
|
||||
for (let languageId in embeddedLanguages) {
|
||||
if (p.embeddedLanguageIds.indexOf(languageId) !== -1) {
|
||||
// open the document so that validation is triggered in the embedded mode
|
||||
let virtualUri = getEmbeddedContentUri(p.uri, languageId);
|
||||
openEmbeddedContentDocument(virtualUri, p.version);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function ensureContentUpdated(virtualURI: Uri, expectedVersion: number) {
|
||||
let virtualURIString = virtualURI.toString();
|
||||
|
@ -94,7 +100,7 @@ export function initializeEmbeddedContentDocuments(embeddedScheme: string, clien
|
|||
return Promise.resolve();
|
||||
};
|
||||
|
||||
function openVirtualDocument(virtualURI: Uri, expectedVersion: number): Thenable<TextDocument> {
|
||||
function openEmbeddedContentDocument(virtualURI: Uri, expectedVersion: number): Thenable<TextDocument> {
|
||||
return ensureContentUpdated(virtualURI, expectedVersion).then(_ => {
|
||||
return workspace.openTextDocument(virtualURI).then(document => {
|
||||
if (expectedVersion === openVirtualDocuments[virtualURI.toString()]) {
|
||||
|
@ -106,8 +112,8 @@ export function initializeEmbeddedContentDocuments(embeddedScheme: string, clien
|
|||
};
|
||||
|
||||
return {
|
||||
getVirtualDocumentUri,
|
||||
openVirtualDocument,
|
||||
getEmbeddedContentUri,
|
||||
openEmbeddedContentDocument,
|
||||
dispose: Disposable.from(...toDispose).dispose
|
||||
};
|
||||
|
||||
|
|
27
extensions/html/client/src/embeddedContentUri.ts
Normal file
27
extensions/html/client/src/embeddedContentUri.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Uri } from 'vscode';
|
||||
|
||||
export const EMBEDDED_CONTENT_SCHEME = 'embedded-content';
|
||||
|
||||
export function isEmbeddedContentUri(virtualDocumentUri: Uri): boolean {
|
||||
return virtualDocumentUri.scheme === EMBEDDED_CONTENT_SCHEME;
|
||||
}
|
||||
|
||||
export function getEmbeddedContentUri(parentDocumentUri: string, embeddedLanguageId: string): Uri {
|
||||
return Uri.parse(EMBEDDED_CONTENT_SCHEME + '://' + embeddedLanguageId + '/' + encodeURIComponent(parentDocumentUri) + '.' + embeddedLanguageId);
|
||||
};
|
||||
|
||||
export function getHostDocumentUri(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);
|
||||
};
|
||||
|
||||
export function getEmbeddedLanguageId(virtualDocumentUri: Uri): string {
|
||||
return virtualDocumentUri.authority;
|
||||
}
|
|
@ -51,17 +51,17 @@ export function activate(context: ExtensionContext) {
|
|||
debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions }
|
||||
};
|
||||
|
||||
let documentSelector = ['html', 'handlebars', 'razor'];
|
||||
let embeddedLanguages = { 'css': true };
|
||||
|
||||
// Options to control the language client
|
||||
let clientOptions: LanguageClientOptions = {
|
||||
// Register the server for json documents
|
||||
documentSelector: ['html', 'handlebars', 'razor'],
|
||||
documentSelector,
|
||||
synchronize: {
|
||||
// Synchronize the setting section 'html' to the server
|
||||
configurationSection: ['html'],
|
||||
configurationSection: ['html'], // Synchronize the setting section 'html' to the server
|
||||
},
|
||||
|
||||
initializationOptions: {
|
||||
embeddedLanguages: { 'css': true },
|
||||
embeddedLanguages,
|
||||
['format.enable']: workspace.getConfiguration('html').get('format.enable')
|
||||
}
|
||||
};
|
||||
|
@ -69,14 +69,14 @@ 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 embeddedDocuments = initializeEmbeddedContentDocuments('html-embedded', client);
|
||||
let embeddedDocuments = initializeEmbeddedContentDocuments(documentSelector, embeddedLanguages, client);
|
||||
context.subscriptions.push(embeddedDocuments);
|
||||
|
||||
client.onRequest(EmbeddedCompletionRequest.type, params => {
|
||||
let position = Protocol2Code.asPosition(params.position);
|
||||
let virtualDocumentURI = embeddedDocuments.getVirtualDocumentUri(params.uri, params.embeddedLanguageId);
|
||||
let virtualDocumentURI = embeddedDocuments.getEmbeddedContentUri(params.uri, params.embeddedLanguageId);
|
||||
|
||||
return embeddedDocuments.openVirtualDocument(virtualDocumentURI, params.version).then(document => {
|
||||
return embeddedDocuments.openEmbeddedContentDocument(virtualDocumentURI, params.version).then(document => {
|
||||
if (document) {
|
||||
return commands.executeCommand<CompletionList>('vscode.executeCompletionItemProvider', virtualDocumentURI, position).then(completionList => {
|
||||
if (completionList) {
|
||||
|
@ -94,8 +94,8 @@ export function activate(context: ExtensionContext) {
|
|||
|
||||
client.onRequest(EmbeddedHoverRequest.type, params => {
|
||||
let position = Protocol2Code.asPosition(params.position);
|
||||
let virtualDocumentURI = embeddedDocuments.getVirtualDocumentUri(params.uri, params.embeddedLanguageId);
|
||||
return embeddedDocuments.openVirtualDocument(virtualDocumentURI, params.version).then(document => {
|
||||
let virtualDocumentURI = embeddedDocuments.getEmbeddedContentUri(params.uri, params.embeddedLanguageId);
|
||||
return embeddedDocuments.openEmbeddedContentDocument(virtualDocumentURI, params.version).then(document => {
|
||||
if (document) {
|
||||
return commands.executeCommand<Hover[]>('vscode.executeHoverProvider', virtualDocumentURI, position).then(hover => {
|
||||
if (hover && hover.length > 0) {
|
||||
|
|
|
@ -19,6 +19,20 @@ export function getEmbeddedLanguageAtPosition(languageService: LanguageService,
|
|||
return null;
|
||||
}
|
||||
|
||||
export function hasEmbeddedContent(languageService: LanguageService, document: TextDocument, htmlDocument: HTMLDocument, embeddedLanguages: { [languageId: string]: boolean }): string[] {
|
||||
let embeddedLanguageIds: { [languageId: string]: boolean } = {};
|
||||
function collectEmbeddedLanguages(node: Node): void {
|
||||
let c = getEmbeddedContentForNode(languageService, document, node);
|
||||
if (c && embeddedLanguages[c.languageId] && !isWhitespace(document.getText().substring(c.start, c.end))) {
|
||||
embeddedLanguageIds[c.languageId] = true;
|
||||
}
|
||||
node.children.forEach(collectEmbeddedLanguages);
|
||||
}
|
||||
|
||||
htmlDocument.roots.forEach(collectEmbeddedLanguages);
|
||||
return Object.keys(embeddedLanguageIds);
|
||||
}
|
||||
|
||||
export function getEmbeddedContent(languageService: LanguageService, document: TextDocument, htmlDocument: HTMLDocument, languageId: string): string {
|
||||
let contents = [];
|
||||
function collectEmbeddedNodes(node: Node): void {
|
||||
|
@ -104,4 +118,8 @@ function getEmbeddedContentForNode(languageService: LanguageService, document: T
|
|||
}
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
|
||||
function isWhitespace(str: string) {
|
||||
return str.match(/^\s*$/);
|
||||
}
|
|
@ -4,11 +4,14 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, FormattingOptions, RequestType, CompletionList, Position, Hover } from 'vscode-languageserver';
|
||||
import {
|
||||
createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, FormattingOptions, RequestType, NotificationType,
|
||||
CompletionList, Position, Hover
|
||||
} from 'vscode-languageserver';
|
||||
|
||||
import { HTMLDocument, getLanguageService, CompletionConfiguration, HTMLFormatConfiguration, DocumentContext } from 'vscode-html-languageservice';
|
||||
import { HTMLDocument, getLanguageService, CompletionConfiguration, HTMLFormatConfiguration, DocumentContext, TextDocument } from 'vscode-html-languageservice';
|
||||
import { getLanguageModelCache } from './languageModelCache';
|
||||
import { getEmbeddedContent, getEmbeddedLanguageAtPosition } from './embeddedSupport';
|
||||
import { getEmbeddedContent, getEmbeddedLanguageAtPosition, hasEmbeddedContent } from './embeddedSupport';
|
||||
import * as url from 'url';
|
||||
import * as path from 'path';
|
||||
import uri from 'vscode-uri';
|
||||
|
@ -52,6 +55,16 @@ namespace EmbeddedContentRequest {
|
|||
export const type: RequestType<EmbeddedContentParams, EmbeddedContent, any> = { get method() { return 'embedded/content'; } };
|
||||
}
|
||||
|
||||
interface EmbeddedContentChangedParams {
|
||||
uri: string;
|
||||
version: number;
|
||||
embeddedLanguageIds: string[];
|
||||
}
|
||||
|
||||
namespace EmbeddedContentChangedNotification {
|
||||
export const type: NotificationType<EmbeddedContentChangedParams> = { get method() { return 'embedded/contentchanged'; } };
|
||||
}
|
||||
|
||||
// Create a connection for the server
|
||||
let connection: IConnection = createConnection();
|
||||
|
||||
|
@ -115,6 +128,53 @@ connection.onDidChangeConfiguration((change) => {
|
|||
languageSettings = settings.html;
|
||||
});
|
||||
|
||||
let pendingValidationRequests: { [uri: string]: NodeJS.Timer } = {};
|
||||
const validationDelayMs = 200;
|
||||
|
||||
// The content of a text document has changed. This event is emitted
|
||||
// when the text document first opened or when its content has changed.
|
||||
documents.onDidChangeContent(change => {
|
||||
triggerValidation(change.document);
|
||||
});
|
||||
|
||||
// a document has closed: clear all diagnostics
|
||||
documents.onDidClose(event => {
|
||||
cleanPendingValidation(event.document);
|
||||
//connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] });
|
||||
if (embeddedLanguages) {
|
||||
connection.sendNotification(EmbeddedContentChangedNotification.type, { uri: event.document.uri, version: event.document.version, embeddedLanguageIds: [] });
|
||||
}
|
||||
});
|
||||
|
||||
function cleanPendingValidation(textDocument: TextDocument): void {
|
||||
let request = pendingValidationRequests[textDocument.uri];
|
||||
if (request) {
|
||||
clearTimeout(request);
|
||||
delete pendingValidationRequests[textDocument.uri];
|
||||
}
|
||||
}
|
||||
|
||||
function triggerValidation(textDocument: TextDocument): void {
|
||||
cleanPendingValidation(textDocument);
|
||||
pendingValidationRequests[textDocument.uri] = setTimeout(() => {
|
||||
delete pendingValidationRequests[textDocument.uri];
|
||||
validateTextDocument(textDocument);
|
||||
}, validationDelayMs);
|
||||
}
|
||||
|
||||
function validateTextDocument(textDocument: TextDocument): void {
|
||||
let htmlDocument = htmlDocuments.get(textDocument);
|
||||
//let diagnostics = languageService.doValidation(textDocument, htmlDocument);
|
||||
// Send the computed diagnostics to VSCode.
|
||||
//connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
|
||||
if (embeddedLanguages) {
|
||||
let embeddedLanguageIds = hasEmbeddedContent(languageService, textDocument, htmlDocument, embeddedLanguages);
|
||||
let p = { uri: textDocument.uri, version: textDocument.version, embeddedLanguageIds };
|
||||
console.log(JSON.stringify(p));
|
||||
connection.sendNotification(EmbeddedContentChangedNotification.type, p);
|
||||
}
|
||||
}
|
||||
|
||||
connection.onCompletion(textDocumentPosition => {
|
||||
let document = documents.get(textDocumentPosition.textDocument.uri);
|
||||
let htmlDocument = htmlDocuments.get(document);
|
||||
|
|
Loading…
Reference in a new issue