deploy: current vibn theia state
Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,407 @@
|
||||
// *****************************************************************************
|
||||
// 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
|
||||
// *****************************************************************************
|
||||
import '../../../src/browser/style/debug.css';
|
||||
|
||||
import { ConsoleSessionManager } from '@theia/console/lib/browser/console-session-manager';
|
||||
import { ConsoleOptions, ConsoleWidget } from '@theia/console/lib/browser/console-widget';
|
||||
import { AbstractViewContribution, bindViewContribution, codicon, HoverService, Widget, WidgetFactory } from '@theia/core/lib/browser';
|
||||
import { ContextKey, ContextKeyService } from '@theia/core/lib/browser/context-key-service';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import { Command, CommandRegistry } from '@theia/core/lib/common/command';
|
||||
import { Severity } from '@theia/core/lib/common/severity';
|
||||
import { inject, injectable, interfaces, postConstruct } from '@theia/core/shared/inversify';
|
||||
import * as React from '@theia/core/shared/react';
|
||||
import { SelectComponent, SelectOption } from '@theia/core/lib/browser/widgets/select-component';
|
||||
import { DebugSession } from '../debug-session';
|
||||
import { DebugSessionManager, DidChangeActiveDebugSession } from '../debug-session-manager';
|
||||
import { DebugConsoleSession, DebugConsoleSessionFactory } from './debug-console-session';
|
||||
import { Disposable, DisposableCollection, Emitter, Event, InMemoryResources } from '@theia/core';
|
||||
import { MarkdownStringImpl } from '@theia/core/lib/common/markdown-rendering';
|
||||
import debounce = require('@theia/core/shared/lodash.debounce');
|
||||
|
||||
export type InDebugReplContextKey = ContextKey<boolean>;
|
||||
export const InDebugReplContextKey = Symbol('inDebugReplContextKey');
|
||||
|
||||
export namespace DebugConsoleCommands {
|
||||
|
||||
export const DEBUG_CATEGORY = 'Debug';
|
||||
|
||||
export const CLEAR = Command.toDefaultLocalizedCommand({
|
||||
id: 'debug.console.clear',
|
||||
category: DEBUG_CATEGORY,
|
||||
label: 'Clear Console',
|
||||
iconClass: codicon('clear-all')
|
||||
});
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class DebugConsoleContribution extends AbstractViewContribution<ConsoleWidget> implements TabBarToolbarContribution, Disposable {
|
||||
|
||||
@inject(ConsoleSessionManager)
|
||||
protected consoleSessionManager: ConsoleSessionManager;
|
||||
|
||||
@inject(DebugConsoleSessionFactory)
|
||||
protected debugConsoleSessionFactory: DebugConsoleSessionFactory;
|
||||
|
||||
@inject(DebugSessionManager)
|
||||
protected debugSessionManager: DebugSessionManager;
|
||||
|
||||
@inject(InMemoryResources)
|
||||
protected readonly resources: InMemoryResources;
|
||||
|
||||
@inject(HoverService)
|
||||
protected readonly hoverService: HoverService;
|
||||
|
||||
protected readonly DEBUG_CONSOLE_SEVERITY_ID = 'debugConsoleSeverity';
|
||||
|
||||
protected filterInputRef: HTMLInputElement | undefined;
|
||||
protected currentFilterValue = '';
|
||||
protected readonly filterChangedEmitter = new Emitter<void>();
|
||||
protected readonly toDispose = new DisposableCollection();
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
widgetId: DebugConsoleContribution.options.id,
|
||||
widgetName: DebugConsoleContribution.options.title!.label!,
|
||||
defaultWidgetOptions: {
|
||||
area: 'bottom'
|
||||
},
|
||||
toggleCommandId: 'debug:console:toggle',
|
||||
toggleKeybinding: 'ctrlcmd+shift+y'
|
||||
});
|
||||
}
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.resources.add(DebugConsoleSession.uri, '');
|
||||
this.toDispose.pushAll([
|
||||
this.debugSessionManager.onDidCreateDebugSession(session => {
|
||||
const consoleParent = session.findConsoleParent();
|
||||
if (consoleParent) {
|
||||
const parentConsoleSession = this.consoleSessionManager.get(consoleParent.id);
|
||||
if (parentConsoleSession instanceof DebugConsoleSession) {
|
||||
session.on('output', event => parentConsoleSession.logOutput(parentConsoleSession.debugSession, event));
|
||||
}
|
||||
} else {
|
||||
const consoleSession = this.debugConsoleSessionFactory(session);
|
||||
this.consoleSessionManager.add(consoleSession);
|
||||
session.on('output', event => consoleSession.logOutput(session, event));
|
||||
}
|
||||
}),
|
||||
this.debugSessionManager.onDidChangeActiveDebugSession(event => this.handleActiveDebugSessionChanged(event)),
|
||||
this.debugSessionManager.onDidDestroyDebugSession(session => {
|
||||
const consoleSession = this.consoleSessionManager.get(session.id);
|
||||
if (consoleSession instanceof DebugConsoleSession) {
|
||||
consoleSession.markTerminated();
|
||||
}
|
||||
}),
|
||||
this.consoleSessionManager.onDidChangeSelectedSession(() => {
|
||||
const session = this.consoleSessionManager.selectedSession;
|
||||
if (session && this.filterInputRef) {
|
||||
const filterValue = session.filterText || '';
|
||||
this.filterInputRef.value = filterValue;
|
||||
this.currentFilterValue = filterValue;
|
||||
this.filterChangedEmitter.fire();
|
||||
}
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
protected handleActiveDebugSessionChanged(event: DidChangeActiveDebugSession): void {
|
||||
if (!event.current) {
|
||||
return;
|
||||
} else {
|
||||
const topSession = event.current.findConsoleParent() || event.current;
|
||||
const consoleSession = topSession ? this.consoleSessionManager.get(topSession.id) : undefined;
|
||||
this.consoleSessionManager.selectedSession = consoleSession;
|
||||
const consoleSelector = document.getElementById('debugConsoleSelector');
|
||||
if (consoleSession && consoleSelector instanceof HTMLSelectElement) {
|
||||
consoleSelector.value = consoleSession.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override registerCommands(commands: CommandRegistry): void {
|
||||
super.registerCommands(commands);
|
||||
commands.registerCommand(DebugConsoleCommands.CLEAR, {
|
||||
isEnabled: widget => this.withWidget(widget, () => true),
|
||||
isVisible: widget => this.withWidget(widget, () => true),
|
||||
execute: widget => this.withWidget(widget, () => {
|
||||
this.clearConsole();
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
async registerToolbarItems(toolbarRegistry: TabBarToolbarRegistry): Promise<void> {
|
||||
toolbarRegistry.registerItem({
|
||||
id: 'debug-console-severity',
|
||||
render: widget => this.renderSeveritySelector(widget),
|
||||
isVisible: widget => this.withWidget(widget, () => true),
|
||||
onDidChange: this.consoleSessionManager.onDidChangeSeverity,
|
||||
priority: -3,
|
||||
});
|
||||
|
||||
toolbarRegistry.registerItem({
|
||||
id: 'debug-console-filter',
|
||||
render: () => this.renderFilterInput(),
|
||||
isVisible: widget => this.withWidget(widget, () => true),
|
||||
onDidChange: this.filterChangedEmitter.event,
|
||||
priority: -2,
|
||||
});
|
||||
|
||||
toolbarRegistry.registerItem({
|
||||
id: 'debug-console-session-selector',
|
||||
render: widget => this.renderDebugConsoleSelector(widget),
|
||||
isVisible: widget => this.withWidget(widget, () => this.consoleSessionManager.all.length >= 1),
|
||||
onDidChange: Event.any(
|
||||
this.consoleSessionManager.onDidAddSession,
|
||||
this.consoleSessionManager.onDidDeleteSession,
|
||||
this.consoleSessionManager.onDidChangeSelectedSession
|
||||
) as Event<void>,
|
||||
priority: -1,
|
||||
});
|
||||
|
||||
toolbarRegistry.registerItem({
|
||||
id: DebugConsoleCommands.CLEAR.id,
|
||||
command: DebugConsoleCommands.CLEAR.id,
|
||||
tooltip: DebugConsoleCommands.CLEAR.label,
|
||||
priority: 0,
|
||||
});
|
||||
}
|
||||
|
||||
static options: ConsoleOptions = {
|
||||
id: 'debug-console',
|
||||
title: {
|
||||
label: nls.localizeByDefault('Debug Console'),
|
||||
iconClass: codicon('debug-console')
|
||||
},
|
||||
input: {
|
||||
uri: DebugConsoleSession.uri,
|
||||
options: {
|
||||
autoSizing: true,
|
||||
minHeight: 1,
|
||||
maxHeight: 10
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static async create(parent: interfaces.Container): Promise<ConsoleWidget> {
|
||||
const inputFocusContextKey = parent.get<InDebugReplContextKey>(InDebugReplContextKey);
|
||||
const child = ConsoleWidget.createContainer(parent, {
|
||||
...DebugConsoleContribution.options,
|
||||
inputFocusContextKey
|
||||
});
|
||||
const widget = child.get(ConsoleWidget);
|
||||
await widget.ready;
|
||||
return widget;
|
||||
}
|
||||
|
||||
static bindContribution(bind: interfaces.Bind): void {
|
||||
bind(InDebugReplContextKey).toDynamicValue(({ container }) =>
|
||||
container.get<ContextKeyService>(ContextKeyService).createKey('inDebugRepl', false)
|
||||
).inSingletonScope();
|
||||
bind(DebugConsoleSession).toSelf().inRequestScope();
|
||||
bind(DebugConsoleSessionFactory).toFactory(context => (session: DebugSession) => {
|
||||
const consoleSession = context.container.get(DebugConsoleSession);
|
||||
consoleSession.debugSession = session;
|
||||
return consoleSession;
|
||||
});
|
||||
bind(ConsoleSessionManager).toSelf().inSingletonScope();
|
||||
bindViewContribution(bind, DebugConsoleContribution);
|
||||
bind(TabBarToolbarContribution).toService(DebugConsoleContribution);
|
||||
bind(WidgetFactory).toDynamicValue(({ container }) => ({
|
||||
id: DebugConsoleContribution.options.id,
|
||||
createWidget: () => DebugConsoleContribution.create(container)
|
||||
}));
|
||||
}
|
||||
|
||||
protected renderSeveritySelector(widget: Widget | undefined): React.ReactNode {
|
||||
const severityElements: SelectOption[] = Severity.toArray().map(e => ({
|
||||
value: e,
|
||||
label: Severity.toLocaleString(e)
|
||||
}));
|
||||
|
||||
return <div onMouseEnter={this.handleSeverityMouseEnter}>
|
||||
<SelectComponent
|
||||
id={this.DEBUG_CONSOLE_SEVERITY_ID}
|
||||
key="debugConsoleSeverity"
|
||||
options={severityElements}
|
||||
defaultValue={this.consoleSessionManager.severity || Severity.Ignore}
|
||||
onChange={this.changeSeverity}
|
||||
/>
|
||||
</div>;
|
||||
}
|
||||
|
||||
protected handleSeverityMouseEnter = (e: React.MouseEvent<HTMLDivElement>): void => {
|
||||
const tooltipContent = new MarkdownStringImpl();
|
||||
tooltipContent.appendMarkdown(nls.localize(
|
||||
'theia/debug/consoleSeverityTooltip',
|
||||
'Filter console output by severity level. Only messages with the selected severity will be shown.'
|
||||
));
|
||||
this.hoverService.requestHover({
|
||||
content: tooltipContent,
|
||||
target: e.currentTarget,
|
||||
position: 'bottom'
|
||||
});
|
||||
};
|
||||
|
||||
protected renderDebugConsoleSelector(widget: Widget | undefined): React.ReactNode {
|
||||
const availableConsoles: SelectOption[] = [];
|
||||
const sortedSessions = this.consoleSessionManager.all
|
||||
.filter((e): e is DebugConsoleSession => e instanceof DebugConsoleSession)
|
||||
.sort((a, b) => {
|
||||
if (a.terminated !== b.terminated) {
|
||||
return a.terminated ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
sortedSessions.forEach(session => {
|
||||
let label = session.debugSession.label;
|
||||
if (session.terminated) {
|
||||
label = `${label} (${nls.localizeByDefault('Stopped')})`;
|
||||
}
|
||||
availableConsoles.push({
|
||||
value: session.id,
|
||||
label
|
||||
});
|
||||
});
|
||||
|
||||
const selectedId = this.consoleSessionManager.selectedSession?.id;
|
||||
|
||||
return <div onMouseEnter={this.handleSessionSelectorMouseEnter}><SelectComponent
|
||||
key="debugConsoleSelector"
|
||||
options={availableConsoles}
|
||||
defaultValue={selectedId}
|
||||
onChange={this.changeDebugConsole} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
protected handleSessionSelectorMouseEnter = (e: React.MouseEvent<HTMLDivElement>): void => {
|
||||
const tooltipContent = new MarkdownStringImpl();
|
||||
tooltipContent.appendMarkdown(nls.localize(
|
||||
'theia/debug/consoleSessionSelectorTooltip',
|
||||
'Switch between debug sessions. Each debug session has its own console output.'
|
||||
));
|
||||
this.hoverService.requestHover({
|
||||
content: tooltipContent,
|
||||
target: e.currentTarget,
|
||||
position: 'bottom'
|
||||
});
|
||||
};
|
||||
|
||||
protected renderFilterInput(): React.ReactNode {
|
||||
return (
|
||||
<div className="item enabled debug-console-filter-container">
|
||||
<input
|
||||
type="text"
|
||||
className="theia-input"
|
||||
placeholder={nls.localize('theia/debug/consoleFilter', 'Filter (e.g. text, !exclude)')}
|
||||
aria-label={nls.localize('theia/debug/consoleFilterAriaLabel', 'Filter debug console output')}
|
||||
ref={ref => { this.filterInputRef = ref ?? undefined; }}
|
||||
onChange={this.handleFilterInputChange}
|
||||
onMouseEnter={this.handleFilterMouseEnter}
|
||||
/>
|
||||
{this.currentFilterValue && <span
|
||||
className="debug-console-filter-btn codicon codicon-close action-label"
|
||||
role="button"
|
||||
aria-label={nls.localizeByDefault('Clear')}
|
||||
onClick={this.handleFilterClear}
|
||||
title={nls.localizeByDefault('Clear')}
|
||||
/>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
protected handleFilterInputChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
const value = e.target.value;
|
||||
this.currentFilterValue = value;
|
||||
this.filterChangedEmitter.fire();
|
||||
this.applyFilterDebounced(value);
|
||||
};
|
||||
|
||||
protected applyFilterDebounced = debounce((value: string) => {
|
||||
const session = this.consoleSessionManager.selectedSession;
|
||||
if (session) {
|
||||
session.filterText = value;
|
||||
}
|
||||
}, 150);
|
||||
|
||||
protected handleFilterClear = (): void => {
|
||||
if (this.filterInputRef) {
|
||||
this.filterInputRef.value = '';
|
||||
}
|
||||
this.currentFilterValue = '';
|
||||
const session = this.consoleSessionManager.selectedSession;
|
||||
if (session) {
|
||||
session.filterText = '';
|
||||
}
|
||||
this.filterChangedEmitter.fire();
|
||||
};
|
||||
|
||||
protected handleFilterMouseEnter = (e: React.MouseEvent<HTMLInputElement>): void => {
|
||||
const tooltipContent = new MarkdownStringImpl();
|
||||
tooltipContent.appendMarkdown(nls.localize(
|
||||
'theia/debug/consoleFilterTooltip',
|
||||
'Filter console output by text. Separate multiple terms with commas. Prefix with `!` to exclude a term.\n\n' +
|
||||
'Examples:\n\n' +
|
||||
'- `text` - show lines containing "text"\n' +
|
||||
'- `text, other` - show lines containing "text" or "other"\n' +
|
||||
'- `!text` - hide lines containing "text"\n' +
|
||||
'- `text, !other` - show "text" but hide "other"'
|
||||
));
|
||||
this.hoverService.requestHover({
|
||||
content: tooltipContent,
|
||||
target: e.currentTarget,
|
||||
position: 'bottom'
|
||||
});
|
||||
};
|
||||
|
||||
protected changeDebugConsole = (option: SelectOption) => {
|
||||
const id = option.value!;
|
||||
const session = this.consoleSessionManager.get(id);
|
||||
this.consoleSessionManager.selectedSession = session;
|
||||
};
|
||||
|
||||
protected changeSeverity = (option: SelectOption) => {
|
||||
this.consoleSessionManager.severity = Severity.fromValue(option.value);
|
||||
};
|
||||
|
||||
protected withWidget<T>(widget: Widget | undefined = this.tryGetWidget(), fn: (widget: ConsoleWidget) => T): T | false {
|
||||
if (widget instanceof ConsoleWidget && widget.id === DebugConsoleContribution.options.id) {
|
||||
return fn(widget);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the console widget.
|
||||
*/
|
||||
protected async clearConsole(): Promise<void> {
|
||||
const widget = await this.widget;
|
||||
widget.clear();
|
||||
const selectedSession = this.consoleSessionManager.selectedSession;
|
||||
if (selectedSession instanceof DebugConsoleSession && selectedSession.terminated) {
|
||||
this.consoleSessionManager.delete(selectedSession.id);
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.toDispose.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
481
packages/debug/src/browser/console/debug-console-items.tsx
Normal file
481
packages/debug/src/browser/console/debug-console-items.tsx
Normal file
@@ -0,0 +1,481 @@
|
||||
// *****************************************************************************
|
||||
// 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
|
||||
// *****************************************************************************
|
||||
|
||||
import * as React from '@theia/core/shared/react';
|
||||
import { DebugProtocol } from '@vscode/debugprotocol/lib/debugProtocol';
|
||||
import { codicon, SingleTextInputDialog } from '@theia/core/lib/browser';
|
||||
import { ConsoleItem, CompositeConsoleItem } from '@theia/console/lib/browser/console-session';
|
||||
import { DebugSession, formatMessage } from '../debug-session';
|
||||
import { Severity } from '@theia/core/lib/common/severity';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { generateUuid, nls } from '@theia/core';
|
||||
|
||||
export type DebugSessionProvider = () => DebugSession | undefined;
|
||||
|
||||
export class ExpressionContainer implements CompositeConsoleItem {
|
||||
|
||||
private static readonly BASE_CHUNK_SIZE = 100;
|
||||
|
||||
protected readonly sessionProvider: DebugSessionProvider;
|
||||
protected get session(): DebugSession | undefined {
|
||||
return this.sessionProvider();
|
||||
}
|
||||
|
||||
readonly id: string | number;
|
||||
protected variablesReference: number;
|
||||
protected namedVariables: number | undefined;
|
||||
protected indexedVariables: number | undefined;
|
||||
protected presentationHint: DebugProtocol.VariablePresentationHint | undefined;
|
||||
protected readonly startOfVariables: number;
|
||||
|
||||
constructor(options: ExpressionContainer.Options) {
|
||||
this.sessionProvider = options.session;
|
||||
this.id = options.id ?? generateUuid();
|
||||
this.variablesReference = options.variablesReference || 0;
|
||||
this.namedVariables = options.namedVariables;
|
||||
this.indexedVariables = options.indexedVariables;
|
||||
this.startOfVariables = options.startOfVariables || 0;
|
||||
this.presentationHint = options.presentationHint;
|
||||
if (this.lazy) {
|
||||
(this as CompositeConsoleItem).expandByDefault = () => !this.lazy && !this.session?.autoExpandLazyVariables;
|
||||
}
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
get reference(): number | undefined {
|
||||
return this.variablesReference;
|
||||
}
|
||||
|
||||
get hasElements(): boolean {
|
||||
return !!this.variablesReference && !this.lazy;
|
||||
}
|
||||
|
||||
get lazy(): boolean {
|
||||
return !!this.presentationHint?.lazy;
|
||||
}
|
||||
|
||||
async resolveLazy(): Promise<void> {
|
||||
const { session, variablesReference, lazy } = this;
|
||||
if (!session || !variablesReference || !lazy) {
|
||||
return;
|
||||
}
|
||||
const response = await session.sendRequest('variables', { variablesReference });
|
||||
const { variables } = response.body;
|
||||
if (variables.length !== 1) {
|
||||
return;
|
||||
}
|
||||
this.handleResolvedLazy(variables[0]);
|
||||
}
|
||||
|
||||
protected handleResolvedLazy(resolved: DebugProtocol.Variable): void {
|
||||
this.variablesReference = resolved.variablesReference;
|
||||
this.namedVariables = resolved.namedVariables;
|
||||
this.indexedVariables = resolved.indexedVariables;
|
||||
this.presentationHint = resolved.presentationHint;
|
||||
}
|
||||
|
||||
protected elements: Promise<ExpressionContainer[]> | undefined;
|
||||
async getElements(): Promise<IterableIterator<ExpressionContainer>> {
|
||||
if (!this.hasElements || !this.session) {
|
||||
return [][Symbol.iterator]();
|
||||
}
|
||||
if (!this.elements) {
|
||||
this.elements = this.doResolve();
|
||||
}
|
||||
return (await this.elements)[Symbol.iterator]();
|
||||
}
|
||||
protected async doResolve(): Promise<ExpressionContainer[]> {
|
||||
const result: ExpressionContainer[] = [];
|
||||
if (this.namedVariables) {
|
||||
await this.fetch(result, 'named');
|
||||
}
|
||||
if (this.indexedVariables) {
|
||||
let chunkSize = ExpressionContainer.BASE_CHUNK_SIZE;
|
||||
while (this.indexedVariables > chunkSize * ExpressionContainer.BASE_CHUNK_SIZE) {
|
||||
chunkSize *= ExpressionContainer.BASE_CHUNK_SIZE;
|
||||
}
|
||||
if (this.indexedVariables > chunkSize) {
|
||||
const numberOfChunks = Math.ceil(this.indexedVariables / chunkSize);
|
||||
for (let i = 0; i < numberOfChunks; i++) {
|
||||
const start = this.startOfVariables + i * chunkSize;
|
||||
const count = Math.min(chunkSize, this.indexedVariables - i * chunkSize);
|
||||
const { variablesReference } = this;
|
||||
const name = `[${start}..${start + count - 1}]`;
|
||||
result.push(new DebugVirtualVariable({
|
||||
session: this.sessionProvider,
|
||||
id: `${this.id}:${name}`,
|
||||
variablesReference,
|
||||
namedVariables: 0,
|
||||
indexedVariables: count,
|
||||
startOfVariables: start,
|
||||
name
|
||||
}));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
await this.fetch(result, 'indexed', this.startOfVariables, this.indexedVariables);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected fetch(result: ConsoleItem[], filter: 'named'): Promise<void>;
|
||||
protected fetch(result: ConsoleItem[], filter: 'indexed', start: number, count?: number): Promise<void>;
|
||||
protected async fetch(result: ConsoleItem[], filter: 'indexed' | 'named', start?: number, count?: number): Promise<void> {
|
||||
try {
|
||||
const { session } = this;
|
||||
if (session) {
|
||||
const { variablesReference } = this;
|
||||
const response = await session.sendRequest('variables', { variablesReference, filter, start, count });
|
||||
const { variables } = response.body;
|
||||
const names = new Set<string>();
|
||||
const debugVariables: DebugVariable[] = [];
|
||||
for (const variable of variables) {
|
||||
if (!names.has(variable.name)) {
|
||||
const v = new DebugVariable(this.sessionProvider, variable, this);
|
||||
debugVariables.push(v);
|
||||
result.push(v);
|
||||
names.add(variable.name);
|
||||
}
|
||||
}
|
||||
if (session.autoExpandLazyVariables) {
|
||||
await Promise.all(debugVariables.map(v => v.lazy && v.resolveLazy()));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
result.push({
|
||||
severity: Severity.Error,
|
||||
visible: !!e.message,
|
||||
render: () => e.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
export namespace ExpressionContainer {
|
||||
export interface Options {
|
||||
session: DebugSessionProvider,
|
||||
id?: string | number,
|
||||
variablesReference?: number
|
||||
namedVariables?: number
|
||||
indexedVariables?: number
|
||||
startOfVariables?: number
|
||||
presentationHint?: DebugProtocol.VariablePresentationHint
|
||||
}
|
||||
}
|
||||
|
||||
export class DebugVariable extends ExpressionContainer {
|
||||
|
||||
static booleanRegex = /^true|false$/i;
|
||||
static stringRegex = /^(['"]).*\1$/;
|
||||
|
||||
constructor(
|
||||
session: DebugSessionProvider,
|
||||
protected readonly variable: DebugProtocol.Variable,
|
||||
readonly parent: ExpressionContainer
|
||||
) {
|
||||
super({
|
||||
session,
|
||||
id: `${parent.id}:${variable.name}`,
|
||||
variablesReference: variable.variablesReference,
|
||||
namedVariables: variable.namedVariables,
|
||||
indexedVariables: variable.indexedVariables,
|
||||
presentationHint: variable.presentationHint
|
||||
});
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this.variable.name;
|
||||
}
|
||||
get evaluateName(): string | undefined {
|
||||
return this.variable.evaluateName;
|
||||
}
|
||||
protected _type: string | undefined;
|
||||
get type(): string | undefined {
|
||||
return this._type || this.variable.type;
|
||||
}
|
||||
protected _value: string | undefined;
|
||||
get value(): string {
|
||||
return this._value || this.variable.value;
|
||||
}
|
||||
|
||||
get readOnly(): boolean {
|
||||
return this.presentationHint?.attributes?.includes('readOnly') || this.lazy;
|
||||
}
|
||||
|
||||
override render(): React.ReactNode {
|
||||
const { type, value, name, lazy } = this;
|
||||
return <div className={this.variableClassName}>
|
||||
<span title={type || name} className='name' ref={this.setNameRef}>{name}{(value || lazy) && ': '}</span>
|
||||
{lazy && <span title={nls.localizeByDefault('Click to expand')} className={codicon('eye') + ' lazy-button'} onClick={this.handleLazyButtonClick} />}
|
||||
<span title={value} className='value' ref={this.setValueRef}>{value}</span>
|
||||
</div>;
|
||||
}
|
||||
|
||||
private readonly handleLazyButtonClick = () => this.resolveLazy();
|
||||
|
||||
protected get variableClassName(): string {
|
||||
const { type, value } = this;
|
||||
const classNames = ['theia-debug-console-variable'];
|
||||
if (type === 'number' || type === 'boolean' || type === 'string') {
|
||||
classNames.push(type);
|
||||
} else if (!isNaN(+value)) {
|
||||
classNames.push('number');
|
||||
} else if (DebugVariable.booleanRegex.test(value)) {
|
||||
classNames.push('boolean');
|
||||
} else if (DebugVariable.stringRegex.test(value)) {
|
||||
classNames.push('string');
|
||||
}
|
||||
return classNames.join(' ');
|
||||
}
|
||||
|
||||
protected override handleResolvedLazy(resolved: DebugProtocol.Variable): void {
|
||||
this._value = resolved.value;
|
||||
this._type = resolved.type || this._type;
|
||||
super.handleResolvedLazy(resolved);
|
||||
this.session?.['onDidResolveLazyVariableEmitter'].fire(this);
|
||||
}
|
||||
|
||||
get supportSetVariable(): boolean {
|
||||
return !!this.session && !!this.session.capabilities.supportsSetVariable;
|
||||
}
|
||||
async setValue(value: string): Promise<void> {
|
||||
if (!this.session || value === this.value) {
|
||||
return;
|
||||
}
|
||||
const { name, parent } = this;
|
||||
const variablesReference = parent['variablesReference'];
|
||||
const response = await this.session.sendRequest('setVariable', { variablesReference, name, value });
|
||||
this._value = response.body.value;
|
||||
this._type = response.body.type;
|
||||
this.variablesReference = response.body.variablesReference || 0;
|
||||
this.namedVariables = response.body.namedVariables;
|
||||
this.indexedVariables = response.body.indexedVariables;
|
||||
this.elements = undefined;
|
||||
this.session['fireDidChange']();
|
||||
}
|
||||
|
||||
get supportCopyValue(): boolean {
|
||||
return !!this.valueRef && document.queryCommandSupported('copy');
|
||||
}
|
||||
copyValue(): void {
|
||||
const selection = document.getSelection();
|
||||
if (this.valueRef && selection) {
|
||||
selection.selectAllChildren(this.valueRef);
|
||||
document.execCommand('copy');
|
||||
}
|
||||
}
|
||||
protected valueRef: HTMLSpanElement | undefined;
|
||||
protected setValueRef = (valueRef: HTMLSpanElement | null) => this.valueRef = valueRef || undefined;
|
||||
|
||||
get supportCopyAsExpression(): boolean {
|
||||
return !!this.nameRef && document.queryCommandSupported('copy');
|
||||
}
|
||||
copyAsExpression(): void {
|
||||
const selection = document.getSelection();
|
||||
if (this.nameRef && selection) {
|
||||
selection.selectAllChildren(this.nameRef);
|
||||
document.execCommand('copy');
|
||||
}
|
||||
}
|
||||
protected nameRef: HTMLSpanElement | undefined;
|
||||
protected setNameRef = (nameRef: HTMLSpanElement | null) => this.nameRef = nameRef || undefined;
|
||||
|
||||
async open(): Promise<void> {
|
||||
if (!this.supportSetVariable || this.readOnly) {
|
||||
return;
|
||||
}
|
||||
const input = new SingleTextInputDialog({
|
||||
title: nls.localize('theia/debug/debugVariableInput', 'Set {0} Value', this.name),
|
||||
initialValue: this.value,
|
||||
placeholder: nls.localizeByDefault('Value'),
|
||||
validate: async (value, mode) => {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
if (mode === 'open') {
|
||||
try {
|
||||
await this.setValue(value);
|
||||
} catch (error) {
|
||||
console.error('setValue failed:', error);
|
||||
if (error.body?.error) {
|
||||
const errorMessage: DebugProtocol.Message = error.body.error;
|
||||
if (errorMessage.showUser) {
|
||||
return formatMessage(errorMessage.format, errorMessage.variables);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
await input.open();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class DebugVirtualVariable extends ExpressionContainer {
|
||||
|
||||
constructor(
|
||||
protected readonly options: VirtualVariableItem.Options
|
||||
) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
override render(): React.ReactNode {
|
||||
return this.options.name;
|
||||
}
|
||||
}
|
||||
export namespace VirtualVariableItem {
|
||||
export interface Options extends ExpressionContainer.Options {
|
||||
name: string
|
||||
}
|
||||
}
|
||||
|
||||
export class ExpressionItem extends ExpressionContainer {
|
||||
|
||||
severity?: Severity;
|
||||
static notAvailable = nls.localizeByDefault('not available');
|
||||
|
||||
protected _value = ExpressionItem.notAvailable;
|
||||
get value(): string {
|
||||
return this._value;
|
||||
}
|
||||
protected _type: string | undefined;
|
||||
get type(): string | undefined {
|
||||
return this._type;
|
||||
}
|
||||
|
||||
protected _available = false;
|
||||
get available(): boolean {
|
||||
return this._available;
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected _expression: string,
|
||||
session: DebugSessionProvider,
|
||||
id?: string | number
|
||||
) {
|
||||
super({ session, id });
|
||||
}
|
||||
|
||||
get expression(): string {
|
||||
return this._expression;
|
||||
}
|
||||
|
||||
override render(): React.ReactNode {
|
||||
const valueClassNames: string[] = [];
|
||||
if (!this._available) {
|
||||
valueClassNames.push(ConsoleItem.errorClassName);
|
||||
valueClassNames.push('theia-debug-console-unavailable');
|
||||
}
|
||||
return <div className={'theia-debug-console-expression'}>
|
||||
<div>{this._expression}</div>
|
||||
<div className={valueClassNames.join(' ')}>{this._value}</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
async evaluate(context: string = 'repl', resolveLazy = true): Promise<void> {
|
||||
const session = this.session;
|
||||
if (!session?.currentFrame) {
|
||||
this.setResult(undefined, ExpressionItem.notAvailable);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await session.evaluate(this._expression, context);
|
||||
this.setResult(body);
|
||||
if (this.lazy && resolveLazy) {
|
||||
await this.resolveLazy();
|
||||
}
|
||||
} catch (err) {
|
||||
this.setResult(undefined, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
protected setResult(body?: DebugProtocol.EvaluateResponse['body'], error: string = ExpressionItem.notAvailable): void {
|
||||
if (body) {
|
||||
this._value = body.result;
|
||||
this._type = body.type;
|
||||
this._available = true;
|
||||
this.variablesReference = body.variablesReference;
|
||||
this.namedVariables = body.namedVariables;
|
||||
this.indexedVariables = body.indexedVariables;
|
||||
this.presentationHint = body.presentationHint;
|
||||
this.severity = Severity.Log;
|
||||
} else {
|
||||
this._value = error;
|
||||
this._type = undefined;
|
||||
this._available = false;
|
||||
this.variablesReference = 0;
|
||||
this.namedVariables = undefined;
|
||||
this.indexedVariables = undefined;
|
||||
this.presentationHint = undefined;
|
||||
this.severity = Severity.Error;
|
||||
}
|
||||
this.elements = undefined;
|
||||
}
|
||||
|
||||
protected override handleResolvedLazy(resolved: DebugProtocol.Variable): void {
|
||||
this._value = resolved.value;
|
||||
this._type = resolved.type || this._type;
|
||||
super.handleResolvedLazy(resolved);
|
||||
}
|
||||
}
|
||||
|
||||
export class DebugScope extends ExpressionContainer {
|
||||
|
||||
constructor(
|
||||
protected readonly raw: DebugProtocol.Scope,
|
||||
session: DebugSessionProvider,
|
||||
id: number
|
||||
) {
|
||||
super({
|
||||
session,
|
||||
id: `${raw.name}:${id}`,
|
||||
variablesReference: raw.variablesReference,
|
||||
namedVariables: raw.namedVariables,
|
||||
indexedVariables: raw.indexedVariables
|
||||
});
|
||||
}
|
||||
|
||||
override render(): React.ReactNode {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
get expensive(): boolean {
|
||||
return this.raw.expensive;
|
||||
}
|
||||
|
||||
get range(): monaco.Range | undefined {
|
||||
const { line, column, endLine, endColumn } = this.raw;
|
||||
if (line !== undefined && column !== undefined && endLine !== undefined && endColumn !== undefined) {
|
||||
return new monaco.Range(line, column, endLine, endColumn);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this.raw.name;
|
||||
}
|
||||
|
||||
expandByDefault(): boolean {
|
||||
return this.raw.presentationHint === 'locals';
|
||||
}
|
||||
|
||||
}
|
||||
270
packages/debug/src/browser/console/debug-console-session.ts
Normal file
270
packages/debug/src/browser/console/debug-console-session.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
// *****************************************************************************
|
||||
// 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
|
||||
// *****************************************************************************
|
||||
|
||||
import throttle = require('@theia/core/shared/lodash.throttle');
|
||||
import { DebugProtocol } from '@vscode/debugprotocol/lib/debugProtocol';
|
||||
import { ConsoleSession, ConsoleItem } from '@theia/console/lib/browser/console-session';
|
||||
import { AnsiConsoleItem } from '@theia/console/lib/browser/ansi-console-item';
|
||||
import { DebugSession } from '../debug-session';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { ExpressionContainer, ExpressionItem } from './debug-console-items';
|
||||
import { Severity } from '@theia/core/lib/common/severity';
|
||||
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
||||
import { DebugSessionManager } from '../debug-session-manager';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { LanguageSelector } from '@theia/monaco-editor-core/esm/vs/editor/common/languageSelector';
|
||||
import { Disposable } from '@theia/core';
|
||||
|
||||
export const DebugConsoleSessionFactory = Symbol('DebugConsoleSessionFactory');
|
||||
|
||||
export type DebugConsoleSessionFactory = (debugSession: DebugSession) => DebugConsoleSession;
|
||||
|
||||
@injectable()
|
||||
export class DebugConsoleSession extends ConsoleSession {
|
||||
|
||||
static uri = new URI().withScheme('debugconsole');
|
||||
|
||||
@inject(DebugSessionManager) protected readonly sessionManager: DebugSessionManager;
|
||||
|
||||
protected items: ConsoleItem[] = [];
|
||||
|
||||
protected _terminated = false;
|
||||
|
||||
protected _debugSession: DebugSession;
|
||||
|
||||
// content buffer for [append](#append) method
|
||||
protected uncompletedItemContent: string | undefined;
|
||||
|
||||
protected readonly completionKinds = new Map<DebugProtocol.CompletionItemType | undefined, monaco.languages.CompletionItemKind>();
|
||||
|
||||
get debugSession(): DebugSession {
|
||||
return this._debugSession;
|
||||
}
|
||||
|
||||
set debugSession(value: DebugSession) {
|
||||
this._debugSession = value;
|
||||
this.id = value.id;
|
||||
}
|
||||
|
||||
get terminated(): boolean {
|
||||
return this._terminated;
|
||||
}
|
||||
|
||||
markTerminated(): void {
|
||||
if (!this._terminated) {
|
||||
this._terminated = true;
|
||||
this.fireDidChange();
|
||||
}
|
||||
}
|
||||
|
||||
@postConstruct()
|
||||
init(): void {
|
||||
this.completionKinds.set('method', monaco.languages.CompletionItemKind.Method);
|
||||
this.completionKinds.set('function', monaco.languages.CompletionItemKind.Function);
|
||||
this.completionKinds.set('constructor', monaco.languages.CompletionItemKind.Constructor);
|
||||
this.completionKinds.set('field', monaco.languages.CompletionItemKind.Field);
|
||||
this.completionKinds.set('variable', monaco.languages.CompletionItemKind.Variable);
|
||||
this.completionKinds.set('class', monaco.languages.CompletionItemKind.Class);
|
||||
this.completionKinds.set('interface', monaco.languages.CompletionItemKind.Interface);
|
||||
this.completionKinds.set('module', monaco.languages.CompletionItemKind.Module);
|
||||
this.completionKinds.set('property', monaco.languages.CompletionItemKind.Property);
|
||||
this.completionKinds.set('unit', monaco.languages.CompletionItemKind.Unit);
|
||||
this.completionKinds.set('value', monaco.languages.CompletionItemKind.Value);
|
||||
this.completionKinds.set('enum', monaco.languages.CompletionItemKind.Enum);
|
||||
this.completionKinds.set('keyword', monaco.languages.CompletionItemKind.Keyword);
|
||||
this.completionKinds.set('snippet', monaco.languages.CompletionItemKind.Snippet);
|
||||
this.completionKinds.set('text', monaco.languages.CompletionItemKind.Text);
|
||||
this.completionKinds.set('color', monaco.languages.CompletionItemKind.Color);
|
||||
this.completionKinds.set('file', monaco.languages.CompletionItemKind.File);
|
||||
this.completionKinds.set('reference', monaco.languages.CompletionItemKind.Reference);
|
||||
this.completionKinds.set('customcolor', monaco.languages.CompletionItemKind.Color);
|
||||
this.toDispose.push((monaco.languages.registerCompletionItemProvider as (languageId: LanguageSelector, provider: monaco.languages.CompletionItemProvider) => Disposable)({
|
||||
scheme: DebugConsoleSession.uri.scheme,
|
||||
hasAccessToAllModels: true
|
||||
}, {
|
||||
triggerCharacters: ['.'],
|
||||
provideCompletionItems: (model, position) => this.completions(model, position),
|
||||
}));
|
||||
this.toDispose.push(this.sessionManager.onDidResolveLazyVariable(() => this.fireDidChange()));
|
||||
}
|
||||
|
||||
getElements(): IterableIterator<ConsoleItem> {
|
||||
return this.items.filter(e => this.matchesFilter(e))[Symbol.iterator]();
|
||||
}
|
||||
|
||||
protected matchesFilter(item: ConsoleItem): boolean {
|
||||
if (this.severity && item.severity !== this.severity) {
|
||||
return false;
|
||||
}
|
||||
if (this.filterText) {
|
||||
const text = this.getItemText(item).toLowerCase();
|
||||
const parsedFilters = this.parseFilterText(this.filterText.toLowerCase());
|
||||
|
||||
if (parsedFilters.include.length > 0) {
|
||||
const matchesAnyInclude = parsedFilters.include.some(filter => text.includes(filter));
|
||||
if (!matchesAnyInclude) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const filter of parsedFilters.exclude) {
|
||||
if (text.includes(filter)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected parseFilterText(filterText: string): { include: string[]; exclude: string[] } {
|
||||
const include: string[] = [];
|
||||
const exclude: string[] = [];
|
||||
|
||||
const terms = filterText.split(',').map(term => term.trim()).filter(term => term.length > 0);
|
||||
|
||||
for (const term of terms) {
|
||||
if (term.startsWith('!') && term.length > 1) {
|
||||
exclude.push(term.substring(1));
|
||||
} else {
|
||||
include.push(term);
|
||||
}
|
||||
}
|
||||
|
||||
return { include, exclude };
|
||||
}
|
||||
|
||||
protected getItemText(item: ConsoleItem): string {
|
||||
if (item instanceof AnsiConsoleItem) {
|
||||
return item.content;
|
||||
}
|
||||
if (item instanceof ExpressionItem) {
|
||||
return `${item.expression} ${item.value}`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
protected async completions(model: monaco.editor.ITextModel, position: monaco.Position): Promise<monaco.languages.CompletionList | undefined> {
|
||||
const completionSession = this.findCompletionSession();
|
||||
if (completionSession) {
|
||||
const column = position.column;
|
||||
const lineNumber = position.lineNumber;
|
||||
const word = model.getWordAtPosition({ column, lineNumber });
|
||||
const overwriteBefore = word ? word.word.length : 0;
|
||||
const text = model.getValue();
|
||||
const items = await completionSession.completions(text, column, lineNumber);
|
||||
const suggestions = items.map(item => this.asCompletionItem(text, position, overwriteBefore, item));
|
||||
return { suggestions };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected findCurrentSession(): DebugSession | undefined {
|
||||
const currentSession = this.sessionManager.currentSession;
|
||||
if (!currentSession) {
|
||||
return undefined;
|
||||
}
|
||||
if (currentSession.id === this.debugSession.id) {
|
||||
// perfect match
|
||||
return this.debugSession;
|
||||
}
|
||||
const parentSession = currentSession.findConsoleParent();
|
||||
if (parentSession?.id === this.debugSession.id) {
|
||||
// child of our session
|
||||
return currentSession;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected findCompletionSession(): DebugSession | undefined {
|
||||
let completionSession: DebugSession | undefined = this.findCurrentSession();
|
||||
while (completionSession !== undefined) {
|
||||
if (completionSession.capabilities.supportsCompletionsRequest) {
|
||||
return completionSession;
|
||||
}
|
||||
completionSession = completionSession.parentSession;
|
||||
}
|
||||
return completionSession;
|
||||
}
|
||||
|
||||
protected asCompletionItem(text: string, position: monaco.Position, overwriteBefore: number, item: DebugProtocol.CompletionItem): monaco.languages.CompletionItem {
|
||||
return {
|
||||
label: item.label,
|
||||
insertText: item.text || item.label,
|
||||
kind: this.completionKinds.get(item.type) || monaco.languages.CompletionItemKind.Property,
|
||||
filterText: (item.start && item.length) ? text.substring(item.start, item.start + item.length).concat(item.label) : undefined,
|
||||
range: monaco.Range.fromPositions(position.delta(0, -(item.length || overwriteBefore)), position),
|
||||
sortText: item.sortText
|
||||
};
|
||||
}
|
||||
|
||||
async execute(value: string): Promise<void> {
|
||||
const expression = new ExpressionItem(value, () => this.findCurrentSession());
|
||||
this.items.push(expression);
|
||||
await expression.evaluate();
|
||||
this.fireDidChange();
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.items = [];
|
||||
this.fireDidChange();
|
||||
}
|
||||
|
||||
append(value: string): void {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lastItem = this.items.slice(-1)[0];
|
||||
if (lastItem instanceof AnsiConsoleItem && lastItem.content === this.uncompletedItemContent) {
|
||||
this.items.pop();
|
||||
this.uncompletedItemContent += value;
|
||||
} else {
|
||||
this.uncompletedItemContent = value;
|
||||
}
|
||||
|
||||
this.items.push(new AnsiConsoleItem(this.uncompletedItemContent, Severity.Info));
|
||||
this.fireDidChange();
|
||||
}
|
||||
|
||||
appendLine(value: string): void {
|
||||
this.items.push(new AnsiConsoleItem(value, Severity.Info));
|
||||
this.fireDidChange();
|
||||
}
|
||||
|
||||
async logOutput(session: DebugSession, event: DebugProtocol.OutputEvent): Promise<void> {
|
||||
const body = event.body;
|
||||
const { category, variablesReference } = body;
|
||||
if (category === 'telemetry') {
|
||||
console.debug(`telemetry/${event.body.output}`, event.body.data);
|
||||
return;
|
||||
}
|
||||
const severity = category === 'stderr' ? Severity.Error : event.body.category === 'console' ? Severity.Warning : Severity.Info;
|
||||
if (variablesReference) {
|
||||
const items = await new ExpressionContainer({ session: () => session, variablesReference }).getElements();
|
||||
for (const item of items) {
|
||||
this.items.push(Object.assign(item, { severity }));
|
||||
}
|
||||
} else if (typeof body.output === 'string') {
|
||||
for (const line of body.output.split('\n')) {
|
||||
this.items.push(new AnsiConsoleItem(line, severity));
|
||||
}
|
||||
}
|
||||
this.fireDidChange();
|
||||
}
|
||||
|
||||
protected override fireDidChange = throttle(() => super.fireDidChange(), 50);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user