Skip to content

feat: make IHTMLDataProvider async #199

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/htmlLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,20 @@ export * from './htmlLanguageTypes';
export interface LanguageService {
setDataProviders(useDefaultDataProvider: boolean, customDataProviders: IHTMLDataProvider[]): void;
createScanner(input: string, initialOffset?: number): Scanner;
parseHTMLDocument(document: TextDocument): HTMLDocument;
parseHTMLDocument(document: TextDocument): Promise<HTMLDocument>;
findDocumentHighlights(document: TextDocument, position: Position, htmlDocument: HTMLDocument): DocumentHighlight[];
doComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument, options?: CompletionConfiguration): CompletionList;
doComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument, options?: CompletionConfiguration): Promise<CompletionList>;
doComplete2(document: TextDocument, position: Position, htmlDocument: HTMLDocument, documentContext: DocumentContext, options?: CompletionConfiguration): Promise<CompletionList>;
setCompletionParticipants(registeredCompletionParticipants: ICompletionParticipant[]): void;
doHover(document: TextDocument, position: Position, htmlDocument: HTMLDocument, options?: HoverSettings): Hover | null;
doHover(document: TextDocument, position: Position, htmlDocument: HTMLDocument, options?: HoverSettings): Promise<Hover | null>;
format(document: TextDocument, range: Range | undefined, options: HTMLFormatConfiguration): TextEdit[];
findDocumentLinks(document: TextDocument, documentContext: DocumentContext): DocumentLink[];
findDocumentSymbols(document: TextDocument, htmlDocument: HTMLDocument): SymbolInformation[];
findDocumentSymbols2(document: TextDocument, htmlDocument: HTMLDocument): DocumentSymbol[];
doQuoteComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument, options?: CompletionConfiguration): string | null;
doTagComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument): string | null;
getFoldingRanges(document: TextDocument, context?: { rangeLimit?: number }): FoldingRange[];
getSelectionRanges(document: TextDocument, positions: Position[]): SelectionRange[];
doTagComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument): Promise<string | null>;
getFoldingRanges(document: TextDocument, context?: { rangeLimit?: number }): Promise<FoldingRange[]>;
getSelectionRanges(document: TextDocument, positions: Position[]): Promise<SelectionRange[]>;
doRename(document: TextDocument, position: Position, newName: string, htmlDocument: HTMLDocument): WorkspaceEdit | null;
findMatchingTagPosition(document: TextDocument, position: Position, htmlDocument: HTMLDocument): Position | null;
/** Deprecated, Use findLinkedEditingRanges instead */
Expand Down
6 changes: 3 additions & 3 deletions src/htmlLanguageTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,9 @@ export interface IHTMLDataProvider {
getId(): string;
isApplicable(languageId: string): boolean;

provideTags(): ITagData[];
provideAttributes(tag: string): IAttributeData[];
provideValues(tag: string, attribute: string): IValueData[];
provideTags(): Promise<ITagData[]> | ITagData[];
provideAttributes(tag: string): Promise<IAttributeData[]> | IAttributeData[];
provideValues(tag: string, attribute: string): Promise<IValueData[]> | IValueData[];
}

