mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-24 12:19:20 +00:00
Fix #46639
This commit is contained in:
@@ -17,8 +17,8 @@
|
||||
],
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
"outFiles": ["${workspaceFolder}/client/out/**/*.js"]
|
||||
// "preLaunchTask": "npm"
|
||||
"outFiles": ["${workspaceFolder}/client/out/**/*.js"],
|
||||
"preLaunchTask": "npm"
|
||||
},
|
||||
{
|
||||
"name": "Launch Tests",
|
||||
|
||||
@@ -76,7 +76,7 @@ connection.onInitialize((params: InitializeParams): InitializeResult => {
|
||||
let capabilities: ServerCapabilities & FoldingProviderServerCapabilities = {
|
||||
// Tell the client that the server works in FULL text document sync mode
|
||||
textDocumentSync: documents.syncKind,
|
||||
completionProvider: snippetSupport ? { resolveProvider: false } : undefined,
|
||||
completionProvider: snippetSupport ? { resolveProvider: false, triggerCharacters: ['/'] } : undefined,
|
||||
hoverProvider: true,
|
||||
documentSymbolProvider: true,
|
||||
referencesProvider: true,
|
||||
@@ -192,7 +192,7 @@ connection.onCompletion((textDocumentPosition, token) => {
|
||||
cssLS.setCompletionParticipants([getPathCompletionParticipant(document, workspaceFolders, pathCompletionList)]);
|
||||
const result = cssLS.doComplete(document, textDocumentPosition.position, stylesheets.get(document))!; /* TODO: remove ! once LS has null annotations */
|
||||
return {
|
||||
isIncomplete: result.isIncomplete,
|
||||
isIncomplete: pathCompletionList.isIncomplete,
|
||||
items: [...pathCompletionList.items, ...result.items]
|
||||
};
|
||||
}, null, `Error while computing completions for ${textDocumentPosition.textDocument.uri}`, token);
|
||||
|
||||
@@ -21,18 +21,29 @@ export function getPathCompletionParticipant(
|
||||
): ICompletionParticipant {
|
||||
return {
|
||||
onURILiteralValue: ({ position, range, uriValue }) => {
|
||||
const isValueQuoted = startsWith(uriValue, `'`) || startsWith(uriValue, `"`);
|
||||
const fullValue = stripQuotes(uriValue);
|
||||
const valueBeforeCursor = isValueQuoted
|
||||
? fullValue.slice(0, position.character - (range.start.character + 1))
|
||||
: fullValue.slice(0, position.character - range.start.character);
|
||||
|
||||
if (shouldDoPathCompletion(fullValue)) {
|
||||
if (!workspaceFolders || workspaceFolders.length === 0) {
|
||||
return;
|
||||
}
|
||||
const workspaceRoot = resolveWorkspaceRoot(document, workspaceFolders);
|
||||
|
||||
const paths = providePaths(fullValue, URI.parse(document.uri).fsPath, workspaceRoot);
|
||||
result.items = [...paths.map(p => pathToSuggestion(p, fullValue, fullValue, range)), ...result.items];
|
||||
if (fullValue === '.' || fullValue === '..') {
|
||||
result.isIncomplete = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!workspaceFolders || workspaceFolders.length === 0) {
|
||||
return;
|
||||
}
|
||||
const workspaceRoot = resolveWorkspaceRoot(document, workspaceFolders);
|
||||
const paths = providePaths(valueBeforeCursor, URI.parse(document.uri).fsPath, workspaceRoot);
|
||||
|
||||
const fullValueRange = isValueQuoted ? shiftRange(range, 1, -1) : range;
|
||||
const replaceRange = pathToReplaceRange(valueBeforeCursor, fullValue, fullValueRange);
|
||||
const suggestions = paths.map(p => pathToSuggestion(p, replaceRange));
|
||||
result.items = [...suggestions, ...result.items];
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@@ -44,13 +55,6 @@ function stripQuotes(fullValue: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function shouldDoPathCompletion(fullValue: string) {
|
||||
if (fullValue === '.') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of path suggestions. Folder suggestions are suffixed with a slash.
|
||||
*/
|
||||
@@ -85,29 +89,33 @@ const isDir = (p: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
function pathToSuggestion(p: string, valueBeforeCursor: string, fullValue: string, range: Range): CompletionItem {
|
||||
const isDir = p[p.length - 1] === '/';
|
||||
|
||||
function pathToReplaceRange(valueBeforeCursor: string, fullValue: string, fullValueRange: Range) {
|
||||
let replaceRange: Range;
|
||||
const lastIndexOfSlash = valueBeforeCursor.lastIndexOf('/');
|
||||
if (lastIndexOfSlash === -1) {
|
||||
replaceRange = shiftRange(range, 1, -1);
|
||||
replaceRange = fullValueRange;
|
||||
} else {
|
||||
// For cases where cursor is in the middle of attribute value, like <script src="./s|rc/test.js">
|
||||
// Find the last slash before cursor, and calculate the start of replace range from there
|
||||
const valueAfterLastSlash = fullValue.slice(lastIndexOfSlash + 1);
|
||||
const startPos = shiftPosition(range.end, -1 - valueAfterLastSlash.length);
|
||||
const startPos = shiftPosition(fullValueRange.end, -valueAfterLastSlash.length);
|
||||
// If whitespace exists, replace until it
|
||||
const whiteSpaceIndex = valueAfterLastSlash.indexOf(' ');
|
||||
let endPos;
|
||||
if (whiteSpaceIndex !== -1) {
|
||||
endPos = shiftPosition(startPos, whiteSpaceIndex);
|
||||
} else {
|
||||
endPos = shiftPosition(range.end, -1);
|
||||
endPos = fullValueRange.end;
|
||||
}
|
||||
replaceRange = Range.create(startPos, endPos);
|
||||
}
|
||||
|
||||
return replaceRange;
|
||||
}
|
||||
|
||||
function pathToSuggestion(p: string, replaceRange: Range): CompletionItem {
|
||||
const isDir = p[p.length - 1] === '/';
|
||||
|
||||
if (isDir) {
|
||||
return {
|
||||
label: p,
|
||||
|
||||
@@ -120,4 +120,34 @@ suite('Completions', () => {
|
||||
]
|
||||
}, testUri, folders);
|
||||
});
|
||||
|
||||
test('CSS Path Completion - Unquoted url', function () {
|
||||
let testUri = Uri.file(path.resolve(__dirname, '../../test/pathCompletionFixtures/about/about.css')).toString();
|
||||
let folders = [{ name: 'x', uri: Uri.file(path.resolve(__dirname, '../../test')).toString() }];
|
||||
|
||||
assertCompletions('html { background-image: url(./|)', {
|
||||
items: [
|
||||
{ label: 'about.html', resultText: 'html { background-image: url(./about.html)' }
|
||||
]
|
||||
}, testUri, folders);
|
||||
|
||||
assertCompletions('html { background-image: url(./a|)', {
|
||||
items: [
|
||||
{ label: 'about.html', resultText: 'html { background-image: url(./about.html)' }
|
||||
]
|
||||
}, testUri, folders);
|
||||
|
||||
assertCompletions('html { background-image: url(../|src/)', {
|
||||
items: [
|
||||
{ label: 'about/', resultText: 'html { background-image: url(../about/)' }
|
||||
]
|
||||
}, testUri, folders);
|
||||
|
||||
assertCompletions('html { background-image: url(../s|rc/)', {
|
||||
items: [
|
||||
{ label: 'about/', resultText: 'html { background-image: url(../about/)' }
|
||||
]
|
||||
}, testUri, folders);
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user