All files / languages/triplate/providers triplate-hover-provider.ts

100% Statements 23/23
100% Branches 14/14
100% Functions 4/4
100% Lines 22/22

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58          9x     10x   10x 1x     9x 9x   9x 1x       8x 12x   10x 6x   6x   2x         6x   6x 6x   6x               9x 9x 8x       1x      
import * as vscode from 'vscode';
import { isTemplate } from 'triplate';
import { TriplateCompileCache } from '../triplate-compile-cache';
 
export class TriplateHoverProvider implements vscode.HoverProvider {
	private readonly _cache = new TriplateCompileCache();
 
	provideHover(document: vscode.TextDocument, position: vscode.Position): vscode.ProviderResult<vscode.Hover> {
		const text = document.getText();
 
		if (!isTemplate(text)) {
			return null;
		}
 
		const offset = document.offsetAt(position);
		const name = this._getInterpolationNameAt(text, offset);
 
		if (!name) {
			return null;
		}
 
		// `triplate` is the single source of truth for the parameter schema.
		const compiled = this._cache.get(document);
		const declaration = compiled?.schema.params.find(p => p.name === name);
 
		if (declaration) {
			const typeLabel = `${declaration.type.base.kind}${declaration.type.array ? '[]' : ''}`;
 
			return this._hover(name, typeLabel, declaration.type.optional);
		} else {
			return null;
		}
	}
 
	private _hover(name: string, typeLabel: string, optional: boolean): vscode.Hover {
		const attrs = optional ? ' ยท optional' : '';
 
		const content = new vscode.MarkdownString();
		content.appendMarkdown(`**${name}** ${typeLabel}${attrs}`);
 
		return new vscode.Hover(content);
	}
 
	/**
	 * Returns the name of the triplate `${name}` interpolation that contains `offset`,
	 * or `null` if the offset is not inside any interpolation.
	 */
	private _getInterpolationNameAt(text: string, offset: number): string | null {
		for (const m of text.matchAll(/\$\{([^}]*)\}/g)) {
			if (m.index! <= offset && offset < m.index! + m[0].length) {
				return m[1];
			}
		}
		
		return null;
	}
}