/**
Expand Down
13 changes: 7 additions & 6 deletions src/languageFacts/dataManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ export class HTMLDataManager {
return !!e && arrays.binarySearch(voidElements, e.toLowerCase(), (s1: string, s2: string) => s1.localeCompare(s2)) >= 0;
}

getVoidElements(languageId: string): string[];
getVoidElements(dataProviders: IHTMLDataProvider[]): string[];
getVoidElements(languageOrProviders: string | IHTMLDataProvider[]): string[] {
getVoidElements(languageId: string): Promise<string[]>;
getVoidElements(dataProviders: IHTMLDataProvider[]): Promise<string[]>;
async getVoidElements(languageOrProviders: string | IHTMLDataProvider[]): Promise<string[]> {
const dataProviders = Array.isArray(languageOrProviders) ? languageOrProviders : this.getDataProviders().filter(p => p.isApplicable(languageOrProviders!));
const voidTags: string[] = [];
dataProviders.forEach((provider) => {
provider.provideTags().filter(tag => tag.void).forEach(tag => voidTags.push(tag.name));
});
for (const provider of dataProviders) {
const tags = await provider.provideTags();
tags.filter(tag => tag.void).forEach(tag => voidTags.push(tag.name));
}
return voidTags.sort();
}

Expand Down
4 changes: 2 additions & 2 deletions src/parser/htmlParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ export class HTMLParser {

}

public parseDocument(document: TextDocument): HTMLDocument {
return this.parse(document.getText(), this.dataManager.getVoidElements(document.languageId));
public async parseDocument(document: TextDocument): Promise<HTMLDocument> {
return this.parse(document.getText(), await this.dataManager.getVoidElements(document.languageId));
}

public parse(text: string, voidElements: string[]): HTMLDocument {
Expand Down
66 changes: 35 additions & 31 deletions src/services/htmlCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class HTMLCompletion {
const contributedParticipants = this.completionParticipants;
this.completionParticipants = [participant as ICompletionParticipant].concat(contributedParticipants);

const result = this.doComplete(document, position, htmlDocument, settings);
const result = await this.doComplete(document, position, htmlDocument, settings);
try {
const pathCompletionResult = await participant.computeCompletions(document, documentContext);
return {
Expand All @@ -52,12 +52,12 @@ export class HTMLCompletion {
}
}

doComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument, settings?: CompletionConfiguration): CompletionList {
const result = this._doComplete(document, position, htmlDocument, settings);
async doComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument, settings?: CompletionConfiguration): Promise<CompletionList> {
const result = await this._doComplete(document, position, htmlDocument, settings);
return this.convertCompletionList(result);
}

private _doComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument, settings?: CompletionConfiguration): CompletionList {
private async _doComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument, settings?: CompletionConfiguration): Promise<CompletionList> {
const result: CompletionList = {
isIncomplete: false,
items: []
Expand Down Expand Up @@ -86,19 +86,20 @@ export class HTMLCompletion {
return { start: document.positionAt(replaceStart), end: document.positionAt(replaceEnd) };
}

function collectOpenTagSuggestions(afterOpenBracket: number, tagNameEnd?: number): CompletionList {
async function collectOpenTagSuggestions(afterOpenBracket: number, tagNameEnd?: number): Promise<CompletionList> {
const range = getReplaceRange(afterOpenBracket, tagNameEnd);
dataProviders.forEach((provider) => {
provider.provideTags().forEach(tag => {
for (const provider of dataProviders) {
const tags = await provider.provideTags();
for (const tag of tags) {
result.items.push({
label: tag.name,
kind: CompletionItemKind.Property,
documentation: generateDocumentation(tag, undefined, doesSupportMarkdown),
textEdit: TextEdit.replace(range, tag.name),
insertTextFormat: InsertTextFormat.PlainText
});
});
});
}
}
return result;
}

Expand All @@ -117,7 +118,7 @@ export class HTMLCompletion {
return text.substring(0, offset);
}

function collectCloseTagSuggestions(afterOpenBracket: number, inOpenTag: boolean, tagNameEnd: number = offset): CompletionList {
async function collectCloseTagSuggestions(afterOpenBracket: number, inOpenTag: boolean, tagNameEnd: number = offset): Promise<CompletionList> {
const range = getReplaceRange(afterOpenBracket, tagNameEnd);
const closeTag = isFollowedBy(text, tagNameEnd, ScannerState.WithinEndTag, TokenType.EndTagClose) ? '' : '>';
let curr: Node | undefined = node;
Expand Down Expand Up @@ -150,26 +151,27 @@ export class HTMLCompletion {
return result;
}

dataProviders.forEach(provider => {
provider.provideTags().forEach(tag => {
for (const provider of dataProviders) {
const tags = await provider.provideTags();
for (const tag of tags) {
result.items.push({
label: '/' + tag.name,
kind: CompletionItemKind.Property,
documentation: generateDocumentation(tag, undefined, doesSupportMarkdown),
filterText: '/' + tag.name + closeTag,
filterText: '/' + tag.name,
textEdit: TextEdit.replace(range, '/' + tag.name + closeTag),
insertTextFormat: InsertTextFormat.PlainText
});
});
});
}
}
return result;
}

const collectAutoCloseTagSuggestion = (tagCloseEnd: number, tag: string): CompletionList => {
const collectAutoCloseTagSuggestion = async (tagCloseEnd: number, tag: string): Promise<CompletionList> => {
if (settings && settings.hideAutoCompleteProposals) {
return result;
}
voidElements ??= this.dataManager.getVoidElements(dataProviders);
voidElements ??= await this.dataManager.getVoidElements(dataProviders);
if (!this.dataManager.isVoidElement(tag, voidElements)) {
const pos = document.positionAt(tagCloseEnd);
result.items.push({
Expand Down Expand Up @@ -197,7 +199,7 @@ export class HTMLCompletion {
return existingAttributes;
}

function collectAttributeNameSuggestions(nameStart: number, nameEnd: number = offset): CompletionList {
async function collectAttributeNameSuggestions(nameStart: number, nameEnd: number = offset): Promise<CompletionList> {
let replaceEnd = offset;
while (replaceEnd < nameEnd && text[replaceEnd] !== '<') { // < is a valid attribute name character, but we rather assume the attribute name ends. See #23236.
replaceEnd++;
Expand All @@ -220,10 +222,11 @@ export class HTMLCompletion {
// include current typing attribute
seenAttributes[currentAttribute] = false;

dataProviders.forEach(provider => {
provider.provideAttributes(currentTag).forEach(attr => {
for (const provider of dataProviders) {
const attributes = await provider.provideAttributes(currentTag);
for (const attr of attributes) {
if (seenAttributes[attr.name]) {
return;
continue;
}
seenAttributes[attr.name] = true;

Expand All @@ -247,8 +250,8 @@ export class HTMLCompletion {
insertTextFormat: InsertTextFormat.Snippet,
command
});
});
});
}
}
collectDataAttributesSuggestions(range, seenAttributes);
return result;
}
Expand Down Expand Up @@ -280,7 +283,7 @@ export class HTMLCompletion {
}));
}

function collectAttributeValueSuggestions(valueStart: number, valueEnd: number = offset): CompletionList {
async function collectAttributeValueSuggestions(valueStart: number, valueEnd: number = offset): Promise<CompletionList> {
let range: Range;
let addQuotes: boolean;
let valuePrefix: string;
Expand Down Expand Up @@ -315,20 +318,21 @@ export class HTMLCompletion {
}
}

dataProviders.forEach(provider => {
provider.provideValues(currentTag, currentAttributeName).forEach(value => {
for (const provider of dataProviders) {
const values = await provider.provideValues(currentTag, currentAttributeName);
for (const value of values) {
const insertText = addQuotes ? '"' + value.name + '"' : value.name;

result.items.push({
label: value.name,
filterText: insertText,
kind: CompletionItemKind.Unit,
documentation: generateDocumentation(value, undefined, doesSupportMarkdown),
textEdit: TextEdit.replace(range, insertText),
insertTextFormat: InsertTextFormat.PlainText
});
});
});
}

}
collectCharacterEntityProposals();
return result;
}
Expand Down Expand Up @@ -528,7 +532,7 @@ export class HTMLCompletion {
return null;
}

doTagComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument): string | null {
async doTagComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument): Promise<string | null> {
const offset = document.offsetAt(position);
if (offset <= 0) {
return null;
Expand All @@ -537,7 +541,7 @@ export class HTMLCompletion {
if (char === '>') {
const node = htmlDocument.findNodeBefore(offset);
if (node && node.tag && node.start < offset && (!node.endTagStart || node.endTagStart > offset)) {
const voidElements = this.dataManager.getVoidElements(document.languageId);
const voidElements = await this.dataManager.getVoidElements(document.languageId);
if (!this.dataManager.isVoidElement(node.tag, voidElements)) {
const scanner = createScanner(document.getText(), node.start);
let token = scanner.scan();
Expand Down
4 changes: 2 additions & 2 deletions src/services/htmlFolding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export class HTMLFolding {
return result;
}

public getFoldingRanges(document: TextDocument, context: { rangeLimit?: number } | undefined): FoldingRange[] {
public async getFoldingRanges(document: TextDocument, context: { rangeLimit?: number } | undefined): Promise<FoldingRange[]> {
const scanner = createScanner(document.getText());
let token = scanner.scan();
const ranges: FoldingRange[] = [];
Expand Down Expand Up @@ -114,7 +114,7 @@ export class HTMLFolding {
if (!lastTagName) {
break;
}
voidElements ??= this.dataManager.getVoidElements(document.languageId);
voidElements ??= await this.dataManager.getVoidElements(document.languageId);
if (!this.dataManager.isVoidElement(lastTagName, voidElements)) {
break;
}
Expand Down
23 changes: 13 additions & 10 deletions src/services/htmlHover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class HTMLHover {

constructor(private lsOptions: LanguageServiceOptions, private dataManager: HTMLDataManager) { }

doHover(document: TextDocument, position: Position, htmlDocument: HTMLDocument, options?: HoverSettings): Hover | null {
async doHover(document: TextDocument, position: Position, htmlDocument: HTMLDocument, options?: HoverSettings): Promise<Hover | null> {
const convertContents = this.convertContents.bind(this);
const doesSupportMarkdown = this.doesSupportMarkdown();

Expand All @@ -30,11 +30,12 @@ export class HTMLHover {
}
const dataProviders = this.dataManager.getDataProviders().filter(p => p.isApplicable(document.languageId));

function getTagHover(currTag: string, range: Range, open: boolean): Hover | null {
async function getTagHover(currTag: string, range: Range, open: boolean): Promise<Hover | null> {
for (const provider of dataProviders) {
let hover: Hover | null = null;

provider.provideTags().forEach(tag => {
const tags = await provider.provideTags();
for (const tag of tags) {
if (tag.name.toLowerCase() === currTag.toLowerCase()) {
let markupContent = generateDocumentation(tag, options, doesSupportMarkdown);
if (!markupContent) {
Expand All @@ -45,7 +46,7 @@ export class HTMLHover {
}
hover = { contents: markupContent, range };
}
});
}

if (hover) {
(hover as Hover).contents = convertContents((hover as Hover).contents);
Expand All @@ -55,11 +56,12 @@ export class HTMLHover {
return null;
}

function getAttrHover(currTag: string, currAttr: string, range: Range): Hover | null {
async function getAttrHover(currTag: string, currAttr: string, range: Range): Promise<Hover | null> {
for (const provider of dataProviders) {
let hover: Hover | null = null;

provider.provideAttributes(currTag).forEach(attr => {
const attributes = await provider.provideAttributes(currTag);
for (const attr of attributes) {
if (currAttr === attr.name && attr.description) {
const contentsDoc = generateDocumentation(attr, options, doesSupportMarkdown);
if (contentsDoc) {
Expand All @@ -68,7 +70,7 @@ export class HTMLHover {
hover = null;
}
}
});
}

if (hover) {
(hover as Hover).contents = convertContents((hover as Hover).contents);
Expand All @@ -78,11 +80,12 @@ export class HTMLHover {
return null;
}

function getAttrValueHover(currTag: string, currAttr: string, currAttrValue: string, range: Range): Hover | null {
async function getAttrValueHover(currTag: string, currAttr: string, currAttrValue: string, range: Range): Promise<Hover | null> {
for (const provider of dataProviders) {
let hover: Hover | null = null;

provider.provideValues(currTag, currAttr).forEach(attrValue => {
const values = await provider.provideValues(currTag, currAttr);
for (const attrValue of values) {
if (currAttrValue === attrValue.name && attrValue.description) {
const contentsDoc = generateDocumentation(attrValue, options, doesSupportMarkdown);
if (contentsDoc) {
Expand All @@ -91,7 +94,7 @@ export class HTMLHover {
hover = null;
}
}
});
}

if (hover) {
(hover as Hover).contents = convertContents((hover as Hover).contents);
Expand Down
4 changes: 2 additions & 2 deletions src/services/htmlSelectionRange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export class HTMLSelectionRange {
constructor(private htmlParser: HTMLParser) {
}

public getSelectionRanges(document: TextDocument, positions: Position[]): SelectionRange[] {
const htmlDocument = this.htmlParser.parseDocument(document);
public async getSelectionRanges(document: TextDocument, positions: Position[]): Promise<SelectionRange[]> {
const htmlDocument = await this.htmlParser.parseDocument(document);
return positions.map(p => this.getSelectionRange(p, document, htmlDocument));
}
private getSelectionRange(position: Position, document: TextDocument, htmlDocument: HTMLDocument): SelectionRange {
Expand Down
Loading