All files / views/trees/definition-tree definition-node-decoration-provider.ts

100% Statements 57/57
100% Branches 39/39
100% Functions 10/10
100% Lines 57/57

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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158                      1x       1x       1x       1x               25x   25x   25x   25x   25x         7x       25x       40x       25x     25x 11x 1x   1x       25x 13x   12x   1x       25x   1x         26x   26x   1x     1x     24x           15x   15x 3x     12x   12x 1x 1x 1x   1x     11x 1x     10x     2x     8x 1x     7x 15x   15x   15x 6x 2x     4x     2x       2x     5x 2x 2x 2x   2x      
import * as vscode from 'vscode';
import { NamedNode, VocabularyRepository } from '@faubulous/mentor-rdf';
import { container } from 'tsyringe';
import { ServiceToken } from '@src/services/tokens';
import { ISettingsService } from '@src/services/core';
import { IDocumentContextService } from '@src/services/document';
import { getConfig } from '@src/utilities/vscode/config';
 
/**
 * Indicates the where missing language tags should be decorated.
 */
enum MissingLanguageTagDecorationScope {
	/**
	 * Disable the decoration of missing language tags.
	 */
	Disabled,
	/**
	 * Decorate missing language tags in all sources.
	 */
	All,
	/**
	 * Decorate missing language tags only in the active document.
	 */
	Document
}
 
/**
 * A decoration provider that adds a badge to definition tree nodes.
 */
export class DefinitionNodeDecorationProvider implements vscode.FileDecorationProvider {
 
	private readonly _warningColor = new vscode.ThemeColor("list.warningForeground");
 
	private readonly _disabledColor = new vscode.ThemeColor("descriptionForeground");
 
	private _labelPredicates = new Set<string>();
 
	private readonly _onDidChangeFileDecorations = new vscode.EventEmitter<vscode.Uri | vscode.Uri[] | undefined>();
 
	readonly onDidChangeFileDecorations? = this._onDidChangeFileDecorations.event;
 
	private _decorationScope: MissingLanguageTagDecorationScope;
 
	private get _vocabulary() {
		return container.resolve<VocabularyRepository>(ServiceToken.VocabularyRepository);
	}
 
	private get _settings() {
		return container.resolve<ISettingsService>(ServiceToken.SettingsService);
	}
 
	private get _contextService() {
		return container.resolve<IDocumentContextService>(ServiceToken.DocumentContextService);
	}
 
	constructor() {
		this._decorationScope = this._getDecorationScopeFromConfiguration();
 
		// If the configuration for decorating missing language tags changes, update the decoration provider.
		vscode.workspace.onDidChangeConfiguration((e) => {
			if (e.affectsConfiguration('mentor.definitionTree.decorateMissingLanguageTags')) {
				this._decorationScope = this._getDecorationScopeFromConfiguration();
 
				this._onDidChangeFileDecorations.fire(undefined);
			}
		});
 
		this._contextService.onDidChangeDocumentContext((context) => {
			if (context) {
				// When the context changes, the label predicates need to be updated.
				this._labelPredicates = new Set(context?.predicates.label ?? []);
			} else {
				this._labelPredicates = new Set();
			}
		});
 
		this._settings.onDidChange("view.activeLanguage", () => {
			// When the active language changes, the decorations need to be updated.
			this._onDidChangeFileDecorations.fire(undefined);
		});
	}
 
	private _getDecorationScopeFromConfiguration(): MissingLanguageTagDecorationScope {
		const result = getConfig().get('definitionTree.decorateMissingLanguageTags');
 
		switch (result) {
			case 'Document': {
				return MissingLanguageTagDecorationScope.Document;
			}
			case 'All': {
				return MissingLanguageTagDecorationScope.All;
			}
			default: {
				return MissingLanguageTagDecorationScope.Disabled;
			}
		}
	}
 
	provideFileDecoration(uri: vscode.Uri, token: vscode.CancellationToken) {
		const context = this._contextService.activeContext;
 
		if (!context || !uri || uri.scheme === 'mentor' || uri.scheme === 'file') {
			return undefined;
		}
 
		const node = new NamedNode(uri.toString());
 
		if (!context.subjects[node.value]) {
			const result = new vscode.FileDecoration(undefined, undefined, this._disabledColor);
			result.propagate = false;
			result.tooltip = `This subject is not defined in the active document.`;
 
			return result;
		}
 
		if (this._decorationScope === MissingLanguageTagDecorationScope.Disabled) {
			return undefined;
		}
 
		if (!context.primaryLanguage || !context.activeLanguage) {
			// Note: The document may not have a language set if 
			// there are no language tags used in the document.
			return undefined;
		}
 
		if (!context.references[node.value]) {
			return undefined;
		}
 
		const graphUris = this._decorationScope === MissingLanguageTagDecorationScope.Document ? context.graphs : undefined;
		const activeLanguage = context.activeLanguage;
 
		let hasLabels = false;
 
		for (let triple of this._vocabulary.store.matchAll(graphUris, node, null, null, false)) {
			if (triple.object.termType !== "Literal" || !this._labelPredicates.has(triple.predicate.value)) {
				continue;
			}
 
			if (!triple.object.language || triple.object.language.startsWith(activeLanguage)) {
				// Either there is no language tag (valid for all languages) 
				// or the language tag is in the active language.
				return undefined;
			}
 
			// Only enable the decoration if the subject is a subject in the configured graphs (document or entire background).
			hasLabels = true;
		}
 
		if (hasLabels) {
			const result = new vscode.FileDecoration(undefined, undefined, this._warningColor);
			result.propagate = true;
			result.tooltip = `This definition is not available in the active language @${activeLanguage}.`;
 
			return result;
		}
	}
}