@theia/terminal-manager 1.67.0-next.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +31 -0
  2. package/lib/browser/terminal-manager-frontend-contribution.d.ts +25 -0
  3. package/lib/browser/terminal-manager-frontend-contribution.d.ts.map +1 -0
  4. package/lib/browser/terminal-manager-frontend-contribution.js +173 -0
  5. package/lib/browser/terminal-manager-frontend-contribution.js.map +1 -0
  6. package/lib/browser/terminal-manager-frontend-module.d.ts +5 -0
  7. package/lib/browser/terminal-manager-frontend-module.d.ts.map +1 -0
  8. package/lib/browser/terminal-manager-frontend-module.js +55 -0
  9. package/lib/browser/terminal-manager-frontend-module.js.map +1 -0
  10. package/lib/browser/terminal-manager-frontend-view-contribution.d.ts +27 -0
  11. package/lib/browser/terminal-manager-frontend-view-contribution.d.ts.map +1 -0
  12. package/lib/browser/terminal-manager-frontend-view-contribution.js +278 -0
  13. package/lib/browser/terminal-manager-frontend-view-contribution.js.map +1 -0
  14. package/lib/browser/terminal-manager-preferences.d.ts +12 -0
  15. package/lib/browser/terminal-manager-preferences.d.ts.map +1 -0
  16. package/lib/browser/terminal-manager-preferences.js +42 -0
  17. package/lib/browser/terminal-manager-preferences.js.map +1 -0
  18. package/lib/browser/terminal-manager-tree-model.d.ts +71 -0
  19. package/lib/browser/terminal-manager-tree-model.d.ts.map +1 -0
  20. package/lib/browser/terminal-manager-tree-model.js +299 -0
  21. package/lib/browser/terminal-manager-tree-model.js.map +1 -0
  22. package/lib/browser/terminal-manager-tree-widget.d.ts +40 -0
  23. package/lib/browser/terminal-manager-tree-widget.d.ts.map +1 -0
  24. package/lib/browser/terminal-manager-tree-widget.js +276 -0
  25. package/lib/browser/terminal-manager-tree-widget.js.map +1 -0
  26. package/lib/browser/terminal-manager-types.d.ts +77 -0
  27. package/lib/browser/terminal-manager-types.d.ts.map +1 -0
  28. package/lib/browser/terminal-manager-types.js +117 -0
  29. package/lib/browser/terminal-manager-types.js.map +1 -0
  30. package/lib/browser/terminal-manager-widget.d.ts +108 -0
  31. package/lib/browser/terminal-manager-widget.d.ts.map +1 -0
  32. package/lib/browser/terminal-manager-widget.js +616 -0
  33. package/lib/browser/terminal-manager-widget.js.map +1 -0
  34. package/lib/package.spec.d.ts +1 -0
  35. package/lib/package.spec.d.ts.map +1 -0
  36. package/lib/package.spec.js +26 -0
  37. package/lib/package.spec.js.map +1 -0
  38. package/package.json +40 -0
  39. package/src/browser/terminal-manager-frontend-contribution.ts +164 -0
  40. package/src/browser/terminal-manager-frontend-module.ts +64 -0
  41. package/src/browser/terminal-manager-frontend-view-contribution.ts +309 -0
  42. package/src/browser/terminal-manager-preferences.ts +52 -0
  43. package/src/browser/terminal-manager-tree-model.ts +336 -0
  44. package/src/browser/terminal-manager-tree-widget.tsx +305 -0
  45. package/src/browser/terminal-manager-types.ts +173 -0
  46. package/src/browser/terminal-manager-widget.ts +686 -0
  47. package/src/browser/terminal-manager.css +51 -0
  48. package/src/package.spec.ts +27 -0
