Skip to content

Commit 1d7480c

Browse files
authored
Disable view actions while a viewer is already loading (#8268)
This PR disables view actions in the variables pane when a view request is already in-progress to avoid unintended duplicate requests. ### Release Notes #### New Features - N/A #### Bug Fixes - "View" actions are disabled in the variables pane while one is already in-progress (#4547). ### QA Notes Run `import time; time.sleep(10)` in a Python console and check: 1. If there is no viewer open for the variable, the following should be disabled after the first click: 1. The viewer button (e.g. table icon) 2. Right click -> "View" 3. Double-click to view 2. If there is a viewer open for the variable, any of the above view actions should focus the viewer pane. 3. You should be able to queue view actions for multiple variables. @:data-explorer @:variables
1 parent 1797942 commit 1d7480c

File tree

5 files changed

+90
-23
lines changed

5 files changed

+90
-23
lines changed

src/vs/workbench/contrib/positronVariables/browser/components/variableItem.css

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,12 @@
123123
background-color: transparent;
124124
}
125125

126-
.variable-item .details-column .right-column .viewer-icon:hover {
126+
.variable-item .details-column .right-column .viewer-icon.enabled:hover {
127127
border: 1px solid var(--vscode-button-foreground, #ffffff);
128-
background-color: var(--vscode-button-background, #0e639c);
128+
background-color: var(--vscode-button-background, #b7c7d1);
129+
}
130+
131+
.variable-item .details-column .right-column .viewer-icon.disabled {
132+
cursor: auto;
133+
opacity: 0.6;
129134
}

src/vs/workbench/contrib/positronVariables/browser/components/variableItem.tsx

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,28 @@ export const formatSize = (size: number) => {
6969
return localize('positron.sizeTB', "{0} TB", (size / TB).toFixed(2));
7070
};
7171

72+
const viewQueuedLabel = (variableItem: IVariableItem) => {
73+
switch (variableItem.kind) {
74+
case 'table':
75+
return localize('positron.variables.viewTableQueued', 'Data Table Queued...');
76+
case 'connection':
77+
return localize('positron.variables.viewConnectionQueued', 'Connection Queued...');
78+
default:
79+
return localize('positron.variables.viewQueued', 'View Queued...');
80+
}
81+
};
82+
83+
const viewLabel = (variableItem: IVariableItem) => {
84+
switch (variableItem.kind) {
85+
case 'table':
86+
return localize('positron.variables.viewTable', 'View Data Table');
87+
case 'connection':
88+
return localize('positron.variables.viewConnection', 'View Connection');
89+
default:
90+
return localize('positron.variables.view', 'View');
91+
}
92+
};
93+
7294
/**
7395
* VariableItemProps interface.
7496
*/
@@ -104,11 +126,17 @@ export const VariableItem = (props: VariableItemProps) => {
104126
* State hooks.
105127
*/
106128
const [isRecent, setIsRecent] = useState(props.variableItem.isRecent.get());
129+
const [isViewLoading, setIsViewLoading] = useState(props.variableItem.isViewLoading.get());
107130

108131
useEffect(() => {
109132
const disposableStore = new DisposableStore();
110-
const evt = Event.fromObservable(props.variableItem.isRecent, disposableStore);
111-
evt(e => setIsRecent(e));
133+
134+
const onDidChangeIsRecent = Event.fromObservable(props.variableItem.isRecent, disposableStore);
135+
disposableStore.add(onDidChangeIsRecent(e => setIsRecent(e)));
136+
137+
const onDidChangeIsViewLoading = Event.fromObservable(props.variableItem.isViewLoading, disposableStore);
138+
disposableStore.add(onDidChangeIsViewLoading(e => setIsViewLoading(e)));
139+
112140
return () => disposableStore.dispose();
113141
}, [props.variableItem]);
114142

@@ -274,10 +302,10 @@ export const VariableItem = (props: VariableItemProps) => {
274302
if (!props.disabled && props.variableItem.hasViewer) {
275303
actions.push({
276304
id: POSITRON_VARIABLES_VIEW,
277-
label: localize('positron.variables.view', "View"),
305+
label: viewLabel(props.variableItem),
278306
tooltip: '',
279307
class: undefined,
280-
enabled: true,
308+
enabled: !isViewLoading,
281309
run: () => viewVariableItem(props.variableItem)
282310
});
283311
}
@@ -391,19 +419,24 @@ export const VariableItem = (props: VariableItemProps) => {
391419
const RightColumn = () => {
392420
if (!props.disabled && props.variableItem.hasViewer) {
393421
let icon = 'codicon codicon-open-preview';
394-
if (props.variableItem.kind === 'table') {
422+
if (isViewLoading) {
423+
icon = 'codicon codicon-notebook-state-pending';
424+
} else if (props.variableItem.kind === 'table') {
395425
icon = 'codicon codicon-table';
396426
} else if (props.variableItem.kind === 'connection') {
397427
icon = 'codicon codicon-database';
398428
}
399-
icon = 'viewer-icon ' + icon + ' ' + props.variableItem.kind;
429+
const enablement = isViewLoading ? 'disabled' : 'enabled';
430+
icon = `viewer-icon ${enablement} ${icon} ${props.variableItem.kind}`;
400431

401432
return (
402433
<div className='right-column'>
403434
<div
404435
className={icon}
405-
title={localize('positron.variables.clickToView', "Click to view")}
406-
onMouseDown={viewerMouseDownHandler}
436+
title={isViewLoading ?
437+
viewQueuedLabel(props.variableItem) :
438+
viewLabel(props.variableItem)}
439+
onMouseDown={isViewLoading ? undefined : viewerMouseDownHandler}
407440
></div>
408441
</div>
409442
);
@@ -428,7 +461,7 @@ export const VariableItem = (props: VariableItemProps) => {
428461

429462
// Render.
430463
return (
431-
<div className={classNames} style={props.style} onDoubleClick={doubleClickHandler} onMouseDown={mouseDownHandler}>
464+
<div className={classNames} style={props.style} onDoubleClick={isViewLoading ? undefined : doubleClickHandler} onMouseDown={mouseDownHandler}>
432465
<div className='name-column' style={{ width: props.nameColumnWidth, minWidth: props.nameColumnWidth }}>
433466
<div className='name-column-indenter' style={{ marginLeft: props.variableItem.indentLevel * 20 }}>
434467
<div className='gutter'>

src/vs/workbench/services/languageRuntime/common/languageRuntimeVariablesClient.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { Emitter } from '../../../../base/common/event.js';
77
import { Disposable } from '../../../../base/common/lifecycle.js';
8-
import { ISettableObservable } from '../../../../base/common/observableInternal/base.js';
8+
import { ISettableObservable } from '../../../../base/common/observable.js';
99
import { IRuntimeClientInstance, RuntimeClientState, RuntimeClientStatus } from './languageRuntimeClientInstance.js';
1010
import { ClipboardFormatFormat, PositronVariablesComm, RefreshEvent, UpdateEvent, Variable } from './positronVariablesComm.js';
1111

@@ -14,6 +14,11 @@ import { ClipboardFormatFormat, PositronVariablesComm, RefreshEvent, UpdateEvent
1414
* and methods.
1515
*/
1616
export class PositronVariable {
17+
/**
18+
* The current pending view request.
19+
*/
20+
private _pendingViewPromise?: Promise<string | undefined>;
21+
1722
/**
1823
* Creates a new PositronVariable instance.
1924
*
@@ -79,8 +84,18 @@ export class PositronVariable {
7984
* @returns The ID of the viewer that was opened, if any.
8085
*/
8186
async view(): Promise<string | undefined> {
87+
if (this._pendingViewPromise) {
88+
// If a view is already pending, return the existing promise.
89+
return this._pendingViewPromise;
90+
}
91+
8292
const path = this.parentKeys.concat(this.data.access_key);
83-
return this._comm.view(path);
93+
try {
94+
this._pendingViewPromise = this._comm.view(path);
95+
return await this._pendingViewPromise;
96+
} finally {
97+
this._pendingViewPromise = undefined;
98+
}
8499
}
85100
}
86101

@@ -229,15 +244,6 @@ export class VariablesClientInstance extends Disposable {
229244
return formatted.content;
230245
}
231246

232-
/**
233-
* Requests that the variables client open a viewer for the specified variable.
234-
*
235-
* @param path The path to the variable to view
236-
*/
237-
public async requestView(path: string[]) {
238-
await this._comm.view(path);
239-
}
240-
241247
/**
242248
* Gets the underlying comm client.
243249
*/

src/vs/workbench/services/positronVariables/common/classes/variableItem.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ export class VariableItem implements IVariableItem {
4242
*/
4343
private readonly _isRecent: ISettableObservable<boolean>;
4444

45+
/**
46+
* Whether a viewer is currently loading for the variable item.
47+
*/
48+
private readonly _isViewLoading: ISettableObservable<boolean>;
49+
4550
//#endregion Private Properties
4651

4752
//#region Public Properties
@@ -181,6 +186,13 @@ export class VariableItem implements IVariableItem {
181186
return this._isRecent;
182187
}
183188

189+
/**
190+
* Whether a viewer is currently loading for the variable item.
191+
*/
192+
get isViewLoading() {
193+
return this._isViewLoading;
194+
}
195+
184196
/**
185197
* Get the raw data of the variable from the language runtime.
186198
*/
@@ -200,6 +212,7 @@ export class VariableItem implements IVariableItem {
200212
constructor(variable: PositronVariable, isRecent: boolean) {
201213
this._variable = variable;
202214
this._isRecent = observableValue(variable.data.access_key, isRecent);
215+
this._isViewLoading = observableValue(this, false);
203216

204217
// Clear recent flag after 2 seconds.
205218
setTimeout(() => this._isRecent.set(false, undefined), 2000);
@@ -325,7 +338,12 @@ export class VariableItem implements IVariableItem {
325338
* Requests that a viewer be opened for this variable.
326339
*/
327340
async view(): Promise<string | undefined> {
328-
return this._variable.view();
341+
this._isViewLoading.set(true, undefined);
342+
try {
343+
return await this._variable.view();
344+
} finally {
345+
this._isViewLoading.set(false, undefined);
346+
}
329347
}
330348

331349
//#endregion Public Methods

src/vs/workbench/services/positronVariables/common/interfaces/variableItem.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ export interface IVariableItem {
7575
*/
7676
readonly isRecent: IObservable<boolean>;
7777

78+
/**
79+
* Whether a viewer is currently loading for the variable item.
80+
*/
81+
readonly isViewLoading: IObservable<boolean>;
82+
7883
/**
7984
* Formats the value of this variable item in a format suitable for placing on the clipboard.
8085
* @param mime The desired MIME type of the format, such as 'text/plain' or 'text/html'.

0 commit comments

Comments
 (0)