Skip to content

Commit 470db56

Browse files
author
Akos Kitta
committed
fix: debug widget layout updates
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>
1 parent 8c1ffe9 commit 470db56

File tree

3 files changed

+182
-11
lines changed

3 files changed

+182
-11
lines changed

arduino-ide-extension/src/browser/theia/debug/debug-widget.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
import {
2-
codicon,
3-
PanelLayout,
4-
Widget,
5-
} from '@theia/core/lib/browser/widgets/widget';
1+
import { codicon } from '@theia/core/lib/browser/widgets/widget';
2+
import { Widget } from '@theia/core/shared/@phosphor/widgets';
63
import {
74
inject,
85
injectable,
96
postConstruct,
107
} from '@theia/core/shared/inversify';
118
import { DebugWidget as TheiaDebugWidget } from '@theia/debug/lib/browser/view/debug-widget';
129
import { DebugDisabledStatusMessageSource } from '../../contributions/debug';
10+
import {
11+
removeWidgetIfPresent,
12+
unshiftWidgetIfNotPresent,
13+
} from '../dialogs/widgets';
1314

1415
@injectable()
1516
export class DebugWidget extends TheiaDebugWidget {
@@ -38,12 +39,10 @@ export class DebugWidget extends TheiaDebugWidget {
3839
this.messageNode.textContent = message ?? '';
3940
const enabled = !message;
4041
updateVisibility(enabled, this.toolbar, this.sessionWidget);
41-
if (this.layout instanceof PanelLayout) {
42-
if (enabled) {
43-
this.layout.removeWidget(this.statusMessageWidget);
44-
} else {
45-
this.layout.insertWidget(0, this.statusMessageWidget);
46-
}
42+
if (enabled) {
43+
removeWidgetIfPresent(this.layout, this.statusMessageWidget);
44+
} else {
45+
unshiftWidgetIfNotPresent(this.layout, this.statusMessageWidget);
4746
}
4847
this.title.iconClass = enabled ? codicon('debug-alt') : 'fa fa-ban'; // TODO: find a better icon?
4948
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import {
2+
Layout,
3+
PanelLayout,
4+
Widget,
5+
} from '@theia/core/shared/@phosphor/widgets';
6+
7+
/**
8+
*
9+
* Removes the widget from the layout if the `layout` is a `PanelLayout` and the widget is present in the layout.
10+
* Otherwise, it's NOOP
11+
* @param layout the layout to remove the widget from. Must be a `PanelLayout`.
12+
* @param toRemove the widget to remove from the layout
13+
*/
14+
export function removeWidgetIfPresent(
15+
layout: Layout | null,
16+
toRemove: Widget
17+
): void {
18+
if (layout instanceof PanelLayout) {
19+
const index = layout.widgets.indexOf(toRemove);
20+
if (index < 0) {
21+
// Unlike the default `PanelLayout#removeWidget` behavior, (https://github.com/phosphorjs/phosphor/blob/9f5e11025b62d2c4a6fb59e2681ae1ed323dcde4/packages/widgets/src/panellayout.ts#L154-L156)
22+
// do not try to remove widget if it's not present (the index is negative).
23+
// 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)
24+
// See https://github.com/arduino/arduino-ide/issues/2354 for more details.
25+
return;
26+
}
27+
layout.removeWidget(toRemove);
28+
}
29+
}
30+
31+
/**
32+
*
33+
* 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.
34+
* Otherwise, it's NOOP
35+
* @param layout the layout to add the widget to. Must be a `PanelLayout`.
36+
* @param toAdd the widget to add to the layout
37+
*/
38+
export function unshiftWidgetIfNotPresent(
39+
layout: Layout | null,
40+
toAdd: Widget
41+
): void {
42+
if (layout instanceof PanelLayout) {
43+
const index = layout.widgets.indexOf(toAdd);
44+
if (index >= 0) {
45+
// Do not try to add the widget to the layout if it's already present.
46+
// This is the counterpart logic of the `removeWidgetIfPresent` function.
47+
return;
48+
}
49+
layout.insertWidget(0, toAdd);
50+
}
51+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import {
2+
Disposable,
3+
DisposableCollection,
4+
} from '@theia/core/lib/common/disposable';
5+
import type { PanelLayout, Widget } from '@theia/core/shared/@phosphor/widgets';
6+
import { expect } from 'chai';
7+
import type {
8+
removeWidgetIfPresent,
9+
unshiftWidgetIfNotPresent,
10+
} from '../../browser/theia/dialogs/widgets';
11+
12+
describe('widgets', () => {
13+
let toDispose: DisposableCollection;
14+
15+
beforeEach(() => {
16+
const disableJSDOM =
17+
require('@theia/core/lib/browser/test/jsdom').enableJSDOM();
18+
toDispose = new DisposableCollection(
19+
Disposable.create(() => disableJSDOM())
20+
);
21+
});
22+
23+
afterEach(() => toDispose.dispose());
24+
25+
describe('removeWidgetIfPresent', () => {
26+
let testMe: typeof removeWidgetIfPresent;
27+
28+
beforeEach(
29+
() =>
30+
(testMe =
31+
require('../../browser/theia/dialogs/widgets').removeWidgetIfPresent)
32+
);
33+
34+
it('should remove the widget if present', () => {
35+
const layout = newPanelLayout();
36+
const widget = newWidget();
37+
layout.addWidget(widget);
38+
const toRemoveWidget = newWidget();
39+
layout.addWidget(toRemoveWidget);
40+
41+
expect(layout.widgets).to.be.deep.equal([widget, toRemoveWidget]);
42+
43+
testMe(layout, toRemoveWidget);
44+
45+
expect(layout.widgets).to.be.deep.equal([widget]);
46+
});
47+
48+
it('should be noop if the widget is not part of the layout', () => {
49+
const layout = newPanelLayout();
50+
const widget = newWidget();
51+
layout.addWidget(widget);
52+
53+
expect(layout.widgets).to.be.deep.equal([widget]);
54+
55+
testMe(layout, newWidget());
56+
57+
expect(layout.widgets).to.be.deep.equal([widget]);
58+
});
59+
});
60+
61+
describe('unshiftWidgetIfNotPresent', () => {
62+
let testMe: typeof unshiftWidgetIfNotPresent;
63+
64+
beforeEach(
65+
() =>
66+
(testMe =
67+
require('../../browser/theia/dialogs/widgets').unshiftWidgetIfNotPresent)
68+
);
69+
70+
it('should unshift the widget if not present', () => {
71+
const layout = newPanelLayout();
72+
const widget = newWidget();
73+
layout.addWidget(widget);
74+
75+
expect(layout.widgets).to.be.deep.equal([widget]);
76+
77+
const toAdd = newWidget();
78+
testMe(layout, toAdd);
79+
80+
expect(layout.widgets).to.be.deep.equal([toAdd, widget]);
81+
});
82+
83+
it('should be NOOP if widget is already part of the layout (at 0 index)', () => {
84+
const layout = newPanelLayout();
85+
const toAdd = newWidget();
86+
layout.addWidget(toAdd);
87+
const widget = newWidget();
88+
layout.addWidget(widget);
89+
90+
expect(layout.widgets).to.be.deep.equal([toAdd, widget]);
91+
92+
testMe(layout, toAdd);
93+
94+
expect(layout.widgets).to.be.deep.equal([toAdd, widget]);
95+
});
96+
97+
it('should be NOOP if widget is already part of the layout (at >0 index)', () => {
98+
const layout = newPanelLayout();
99+
const widget = newWidget();
100+
layout.addWidget(widget);
101+
const toAdd = newWidget();
102+
layout.addWidget(toAdd);
103+
104+
expect(layout.widgets).to.be.deep.equal([widget, toAdd]);
105+
106+
testMe(layout, toAdd);
107+
108+
expect(layout.widgets).to.be.deep.equal([widget, toAdd]);
109+
});
110+
});
111+
112+
function newWidget(): Widget {
113+
const { Widget } = require('@theia/core/shared/@phosphor/widgets');
114+
return new Widget();
115+
}
116+
117+
function newPanelLayout(): PanelLayout {
118+
const { PanelLayout } = require('@theia/core/shared/@phosphor/widgets');
119+
return new PanelLayout();
120+
}
121+
});

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy