Skip to content

Commit 0df7584

Browse files
committed
Add auto completion for attribute tags
1 parent 94e5422 commit 0df7584

File tree

5 files changed

+74
-5
lines changed

5 files changed

+74
-5
lines changed

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11

2+
4.2.0 / 2021-11-08
3+
==================
4+
* Added new API `htmlLanguageService.doQuoteComplete`. Called after an `attribute=`, it will compute either `""` or `''` depending on `CompletionConfiguration.attributeDefaultValue` or null, if no quote completion should be performed.
5+
26
4.1.0 / 2021-09-27
37
==================
48
* New settings `CompletionConfiguration.attributeDefaultValue`. Defines how attribute values are completed: With single or double quotes, or no quotes.
@@ -11,12 +15,12 @@
1115
3.2.0 / 2020-11-30
1216
==================
1317
* New parameter `HoverSettings` for `LanguageService.doHover`: Defines whether the hover contains element documentation and/or a reference to MDN.
14-
* Deprecated `LanguageService.findOnTypeRenameRanges`, replaced by New API `LanguageService.findLinkedEditingRanges`.
18+
* Deprecated `LanguageService.findOnTypeRenameRanges`, replaced by New API `LanguageService.findLinkedEditingRanges`.
1519

1620
3.1.0 / 2020-07-29
1721
==================
1822
* Use `TextDocument` from `vscode-languageserver-textdocument`
19-
* Fix formatting for `<p>` tags with optional closing
23+
* Fix formatting for `<p>` tags with optional closing
2024
* New API `LanguageService.findOnTypeRenameRanges`. For a given position, find the matching close tag so they can be renamed synchronously.
2125
* New API `LanguageServiceOptions.customDataProviders` to add the knowledge of custom tags, attributes and attribute-values and `LanguageService.setDataProviders` to update the data providers.
2226
* New API `getDefaultHTMLDataProvider` to get the default HTML data provider and `newHTMLDataProvider` to create a new provider from data.

src/htmlLanguageService.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export interface LanguageService {
3939
format(document: TextDocument, range: Range | undefined, options: HTMLFormatConfiguration): TextEdit[];
4040
findDocumentLinks(document: TextDocument, documentContext: DocumentContext): DocumentLink[];
4141
findDocumentSymbols(document: TextDocument, htmlDocument: HTMLDocument): SymbolInformation[];
42+
doQuoteComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument, options?: CompletionConfiguration): string | null;
4243
doTagComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument): string | null;
4344
getFoldingRanges(document: TextDocument, context?: { rangeLimit?: number }): FoldingRange[];
4445
getSelectionRanges(document: TextDocument, positions: Position[]): SelectionRange[];
@@ -71,6 +72,7 @@ export function getLanguageService(options: LanguageServiceOptions = defaultLang
7172
findDocumentSymbols,
7273
getFoldingRanges,
7374
getSelectionRanges,
75+
doQuoteComplete: htmlCompletion.doQuoteComplete.bind(htmlCompletion),
7476
doTagComplete: htmlCompletion.doTagComplete.bind(htmlCompletion),
7577
doRename,
7678
findMatchingTagPosition,
@@ -86,4 +88,3 @@ export function newHTMLDataProvider(id: string, customData: HTMLDataV1): IHTMLDa
8688
export function getDefaultHTMLDataProvider(): IHTMLDataProvider {
8789
return newHTMLDataProvider('default', htmlData);
8890
}
89-

src/services/htmlCompletion.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,44 @@ export class HTMLCompletion {
490490
return result;
491491
}
492492

493+
doQuoteComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument, settings?: CompletionConfiguration): string | null {
494+
const offset = document.offsetAt(position);
495+
if (offset <= 0) {
496+
return null;
497+
}
498+
const defaultValue = settings?.attributeDefaultValue ?? 'doublequotes';
499+
if (defaultValue === 'empty') {
500+
return null;
501+
}
502+
const char = document.getText().charAt(offset - 1);
503+
if (char !== '=') {
504+
return null;
505+
}
506+
const value = defaultValue === 'doublequotes' ? '""' : '\'\'';
507+
const node = htmlDocument.findNodeBefore(offset);
508+
if (node && node.attributes && node.start < offset && (!node.endTagStart || node.endTagStart > offset)) {
509+
const scanner = createScanner(document.getText(), node.start);
510+
let token = scanner.scan();
511+
while (token !== TokenType.EOS && scanner.getTokenEnd() <= offset) {
512+
if (token === TokenType.AttributeName&& scanner.getTokenEnd() === offset - 1) {
513+
// Ensure the token is a valid standalone attribute name
514+
token = scanner.scan(); // this should be the = just written
515+
if (token !== TokenType.DelimiterAssign) {
516+
return null;
517+
}
518+
token = scanner.scan();
519+
// Any non-attribute valid tag
520+
if (token === TokenType.Unknown || token === TokenType.AttributeValue) {
521+
return null;
522+
}
523+
return value;
524+
}
525+
token = scanner.scan();
526+
}
527+
}
528+
return null;
529+
}
530+
493531
doTagComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument): string | null {
494532
const offset = document.offsetAt(position);
495533
if (offset <= 0) {

src/test/completion.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { testCompletionFor, testTagCompletion } from "./completionUtil";
6+
import { testCompletionFor, testQuoteCompletion, testTagCompletion } from "./completionUtil";
77

88
suite('HTML Completion', () => {
99
test('Complete', function (): any {
@@ -428,6 +428,19 @@ suite('HTML Completion', () => {
428428
);
429429
});
430430

431+
test('doQuoteComplete', function (): any {
432+
testQuoteCompletion('<a foo=|', '""');
433+
testQuoteCompletion('<a foo=|', '\'\'', { attributeDefaultValue: 'singlequotes'});
434+
testQuoteCompletion('<a foo=|', null, { attributeDefaultValue: 'empty'});
435+
testQuoteCompletion('<a foo=|=', null);
436+
testQuoteCompletion('<a foo=|></a>', '""');
437+
testQuoteCompletion('<a foo="bar=|"', null);
438+
testQuoteCompletion('<a baz=| foo="bar">', '""');
439+
testQuoteCompletion('<a>< foo=| /a>', null);
440+
testQuoteCompletion('<a></ foo=| a>', null);
441+
testQuoteCompletion('<a foo="bar" \n baz=| ></a>', '""');
442+
});
443+
431444
test('doTagComplete', function (): any {
432445
testTagCompletion('<div>|', '$0</div>');
433446
testTagCompletion('<div>|</div>', null);

src/test/completionUtil.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import * as assert from 'assert';
77
import * as htmlLanguageService from '../htmlLanguageService';
88

9-
import { TextDocument, CompletionList, CompletionItemKind, MarkupContent, TextEdit } from '../htmlLanguageService';
9+
import { TextDocument, CompletionList, CompletionItemKind, MarkupContent, TextEdit, CompletionConfiguration } from '../htmlLanguageService';
1010

1111
interface ItemDescription {
1212
label: string;
@@ -79,6 +79,19 @@ export function testCompletionFor(value: string, expected: { count?: number, ite
7979
}
8080
}
8181

82+
export function testQuoteCompletion(value: string, expected: string | null, options?: CompletionConfiguration): void {
83+
const offset = value.indexOf('|');
84+
value = value.substr(0, offset) + value.substr(offset + 1);
85+
86+
const ls = htmlLanguageService.getLanguageService();
87+
88+
const document = TextDocument.create('test://test/test.html', 'html', 0, value);
89+
const position = document.positionAt(offset);
90+
const htmlDoc = ls.parseHTMLDocument(document);
91+
const actual = ls.doQuoteComplete(document, position, htmlDoc, options);
92+
assert.strictEqual(actual, expected);
93+
}
94+
8295
export function testTagCompletion(value: string, expected: string | null): void {
8396
const offset = value.indexOf('|');
8497
value = value.substr(0, offset) + value.substr(offset + 1);

0 commit comments

Comments
 (0)