diff --git a/extensions/html/package.json b/extensions/html/package.json index bce7398ef258c8446d9bc424767e17dc80ab286a..0c1ad9ea506e1cd6e10bbe2ebb012e38022b1573 100644 --- a/extensions/html/package.json +++ b/extensions/html/package.json @@ -104,7 +104,7 @@ } }, "dependencies": { - "vscode-languageclient": "^2.4.2-next.22", + "vscode-languageclient": "^2.6.0-next.1", "vscode-nls": "^1.0.7" } } diff --git a/extensions/html/server/package.json b/extensions/html/server/package.json index f885a65adc3c3f66c3b6eb59759828688ae23c78..755a6eaadd8d406b0142d9107a30ed73771c83ce 100644 --- a/extensions/html/server/package.json +++ b/extensions/html/server/package.json @@ -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" }, diff --git a/extensions/html/server/src/htmlServerMain.ts b/extensions/html/server/src/htmlServerMain.ts index c9389ca38b1258595879f6b8da505ba6ee5783e1..a26b216278259030557072c99134308d9daf58a2 100644 --- a/extensions/html/server/src/htmlServerMain.ts +++ b/extensions/html/server/src/htmlServerMain.ts @@ -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 diff --git a/extensions/html/server/src/service/htmlLanguageService.ts b/extensions/html/server/src/service/htmlLanguageService.ts index 2a9c464bd27851a0ee38af8dd94afd0fab121077..b5244d1348d251d6fbd74f3d59babeeee8a07940 100644 --- a/extensions/html/server/src/service/htmlLanguageService.ts +++ b/extensions/html/server/src/service/htmlLanguageService.ts @@ -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 diff --git a/extensions/html/server/src/service/services/htmlLinks.ts b/extensions/html/server/src/service/services/htmlLinks.ts index 18e8c921af553355c19604acdfaffe9c13d66935..650a2296e816f8d93cefe80de0820fd900cb0313 100644 --- a/extensions/html/server/src/service/services/htmlLinks.ts +++ b/extensions/html/server/src/service/services/htmlLinks.ts @@ -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 diff --git a/extensions/html/server/src/service/test/completion.test.ts b/extensions/html/server/src/service/test/completion.test.ts index 73fb2e7804d219a138b39d5004a331d56a46fed1..52db1dddc5fba6ec932196fd3878d477813d72bb 100644 --- a/extensions/html/server/src/service/test/completion.test.ts +++ b/extensions/html/server/src/service/test/completion.test.ts @@ -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[], testDone) { Promise.all(tests).then(() => { @@ -275,12 +270,11 @@ suite('HTML Completion', () => { ], testDone); }); - suite('Handlevar Completion', (testDone) => { + test('Handlebar Completion', function (testDone) { run([ - - testCompletionFor('' , { + testCompletionFor('' , { items: [ - { label: 'div', resultText: '' }, + { label: 'div', resultText: '' }, ] }) ], testDone); diff --git a/extensions/html/server/src/service/test/highlighting.test.ts b/extensions/html/server/src/service/test/highlighting.test.ts index 0ba7d1b398f220fddec77176cfd3959f55efe94d..a8ca00452ac7816ce6e723ecd94e962f5119e838 100644 --- a/extensions/html/server/src/service/test/highlighting.test.ts +++ b/extensions/html/server/src/service/test/highlighting.test.ts @@ -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(); diff --git a/extensions/html/server/src/service/test/links.test.ts b/extensions/html/server/src/service/test/links.test.ts index 66cabef6ce022082001a28f9f008d3ac5553d604..6d7040a816df14828ffc1c72d3fc450aba0da04e 100644 --- a/extensions/html/server/src/service/test/links.test.ts +++ b/extensions/html/server/src/service/test/links.test.ts @@ -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, ``); + 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('', [ 9 ]); + testLinkDetection('', [ 8 ]); + }); + }); \ No newline at end of file