// ***************************************************************************** // Copyright (C) 2018 TypeFox and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Based on https://github.com/theia-ide/vscode/blob/standalone/0.19.x/src/vs/workbench/contrib/debug/common/debugModel.ts import * as React from '@theia/core/shared/react'; import { DISABLED_CLASS } from '@theia/core/lib/browser'; import { EditorWidget, Range, Position, EditorOpenerOptions } from '@theia/editor/lib/browser'; import { DebugProtocol } from '@vscode/debugprotocol/lib/debugProtocol'; import { TreeElement } from '@theia/core/lib/browser/source-tree'; import { DebugScope } from '../console/debug-console-items'; import { DebugSource } from './debug-source'; import { RecursivePartial } from '@theia/core'; import { DebugSession } from '../debug-session'; import { DebugThread } from './debug-thread'; import * as monaco from '@theia/monaco-editor-core'; import { stringHash } from '@theia/core/lib/common/hash'; export class DebugStackFrameData { readonly raw: DebugProtocol.StackFrame; } export class DebugStackFrame extends DebugStackFrameData implements TreeElement { constructor( readonly thread: DebugThread, readonly session: DebugSession, readonly id: string ) { super(); } /** * Returns the frame identifier from the debug protocol. */ get frameId(): number { return this.raw.id; } protected _source: DebugSource | undefined; get source(): DebugSource | undefined { return this._source; } update(data: Partial): void { Object.assign(this, data); this._source = this.raw.source && this.session.getSource(this.raw.source); } async restart(): Promise { await this.session.sendRequest('restartFrame', this.toArgs({ threadId: this.thread.id })); } async open(options?: EditorOpenerOptions): Promise { if (!this.source) { return undefined; } const { line, column, endLine, endColumn } = this.raw; const selection: RecursivePartial = { start: Position.create(this.clampPositive(line - 1), this.clampPositive(column - 1)) }; if (typeof endLine === 'number') { selection.end = { line: this.clampPositive(endLine - 1), character: typeof endColumn === 'number' ? this.clampPositive(endColumn - 1) : undefined }; } this.source.open({ mode: 'reveal', ...options, selection }); } /** * Debugger can send `column: 0` value despite of initializing the debug session with `columnsStartAt1: true`. * This method can be used to ensure that neither `column` nor `column` are negative numbers. * See https://github.com/microsoft/vscode-mock-debug/issues/85. */ protected clampPositive(value: number): number { return Math.max(value, 0); } protected scopes: Promise | undefined; getScopes(): Promise { return this.scopes || (this.scopes = this.doGetScopes()); } protected async doGetScopes(): Promise { let response; try { response = await this.session.sendRequest('scopes', this.toArgs()); } catch { // no-op: ignore debug adapter errors } if (!response) { return []; } const scopeIds = new Set(); return response.body.scopes.map(raw => { // just as in VS Code, the id is based on the name and location to retain expansion state across multiple pauses let id = 0; do { id = stringHash(`${raw.name}:${raw.line}:${raw.column}`, id); } while (scopeIds.has(id)); scopeIds.add(id); return new DebugScope(raw, () => this.session, id); }); } // https://github.com/theia-ide/vscode/blob/standalone/0.19.x/src/vs/workbench/contrib/debug/common/debugModel.ts#L324-L335 async getMostSpecificScopes(range: monaco.IRange): Promise { const scopes = await this.getScopes(); const nonExpensiveScopes = scopes.filter(s => !s.expensive); const haveRangeInfo = nonExpensiveScopes.some(s => !!s.range); if (!haveRangeInfo) { return nonExpensiveScopes; } const scopesContainingRange = nonExpensiveScopes.filter(scope => scope.range && monaco.Range.containsRange(scope.range, range)) .sort((first, second) => (first.range!.endLineNumber - first.range!.startLineNumber) - (second.range!.endLineNumber - second.range!.startLineNumber)); return scopesContainingRange.length ? scopesContainingRange : nonExpensiveScopes; } protected toArgs(arg?: T): { frameId: number } & T { return Object.assign({}, arg, { frameId: this.raw.id }); } render(): React.ReactNode { const classNames = ['theia-debug-stack-frame']; if (this.raw.presentationHint === 'label') { classNames.push('label'); } if (this.raw.presentationHint === 'subtle') { classNames.push('subtle'); } if (!this.source || this.source.raw.presentationHint === 'deemphasize') { classNames.push(DISABLED_CLASS); } return
{this.raw.name} {this.renderFile()}
; } protected renderFile(): React.ReactNode { const { source } = this; if (!source) { return undefined; } const origin = source.raw.origin && `\n${source.raw.origin}` || ''; return {source.name} {this.raw.line}:{this.raw.column} ; } get range(): monaco.IRange | undefined { const { source, line: startLine, column: startColumn, endLine, endColumn } = this.raw; if (source) { return new monaco.Range(startLine, startColumn, endLine || startLine, endColumn || startColumn); } return undefined; } }