mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-10 12:56:32 +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:
parent
3bc412b42f
commit
d0e383853f
@ -60,6 +60,7 @@
|
|||||||
"@types/react-virtualized": "^9.21.21",
|
"@types/react-virtualized": "^9.21.21",
|
||||||
"@types/temp": "^0.8.34",
|
"@types/temp": "^0.8.34",
|
||||||
"@types/which": "^1.3.1",
|
"@types/which": "^1.3.1",
|
||||||
|
"@vscode/debugprotocol": "^1.51.0",
|
||||||
"arduino-serial-plotter-webapp": "0.2.0",
|
"arduino-serial-plotter-webapp": "0.2.0",
|
||||||
"async-mutex": "^0.3.0",
|
"async-mutex": "^0.3.0",
|
||||||
"auth0-js": "^9.14.0",
|
"auth0-js": "^9.14.0",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import '../../src/browser/style/index.css';
|
import '../../src/browser/style/index.css';
|
||||||
import { ContainerModule } from '@theia/core/shared/inversify';
|
import { Container, ContainerModule } from '@theia/core/shared/inversify';
|
||||||
import { WidgetFactory } from '@theia/core/lib/browser/widget-manager';
|
import { WidgetFactory } from '@theia/core/lib/browser/widget-manager';
|
||||||
import { CommandContribution } from '@theia/core/lib/common/command';
|
import { CommandContribution } from '@theia/core/lib/common/command';
|
||||||
import { bindViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
|
import { bindViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
|
||||||
@ -331,6 +331,18 @@ import { TypeHierarchyServiceProvider } from './theia/typehierarchy/type-hierarc
|
|||||||
import { TypeHierarchyServiceProvider as TheiaTypeHierarchyServiceProvider } from '@theia/typehierarchy/lib/browser/typehierarchy-service';
|
import { TypeHierarchyServiceProvider as TheiaTypeHierarchyServiceProvider } from '@theia/typehierarchy/lib/browser/typehierarchy-service';
|
||||||
import { TypeHierarchyContribution } from './theia/typehierarchy/type-hierarchy-contribution';
|
import { TypeHierarchyContribution } from './theia/typehierarchy/type-hierarchy-contribution';
|
||||||
import { TypeHierarchyContribution as TheiaTypeHierarchyContribution } from '@theia/typehierarchy/lib/browser/typehierarchy-contribution';
|
import { TypeHierarchyContribution as TheiaTypeHierarchyContribution } from '@theia/typehierarchy/lib/browser/typehierarchy-contribution';
|
||||||
|
import { DefaultDebugSessionFactory } from './theia/debug/debug-session-contribution';
|
||||||
|
import { DebugSessionFactory } from '@theia/debug/lib/browser/debug-session-contribution';
|
||||||
|
import { DebugToolbar } from './theia/debug/debug-toolbar-widget';
|
||||||
|
import { DebugToolBar as TheiaDebugToolbar } from '@theia/debug/lib/browser/view/debug-toolbar-widget';
|
||||||
|
import { PluginMenuCommandAdapter } from './theia/plugin-ext/plugin-menu-command-adapter';
|
||||||
|
import { PluginMenuCommandAdapter as TheiaPluginMenuCommandAdapter } from '@theia/plugin-ext/lib/main/browser/menus/plugin-menu-command-adapter';
|
||||||
|
import { DebugSessionManager } from './theia/debug/debug-session-manager';
|
||||||
|
import { DebugSessionManager as TheiaDebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager';
|
||||||
|
import { DebugWidget } from '@theia/debug/lib/browser/view/debug-widget';
|
||||||
|
import { DebugViewModel } from '@theia/debug/lib/browser/view/debug-view-model';
|
||||||
|
import { DebugSessionWidget } from '@theia/debug/lib/browser/view/debug-session-widget';
|
||||||
|
import { DebugConfigurationWidget } from '@theia/debug/lib/browser/view/debug-configuration-widget';
|
||||||
|
|
||||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||||
// Commands and toolbar items
|
// Commands and toolbar items
|
||||||
@ -960,4 +972,36 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|||||||
);
|
);
|
||||||
bind(TypeHierarchyContribution).toSelf().inSingletonScope();
|
bind(TypeHierarchyContribution).toSelf().inSingletonScope();
|
||||||
rebind(TheiaTypeHierarchyContribution).toService(TypeHierarchyContribution);
|
rebind(TheiaTypeHierarchyContribution).toService(TypeHierarchyContribution);
|
||||||
|
|
||||||
|
// patched the debugger for `cortex-debug@1.5.1`
|
||||||
|
// https://github.com/eclipse-theia/theia/issues/11871
|
||||||
|
// https://github.com/eclipse-theia/theia/issues/11879
|
||||||
|
// https://github.com/eclipse-theia/theia/issues/11880
|
||||||
|
// https://github.com/eclipse-theia/theia/issues/11885
|
||||||
|
// https://github.com/eclipse-theia/theia/issues/11886
|
||||||
|
// https://github.com/eclipse-theia/theia/issues/11916
|
||||||
|
// based on: https://github.com/eclipse-theia/theia/compare/master...kittaakos:theia:%2311871
|
||||||
|
bind(DefaultDebugSessionFactory).toSelf().inSingletonScope();
|
||||||
|
rebind(DebugSessionFactory).toService(DefaultDebugSessionFactory);
|
||||||
|
bind(DebugSessionManager).toSelf().inSingletonScope();
|
||||||
|
rebind(TheiaDebugSessionManager).toService(DebugSessionManager);
|
||||||
|
bind(DebugToolbar).toSelf().inSingletonScope();
|
||||||
|
rebind(TheiaDebugToolbar).toService(DebugToolbar);
|
||||||
|
bind(PluginMenuCommandAdapter).toSelf().inSingletonScope();
|
||||||
|
rebind(TheiaPluginMenuCommandAdapter).toService(PluginMenuCommandAdapter);
|
||||||
|
bind(WidgetFactory)
|
||||||
|
.toDynamicValue(({ container }) => ({
|
||||||
|
id: DebugWidget.ID,
|
||||||
|
createWidget: () => {
|
||||||
|
const child = new Container({ defaultScope: 'Singleton' });
|
||||||
|
child.parent = container;
|
||||||
|
child.bind(DebugViewModel).toSelf();
|
||||||
|
child.bind(DebugToolbar).toSelf(); // patched toolbar
|
||||||
|
child.bind(DebugSessionWidget).toSelf();
|
||||||
|
child.bind(DebugConfigurationWidget).toSelf();
|
||||||
|
child.bind(DebugWidget).toSelf();
|
||||||
|
return child.get(DebugWidget);
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
.inSingletonScope();
|
||||||
});
|
});
|
||||||
|
@ -176,3 +176,13 @@ button.theia-button.message-box-dialog-button {
|
|||||||
outline: 1px dashed var(--theia-focusBorder);
|
outline: 1px dashed var(--theia-focusBorder);
|
||||||
outline-offset: -2px;
|
outline-offset: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.debug-toolbar .debug-action>div {
|
||||||
|
font-family: var(--theia-ui-font-family);
|
||||||
|
font-size: var(--theia-ui-font-size0);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
align-self: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: inherit;
|
||||||
|
}
|
||||||
|
@ -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}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
import {
|
||||||
|
Disposable,
|
||||||
|
DisposableCollection,
|
||||||
|
} from '@theia/core/lib/common/disposable';
|
||||||
|
import { DebuggerDescription } from '@theia/debug/lib/common/debug-service';
|
||||||
|
import { DebugMainImpl as TheiaDebugMainImpl } from '@theia/plugin-ext/lib/main/browser/debug/debug-main';
|
||||||
|
import { PluginDebugAdapterContribution } from '@theia/plugin-ext/lib/main/browser/debug/plugin-debug-adapter-contribution';
|
||||||
|
import { PluginDebugSessionFactory } from './plugin-debug-session-factory';
|
||||||
|
|
||||||
|
export class DebugMainImpl extends TheiaDebugMainImpl {
|
||||||
|
override async $registerDebuggerContribution(
|
||||||
|
description: DebuggerDescription
|
||||||
|
): Promise<void> {
|
||||||
|
const debugType = description.type;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const _this = <any>this;
|
||||||
|
const terminalOptionsExt = await _this.debugExt.$getTerminalCreationOptions(
|
||||||
|
debugType
|
||||||
|
);
|
||||||
|
|
||||||
|
if (_this.toDispose.disposed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const debugSessionFactory = new PluginDebugSessionFactory(
|
||||||
|
_this.terminalService,
|
||||||
|
_this.editorManager,
|
||||||
|
_this.breakpointsManager,
|
||||||
|
_this.labelProvider,
|
||||||
|
_this.messages,
|
||||||
|
_this.outputChannelManager,
|
||||||
|
_this.debugPreferences,
|
||||||
|
async (sessionId: string) => {
|
||||||
|
const connection = await _this.connectionMain.ensureConnection(
|
||||||
|
sessionId
|
||||||
|
);
|
||||||
|
return connection;
|
||||||
|
},
|
||||||
|
_this.fileService,
|
||||||
|
terminalOptionsExt,
|
||||||
|
_this.debugContributionProvider,
|
||||||
|
_this.workspaceService
|
||||||
|
);
|
||||||
|
|
||||||
|
const toDispose = new DisposableCollection(
|
||||||
|
Disposable.create(() => _this.debuggerContributions.delete(debugType))
|
||||||
|
);
|
||||||
|
_this.debuggerContributions.set(debugType, toDispose);
|
||||||
|
toDispose.pushAll([
|
||||||
|
_this.pluginDebugService.registerDebugAdapterContribution(
|
||||||
|
new PluginDebugAdapterContribution(
|
||||||
|
description,
|
||||||
|
_this.debugExt,
|
||||||
|
_this.pluginService
|
||||||
|
)
|
||||||
|
),
|
||||||
|
_this.sessionContributionRegistrator.registerDebugSessionContribution({
|
||||||
|
debugType: description.type,
|
||||||
|
debugSessionFactory: () => debugSessionFactory,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
_this.toDispose.push(
|
||||||
|
Disposable.create(() => this.$unregisterDebuggerConfiguration(debugType))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,17 @@
|
|||||||
import { Emitter, Event, JsonRpcProxy } from '@theia/core';
|
import { Emitter, Event, JsonRpcProxy } from '@theia/core';
|
||||||
import { injectable, interfaces } from '@theia/core/shared/inversify';
|
import { injectable, interfaces } from '@theia/core/shared/inversify';
|
||||||
import { HostedPluginServer } from '@theia/plugin-ext/lib/common/plugin-protocol';
|
import { HostedPluginServer } from '@theia/plugin-ext/lib/common/plugin-protocol';
|
||||||
import { HostedPluginSupport as TheiaHostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
|
import { RPCProtocol } from '@theia/plugin-ext/lib/common/rpc-protocol';
|
||||||
|
import {
|
||||||
|
HostedPluginSupport as TheiaHostedPluginSupport,
|
||||||
|
PluginHost,
|
||||||
|
} from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
|
||||||
|
import { PluginWorker } from '@theia/plugin-ext/lib/hosted/browser/plugin-worker';
|
||||||
|
import { setUpPluginApi } from '@theia/plugin-ext/lib/main/browser/main-context';
|
||||||
|
import { PLUGIN_RPC_CONTEXT } from '@theia/plugin-ext/lib/common/plugin-api-rpc';
|
||||||
|
import { DebugMainImpl } from './debug-main';
|
||||||
|
import { ConnectionImpl } from '@theia/plugin-ext/lib/common/connection';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class HostedPluginSupport extends TheiaHostedPluginSupport {
|
export class HostedPluginSupport extends TheiaHostedPluginSupport {
|
||||||
private readonly onDidLoadEmitter = new Emitter<void>();
|
private readonly onDidLoadEmitter = new Emitter<void>();
|
||||||
@ -31,4 +41,26 @@ export class HostedPluginSupport extends TheiaHostedPluginSupport {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return (this as any).server;
|
return (this as any).server;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// to patch the VS Code extension based debugger
|
||||||
|
// eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
|
||||||
|
protected override initRpc(host: PluginHost, pluginId: string): RPCProtocol {
|
||||||
|
const rpc =
|
||||||
|
host === 'frontend' ? new PluginWorker().rpc : this.createServerRpc(host);
|
||||||
|
setUpPluginApi(rpc, this.container);
|
||||||
|
this.patchDebugMain(rpc);
|
||||||
|
this.mainPluginApiProviders
|
||||||
|
.getContributions()
|
||||||
|
.forEach((p) => p.initialize(rpc, this.container));
|
||||||
|
return rpc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private patchDebugMain(rpc: RPCProtocol): void {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const connectionMain = (rpc as any).locals.get(
|
||||||
|
PLUGIN_RPC_CONTEXT.CONNECTION_MAIN.id
|
||||||
|
) as ConnectionImpl;
|
||||||
|
const debugMain = new DebugMainImpl(rpc, connectionMain, this.container);
|
||||||
|
rpc.set(PLUGIN_RPC_CONTEXT.DEBUG_MAIN, debugMain);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
import { DebugSession } from '@theia/debug/lib/browser/debug-session';
|
||||||
|
import { DebugSessionConnection } from '@theia/debug/lib/browser/debug-session-connection';
|
||||||
|
import { DebugConfigurationSessionOptions } from '@theia/debug/lib/browser/debug-session-options';
|
||||||
|
import { PluginDebugSessionFactory as TheiaPluginDebugSessionFactory } from '@theia/plugin-ext/lib/main/browser/debug/plugin-debug-session-factory';
|
||||||
|
import { PluginDebugSession } from './plugin-debug-session';
|
||||||
|
|
||||||
|
export class PluginDebugSessionFactory extends TheiaPluginDebugSessionFactory {
|
||||||
|
override get(
|
||||||
|
sessionId: string,
|
||||||
|
options: DebugConfigurationSessionOptions,
|
||||||
|
parentSession?: DebugSession
|
||||||
|
): DebugSession {
|
||||||
|
const connection = new DebugSessionConnection(
|
||||||
|
sessionId,
|
||||||
|
this.connectionFactory,
|
||||||
|
this.getTraceOutputChannel()
|
||||||
|
);
|
||||||
|
|
||||||
|
return new PluginDebugSession(
|
||||||
|
sessionId,
|
||||||
|
options,
|
||||||
|
parentSession,
|
||||||
|
connection,
|
||||||
|
this.terminalService,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
this.editorManager as any,
|
||||||
|
this.breakpoints,
|
||||||
|
this.labelProvider,
|
||||||
|
this.messages,
|
||||||
|
this.fileService,
|
||||||
|
this.terminalOptionsExt,
|
||||||
|
this.debugContributionProvider,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
this.workspaceService as any
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
import { ContributionProvider, MessageClient } from '@theia/core';
|
||||||
|
import { LabelProvider } from '@theia/core/lib/browser';
|
||||||
|
import { BreakpointManager } from '@theia/debug/lib/browser/breakpoint/breakpoint-manager';
|
||||||
|
import { DebugContribution } from '@theia/debug/lib/browser/debug-contribution';
|
||||||
|
import { DebugSession as TheiaDebugSession } from '@theia/debug/lib/browser/debug-session';
|
||||||
|
import { DebugSessionConnection } from '@theia/debug/lib/browser/debug-session-connection';
|
||||||
|
import { DebugConfigurationSessionOptions } from '@theia/debug/lib/browser/debug-session-options';
|
||||||
|
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||||
|
import { TerminalOptionsExt } from '@theia/plugin-ext';
|
||||||
|
import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service';
|
||||||
|
import {
|
||||||
|
TerminalWidget,
|
||||||
|
TerminalWidgetOptions,
|
||||||
|
} from '@theia/terminal/lib/browser/base/terminal-widget';
|
||||||
|
import { DebugSession } from '../debug/debug-session';
|
||||||
|
import { EditorManager } from '../editor/editor-manager';
|
||||||
|
import { WorkspaceService } from '../workspace/workspace-service';
|
||||||
|
|
||||||
|
// This class extends the patched debug session, and not the default debug session from Theia
|
||||||
|
export class PluginDebugSession extends DebugSession {
|
||||||
|
constructor(
|
||||||
|
override readonly id: string,
|
||||||
|
override readonly options: DebugConfigurationSessionOptions,
|
||||||
|
override readonly parentSession: TheiaDebugSession | undefined,
|
||||||
|
protected override readonly connection: DebugSessionConnection,
|
||||||
|
protected override readonly terminalServer: TerminalService,
|
||||||
|
protected override readonly editorManager: EditorManager,
|
||||||
|
protected override readonly breakpoints: BreakpointManager,
|
||||||
|
protected override readonly labelProvider: LabelProvider,
|
||||||
|
protected override readonly messages: MessageClient,
|
||||||
|
protected override readonly fileService: FileService,
|
||||||
|
protected readonly terminalOptionsExt: TerminalOptionsExt | undefined,
|
||||||
|
protected override readonly debugContributionProvider: ContributionProvider<DebugContribution>,
|
||||||
|
protected override readonly workspaceService: WorkspaceService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
id,
|
||||||
|
options,
|
||||||
|
parentSession,
|
||||||
|
connection,
|
||||||
|
terminalServer,
|
||||||
|
editorManager,
|
||||||
|
breakpoints,
|
||||||
|
labelProvider,
|
||||||
|
messages,
|
||||||
|
fileService,
|
||||||
|
debugContributionProvider,
|
||||||
|
workspaceService
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async doCreateTerminal(
|
||||||
|
terminalWidgetOptions: TerminalWidgetOptions
|
||||||
|
): Promise<TerminalWidget> {
|
||||||
|
terminalWidgetOptions = Object.assign(
|
||||||
|
{},
|
||||||
|
terminalWidgetOptions,
|
||||||
|
this.terminalOptionsExt
|
||||||
|
);
|
||||||
|
return super.doCreateTerminal(terminalWidgetOptions);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
import { MenuPath } from '@theia/core';
|
||||||
|
import { TAB_BAR_TOOLBAR_CONTEXT_MENU } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||||
|
import { injectable, postConstruct } from '@theia/core/shared/inversify';
|
||||||
|
import { DebugToolBar } from '@theia/debug/lib/browser/view/debug-toolbar-widget';
|
||||||
|
import { DebugVariablesWidget } from '@theia/debug/lib/browser/view/debug-variables-widget';
|
||||||
|
import {
|
||||||
|
ArgumentAdapter,
|
||||||
|
PluginMenuCommandAdapter as TheiaPluginMenuCommandAdapter,
|
||||||
|
} from '@theia/plugin-ext/lib/main/browser/menus/plugin-menu-command-adapter';
|
||||||
|
import {
|
||||||
|
codeToTheiaMappings,
|
||||||
|
ContributionPoint,
|
||||||
|
} from '@theia/plugin-ext/lib/main/browser/menus/vscode-theia-menu-mappings';
|
||||||
|
|
||||||
|
function patch(
|
||||||
|
toPatch: typeof codeToTheiaMappings,
|
||||||
|
key: string,
|
||||||
|
value: MenuPath[]
|
||||||
|
): void {
|
||||||
|
const loose = toPatch as Map<string, MenuPath[]>;
|
||||||
|
if (!loose.has(key)) {
|
||||||
|
loose.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// mappings is a const and cannot be customized with DI
|
||||||
|
patch(codeToTheiaMappings, 'debug/variables/context', [
|
||||||
|
DebugVariablesWidget.CONTEXT_MENU,
|
||||||
|
]);
|
||||||
|
patch(codeToTheiaMappings, 'debug/toolBar', [DebugToolBar.MENU]);
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class PluginMenuCommandAdapter extends TheiaPluginMenuCommandAdapter {
|
||||||
|
@postConstruct()
|
||||||
|
protected override init(): void {
|
||||||
|
const toCommentArgs: ArgumentAdapter = (...args) =>
|
||||||
|
this.toCommentArgs(...args);
|
||||||
|
const firstArgOnly: ArgumentAdapter = (...args) => [args[0]];
|
||||||
|
const noArgs: ArgumentAdapter = () => [];
|
||||||
|
const toScmArgs: ArgumentAdapter = (...args) => this.toScmArgs(...args);
|
||||||
|
const selectedResource = () => this.getSelectedResources();
|
||||||
|
const widgetURI: ArgumentAdapter = (widget) =>
|
||||||
|
this.codeEditorUtil.is(widget)
|
||||||
|
? [this.codeEditorUtil.getResourceUri(widget)]
|
||||||
|
: [];
|
||||||
|
(<Array<[ContributionPoint, ArgumentAdapter | undefined]>>[
|
||||||
|
['comments/comment/context', toCommentArgs],
|
||||||
|
['comments/comment/title', toCommentArgs],
|
||||||
|
['comments/commentThread/context', toCommentArgs],
|
||||||
|
['debug/callstack/context', firstArgOnly],
|
||||||
|
['debug/variables/context', firstArgOnly],
|
||||||
|
['debug/toolBar', noArgs],
|
||||||
|
['editor/context', selectedResource],
|
||||||
|
['editor/title', widgetURI],
|
||||||
|
['editor/title/context', selectedResource],
|
||||||
|
['explorer/context', selectedResource],
|
||||||
|
['scm/resourceFolder/context', toScmArgs],
|
||||||
|
['scm/resourceGroup/context', toScmArgs],
|
||||||
|
['scm/resourceState/context', toScmArgs],
|
||||||
|
['scm/title', () => this.toScmArg(this.scmService.selectedRepository)],
|
||||||
|
['timeline/item/context', (...args) => this.toTimelineArgs(...args)],
|
||||||
|
['view/item/context', (...args) => this.toTreeArgs(...args)],
|
||||||
|
['view/title', noArgs],
|
||||||
|
]).forEach(([contributionPoint, adapter]) => {
|
||||||
|
if (adapter) {
|
||||||
|
const paths = codeToTheiaMappings.get(contributionPoint);
|
||||||
|
if (paths) {
|
||||||
|
paths.forEach((path) => this.addArgumentAdapter(path, adapter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.addArgumentAdapter(TAB_BAR_TOOLBAR_CONTEXT_MENU, widgetURI);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user