@@ -0,0 +1,336 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2023 Ericsson and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { injectable, postConstruct } from '@theia/core/shared/inversify';
18
+ import { TreeModelImpl, CompositeTreeNode, SelectableTreeNode, DepthFirstTreeIterator } from '@theia/core/lib/browser';
19
+ import { Emitter } from '@theia/core';
20
+ import { TerminalManagerTreeTypes } from './terminal-manager-types';
21
+
22
+ @injectable()
23
+ export class TerminalManagerTreeModel extends TreeModelImpl {
24
+ activePageNode: TerminalManagerTreeTypes.PageNode | undefined;
25
+ activeGroupNode: TerminalManagerTreeTypes.TerminalGroupNode | undefined;
26
+ activeTerminalNode: TerminalManagerTreeTypes.TerminalNode | undefined;
27
+
28
+ protected onDidChangeTreeSelectionEmitter = new Emitter<TerminalManagerTreeTypes.SelectionChangedEvent>();
29
+ readonly onDidChangeTreeSelection = this.onDidChangeTreeSelectionEmitter.event;
30
+
31
+ protected onDidAddPageEmitter = new Emitter<{ pageId: TerminalManagerTreeTypes.PageId, terminalKey: TerminalManagerTreeTypes.TerminalKey }>();
32
+ readonly onDidAddPage = this.onDidAddPageEmitter.event;
33
+ protected onDidDeletePageEmitter = new Emitter<TerminalManagerTreeTypes.PageId>();
34
+ readonly onDidDeletePage = this.onDidDeletePageEmitter.event;
35
+
36
+ protected onDidRenameNodeEmitter = new Emitter<TerminalManagerTreeTypes.TerminalManagerTreeNode>();
37
+ readonly onDidRenameNode = this.onDidRenameNodeEmitter.event;
38
+
39
+ protected onDidAddTerminalGroupEmitter = new Emitter<{
40
+ groupId: TerminalManagerTreeTypes.GroupId,
41
+ pageId: TerminalManagerTreeTypes.PageId,
42
+ terminalKey: TerminalManagerTreeTypes.TerminalKey,
43
+ }>();
44
+ readonly onDidAddTerminalGroup = this.onDidAddTerminalGroupEmitter.event;
45
+
46
+ protected onDidDeleteTerminalGroupEmitter = new Emitter<TerminalManagerTreeTypes.GroupId>();
47
+ readonly onDidDeleteTerminalGroup = this.onDidDeleteTerminalGroupEmitter.event;
48
+
49
+ protected onDidAddTerminalToGroupEmitter = new Emitter<{
50
+ terminalId: TerminalManagerTreeTypes.TerminalKey,
51
+ groupId: TerminalManagerTreeTypes.GroupId,
52
+ }>();
53
+ readonly onDidAddTerminalToGroup = this.onDidAddTerminalToGroupEmitter.event;
54
+
55
+ protected onDidDeleteTerminalFromGroupEmitter = new Emitter<{
56
+ terminalId: TerminalManagerTreeTypes.TerminalKey,
57
+ groupId: TerminalManagerTreeTypes.GroupId,
58
+ }>();
59
+ readonly onDidDeleteTerminalFromGroup = this.onDidDeleteTerminalFromGroupEmitter.event;
60
+
61
+ @postConstruct()
62
+ protected override init(): void {
63
+ super.init();
64
+ this.toDispose.push(this.selectionService.onSelectionChanged(selectionEvent => {
65
+ const selectedNode = selectionEvent.find(node => node.selected);
66
+ if (selectedNode) {
67
+ this.handleSelectionChanged(selectedNode);
68
+ }
69
+ }));
70
+ this.root = { id: 'root', parent: undefined, children: [], visible: false } as CompositeTreeNode;
71
+ }
72
+
73
+ addTerminalPage(
74
+ terminalKey: TerminalManagerTreeTypes.TerminalKey,
75
+ groupId: TerminalManagerTreeTypes.GroupId,
76
+ pageId: TerminalManagerTreeTypes.PageId,
77
+ ): void {
78
+ const pageNode = this.createPageNode(pageId);
79
+ const groupNode = this.createGroupNode(groupId, pageId);
80
+ const terminalNode = this.createTerminalNode(terminalKey, groupId);
81
+ if (this.root && CompositeTreeNode.is(this.root)) {
82
+ this.activePageNode = pageNode;
83
+ CompositeTreeNode.addChild(groupNode, terminalNode);
84
+ CompositeTreeNode.addChild(pageNode, groupNode);
85
+ this.root = CompositeTreeNode.addChild(this.root, pageNode);
86
+ this.onDidAddPageEmitter.fire({ pageId: pageNode.id, terminalKey });
87
+ setTimeout(() => {
88
+ this.selectionService.addSelection(terminalNode);
89
+ });
90
+ }
91
+ }
92
+
93
+ protected createPageNode(pageId: TerminalManagerTreeTypes.PageId): TerminalManagerTreeTypes.PageNode {
94
+ const currentPageNumber = this.getNextPageCounter();
95
+ return {
96
+ id: pageId,
97
+ label: `Page(${currentPageNumber})`,
98
+ parent: undefined,
99
+ selected: false,
100
+ children: [],
101
+ page: true,
102
+ isEditing: false,
103
+ expanded: true,
104
+ counter: currentPageNumber,
105
+ };
106
+ }
107
+
108
+ protected getNextPageCounter(): number {
109
+ return Math.max(0, ...Array.from(this.pages.values(), page => page.counter)) + 1;
110
+ }
111
+
112
+ deleteTerminalPage(pageId: TerminalManagerTreeTypes.PageId): void {
113
+ const pageNode = this.getNode(pageId);
114
+ if (TerminalManagerTreeTypes.isPageNode(pageNode) && CompositeTreeNode.is(this.root)) {
115
+ while (pageNode.children.length > 0) {
116
+ const groupNode = pageNode.children[pageNode.children.length - 1];
117
+ this.doDeleteTerminalGroup(groupNode, pageNode);
118
+ }
119
+ this.onDidDeletePageEmitter.fire(pageNode.id);
120
+ CompositeTreeNode.removeChild(this.root, pageNode);
121
+ setTimeout(() => this.selectPrevNode());
122
+ this.refresh();
123
+ }
124
+ }
125
+
126
+ addTerminalGroup(
127
+ terminalKey: TerminalManagerTreeTypes.TerminalKey,
128
+ groupId: TerminalManagerTreeTypes.GroupId,
129
+ pageId: TerminalManagerTreeTypes.PageId,
130
+ ): void {
131
+ const groupNode = this.createGroupNode(groupId, pageId);
132
+ const terminalNode = this.createTerminalNode(terminalKey, groupId);
133
+ const pageNode = this.getNode(pageId);
134
+ if (this.root && CompositeTreeNode.is(this.root) && TerminalManagerTreeTypes.isPageNode(pageNode)) {
135
+ this.onDidAddTerminalGroupEmitter.fire({ groupId: groupNode.id, pageId, terminalKey });
136
+ CompositeTreeNode.addChild(groupNode, terminalNode);
137
+ CompositeTreeNode.addChild(pageNode, groupNode);
138
+ this.refresh();
139
+ setTimeout(() => {
140
+ this.selectionService.addSelection(terminalNode);
141
+ });
142
+ }
143
+ }
144
+
145
+ protected createGroupNode(
146
+ groupId: TerminalManagerTreeTypes.GroupId,
147
+ pageId: TerminalManagerTreeTypes.PageId,
148
+ ): TerminalManagerTreeTypes.TerminalGroupNode {
149
+ const currentGroupNum = this.getNextGroupCounterForPage(pageId);
150
+ return {
151
+ id: groupId,
152
+ label: `Group(${currentGroupNum})`,
153
+ parent: undefined,
154
+ selected: false,
155
+ children: [],
156
+ terminalGroup: true,
157
+ isEditing: false,
158
+ parentPageId: pageId,
159
+ expanded: true,
160
+ counter: currentGroupNum,
161
+ };
162
+ }
163
+
164
+ protected getNextGroupCounterForPage(pageId: TerminalManagerTreeTypes.PageId): number {
165
+ const page = this.pages.get(pageId);
166
+ if (page) {
167
+ return Math.max(0, ...page.children.map(group => group.counter)) + 1;
168
+ }
169
+ return 1;
170
+ }
171
+
172
+ deleteTerminalGroup(groupId: TerminalManagerTreeTypes.GroupId): void {
173
+ const groupNode = this.tree.getNode(groupId);
174
+ const parentPageNode = groupNode?.parent;
175
+ if (TerminalManagerTreeTypes.isGroupNode(groupNode) && TerminalManagerTreeTypes.isPageNode(parentPageNode)) {
176
+ if (parentPageNode.children.length === 1) {
177
+ this.deleteTerminalPage(parentPageNode.id);
178
+ } else {
179
+ this.doDeleteTerminalGroup(groupNode, parentPageNode);
180
+ this.refresh();
181
+ }
182
+ }
183
+ }
184
+
185
+ protected doDeleteTerminalGroup(group: TerminalManagerTreeTypes.TerminalGroupNode, page: TerminalManagerTreeTypes.PageNode): void {
186
+ while (group.children.length > 0) {
187
+ const terminalNode = group.children[group.children.length - 1];
188
+ this.doDeleteTerminalNode(terminalNode, group);
189
+ }
190
+ this.onDidDeleteTerminalGroupEmitter.fire(group.id);
191
+ CompositeTreeNode.removeChild(page, group);
192
+ }
193
+
194
+ addTerminal(newTerminalId: TerminalManagerTreeTypes.TerminalKey, groupId: TerminalManagerTreeTypes.GroupId): void {
195
+ const groupNode = this.getNode(groupId);
196
+ if (groupNode && TerminalManagerTreeTypes.isGroupNode(groupNode)) {
197
+ const terminalNode = this.createTerminalNode(newTerminalId, groupId);
198
+ CompositeTreeNode.addChild(groupNode, terminalNode);
199
+ this.onDidAddTerminalToGroupEmitter.fire({ terminalId: newTerminalId, groupId });
200
+ this.refresh();
201
+ setTimeout(() => {
202
+ if (SelectableTreeNode.is(terminalNode)) {
203
+ this.selectionService.addSelection(terminalNode);
204
+ }
205
+ });
206
+ }
207
+ }
208
+
209
+ createTerminalNode(
210
+ terminalId: TerminalManagerTreeTypes.TerminalKey,
211
+ groupId: TerminalManagerTreeTypes.GroupId,
212
+ ): TerminalManagerTreeTypes.TerminalNode {
213
+ return {
214
+ id: terminalId,
215
+ label: 'Terminal',
216
+ parent: undefined,
217
+ children: [],
218
+ selected: false,
219
+ terminal: true,
220
+ isEditing: false,
221
+ parentGroupId: groupId,
222
+ };
223
+ }
224
+
225
+ deleteTerminalNode(terminalId: TerminalManagerTreeTypes.TerminalKey): void {
226
+ const terminalNode = this.getNode(terminalId);
227
+ const parentGroupNode = terminalNode?.parent;
228
+ if (TerminalManagerTreeTypes.isTerminalNode(terminalNode) && TerminalManagerTreeTypes.isGroupNode(parentGroupNode)) {
229
+ if (parentGroupNode.children.length === 1) {
230
+ this.deleteTerminalGroup(parentGroupNode.id);
231
+ } else {
232
+ this.doDeleteTerminalNode(terminalNode, parentGroupNode);
233
+ this.refresh();
234
+ }
235
+ }
236
+ }
237
+
238
+ protected doDeleteTerminalNode(node: TerminalManagerTreeTypes.TerminalNode, parent: TerminalManagerTreeTypes.TerminalGroupNode): void {
239
+ if (TerminalManagerTreeTypes.isGroupNode(parent)) {
240
+ this.onDidDeleteTerminalFromGroupEmitter.fire({
241
+ terminalId: node.id,
242
+ groupId: parent.id,
243
+ });
244
+ CompositeTreeNode.removeChild(parent, node);
245
+ }
246
+ }
247
+
248
+ toggleRenameTerminal(entityId: TerminalManagerTreeTypes.TerminalManagerValidId): void {
249
+ const node = this.getNode(entityId);
250
+ if (TerminalManagerTreeTypes.isTerminalManagerTreeNode(node)) {
251
+ node.isEditing = true;
252
+ this.fireChanged();
253
+ }
254
+ }
255
+
256
+ acceptRename(nodeId: string, newName: string): void {
257
+ const node = this.getNode(nodeId);
258
+ if (TerminalManagerTreeTypes.isTerminalManagerTreeNode(node)) {
259
+ const trimmedName = newName.trim();
260
+ node.label = trimmedName === '' ? node.label : newName;
261
+ node.isEditing = false;
262
+ this.fireChanged();
263
+ this.onDidRenameNodeEmitter.fire(node);
264
+ }
265
+ }
266
+
267
+ handleSelectionChanged(selectedNode: SelectableTreeNode): void {
268
+ let activeTerminal: TerminalManagerTreeTypes.TerminalNode | undefined = undefined;
269
+ let activeGroup: TerminalManagerTreeTypes.TerminalGroupNode | undefined = undefined;
270
+ let activePage: TerminalManagerTreeTypes.PageNode | undefined = undefined;
271
+
272
+ if (TerminalManagerTreeTypes.isTerminalNode(selectedNode)) {
273
+ activeTerminal = selectedNode;
274
+ const { parent } = activeTerminal;
275
+ if (TerminalManagerTreeTypes.isGroupNode(parent)) {
276
+ activeGroup = parent;
277
+ const grandparent = activeGroup.parent;
278
+ if (TerminalManagerTreeTypes.isPageNode(grandparent)) {
279
+ activePage = grandparent;
280
+ }
281
+ } else if (TerminalManagerTreeTypes.isPageNode(parent)) {
282
+ activePage = parent;
283
+ }
284
+ } else if (TerminalManagerTreeTypes.isGroupNode(selectedNode)) {
285
+ const { parent } = selectedNode;
286
+ activeGroup = selectedNode;
287
+ if (TerminalManagerTreeTypes.isPageNode(parent)) {
288
+ activePage = parent;
289
+ }
290
+ } else if (TerminalManagerTreeTypes.isPageNode(selectedNode)) {
291
+ activePage = selectedNode;
292
+ }
293
+
294
+ this.activeTerminalNode = activeTerminal;
295
+ this.activeGroupNode = activeGroup;
296
+ this.activePageNode = activePage;
297
+ this.onDidChangeTreeSelectionEmitter.fire({
298
+ activePageId: activePage?.id,
299
+ activeTerminalId: activeTerminal?.id,
300
+ activeGroupId: activeGroup?.id,
301
+ });
302
+ }
303
+
304
+ get pages(): Map<TerminalManagerTreeTypes.PageId, TerminalManagerTreeTypes.PageNode> {
305
+ const pages = new Map<TerminalManagerTreeTypes.PageId, TerminalManagerTreeTypes.PageNode>();
306
+ if (!this.root) {
307
+ return pages;
308
+ }
309
+ for (const node of new DepthFirstTreeIterator(this.root)) {
310
+ if (TerminalManagerTreeTypes.isPageNode(node)) {
311
+ pages.set(node.id, node);
312
+ }
313
+ }
314
+ return pages;
315
+ }
316
+
317
+ getPageIdForTerminal(terminalKey: TerminalManagerTreeTypes.TerminalKey): TerminalManagerTreeTypes.PageId | undefined {
318
+ const terminalNode = this.getNode(terminalKey);
319
+ if (!TerminalManagerTreeTypes.isTerminalNode(terminalNode)) {
320
+ return undefined;
321
+ }
322
+ const { parentGroupId } = terminalNode;
323
+ const groupNode = this.getNode(parentGroupId);
324
+ if (!TerminalManagerTreeTypes.isGroupNode(groupNode)) {
325
+ return undefined;
326
+ }
327
+ return groupNode.parentPageId;
328
+ }
329
+
330
+ selectTerminalNode(terminalKey: TerminalManagerTreeTypes.TerminalKey): void {
331
+ const node = this.getNode(terminalKey);
332
+ if (node && TerminalManagerTreeTypes.isTerminalNode(node)) {
333
+ this.selectNode(node);
334
+ }
335
+ }
336
+ }
@@ -0,0 +1,305 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2023 Ericsson and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import * as React from '@theia/core/shared/react';
18
+ import { Container, inject, injectable, interfaces, postConstruct } from '@theia/core/shared/inversify';
19
+ import {
20
+ codicon,
21
+ CompositeTreeNode,
22
+ createTreeContainer,
23
+ Key,
24
+ Message,
25
+ NodeProps,
26
+ SelectableTreeNode,
27
+ TreeModel,
28
+ TreeNode,
29
+ TreeWidget,
30
+ TREE_NODE_INDENT_GUIDE_CLASS,
31
+ } from '@theia/core/lib/browser';
32
+ import { CommandRegistry, CompoundMenuNode, Emitter, MenuAction, MenuModelRegistry } from '@theia/core';
33
+ import { TerminalManagerTreeModel } from './terminal-manager-tree-model';
34
+ import { ReactInteraction, TerminalManagerTreeTypes, TERMINAL_MANAGER_TREE_CONTEXT_MENU } from './terminal-manager-types';
35
+
36
+ /* eslint-disable no-param-reassign */
37
+ @injectable()
38
+ export class TerminalManagerTreeWidget extends TreeWidget {
39
+ static ID = 'terminal-manager-tree-widget';
40
+
41
+ protected onDidChangeEmitter = new Emitter();
42
+ readonly onDidChange = this.onDidChangeEmitter.event;
43
+
44
+ @inject(MenuModelRegistry) protected menuRegistry: MenuModelRegistry;
45
+ @inject(TreeModel) override readonly model: TerminalManagerTreeModel;
46
+ @inject(CommandRegistry) protected commandRegistry: CommandRegistry;
47
+
48
+ static createContainer(parent: interfaces.Container): Container {
49
+ const child = createTreeContainer(
50
+ parent,
51
+ {
52
+ props: {
53
+ leftPadding: 8,
54
+ contextMenuPath: TERMINAL_MANAGER_TREE_CONTEXT_MENU,
55
+ expandOnlyOnExpansionToggleClick: true,
56
+ },
57
+ },
58
+ );
59
+ child.bind(TerminalManagerTreeModel).toSelf().inSingletonScope();
60
+ child.rebind(TreeModel).to(TerminalManagerTreeModel);
61
+ child.bind(TerminalManagerTreeWidget).toSelf().inSingletonScope();
62
+ return child;
63
+ }
64
+
65
+ static createWidget(parent: interfaces.Container): TerminalManagerTreeWidget {
66
+ return TerminalManagerTreeWidget.createContainer(parent).get(TerminalManagerTreeWidget);
67
+ }
68
+
69
+ @postConstruct()
70
+ protected override init(): void {
71
+ super.init();
72
+ this.id = 'terminal-manager-tree-widget';
73
+ this.addClass(TerminalManagerTreeWidget.ID);
74
+ this.toDispose.push(this.onDidChangeEmitter);
75
+ }
76
+
77
+ protected override toContextMenuArgs(node: SelectableTreeNode): TerminalManagerTreeTypes.ContextMenuArgs | undefined {
78
+ if (
79
+ TerminalManagerTreeTypes.isPageNode(node)
80
+ || TerminalManagerTreeTypes.isTerminalNode(node)
81
+ || TerminalManagerTreeTypes.isGroupNode(node)
82
+ ) {
83
+ return TerminalManagerTreeTypes.toContextMenuArgs(this, node);
84
+ }
85
+ return undefined;
86
+ }
87
+
88
+ protected override renderCaption(node: TreeNode, props: NodeProps): React.ReactNode {
89
+ if (TerminalManagerTreeTypes.isTerminalManagerTreeNode(node) && !!node.isEditing) {
90
+ const label = this.toNodeName(node);
91
+ // eslint-disable-next-line @typescript-eslint/ban-types
92
+ const assignRef = (element: HTMLInputElement | null) => {
93
+ if (element) {
94
+ element.selectionStart = 0;
95
+ element.selectionEnd = label.length;
96
+ }
97
+ };
98
+ return (
99
+ <input
100
+ spellCheck={false}
101
+ type='text'
102
+ className='theia-input rename-node-input'
103
+ defaultValue={label}
104
+ onBlur={this.handleRenameOnBlur}
105
+ data-id={node.id}
106
+ onKeyDown={this.handleRenameOnKeyDown}
107
+ autoFocus={true}
108
+ ref={assignRef}
109
+ />
110
+ );
111
+ }
112
+ return super.renderCaption(node, props);
113
+ }
114
+
115
+ protected handleRenameOnBlur = (e: React.FocusEvent<HTMLInputElement>): void => this.doHandleRenameOnBlur(e);
116
+ protected doHandleRenameOnBlur(e: React.FocusEvent<HTMLInputElement>): void {
117
+ const { value } = e.currentTarget;
118
+ const id = e.currentTarget.getAttribute('data-id');
119
+ if (id) {
120
+ this.model.acceptRename(id, value);
121
+ }
122
+ }
123
+
124
+ protected override renderExpansionToggle(node: TreeNode, props: NodeProps): React.ReactNode {
125
+ return super.renderExpansionToggle(node, props);
126
+ }
127
+
128
+ protected handleRenameOnKeyDown = (e: React.KeyboardEvent<HTMLInputElement>): void => this.doHandleRenameOnKeyDown(e);
129
+ protected doHandleRenameOnKeyDown(e: React.KeyboardEvent<HTMLInputElement>): void {
130
+ const { value, defaultValue } = e.currentTarget;
131
+ const id = e.currentTarget.getAttribute('data-id');
132
+ e.stopPropagation();
133
+ if (e.key === 'Escape') {
134
+ e.preventDefault();
135
+ if (value && id) {
136
+ this.model.acceptRename(id, defaultValue);
137
+ }
138
+ } else if (e.key === 'Tab' || e.key === 'Enter') {
139
+ e.preventDefault();
140
+ if (value && id) {
141
+ this.model.acceptRename(id, value.trim());
142
+ }
143
+ }
144
+ }
145
+
146
+ // @ts-expect-error 2416 cf. https://github.com/eclipse-theia/theia/issues/11640
147
+ protected override handleLeft(event: KeyboardEvent): boolean | Promise<void> {
148
+ if ((event.target as HTMLElement).tagName === 'INPUT') { return false; };
149
+ return super.handleLeft(event);
150
+ }
151
+
152
+ // @ts-expect-error 2416 cf. https://github.com/eclipse-theia/theia/issues/11640
153
+ protected override handleRight(event: KeyboardEvent): boolean | Promise<void> {
154
+ if ((event.target as HTMLElement).tagName === 'INPUT') { return false; };
155
+ return super.handleRight(event);
156
+ }
157
+
158
+ // cf. https://github.com/eclipse-theia/theia/issues/11640
159
+ protected override handleEscape(event: KeyboardEvent): boolean | void {
160
+ if ((event.target as HTMLElement).tagName === 'INPUT') { return false; };
161
+ return super.handleEscape(event);
162
+ }
163
+
164
+ // cf. https://github.com/eclipse-theia/theia/issues/11640
165
+ protected override handleEnter(event: KeyboardEvent): boolean | void {
166
+ if ((event.target as HTMLElement).tagName === 'INPUT') { return false; };
167
+ return super.handleEnter(event);
168
+ }
169
+
170
+ // cf. https://github.com/eclipse-theia/theia/issues/11640
171
+ protected override handleSpace(event: KeyboardEvent): boolean | void {
172
+ if ((event.target as HTMLElement).tagName === 'INPUT') { return false; };
173
+ return super.handleSpace(event);
174
+ }
175
+
176
+ protected override renderTailDecorations(node: TreeNode, _props: NodeProps): React.ReactNode {
177
+ if (TerminalManagerTreeTypes.isTerminalManagerTreeNode(node)) {
178
+ const inlineActionsForNode = this.resolveInlineActionForNode(node);
179
+ return (
180
+ <div className='terminal-manager-inline-actions-container'>
181
+ <div className='terminal-manager-inline-actions'>
182
+ {inlineActionsForNode.map(({ icon, commandId, label }) => (
183
+ <span
184
+ key={commandId}
185
+ data-command-id={commandId}
186
+ data-node-id={node.id}
187
+ className={icon}
188
+ onClick={this.handleActionItemOnClick}
189
+ onKeyDown={this.handleActionItemOnClick}
190
+ role='button'
191
+ tabIndex={0}
192
+ title={label}
193
+ />
194
+ ))}
195
+ </div>
196
+ </div>
197
+ );
198
+ }
199
+ return undefined;
200
+ }
201
+
202
+ protected handleActionItemOnClick = (e: ReactInteraction<HTMLSpanElement>): void => this.doHandleActionItemOnClick(e);
203
+ protected doHandleActionItemOnClick(e: ReactInteraction<HTMLSpanElement>): void {
204
+ if ('key' in e && e.key !== Key.ENTER.code) {
205
+ return;
206
+ }
207
+ e.stopPropagation();
208
+ const commandId = e.currentTarget.getAttribute('data-command-id');
209
+ const nodeId = e.currentTarget.getAttribute('data-node-id');
210
+ if (commandId && nodeId) {
211
+ const node = this.model.getNode(nodeId);
212
+ if (TerminalManagerTreeTypes.isTerminalManagerTreeNode(node)) {
213
+ const args = TerminalManagerTreeTypes.toContextMenuArgs(this, node);
214
+ this.commandRegistry.executeCommand(commandId, ...args);
215
+ }
216
+ }
217
+ }
218
+
219
+ protected resolveInlineActionForNode(node: TerminalManagerTreeTypes.TerminalManagerTreeNode): MenuAction[] {
220
+ let menuNode: CompoundMenuNode | undefined = undefined;
221
+ const inlineActionProps: MenuAction[] = [];
222
+ if (TerminalManagerTreeTypes.isPageNode(node)) {
223
+ menuNode = this.menuRegistry.getMenu(TerminalManagerTreeTypes.PAGE_NODE_MENU);
224
+ } else if (TerminalManagerTreeTypes.isGroupNode(node)) {
225
+ menuNode = this.menuRegistry.getMenu(TerminalManagerTreeTypes.GROUP_NODE_MENU);
226
+ } else if (TerminalManagerTreeTypes.isTerminalNode(node)) {
227
+ menuNode = this.menuRegistry.getMenu(TerminalManagerTreeTypes.TERMINAL_NODE_MENU);
228
+ }
229
+ if (!menuNode) {
230
+ return [];
231
+ }
232
+ const menuItems = menuNode.children;
233
+ menuItems.forEach(item => {
234
+ const commandId = item.id;
235
+ const args = TerminalManagerTreeTypes.toContextMenuArgs(this, node);
236
+ const isVisible = this.commandRegistry.isVisible(commandId, ...args);
237
+ if (isVisible) {
238
+ const command = this.commandRegistry.getCommand(commandId);
239
+ const icon = command?.iconClass ? command.iconClass : '';
240
+ const label = command?.label ? command.label : '';
241
+ inlineActionProps.push({ icon, label, commandId });
242
+ }
243
+ });
244
+ return inlineActionProps;
245
+ }
246
+
247
+ protected override renderIcon(node: TreeNode, _props: NodeProps): React.ReactNode {
248
+ if (TerminalManagerTreeTypes.isTerminalNode(node)) {
249
+ return <span className={`${codicon('terminal')}`} />;
250
+ } else if (TerminalManagerTreeTypes.isPageNode(node)) {
251
+ return <span className={`${codicon('terminal-tmux')}`} />;
252
+ } else if (TerminalManagerTreeTypes.isGroupNode(node)) {
253
+ return <span className={`${codicon('split-horizontal')}`} />;
254
+ }
255
+ return undefined;
256
+ }
257
+
258
+ protected override toNodeName(node: TerminalManagerTreeTypes.TerminalManagerTreeNode): string {
259
+ return node.label ?? 'node.id';
260
+ }
261
+
262
+ protected override onUpdateRequest(msg: Message): void {
263
+ super.onUpdateRequest(msg);
264
+ this.onDidChangeEmitter.fire(undefined);
265
+ }
266
+
267
+ protected override renderIndent(node: TreeNode, props: NodeProps): React.ReactNode {
268
+ const renderIndentGuides = this.corePreferences['workbench.tree.renderIndentGuides'];
269
+ if (renderIndentGuides === 'none') {
270
+ return undefined;
271
+ }
272
+
273
+ const indentDivs: React.ReactNode[] = [];
274
+ let current: TreeNode | undefined = node;
275
+ let { depth } = props;
276
+ while (current && depth) {
277
+ const classNames: string[] = [TREE_NODE_INDENT_GUIDE_CLASS];
278
+ if (this.needsActiveIndentGuideline(current)) {
279
+ classNames.push('active');
280
+ } else {
281
+ classNames.push(renderIndentGuides === 'onHover' ? 'hover' : 'always');
282
+ }
283
+ const paddingLeft = this.props.leftPadding * depth;
284
+ indentDivs.unshift(<div
285
+ key={depth}
286
+ className={classNames.join(' ')}
287
+ style={{
288
+ paddingLeft: `${paddingLeft}px`,
289
+ }}
290
+ />);
291
+ current = current.parent;
292
+ depth -= 1;
293
+ }
294
+ return indentDivs;
295
+ }
296
+
297
+ protected override getDepthForNode(node: TreeNode, depths: Map<CompositeTreeNode | undefined, number>): number {
298
+ const parentDepth = depths.get(node.parent);
299
+ if (TerminalManagerTreeTypes.isTerminalNode(node) && parentDepth === undefined) {
300
+ return 1;
301
+ }
302
+ return super.getDepthForNode(node, depths);
303
+ }
304
+ }
305
+