From 6058b8b0f6a24ad5b905d99d780a31b9e3d578d7 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 25 May 2020 12:02:30 +0200 Subject: Flatten commands.ts --- editors/code/src/commands/index.ts | 283 ------------------------------- editors/code/src/commands/runnables.ts | 218 ------------------------ editors/code/src/commands/syntax_tree.ts | 263 ---------------------------- 3 files changed, 764 deletions(-) delete mode 100644 editors/code/src/commands/index.ts delete mode 100644 editors/code/src/commands/runnables.ts delete mode 100644 editors/code/src/commands/syntax_tree.ts (limited to 'editors/code/src/commands') diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts deleted file mode 100644 index 1585912a2..000000000 --- a/editors/code/src/commands/index.ts +++ /dev/null @@ -1,283 +0,0 @@ -import * as vscode from 'vscode'; -import * as lc from 'vscode-languageclient'; -import * as ra from '../rust-analyzer-api'; - -import { Ctx, Cmd } from '../ctx'; -import { applySnippetWorkspaceEdit } from '../snippets'; -import { spawnSync } from 'child_process'; - -export * from './syntax_tree'; -export * from './runnables'; - -export function analyzerStatus(ctx: Ctx): Cmd { - const tdcp = new class implements vscode.TextDocumentContentProvider { - readonly uri = vscode.Uri.parse('rust-analyzer-status://status'); - readonly eventEmitter = new vscode.EventEmitter(); - - provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult { - if (!vscode.window.activeTextEditor) return ''; - - return ctx.client.sendRequest(ra.analyzerStatus, null); - } - - get onDidChange(): vscode.Event { - return this.eventEmitter.event; - } - }(); - - let poller: NodeJS.Timer | undefined = undefined; - - ctx.pushCleanup( - vscode.workspace.registerTextDocumentContentProvider( - 'rust-analyzer-status', - tdcp, - ), - ); - - ctx.pushCleanup({ - dispose() { - if (poller !== undefined) { - clearInterval(poller); - } - }, - }); - - return async () => { - if (poller === undefined) { - poller = setInterval(() => tdcp.eventEmitter.fire(tdcp.uri), 1000); - } - const document = await vscode.workspace.openTextDocument(tdcp.uri); - return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true); - }; -} - -export function matchingBrace(ctx: Ctx): Cmd { - return async () => { - const editor = ctx.activeRustEditor; - const client = ctx.client; - if (!editor || !client) return; - - const response = await client.sendRequest(ra.matchingBrace, { - textDocument: { uri: editor.document.uri.toString() }, - positions: editor.selections.map(s => - client.code2ProtocolConverter.asPosition(s.active), - ), - }); - editor.selections = editor.selections.map((sel, idx) => { - const active = client.protocol2CodeConverter.asPosition( - response[idx], - ); - const anchor = sel.isEmpty ? active : sel.anchor; - return new vscode.Selection(anchor, active); - }); - editor.revealRange(editor.selection); - }; -} - -export function joinLines(ctx: Ctx): Cmd { - return async () => { - const editor = ctx.activeRustEditor; - const client = ctx.client; - if (!editor || !client) return; - - const items: lc.TextEdit[] = await client.sendRequest(ra.joinLines, { - ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)), - textDocument: { uri: editor.document.uri.toString() }, - }); - editor.edit((builder) => { - client.protocol2CodeConverter.asTextEdits(items).forEach((edit) => { - builder.replace(edit.range, edit.newText); - }); - }); - }; -} - -export function onEnter(ctx: Ctx): Cmd { - async function handleKeypress() { - const editor = ctx.activeRustEditor; - const client = ctx.client; - - if (!editor || !client) return false; - - const change = await client.sendRequest(ra.onEnter, { - textDocument: { uri: editor.document.uri.toString() }, - position: client.code2ProtocolConverter.asPosition( - editor.selection.active, - ), - }).catch(_error => { - // client.logFailedRequest(OnEnterRequest.type, error); - return null; - }); - if (!change) return false; - - const workspaceEdit = client.protocol2CodeConverter.asWorkspaceEdit(change); - await applySnippetWorkspaceEdit(workspaceEdit); - return true; - } - - return async () => { - if (await handleKeypress()) return; - - await vscode.commands.executeCommand('default:type', { text: '\n' }); - }; -} - -export function parentModule(ctx: Ctx): Cmd { - return async () => { - const editor = ctx.activeRustEditor; - const client = ctx.client; - if (!editor || !client) return; - - const response = await client.sendRequest(ra.parentModule, { - textDocument: { uri: editor.document.uri.toString() }, - position: client.code2ProtocolConverter.asPosition( - editor.selection.active, - ), - }); - const loc = response[0]; - if (loc == null) return; - - const uri = client.protocol2CodeConverter.asUri(loc.uri); - const range = client.protocol2CodeConverter.asRange(loc.range); - - const doc = await vscode.workspace.openTextDocument(uri); - const e = await vscode.window.showTextDocument(doc); - e.selection = new vscode.Selection(range.start, range.start); - e.revealRange(range, vscode.TextEditorRevealType.InCenter); - }; -} - -export function ssr(ctx: Ctx): Cmd { - return async () => { - const client = ctx.client; - if (!client) return; - - const options: vscode.InputBoxOptions = { - value: "() ==>> ()", - prompt: "Enter request, for example 'Foo($a:expr) ==> Foo::new($a)' ", - validateInput: async (x: string) => { - try { - await client.sendRequest(ra.ssr, { query: x, parseOnly: true }); - } catch (e) { - return e.toString(); - } - return null; - } - }; - const request = await vscode.window.showInputBox(options); - if (!request) return; - - const edit = await client.sendRequest(ra.ssr, { query: request, parseOnly: false }); - - await vscode.workspace.applyEdit(client.protocol2CodeConverter.asWorkspaceEdit(edit)); - }; -} - -export function serverVersion(ctx: Ctx): Cmd { - return async () => { - const { stdout } = spawnSync(ctx.serverPath, ["--version"], { encoding: "utf8" }); - const commitHash = stdout.slice(`rust-analyzer `.length).trim(); - const { releaseTag } = ctx.config.package; - - void vscode.window.showInformationMessage( - `rust-analyzer version: ${releaseTag ?? "unreleased"} (${commitHash})` - ); - }; -} - -export function toggleInlayHints(ctx: Ctx): Cmd { - return async () => { - await vscode - .workspace - .getConfiguration(`${ctx.config.rootSection}.inlayHints`) - .update('enable', !ctx.config.inlayHints.enable, vscode.ConfigurationTarget.Workspace); - }; -} - -// Opens the virtual file that will show the syntax tree -// -// The contents of the file come from the `TextDocumentContentProvider` -export function expandMacro(ctx: Ctx): Cmd { - function codeFormat(expanded: ra.ExpandedMacro): string { - let result = `// Recursive expansion of ${expanded.name}! macro\n`; - result += '// ' + '='.repeat(result.length - 3); - result += '\n\n'; - result += expanded.expansion; - - return result; - } - - const tdcp = new class implements vscode.TextDocumentContentProvider { - uri = vscode.Uri.parse('rust-analyzer://expandMacro/[EXPANSION].rs'); - eventEmitter = new vscode.EventEmitter(); - async provideTextDocumentContent(_uri: vscode.Uri): Promise { - const editor = vscode.window.activeTextEditor; - const client = ctx.client; - if (!editor || !client) return ''; - - const position = editor.selection.active; - - const expanded = await client.sendRequest(ra.expandMacro, { - textDocument: { uri: editor.document.uri.toString() }, - position, - }); - - if (expanded == null) return 'Not available'; - - return codeFormat(expanded); - } - - get onDidChange(): vscode.Event { - return this.eventEmitter.event; - } - }(); - - ctx.pushCleanup( - vscode.workspace.registerTextDocumentContentProvider( - 'rust-analyzer', - tdcp, - ), - ); - - return async () => { - const document = await vscode.workspace.openTextDocument(tdcp.uri); - tdcp.eventEmitter.fire(tdcp.uri); - return vscode.window.showTextDocument( - document, - vscode.ViewColumn.Two, - true, - ); - }; -} - -export function collectGarbage(ctx: Ctx): Cmd { - return async () => ctx.client.sendRequest(ra.collectGarbage, null); -} - -export function showReferences(ctx: Ctx): Cmd { - return (uri: string, position: lc.Position, locations: lc.Location[]) => { - const client = ctx.client; - if (client) { - vscode.commands.executeCommand( - 'editor.action.showReferences', - vscode.Uri.parse(uri), - client.protocol2CodeConverter.asPosition(position), - locations.map(client.protocol2CodeConverter.asLocation), - ); - } - }; -} - -export function applyActionGroup(_ctx: Ctx): Cmd { - return async (actions: { label: string; edit: vscode.WorkspaceEdit }[]) => { - const selectedAction = await vscode.window.showQuickPick(actions); - if (!selectedAction) return; - await applySnippetWorkspaceEdit(selectedAction.edit); - }; -} - -export function applySnippetWorkspaceEditCommand(_ctx: Ctx): Cmd { - return async (edit: vscode.WorkspaceEdit) => { - await applySnippetWorkspaceEdit(edit); - }; -} diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts deleted file mode 100644 index 0bd30fb07..000000000 --- a/editors/code/src/commands/runnables.ts +++ /dev/null @@ -1,218 +0,0 @@ -import * as vscode from 'vscode'; -import * as lc from 'vscode-languageclient'; -import * as ra from '../rust-analyzer-api'; - -import { Ctx, Cmd } from '../ctx'; -import { startDebugSession, getDebugConfiguration } from '../debug'; - -const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }]; - -async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, debuggeeOnly = false, showButtons: boolean = true): Promise { - const editor = ctx.activeRustEditor; - const client = ctx.client; - if (!editor || !client) return; - - const textDocument: lc.TextDocumentIdentifier = { - uri: editor.document.uri.toString(), - }; - - const runnables = await client.sendRequest(ra.runnables, { - textDocument, - position: client.code2ProtocolConverter.asPosition( - editor.selection.active, - ), - }); - const items: RunnableQuickPick[] = []; - if (prevRunnable) { - items.push(prevRunnable); - } - for (const r of runnables) { - if ( - prevRunnable && - JSON.stringify(prevRunnable.runnable) === JSON.stringify(r) - ) { - continue; - } - - if (debuggeeOnly && (r.label.startsWith('doctest') || r.label.startsWith('cargo'))) { - continue; - } - items.push(new RunnableQuickPick(r)); - } - - if (items.length === 0) { - // it is the debug case, run always has at least 'cargo check ...' - // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_runnables - vscode.window.showErrorMessage("There's no debug target!"); - return; - } - - return await new Promise((resolve) => { - const disposables: vscode.Disposable[] = []; - const close = (result?: RunnableQuickPick) => { - resolve(result); - disposables.forEach(d => d.dispose()); - }; - - const quickPick = vscode.window.createQuickPick(); - quickPick.items = items; - quickPick.title = "Select Runnable"; - if (showButtons) { - quickPick.buttons = quickPickButtons; - } - disposables.push( - quickPick.onDidHide(() => close()), - quickPick.onDidAccept(() => close(quickPick.selectedItems[0])), - quickPick.onDidTriggerButton((_button) => { - (async () => await makeDebugConfig(ctx, quickPick.activeItems[0]))(); - close(); - }), - quickPick.onDidChangeActive((active) => { - if (showButtons && active.length > 0) { - if (active[0].label.startsWith('cargo')) { - // save button makes no sense for `cargo test` or `cargo check` - quickPick.buttons = []; - } else if (quickPick.buttons.length === 0) { - quickPick.buttons = quickPickButtons; - } - } - }), - quickPick - ); - quickPick.show(); - }); -} - -export function run(ctx: Ctx): Cmd { - let prevRunnable: RunnableQuickPick | undefined; - - return async () => { - const item = await selectRunnable(ctx, prevRunnable); - if (!item) return; - - item.detail = 'rerun'; - prevRunnable = item; - const task = createTask(item.runnable); - return await vscode.tasks.executeTask(task); - }; -} - -export function runSingle(ctx: Ctx): Cmd { - return async (runnable: ra.Runnable) => { - const editor = ctx.activeRustEditor; - if (!editor) return; - - const task = createTask(runnable); - task.group = vscode.TaskGroup.Build; - task.presentationOptions = { - reveal: vscode.TaskRevealKind.Always, - panel: vscode.TaskPanelKind.Dedicated, - clear: true, - }; - - return vscode.tasks.executeTask(task); - }; -} - -export function debug(ctx: Ctx): Cmd { - let prevDebuggee: RunnableQuickPick | undefined; - - return async () => { - const item = await selectRunnable(ctx, prevDebuggee, true); - if (!item) return; - - item.detail = 'restart'; - prevDebuggee = item; - return await startDebugSession(ctx, item.runnable); - }; -} - -export function debugSingle(ctx: Ctx): Cmd { - return async (config: ra.Runnable) => { - await startDebugSession(ctx, config); - }; -} - -async function makeDebugConfig(ctx: Ctx, item: RunnableQuickPick): Promise { - const scope = ctx.activeRustEditor?.document.uri; - if (!scope) return; - - const debugConfig = await getDebugConfiguration(ctx, item.runnable); - if (!debugConfig) return; - - const wsLaunchSection = vscode.workspace.getConfiguration("launch", scope); - const configurations = wsLaunchSection.get("configurations") || []; - - const index = configurations.findIndex(c => c.name === debugConfig.name); - if (index !== -1) { - const answer = await vscode.window.showErrorMessage(`Launch configuration '${debugConfig.name}' already exists!`, 'Cancel', 'Update'); - if (answer === "Cancel") return; - - configurations[index] = debugConfig; - } else { - configurations.push(debugConfig); - } - - await wsLaunchSection.update("configurations", configurations); -} - -export function newDebugConfig(ctx: Ctx): Cmd { - return async () => { - const item = await selectRunnable(ctx, undefined, true, false); - if (!item) return; - - await makeDebugConfig(ctx, item); - }; -} - -class RunnableQuickPick implements vscode.QuickPickItem { - public label: string; - public description?: string | undefined; - public detail?: string | undefined; - public picked?: boolean | undefined; - - constructor(public runnable: ra.Runnable) { - this.label = runnable.label; - } -} - -interface CargoTaskDefinition extends vscode.TaskDefinition { - type: 'cargo'; - label: string; - command: string; - args: string[]; - env?: { [key: string]: string }; -} - -function createTask(spec: ra.Runnable): vscode.Task { - const TASK_SOURCE = 'Rust'; - const definition: CargoTaskDefinition = { - type: 'cargo', - label: spec.label, - command: spec.bin, - args: spec.extraArgs ? [...spec.args, '--', ...spec.extraArgs] : spec.args, - env: spec.env, - }; - - const execOption: vscode.ShellExecutionOptions = { - cwd: spec.cwd || '.', - env: definition.env, - }; - const exec = new vscode.ShellExecution( - definition.command, - definition.args, - execOption, - ); - - const f = vscode.workspace.workspaceFolders![0]; - const t = new vscode.Task( - definition, - f, - definition.label, - TASK_SOURCE, - exec, - ['$rustc'], - ); - t.presentationOptions.clear = true; - return t; -} diff --git a/editors/code/src/commands/syntax_tree.ts b/editors/code/src/commands/syntax_tree.ts deleted file mode 100644 index a5446c327..000000000 --- a/editors/code/src/commands/syntax_tree.ts +++ /dev/null @@ -1,263 +0,0 @@ -import * as vscode from 'vscode'; -import * as ra from '../rust-analyzer-api'; - -import { Ctx, Cmd, Disposable } from '../ctx'; -import { isRustDocument, RustEditor, isRustEditor, sleep } from '../util'; - -const AST_FILE_SCHEME = "rust-analyzer"; - -// Opens the virtual file that will show the syntax tree -// -// The contents of the file come from the `TextDocumentContentProvider` -export function syntaxTree(ctx: Ctx): Cmd { - const tdcp = new TextDocumentContentProvider(ctx); - - void new AstInspector(ctx); - - ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider(AST_FILE_SCHEME, tdcp)); - ctx.pushCleanup(vscode.languages.setLanguageConfiguration("ra_syntax_tree", { - brackets: [["[", ")"]], - })); - - return async () => { - const editor = vscode.window.activeTextEditor; - const rangeEnabled = !!editor && !editor.selection.isEmpty; - - const uri = rangeEnabled - ? vscode.Uri.parse(`${tdcp.uri.toString()}?range=true`) - : tdcp.uri; - - const document = await vscode.workspace.openTextDocument(uri); - - tdcp.eventEmitter.fire(uri); - - void await vscode.window.showTextDocument(document, { - viewColumn: vscode.ViewColumn.Two, - preserveFocus: true - }); - }; -} - -class TextDocumentContentProvider implements vscode.TextDocumentContentProvider { - readonly uri = vscode.Uri.parse('rust-analyzer://syntaxtree/tree.rast'); - readonly eventEmitter = new vscode.EventEmitter(); - - - constructor(private readonly ctx: Ctx) { - vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions); - vscode.window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, ctx.subscriptions); - } - - private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) { - if (isRustDocument(event.document)) { - // We need to order this after language server updates, but there's no API for that. - // Hence, good old sleep(). - void sleep(10).then(() => this.eventEmitter.fire(this.uri)); - } - } - private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined) { - if (editor && isRustEditor(editor)) { - this.eventEmitter.fire(this.uri); - } - } - - provideTextDocumentContent(uri: vscode.Uri, ct: vscode.CancellationToken): vscode.ProviderResult { - const rustEditor = this.ctx.activeRustEditor; - if (!rustEditor) return ''; - - // When the range based query is enabled we take the range of the selection - const range = uri.query === 'range=true' && !rustEditor.selection.isEmpty - ? this.ctx.client.code2ProtocolConverter.asRange(rustEditor.selection) - : null; - - const params = { textDocument: { uri: rustEditor.document.uri.toString() }, range, }; - return this.ctx.client.sendRequest(ra.syntaxTree, params, ct); - } - - get onDidChange(): vscode.Event { - return this.eventEmitter.event; - } -} - - -// FIXME: consider implementing this via the Tree View API? -// https://code.visualstudio.com/api/extension-guides/tree-view -class AstInspector implements vscode.HoverProvider, vscode.DefinitionProvider, Disposable { - private readonly astDecorationType = vscode.window.createTextEditorDecorationType({ - borderColor: new vscode.ThemeColor('rust_analyzer.syntaxTreeBorder'), - borderStyle: "solid", - borderWidth: "2px", - - }); - private rustEditor: undefined | RustEditor; - - // Lazy rust token range -> syntax tree file range. - private readonly rust2Ast = new Lazy(() => { - const astEditor = this.findAstTextEditor(); - if (!this.rustEditor || !astEditor) return undefined; - - const buf: [vscode.Range, vscode.Range][] = []; - for (let i = 0; i < astEditor.document.lineCount; ++i) { - const astLine = astEditor.document.lineAt(i); - - // Heuristically look for nodes with quoted text (which are token nodes) - const isTokenNode = astLine.text.lastIndexOf('"') >= 0; - if (!isTokenNode) continue; - - const rustRange = this.parseRustTextRange(this.rustEditor.document, astLine.text); - if (!rustRange) continue; - - buf.push([rustRange, this.findAstNodeRange(astLine)]); - } - return buf; - }); - - constructor(ctx: Ctx) { - ctx.pushCleanup(vscode.languages.registerHoverProvider({ scheme: AST_FILE_SCHEME }, this)); - ctx.pushCleanup(vscode.languages.registerDefinitionProvider({ language: "rust" }, this)); - vscode.workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, ctx.subscriptions); - vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions); - vscode.window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, ctx.subscriptions); - - ctx.pushCleanup(this); - } - dispose() { - this.setRustEditor(undefined); - } - - private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) { - if (this.rustEditor && event.document.uri.toString() === this.rustEditor.document.uri.toString()) { - this.rust2Ast.reset(); - } - } - - private onDidCloseTextDocument(doc: vscode.TextDocument) { - if (this.rustEditor && doc.uri.toString() === this.rustEditor.document.uri.toString()) { - this.setRustEditor(undefined); - } - } - - private onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]) { - if (!this.findAstTextEditor()) { - this.setRustEditor(undefined); - return; - } - this.setRustEditor(editors.find(isRustEditor)); - } - - private findAstTextEditor(): undefined | vscode.TextEditor { - return vscode.window.visibleTextEditors.find(it => it.document.uri.scheme === AST_FILE_SCHEME); - } - - private setRustEditor(newRustEditor: undefined | RustEditor) { - if (this.rustEditor && this.rustEditor !== newRustEditor) { - this.rustEditor.setDecorations(this.astDecorationType, []); - this.rust2Ast.reset(); - } - this.rustEditor = newRustEditor; - } - - // additional positional params are omitted - provideDefinition(doc: vscode.TextDocument, pos: vscode.Position): vscode.ProviderResult { - if (!this.rustEditor || doc.uri.toString() !== this.rustEditor.document.uri.toString()) return; - - const astEditor = this.findAstTextEditor(); - if (!astEditor) return; - - const rust2AstRanges = this.rust2Ast.get()?.find(([rustRange, _]) => rustRange.contains(pos)); - if (!rust2AstRanges) return; - - const [rustFileRange, astFileRange] = rust2AstRanges; - - astEditor.revealRange(astFileRange); - astEditor.selection = new vscode.Selection(astFileRange.start, astFileRange.end); - - return [{ - targetRange: astFileRange, - targetUri: astEditor.document.uri, - originSelectionRange: rustFileRange, - targetSelectionRange: astFileRange, - }]; - } - - // additional positional params are omitted - provideHover(doc: vscode.TextDocument, hoverPosition: vscode.Position): vscode.ProviderResult { - if (!this.rustEditor) return; - - const astFileLine = doc.lineAt(hoverPosition.line); - - const rustFileRange = this.parseRustTextRange(this.rustEditor.document, astFileLine.text); - if (!rustFileRange) return; - - this.rustEditor.setDecorations(this.astDecorationType, [rustFileRange]); - this.rustEditor.revealRange(rustFileRange); - - const rustSourceCode = this.rustEditor.document.getText(rustFileRange); - const astFileRange = this.findAstNodeRange(astFileLine); - - return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astFileRange); - } - - private findAstNodeRange(astLine: vscode.TextLine): vscode.Range { - const lineOffset = astLine.range.start; - const begin = lineOffset.translate(undefined, astLine.firstNonWhitespaceCharacterIndex); - const end = lineOffset.translate(undefined, astLine.text.trimEnd().length); - return new vscode.Range(begin, end); - } - - private parseRustTextRange(doc: vscode.TextDocument, astLine: string): undefined | vscode.Range { - const parsedRange = /(\d+)\.\.(\d+)/.exec(astLine); - if (!parsedRange) return; - - const [begin, end] = parsedRange - .slice(1) - .map(off => this.positionAt(doc, +off)); - - return new vscode.Range(begin, end); - } - - // Memoize the last value, otherwise the CPU is at 100% single core - // with quadratic lookups when we build rust2Ast cache - cache?: { doc: vscode.TextDocument; offset: number; line: number }; - - positionAt(doc: vscode.TextDocument, targetOffset: number): vscode.Position { - if (doc.eol === vscode.EndOfLine.LF) { - return doc.positionAt(targetOffset); - } - - // Dirty workaround for crlf line endings - // We are still in this prehistoric era of carriage returns here... - - let line = 0; - let offset = 0; - - const cache = this.cache; - if (cache?.doc === doc && cache.offset <= targetOffset) { - ({ line, offset } = cache); - } - - while (true) { - const lineLenWithLf = doc.lineAt(line).text.length + 1; - if (offset + lineLenWithLf > targetOffset) { - this.cache = { doc, offset, line }; - return doc.positionAt(targetOffset + line); - } - offset += lineLenWithLf; - line += 1; - } - } -} - -class Lazy { - val: undefined | T; - - constructor(private readonly compute: () => undefined | T) { } - - get() { - return this.val ?? (this.val = this.compute()); - } - - reset() { - this.val = undefined; - } -} -- cgit v1.2.3