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 | 20x 20x 7x 20x 20x 20x 20x 20x 10x 10x 10x 2x 2x 5x 8x 8x 8x 8x 8x 8x 8x 8x 5x 1x 1x 1x 4x 4x 3x 3x 3x 1x 1x 2x 2x 2x 2x 3x 1x 1x 1x 1x 1x | import * as vscode from 'vscode';
import { NotSupportedError } from '@src/utilities/error';
import { getConfig } from '@src/utilities/vscode/config';
/**
* The URI scheme for editable template documents opened from the Mentor settings UI.
*/
const TEMPLATE_URI_SCHEME = 'mentor-template';
/**
* Maps a Mentor language id to the file extension used in template URIs so VS Code picks the
* matching grammar (and the triplate injection grammar) for the opened document.
*/
const LANGUAGE_EXTENSIONS: Record<string, string> = {
turtle: '.ttl',
sparql: '.sparql',
trig: '.trig',
n3: '.n3',
ntriples: '.nt',
nquads: '.nq',
};
function extensionForLanguage(language: string): string {
return LANGUAGE_EXTENSIONS[language] ?? '.txt';
}
const encoder = new TextEncoder();
const decoder = new TextDecoder();
/**
* Backs editable template documents under the `mentor-template` scheme. Two URI shapes are served:
*
* `mentor-template:/global/<configKey>.<ext>` — read/write the `mentor.<configKey>` setting.
* `mentor-template:/scratch/<token>.<ext>` — read/write an in-memory buffer; saving fires
* {@link onDidSaveScratch}.
*
* The scratch backing exists because per-store query-template overrides live only in the settings
* webview's transient draft until the store is saved — the host cannot read that draft. The webview
* seeds the buffer before opening the editor and listens for the save event to fold the edit back
* into its draft. Global templates need no such round-trip: writing the setting triggers
* `onDidChangeConfiguration`, which re-renders the settings textarea.
*/
export class TemplateFileSystemProvider implements vscode.FileSystemProvider {
private static readonly _scratch = new Map<string, string>();
private static readonly _onDidSaveScratch = new vscode.EventEmitter<{ token: string; content: string }>();
/** Fires after a scratch-backed template is saved, carrying the token and the new content. */
static readonly onDidSaveScratch = TemplateFileSystemProvider._onDidSaveScratch.event;
private readonly _onDidChangeFile = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
readonly onDidChangeFile = this._onDidChangeFile.event;
constructor(context: vscode.ExtensionContext) {
// Self-register with the extension context for automatic disposal.
context.subscriptions.push(
vscode.workspace.registerFileSystemProvider(TEMPLATE_URI_SCHEME, this, {
isCaseSensitive: true,
isReadonly: false
})
);
}
/** Seeds the in-memory buffer for a scratch token before its editor is opened. */
static seedScratch(token: string, content: string): void {
TemplateFileSystemProvider._scratch.set(token, content);
}
/** Builds the scratch URI for an opaque token and Mentor language id. */
static scratchUri(token: string, language: string): vscode.Uri {
return vscode.Uri.from({ scheme: TEMPLATE_URI_SCHEME, path: `/scratch/${token}${extensionForLanguage(language)}` });
}
/** Builds the config-backed URI for a `mentor.<configKey>` setting and Mentor language id. */
static globalUri(configKey: string, language: string): vscode.Uri {
return vscode.Uri.from({ scheme: TEMPLATE_URI_SCHEME, path: `/global/${configKey}${extensionForLanguage(language)}` });
}
/**
* Splits a template URI into its backing kind and identifier. The trailing extension (added only
* so VS Code resolves a grammar) is stripped; a config key keeps its embedded dots because only
* the final extension segment is removed.
*/
private static _parse(uri: vscode.Uri): { kind: 'global' | 'scratch'; id: string } {
const path = uri.path.replace(/^\/+/, '');
const slash = path.indexOf('/');
const kind = path.slice(0, slash) as 'global' | 'scratch';
let id = path.slice(slash + 1);
const dot = id.lastIndexOf('.');
Eif (dot >= 0) {
id = id.slice(0, dot);
}
return { kind, id };
}
private static _currentValue(kind: 'global' | 'scratch', id: string): string {
return kind === 'scratch'
? TemplateFileSystemProvider._scratch.get(id) ?? ''
: getConfig().get<string>(id) ?? '';
}
stat(uri: vscode.Uri): vscode.FileStat {
const { kind, id } = TemplateFileSystemProvider._parse(uri);
const value = TemplateFileSystemProvider._currentValue(kind, id);
// A fixed mtime keeps VS Code from treating repeated reads as external changes.
return { type: vscode.FileType.File, ctime: 0, mtime: 0, size: encoder.encode(value).length };
}
readFile(uri: vscode.Uri): Uint8Array {
const { kind, id } = TemplateFileSystemProvider._parse(uri);
return encoder.encode(TemplateFileSystemProvider._currentValue(kind, id));
}
async writeFile(uri: vscode.Uri, content: Uint8Array): Promise<void> {
const { kind, id } = TemplateFileSystemProvider._parse(uri);
const text = decoder.decode(content);
if (kind === 'scratch') {
TemplateFileSystemProvider._scratch.set(id, text);
TemplateFileSystemProvider._onDidSaveScratch.fire({ token: id, content: text });
} else {
// Write to the scope that currently holds the value so the editor change is not shadowed
// by a higher-precedence scope; default to User (Global) when the setting is unset.
const config = getConfig();
const inspected = config.inspect(id);
const target = inspected?.workspaceValue !== undefined
? vscode.ConfigurationTarget.Workspace
: vscode.ConfigurationTarget.Global;
await config.update(id, text, target);
}
this._onDidChangeFile.fire([{ type: vscode.FileChangeType.Changed, uri }]);
}
readDirectory(): [string, vscode.FileType][] {
return [];
}
createDirectory(): void {
throw new NotSupportedError();
}
delete(): void {
throw new NotSupportedError();
}
rename(): void {
throw new NotSupportedError();
}
watch(): vscode.Disposable {
return new vscode.Disposable(() => { });
}
}
|