mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-28 12:33:35 +01:00
Tests and handling of quotes
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
82
extensions/css/server/src/test/completion.test.ts
Normal file
82
extensions/css/server/src/test/completion.test.ts
Normal file
@@ -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);
|
||||
});
|
||||
});
|
||||
59
extensions/css/server/src/utils/arrays.ts
Normal file
59
extensions/css/server/src/utils/arrays.ts
Normal file
@@ -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<T>(to: T[], from: T[]) {
|
||||
if (from) {
|
||||
for (var i = 0; i < from.length; i++) {
|
||||
to.push(from[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function contains<T>(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<T>(data: T[], compare: (a: T, b: T) => number): T[] {
|
||||
_divideAndMerge(data, compare);
|
||||
return data;
|
||||
}
|
||||
|
||||
function _divideAndMerge<T>(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++];
|
||||
}
|
||||
}
|
||||
32
extensions/css/server/src/utils/edits.ts
Normal file
32
extensions/css/server/src/utils/edits.ts
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
@@ -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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
@@ -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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
Reference in New Issue
Block a user