All files / languages/turtle/providers turtle-prefix-completion-provider.ts

96.87% Statements 31/32
95.45% Branches 21/22
100% Functions 3/3
96.87% Lines 31/32

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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95                  33x     33x 33x 33x   33x       12x         12x 1x         11x 1x             10x   10x       10x     10x 5x     5x 5x   5x 1x     4x   4x 1x       3x   3x 1x     2x 2x   2x 1x         1x                     11x    
import * as vscode from 'vscode';
import { RdfToken } from '@faubulous/mentor-rdf-parsers';
import { getTokenIndexAtPosition } from '@src/utilities';
import { IDocumentContextService } from '@src/services/document';
import { IPrefixLookupService } from '@src/services/document';
import { TurtleDocument } from '@src/languages/turtle/turtle-document';
import { TurtleFeatureProvider } from '@src/languages/turtle/turtle-feature-provider';
 
export class TurtlePrefixCompletionProvider extends TurtleFeatureProvider implements vscode.InlineCompletionItemProvider {
	protected readonly prefixTokenTypes = new Set([RdfToken.PREFIX.name, RdfToken.TTL_PREFIX.name]);
 
	constructor(
		readonly onComplete: (uri: string) => string,
		private readonly _contextService: IDocumentContextService,
		private readonly _prefixLookup: IPrefixLookupService
	) {
		super();
	}
 
	provideInlineCompletionItems(document: vscode.TextDocument, position: vscode.Position, completion: vscode.InlineCompletionContext, token?: vscode.CancellationToken): vscode.ProviderResult<vscode.InlineCompletionItem[] | vscode.InlineCompletionList> {
		const context = this._contextService.getDocumentContext(document, TurtleDocument);
 
		// Inline completions are requested (and the previous request cancelled)
		// on essentially every keystroke; skip the full tokenization when the
		// result would be discarded anyway.
		if (!context || token?.isCancellationRequested) {
			return null;
		}
 
		// Only prefix declarations are completed inline. Checking the current line first
		// avoids tokenizing the document on the keystrokes of every other line.
		if (!this._isPrefixDeclarationLine(document.lineAt(position.line).text)) {
			return null;
		}
 
		// Tokenize the current document text so that completions are always based on
		// the up-to-date buffer content, also for documents that are not indexed (e.g.
		// untitled documents). The tokens are cached per document version and shared
		// with the other providers that run on the same keystroke.
		const tokens = context.tokenizeDocument(document);
 
		Iif (token?.isCancellationRequested) {
			return null;
		}
 
		const n = getTokenIndexAtPosition(tokens, position);
 
		// We also need the previous token to determine if this is a prefix definition.
		if (n < 1) {
			return null;
		}
 
		const currentToken = tokens[n];
		const currentType = currentToken.tokenType.name;
 
		if (!currentType || currentType !== RdfToken.PNAME_NS.name) {
			return;
		}
 
		const previousToken = tokens[n - 1];
 
		if (!previousToken) {
			return null;
		}
 
		// Only do inline completion for prefix defitions.
		const previousType = previousToken.tokenType.name;
 
		if (!previousType || !this.prefixTokenTypes.has(previousType)) {
			return null;
		}
 
		const prefix = currentToken.image.split(":")[0];
		const uri = this._prefixLookup.getUriForPrefix(document.uri.toString(), prefix);
 
		if (uri) {
			return [{
				insertText: this.onComplete(uri),
				range: new vscode.Range(position, position)
			}];
		} else {
			return [];
		}
	}
 
	/**
	 * Indicates whether a line may contain a prefix declaration, i.e. the Turtle `@prefix`
	 * or the SPARQL `PREFIX` keyword (case-insensitive). The token check that follows decides
	 * precisely; this only rules out the lines that certainly do not declare a prefix.
	 * @param lineText The text of the line at the cursor.
	 */
	private _isPrefixDeclarationLine(lineText: string): boolean {
		return /(^|\s)@?prefix\s/i.test(lineText);
	}
}