Skip to content

Commit e8d46aa

Browse files
committed
Add refresh button to ports view to refresh candidate
Fix a bad leak in the ports view Fix the ip address of candidate ports
1 parent fccb25e commit e8d46aa

3 files changed

Lines changed: 138 additions & 74 deletions

File tree

src/vs/workbench/api/node/extHostTunnelService.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,14 +178,25 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
178178
const address = row.local_address.split(':');
179179
return {
180180
socket: parseInt(row.inode, 10),
181-
ip: address[0],
181+
ip: this.parseIpAddress(address[0]),
182182
port: parseInt(address[1], 16)
183183
};
184184
}).map(port => [port.port, port])
185185
).values()
186186
];
187187
}
188188

189+
private parseIpAddress(hex: string): string {
190+
let result = '';
191+
for (let i = hex.length - 2; (i >= 0); i -= 2) {
192+
result += parseInt(hex.substr(i, 2), 16);
193+
if (i !== 0) {
194+
result += '.';
195+
}
196+
}
197+
return result;
198+
}
199+
189200
private loadConnectionTable(stdout: string): Record<string, string>[] {
190201
const lines = stdout.trim().split('\n');
191202
const names = lines.shift()!.trim().split(/\s+/)

src/vs/workbench/contrib/remote/browser/tunnelView.ts

Lines changed: 80 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ import { Event, Emitter } from 'vs/base/common/event';
2020
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
2121
import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree';
2222
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
23-
import { Disposable, IDisposable, toDisposable, MutableDisposable, dispose } from 'vs/base/common/lifecycle';
23+
import { Disposable, IDisposable, toDisposable, MutableDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
2424
import { ActionBar, ActionViewItem, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
2525
import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
2626
import { ActionRunner, IAction } from 'vs/base/common/actions';
2727
import { IMenuService, MenuId, IMenu, MenuRegistry, MenuItemAction } from 'vs/platform/actions/common/actions';
2828
import { createAndFillInContextMenuActions, createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
29-
import { IRemoteExplorerService, TunnelModel, MakeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService';
29+
import { IRemoteExplorerService, TunnelModel, MakeAddress, TunnelType, ITunnelItem } from 'vs/workbench/services/remote/common/remoteExplorerService';
3030
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
3131
import { INotificationService } from 'vs/platform/notification/common/notification';
3232
import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
@@ -55,13 +55,15 @@ export interface ITunnelViewModel {
5555
readonly forwarded: TunnelItem[];
5656
readonly detected: TunnelItem[];
5757
readonly candidates: Promise<TunnelItem[]>;
58+
readonly input: ITunnelItem | ITunnelGroup | undefined;
5859
groups(): Promise<ITunnelGroup[]>;
5960
}
6061

6162
export class TunnelViewModel extends Disposable implements ITunnelViewModel {
6263
private _onForwardedPortsChanged: Emitter<void> = new Emitter();
6364
public onForwardedPortsChanged: Event<void> = this._onForwardedPortsChanged.event;
6465
private model: TunnelModel;
66+
private _input: ITunnelItem | ITunnelGroup | undefined;
6567

6668
constructor(
6769
@IRemoteExplorerService remoteExplorerService: IRemoteExplorerService) {
@@ -70,6 +72,7 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel {
7072
this._register(this.model.onForwardPort(() => this._onForwardedPortsChanged.fire()));
7173
this._register(this.model.onClosePort(() => this._onForwardedPortsChanged.fire()));
7274
this._register(this.model.onPortName(() => this._onForwardedPortsChanged.fire()));
75+
this._register(this.model.onCandidatesChanged(() => this._onForwardedPortsChanged.fire()));
7376
}
7477

7578
async groups(): Promise<ITunnelGroup[]> {
@@ -96,10 +99,13 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel {
9699
items: candidates
97100
});
98101
}
99-
groups.push({
100-
label: nls.localize('remote.tunnelsView.add', "Forward a Port..."),
101-
tunnelType: TunnelType.Add,
102-
});
102+
if (!this._input) {
103+
this._input = {
104+
label: nls.localize('remote.tunnelsView.add', "Forward a Port..."),
105+
tunnelType: TunnelType.Add,
106+
};
107+
}
108+
groups.push(this._input);
103109
return groups;
104110
}
105111

@@ -128,6 +134,10 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel {
128134
});
129135
}
130136

137+
get input(): ITunnelItem | ITunnelGroup | undefined {
138+
return this._input;
139+
}
140+
131141
dispose() {
132142
super.dispose();
133143
}
@@ -197,15 +207,15 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer<ITunnelGrou
197207
templateData.actionBar.clear();
198208
let editableData: IEditableData | undefined;
199209
if (this.isTunnelItem(node)) {
200-
editableData = this.remoteExplorerService.getEditableData(node.remoteHost, node.remotePort);
210+
editableData = this.remoteExplorerService.getEditableData(node);
201211
if (editableData) {
202212
templateData.iconLabel.element.style.display = 'none';
203213
this.renderInputBox(templateData.container, editableData);
204214
} else {
205215
templateData.iconLabel.element.style.display = 'flex';
206216
this.renderTunnel(node, templateData);
207217
}
208-
} else if ((node.tunnelType === TunnelType.Add) && (editableData = this.remoteExplorerService.getEditableData(undefined, undefined))) {
218+
} else if ((node.tunnelType === TunnelType.Add) && (editableData = this.remoteExplorerService.getEditableData(undefined))) {
209219
templateData.iconLabel.element.style.display = 'none';
210220
this.renderInputBox(templateData.container, editableData);
211221
} else {
@@ -217,14 +227,15 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer<ITunnelGrou
217227
private renderTunnel(node: ITunnelItem, templateData: ITunnelTemplateData) {
218228
templateData.iconLabel.setLabel(node.label, node.description, { title: node.label + ' - ' + node.description, extraClasses: ['tunnel-view-label'] });
219229
templateData.actionBar.context = node;
220-
const contextKeyService = this.contextKeyService.createScoped();
230+
const contextKeyService = this._register(this.contextKeyService.createScoped());
221231
contextKeyService.createKey('view', this.viewId);
222232
contextKeyService.createKey('tunnelType', node.tunnelType);
223233
contextKeyService.createKey('tunnelCloseable', node.closeable);
224-
const menu = this.menuService.createMenu(MenuId.TunnelInline, contextKeyService);
225-
this._register(menu);
234+
const disposableStore = new DisposableStore();
235+
templateData.elementDisposable = disposableStore;
236+
const menu = disposableStore.add(this.menuService.createMenu(MenuId.TunnelInline, contextKeyService));
226237
const actions: IAction[] = [];
227-
this._register(createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, actions));
238+
disposableStore.add(createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, actions));
228239
if (actions) {
229240
templateData.actionBar.push(actions, { icon: true, label: false });
230241
if (this._actionRunner) {
@@ -324,30 +335,12 @@ class TunnelDataSource implements IAsyncDataSource<ITunnelViewModel, ITunnelItem
324335
}
325336
}
326337

327-
enum TunnelType {
328-
Candidate = 'Candidate',
329-
Detected = 'Detected',
330-
Forwarded = 'Forwarded',
331-
Add = 'Add'
332-
}
333-
334338
interface ITunnelGroup {
335339
tunnelType: TunnelType;
336340
label: string;
337341
items?: ITunnelItem[] | Promise<ITunnelItem[]>;
338342
}
339343

340-
interface ITunnelItem {
341-
tunnelType: TunnelType;
342-
remoteHost: string;
343-
remotePort: number;
344-
localAddress?: string;
345-
name?: string;
346-
closeable?: boolean;
347-
readonly description?: string;
348-
readonly label: string;
349-
}
350-
351344
class TunnelItem implements ITunnelItem {
352345
constructor(
353346
public tunnelType: TunnelType,
@@ -472,12 +465,12 @@ export class TunnelPanel extends ViewPane {
472465

473466
this._register(Event.debounce(navigator.onDidOpenResource, (last, event) => event, 75, true)(e => {
474467
if (e.element && (e.element.tunnelType === TunnelType.Add)) {
475-
this.commandService.executeCommand(ForwardPortAction.ID, 'inline add');
468+
this.commandService.executeCommand(ForwardPortAction.INLINE_ID);
476469
}
477470
}));
478471

479472
this._register(this.remoteExplorerService.onDidChangeEditable(async e => {
480-
const isEditing = !!this.remoteExplorerService.getEditableData(e.host, e.port);
473+
const isEditing = !!this.remoteExplorerService.getEditableData(e);
481474

482475
if (!isEditing) {
483476
dom.removeClass(treeContainer, 'highlight');
@@ -487,15 +480,15 @@ export class TunnelPanel extends ViewPane {
487480

488481
if (isEditing) {
489482
dom.addClass(treeContainer, 'highlight');
483+
this.tree.reveal(e ? e : this.viewModel.input);
490484
} else {
491485
this.tree.domFocus();
492486
}
493487
}));
494488
}
495489

496490
private get contributedContextMenu(): IMenu {
497-
const contributedContextMenu = this.menuService.createMenu(MenuId.TunnelContext, this.tree.contextKeyService);
498-
this._register(contributedContextMenu);
491+
const contributedContextMenu = this._register(this.menuService.createMenu(MenuId.TunnelContext, this.tree.contextKeyService));
499492
return contributedContextMenu;
500493
}
501494

@@ -578,12 +571,12 @@ namespace LabelTunnelAction {
578571
return async (accessor, arg) => {
579572
if (arg instanceof TunnelItem) {
580573
const remoteExplorerService = accessor.get(IRemoteExplorerService);
581-
remoteExplorerService.setEditable(arg.remoteHost, arg.remotePort, {
574+
remoteExplorerService.setEditable(arg, {
582575
onFinish: (value, success) => {
583576
if (success) {
584577
remoteExplorerService.tunnelModel.name(arg.remoteHost, arg.remotePort, value);
585578
}
586-
remoteExplorerService.setEditable(arg.remoteHost, arg.remotePort, null);
579+
remoteExplorerService.setEditable(arg, null);
587580
},
588581
validationMessage: () => null,
589582
placeholder: nls.localize('remote.tunnelsView.labelPlaceholder', "Port label"),
@@ -596,7 +589,8 @@ namespace LabelTunnelAction {
596589
}
597590

598591
namespace ForwardPortAction {
599-
export const ID = 'remote.tunnel.forward';
592+
export const INLINE_ID = 'remote.tunnel.forwardInline';
593+
export const COMMANDPALETTE_ID = 'remote.tunnel.forwardCommandPalette';
600594
export const LABEL = nls.localize('remote.tunnel.forward', "Forward a Port");
601595
const forwardPrompt = nls.localize('remote.tunnel.forwardPrompt', "Port number or address (eg. 3000 or 10.10.10.10:2000).");
602596

@@ -615,35 +609,40 @@ namespace ForwardPortAction {
615609
return null;
616610
}
617611

618-
export function handler(): ICommandHandler {
612+
export function inlineHandler(): ICommandHandler {
619613
return async (accessor, arg) => {
620614
const remoteExplorerService = accessor.get(IRemoteExplorerService);
621615
if (arg instanceof TunnelItem) {
622616
remoteExplorerService.forward({ host: arg.remoteHost, port: arg.remotePort });
623-
} else if (arg) {
624-
remoteExplorerService.setEditable(undefined, undefined, {
617+
} else {
618+
remoteExplorerService.setEditable(undefined, {
625619
onFinish: (value, success) => {
626620
let parsed: { host: string, port: number } | undefined;
627621
if (success && (parsed = parseInput(value))) {
628622
remoteExplorerService.forward({ host: parsed.host, port: parsed.port });
629623
}
630-
remoteExplorerService.setEditable(undefined, undefined, null);
624+
remoteExplorerService.setEditable(undefined, null);
631625
},
632626
validationMessage: validateInput,
633627
placeholder: forwardPrompt
634628
});
635-
} else {
636-
const viewsService = accessor.get(IViewsService);
637-
const quickInputService = accessor.get(IQuickInputService);
638-
await viewsService.openView(TunnelPanel.ID, true);
639-
const value = await quickInputService.input({
640-
prompt: forwardPrompt,
641-
validateInput: (value) => Promise.resolve(validateInput(value))
642-
});
643-
let parsed: { host: string, port: number } | undefined;
644-
if (value && (parsed = parseInput(value))) {
645-
remoteExplorerService.forward({ host: parsed.host, port: parsed.port });
646-
}
629+
}
630+
};
631+
}
632+
633+
export function commandPaletteHandler(): ICommandHandler {
634+
return async (accessor, arg) => {
635+
const remoteExplorerService = accessor.get(IRemoteExplorerService);
636+
const viewsService = accessor.get(IViewsService);
637+
const quickInputService = accessor.get(IQuickInputService);
638+
await viewsService.openView(TunnelPanel.ID, true);
639+
const value = await quickInputService.input({
640+
prompt: forwardPrompt,
641+
validateInput: (value) => Promise.resolve(validateInput(value))
642+
});
643+
let parsed: { host: string, port: number } | undefined;
644+
if (value && (parsed = parseInput(value))) {
645+
remoteExplorerService.forward({ host: parsed.host, port: parsed.port });
647646
}
648647
};
649648
}
@@ -702,30 +701,51 @@ namespace CopyAddressAction {
702701
}
703702
}
704703

704+
namespace RefreshTunnelViewAction {
705+
export const ID = 'remote.tunnel.refresh';
706+
export const LABEL = nls.localize('remote.tunnel.refreshView', "Refresh");
707+
708+
export function handler(): ICommandHandler {
709+
return (accessor, arg) => {
710+
const remoteExplorerService = accessor.get(IRemoteExplorerService);
711+
return remoteExplorerService.refresh();
712+
};
713+
}
714+
}
715+
705716
CommandsRegistry.registerCommand(LabelTunnelAction.ID, LabelTunnelAction.handler());
706-
CommandsRegistry.registerCommand(ForwardPortAction.ID, ForwardPortAction.handler());
717+
CommandsRegistry.registerCommand(ForwardPortAction.INLINE_ID, ForwardPortAction.inlineHandler());
718+
CommandsRegistry.registerCommand(ForwardPortAction.COMMANDPALETTE_ID, ForwardPortAction.commandPaletteHandler());
707719
CommandsRegistry.registerCommand(ClosePortAction.ID, ClosePortAction.handler());
708720
CommandsRegistry.registerCommand(OpenPortInBrowserAction.ID, OpenPortInBrowserAction.handler());
709721
CommandsRegistry.registerCommand(CopyAddressAction.ID, CopyAddressAction.handler());
722+
CommandsRegistry.registerCommand(RefreshTunnelViewAction.ID, RefreshTunnelViewAction.handler());
710723

711724
MenuRegistry.appendMenuItem(MenuId.CommandPalette, ({
712725
command: {
713-
id: ForwardPortAction.ID,
726+
id: ForwardPortAction.COMMANDPALETTE_ID,
714727
title: ForwardPortAction.LABEL
715728
},
716729
when: forwardedPortsViewEnabled
717730
}));
718-
719-
720731
MenuRegistry.appendMenuItem(MenuId.TunnelTitle, ({
721732
group: 'navigation',
722733
order: 0,
723734
command: {
724-
id: ForwardPortAction.ID,
735+
id: ForwardPortAction.COMMANDPALETTE_ID,
725736
title: ForwardPortAction.LABEL,
726737
icon: { id: 'codicon/plus' }
727738
}
728739
}));
740+
MenuRegistry.appendMenuItem(MenuId.TunnelTitle, ({
741+
group: 'navigation',
742+
order: 1,
743+
command: {
744+
id: RefreshTunnelViewAction.ID,
745+
title: RefreshTunnelViewAction.LABEL,
746+
icon: { id: 'codicon/refresh' }
747+
}
748+
}));
729749
MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({
730750
group: '0_manage',
731751
order: 0,
@@ -757,7 +777,7 @@ MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({
757777
group: '0_manage',
758778
order: 1,
759779
command: {
760-
id: ForwardPortAction.ID,
780+
id: ForwardPortAction.INLINE_ID,
761781
title: ForwardPortAction.LABEL,
762782
},
763783
when: TunnelTypeContextKey.isEqualTo(TunnelType.Candidate)
@@ -784,7 +804,7 @@ MenuRegistry.appendMenuItem(MenuId.TunnelInline, ({
784804
MenuRegistry.appendMenuItem(MenuId.TunnelInline, ({
785805
order: 0,
786806
command: {
787-
id: ForwardPortAction.ID,
807+
id: ForwardPortAction.INLINE_ID,
788808
title: ForwardPortAction.LABEL,
789809
icon: { id: 'codicon/plus' }
790810
},

0 commit comments

Comments
 (0)