All files / services/core workspace-service.ts

100% Statements 44/44
81.25% Branches 13/16
100% Functions 9/9
100% Lines 44/44

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                  45x   45x         7x       18x       5x       19x 19x 19x   19x   19x 1x     18x   18x 15x 15x       15x 15x 15x   15x 15x 15x           18x               18x   18x 15x     3x 3x   3x               15x 15x 14x   14x   2x               43x 43x   43x                   13x   13x                   38x       38x       38x 38x 38x   38x                                    
import * as vscode from 'vscode';
import { Utils } from 'vscode-uri';
import { WorkspaceDescriptor, IWorkspaceService } from './workspace-service.interface';
 
/**
 * Service for discovering VS Code workspace files in the project directory
 * and providing fast access to their identifiers and paths.
 */
export class WorkspaceService implements IWorkspaceService {
	private _workspaces: WorkspaceDescriptor[] = [];
 
	private readonly _workspaceMap = new Map<string, WorkspaceDescriptor>();
 
	private _activeRootUri: vscode.Uri | undefined;
 
	get activeRootUri(): vscode.Uri | undefined {
		return this._activeRootUri;
	}
 
	get workspaces(): ReadonlyArray<WorkspaceDescriptor> {
		return this._workspaces;
	}
 
	getWorkspaceById(id: string): WorkspaceDescriptor | undefined {
		return this._workspaceMap.get(id);
	}
 
	async discoverWorkspaces(): Promise<void> {
		this._workspaces = [];
		this._workspaceMap.clear();
		this._activeRootUri = undefined;
 
		const folders = vscode.workspace.workspaceFolders;
 
		if (!folders || folders.length === 0) {
			return;
		}
 
		const files = await vscode.workspace.findFiles('**/*.code-workspace');
 
		for (const fileUri of files) {
			for (const folder of folders) {
				const folderPath = folder.uri.path.endsWith('/')
					? folder.uri.path
					: folder.uri.path + '/';
 
				Eif (fileUri.path.startsWith(folderPath)) {
					const content = await WorkspaceService.readWorkspaceFile(fileUri);
					const descriptor = WorkspaceService.createDescriptor(fileUri, folder.uri, content);
 
					this._workspaces.push(descriptor);
					this._workspaceMap.set(descriptor.id, descriptor);
					break;
				}
			}
		}
 
		// Determine the active root URI from the currently-open workspace file.
		this._activeRootUri = this._resolveActiveRootUri();
	}
 
	/**
	 * Determines the monorepo root URI for the currently active workspace.
	 * Uses `vscode.workspace.workspaceFile` to find the active `.code-workspace` descriptor.
	 */
	private _resolveActiveRootUri(): vscode.Uri | undefined {
		const workspaceFile = vscode.workspace.workspaceFile;
 
		if (!workspaceFile) {
			return undefined;
		}
 
		const activeId = WorkspaceService.getIdFromFilename(workspaceFile.path);
		const descriptor = this._workspaceMap.get(activeId);
 
		return descriptor?.rootUri;
	}
 
	/**
	 * Reads and parses a `.code-workspace` JSON file.
	 * Returns `undefined` if the file cannot be read or parsed.
	 */
	static async readWorkspaceFile(fileUri: vscode.Uri): Promise<WorkspaceFileContent | undefined> {
		try {
			const bytes = await vscode.workspace.fs.readFile(fileUri);
			const text = new TextDecoder().decode(bytes);
 
			return JSON.parse(text);
		} catch {
			return undefined;
		}
	}
 
	/**
	 * Extracts the workspace ID from a filename path.
	 */
	static getIdFromFilename(path: string): string {
		const parts = path.split('/');
		const fileName = parts[parts.length - 1];
 
		return fileName.replace(/\.code-workspace$/, '');
	}
 
	/**
	 * Resolves the monorepo root URI from a `.code-workspace` file location and a root offset.
	 * @param workspaceFileUri The URI of the `.code-workspace` file.
	 * @param rootOffset A relative path from the workspace file's directory to the monorepo root (e.g. `"."` or `".."`).
	 * @returns The resolved root URI, with a normalised path (no trailing segments like `/..`).
	 */
	static resolveRootUri(workspaceFileUri: vscode.Uri, rootOffset: string): vscode.Uri {
		const workspaceDir = Utils.dirname(workspaceFileUri);
 
		return Utils.resolvePath(workspaceDir, rootOffset);
	}
 
	/**
	 * Creates a workspace descriptor from a file URI, workspace root URI, and parsed file content.
	 * @param fileUri The URI of the `.code-workspace` file.
	 * @param folderUri The URI of the workspace folder containing the file.
	 * @param content Parsed content of the `.code-workspace` file, if available.
	 */
	static createDescriptor(fileUri: vscode.Uri, folderUri: vscode.Uri, content?: WorkspaceFileContent): WorkspaceDescriptor {
		const rootPath = folderUri.path.endsWith('/')
			? folderUri.path
			: folderUri.path + '/';
 
		const relativePath = fileUri.path.startsWith(rootPath)
			? fileUri.path.substring(rootPath.length)
			: fileUri.path;
 
		const id = WorkspaceService.getIdFromFilename(fileUri.path);
		const rootOffset = content?.settings?.['mentor.workspace.rootOffset'];
		const rootUri = rootOffset ? WorkspaceService.resolveRootUri(fileUri, rootOffset) : undefined;
 
		return {
			id,
			uri: fileUri,
			absolutePath: fileUri.fsPath,
			relativePath,
			rootOffset,
			rootUri,
		};
	}
}
 
/**
 * Minimal shape of a `.code-workspace` JSON file, covering only the fields we need.
 */
export interface WorkspaceFileContent {
	folders?: { path: string }[];
	settings?: { [key: string]: any };
}