Skip to content

feat: Updated to [email protected] #1706

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 1, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -21,7 +21,8 @@
"--plugins=local-dir:../plugins",
"--hosted-plugin-inspect=9339",
"--content-trace",
"--open-devtools"
"--open-devtools",
"--no-ping-timeout",
],
"env": {
"NODE_ENV": "development"
@@ -56,7 +57,8 @@
"--remote-debugging-port=9222",
"--no-app-auto-install",
"--plugins=local-dir:../plugins",
"--hosted-plugin-inspect=9339"
"--hosted-plugin-inspect=9339",
"--no-ping-timeout",
],
"env": {
"NODE_ENV": "development"
1 change: 1 addition & 0 deletions arduino-ide-extension/package.json
Original file line number Diff line number Diff line change
@@ -60,6 +60,7 @@
"@types/react-virtualized": "^9.21.21",
"@types/temp": "^0.8.34",
"@types/which": "^1.3.1",
"@vscode/debugprotocol": "^1.51.0",
"arduino-serial-plotter-webapp": "0.2.0",
"async-mutex": "^0.3.0",
"auth0-js": "^9.14.0",
46 changes: 45 additions & 1 deletion arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
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 { CommandContribution } from '@theia/core/lib/common/command';
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 { TypeHierarchyContribution } from './theia/typehierarchy/type-hierarchy-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) => {
// Commands and toolbar items
@@ -960,4 +972,36 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
);
bind(TypeHierarchyContribution).toSelf().inSingletonScope();
rebind(TheiaTypeHierarchyContribution).toService(TypeHierarchyContribution);

// patched the debugger for `[email protected]`
// 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();
});
10 changes: 10 additions & 0 deletions arduino-ide-extension/src/browser/style/index.css
Original file line number Diff line number Diff line change
@@ -176,3 +176,13 @@ button.theia-button.message-box-dialog-button {
outline: 1px dashed var(--theia-focusBorder);
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;
}
29 changes: 29 additions & 0 deletions arduino-ide-extension/src/browser/theia/debug/debug-action.tsx
Original file line number Diff line number Diff line change
@@ -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>
);
}
}
Original file line number Diff line number Diff line change
@@ -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
);
}
}
120 changes: 120 additions & 0 deletions arduino-ide-extension/src/browser/theia/debug/debug-session-manager.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 231 additions & 0 deletions arduino-ide-extension/src/browser/theia/debug/debug-session.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
32 changes: 32 additions & 0 deletions arduino-ide-extension/src/browser/theia/debug/debug-stack-frame.ts
Original file line number Diff line number Diff line change
@@ -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,
});
}
}
22 changes: 22 additions & 0 deletions arduino-ide-extension/src/browser/theia/debug/debug-thread.ts
Original file line number Diff line number Diff line change
@@ -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()];
}
}
Original file line number Diff line number Diff line change
@@ -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}
/>
)
);
}
}
66 changes: 66 additions & 0 deletions arduino-ide-extension/src/browser/theia/plugin-ext/debug-main.ts
Original file line number Diff line number Diff line change
@@ -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))
);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import { Emitter, Event, JsonRpcProxy } from '@theia/core';
import { injectable, interfaces } from '@theia/core/shared/inversify';
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()
export class HostedPluginSupport extends TheiaHostedPluginSupport {
private readonly onDidLoadEmitter = new Emitter<void>();
@@ -31,4 +41,26 @@ export class HostedPluginSupport extends TheiaHostedPluginSupport {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
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);
}
}
Original file line number Diff line number Diff line change
@@ -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
);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
13 changes: 13 additions & 0 deletions arduino-ide-extension/src/node/arduino-ide-backend-module.ts
Original file line number Diff line number Diff line change
@@ -109,6 +109,10 @@ import {
} from '../common/protocol/survey-service';
import { IsTempSketch } from './is-temp-sketch';
import { rebindNsfwFileSystemWatcher } from './theia/filesystem/nsfw-watcher/nsfw-bindings';
import { MessagingContribution } from './theia/core/messaging-contribution';
import { MessagingService } from '@theia/core/lib/node/messaging/messaging-service';
import { HostedPluginReader } from './theia/plugin-ext/plugin-reader';
import { HostedPluginReader as TheiaHostedPluginReader } from '@theia/plugin-ext/lib/hosted/node/plugin-reader';

export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(BackendApplication).toSelf().inSingletonScope();
@@ -379,6 +383,15 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
.inSingletonScope();

bind(IsTempSketch).toSelf().inSingletonScope();
rebind(MessagingService.Identifier)
.to(MessagingContribution)
.inSingletonScope();

