mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-11-26 10:27:15 +00:00
feat: patched the Theia debug functionality
Patch for: - eclipse-theia/theia#11871 - eclipse-theia/theia#11879 - eclipse-theia/theia#11880 - eclipse-theia/theia#11885 - eclipse-theia/theia#11886 - eclipse-theia/theia#11916 Closes #1582 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import * as React from '@theia/core/shared/react';
|
||||
import { DebugAction as TheiaDebugAction } from '@theia/debug/lib/browser/view/debug-action';
|
||||
import {
|
||||
codiconArray,
|
||||
DISABLED_CLASS,
|
||||
} from '@theia/core/lib/browser/widgets/widget';
|
||||
|
||||
// customized debug action to show the contributed command's label when there is no icon
|
||||
export class DebugAction extends TheiaDebugAction {
|
||||
override render(): React.ReactNode {
|
||||
const { enabled, label, iconClass } = this.props;
|
||||
const classNames = ['debug-action', ...codiconArray(iconClass, true)];
|
||||
if (enabled === false) {
|
||||
classNames.push(DISABLED_CLASS);
|
||||
}
|
||||
return (
|
||||
<span
|
||||
tabIndex={0}
|
||||
className={classNames.join(' ')}
|
||||
title={label}
|
||||
onClick={this.props.run}
|
||||
ref={this.setRef}
|
||||
>
|
||||
{!iconClass ||
|
||||
(iconClass.match(/plugin-icon-\d+/) && <div>{label}</div>)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { DebugSessionConnection } from '@theia/debug/lib/browser/debug-session-connection';
|
||||
import { DefaultDebugSessionFactory as TheiaDefaultDebugSessionFactory } from '@theia/debug/lib/browser/debug-session-contribution';
|
||||
import { DebugConfigurationSessionOptions } from '@theia/debug/lib/browser/debug-session-options';
|
||||
import {
|
||||
DebugAdapterPath,
|
||||
DebugChannel,
|
||||
ForwardingDebugChannel,
|
||||
} from '@theia/debug/lib/common/debug-service';
|
||||
import { DebugSession } from './debug-session';
|
||||
|
||||
@injectable()
|
||||
export class DefaultDebugSessionFactory extends TheiaDefaultDebugSessionFactory {
|
||||
override get(
|
||||
sessionId: string,
|
||||
options: DebugConfigurationSessionOptions,
|
||||
parentSession?: DebugSession
|
||||
): DebugSession {
|
||||
const connection = new DebugSessionConnection(
|
||||
sessionId,
|
||||
() =>
|
||||
new Promise<DebugChannel>((resolve) =>
|
||||
this.connectionProvider.openChannel(
|
||||
`${DebugAdapterPath}/${sessionId}`,
|
||||
(wsChannel) => {
|
||||
resolve(new ForwardingDebugChannel(wsChannel));
|
||||
},
|
||||
{ reconnecting: false }
|
||||
)
|
||||
),
|
||||
this.getTraceOutputChannel()
|
||||
);
|
||||
// patched debug session
|
||||
return new DebugSession(
|
||||
sessionId,
|
||||
options,
|
||||
parentSession,
|
||||
connection,
|
||||
this.terminalService,
|
||||
this.editorManager,
|
||||
this.breakpoints,
|
||||
this.labelProvider,
|
||||
this.messages,
|
||||
this.fileService,
|
||||
this.debugContributionProvider,
|
||||
this.workspaceService
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import type { ContextKey } from '@theia/core/lib/browser/context-key-service';
|
||||
import { injectable, postConstruct } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
DebugSession,
|
||||
DebugState,
|
||||
} from '@theia/debug/lib/browser/debug-session';
|
||||
import { DebugSessionManager as TheiaDebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager';
|
||||
import type { DebugConfigurationSessionOptions } from '@theia/debug/lib/browser/debug-session-options';
|
||||
|
||||
function debugStateLabel(state: DebugState): string {
|
||||
switch (state) {
|
||||
case DebugState.Initializing:
|
||||
return 'initializing';
|
||||
case DebugState.Stopped:
|
||||
return 'stopped';
|
||||
case DebugState.Running:
|
||||
return 'running';
|
||||
default:
|
||||
return 'inactive';
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class DebugSessionManager extends TheiaDebugSessionManager {
|
||||
protected debugStateKey: ContextKey<string>;
|
||||
|
||||
@postConstruct()
|
||||
protected override init(): void {
|
||||
this.debugStateKey = this.contextKeyService.createKey<string>(
|
||||
'debugState',
|
||||
debugStateLabel(this.state)
|
||||
);
|
||||
super.init();
|
||||
}
|
||||
|
||||
protected override fireDidChange(current: DebugSession | undefined): void {
|
||||
this.debugTypeKey.set(current?.configuration.type);
|
||||
this.inDebugModeKey.set(this.inDebugMode);
|
||||
this.debugStateKey.set(debugStateLabel(this.state));
|
||||
this.onDidChangeEmitter.fire(current);
|
||||
}
|
||||
|
||||
protected override async doStart(
|
||||
sessionId: string,
|
||||
options: DebugConfigurationSessionOptions
|
||||
): Promise<DebugSession> {
|
||||
const parentSession =
|
||||
options.configuration.parentSession &&
|
||||
this._sessions.get(options.configuration.parentSession.id);
|
||||
const contrib = this.sessionContributionRegistry.get(
|
||||
options.configuration.type
|
||||
);
|
||||
const sessionFactory = contrib
|
||||
? contrib.debugSessionFactory()
|
||||
: this.debugSessionFactory;
|
||||
const session = sessionFactory.get(sessionId, options, parentSession);
|
||||
this._sessions.set(sessionId, session);
|
||||
|
||||
this.debugTypeKey.set(session.configuration.type);
|
||||
// this.onDidCreateDebugSessionEmitter.fire(session); // defer the didCreate event after start https://github.com/eclipse-theia/theia/issues/11916
|
||||
|
||||
let state = DebugState.Inactive;
|
||||
session.onDidChange(() => {
|
||||
if (state !== session.state) {
|
||||
state = session.state;
|
||||
if (state === DebugState.Stopped) {
|
||||
this.onDidStopDebugSessionEmitter.fire(session);
|
||||
}
|
||||
}
|
||||
this.updateCurrentSession(session);
|
||||
});
|
||||
session.onDidChangeBreakpoints((uri) =>
|
||||
this.fireDidChangeBreakpoints({ session, uri })
|
||||
);
|
||||
session.on('terminated', async (event) => {
|
||||
const restart = event.body && event.body.restart;
|
||||
if (restart) {
|
||||
// postDebugTask isn't run in case of auto restart as well as preLaunchTask
|
||||
this.doRestart(session, !!restart);
|
||||
} else {
|
||||
await session.disconnect(false, () =>
|
||||
this.debug.terminateDebugSession(session.id)
|
||||
);
|
||||
await this.runTask(
|
||||
session.options.workspaceFolderUri,
|
||||
session.configuration.postDebugTask
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
session.on('exited', async (event) => {
|
||||
await session.disconnect(false, () =>
|
||||
this.debug.terminateDebugSession(session.id)
|
||||
);
|
||||
});
|
||||
|
||||
session.onDispose(() => this.cleanup(session));
|
||||
session
|
||||
.start()
|
||||
.then(() => {
|
||||
this.onDidCreateDebugSessionEmitter.fire(session); // now fire the didCreate event
|
||||
this.onDidStartDebugSessionEmitter.fire(session);
|
||||
})
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
.catch((e) => {
|
||||
session.stop(false, () => {
|
||||
this.debug.terminateDebugSession(session.id);
|
||||
});
|
||||
});
|
||||
session.onDidCustomEvent(({ event, body }) =>
|
||||
this.onDidReceiveDebugSessionCustomEventEmitter.fire({
|
||||
event,
|
||||
body,
|
||||
session,
|
||||
})
|
||||
);
|
||||
return session;
|
||||
}
|
||||
}
|
||||
231
arduino-ide-extension/src/browser/theia/debug/debug-session.ts
Normal file
231
arduino-ide-extension/src/browser/theia/debug/debug-session.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
|
||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||
import { Mutable } from '@theia/core/lib/common/types';
|
||||
import { URI } from '@theia/core/lib/common/uri';
|
||||
import { DebugSession as TheiaDebugSession } from '@theia/debug/lib/browser/debug-session';
|
||||
import { DebugFunctionBreakpoint } from '@theia/debug/lib/browser/model/debug-function-breakpoint';
|
||||
import { DebugSourceBreakpoint } from '@theia/debug/lib/browser/model/debug-source-breakpoint';
|
||||
import {
|
||||
DebugThreadData,
|
||||
StoppedDetails,
|
||||
} from '@theia/debug/lib/browser/model/debug-thread';
|
||||
import { DebugProtocol } from '@vscode/debugprotocol';
|
||||
import { DebugThread } from './debug-thread';
|
||||
|
||||
export class DebugSession extends TheiaDebugSession {
|
||||
/**
|
||||
* The `send('initialize')` request resolves later than `on('initialized')` emits the event.
|
||||
* Hence, the `configure` would use the empty object `capabilities`.
|
||||
* Using the empty `capabilities` could result in missing exception breakpoint filters, as
|
||||
* always `capabilities.exceptionBreakpointFilters` is falsy. This deferred promise works
|
||||
* around this timing issue.
|
||||
* See: https://github.com/eclipse-theia/theia/issues/11886.
|
||||
*/
|
||||
protected didReceiveCapabilities = new Deferred();
|
||||
|
||||
protected override async initialize(): Promise<void> {
|
||||
const clientName = FrontendApplicationConfigProvider.get().applicationName;
|
||||
try {
|
||||
const response = await this.connection.sendRequest('initialize', {
|
||||
clientID: clientName.toLocaleLowerCase().replace(/ /g, '_'),
|
||||
clientName,
|
||||
adapterID: this.configuration.type,
|
||||
locale: 'en-US',
|
||||
linesStartAt1: true,
|
||||
columnsStartAt1: true,
|
||||
pathFormat: 'path',
|
||||
supportsVariableType: false,
|
||||
supportsVariablePaging: false,
|
||||
supportsRunInTerminalRequest: true,
|
||||
});
|
||||
this.updateCapabilities(response?.body || {});
|
||||
this.didReceiveCapabilities.resolve();
|
||||
} catch (err) {
|
||||
this.didReceiveCapabilities.reject(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
protected override async configure(): Promise<void> {
|
||||
await this.didReceiveCapabilities.promise;
|
||||
return super.configure();
|
||||
}
|
||||
|
||||
override async stop(isRestart: boolean, callback: () => void): Promise<void> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const _this = this as any;
|
||||
if (!_this.isStopping) {
|
||||
_this.isStopping = true;
|
||||
if (this.configuration.lifecycleManagedByParent && this.parentSession) {
|
||||
await this.parentSession.stop(isRestart, callback);
|
||||
} else {
|
||||
if (this.canTerminate()) {
|
||||
const terminated = this.waitFor('terminated', 5000);
|
||||
try {
|
||||
await this.connection.sendRequest(
|
||||
'terminate',
|
||||
{ restart: isRestart },
|
||||
5000
|
||||
);
|
||||
await terminated;
|
||||
} catch (e) {
|
||||
console.error('Did not receive terminated event in time', e);
|
||||
}
|
||||
} else {
|
||||
const terminateDebuggee =
|
||||
this.initialized && this.capabilities.supportTerminateDebuggee;
|
||||
// Related https://github.com/microsoft/vscode/issues/165138
|
||||
try {
|
||||
await this.sendRequest(
|
||||
'disconnect',
|
||||
{ restart: isRestart, terminateDebuggee },
|
||||
2000
|
||||
);
|
||||
} catch (err) {
|
||||
if (
|
||||
'message' in err &&
|
||||
typeof err.message === 'string' &&
|
||||
err.message.test(err.message)
|
||||
) {
|
||||
// VS Code ignores errors when sending the `disconnect` request.
|
||||
// Debug adapter might not send the `disconnected` event as a response.
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override async sendFunctionBreakpoints(
|
||||
affectedUri: URI
|
||||
): Promise<void> {
|
||||
const all = this.breakpoints
|
||||
.getFunctionBreakpoints()
|
||||
.map(
|
||||
(origin) =>
|
||||
new DebugFunctionBreakpoint(origin, this.asDebugBreakpointOptions())
|
||||
);
|
||||
const enabled = all.filter((b) => b.enabled);
|
||||
if (this.capabilities.supportsFunctionBreakpoints) {
|
||||
try {
|
||||
const response = await this.sendRequest('setFunctionBreakpoints', {
|
||||
breakpoints: enabled.map((b) => b.origin.raw),
|
||||
});
|
||||
// Apparently, `body` and `breakpoints` can be missing.
|
||||
// https://github.com/eclipse-theia/theia/issues/11885
|
||||
// https://github.com/microsoft/vscode/blob/80004351ccf0884b58359f7c8c801c91bb827d83/src/vs/workbench/contrib/debug/browser/debugSession.ts#L448-L449
|
||||
if (response && response.body) {
|
||||
response.body.breakpoints.forEach((raw, index) => {
|
||||
// node debug adapter returns more breakpoints sometimes
|
||||
if (enabled[index]) {
|
||||
enabled[index].update({ raw });
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// could be error or promise rejection of DebugProtocol.SetFunctionBreakpoints
|
||||
if (error instanceof Error) {
|
||||
console.error(`Error setting breakpoints: ${error.message}`);
|
||||
} else {
|
||||
// handle adapters that send failed DebugProtocol.SetFunctionBreakpoints for invalid breakpoints
|
||||
const genericMessage =
|
||||
'Function breakpoint not valid for current debug session';
|
||||
const message = error.message ? `${error.message}` : genericMessage;
|
||||
console.warn(
|
||||
`Could not handle function breakpoints: ${message}, disabling...`
|
||||
);
|
||||
enabled.forEach((b) =>
|
||||
b.update({
|
||||
raw: {
|
||||
verified: false,
|
||||
message,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setBreakpoints(affectedUri, all);
|
||||
}
|
||||
|
||||
protected override async sendSourceBreakpoints(
|
||||
affectedUri: URI,
|
||||
sourceModified?: boolean
|
||||
): Promise<void> {
|
||||
const source = await this.toSource(affectedUri);
|
||||
const all = this.breakpoints
|
||||
.findMarkers({ uri: affectedUri })
|
||||
.map(
|
||||
({ data }) =>
|
||||
new DebugSourceBreakpoint(data, this.asDebugBreakpointOptions())
|
||||
);
|
||||
const enabled = all.filter((b) => b.enabled);
|
||||
try {
|
||||
const breakpoints = enabled.map(({ origin }) => origin.raw);
|
||||
const response = await this.sendRequest('setBreakpoints', {
|
||||
source: source.raw,
|
||||
sourceModified,
|
||||
breakpoints,
|
||||
lines: breakpoints.map(({ line }) => line),
|
||||
});
|
||||
response.body.breakpoints.forEach((raw, index) => {
|
||||
// node debug adapter returns more breakpoints sometimes
|
||||
if (enabled[index]) {
|
||||
enabled[index].update({ raw });
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// could be error or promise rejection of DebugProtocol.SetBreakpointsResponse
|
||||
if (error instanceof Error) {
|
||||
console.error(`Error setting breakpoints: ${error.message}`);
|
||||
} else {
|
||||
// handle adapters that send failed DebugProtocol.SetBreakpointsResponse for invalid breakpoints
|
||||
const genericMessage = 'Breakpoint not valid for current debug session';
|
||||
const message = error.message ? `${error.message}` : genericMessage;
|
||||
console.warn(
|
||||
`Could not handle breakpoints for ${affectedUri}: ${message}, disabling...`
|
||||
);
|
||||
enabled.forEach((b) =>
|
||||
b.update({
|
||||
raw: {
|
||||
verified: false,
|
||||
message,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
this.setSourceBreakpoints(affectedUri, all);
|
||||
}
|
||||
|
||||
protected override doUpdateThreads(
|
||||
threads: DebugProtocol.Thread[],
|
||||
stoppedDetails?: StoppedDetails
|
||||
): void {
|
||||
const existing = this._threads;
|
||||
this._threads = new Map();
|
||||
for (const raw of threads) {
|
||||
const id = raw.id;
|
||||
const thread = existing.get(id) || new DebugThread(this); // patched debug thread
|
||||
this._threads.set(id, thread);
|
||||
const data: Partial<Mutable<DebugThreadData>> = { raw };
|
||||
if (stoppedDetails) {
|
||||
if (stoppedDetails.threadId === id) {
|
||||
data.stoppedDetails = stoppedDetails;
|
||||
} else if (stoppedDetails.allThreadsStopped) {
|
||||
data.stoppedDetails = {
|
||||
// When a debug adapter notifies us that all threads are stopped,
|
||||
// we do not know why the others are stopped, so we should default
|
||||
// to something generic.
|
||||
reason: '',
|
||||
};
|
||||
}
|
||||
}
|
||||
thread.update(data);
|
||||
}
|
||||
this.updateCurrentThread(stoppedDetails);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { WidgetOpenerOptions } from '@theia/core/lib/browser/widget-open-handler';
|
||||
import { Range } from '@theia/core/shared/vscode-languageserver-types';
|
||||
import { DebugStackFrame as TheiaDebugStackFrame } from '@theia/debug/lib/browser/model/debug-stack-frame';
|
||||
import { EditorWidget } from '@theia/editor/lib/browser/editor-widget';
|
||||
|
||||
export class DebugStackFrame extends TheiaDebugStackFrame {
|
||||
override async open(
|
||||
options: WidgetOpenerOptions = {
|
||||
mode: 'reveal',
|
||||
}
|
||||
): Promise<EditorWidget | undefined> {
|
||||
if (!this.source) {
|
||||
return undefined;
|
||||
}
|
||||
const { line, column, endLine, endColumn, source } = this.raw;
|
||||
if (!source) {
|
||||
return undefined;
|
||||
}
|
||||
// create selection based on VS Code
|
||||
// https://github.com/eclipse-theia/theia/issues/11880
|
||||
const selection = Range.create(
|
||||
line,
|
||||
column,
|
||||
endLine || line,
|
||||
endColumn || column
|
||||
);
|
||||
this.source.open({
|
||||
...options,
|
||||
selection,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { DebugStackFrame as TheiaDebugStackFrame } from '@theia/debug/lib/browser/model/debug-stack-frame';
|
||||
import { DebugThread as TheiaDebugThread } from '@theia/debug/lib/browser/model/debug-thread';
|
||||
import { DebugProtocol } from '@vscode/debugprotocol';
|
||||
import { DebugStackFrame } from './debug-stack-frame';
|
||||
|
||||
export class DebugThread extends TheiaDebugThread {
|
||||
protected override doUpdateFrames(
|
||||
frames: DebugProtocol.StackFrame[]
|
||||
): TheiaDebugStackFrame[] {
|
||||
const result = new Set<TheiaDebugStackFrame>();
|
||||
for (const raw of frames) {
|
||||
const id = raw.id;
|
||||
const frame =
|
||||
this._frames.get(id) || new DebugStackFrame(this, this.session); // patched debug stack frame
|
||||
this._frames.set(id, frame);
|
||||
frame.update({ raw });
|
||||
result.add(frame);
|
||||
}
|
||||
this.updateCurrentFrame();
|
||||
return [...result.values()];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
|
||||
import { CommandRegistry } from '@theia/core/lib/common/command';
|
||||
import {
|
||||
ActionMenuNode,
|
||||
CompositeMenuNode,
|
||||
MenuModelRegistry,
|
||||
} from '@theia/core/lib/common/menu';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import * as React from '@theia/core/shared/react';
|
||||
import { DebugState } from '@theia/debug/lib/browser/debug-session';
|
||||
import { DebugAction } from './debug-action';
|
||||
import { DebugToolBar as TheiaDebugToolbar } from '@theia/debug/lib/browser/view/debug-toolbar-widget';
|
||||
|
||||
@injectable()
|
||||
export class DebugToolbar extends TheiaDebugToolbar {
|
||||
@inject(CommandRegistry) private readonly commandRegistry: CommandRegistry;
|
||||
@inject(MenuModelRegistry)
|
||||
private readonly menuModelRegistry: MenuModelRegistry;
|
||||
@inject(ContextKeyService)
|
||||
private readonly contextKeyService: ContextKeyService;
|
||||
|
||||
protected override render(): React.ReactNode {
|
||||
const { state } = this.model;
|
||||
return (
|
||||
<React.Fragment>
|
||||
{this.renderContributedCommands()}
|
||||
{this.renderContinue()}
|
||||
<DebugAction
|
||||
enabled={state === DebugState.Stopped}
|
||||
run={this.stepOver}
|
||||
label={nls.localizeByDefault('Step Over')}
|
||||
iconClass="debug-step-over"
|
||||
ref={this.setStepRef}
|
||||
/>
|
||||
<DebugAction
|
||||
enabled={state === DebugState.Stopped}
|
||||
run={this.stepIn}
|
||||
label={nls.localizeByDefault('Step Into')}
|
||||
iconClass="debug-step-into"
|
||||
/>
|
||||
<DebugAction
|
||||
enabled={state === DebugState.Stopped}
|
||||
run={this.stepOut}
|
||||
label={nls.localizeByDefault('Step Out')}
|
||||
iconClass="debug-step-out"
|
||||
/>
|
||||
<DebugAction
|
||||
enabled={state !== DebugState.Inactive}
|
||||
run={this.restart}
|
||||
label={nls.localizeByDefault('Restart')}
|
||||
iconClass="debug-restart"
|
||||
/>
|
||||
{this.renderStart()}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
private renderContributedCommands(): React.ReactNode {
|
||||
return this.menuModelRegistry
|
||||
.getMenu(TheiaDebugToolbar.MENU)
|
||||
.children.filter((node) => node instanceof CompositeMenuNode)
|
||||
.map((node) => (node as CompositeMenuNode).children)
|
||||
.reduce((acc, curr) => acc.concat(curr), [])
|
||||
.filter((node) => node instanceof ActionMenuNode)
|
||||
.map((node) => this.debugAction(node as ActionMenuNode));
|
||||
}
|
||||
|
||||
private debugAction(node: ActionMenuNode): React.ReactNode {
|
||||
const { label, command, when, icon: iconClass = '' } = node;
|
||||
const run = () => this.commandRegistry.executeCommand(command);
|
||||
const enabled = when ? this.contextKeyService.match(when) : true;
|
||||
return (
|
||||
enabled && (
|
||||
<DebugAction
|
||||
key={command}
|
||||
enabled={enabled}
|
||||
label={label}
|
||||
iconClass={iconClass}
|
||||
run={run}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user