mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-11-09 10:28:32 +00:00
feat: use new debug -I -P CLI output
- Can pick a programmer if missing, - Can auto-select a programmer on app start, - Can edit the `launch.json`, - Adjust board discovery to new gRPC API. From now on, it's a client read stream, not a duplex. - Allow `.cxx` and `.cc` file extensions. (Closes #2265) - Drop `debuggingSupported` from `BoardDetails`. - Dedicated service endpoint for checking the debugger. Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
@@ -1,44 +1,44 @@
|
||||
import debounce from 'p-debounce';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { Event, Emitter } from '@theia/core/lib/common/event';
|
||||
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||
import { DebugConfiguration } from '@theia/debug/lib/common/debug-common';
|
||||
import { DebugConfigurationModel as TheiaDebugConfigurationModel } from '@theia/debug/lib/browser/debug-configuration-model';
|
||||
import { Disposable } from '@theia/core/lib/common/disposable';
|
||||
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { DebugConfigurationManager as TheiaDebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager';
|
||||
import { DebugConfigurationModel as TheiaDebugConfigurationModel } from '@theia/debug/lib/browser/debug-configuration-model';
|
||||
import { DebugConfiguration } from '@theia/debug/lib/common/debug-common';
|
||||
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||
import {
|
||||
FileOperationError,
|
||||
FileOperationResult,
|
||||
} from '@theia/filesystem/lib/common/files';
|
||||
import debounce from 'p-debounce';
|
||||
import { SketchesService } from '../../../common/protocol';
|
||||
import {
|
||||
CurrentSketch,
|
||||
SketchesServiceClientImpl,
|
||||
} from '../../sketches-service-client-impl';
|
||||
import { maybeUpdateReadOnlyState } from '../monaco/monaco-editor-provider';
|
||||
import { DebugConfigurationModel } from './debug-configuration-model';
|
||||
import {
|
||||
FileOperationError,
|
||||
FileOperationResult,
|
||||
} from '@theia/filesystem/lib/common/files';
|
||||
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||
|
||||
@injectable()
|
||||
export class DebugConfigurationManager extends TheiaDebugConfigurationManager {
|
||||
@inject(SketchesService)
|
||||
protected readonly sketchesService: SketchesService;
|
||||
|
||||
private readonly sketchesService: SketchesService;
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
|
||||
private readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
@inject(FrontendApplicationStateService)
|
||||
protected readonly appStateService: FrontendApplicationStateService;
|
||||
|
||||
private readonly appStateService: FrontendApplicationStateService;
|
||||
@inject(FileService)
|
||||
protected readonly fileService: FileService;
|
||||
private readonly fileService: FileService;
|
||||
|
||||
protected onTempContentDidChangeEmitter =
|
||||
private onTempContentDidChangeEmitter =
|
||||
new Emitter<TheiaDebugConfigurationModel.JsonContent>();
|
||||
get onTempContentDidChange(): Event<TheiaDebugConfigurationModel.JsonContent> {
|
||||
return this.onTempContentDidChangeEmitter.event;
|
||||
}
|
||||
|
||||
protected override async doInit(): Promise<void> {
|
||||
this.watchLaunchConfigEditor();
|
||||
this.appStateService.reachedState('ready').then(async () => {
|
||||
const tempContent = await this.getTempLaunchJsonContent();
|
||||
if (!tempContent) {
|
||||
@@ -75,6 +75,19 @@ export class DebugConfigurationManager extends TheiaDebugConfigurationManager {
|
||||
return super.doInit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a listener on current sketch change, and maybe updates the readonly state of the editor showing the debug configuration. aka the `launch.json`.
|
||||
*/
|
||||
private watchLaunchConfigEditor(): Disposable {
|
||||
return this.sketchesServiceClient.onCurrentSketchDidChange(() => {
|
||||
for (const widget of this.editorManager.all) {
|
||||
maybeUpdateReadOnlyState(widget, (uri) =>
|
||||
this.sketchesServiceClient.isReadOnly(uri)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override updateModels = debounce(async () => {
|
||||
await this.appStateService.reachedState('ready');
|
||||
const roots = await this.workspaceService.roots;
|
||||
@@ -111,7 +124,7 @@ export class DebugConfigurationManager extends TheiaDebugConfigurationManager {
|
||||
this.updateCurrent();
|
||||
}, 500);
|
||||
|
||||
protected async getTempLaunchJsonContent(): Promise<
|
||||
private async getTempLaunchJsonContent(): Promise<
|
||||
(TheiaDebugConfigurationModel.JsonContent & { uri: URI }) | URI | undefined
|
||||
> {
|
||||
const sketch = await this.sketchesServiceClient.currentSketch();
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import React from '@theia/core/shared/react';
|
||||
import { DebugAction } from '@theia/debug/lib/browser/view/debug-action';
|
||||
import { DebugConfigurationSelect as TheiaDebugConfigurationSelect } from '@theia/debug/lib/browser/view/debug-configuration-select';
|
||||
import { DebugConfigurationWidget as TheiaDebugConfigurationWidget } from '@theia/debug/lib/browser/view/debug-configuration-widget';
|
||||
|
||||
/**
|
||||
* Patched to programmatically update the debug config <select> in the widget.
|
||||
*/
|
||||
@injectable()
|
||||
export class DebugConfigurationWidget extends TheiaDebugConfigurationWidget {
|
||||
override render(): React.ReactNode {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DebugAction
|
||||
run={this.start}
|
||||
label={nls.localizeByDefault('Start Debugging')}
|
||||
iconClass="debug-start"
|
||||
ref={this.setStepRef}
|
||||
/>
|
||||
{/* The customized select component that will refresh when the config manager did change */}
|
||||
<DebugConfigurationSelect
|
||||
manager={this.manager}
|
||||
quickInputService={this.quickInputService}
|
||||
isMultiRoot={this.workspaceService.isMultiRootWorkspaceOpened}
|
||||
/>
|
||||
<DebugAction
|
||||
run={this.openConfiguration}
|
||||
label={nls.localizeByDefault('Open {0}', '"launch.json"')}
|
||||
iconClass="settings-gear"
|
||||
/>
|
||||
<DebugAction
|
||||
run={this.openConsole}
|
||||
label={nls.localizeByDefault('Debug Console')}
|
||||
iconClass="terminal"
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DebugConfigurationSelect extends TheiaDebugConfigurationSelect {
|
||||
private readonly toDisposeOnUnmount = new DisposableCollection();
|
||||
|
||||
override componentDidMount(): void {
|
||||
super.componentDidMount();
|
||||
this.toDisposeOnUnmount.push(
|
||||
this['manager'].onDidChange(() => this.refreshDebugConfigurations())
|
||||
);
|
||||
}
|
||||
|
||||
override componentWillUnmount(): void {
|
||||
this.toDisposeOnUnmount.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { DebugSession } from '@theia/debug/lib/browser/debug-session';
|
||||
import { DebugSessionManager as TheiaDebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager';
|
||||
import { DebugConfigurationSessionOptions } from '@theia/debug/lib/browser/debug-session-options';
|
||||
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
|
||||
@injectable()
|
||||
export class DebugSessionManager extends TheiaDebugSessionManager {
|
||||
@inject(WorkspaceService)
|
||||
private readonly workspaceService: WorkspaceService;
|
||||
|
||||
protected override doStart(
|
||||
sessionId: string,
|
||||
options: DebugConfigurationSessionOptions
|
||||
): Promise<DebugSession> {
|
||||
this.syncCurrentOptions(options);
|
||||
return super.doStart(sessionId, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the debug config manager knows about the currently started options, and it's not the currently selected one, select it.
|
||||
*/
|
||||
private syncCurrentOptions(options: DebugConfigurationSessionOptions): void {
|
||||
const knownConfigOptions = this.debugConfigurationManager.find(
|
||||
options.configuration,
|
||||
options.workspaceFolderUri ??
|
||||
this.workspaceService
|
||||
.tryGetRoots()
|
||||
.map((stat) => stat.resource.toString())[0]
|
||||
);
|
||||
if (
|
||||
knownConfigOptions &&
|
||||
!deepEqual(knownConfigOptions, this.debugConfigurationManager.current)
|
||||
) {
|
||||
this.debugConfigurationManager.current = knownConfigOptions;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { LOCKED_CLASS, lock } from '@theia/core/lib/browser/widgets/widget';
|
||||
import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
} from '@theia/core/lib/common/disposable';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { Title, Widget } from '@theia/core/shared/@phosphor/widgets';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import { EditorWidget } from '@theia/editor/lib/browser/editor-widget';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import type { ReferencesModel } from '@theia/monaco-editor-core/esm/vs/editor/contrib/gotoSymbol/browser/referencesModel';
|
||||
import {
|
||||
EditorServiceOverrides,
|
||||
MonacoEditor,
|
||||
} from '@theia/monaco/lib/browser/monaco-editor';
|
||||
import { MonacoEditorProvider as TheiaMonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider';
|
||||
import { SketchesServiceClientImpl } from '../../sketches-service-client-impl';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import type { ReferencesModel } from '@theia/monaco-editor-core/esm/vs/editor/contrib/gotoSymbol/browser/referencesModel';
|
||||
|
||||
type CancelablePromise = Promise<ReferencesModel> & {
|
||||
cancel: () => void;
|
||||
@@ -101,3 +104,30 @@ export class MonacoEditorProvider extends TheiaMonacoEditorProvider {
|
||||
editor.updateOptions({ readOnly });
|
||||
}
|
||||
}
|
||||
|
||||
// Theia cannot dynamically set an editor to writable once it was readonly.
|
||||
export function maybeUpdateReadOnlyState(
|
||||
widget: EditorWidget,
|
||||
isReadOnly: (uri: string | URI | monaco.Uri) => boolean
|
||||
): void {
|
||||
const editor = widget.editor;
|
||||
if (!(editor instanceof MonacoEditor)) {
|
||||
return;
|
||||
}
|
||||
const model = editor.document;
|
||||
const oldReadOnly = model.readOnly;
|
||||
const resource = model['resource'];
|
||||
const newReadOnly = Boolean(resource.isReadonly) || isReadOnly(resource.uri);
|
||||
if (oldReadOnly !== newReadOnly) {
|
||||
editor.getControl().updateOptions({ readOnly: newReadOnly });
|
||||
if (newReadOnly) {
|
||||
lock(widget.title);
|
||||
} else {
|
||||
unlock(widget.title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function unlock(title: Title<Widget>): void {
|
||||
title.className = title.className.replace(LOCKED_CLASS, '').trim();
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ class MaybeReadonlyMonacoEditorModel extends SilentMonacoEditorModel {
|
||||
}
|
||||
this._dirty = dirty;
|
||||
if (dirty === false) {
|
||||
(this as any).updateSavedVersionId();
|
||||
this['updateSavedVersionId']();
|
||||
}
|
||||
this.onDirtyChangedEmitter.fire(undefined);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user