Skip to content

Commit

Permalink
fix: debug widget layout updates
Browse files Browse the repository at this point in the history
Use customized `PanelLayout#removeWidget` and
`PanelLayout#insertWidget` logic for the layout
updates. The customized functions ensure no side
effect if adding/removing the widget to/from the
layout but it's already present/absent.

Unlike the default [`PanelLayout#removeWidget`](https://github.com/phosphorjs/phosphor/blob/9f5e11025b62d2c4a6fb59e2681ae1ed323dcde4/packages/widgets/src/panellayout.ts#L154-L156)
behavior, do not try to remove the widget if it's
not present (has a negative index). Otherwise,
required widgets might be removed based on the
default [`ArrayExt#removeAt`](https://github.com/phosphorjs/phosphor/blob/9f5e11025b62d2c4a6fb59e2681ae1ed323dcde4/packages/algorithm/src/array.ts#L1075-L1077)
behavior.

Closes: #2354

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
  • Loading branch information
Akos Kitta authored and kittaakos committed Feb 8, 2024
1 parent 74c5801 commit ca779e5
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 11 deletions.
21 changes: 10 additions & 11 deletions arduino-ide-extension/src/browser/theia/debug/debug-widget.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import {
codicon,
PanelLayout,
Widget,
} from '@theia/core/lib/browser/widgets/widget';
import { codicon } from '@theia/core/lib/browser/widgets/widget';
import { Widget } from '@theia/core/shared/@phosphor/widgets';
import {
inject,
injectable,
postConstruct,
} from '@theia/core/shared/inversify';
import { DebugWidget as TheiaDebugWidget } from '@theia/debug/lib/browser/view/debug-widget';
import { DebugDisabledStatusMessageSource } from '../../contributions/debug';
import {
removeWidgetIfPresent,
unshiftWidgetIfNotPresent,
} from '../dialogs/widgets';

@injectable()
export class DebugWidget extends TheiaDebugWidget {
Expand Down Expand Up @@ -38,12 +39,10 @@ export class DebugWidget extends TheiaDebugWidget {
this.messageNode.textContent = message ?? '';
const enabled = !message;
updateVisibility(enabled, this.toolbar, this.sessionWidget);
if (this.layout instanceof PanelLayout) {
if (enabled) {
this.layout.removeWidget(this.statusMessageWidget);
} else {
this.layout.insertWidget(0, this.statusMessageWidget);
}
if (enabled) {
removeWidgetIfPresent(this.layout, this.statusMessageWidget);
} else {
unshiftWidgetIfNotPresent(this.layout, this.statusMessageWidget);
}
this.title.iconClass = enabled ? codicon('debug-alt') : 'fa fa-ban'; // TODO: find a better icon?
});
Expand Down
51 changes: 51 additions & 0 deletions arduino-ide-extension/src/browser/theia/dialogs/widgets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
Layout,
PanelLayout,
Widget,
} from '@theia/core/shared/@phosphor/widgets';

/**
*
* Removes the widget from the layout if the `layout` is a `PanelLayout` and the widget is present in the layout.
* Otherwise, it's NOOP
* @param layout the layout to remove the widget from. Must be a `PanelLayout`.
* @param toRemove the widget to remove from the layout
*/
export function removeWidgetIfPresent(
layout: Layout | null,
toRemove: Widget
): void {
if (layout instanceof PanelLayout) {
const index = layout.widgets.indexOf(toRemove);
if (index < 0) {
// Unlike the default `PanelLayout#removeWidget` behavior, (https://github.com/phosphorjs/phosphor/blob/9f5e11025b62d2c4a6fb59e2681ae1ed323dcde4/packages/widgets/src/panellayout.ts#L154-L156)
// do not try to remove widget if it's not present (the index is negative).
// Otherwise, required widgets could be removed based on the default ArrayExt behavior (https://github.com/phosphorjs/phosphor/blob/9f5e11025b62d2c4a6fb59e2681ae1ed323dcde4/packages/algorithm/src/array.ts#L1075-L1077)
// See https://github.com/arduino/arduino-ide/issues/2354 for more details.
return;
}
layout.removeWidget(toRemove);
}
}

/**
*
* Inserts the widget to the `0` index of the layout if the `layout` is a `PanelLayout` and the widget is not yet part of the layout.
* Otherwise, it's NOOP
* @param layout the layout to add the widget to. Must be a `PanelLayout`.
* @param toAdd the widget to add to the layout
*/
export function unshiftWidgetIfNotPresent(
layout: Layout | null,
toAdd: Widget
): void {
if (layout instanceof PanelLayout) {
const index = layout.widgets.indexOf(toAdd);
if (index >= 0) {
// Do not try to add the widget to the layout if it's already present.
// This is the counterpart logic of the `removeWidgetIfPresent` function.
return;
}
layout.insertWidget(0, toAdd);
}
}
121 changes: 121 additions & 0 deletions arduino-ide-extension/src/test/browser/widgets.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import {
Disposable,
DisposableCollection,
} from '@theia/core/lib/common/disposable';
import type { PanelLayout, Widget } from '@theia/core/shared/@phosphor/widgets';
import { expect } from 'chai';
import type {
removeWidgetIfPresent,
unshiftWidgetIfNotPresent,
} from '../../browser/theia/dialogs/widgets';

describe('widgets', () => {
let toDispose: DisposableCollection;

beforeEach(() => {
const disableJSDOM =
require('@theia/core/lib/browser/test/jsdom').enableJSDOM();
toDispose = new DisposableCollection(
Disposable.create(() => disableJSDOM())
);
});

afterEach(() => toDispose.dispose());

describe('removeWidgetIfPresent', () => {
let testMe: typeof removeWidgetIfPresent;

beforeEach(
() =>
(testMe =
require('../../browser/theia/dialogs/widgets').removeWidgetIfPresent)
);

it('should remove the widget if present', () => {
const layout = newPanelLayout();
const widget = newWidget();
layout.addWidget(widget);
const toRemoveWidget = newWidget();
layout.addWidget(toRemoveWidget);

expect(layout.widgets).to.be.deep.equal([widget, toRemoveWidget]);

testMe(layout, toRemoveWidget);

expect(layout.widgets).to.be.deep.equal([widget]);
});

it('should be noop if the widget is not part of the layout', () => {
const layout = newPanelLayout();
const widget = newWidget();
layout.addWidget(widget);

expect(layout.widgets).to.be.deep.equal([widget]);

testMe(layout, newWidget());

expect(layout.widgets).to.be.deep.equal([widget]);
});
});

describe('unshiftWidgetIfNotPresent', () => {
let testMe: typeof unshiftWidgetIfNotPresent;

beforeEach(
() =>
(testMe =
require('../../browser/theia/dialogs/widgets').unshiftWidgetIfNotPresent)
);

it('should unshift the widget if not present', () => {
const layout = newPanelLayout();
const widget = newWidget();
layout.addWidget(widget);

expect(layout.widgets).to.be.deep.equal([widget]);

const toAdd = newWidget();
testMe(layout, toAdd);

expect(layout.widgets).to.be.deep.equal([toAdd, widget]);
});

it('should be NOOP if widget is already part of the layout (at 0 index)', () => {
const layout = newPanelLayout();
const toAdd = newWidget();
layout.addWidget(toAdd);
const widget = newWidget();
layout.addWidget(widget);

expect(layout.widgets).to.be.deep.equal([toAdd, widget]);

testMe(layout, toAdd);

expect(layout.widgets).to.be.deep.equal([toAdd, widget]);
});

it('should be NOOP if widget is already part of the layout (at >0 index)', () => {
const layout = newPanelLayout();
const widget = newWidget();
layout.addWidget(widget);
const toAdd = newWidget();
layout.addWidget(toAdd);

expect(layout.widgets).to.be.deep.equal([widget, toAdd]);

testMe(layout, toAdd);

expect(layout.widgets).to.be.deep.equal([widget, toAdd]);
});
});

function newWidget(): Widget {
const { Widget } = require('@theia/core/shared/@phosphor/widgets');
return new Widget();
}

function newPanelLayout(): PanelLayout {
const { PanelLayout } = require('@theia/core/shared/@phosphor/widgets');
return new PanelLayout();
}
});

0 comments on commit ca779e5

Please sign in to comment.