// Removed undesired contributions from VS Code extensions
// Such as the RTOS view from the `cortex-debug` extension
// https://github.com/arduino/arduino-ide/pull/1706#pullrequestreview-1195595080
bind(HostedPluginReader).toSelf().inSingletonScope();
rebind(TheiaHostedPluginReader).toService(HostedPluginReader);
});

function bindChildLogger(bind: interfaces.Bind, name: string): void {
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { MessagingContribution as TheiaMessagingContribution } from '@theia/core/lib/node/messaging/messaging-contribution';
import { injectable } from '@theia/core/shared/inversify';
@injectable()
export class MessagingContribution extends TheiaMessagingContribution {
// https://github.com/eclipse-theia/theia/discussions/11543
protected override checkAliveTimeout = process.argv.includes(
'--no-ping-timeout'
)
? 24 * 60 * 60 * 1_000 // one day
: 30_000;
}
80 changes: 80 additions & 0 deletions arduino-ide-extension/src/node/theia/plugin-ext/plugin-reader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { injectable } from '@theia/core/shared/inversify';
import type {
PluginContribution,
PluginPackage,
} from '@theia/plugin-ext/lib/common/plugin-protocol';
import { HostedPluginReader as TheiaHostedPluginReader } from '@theia/plugin-ext/lib/hosted/node/plugin-reader';

@injectable()
export class HostedPluginReader extends TheiaHostedPluginReader {
override readContribution(
plugin: PluginPackage
): PluginContribution | undefined {
const scanner = this.scanner.getScanner(plugin);
const contributions = scanner.getContribution(plugin);
return this.filterContribution(plugin.name, contributions);
}
private filterContribution(
pluginName: string,
contributions: PluginContribution | undefined
): PluginContribution | undefined {
if (!contributions) {
return contributions;
}
const filter = pluginFilters.get(pluginName);
return filter ? filter(contributions) : contributions;
}
}

type PluginContributionFilter = (
contribution: PluginContribution
) => PluginContribution | undefined;
const cortexDebugFilter: PluginContributionFilter = (
contribution: PluginContribution
) => {
if (contribution.viewsContainers) {
for (const location of Object.keys(contribution.viewsContainers)) {
const viewContainers = contribution.viewsContainers[location];
for (let i = 0; i < viewContainers.length; i++) {
const viewContainer = viewContainers[i];
if (
viewContainer.id === 'cortex-debug' &&
viewContainer.title === 'RTOS'
) {
viewContainers.splice(i, 1);
}
}
}
}
if (contribution.views) {
for (const location of Object.keys(contribution.views)) {
if (location === 'cortex-debug') {
const views = contribution.views[location];
for (let i = 0; i < views.length; i++) {
const view = views[i];
if (view.id === 'cortex-debug.rtos') {
views.splice(i, 1);
}
}
}
}
}
if (contribution.menus) {
for (const location of Object.keys(contribution.menus)) {
if (location === 'commandPalette') {
const menus = contribution.menus[location];
for (let i = 0; i < menus.length; i++) {
const menu = menus[i];
if (menu.command === 'cortex-debug.rtos.toggleRTOSPanel') {
menu.when = 'false';
}
}
}
}
}
return contribution;
};

const pluginFilters = new Map<string, PluginContributionFilter>([
['cortex-debug', cortexDebugFilter],
]);
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -79,7 +79,7 @@
"vscode-arduino-tools": "https://downloads.arduino.cc/vscode-arduino-tools/vscode-arduino-tools-0.0.2-beta.5.vsix",
"vscode-builtin-json": "https://open-vsx.org/api/vscode/json/1.46.1/file/vscode.json-1.46.1.vsix",
"vscode-builtin-json-language-features": "https://open-vsx.org/api/vscode/json-language-features/1.46.1/file/vscode.json-language-features-1.46.1.vsix",
"cortex-debug": "https://open-vsx.org/api/marus25/cortex-debug/0.3.10/file/marus25.cortex-debug-0.3.10.vsix",
"cortex-debug": "https://downloads.arduino.cc/marus25.cortex-debug/marus25.cortex-debug-1.5.1.vsix",
"vscode-language-pack-bg": "https://open-vsx.org/api/MS-CEINTL/vscode-language-pack-bg/1.48.3/file/MS-CEINTL.vscode-language-pack-bg-1.48.3.vsix",
"vscode-language-pack-cs": "https://open-vsx.org/api/MS-CEINTL/vscode-language-pack-cs/1.53.2/file/MS-CEINTL.vscode-language-pack-cs-1.53.2.vsix",
"vscode-language-pack-de": "https://open-vsx.org/api/MS-CEINTL/vscode-language-pack-de/1.53.2/file/MS-CEINTL.vscode-language-pack-de-1.53.2.vsix",