提交 e69fb281 编写于 作者: M Martin Aeschlimann

[html] update language server for document link support

上级 2a4e5b02
......@@ -104,7 +104,7 @@
}
},
"dependencies": {
"vscode-languageclient": "^2.4.2-next.22",
"vscode-languageclient": "^2.6.0-next.1",
"vscode-nls": "^1.0.7"
}
}
......@@ -8,7 +8,7 @@
"node": "*"
},
"dependencies": {
"vscode-languageserver": "^2.4.0-next.12",
"vscode-languageserver": "^2.6.0-next.3",
"vscode-nls": "^1.0.4",
"vscode-uri": "^0.0.7"
},
......
......@@ -27,10 +27,12 @@ let documents: TextDocuments = new TextDocuments();
// for open, change and close text document events
documents.listen(connection);
let workspacePath: string;
// After the server has started the client sends an initilize request. The server receives
// in the passed params the rootPath of the workspace plus the client capabilites
connection.onInitialize((params: InitializeParams): InitializeResult => {
workspacePath = params.rootPath;
return {
capabilities: {
// Tell the client that the server works in FULL text document sync mode
......@@ -38,7 +40,8 @@ connection.onInitialize((params: InitializeParams): InitializeResult => {
completionProvider: { resolveProvider: false, triggerCharacters: ['.', ':', '<', '"', '=', '/'] },
documentHighlightProvider: true,
documentRangeFormattingProvider: true,
documentFormattingProvider: true
documentFormattingProvider: true,
documentLinkProvider: true
}
};
});
......@@ -108,5 +111,11 @@ connection.onDocumentRangeFormatting(formatParams => {
return languageService.format(document, formatParams.range, getFormattingOptions(formatParams));
});
connection.onDocumentLinks(documentLinkParam => {
let document = documents.get(documentLinkParam.textDocument.uri);
return languageService.findDocumentLinks(document, workspacePath);
});
// Listen on the connection
connection.listen();
\ No newline at end of file
......@@ -6,27 +6,11 @@
import {parse} from './parser/htmlParser';
import {doComplete} from './services/htmlCompletion';
import {format} from './services/htmlFormatter';
import {provideLinks} from './services/htmlLinks';
import {findDocumentLinks} from './services/htmlLinks';
import {findDocumentHighlights} from './services/htmlHighlighting';
import {TextDocument, Position, CompletionItem, CompletionList, Hover, Range, SymbolInformation, Diagnostic, TextEdit, DocumentHighlight, FormattingOptions, MarkedString } from 'vscode-languageserver-types';
export {TextDocument, Position, CompletionItem, CompletionList, Hover, Range, SymbolInformation, Diagnostic, TextEdit, DocumentHighlight, FormattingOptions, MarkedString };
export class DocumentLink {
/**
* The range this link applies to.
*/
range: Range;
/**
* The uri this link points to.
*/
target: string;
}
import {TextDocument, Position, CompletionItem, CompletionList, Hover, Range, SymbolInformation, Diagnostic, TextEdit, DocumentHighlight, FormattingOptions, MarkedString, DocumentLink } from 'vscode-languageserver-types';
export {TextDocument, Position, CompletionItem, CompletionList, Hover, Range, SymbolInformation, Diagnostic, TextEdit, DocumentHighlight, FormattingOptions, MarkedString, DocumentLink };
export interface HTMLFormatConfiguration {
tabSize: number;
......@@ -52,7 +36,7 @@ export interface LanguageService {
findDocumentHighlights(document: TextDocument, position: Position, htmlDocument: HTMLDocument): DocumentHighlight[];
doComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument, options?: CompletionConfiguration): CompletionList;
format(document: TextDocument, range: Range, options: HTMLFormatConfiguration): TextEdit[];
provideLinks(document: TextDocument, workspacePath:string): DocumentLink[];
findDocumentLinks(document: TextDocument, workspacePath:string): DocumentLink[];
}
export function getLanguageService() : LanguageService {
......@@ -61,6 +45,6 @@ export function getLanguageService() : LanguageService {
doComplete,
format,
findDocumentHighlights,
provideLinks
findDocumentLinks
};
}
\ No newline at end of file
......@@ -12,15 +12,13 @@ import Uri from 'vscode-uri';
import {DocumentLink} from '../htmlLanguageService';
function _stripQuotes(url: string): string {
function stripQuotes(url: string): string {
return url
.replace(/^'([^']+)'$/,(substr, match1) => match1)
.replace(/^"([^"]+)"$/,(substr, match1) => match1);
}
export function _getWorkspaceUrl(modelAbsoluteUri: Uri, rootAbsoluteUrl: Uri, tokenContent: string): string {
tokenContent = _stripQuotes(tokenContent);
function getWorkspaceUrl(modelAbsoluteUri: Uri, rootAbsoluteUrl: Uri, tokenContent: string): string {
if (/^\s*javascript\:/i.test(tokenContent) || /^\s*\#/i.test(tokenContent)) {
return null;
}
......@@ -64,9 +62,14 @@ export function _getWorkspaceUrl(modelAbsoluteUri: Uri, rootAbsoluteUrl: Uri, to
return potentialResult;
}
function createLink(document: TextDocument, rootAbsoluteUrl: Uri, tokenContent: string, startOffset: number, endOffset: number): DocumentLink {
function createLink(document: TextDocument, rootAbsoluteUrl: Uri, attributeValue: string, startOffset: number, endOffset: number): DocumentLink {
let documentUri = Uri.parse(document.uri);
let workspaceUrl = _getWorkspaceUrl(documentUri, rootAbsoluteUrl, tokenContent);
let tokenContent = stripQuotes(attributeValue);
if (tokenContent.length < attributeValue.length) {
startOffset++;
endOffset--;
}
let workspaceUrl = getWorkspaceUrl(documentUri, rootAbsoluteUrl, tokenContent);
if (!workspaceUrl) {
return null;
}
......@@ -76,7 +79,7 @@ function createLink(document: TextDocument, rootAbsoluteUrl: Uri, tokenContent:
};
}
export function provideLinks(document: TextDocument, workspacePath:string): DocumentLink[] {
export function findDocumentLinks(document: TextDocument, workspacePath:string): DocumentLink[] {
let newLinks: DocumentLink[] = [];
let rootAbsoluteUrl: Uri = null;
......@@ -94,13 +97,13 @@ export function provideLinks(document: TextDocument, workspacePath:string): Docu
while (token !== TokenType.EOS) {
switch (token) {
case TokenType.AttributeName:
let tokenContent = scanner.getTokenText();
afterHrefOrSrc = tokenContent === 'src' || tokenContent === 'href';
let attributeName = scanner.getTokenText();
afterHrefOrSrc = attributeName === 'src' || attributeName === 'href';
break;
case TokenType.AttributeValue:
if (afterHrefOrSrc) {
let tokenContent = scanner.getTokenText();
let link = createLink(document, rootAbsoluteUrl, tokenContent, scanner.getTokenOffset(), scanner.getTokenEnd());
let attributeValue = scanner.getTokenText();
let link = createLink(document, rootAbsoluteUrl, attributeValue, scanner.getTokenOffset(), scanner.getTokenEnd());
if (link) {
newLinks.push(link);
}
......@@ -108,6 +111,7 @@ export function provideLinks(document: TextDocument, workspacePath:string): Docu
}
break;
}
token = scanner.scan();
}
return newLinks;
}
\ No newline at end of file
......@@ -67,21 +67,16 @@ let testCompletionFor = function (value: string, expected: { count?: number, ite
let document = TextDocument.create('test://test/test.html', 'html', 0, value);
let position = document.positionAt(offset);
let htmlDoc = ls.parseHTMLDocument(document);
return asPromise(ls.doComplete(document, position, htmlDoc, settings)).then(list => {
try {
if (expected.count) {
assert.equal(list.items, expected.count);
}
if (expected.items) {
for (let item of expected.items) {
assertCompletion(list, item, document, offset);
}
}
} catch (e) {
return Promise.reject(e);
let list = ls.doComplete(document, position, htmlDoc, settings);
if (expected.count) {
assert.equal(list.items, expected.count);
}
if (expected.items) {
for (let item of expected.items) {
assertCompletion(list, item, document, offset);
}
});
}
return Promise.resolve();
};
function run(tests: Thenable<void>[], testDone) {
Promise.all(tests).then(() => {
......@@ -275,12 +270,11 @@ suite('HTML Completion', () => {
], testDone);
});
suite('Handlevar Completion', (testDone) => {
test('Handlebar Completion', function (testDone) {
run([
testCompletionFor('<script id="entry-template" type="text/x-handlebars-template"> | </script>' , {
testCompletionFor('<script id="entry-template" type="text/x-handlebars-template"> <| </script>' , {
items: [
{ label: 'div', resultText: '<script id="entry-template" type="text/x-handlebars-template"> <div></div> </script>' },
{ label: 'div', resultText: '<script id="entry-template" type="text/x-handlebars-template"> <div </script>' },
]
})
], testDone);
......
......@@ -13,7 +13,6 @@ export function assertHighlights(value: string, expectedMatches: number[], eleme
value = value.substr(0, offset) + value.substr(offset + 1);
let document = TextDocument.create('test://test/test.html', 'html', 0, value);
let htmlDocument = htmlLanguageService.getLanguageService().parseHTMLDocument(document);
let position = document.positionAt(offset);
let ls = htmlLanguageService.getLanguageService();
......
......@@ -9,13 +9,23 @@ import * as assert from 'assert';
import * as htmlLinks from '../services/htmlLinks';
import {CompletionList, TextDocument, TextEdit, Position, CompletionItemKind} from 'vscode-languageserver-types';
import Uri from 'vscode-uri';
import * as htmlLanguageService from '../htmlLanguageService';
suite('HTML Link Detection', () => {
function testLinkCreation(modelUrl:string, rootUrl:string, tokenContent:string, expected:string): void {
var _modelUrl = Uri.parse(modelUrl);
var actual = htmlLinks._getWorkspaceUrl(_modelUrl, Uri.parse(rootUrl), tokenContent);
assert.equal(actual, expected);
let document = TextDocument.create(modelUrl, 'html', 0, `<a href="${tokenContent}">`);
let ls = htmlLanguageService.getLanguageService();
let links = ls.findDocumentLinks(document, rootUrl);
assert.equal(links[0] && links[0].target, expected);
}
function testLinkDetection(value:string, expectedLinkLocations:number[]): void {
let document = TextDocument.create('test://test/test.html', 'html', 0, value);
let ls = htmlLanguageService.getLanguageService();
let links = ls.findDocumentLinks(document, 'test://test');
assert.deepEqual(links.map(l => l.range.start.character), expectedLinkLocations);
}
test('Link creation', () => {
......@@ -68,4 +78,10 @@ suite('HTML Link Detection', () => {
// Bug #18314: Ctrl + Click does not open existing file if folder's name starts with 'c' character
testLinkCreation('file:///c:/Alex/working_dir/18314-link-detection/test.html', 'file:///c:/Alex/working_dir/18314-link-detection/', '/class/class.js', 'file:///c:/Alex/working_dir/18314-link-detection/class/class.js');
});
test('Link detection', () => {
testLinkDetection('<img src="foo.png">', [ 9 ]);
testLinkDetection('<a href="http://server/foo.html">', [ 8 ]);
});
});
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册