All files / providers mentor-uri-handler.ts

96.77% Statements 30/31
100% Branches 12/12
100% Functions 6/6
96.66% Lines 29/30

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                                        10x 120x         10x 10x 10x     10x 10x           8x 1x     7x   3x   3x         3x   3x 3x   3x 1x       2x   2x 1x 1x 1x   1x     1x         3x 3x 3x       3x   3x            
import * as vscode from 'vscode';
import { IPrefixLookupService } from '@src/services/document';
import { Store } from '@faubulous/mentor-rdf';
import { InferenceUri } from '@src/providers/inference-uri';
import { IViewRouter } from '@src/views/webviews';
import { SETTINGS_GROUPS, SettingsSectionId } from '@src/views/webviews/views/settings/sections';
 
/**
 * Single URI handler for the extension. VS Code allows only one handler per
 * extension, so all `vscode://faubulous.mentor/…` deep links are dispatched here
 * by path:
 *
 * - `/inference?uri=<graph>` — opens the inferred-triples graph in a Turtle editor.
 * - `/settings[?section=<id>]` — opens the settings panel, optionally on a section
 *   (e.g. `?section=query.connections`). Intended for linking from external docs.
 */
export class MentorUriHandler implements vscode.UriHandler {
	readonly extensionId: string;
 
	/** Valid settings section ids, derived from the single source of truth. */
	private static readonly _sectionIds = new Set<SettingsSectionId>(
		SETTINGS_GROUPS.flatMap(g => g.sections.map(s => s.id))
	);
 
	constructor(
		context: vscode.ExtensionContext,
		private readonly _store: Store,
		private readonly _prefixLookup: IPrefixLookupService,
		private readonly _router: IViewRouter
	) {
		// Self-register with the extension context for automatic disposal
		this.extensionId = context.extension.id;
		context.subscriptions.push(
			vscode.window.registerUriHandler(this)
		);
	}
 
	async handleUri(uri: vscode.Uri) {
		if (uri.authority !== this.extensionId) {
			return;
		}
 
		switch (uri.path) {
			case '/inference':
				return this.handleInference(uri);
			case '/settings':
				return this.handleSettings(uri);
		}
	}
 
	private async handleInference(uri: vscode.Uri) {
		try {
			// Parse the query parameter directly
			const query = new URLSearchParams(uri.query);
			const targetUri = query.get('uri');
 
			if (!targetUri) {
				throw new Error('No URI provided in inference request');
			}
 
			// Decode the URI parameter
			const inferenceUri = InferenceUri.toInferenceUri(targetUri);
 
			if (this._store.hasGraph(inferenceUri)) {
				const namespaces = this._prefixLookup.getInferencePrefixes();
				const content = await this._store.serializeGraph(inferenceUri, 'text/turtle', undefined, namespaces);
				const document = await vscode.workspace.openTextDocument({ content, language: 'turtle' });
 
				await vscode.window.showTextDocument(document);
			}
		} catch (error) {
			vscode.window.showErrorMessage(`Failed to load inference graph: ${error}`);
		}
	}
 
	private async handleSettings(uri: vscode.Uri) {
		try {
			const requested = new URLSearchParams(uri.query).get('section');
			const section = requested && MentorUriHandler._sectionIds.has(requested as SettingsSectionId)
				? requested as SettingsSectionId
				: undefined;
 
			const router = this._router;
			
			await router.open({ kind: 'settings', section }, vscode.ViewColumn.Active);
		} catch (error) {
			vscode.window.showErrorMessage(`Failed to open settings: ${error}`);
		}
	}
}