From 68ae0e5aa69d9699bb18d44313a9f59f229bd53d Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Wed, 14 Mar 2018 11:05:37 -0700 Subject: [PATCH 1/3] path completion for css. fix #45235 --- extensions/css/.vscode/launch.json | 9 +- extensions/css/server/src/cssServerMain.ts | 15 ++- extensions/css/server/src/pathCompletion.ts | 104 +++++++++++++++++++ extensions/css/server/src/test/emmet.test.ts | 2 +- extensions/css/server/src/utils/strings.ts | 79 ++++++++++++++ 5 files changed, 205 insertions(+), 4 deletions(-) create mode 100644 extensions/css/server/src/pathCompletion.ts create mode 100644 extensions/css/server/src/utils/strings.ts diff --git a/extensions/css/.vscode/launch.json b/extensions/css/.vscode/launch.json index fa74c4170fc0c..68f7c70e450ee 100644 --- a/extensions/css/.vscode/launch.json +++ b/extensions/css/.vscode/launch.json @@ -1,5 +1,11 @@ { "version": "0.2.0", + "compounds": [ + { + "name": "Debug Extension and Language Server", + "configurations": ["Launch Extension", "Attach Language Server"] + } + ], "configurations": [ { "name": "Launch Extension", @@ -32,7 +38,8 @@ "protocol": "inspector", "port": 6044, "sourceMaps": true, - "outFiles": ["${workspaceFolder}/server/out/**/*.js"] + "outFiles": ["${workspaceFolder}/server/out/**/*.js"], + "restart": true } ] } \ No newline at end of file diff --git a/extensions/css/server/src/cssServerMain.ts b/extensions/css/server/src/cssServerMain.ts index 42cef8ddd33f3..a0d7fc0eb095b 100644 --- a/extensions/css/server/src/cssServerMain.ts +++ b/extensions/css/server/src/cssServerMain.ts @@ -8,7 +8,7 @@ import { createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities } from 'vscode-languageserver'; -import { TextDocument } from 'vscode-languageserver-types'; +import { TextDocument, CompletionList } from 'vscode-languageserver-types'; import { ConfigurationRequest } from 'vscode-languageserver-protocol/lib/protocol.configuration.proposed'; import { WorkspaceFolder } from 'vscode-languageserver-protocol/lib/protocol.workspaceFolders.proposed'; @@ -18,6 +18,7 @@ import { getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, import { getLanguageModelCache } from './languageModelCache'; import { formatError, runSafe } from './utils/errors'; import uri from 'vscode-uri'; +import { getPathCompletionParticipant } from './pathCompletion'; export interface Settings { css: LanguageSettings; @@ -184,7 +185,17 @@ function validateTextDocument(textDocument: TextDocument): void { connection.onCompletion(textDocumentPosition => { return runSafe(() => { let document = documents.get(textDocumentPosition.textDocument.uri); - return getLanguageService(document).doComplete(document, textDocumentPosition.position, stylesheets.get(document))!; /* TODO: remove ! once LS has null annotations */ + const cssLS = getLanguageService(document); + const pathCompletionList: CompletionList = { + isIncomplete: false, + items: [] + }; + cssLS.setCompletionParticipants([getPathCompletionParticipant(document, workspaceFolders, pathCompletionList)]); + const result = getLanguageService(document).doComplete(document, textDocumentPosition.position, stylesheets.get(document))!; /* TODO: remove ! once LS has null annotations */ + return { + isIncomplete: result.isIncomplete, + items: [...pathCompletionList.items, ...result.items] + }; }, null, `Error while computing completions for ${textDocumentPosition.textDocument.uri}`); }); diff --git a/extensions/css/server/src/pathCompletion.ts b/extensions/css/server/src/pathCompletion.ts new file mode 100644 index 0000000000000..60028a35704d0 --- /dev/null +++ b/extensions/css/server/src/pathCompletion.ts @@ -0,0 +1,104 @@ +/*--------------------------------------------------------------------------------------------- + * 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, CompletionList, CompletionItemKind, CompletionItem, TextEdit, Range, Position } from 'vscode-languageserver-types'; +import { Proposed } from 'vscode-languageserver-protocol'; +import * as path from 'path'; +import * as fs from 'fs'; +import URI from 'vscode-uri'; +import { ICompletionParticipant } from 'vscode-css-languageservice/lib/umd/cssLanguageService'; +import { startsWith } from './utils/strings'; + +export function getPathCompletionParticipant( + document: TextDocument, + workspaceFolders: Proposed.WorkspaceFolder[] | undefined, + result: CompletionList +): ICompletionParticipant { + return { + onCssURILiteralValue: (context: { uriValue: string, position: Position, range: Range; }) => { + if (!workspaceFolders || workspaceFolders.length === 0) { + return; + } + const workspaceRoot = resolveWorkspaceRoot(document, workspaceFolders); + + const suggestions = providePathSuggestions(context.uriValue, context.range, URI.parse(document.uri).fsPath, workspaceRoot); + result.items = [...suggestions, ...result.items]; + } + }; +} + +export function providePathSuggestions(value: string, range: Range, activeDocFsPath: string, root?: string): CompletionItem[] { + if (startsWith(value, '/') && !root) { + return []; + } + + let replaceRange: Range; + const lastIndexOfSlash = value.lastIndexOf('/'); + if (lastIndexOfSlash === -1) { + replaceRange = getFullReplaceRange(range); + } else { + const valueAfterLastSlash = value.slice(lastIndexOfSlash + 1); + replaceRange = getReplaceRange(range, valueAfterLastSlash); + } + + let parentDir: string; + if (lastIndexOfSlash === -1) { + parentDir = path.resolve(root); + } else { + const valueBeforeLastSlash = value.slice(0, lastIndexOfSlash + 1); + + parentDir = startsWith(value, '/') + ? path.resolve(root, '.' + valueBeforeLastSlash) + : path.resolve(activeDocFsPath, '..', valueBeforeLastSlash); + } + + try { + return fs.readdirSync(parentDir).map(f => { + if (isDir(path.resolve(parentDir, f))) { + return { + label: f + '/', + kind: CompletionItemKind.Folder, + textEdit: TextEdit.replace(replaceRange, f + '/'), + command: { + title: 'Suggest', + command: 'editor.action.triggerSuggest' + } + }; + } else { + return { + label: f, + kind: CompletionItemKind.File, + textEdit: TextEdit.replace(replaceRange, f) + }; + } + }); + } catch (e) { + return []; + } +} + +const isDir = (p: string) => { + return fs.statSync(p).isDirectory(); +}; + +function resolveWorkspaceRoot(activeDoc: TextDocument, workspaceFolders: Proposed.WorkspaceFolder[]): string | undefined { + for (let i = 0; i < workspaceFolders.length; i++) { + if (startsWith(activeDoc.uri, workspaceFolders[i].uri)) { + return path.resolve(URI.parse(workspaceFolders[i].uri).fsPath); + } + } +} + +function getFullReplaceRange(valueRange: Range) { + const start = Position.create(valueRange.end.line, valueRange.start.character); + const end = Position.create(valueRange.end.line, valueRange.end.character); + return Range.create(start, end); +} +function getReplaceRange(valueRange: Range, valueAfterLastSlash: string) { + const start = Position.create(valueRange.end.line, valueRange.end.character - valueAfterLastSlash.length); + const end = Position.create(valueRange.end.line, valueRange.end.character); + return Range.create(start, end); +} diff --git a/extensions/css/server/src/test/emmet.test.ts b/extensions/css/server/src/test/emmet.test.ts index 054dc433cc49d..553b55e4d2f2b 100644 --- a/extensions/css/server/src/test/emmet.test.ts +++ b/extensions/css/server/src/test/emmet.test.ts @@ -6,7 +6,7 @@ import 'mocha'; import * as assert from 'assert'; -import { getCSSLanguageService, getSCSSLanguageService } from 'vscode-css-languageservice'; +import { getCSSLanguageService, getSCSSLanguageService } from 'vscode-css-languageservice/lib/umd/cssLanguageService'; import { TextDocument, CompletionList } from 'vscode-languageserver-types'; import { getEmmetCompletionParticipants } from 'vscode-emmet-helper'; diff --git a/extensions/css/server/src/utils/strings.ts b/extensions/css/server/src/utils/strings.ts new file mode 100644 index 0000000000000..1d99cddd56bf4 --- /dev/null +++ b/extensions/css/server/src/utils/strings.ts @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +export function getWordAtText(text: string, offset: number, wordDefinition: RegExp): { start: number, length: number } { + let lineStart = offset; + while (lineStart > 0 && !isNewlineCharacter(text.charCodeAt(lineStart - 1))) { + lineStart--; + } + let offsetInLine = offset - lineStart; + let lineText = text.substr(lineStart); + + // make a copy of the regex as to not keep the state + let flags = wordDefinition.ignoreCase ? 'gi' : 'g'; + wordDefinition = new RegExp(wordDefinition.source, flags); + + let match = wordDefinition.exec(lineText); + while (match && match.index + match[0].length < offsetInLine) { + match = wordDefinition.exec(lineText); + } + if (match && match.index <= offsetInLine) { + return { start: match.index + lineStart, length: match[0].length }; + } + + return { start: offset, length: 0 }; +} + +export function startsWith(haystack: string, needle: string): boolean { + if (haystack.length < needle.length) { + return false; + } + + for (let i = 0; i < needle.length; i++) { + if (haystack[i] !== needle[i]) { + return false; + } + } + + return true; +} + +export function endsWith(haystack: string, needle: string): boolean { + let diff = haystack.length - needle.length; + if (diff > 0) { + return haystack.indexOf(needle, diff) === diff; + } else if (diff === 0) { + return haystack === needle; + } else { + return false; + } +} + +export function repeat(value: string, count: number) { + var s = ''; + while (count > 0) { + if ((count & 1) === 1) { + s += value; + } + value += value; + count = count >>> 1; + } + return s; +} + +export function isWhitespaceOnly(str: string) { + return /^\s*$/.test(str); +} + +export function isEOL(content: string, offset: number) { + return isNewlineCharacter(content.charCodeAt(offset)); +} + +const CR = '\r'.charCodeAt(0); +const NL = '\n'.charCodeAt(0); +export function isNewlineCharacter(charCode: number) { + return charCode === CR || charCode === NL; +} \ No newline at end of file From 0ae6c19a600d3a1f8e7cb10c926004f6927952f5 Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Mon, 19 Mar 2018 07:10:53 -0700 Subject: [PATCH 2/3] Tests and handling of quotes --- extensions/css/server/src/pathCompletion.ts | 17 +++- .../css/server/src/test/completion.test.ts | 82 +++++++++++++++++++ extensions/css/server/src/utils/arrays.ts | 59 +++++++++++++ extensions/css/server/src/utils/edits.ts | 32 ++++++++ extensions/css/server/src/utils/strings.ts | 60 -------------- .../pathCompletionFixtures/about/about.css | 4 + .../pathCompletionFixtures/about/about.html | 0 .../test/pathCompletionFixtures/index.html | 0 .../pathCompletionFixtures/src/feature.js | 4 + .../test/pathCompletionFixtures/src/test.js | 4 + .../server/src/test/completions.test.ts | 2 +- 11 files changed, 201 insertions(+), 63 deletions(-) create mode 100644 extensions/css/server/src/test/completion.test.ts create mode 100644 extensions/css/server/src/utils/arrays.ts create mode 100644 extensions/css/server/src/utils/edits.ts create mode 100644 extensions/css/server/test/pathCompletionFixtures/about/about.css create mode 100644 extensions/css/server/test/pathCompletionFixtures/about/about.html create mode 100644 extensions/css/server/test/pathCompletionFixtures/index.html create mode 100644 extensions/css/server/test/pathCompletionFixtures/src/feature.js create mode 100644 extensions/css/server/test/pathCompletionFixtures/src/test.js diff --git a/extensions/css/server/src/pathCompletion.ts b/extensions/css/server/src/pathCompletion.ts index 60028a35704d0..9c13719630de9 100644 --- a/extensions/css/server/src/pathCompletion.ts +++ b/extensions/css/server/src/pathCompletion.ts @@ -18,13 +18,21 @@ export function getPathCompletionParticipant( result: CompletionList ): ICompletionParticipant { return { - onCssURILiteralValue: (context: { uriValue: string, position: Position, range: Range; }) => { + onURILiteralValue: (context: { uriValue: string, position: Position, range: Range; }) => { if (!workspaceFolders || workspaceFolders.length === 0) { return; } const workspaceRoot = resolveWorkspaceRoot(document, workspaceFolders); - const suggestions = providePathSuggestions(context.uriValue, context.range, URI.parse(document.uri).fsPath, workspaceRoot); + // Handle quoted values + let uriValue = context.uriValue; + let range = context.range; + if (startsWith(uriValue, `'`) || startsWith(uriValue, `"`)) { + uriValue = uriValue.slice(1, -1); + range = getRangeWithoutQuotes(range); + } + + const suggestions = providePathSuggestions(uriValue, range, URI.parse(document.uri).fsPath, workspaceRoot); result.items = [...suggestions, ...result.items]; } }; @@ -102,3 +110,8 @@ function getReplaceRange(valueRange: Range, valueAfterLastSlash: string) { const end = Position.create(valueRange.end.line, valueRange.end.character); return Range.create(start, end); } +function getRangeWithoutQuotes(range: Range) { + const start = Position.create(range.start.line, range.start.character + 1); + const end = Position.create(range.end.line, range.end.character - 1); + return Range.create(start, end); +} diff --git a/extensions/css/server/src/test/completion.test.ts b/extensions/css/server/src/test/completion.test.ts new file mode 100644 index 0000000000000..c2603adb658b7 --- /dev/null +++ b/extensions/css/server/src/test/completion.test.ts @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * 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 'mocha'; +import * as assert from 'assert'; +import * as path from 'path'; +import Uri from 'vscode-uri'; +import { TextDocument, CompletionList } from 'vscode-languageserver-types'; +import { applyEdits } from '../utils/edits'; +import { getPathCompletionParticipant } from '../pathCompletion'; +import { Proposed } from 'vscode-languageserver-protocol'; +import { getCSSLanguageService } from 'vscode-css-languageservice/lib/umd/cssLanguageService'; + +export interface ItemDescription { + label: string; + resultText?: string; +} + +suite('Completions', () => { + const cssLanguageService = getCSSLanguageService(); + + let assertCompletion = function (completions: CompletionList, expected: ItemDescription, document: TextDocument, offset: number) { + let matches = completions.items.filter(completion => { + return completion.label === expected.label; + }); + + assert.equal(matches.length, 1, `${expected.label} should only existing once: Actual: ${completions.items.map(c => c.label).join(', ')}`); + let match = matches[0]; + if (expected.resultText && match.textEdit) { + assert.equal(applyEdits(document, [match.textEdit]), expected.resultText); + } + }; + + function assertCompletions(value: string, expected: { count?: number, items?: ItemDescription[] }, testUri: string, workspaceFolders?: Proposed.WorkspaceFolder[]): void { + const offset = value.indexOf('|'); + value = value.substr(0, offset) + value.substr(offset + 1); + + const document = TextDocument.create(testUri, 'css', 0, value); + const position = document.positionAt(offset); + + if (!workspaceFolders) { + workspaceFolders = [{ name: 'x', uri: path.dirname(testUri) }]; + } + + let participantResult = CompletionList.create([]); + cssLanguageService.setCompletionParticipants([getPathCompletionParticipant(document, workspaceFolders, participantResult)]); + + const stylesheet = cssLanguageService.parseStylesheet(document); + let list = cssLanguageService.doComplete!(document, position, stylesheet); + list.items = list.items.concat(participantResult.items); + + if (expected.count) { + assert.equal(list.items.length, expected.count); + } + if (expected.items) { + for (let item of expected.items) { + assertCompletion(list, item, document, offset); + } + } + } + + test('CSS Path completion', function () { + let testUri = Uri.file(path.resolve(__dirname, '../../test/pathCompletionFixtures/about/about.css')).fsPath; + + assertCompletions('html { background-image: url("./|")', { + items: [ + { label: 'about.html', resultText: 'html { background-image: url("./about.html")' } + ] + }, testUri); + + assertCompletions(`html { background-image: url('../|')`, { + items: [ + { label: 'about/', resultText: `html { background-image: url('../about/')` }, + { label: 'index.html', resultText: `html { background-image: url('../index.html')` }, + { label: 'src/', resultText: `html { background-image: url('../src/')` } + ] + }, testUri); + }); +}); \ No newline at end of file diff --git a/extensions/css/server/src/utils/arrays.ts b/extensions/css/server/src/utils/arrays.ts new file mode 100644 index 0000000000000..a33ef18597c8f --- /dev/null +++ b/extensions/css/server/src/utils/arrays.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +export function pushAll(to: T[], from: T[]) { + if (from) { + for (var i = 0; i < from.length; i++) { + to.push(from[i]); + } + } +} + +export function contains(arr: T[], val: T) { + return arr.indexOf(val) !== -1; +} + +/** + * Like `Array#sort` but always stable. Usually runs a little slower `than Array#sort` + * so only use this when actually needing stable sort. + */ +export function mergeSort(data: T[], compare: (a: T, b: T) => number): T[] { + _divideAndMerge(data, compare); + return data; +} + +function _divideAndMerge(data: T[], compare: (a: T, b: T) => number): void { + if (data.length <= 1) { + // sorted + return; + } + const p = (data.length / 2) | 0; + const left = data.slice(0, p); + const right = data.slice(p); + + _divideAndMerge(left, compare); + _divideAndMerge(right, compare); + + let leftIdx = 0; + let rightIdx = 0; + let i = 0; + while (leftIdx < left.length && rightIdx < right.length) { + let ret = compare(left[leftIdx], right[rightIdx]); + if (ret <= 0) { + // smaller_equal -> take left to preserve order + data[i++] = left[leftIdx++]; + } else { + // greater -> take right + data[i++] = right[rightIdx++]; + } + } + while (leftIdx < left.length) { + data[i++] = left[leftIdx++]; + } + while (rightIdx < right.length) { + data[i++] = right[rightIdx++]; + } +} diff --git a/extensions/css/server/src/utils/edits.ts b/extensions/css/server/src/utils/edits.ts new file mode 100644 index 0000000000000..66c74be030f8e --- /dev/null +++ b/extensions/css/server/src/utils/edits.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * 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, TextEdit } from 'vscode-languageserver-types'; +import { mergeSort } from './arrays'; + +export function applyEdits(document: TextDocument, edits: TextEdit[]): string { + let text = document.getText(); + let sortedEdits = mergeSort(edits, (a, b) => { + let diff = a.range.start.line - b.range.start.line; + if (diff === 0) { + return a.range.start.character - b.range.start.character; + } + return 0; + }); + let lastModifiedOffset = text.length; + for (let i = sortedEdits.length - 1; i >= 0; i--) { + let e = sortedEdits[i]; + let startOffset = document.offsetAt(e.range.start); + let endOffset = document.offsetAt(e.range.end); + if (endOffset <= lastModifiedOffset) { + text = text.substring(0, startOffset) + e.newText + text.substring(endOffset, text.length); + } else { + throw new Error('Ovelapping edit'); + } + lastModifiedOffset = startOffset; + } + return text; +} \ No newline at end of file diff --git a/extensions/css/server/src/utils/strings.ts b/extensions/css/server/src/utils/strings.ts index 1d99cddd56bf4..f7ad0845cc8e8 100644 --- a/extensions/css/server/src/utils/strings.ts +++ b/extensions/css/server/src/utils/strings.ts @@ -4,29 +4,6 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -export function getWordAtText(text: string, offset: number, wordDefinition: RegExp): { start: number, length: number } { - let lineStart = offset; - while (lineStart > 0 && !isNewlineCharacter(text.charCodeAt(lineStart - 1))) { - lineStart--; - } - let offsetInLine = offset - lineStart; - let lineText = text.substr(lineStart); - - // make a copy of the regex as to not keep the state - let flags = wordDefinition.ignoreCase ? 'gi' : 'g'; - wordDefinition = new RegExp(wordDefinition.source, flags); - - let match = wordDefinition.exec(lineText); - while (match && match.index + match[0].length < offsetInLine) { - match = wordDefinition.exec(lineText); - } - if (match && match.index <= offsetInLine) { - return { start: match.index + lineStart, length: match[0].length }; - } - - return { start: offset, length: 0 }; -} - export function startsWith(haystack: string, needle: string): boolean { if (haystack.length < needle.length) { return false; @@ -40,40 +17,3 @@ export function startsWith(haystack: string, needle: string): boolean { return true; } - -export function endsWith(haystack: string, needle: string): boolean { - let diff = haystack.length - needle.length; - if (diff > 0) { - return haystack.indexOf(needle, diff) === diff; - } else if (diff === 0) { - return haystack === needle; - } else { - return false; - } -} - -export function repeat(value: string, count: number) { - var s = ''; - while (count > 0) { - if ((count & 1) === 1) { - s += value; - } - value += value; - count = count >>> 1; - } - return s; -} - -export function isWhitespaceOnly(str: string) { - return /^\s*$/.test(str); -} - -export function isEOL(content: string, offset: number) { - return isNewlineCharacter(content.charCodeAt(offset)); -} - -const CR = '\r'.charCodeAt(0); -const NL = '\n'.charCodeAt(0); -export function isNewlineCharacter(charCode: number) { - return charCode === CR || charCode === NL; -} \ No newline at end of file diff --git a/extensions/css/server/test/pathCompletionFixtures/about/about.css b/extensions/css/server/test/pathCompletionFixtures/about/about.css new file mode 100644 index 0000000000000..adae63e647cb9 --- /dev/null +++ b/extensions/css/server/test/pathCompletionFixtures/about/about.css @@ -0,0 +1,4 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ \ No newline at end of file diff --git a/extensions/css/server/test/pathCompletionFixtures/about/about.html b/extensions/css/server/test/pathCompletionFixtures/about/about.html new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/extensions/css/server/test/pathCompletionFixtures/index.html b/extensions/css/server/test/pathCompletionFixtures/index.html new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/extensions/css/server/test/pathCompletionFixtures/src/feature.js b/extensions/css/server/test/pathCompletionFixtures/src/feature.js new file mode 100644 index 0000000000000..adae63e647cb9 --- /dev/null +++ b/extensions/css/server/test/pathCompletionFixtures/src/feature.js @@ -0,0 +1,4 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ \ No newline at end of file diff --git a/extensions/css/server/test/pathCompletionFixtures/src/test.js b/extensions/css/server/test/pathCompletionFixtures/src/test.js new file mode 100644 index 0000000000000..adae63e647cb9 --- /dev/null +++ b/extensions/css/server/test/pathCompletionFixtures/src/test.js @@ -0,0 +1,4 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ \ No newline at end of file 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 21a5b163bf322..f356d122bec0c 100644 --- a/extensions/html-language-features/server/src/test/completions.test.ts +++ b/extensions/html-language-features/server/src/test/completions.test.ts @@ -8,7 +8,7 @@ import 'mocha'; import * as assert from 'assert'; import * as path from 'path'; import Uri from 'vscode-uri'; -import { TextDocument, CompletionList, CompletionItemKind, } from 'vscode-languageserver-types'; +import { TextDocument, CompletionList, CompletionItemKind } from 'vscode-languageserver-types'; import { getLanguageModes } from '../modes/languageModes'; import { getPathCompletionParticipant } from '../modes/pathCompletion'; import { WorkspaceFolder } from 'vscode-languageserver'; From e394797455afaa4b6411d8579f6a7befd55ec04f Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Tue, 20 Mar 2018 14:24:09 -0700 Subject: [PATCH 3/3] Update deps, remove applyEdits --- extensions/css/server/package.json | 4 +- extensions/css/server/src/cssServerMain.ts | 15 ++--- extensions/css/server/src/pathCompletion.ts | 12 ++-- .../css/server/src/test/completion.test.ts | 9 ++- extensions/css/server/src/utils/arrays.ts | 59 ------------------- extensions/css/server/src/utils/edits.ts | 32 ---------- extensions/css/server/yarn.lock | 44 +++++++------- 7 files changed, 43 insertions(+), 132 deletions(-) delete mode 100644 extensions/css/server/src/utils/arrays.ts delete mode 100644 extensions/css/server/src/utils/edits.ts diff --git a/extensions/css/server/package.json b/extensions/css/server/package.json index 21ec29c7af981..cdd258957b1ee 100644 --- a/extensions/css/server/package.json +++ b/extensions/css/server/package.json @@ -8,9 +8,9 @@ "node": "*" }, "dependencies": { - "vscode-css-languageservice": "^3.0.7", + "vscode-css-languageservice": "^3.0.9-next.1", "vscode-emmet-helper": "^1.2.0", - "vscode-languageserver": "4.0.0-next.4" + "vscode-languageserver": "^4.0.0" }, "devDependencies": { "@types/mocha": "2.2.33", diff --git a/extensions/css/server/src/cssServerMain.ts b/extensions/css/server/src/cssServerMain.ts index a0d7fc0eb095b..66b473f1a6d8c 100644 --- a/extensions/css/server/src/cssServerMain.ts +++ b/extensions/css/server/src/cssServerMain.ts @@ -5,19 +5,16 @@ 'use strict'; import { - createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities + createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities, + ConfigurationRequest, WorkspaceFolder, DocumentColorRequest, ColorPresentationRequest } from 'vscode-languageserver'; import { TextDocument, CompletionList } from 'vscode-languageserver-types'; -import { ConfigurationRequest } from 'vscode-languageserver-protocol/lib/protocol.configuration.proposed'; -import { WorkspaceFolder } from 'vscode-languageserver-protocol/lib/protocol.workspaceFolders.proposed'; -import { DocumentColorRequest, ServerCapabilities as CPServerCapabilities, ColorPresentationRequest } from 'vscode-languageserver-protocol/lib/protocol.colorProvider.proposed'; - import { getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService, Stylesheet } from 'vscode-css-languageservice'; import { getLanguageModelCache } from './languageModelCache'; import { formatError, runSafe } from './utils/errors'; -import uri from 'vscode-uri'; +import URI from 'vscode-uri'; import { getPathCompletionParticipant } from './pathCompletion'; export interface Settings { @@ -61,7 +58,7 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { if (!Array.isArray(workspaceFolders)) { workspaceFolders = []; if (params.rootPath) { - workspaceFolders.push({ name: '', uri: uri.file(params.rootPath).toString() }); + workspaceFolders.push({ name: '', uri: URI.file(params.rootPath).toString() }); } } @@ -75,7 +72,7 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { } let snippetSupport = hasClientCapability('textDocument.completion.completionItem.snippetSupport'); scopedSettingsSupport = hasClientCapability('workspace.configuration'); - let capabilities: ServerCapabilities & CPServerCapabilities = { + let capabilities: ServerCapabilities = { // Tell the client that the server works in FULL text document sync mode textDocumentSync: documents.syncKind, completionProvider: snippetSupport ? { resolveProvider: false } : undefined, @@ -191,7 +188,7 @@ connection.onCompletion(textDocumentPosition => { items: [] }; cssLS.setCompletionParticipants([getPathCompletionParticipant(document, workspaceFolders, pathCompletionList)]); - const result = getLanguageService(document).doComplete(document, textDocumentPosition.position, stylesheets.get(document))!; /* TODO: remove ! once LS has null annotations */ + const result = cssLS.doComplete(document, textDocumentPosition.position, stylesheets.get(document))!; /* TODO: remove ! once LS has null annotations */ return { isIncomplete: result.isIncomplete, items: [...pathCompletionList.items, ...result.items] diff --git a/extensions/css/server/src/pathCompletion.ts b/extensions/css/server/src/pathCompletion.ts index 9c13719630de9..76b93f3a62849 100644 --- a/extensions/css/server/src/pathCompletion.ts +++ b/extensions/css/server/src/pathCompletion.ts @@ -4,17 +4,19 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { TextDocument, CompletionList, CompletionItemKind, CompletionItem, TextEdit, Range, Position } from 'vscode-languageserver-types'; -import { Proposed } from 'vscode-languageserver-protocol'; import * as path from 'path'; import * as fs from 'fs'; import URI from 'vscode-uri'; -import { ICompletionParticipant } from 'vscode-css-languageservice/lib/umd/cssLanguageService'; + +import { TextDocument, CompletionList, CompletionItemKind, CompletionItem, TextEdit, Range, Position } from 'vscode-languageserver-types'; +import { WorkspaceFolder } from 'vscode-languageserver'; +import { ICompletionParticipant } from 'vscode-css-languageservice'; + import { startsWith } from './utils/strings'; export function getPathCompletionParticipant( document: TextDocument, - workspaceFolders: Proposed.WorkspaceFolder[] | undefined, + workspaceFolders: WorkspaceFolder[] | undefined, result: CompletionList ): ICompletionParticipant { return { @@ -92,7 +94,7 @@ const isDir = (p: string) => { return fs.statSync(p).isDirectory(); }; -function resolveWorkspaceRoot(activeDoc: TextDocument, workspaceFolders: Proposed.WorkspaceFolder[]): string | undefined { +function resolveWorkspaceRoot(activeDoc: TextDocument, workspaceFolders: WorkspaceFolder[]): string | undefined { for (let i = 0; i < workspaceFolders.length; i++) { if (startsWith(activeDoc.uri, workspaceFolders[i].uri)) { return path.resolve(URI.parse(workspaceFolders[i].uri).fsPath); diff --git a/extensions/css/server/src/test/completion.test.ts b/extensions/css/server/src/test/completion.test.ts index c2603adb658b7..518a467cf4008 100644 --- a/extensions/css/server/src/test/completion.test.ts +++ b/extensions/css/server/src/test/completion.test.ts @@ -9,10 +9,9 @@ import * as assert from 'assert'; import * as path from 'path'; import Uri from 'vscode-uri'; import { TextDocument, CompletionList } from 'vscode-languageserver-types'; -import { applyEdits } from '../utils/edits'; +import { WorkspaceFolder } from 'vscode-languageserver-protocol'; import { getPathCompletionParticipant } from '../pathCompletion'; -import { Proposed } from 'vscode-languageserver-protocol'; -import { getCSSLanguageService } from 'vscode-css-languageservice/lib/umd/cssLanguageService'; +import { getCSSLanguageService } from 'vscode-css-languageservice'; export interface ItemDescription { label: string; @@ -30,11 +29,11 @@ suite('Completions', () => { assert.equal(matches.length, 1, `${expected.label} should only existing once: Actual: ${completions.items.map(c => c.label).join(', ')}`); let match = matches[0]; if (expected.resultText && match.textEdit) { - assert.equal(applyEdits(document, [match.textEdit]), expected.resultText); + assert.equal(TextDocument.applyEdits(document, [match.textEdit]), expected.resultText); } }; - function assertCompletions(value: string, expected: { count?: number, items?: ItemDescription[] }, testUri: string, workspaceFolders?: Proposed.WorkspaceFolder[]): void { + function assertCompletions(value: string, expected: { count?: number, items?: ItemDescription[] }, testUri: string, workspaceFolders?: WorkspaceFolder[]): void { const offset = value.indexOf('|'); value = value.substr(0, offset) + value.substr(offset + 1); diff --git a/extensions/css/server/src/utils/arrays.ts b/extensions/css/server/src/utils/arrays.ts deleted file mode 100644 index a33ef18597c8f..0000000000000 --- a/extensions/css/server/src/utils/arrays.ts +++ /dev/null @@ -1,59 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -export function pushAll(to: T[], from: T[]) { - if (from) { - for (var i = 0; i < from.length; i++) { - to.push(from[i]); - } - } -} - -export function contains(arr: T[], val: T) { - return arr.indexOf(val) !== -1; -} - -/** - * Like `Array#sort` but always stable. Usually runs a little slower `than Array#sort` - * so only use this when actually needing stable sort. - */ -export function mergeSort(data: T[], compare: (a: T, b: T) => number): T[] { - _divideAndMerge(data, compare); - return data; -} - -function _divideAndMerge(data: T[], compare: (a: T, b: T) => number): void { - if (data.length <= 1) { - // sorted - return; - } - const p = (data.length / 2) | 0; - const left = data.slice(0, p); - const right = data.slice(p); - - _divideAndMerge(left, compare); - _divideAndMerge(right, compare); - - let leftIdx = 0; - let rightIdx = 0; - let i = 0; - while (leftIdx < left.length && rightIdx < right.length) { - let ret = compare(left[leftIdx], right[rightIdx]); - if (ret <= 0) { - // smaller_equal -> take left to preserve order - data[i++] = left[leftIdx++]; - } else { - // greater -> take right - data[i++] = right[rightIdx++]; - } - } - while (leftIdx < left.length) { - data[i++] = left[leftIdx++]; - } - while (rightIdx < right.length) { - data[i++] = right[rightIdx++]; - } -} diff --git a/extensions/css/server/src/utils/edits.ts b/extensions/css/server/src/utils/edits.ts deleted file mode 100644 index 66c74be030f8e..0000000000000 --- a/extensions/css/server/src/utils/edits.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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, TextEdit } from 'vscode-languageserver-types'; -import { mergeSort } from './arrays'; - -export function applyEdits(document: TextDocument, edits: TextEdit[]): string { - let text = document.getText(); - let sortedEdits = mergeSort(edits, (a, b) => { - let diff = a.range.start.line - b.range.start.line; - if (diff === 0) { - return a.range.start.character - b.range.start.character; - } - return 0; - }); - let lastModifiedOffset = text.length; - for (let i = sortedEdits.length - 1; i >= 0; i--) { - let e = sortedEdits[i]; - let startOffset = document.offsetAt(e.range.start); - let endOffset = document.offsetAt(e.range.end); - if (endOffset <= lastModifiedOffset) { - text = text.substring(0, startOffset) + e.newText + text.substring(endOffset, text.length); - } else { - throw new Error('Ovelapping edit'); - } - lastModifiedOffset = startOffset; - } - return text; -} \ No newline at end of file diff --git a/extensions/css/server/yarn.lock b/extensions/css/server/yarn.lock index 0c0ace42012a0..579deb4c9f18f 100644 --- a/extensions/css/server/yarn.lock +++ b/extensions/css/server/yarn.lock @@ -18,12 +18,12 @@ jsonc-parser@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-1.0.0.tgz#ddcc864ae708e60a7a6dd36daea00172fa8d9272" -vscode-css-languageservice@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-3.0.7.tgz#ca53f94db020e4e6c7059963fed8dc256593decc" +vscode-css-languageservice@^3.0.9-next.1: + version "3.0.9-next.1" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-3.0.9-next.1.tgz#92319b641a7624e84950aef2c55e5b4db001ff9a" dependencies: - vscode-languageserver-types "^3.6.0-next.1" - vscode-nls "^2.0.1" + vscode-languageserver-types "^3.6.1" + vscode-nls "^3.2.1" vscode-emmet-helper@^1.2.0: version "1.2.0" @@ -33,31 +33,35 @@ vscode-emmet-helper@^1.2.0: jsonc-parser "^1.0.0" vscode-languageserver-types "^3.6.0-next.1" -vscode-jsonrpc@^3.6.0-next.1: - version "3.6.0-next.1" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.6.0-next.1.tgz#3cb463dffe5842d6aec16718ca9252708cd6aabe" +vscode-jsonrpc@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.6.0.tgz#848d56995d5168950d84feb5d9c237ae5c6a02d4" -vscode-languageserver-protocol@^3.6.0-next.5: - version "3.6.0-next.5" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.6.0-next.5.tgz#ed2ec2db759826f753c0a13977dfb2bedc4d31b3" +vscode-languageserver-protocol@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.6.0.tgz#579642cdcccf74b0cd771c33daa3239acb40d040" dependencies: - vscode-jsonrpc "^3.6.0-next.1" - vscode-languageserver-types "^3.6.0-next.1" + vscode-jsonrpc "^3.6.0" + vscode-languageserver-types "^3.6.0" + +vscode-languageserver-types@^3.6.0, vscode-languageserver-types@^3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.6.1.tgz#4bc06a48dff653495f12f94b8b1e228988a1748d" vscode-languageserver-types@^3.6.0-next.1: version "3.6.0-next.1" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.6.0-next.1.tgz#98e488d3f87b666b4ee1a3d89f0023e246d358f3" -vscode-languageserver@4.0.0-next.4: - version "4.0.0-next.4" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-4.0.0-next.4.tgz#162440b15bedaab07e1676f046e4d9b8578b3d92" +vscode-languageserver@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-4.0.0.tgz#8b792f0d6d10acfe363d02371ed4ce53d08af88a" dependencies: - vscode-languageserver-protocol "^3.6.0-next.5" + vscode-languageserver-protocol "^3.6.0" vscode-uri "^1.0.1" -vscode-nls@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-2.0.2.tgz#808522380844b8ad153499af5c3b03921aea02da" +vscode-nls@^3.2.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.2.tgz#3817eca5b985c2393de325197cf4e15eb2aa5350" vscode-uri@^1.0.1: version "1.0.1"