All files / languages/sparql/services sparql-status-bar-service.ts

98.03% Statements 50/51
100% Branches 14/14
92.3% Functions 12/13
98.03% Lines 50/51

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 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181                                  18x               18x                       18x                         18x           18x         18x 18x   18x         18x 18x   18x   18x   18x 1x 4x   3x 3x     1x 1x     3x 3x     2x 2x     4x   3x   1x   4x 4x     3x   3x 2x   3x                                       39x   39x 7x     39x     5x 5x   5x     39x 39x                 28x   28x   28x 12x     28x         28x       1x   1x 8x        
import * as vscode from 'vscode';
import { ISparqlConnectionRegistry, ISparqlEndpointTester, ISparqlQueryService, IGraphManagementService } from '@src/languages/sparql/services';
import { getDisplayName } from '@src/languages/sparql/services/sparql-query-state';
import { SparqlConnection } from '@src/languages/sparql/services/sparql-connection';
 
/**
 * Owns a single, permanently visible SPARQL status bar item that opens the SPARQL
 * results panel when clicked. Its label reflects all current activity at once —
 * query execution / connection testing and named-graph loading are composed into one
 * label — and reverts to a summary of the configured connections and loaded graphs
 * when nothing is in progress.
 */
export class SparqlStatusBarService implements vscode.Disposable {
 
	/** 
	 * Separator between activity segments when several are active at once.
	 */
	private readonly _segmentSeparator = ' · ';
 
	/**
	 * Status bar priority. The low value keeps the item at the far right of the
	 * left-aligned status bar — after built-in items such as "Auto Attach"
	 * (priority 0) — so it stays with the other Mentor items, as the last one
	 * after the indexer (-10001) and validation (-10002) items.
	 */
	private readonly _priority = -10003;
 
	/**
	 * The status bar item owned by this service. It is created once and kept alive 
	 * for the entire extension lifetime, and its label is updated to reflect the 
	 * current state of SPARQL-related activity.
	 */
	private readonly _statusBarItem: vscode.StatusBarItem;
 
	/**
	 * Subscriptions being disposed of when this service is disposed.
	 */
	private readonly _subscriptions: vscode.Disposable[] = [];
 
	/**
	 * The current query-execution / connection-test segment, if any.
	 */
	private _activitySegment: string | undefined;
 
	/**
	 * Currently in-flight graph loads, keyed by connection ID, in start order.
	 * Graph loads can run concurrently (e.g. multiple connections auto-loading
	 * at once), so this map — rather than a single connection — tracks all of
	 * them; the oldest still-active entry is shown as the "current" one.
	 */
	private readonly _activeGraphLoads = new Map<string, SparqlConnection>();
 
	/**
	 * Total number of graph loads in the current batch, used to show progress
	 * like "Loading graphs 2 of 5: https://example.org/sparql"
	 */
	private _totalGraphLoads = 0;
 
	constructor(
		queryService: ISparqlQueryService,
		endpointTester: ISparqlEndpointTester,
		private readonly _graphService: IGraphManagementService,
		private readonly _connectionRegistry: ISparqlConnectionRegistry
	) {
		this._statusBarItem = vscode.window.createStatusBarItem(
			vscode.StatusBarAlignment.Left,
			this._priority
		);
 
		this._statusBarItem.command = 'mentor.command.showSparqlPanel';
		this._statusBarItem.tooltip = 'Show SPARQL Panel';
 
		this._render();
 
		const graphService = this._graphService;
 
		this._subscriptions.push(
			this._connectionRegistry.onDidChangeConnections(() => this._render()),
			graphService.onDidChangeGraphs(() => this._render()),
			queryService.onDidQueryExecutionStart(state => {
				this._activitySegment = `$(sync~spin) Executing: ${getDisplayName(state)}`;
				this._render();
			}),
			queryService.onDidQueryExecutionEnd(() => {
				this._activitySegment = undefined;
				this._render();
			}),
			endpointTester.onDidConnectionTestStart(connection => {
				this._activitySegment = `$(sync~spin) Testing: ${connection.endpointUrl}`;
				this._render();
			}),
			endpointTester.onDidConnectionTestEnd(() => {
				this._activitySegment = undefined;
				this._render();
			}),
			graphService.onDidGraphLoadStart(connection => {
				if (this._activeGraphLoads.size === 0) {
					// First load of this batch — count total from scratch.
					this._totalGraphLoads = 1;
				} else {
					this._totalGraphLoads++;
				}
				this._activeGraphLoads.set(connection.id, connection);
				this._render();
			}),
			graphService.onDidGraphLoadEnd(connection => {
				this._activeGraphLoads.delete(connection.id);
 
				if (this._activeGraphLoads.size === 0) {
					this._totalGraphLoads = 0;
				}
				this._render();
			})
		);
	}
 
	/**
	 * The currently rendered status bar text. The status bar item itself is not
	 * reachable through the VS Code API, so consistency checks (e2e tests) read
	 * the rendered label through this getter.
	 */
	get statusBarText(): string {
		return this._statusBarItem.text;
	}
 
	/**
	 * Composes the item text from all active states and keeps it visible. Falls back
	 * to a summary of the configured connections and loaded graphs when nothing is
	 * in progress.
	 */
	private _render(): void {
		const segments: string[] = [];
 
		if (this._activitySegment) {
			segments.push(this._activitySegment);
		}
 
		if (this._activeGraphLoads.size > 0) {
			// The oldest still-active load is shown as "current" — with concurrent
			// loads this is the one that has been in flight the longest.
			const current = this._activeGraphLoads.values().next().value!;
			const done = this._totalGraphLoads - this._activeGraphLoads.size;
 
			segments.push(`$(sync~spin) Loading graphs ${done + 1} of ${this._totalGraphLoads}: ${current.endpointUrl}`);
		}
 
		this._statusBarItem.text = segments.length > 0 ? segments.join(this._segmentSeparator) : this._getSummaryLabel();
		this._statusBarItem.show();
	}
 
	/**
	 * Builds the idle label summarizing the configured connections and the total
	 * number of loaded named graphs, in the same style as the workspace index item.
	 * @returns The summary label, e.g. `$(arrow-swap) 2 connections; 12 graphs`.
	 */
	private _getSummaryLabel(): string {
		const connections = this._connectionRegistry.getConnections();
 
		let graphCount = 0;
 
		for (const connection of connections) {
			graphCount += this._graphService.getGraphsForConnection(connection.id, false).length;
		}
 
		const parts = [
			`${connections.length} Connection${connections.length === 1 ? '' : 's'}`,
			`${graphCount} graph${graphCount === 1 ? '' : 's'}`,
		];
 
		return `$(arrow-swap) ${parts.join('; ')}`;
	}
 
	dispose(): void {
		this._statusBarItem.dispose();
 
		for (const sub of this._subscriptions) {
			sub.dispose();
		}
	}
}