diff --git a/extensions/html/server/src/modes/pathCompletion.ts b/extensions/html/server/src/modes/pathCompletion.ts index 9e1c659cad2..27c347013e7 100644 --- a/extensions/html/server/src/modes/pathCompletion.ts +++ b/extensions/html/server/src/modes/pathCompletion.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { TextDocument, CompletionList, CompletionItemKind, CompletionItem } from 'vscode-languageserver-types'; +import { TextDocument, CompletionList, CompletionItemKind, CompletionItem, TextEdit, Range, Position } from 'vscode-languageserver-types'; import { WorkspaceFolder } from 'vscode-languageserver-protocol/lib/protocol.workspaceFolders.proposed'; import * as path from 'path'; import * as fs from 'fs'; @@ -32,7 +32,7 @@ export function getPathCompletionParticipant( workspaceRoot = resolveWorkspaceRoot(document, workspaceFolders); } - const suggestions = providePathSuggestions(value, URI.parse(document.uri).fsPath, workspaceRoot); + const suggestions = providePathSuggestions(value, range, URI.parse(document.uri).fsPath, workspaceRoot); result.items = [...suggestions, ...result.items]; } } @@ -55,7 +55,7 @@ function shouldDoPathCompletion(tag: string, attr: string, value: string): boole return false; } -export function providePathSuggestions(value: string, activeDocFsPath: string, root?: string): CompletionItem[] { +export function providePathSuggestions(value: string, range: Range, activeDocFsPath: string, root?: string): CompletionItem[] { if (value.indexOf('/') === -1) { return []; } @@ -65,8 +65,8 @@ export function providePathSuggestions(value: string, activeDocFsPath: string, r } const lastIndexOfSlash = value.lastIndexOf('/'); - const valueAfterLastSlash = value.slice(lastIndexOfSlash + 1); const valueBeforeLastSlash = value.slice(0, lastIndexOfSlash + 1); + const valueAfterLastSlash = value.slice(lastIndexOfSlash + 1); const parentDir = startsWith(value, '/') ? path.resolve(root, '.' + valueBeforeLastSlash) : path.resolve(activeDocFsPath, '..', valueBeforeLastSlash); @@ -75,11 +75,13 @@ export function providePathSuggestions(value: string, activeDocFsPath: string, r return []; } + const replaceRange = Range.create(Position.create(range.end.line, range.end.character - valueAfterLastSlash.length), range.end); + return fs.readdirSync(parentDir).map(f => { return { label: f, kind: isDir(path.resolve(parentDir, f)) ? CompletionItemKind.Folder : CompletionItemKind.File, - insertText: f.slice(valueAfterLastSlash.length) + textEdit: TextEdit.replace(replaceRange, f) }; }); } diff --git a/extensions/html/server/src/test/pathCompletion/pathCompletion.test.ts b/extensions/html/server/src/test/pathCompletion/pathCompletion.test.ts index ef45f6e4d1b..908ad285442 100644 --- a/extensions/html/server/src/test/pathCompletion/pathCompletion.test.ts +++ b/extensions/html/server/src/test/pathCompletion/pathCompletion.test.ts @@ -7,16 +7,17 @@ import * as assert from 'assert'; import * as path from 'path'; import { providePathSuggestions } from '../../modes/pathCompletion'; -import { CompletionItemKind } from 'vscode-languageserver/lib/main'; +import { CompletionItemKind, Range, Position } from 'vscode-languageserver-types'; const fixtureRoot = path.resolve(__dirname, '../../../test/pathCompletionFixtures'); suite('Path Completion - Relative Path', () => { + const mockRange = Range.create(Position.create(0, 3), Position.create(0, 5)); test('Current Folder', () => { const value = './'; const activeFileFsPath = path.resolve(fixtureRoot, 'index.html'); - const suggestions = providePathSuggestions(value, activeFileFsPath); + const suggestions = providePathSuggestions(value, mockRange, activeFileFsPath); assert.equal(suggestions.length, 3); assert.equal(suggestions[0].label, 'about'); @@ -31,7 +32,7 @@ suite('Path Completion - Relative Path', () => { test('Parent Folder', () => { const value = '../'; const activeFileFsPath = path.resolve(fixtureRoot, 'about/about.html'); - const suggestions = providePathSuggestions(value, activeFileFsPath); + const suggestions = providePathSuggestions(value, mockRange, activeFileFsPath); assert.equal(suggestions.length, 3); assert.equal(suggestions[0].label, 'about'); @@ -46,7 +47,7 @@ suite('Path Completion - Relative Path', () => { test('Adjacent Folder', () => { const value = '../src/'; const activeFileFsPath = path.resolve(fixtureRoot, 'about/about.html'); - const suggestions = providePathSuggestions(value, activeFileFsPath); + const suggestions = providePathSuggestions(value, mockRange, activeFileFsPath); assert.equal(suggestions.length, 2); assert.equal(suggestions[0].label, 'feature.js'); @@ -60,13 +61,15 @@ suite('Path Completion - Relative Path', () => { }); suite('Path Completion - Absolute Path', () => { + const mockRange = Range.create(Position.create(0, 3), Position.create(0, 5)); + test('Root', () => { const value = '/'; const activeFileFsPath1 = path.resolve(fixtureRoot, 'index.html'); const activeFileFsPath2 = path.resolve(fixtureRoot, 'about/index.html'); - const suggestions1 = providePathSuggestions(value, activeFileFsPath1, fixtureRoot); - const suggestions2 = providePathSuggestions(value, activeFileFsPath2, fixtureRoot); + const suggestions1 = providePathSuggestions(value, mockRange, activeFileFsPath1, fixtureRoot); + const suggestions2 = providePathSuggestions(value, mockRange, activeFileFsPath2, fixtureRoot); const verify = (suggestions) => { assert.equal(suggestions[0].label, 'about'); @@ -85,7 +88,7 @@ suite('Path Completion - Absolute Path', () => { test('Sub Folder', () => { const value = '/src/'; const activeFileFsPath = path.resolve(fixtureRoot, 'about/about.html'); - const suggestions = providePathSuggestions(value, activeFileFsPath, fixtureRoot); + const suggestions = providePathSuggestions(value, mockRange, activeFileFsPath, fixtureRoot); assert.equal(suggestions.length, 2); assert.equal(suggestions[0].label, 'feature.js'); @@ -97,10 +100,12 @@ suite('Path Completion - Absolute Path', () => { }); suite('Path Completion - Incomplete Path at End', () => { + const mockRange = Range.create(Position.create(0, 3), Position.create(0, 5)); + test('Incomplete Path that starts with slash', () => { const value = '/src/f'; const activeFileFsPath = path.resolve(fixtureRoot, 'about/about.html'); - const suggestions = providePathSuggestions(value, activeFileFsPath, fixtureRoot); + const suggestions = providePathSuggestions(value, mockRange, activeFileFsPath, fixtureRoot); assert.equal(suggestions.length, 2); assert.equal(suggestions[0].label, 'feature.js'); @@ -113,7 +118,7 @@ suite('Path Completion - Incomplete Path at End', () => { test('Incomplete Path that does not start with slash', () => { const value = '../src/f'; const activeFileFsPath = path.resolve(fixtureRoot, 'about/about.html'); - const suggestions = providePathSuggestions(value, activeFileFsPath, fixtureRoot); + const suggestions = providePathSuggestions(value, mockRange, activeFileFsPath, fixtureRoot); assert.equal(suggestions.length, 2); assert.equal(suggestions[0].label, 'feature.js'); @@ -122,4 +127,22 @@ suite('Path Completion - Incomplete Path at End', () => { assert.equal(suggestions[0].kind, CompletionItemKind.File); assert.equal(suggestions[1].kind, CompletionItemKind.File); }); -}); \ No newline at end of file +}); + +suite('Path Completion - TextEdit', () => { + test('TextEdit has correct replace text and range', () => { + const value = './'; + const activeFileFsPath = path.resolve(fixtureRoot, 'index.html'); + const range = Range.create(Position.create(0, 3), Position.create(0, 5)); + const suggestions = providePathSuggestions(value, range, activeFileFsPath); + + assert.equal(suggestions[0].textEdit.newText, 'about'); + assert.equal(suggestions[1].textEdit.newText, 'index.html'); + assert.equal(suggestions[2].textEdit.newText, 'src'); + + assert.equal(suggestions[0].textEdit.range.start.character, 5); + assert.equal(suggestions[1].textEdit.range.start.character, 5); + assert.equal(suggestions[2].textEdit.range.start.character, 5); + }); +}); +