提交 5dfa0e03 编写于 作者: M Martin Aeschlimann

Add LanguageService as facade

上级 3c1503a2
/*---------------------------------------------------------------------------------------------
* 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 {TextDocument, Position, CompletionList, Hover, Range, SymbolInformation, Diagnostic,
Location, DocumentHighlight, CodeActionContext, Command} from 'vscode-languageserver';
import {Stylesheet} from './parser/cssNodes';
import {Parser} from './parser/cssParser';
import {CSSCompletion} from './services/cssCompletion';
import {CSSHover} from './services/cssHover';
import {CSSNavigation} from './services/cssNavigation';
import {CSSCodeActions} from './services/cssCodeActions';
import {CSSValidation} from './services/cssValidation';
export interface LanguageService {
configure(raw: LanguageSettings): void;
doValidation(document: TextDocument, stylesheet: Stylesheet): Thenable<Diagnostic[]>;
parseStylesheet(document: TextDocument): Stylesheet;
doComplete(document: TextDocument, position: Position, stylesheet: Stylesheet): Thenable<CompletionList>;
doHover(document: TextDocument, position: Position, stylesheet: Stylesheet): Thenable<Hover>;
findDefinition(document: TextDocument, position: Position, stylesheet: Stylesheet): Thenable<Location>;
findReferences(document: TextDocument, position: Position, stylesheet: Stylesheet): Thenable<Location[]>;
findDocumentHighlights(document: TextDocument, position: Position, stylesheet: Stylesheet): Thenable<DocumentHighlight[]>;
findDocumentSymbols(document: TextDocument, stylesheet: Stylesheet): Thenable<SymbolInformation[]>;
doCodeActions(document: TextDocument, range: Range, context: CodeActionContext, stylesheet: Stylesheet): Thenable<Command[]>;
findColorSymbols(document: TextDocument, stylesheet: Stylesheet): Thenable<Range[]>;
}
export interface LanguageSettings {
validate?: boolean;
lint?: any;
}
export function getCSSLanguageService() : LanguageService {
let parser = new Parser();
let cssCompletion = new CSSCompletion();
let cssHover = new CSSHover();
let cssValidation = new CSSValidation();
let cssNavigation = new CSSNavigation();
let cssCodeActions = new CSSCodeActions();
return {
configure: cssValidation.configure.bind(cssValidation),
doValidation: cssValidation.doValidation.bind(cssValidation),
parseStylesheet: parser.parseStylesheet.bind(parser),
doComplete: cssCompletion.doComplete.bind(cssCompletion),
doHover: cssHover.doHover.bind(cssHover),
findDefinition: cssNavigation.findDefinition.bind(cssNavigation),
findReferences: cssNavigation.findReferences.bind(cssNavigation),
findDocumentHighlights: cssNavigation.findDocumentHighlights.bind(cssNavigation),
findDocumentSymbols: cssNavigation.findDocumentSymbols.bind(cssNavigation),
doCodeActions: cssCodeActions.doCodeActions.bind(cssCodeActions),
findColorSymbols: cssNavigation.findColorSymbols.bind(cssNavigation)
};
}
\ No newline at end of file
......@@ -9,13 +9,7 @@ import {
TextDocuments, TextDocument, InitializeParams, InitializeResult, RequestType
} from 'vscode-languageserver';
import {Parser} from './parser/cssParser';
import {CSSCompletion} from './services/cssCompletion';
import {CSSHover} from './services/cssHover';
import {CSSNavigation} from './services/cssNavigation';
import {CSSCodeActions} from './services/cssCodeActions';
import {CSSValidation, Settings} from './services/cssValidation';
import {getCSSLanguageService, LanguageSettings, LanguageService} from './cssLanguageService';
import {Stylesheet} from './parser/cssNodes';
import * as nls from 'vscode-nls';
......@@ -25,6 +19,12 @@ namespace ColorSymbolRequest {
export const type: RequestType<string, Range[], any> = { get method() { return 'css/colorSymbols'; } };
}
export interface Settings {
css: LanguageSettings;
less: LanguageSettings;
scss: LanguageSettings;
}
// Create a connection for the server. The connection uses for
// stdin / stdout for message passing
let connection: IConnection = createConnection(new IPCMessageReader(process), new IPCMessageWriter(process));
......@@ -55,11 +55,15 @@ connection.onInitialize((params: InitializeParams): InitializeResult => {
};
});
let cssCompletion = new CSSCompletion();
let cssHover = new CSSHover();
let cssValidation = new CSSValidation();
let cssNavigation = new CSSNavigation();
let cssCodeActions = new CSSCodeActions();
let languageServices : { [id:string]: LanguageService} = {
css: getCSSLanguageService()
};
function getLanguageService(document: TextDocument) {
let service = languageServices[document.languageId];
// todo handle unknown servce
return service;
}
// The content of a text document has changed. This event is emitted
// when the text document first opened or when its content has changed.
......@@ -73,78 +77,71 @@ connection.onDidChangeConfiguration((change) => {
});
function updateConfiguration(settings: Settings) {
cssValidation.configure(settings.css);
for (let languageId in languageServices) {
languageServices[languageId].configure(settings[languageId]);
}
// Revalidate any open text documents
documents.all().forEach(validateTextDocument);
}
function validateTextDocument(textDocument: TextDocument): void {
if (textDocument.getText().length === 0) {
// ignore empty documents
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics: [] });
return;
}
let stylesheet = getStylesheet(textDocument);
cssValidation.doValidation(textDocument, stylesheet).then(diagnostics => {
getLanguageService(textDocument).doValidation(textDocument, stylesheet).then(diagnostics => {
// Send the computed diagnostics to VSCode.
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
});
}
let parser = new Parser();
function getStylesheet(document: TextDocument): Stylesheet {
return parser.parseStylesheet(document);
return getLanguageService(document).parseStylesheet(document);
}
connection.onCompletion(textDocumentPosition => {
let document = documents.get(textDocumentPosition.textDocument.uri);
let stylesheet = getStylesheet(document);
return cssCompletion.doComplete(document, textDocumentPosition.position, stylesheet);
return getLanguageService(document).doComplete(document, textDocumentPosition.position, stylesheet);
});
connection.onHover(textDocumentPosition => {
let document = documents.get(textDocumentPosition.textDocument.uri);
let styleSheet = getStylesheet(document);
return cssHover.doHover(document, textDocumentPosition.position, styleSheet);
return getLanguageService(document).doHover(document, textDocumentPosition.position, styleSheet);
});
connection.onDocumentSymbol(documentSymbolParams => {
let document = documents.get(documentSymbolParams.textDocument.uri);
let stylesheet = getStylesheet(document);
return cssNavigation.findDocumentSymbols(document, stylesheet);
return getLanguageService(document).findDocumentSymbols(document, stylesheet);
});
connection.onDefinition(documentSymbolParams => {
let document = documents.get(documentSymbolParams.textDocument.uri);
let stylesheet = getStylesheet(document);
return cssNavigation.findDefinition(document, documentSymbolParams.position, stylesheet);
return getLanguageService(document).findDefinition(document, documentSymbolParams.position, stylesheet);
});
connection.onDocumentHighlight(documentSymbolParams => {
let document = documents.get(documentSymbolParams.textDocument.uri);
let stylesheet = getStylesheet(document);
return cssNavigation.findDocumentHighlights(document, documentSymbolParams.position, stylesheet);
return getLanguageService(document).findDocumentHighlights(document, documentSymbolParams.position, stylesheet);
});
connection.onReferences(referenceParams => {
let document = documents.get(referenceParams.textDocument.uri);
let stylesheet = getStylesheet(document);
return cssNavigation.findReferences(document, referenceParams.position, stylesheet);
return getLanguageService(document).findReferences(document, referenceParams.position, stylesheet);
});
connection.onCodeAction(codeActionParams => {
let document = documents.get(codeActionParams.textDocument.uri);
let stylesheet = getStylesheet(document);
return cssCodeActions.doCodeActions(document, codeActionParams.range, codeActionParams.context, stylesheet);
return getLanguageService(document).doCodeActions(document, codeActionParams.range, codeActionParams.context, stylesheet);
});
connection.onRequest(ColorSymbolRequest.type, uri => {
let document = documents.get(uri);
let stylesheet = getStylesheet(document);
return cssNavigation.findColorSymbols(document, stylesheet);
return getLanguageService(document).findColorSymbols(document, stylesheet);
});
// Listen on the connection
......
......@@ -32,7 +32,7 @@ export class CSSCompletion {
}
public doComplete(document: TextDocument, position: Position, styleSheet: nodes.Stylesheet): CompletionList {
public doComplete(document: TextDocument, position: Position, styleSheet: nodes.Stylesheet): Thenable<CompletionList> {
this.offset = document.offsetAt(position);
this.position = position;
this.currentWord = getCurrentWord(document, this.offset);
......@@ -67,23 +67,23 @@ export class CSSCompletion {
this.getCompletionsForFunctionArgument(null, <nodes.Function>node, result);
}
if (result.items.length > 0) {
return result;
return Promise.resolve(result);
}
}
this.getCompletionsForStylesheet(result);
if (result.items.length > 0) {
return result;
return Promise.resolve(result);
}
if (this.variablePrefix && this.currentWord.indexOf(this.variablePrefix) === 0) {
this.getVariableProposals(result);
if (result.items.length > 0) {
return result;
return Promise.resolve(result);
}
}
// no match, don't show text matches
return result;
return Promise.resolve(result);
}
public getCompletionsForDeclarationProperty(result: CompletionList): CompletionList {
......
......@@ -40,7 +40,7 @@ export class CSSHover {
if (node instanceof nodes.Declaration) {
let propertyName = node.getFullPropertyName();
let entry = languageFacts.getProperties()[propertyName];
if (entry) {
if (entry && entry.description) {
return Promise.resolve({
contents: entry.description,
range: getRange(node)
......
......@@ -14,7 +14,7 @@ const localize = nls.loadMessageBundle();
export class CSSNavigation {
public findDefinition(document: TextDocument, position: Position, stylesheet: nodes.Node): Location {
public findDefinition(document: TextDocument, position: Position, stylesheet: nodes.Node): Thenable<Location> {
let symbols = new Symbols(stylesheet);
let offset = document.offsetAt(position);
......@@ -29,35 +29,35 @@ export class CSSNavigation {
return null;
}
return {
return Promise.resolve({
uri: document.uri,
range: getRange(node, document)
};
range: getRange(symbol.node, document)
});
}
public findReferences(document: TextDocument, position: Position, stylesheet: nodes.Node): Location[] {
return this.findDocumentHighlights(document, position, stylesheet).map(h => {
public findReferences(document: TextDocument, position: Position, stylesheet: nodes.Stylesheet): Thenable<Location[]> {
return this.findDocumentHighlights(document, position, stylesheet).then(highlights => highlights.map(h => {
return {
uri: document.uri,
range: h.range
};
});
}));
}
public findDocumentHighlights(document: TextDocument, position: Position, stylesheet: nodes.Node): DocumentHighlight[] {
public findDocumentHighlights(document: TextDocument, position: Position, stylesheet: nodes.Stylesheet): Thenable<DocumentHighlight[]> {
let result: DocumentHighlight[] = [];
let offset = document.offsetAt(position);
let node = nodes.getNodeAtOffset(stylesheet, offset);
if (!node || node.type === nodes.NodeType.Stylesheet || node.type === nodes.NodeType.Declarations) {
return result;
return Promise.resolve(result);
}
let symbols = new Symbols(stylesheet);
let symbol = symbols.findSymbolFromNode(node);
let name = node.getText();
stylesheet.accept((candidate) => {
stylesheet.accept(candidate => {
if (symbol) {
if (symbols.matchesSymbol(candidate, symbol)) {
result.push({
......@@ -76,12 +76,10 @@ export class CSSNavigation {
return true;
});
return result;
return Promise.resolve(result);
}
public findDocumentSymbols(document: TextDocument, stylesheet: nodes.Stylesheet): SymbolInformation[] {
public findDocumentSymbols(document: TextDocument, stylesheet: nodes.Stylesheet): Thenable<SymbolInformation[]> {
let result: SymbolInformation[] = [];
......@@ -118,10 +116,10 @@ export class CSSNavigation {
return true;
});
return result;
return Promise.resolve(result);
}
public findColorSymbols(document: TextDocument, stylesheet: nodes.Stylesheet): Range[] {
public findColorSymbols(document: TextDocument, stylesheet: nodes.Stylesheet): Thenable<Range[]> {
let result: Range[] = [];
stylesheet.accept((node) => {
if (isColorValue(node)) {
......@@ -129,7 +127,7 @@ export class CSSNavigation {
}
return true;
});
return result;
return Promise.resolve(result);
}
}
......
......@@ -8,18 +8,7 @@ import * as nodes from '../parser/cssNodes';
import {TextDocument, Range, Diagnostic, DiagnosticSeverity} from 'vscode-languageserver';
import {ILintConfigurationSettings, sanitize} from './lintRules';
import {LintVisitor} from './lint';
export interface LanguageSettings {
validate?: boolean;
lint?: ILintConfigurationSettings;
}
// The settings interface describe the server relevant settings part
export interface Settings {
css: LanguageSettings;
less: LanguageSettings;
scss: LanguageSettings;
}
import {LanguageSettings} from '../cssLanguageService';
export class CSSValidation {
......
......@@ -36,7 +36,7 @@ suite('CSS - Completion', () => {
}
};
let testCompletionFor = function (value: string, stringBefore: string, expected: { count?: number, items?: ItemDescription[] }): Thenable<CompletionList> {
let testCompletionFor = function (value: string, stringBefore: string, expected: { count?: number, items?: ItemDescription[] }): Thenable<void> {
let idx = stringBefore ? value.indexOf(stringBefore) + stringBefore.length : 0;
let completionProvider = new CSSCompletion();
......@@ -44,16 +44,16 @@ suite('CSS - Completion', () => {
let document = TextDocument.create('test://test/test.css', 'css', 0, value);
let position = Position.create(0, idx);
let jsonDoc = new Parser().parseStylesheet(document);
let list = completionProvider.doComplete(document, position, jsonDoc);
if (expected.count) {
assert.equal(list.items, expected.count);
}
if (expected.items) {
for (let item of expected.items) {
assertCompletion(list, item, document);
return completionProvider.doComplete(document, position, jsonDoc).then(list => {
if (expected.count) {
assert.equal(list.items, expected.count);
}
}
return Promise.resolve(null);
if (expected.items) {
for (let item of expected.items) {
assertCompletion(list, item, document);
}
}
});
};
test('sylesheet', function (testDone): any {
......
......@@ -17,7 +17,7 @@ export function assertScopesAndSymbols(p: Parser, input: string, expected: strin
assert.equal(scopeToString(global), expected);
}
export function assertHighlights(p: Parser, input: string, marker: string, expectedMatches: number, expectedWrites: number): void {
export function assertHighlights(p: Parser, input: string, marker: string, expectedMatches: number, expectedWrites: number): Thenable<void> {
let document = TextDocument.create('test://test/test.css', 'css', 0, input);
let stylesheet = p.parseStylesheet(document);
......@@ -26,19 +26,20 @@ export function assertHighlights(p: Parser, input: string, marker: string, expec
let index = input.indexOf(marker) + marker.length;
let position = document.positionAt(index);
let highlights = new CSSNavigation().findDocumentHighlights(document, position, stylesheet);
assert.equal(highlights.length, expectedMatches);
let nWrites = 0;
for (let highlight of highlights) {
if (highlight.kind === DocumentHighlightKind.Write) {
nWrites++;
return new CSSNavigation().findDocumentHighlights(document, position, stylesheet).then(highlights => {
assert.equal(highlights.length, expectedMatches);
let nWrites = 0;
for (let highlight of highlights) {
if (highlight.kind === DocumentHighlightKind.Write) {
nWrites++;
}
let range = highlight.range;
let start = document.offsetAt(range.start), end = document.offsetAt(range.end);
assert.equal(document.getText().substring(start, end), marker);
}
let range = highlight.range;
let start = document.offsetAt(range.start), end = document.offsetAt(range.end);
assert.equal(document.getText().substring(start, end), marker);
}
assert.equal(nWrites, expectedWrites);
assert.equal(nWrites, expectedWrites);
});
}
......@@ -169,10 +170,12 @@ suite('CSS - Symbols', () => {
assertScopesAndSymbols(p, '@font-face { font-family: "Bitstream Vera Serif Bold"; }', '[]');
});
test('mark occurrences', function () {
test('mark occurrences', function (testDone) {
let p = new Parser();
assertHighlights(p, '@keyframes id {}; #main { animation: id 4s linear 0s infinite alternate; }', 'id', 2, 1);
assertHighlights(p, '@keyframes id {}; #main { animation-name: id; foo: id;}', 'id', 2, 1);
Promise.all([
assertHighlights(p, '@keyframes id {}; #main { animation: id 4s linear 0s infinite alternate; }', 'id', 2, 1),
assertHighlights(p, '@keyframes id {}; #main { animation-name: id; foo: id;}', 'id', 2, 1)
]).then(() => testDone(), (error) => testDone(error));
});
test('test variables in root scope', function () {
......@@ -195,33 +198,45 @@ suite('CSS - Symbols', () => {
assertSymbolsInScope(p, '.a{ --var1: abc; } .b{ --var2: abc; } :root{ --var3: abc;}', 2, { name: '--var1', type: nodes.ReferenceType.Variable }, { name: '--var2', type: nodes.ReferenceType.Variable }, { name: '--var3', type: nodes.ReferenceType.Variable });
});
test('mark occurrences for variable defined in root and used in a rule', function () {
test('mark occurrences for variable defined in root and used in a rule', function (testDone) {
let p = new Parser();
assertHighlights(p, '.a{ background: let(--var1); } :root{ --var1: abc;}', '--var1', 2, 1);
Promise.all([
assertHighlights(p, '.a{ background: let(--var1); } :root{ --var1: abc;}', '--var1', 2, 1)
]).then(() => testDone(), (error) => testDone(error));
});
test('mark occurrences for variable defined in a rule and used in a different rule', function () {
test('mark occurrences for variable defined in a rule and used in a different rule', function (testDone) {
let p = new Parser();
assertHighlights(p, '.a{ background: let(--var1); } :b{ --var1: abc;}', '--var1', 2, 1);
Promise.all([
assertHighlights(p, '.a{ background: let(--var1); } :b{ --var1: abc;}', '--var1', 2, 1)
]).then(() => testDone(), (error) => testDone(error));
});
test('mark occurrences for property', function () {
test('mark occurrences for property', function (testDone) {
let p = new Parser();
assertHighlights(p, 'body { display: inline } #foo { display: inline }', 'display', 2, 0);
Promise.all([
assertHighlights(p, 'body { display: inline } #foo { display: inline }', 'display', 2, 0)
]).then(() => testDone(), (error) => testDone(error));
});
test('mark occurrences for value', function () {
test('mark occurrences for value', function (testDone) {
let p = new Parser();
assertHighlights(p, 'body { display: inline } #foo { display: inline }', 'inline', 2, 0);
Promise.all([
assertHighlights(p, 'body { display: inline } #foo { display: inline }', 'inline', 2, 0)
]).then(() => testDone(), (error) => testDone(error));
});
test('mark occurrences for selector', function () {
test('mark occurrences for selector', function (testDone) {
let p = new Parser();
assertHighlights(p, 'body { display: inline } #foo { display: inline }', 'body', 1, 1);
Promise.all([
assertHighlights(p, 'body { display: inline } #foo { display: inline }', 'body', 1, 1)
]).then(() => testDone(), (error) => testDone(error));
});
test('mark occurrences for comment', function () {
test('mark occurrences for comment', function (testDone) {
let p = new Parser();
assertHighlights(p, '/* comment */body { display: inline } ', 'comment', 0, 0);
Promise.all([
assertHighlights(p, '/* comment */body { display: inline } ', 'comment', 0, 0)
]).then(() => testDone(), (error) => testDone(error));
});
});
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册