diff --git a/extensions/html-language-features/server/src/htmlServerMain.ts b/extensions/html-language-features/server/src/htmlServerMain.ts index e22ad15c346..8afdc4d3222 100644 --- a/extensions/html-language-features/server/src/htmlServerMain.ts +++ b/extensions/html-language-features/server/src/htmlServerMain.ts @@ -19,7 +19,6 @@ import { getDocumentContext } from './utils/documentContext'; import uri from 'vscode-uri'; import { formatError, runSafe, runSafeAsync } from './utils/runner'; import { doComplete as emmetDoComplete, updateExtensionsPath as updateEmmetExtensionsPath, getEmmetCompletionParticipants } from 'vscode-emmet-helper'; -import { getPathCompletionParticipant } from './modes/pathCompletion'; import { FoldingRangesRequest, FoldingProviderServerCapabilities } from './protocol/foldingProvider.proposed'; import { getFoldingRegions } from './modes/htmlFolding'; @@ -95,7 +94,11 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { } } - languageModes = getLanguageModes(initializationOptions ? initializationOptions.embeddedLanguages : { css: true, javascript: true }); + const workspace = { + get settings() { return globalSettings; }, + get folders() { return workspaceFolders; } + }; + languageModes = getLanguageModes(initializationOptions ? initializationOptions.embeddedLanguages : { css: true, javascript: true }, workspace); documents.onDidClose(e => { languageModes.onDocumentRemoved(e.document); }); @@ -149,6 +152,7 @@ connection.onInitialized((p) => { } } workspaceFolders = updatedFolders.concat(toAdd); + documents.all().forEach(triggerValidation); }); } }); @@ -158,13 +162,7 @@ let formatterRegistration: Thenable | null = null; // The settings have changed. Is send on server activation as well. connection.onDidChangeConfiguration((change) => { globalSettings = change.settings; - documentSettings = {}; // reset all document settings - languageModes.getAllModes().forEach(m => { - if (m.configure) { - m.configure(change.settings); - } - }); documents.all().forEach(triggerValidation); // dynamically enable & disable the formatter @@ -288,24 +286,12 @@ connection.onCompletion(async (textDocumentPosition, token) => { cachedCompletionList = null; const emmetCompletionList = CompletionList.create([], false); - const pathCompletionList = CompletionList.create([], false); const emmetCompletionParticipant = getEmmetCompletionParticipants(document, textDocumentPosition.position, mode.getId(), emmetSettings, emmetCompletionList); const completionParticipants = [emmetCompletionParticipant]; - // Ideally, fix this in the Language Service side - // Check participants' methods before calling them - if (mode.getId() === 'html') { - const pathCompletionParticipant = getPathCompletionParticipant(document, workspaceFolders, pathCompletionList); - completionParticipants.push(pathCompletionParticipant); - } let settings = await getDocumentSettings(document, () => doComplete.length > 2); let result = doComplete(document, textDocumentPosition.position, settings, completionParticipants); - if (!result) { - result = pathCompletionList; - } else { - result.items.push(...pathCompletionList.items); - } if (emmetCompletionList.isIncomplete) { cachedCompletionList = result; if (hexColorRegex.test(emmetCompletionList.items[0].label) && result.items.some(x => x.label === emmetCompletionList.items[0].label)) { diff --git a/extensions/html-language-features/server/src/modes/cssMode.ts b/extensions/html-language-features/server/src/modes/cssMode.ts index fef5c40b6fc..61f00efdbd5 100644 --- a/extensions/html-language-features/server/src/modes/cssMode.ts +++ b/extensions/html-language-features/server/src/modes/cssMode.ts @@ -5,14 +5,14 @@ 'use strict'; import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache'; -import { TextDocument, Position, Range } from 'vscode-languageserver-types'; +import { TextDocument, Position, Range, CompletionList } from 'vscode-languageserver-types'; import { getCSSLanguageService, Stylesheet, ICompletionParticipant } from 'vscode-css-languageservice'; -import { LanguageMode, Settings } from './languageModes'; +import { LanguageMode, Workspace } from './languageModes'; import { HTMLDocumentRegions, CSS_STYLE_RULE } from './embeddedSupport'; import { Color } from 'vscode-languageserver'; import { extractAbbreviation } from 'vscode-emmet-helper'; -export function getCSSMode(documentRegions: LanguageModelCache): LanguageMode { +export function getCSSMode(documentRegions: LanguageModelCache, workspace: Workspace): LanguageMode { let cssLanguageService = getCSSLanguageService(); let embeddedCSSDocuments = getLanguageModelCache(10, 60, document => documentRegions.get(document).getEmbeddedDocument('css')); let cssStylesheets = getLanguageModelCache(10, 60, document => cssLanguageService.parseStylesheet(document)); @@ -21,19 +21,16 @@ export function getCSSMode(documentRegions: LanguageModelCache(10, 60, document => htmlLanguageService.parseHTMLDocument(document)); - let completionParticipants: ICompletionParticipant[] = []; return { getId() { return 'html'; }, - configure(options: any) { - globalSettings = options; - }, - doComplete(document: TextDocument, position: Position, settings: Settings = globalSettings, registeredCompletionParticipants?: ICompletionParticipant[]) { - if (registeredCompletionParticipants) { - completionParticipants = registeredCompletionParticipants; - } + doComplete(document: TextDocument, position: Position, settings = workspace.settings, completionParticipants?: ICompletionParticipant[]) { let options = settings && settings.html && settings.html.suggest; let doAutoComplete = settings && settings.html && settings.html.autoClosingTags; if (doAutoComplete) { options.hideAutoCompleteProposals = true; } + let pathCompletionProposals: CompletionItem[] = []; + let participants = [getPathCompletionParticipant(document, workspace.folders, pathCompletionProposals)]; + if (completionParticipants) { + participants.push(...completionParticipants); + } + htmlLanguageService.setCompletionParticipants(participants); const htmlDocument = htmlDocuments.get(document); - htmlLanguageService.setCompletionParticipants(completionParticipants); - - return htmlLanguageService.doComplete(document, position, htmlDocument, options); - }, - setCompletionParticipants(registeredCompletionParticipants: any[]) { - completionParticipants = registeredCompletionParticipants; + let completionList = htmlLanguageService.doComplete(document, position, htmlDocument, options); + completionList.items.push(...pathCompletionProposals); + return completionList; }, doHover(document: TextDocument, position: Position) { return htmlLanguageService.doHover(document, position, htmlDocuments.get(document)); @@ -53,7 +49,7 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService): LanguageM findDocumentSymbols(document: TextDocument) { return htmlLanguageService.findDocumentSymbols(document, htmlDocuments.get(document)); }, - format(document: TextDocument, range: Range, formatParams: FormattingOptions, settings: Settings = globalSettings) { + format(document: TextDocument, range: Range, formatParams: FormattingOptions, settings = workspace.settings) { let formatSettings: HTMLFormatConfiguration = settings && settings.html && settings.html.format; if (formatSettings) { formatSettings = merge(formatSettings, {}); diff --git a/extensions/html-language-features/server/src/modes/javascriptMode.ts b/extensions/html-language-features/server/src/modes/javascriptMode.ts index c7c803e5684..23a0e5b7134 100644 --- a/extensions/html-language-features/server/src/modes/javascriptMode.ts +++ b/extensions/html-language-features/server/src/modes/javascriptMode.ts @@ -6,7 +6,7 @@ import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache'; import { SymbolInformation, SymbolKind, CompletionItem, Location, SignatureHelp, SignatureInformation, ParameterInformation, Definition, TextEdit, TextDocument, Diagnostic, DiagnosticSeverity, Range, CompletionItemKind, Hover, MarkedString, DocumentHighlight, DocumentHighlightKind, CompletionList, Position, FormattingOptions } from 'vscode-languageserver-types'; -import { LanguageMode, Settings } from './languageModes'; +import { LanguageMode, Settings, Workspace } from './languageModes'; import { getWordAtText, startsWith, isWhitespaceOnly, repeat } from '../utils/strings'; import { HTMLDocumentRegions } from './embeddedSupport'; @@ -19,7 +19,7 @@ const JQUERY_D_TS = join(__dirname, '../../lib/jquery.d.ts'); const JS_WORD_REGEX = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g; -export function getJavascriptMode(documentRegions: LanguageModelCache): LanguageMode { +export function getJavascriptMode(documentRegions: LanguageModelCache, workspace: Workspace): LanguageMode { let jsDocuments = getLanguageModelCache(10, 60, document => documentRegions.get(document).getEmbeddedDocument('javascript')); let compilerOptions: ts.CompilerOptions = { allowNonTsExtensions: true, allowJs: true, lib: ['lib.es6.d.ts'], target: ts.ScriptTarget.Latest, moduleResolution: ts.ModuleResolutionKind.Classic }; @@ -67,9 +67,6 @@ export function getJavascriptMode(documentRegions: LanguageModelCache; +export interface Workspace { + readonly settings: Settings; + readonly folders: WorkspaceFolder[]; } export interface LanguageMode { getId(): string; - configure?: (options: Settings) => void; doValidation?: (document: TextDocument, settings?: Settings) => Diagnostic[]; - doComplete?: (document: TextDocument, position: Position, settings?: Settings, registeredCompletionParticipants?: any[]) => CompletionList | null; - setCompletionParticipants?: (registeredCompletionParticipants: any[]) => void; + doComplete?: (document: TextDocument, position: Position, settings?: Settings, registeredCompletionParticipants?: any[]) => CompletionList; doResolve?: (document: TextDocument, item: CompletionItem) => CompletionItem; doHover?: (document: TextDocument, position: Position) => Hover | null; doSignatureHelp?: (document: TextDocument, position: Position) => SignatureHelp | null; @@ -69,7 +68,7 @@ export interface LanguageModeRange extends Range { attributeValue?: boolean; } -export function getLanguageModes(supportedLanguages: { [languageId: string]: boolean; }): LanguageModes { +export function getLanguageModes(supportedLanguages: { [languageId: string]: boolean; }, workspace: Workspace): LanguageModes { var htmlLanguageService = getHTMLLanguageService(); let documentRegions = getLanguageModelCache(10, 60, document => getDocumentRegions(htmlLanguageService, document)); @@ -78,12 +77,12 @@ export function getLanguageModes(supportedLanguages: { [languageId: string]: boo modelCaches.push(documentRegions); let modes = Object.create(null); - modes['html'] = getHTMLMode(htmlLanguageService); + modes['html'] = getHTMLMode(htmlLanguageService, workspace); if (supportedLanguages['css']) { - modes['css'] = getCSSMode(documentRegions); + modes['css'] = getCSSMode(documentRegions, workspace); } if (supportedLanguages['javascript']) { - modes['javascript'] = getJavascriptMode(documentRegions); + modes['javascript'] = getJavascriptMode(documentRegions, workspace); } return { getModeAtPosition(document: TextDocument, position: Position): LanguageMode | undefined { diff --git a/extensions/html-language-features/server/src/modes/pathCompletion.ts b/extensions/html-language-features/server/src/modes/pathCompletion.ts index 5e2c2077b16..afb5089fbfd 100644 --- a/extensions/html-language-features/server/src/modes/pathCompletion.ts +++ b/extensions/html-language-features/server/src/modes/pathCompletion.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { TextDocument, CompletionList, CompletionItemKind, CompletionItem, TextEdit, Range, Position } from 'vscode-languageserver-types'; +import { TextDocument, CompletionItemKind, CompletionItem, TextEdit, Range, Position } from 'vscode-languageserver-types'; import { WorkspaceFolder } from 'vscode-languageserver'; import * as path from 'path'; import * as fs from 'fs'; @@ -15,22 +15,21 @@ import { contains } from '../utils/arrays'; export function getPathCompletionParticipant( document: TextDocument, - workspaceFolders: WorkspaceFolder[] | undefined, - result: CompletionList + workspaceFolders: WorkspaceFolder[], + result: CompletionItem[] ): ICompletionParticipant { return { onHtmlAttributeValue: ({ tag, position, attribute, value: valueBeforeCursor, range }) => { const fullValue = getFullValueWithoutQuotes(document, range); if (shouldDoPathCompletion(tag, attribute, fullValue)) { - if (!workspaceFolders || workspaceFolders.length === 0) { + if (workspaceFolders.length === 0) { return; } const workspaceRoot = resolveWorkspaceRoot(document, workspaceFolders); const paths = providePaths(valueBeforeCursor, URI.parse(document.uri).fsPath, workspaceRoot); - const suggestions = paths.map(p => pathToSuggestion(p, valueBeforeCursor, fullValue, range)); - result.items = [...suggestions, ...result.items]; + result.push(...paths.map(p => pathToSuggestion(p, valueBeforeCursor, fullValue, range))); } } }; diff --git a/extensions/html-language-features/server/src/test/completions.test.ts b/extensions/html-language-features/server/src/test/completions.test.ts index 80503b77362..23b119eb826 100644 --- a/extensions/html-language-features/server/src/test/completions.test.ts +++ b/extensions/html-language-features/server/src/test/completions.test.ts @@ -7,10 +7,9 @@ import 'mocha'; import * as assert from 'assert'; import * as path from 'path'; -// import Uri from 'vscode-uri'; +import Uri from 'vscode-uri'; import { TextDocument, CompletionList, CompletionItemKind } from 'vscode-languageserver-types'; import { getLanguageModes } from '../modes/languageModes'; -import { getPathCompletionParticipant } from '../modes/pathCompletion'; import { WorkspaceFolder } from 'vscode-languageserver'; export interface ItemDescription { @@ -22,7 +21,7 @@ export interface ItemDescription { notAvailable?: boolean; } -export function assertCompletion (completions: CompletionList, expected: ItemDescription, document: TextDocument, offset: number) { +export function assertCompletion(completions: CompletionList, expected: ItemDescription, document: TextDocument, offset: number) { let matches = completions.items.filter(completion => { return completion.label === expected.label; }); @@ -49,32 +48,22 @@ export function assertCompletion (completions: CompletionList, expected: ItemDes const testUri = 'test://test/test.html'; -export function testCompletionFor( - value: string, - expected: { count?: number, items?: ItemDescription[] }, - uri = testUri, - workspaceFolders?: WorkspaceFolder[] -): void { +export function testCompletionFor(value: string, expected: { count?: number, items?: ItemDescription[] }, uri = testUri, workspaceFolders?: WorkspaceFolder[]): void { let offset = value.indexOf('|'); value = value.substr(0, offset) + value.substr(offset + 1); + let workspace = { + settings: {}, + folders: workspaceFolders || [{ name: 'x', uri: uri.substr(0, uri.lastIndexOf('/')) }] + }; + let document = TextDocument.create(uri, 'html', 0, value); let position = document.positionAt(offset); - var languageModes = getLanguageModes({ css: true, javascript: true }); + var languageModes = getLanguageModes({ css: true, javascript: true }, workspace); var mode = languageModes.getModeAtPosition(document, position)!; - if (!workspaceFolders) { - workspaceFolders = [{ name: 'x', uri: path.dirname(uri) }]; - } - - let participantResult = CompletionList.create([]); - if (mode.setCompletionParticipants) { - mode.setCompletionParticipants([getPathCompletionParticipant(document, workspaceFolders, participantResult)]); - } - - let list = mode.doComplete!(document, position)!; - list.items = list.items.concat(participantResult.items); + let list = mode.doComplete!(document, position); if (expected.count) { assert.equal(list.items, expected.count); @@ -107,10 +96,10 @@ suite('HTML Path Completion', () => { command: 'editor.action.triggerSuggest' }; - const fixtureRoot = path.resolve(__dirname, 'pathcompletionfixtures'); - const fixtureWorkspace = { name: 'fixture', uri: fixtureRoot }; - const indexHtmlUri = path.resolve(fixtureRoot, 'index.html'); - const aboutHtmlUri = path.resolve(fixtureRoot, 'about/about.html'); + const fixtureRoot = path.resolve(__dirname, 'pathCompletionFixtures'); + const fixtureWorkspace = { name: 'fixture', uri: Uri.file(fixtureRoot).toString() }; + const indexHtmlUri = Uri.file(path.resolve(fixtureRoot, 'index.html')).toString(); + const aboutHtmlUri = Uri.file(path.resolve(fixtureRoot, 'about/about.html')).toString(); test('Basics - Correct label/kind/result/command', () => { testCompletionFor('