Skip to content

Commit dff6e64

Browse files
Kartik Rajwesm
Kartik Raj
authored andcommitted
Add experiments to move interpreter display status bar item to the right (microsoft/vscode-python#18152)
* Move interpreter display status bar item to the right * Simulate pinning version in status bar * Add pinning behind experiment * Fix tests * Add tests * Add unpinned language status item experiment * Add tests * Add news entry
1 parent 552366f commit dff6e64

File tree

12 files changed

+505
-260
lines changed

12 files changed

+505
-260
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Move pinned interpreter status bar item towards the right behind `pythonInterpreterInfoPinned` experiment.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Move interpreter status bar item into the `Python` language status item behind `pythonInterpreterInfoUnpinned` experiment.

extensions/positron-python/src/client/common/application/applicationShell.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ import {
77
CancellationToken,
88
CancellationTokenSource,
99
Disposable,
10+
DocumentSelector,
1011
env,
1112
Event,
1213
InputBox,
1314
InputBoxOptions,
15+
languages,
16+
LanguageStatusItem,
1417
MessageItem,
1518
MessageOptions,
1619
OpenDialogOptions,
@@ -149,4 +152,7 @@ export class ApplicationShell implements IApplicationShell {
149152
public createOutputChannel(name: string): OutputChannel {
150153
return window.createOutputChannel(name);
151154
}
155+
public createLanguageStatusItem(id: string, selector: DocumentSelector): LanguageStatusItem {
156+
return languages.createLanguageStatusItem(id, selector);
157+
}
152158
}

extensions/positron-python/src/client/common/application/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
GlobPattern,
2525
InputBox,
2626
InputBoxOptions,
27+
LanguageStatusItem,
2728
MessageItem,
2829
MessageOptions,
2930
OpenDialogOptions,
@@ -416,6 +417,7 @@ export interface IApplicationShell {
416417
* @param name Human-readable string which will be used to represent the channel in the UI.
417418
*/
418419
createOutputChannel(name: string): OutputChannel;
420+
createLanguageStatusItem(id: string, selector: DocumentSelector): LanguageStatusItem;
419421
}
420422

421423
export const ICommandManager = Symbol('ICommandManager');

extensions/positron-python/src/client/common/experiments/groups.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@
22
export enum ShowExtensionSurveyPrompt {
33
experiment = 'pythonSurveyNotification',
44
}
5+
export enum InterpreterStatusBarPosition {
6+
Pinned = 'pythonInterpreterInfoPinned',
7+
Unpinned = 'pythonInterpreterInfoUnpinned',
8+
}

extensions/positron-python/src/client/interpreter/display/index.ts

Lines changed: 91 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import { inject, injectable } from 'inversify';
2-
import { Disposable, StatusBarAlignment, StatusBarItem, Uri } from 'vscode';
2+
import { Disposable, LanguageStatusItem, LanguageStatusSeverity, StatusBarAlignment, StatusBarItem, Uri } from 'vscode';
3+
import { IExtensionSingleActivationService } from '../../activation/types';
34
import { IApplicationShell, IWorkspaceService } from '../../common/application/types';
5+
import { Commands, PYTHON_LANGUAGE } from '../../common/constants';
6+
import { InterpreterStatusBarPosition } from '../../common/experiments/groups';
47
import '../../common/extensions';
5-
import { IDisposableRegistry, IPathUtils, Resource } from '../../common/types';
6-
import { Interpreters } from '../../common/utils/localize';
8+
import { IDisposableRegistry, IExperimentService, IPathUtils, Resource } from '../../common/types';
9+
import { InterpreterQuickPickList, Interpreters } from '../../common/utils/localize';
710
import { IServiceContainer } from '../../ioc/types';
811
import { traceLog } from '../../logging';
912
import { PythonEnvironment } from '../../pythonEnvironments/info';
@@ -14,9 +17,19 @@ import {
1417
IInterpreterStatusbarVisibilityFilter,
1518
} from '../contracts';
1619

20+
/**
21+
* Based on https://github.com/microsoft/vscode-python/issues/18040#issuecomment-992567670.
22+
* This is to ensure the item appears right after the Python language status item.
23+
*/
24+
const STATUS_BAR_ITEM_PRIORITY = 100.09999;
1725
@injectable()
18-
export class InterpreterDisplay implements IInterpreterDisplay {
19-
private readonly statusBar: StatusBarItem;
26+
export class InterpreterDisplay implements IInterpreterDisplay, IExtensionSingleActivationService {
27+
public supportedWorkspaceTypes: { untrustedWorkspace: boolean; virtualWorkspace: boolean } = {
28+
untrustedWorkspace: false,
29+
virtualWorkspace: true,
30+
};
31+
private statusBar: StatusBarItem | undefined;
32+
private languageStatus: LanguageStatusItem | undefined;
2033
private readonly helper: IInterpreterHelper;
2134
private readonly workspaceService: IWorkspaceService;
2235
private readonly pathUtils: IPathUtils;
@@ -26,26 +39,48 @@ export class InterpreterDisplay implements IInterpreterDisplay {
2639
private interpreterPath: string | undefined;
2740
private statusBarCanBeDisplayed?: boolean;
2841
private visibilityFilters: IInterpreterStatusbarVisibilityFilter[] = [];
42+
private disposableRegistry: Disposable[];
43+
private experiments: IExperimentService;
2944

3045
constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) {
3146
this.helper = serviceContainer.get<IInterpreterHelper>(IInterpreterHelper);
3247
this.workspaceService = serviceContainer.get<IWorkspaceService>(IWorkspaceService);
3348
this.pathUtils = serviceContainer.get<IPathUtils>(IPathUtils);
3449
this.interpreterService = serviceContainer.get<IInterpreterService>(IInterpreterService);
3550

36-
const application = serviceContainer.get<IApplicationShell>(IApplicationShell);
37-
const disposableRegistry = serviceContainer.get<Disposable[]>(IDisposableRegistry);
38-
39-
this.statusBar = application.createStatusBarItem(StatusBarAlignment.Left, 100);
40-
this.statusBar.command = 'python.setInterpreter';
41-
disposableRegistry.push(this.statusBar);
51+
this.disposableRegistry = serviceContainer.get<Disposable[]>(IDisposableRegistry);
4252

4353
this.interpreterService.onDidChangeInterpreterInformation(
4454
this.onDidChangeInterpreterInformation,
4555
this,
46-
disposableRegistry,
56+
this.disposableRegistry,
4757
);
58+
this.experiments = this.serviceContainer.get<IExperimentService>(IExperimentService);
4859
}
60+
61+
public async activate(): Promise<void> {
62+
const application = this.serviceContainer.get<IApplicationShell>(IApplicationShell);
63+
if (this.experiments.inExperimentSync(InterpreterStatusBarPosition.Unpinned)) {
64+
this.languageStatus = application.createLanguageStatusItem('python.selectedInterpreter', {
65+
language: PYTHON_LANGUAGE,
66+
});
67+
this.languageStatus.severity = LanguageStatusSeverity.Information;
68+
this.languageStatus.command = {
69+
title: InterpreterQuickPickList.browsePath.openButtonLabel(),
70+
command: Commands.Set_Interpreter,
71+
};
72+
this.disposableRegistry.push(this.languageStatus);
73+
} else {
74+
let [alignment, priority] = [StatusBarAlignment.Left, 100];
75+
if (this.experiments.inExperimentSync(InterpreterStatusBarPosition.Pinned)) {
76+
[alignment, priority] = [StatusBarAlignment.Right, STATUS_BAR_ITEM_PRIORITY];
77+
}
78+
this.statusBar = application.createStatusBarItem(alignment, priority);
79+
this.statusBar.command = Commands.Set_Interpreter;
80+
this.disposableRegistry.push(this.statusBar);
81+
}
82+
}
83+
4984
public async refresh(resource?: Uri) {
5085
// Use the workspace Uri if available
5186
if (resource && this.workspaceService.getWorkspaceFolder(resource)) {
@@ -72,30 +107,56 @@ export class InterpreterDisplay implements IInterpreterDisplay {
72107
private async updateDisplay(workspaceFolder?: Uri) {
73108
const interpreter = await this.interpreterService.getActiveInterpreter(workspaceFolder);
74109
this.currentlySelectedWorkspaceFolder = workspaceFolder;
75-
if (interpreter) {
76-
this.statusBar.color = '';
77-
this.statusBar.tooltip = this.pathUtils.getDisplayName(interpreter.path, workspaceFolder?.fsPath);
78-
if (this.interpreterPath !== interpreter.path) {
79-
traceLog(
80-
Interpreters.pythonInterpreterPath().format(
81-
this.pathUtils.getDisplayName(interpreter.path, workspaceFolder?.fsPath),
82-
),
83-
);
84-
this.interpreterPath = interpreter.path;
110+
if (this.statusBar) {
111+
if (interpreter) {
112+
this.statusBar.color = '';
113+
this.statusBar.tooltip = this.pathUtils.getDisplayName(interpreter.path, workspaceFolder?.fsPath);
114+
if (this.interpreterPath !== interpreter.path) {
115+
traceLog(
116+
Interpreters.pythonInterpreterPath().format(
117+
this.pathUtils.getDisplayName(interpreter.path, workspaceFolder?.fsPath),
118+
),
119+
);
120+
this.interpreterPath = interpreter.path;
121+
}
122+
let text = interpreter.displayName!;
123+
if (this.experiments.inExperimentSync(InterpreterStatusBarPosition.Pinned)) {
124+
text = text.startsWith('Python') ? text.substring('Python'.length).trim() : text;
125+
}
126+
this.statusBar.text = text;
127+
this.currentlySelectedInterpreterPath = interpreter.path;
128+
} else {
129+
this.statusBar.tooltip = '';
130+
this.statusBar.color = '';
131+
this.statusBar.text = '$(alert) Select Python Interpreter';
132+
this.currentlySelectedInterpreterPath = undefined;
133+
}
134+
} else if (this.languageStatus) {
135+
if (interpreter) {
136+
this.languageStatus.detail = this.pathUtils.getDisplayName(interpreter.path, workspaceFolder?.fsPath);
137+
if (this.interpreterPath !== interpreter.path) {
138+
traceLog(
139+
Interpreters.pythonInterpreterPath().format(
140+
this.pathUtils.getDisplayName(interpreter.path, workspaceFolder?.fsPath),
141+
),
142+
);
143+
this.interpreterPath = interpreter.path;
144+
}
145+
let text = interpreter.displayName!;
146+
text = text.startsWith('Python') ? text.substring('Python'.length).trim() : text;
147+
this.languageStatus.text = text;
148+
this.currentlySelectedInterpreterPath = interpreter.path;
149+
} else {
150+
this.languageStatus.text = '$(alert) No Interpreter Selected';
151+
this.languageStatus.detail = undefined;
152+
this.currentlySelectedInterpreterPath = undefined;
85153
}
86-
this.statusBar.text = interpreter.displayName!;
87-
this.currentlySelectedInterpreterPath = interpreter.path;
88-
} else {
89-
this.statusBar.tooltip = '';
90-
this.statusBar.color = '';
91-
this.statusBar.text = '$(alert) Select Python Interpreter';
92-
this.currentlySelectedInterpreterPath = undefined;
93154
}
94155
this.statusBarCanBeDisplayed = true;
95156
this.updateVisibility();
96157
}
97158
private updateVisibility() {
98-
if (!this.statusBarCanBeDisplayed) {
159+
if (!this.statusBar || !this.statusBarCanBeDisplayed) {
99160
return;
100161
}
101162
if (this.visibilityFilters.length === 0 || this.visibilityFilters.every((filter) => !filter.hidden)) {

extensions/positron-python/src/client/interpreter/interpreterService.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
1+
// eslint-disable-next-line max-classes-per-file
12
import { inject, injectable } from 'inversify';
23
import { Disposable, Event, EventEmitter, Uri } from 'vscode';
34
import '../common/extensions';
45
import { IDocumentManager } from '../common/application/types';
56
import { IPythonExecutionFactory } from '../common/process/types';
6-
import { IConfigurationService, IDisposableRegistry, IInterpreterPathService } from '../common/types';
7+
import {
8+
IConfigurationService,
9+
IDisposableRegistry,
10+
IExperimentService,
11+
IInterpreterPathService,
12+
} from '../common/types';
713
import { IServiceContainer } from '../ioc/types';
814
import { PythonEnvironment } from '../pythonEnvironments/info';
915
import {
1016
IComponentAdapter,
1117
IInterpreterDisplay,
1218
IInterpreterService,
19+
IInterpreterStatusbarVisibilityFilter,
1320
PythonEnvironmentsChangedEvent,
1421
} from './contracts';
1522
import { PythonLocatorQuery } from '../pythonEnvironments/base/locator';
1623
import { traceError } from '../logging';
24+
import { PYTHON_LANGUAGE } from '../common/constants';
25+
import { InterpreterStatusBarPosition } from '../common/experiments/groups';
1726

1827
type StoredPythonEnvironment = PythonEnvironment & { store?: boolean };
1928

@@ -80,6 +89,22 @@ export class InterpreterService implements Disposable, IInterpreterService {
8089
public initialize(): void {
8190
const disposables = this.serviceContainer.get<Disposable[]>(IDisposableRegistry);
8291
const documentManager = this.serviceContainer.get<IDocumentManager>(IDocumentManager);
92+
const interpreterDisplay = this.serviceContainer.get<IInterpreterDisplay>(IInterpreterDisplay);
93+
const filter = new (class implements IInterpreterStatusbarVisibilityFilter {
94+
constructor(private readonly docManager: IDocumentManager) {}
95+
96+
public readonly interpreterVisibilityEmitter = new EventEmitter<void>();
97+
98+
public readonly changed = this.interpreterVisibilityEmitter.event;
99+
100+
get hidden() {
101+
return this.docManager.activeTextEditor?.document.languageId !== PYTHON_LANGUAGE;
102+
}
103+
})(documentManager);
104+
const experiments = this.serviceContainer.get<IExperimentService>(IExperimentService);
105+
if (experiments.inExperimentSync(InterpreterStatusBarPosition.Pinned)) {
106+
interpreterDisplay.registerVisibilityFilter(filter);
107+
}
83108
disposables.push(
84109
this.onDidChangeInterpreters((e) => {
85110
const interpreter = e.old ?? e.new;
@@ -89,9 +114,16 @@ export class InterpreterService implements Disposable, IInterpreterService {
89114
}),
90115
);
91116
disposables.push(
92-
documentManager.onDidChangeActiveTextEditor((e) =>
93-
e && e.document ? this.refresh(e.document.uri) : undefined,
94-
),
117+
documentManager.onDidOpenTextDocument(() => {
118+
// To handle scenario when language mode is set to "python"
119+
filter.interpreterVisibilityEmitter.fire();
120+
}),
121+
documentManager.onDidChangeActiveTextEditor((e) => {
122+
filter.interpreterVisibilityEmitter.fire();
123+
if (e && e.document) {
124+
this.refresh(e.document.uri);
125+
}
126+
}),
95127
);
96128
const pySettings = this.configService.getSettings();
97129
this._pythonPathSetting = pySettings.pythonPath;

extensions/positron-python/src/client/interpreter/serviceRegistry.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export function registerInterpreterTypes(serviceManager: IServiceManager): void
5757

5858
serviceManager.addSingleton<IInterpreterService>(IInterpreterService, InterpreterService);
5959
serviceManager.addSingleton<IInterpreterDisplay>(IInterpreterDisplay, InterpreterDisplay);
60+
serviceManager.addBinding(IInterpreterDisplay, IExtensionSingleActivationService);
6061

6162
serviceManager.addSingleton<IPythonPathUpdaterServiceFactory>(
6263
IPythonPathUpdaterServiceFactory,

0 commit comments

Comments
 (0)