mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-24 10:38:59 +01:00
[html] update language server for document link support
This commit is contained in:
@@ -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();
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 ]);
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user