Skip to content

Commit 7894ae6

Browse files
author
Mikhail Arkhipov
authored
Improve virtual env detection + #807 (#831)
* Fix pylint search * Handle quote escapes in strings * Escapes in strings * CR feedback * Missing pip * Test * Tests * Tests * Mac python path * Tests * Tests * Test * "Go To Python object" doesn't work * Proper detection and type population in virtual env * Test fixes * Simplify venv check * Remove duplicates * Test * Discover pylintrc better + tests * Undo change * CR feedback * Set interprereter before checking install * Fix typo and path compare on Windows * Rename method * #815 - 'F' in flake8 means warning
1 parent 6eabde4 commit 7894ae6

19 files changed

Lines changed: 212 additions & 143 deletions

File tree

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,7 +1297,7 @@
12971297
},
12981298
"python.linting.flake8CategorySeverity.F": {
12991299
"type": "string",
1300-
"default": "Error",
1300+
"default": "Warning",
13011301
"description": "Severity of Flake8 message type 'F'.",
13021302
"enum": [
13031303
"Hint",
@@ -1859,4 +1859,4 @@
18591859
"publisherDisplayName": "Microsoft",
18601860
"publisherId": "998b010b-e2af-44a5-a6cd-0b5fd3b9b6f8"
18611861
}
1862-
}
1862+
}

src/client/common/configSettings.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,12 @@ export class PythonSettings extends EventEmitter implements IPythonSettings {
168168
W: vscode.DiagnosticSeverity.Warning
169169
},
170170
flake8CategorySeverity: {
171-
F: vscode.DiagnosticSeverity.Error,
172171
E: vscode.DiagnosticSeverity.Error,
173-
W: vscode.DiagnosticSeverity.Warning
172+
W: vscode.DiagnosticSeverity.Warning,
173+
// Per http://flake8.pycqa.org/en/latest/glossary.html#term-error-code
174+
// 'F' does not mean 'fatal as in PyLint but rather 'pyflakes' such as
175+
// unused imports, variables, etc.
176+
F: vscode.DiagnosticSeverity.Warning
174177
},
175178
mypyCategorySeverity: {
176179
error: vscode.DiagnosticSeverity.Error,

src/client/common/platform/fileSystem.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,11 @@ export class FileSystem implements IFileSystem {
8686
return fs.appendFileSync(filename, data, optionsOrEncoding);
8787
}
8888

89+
public getRealPathAsync(filePath: string): Promise<string> {
90+
return new Promise<string>(resolve => {
91+
fs.realpath(filePath, (err, realPath) => {
92+
resolve(err ? filePath : realPath);
93+
});
94+
});
95+
}
8996
}

src/client/common/platform/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,5 @@ export interface IFileSystem {
4141
appendFileSync(filename: string, data: {}, options?: { encoding?: string; mode?: number; flag?: string; }): void;
4242
// tslint:disable-next-line:unified-signatures
4343
appendFileSync(filename: string, data: {}, options?: { encoding?: string; mode?: string; flag?: string; }): void;
44+
getRealPathAsync(path: string): Promise<string>;
4445
}

src/client/interpreter/configuration/interpreterSelector.ts

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,47 @@ import { ConfigurationTarget, Disposable, QuickPickItem, QuickPickOptions, Uri }
44
import { IApplicationShell, ICommandManager, IDocumentManager, IWorkspaceService } from '../../common/application/types';
55
import * as settings from '../../common/configSettings';
66
import { Commands } from '../../common/constants';
7+
import { IFileSystem } from '../../common/platform/types';
78
import { IServiceContainer } from '../../ioc/types';
89
import { IInterpreterService, IShebangCodeLensProvider, PythonInterpreter, WorkspacePythonPath } from '../contracts';
910
import { IInterpreterSelector, IPythonPathUpdaterServiceManager } from './types';
1011

11-
interface IInterpreterQuickPickItem extends QuickPickItem {
12+
export interface IInterpreterQuickPickItem extends QuickPickItem {
1213
path: string;
1314
}
1415

1516
@injectable()
1617
export class InterpreterSelector implements IInterpreterSelector {
1718
private disposables: Disposable[] = [];
18-
private pythonPathUpdaterService: IPythonPathUpdaterServiceManager;
1919
private readonly interpreterManager: IInterpreterService;
2020
private readonly workspaceService: IWorkspaceService;
2121
private readonly applicationShell: IApplicationShell;
2222
private readonly documentManager: IDocumentManager;
23+
private readonly fileSystem: IFileSystem;
24+
2325
constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {
2426
this.interpreterManager = serviceContainer.get<IInterpreterService>(IInterpreterService);
2527
this.workspaceService = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
2628
this.applicationShell = this.serviceContainer.get<IApplicationShell>(IApplicationShell);
2729
this.documentManager = this.serviceContainer.get<IDocumentManager>(IDocumentManager);
30+
this.fileSystem = this.serviceContainer.get<IFileSystem>(IFileSystem);
2831

2932
const commandManager = serviceContainer.get<ICommandManager>(ICommandManager);
3033
this.disposables.push(commandManager.registerCommand(Commands.Set_Interpreter, this.setInterpreter.bind(this)));
3134
this.disposables.push(commandManager.registerCommand(Commands.Set_ShebangInterpreter, this.setShebangInterpreter.bind(this)));
32-
this.pythonPathUpdaterService = serviceContainer.get<IPythonPathUpdaterServiceManager>(IPythonPathUpdaterServiceManager);
3335
}
3436
public dispose() {
3537
this.disposables.forEach(disposable => disposable.dispose());
3638
}
39+
40+
public async getSuggestions(resourceUri?: Uri) {
41+
let interpreters = await this.interpreterManager.getInterpreters(resourceUri);
42+
interpreters = await this.removeDuplicates(interpreters);
43+
// tslint:disable-next-line:no-non-null-assertion
44+
interpreters.sort((a, b) => a.displayName! > b.displayName! ? 1 : -1);
45+
return Promise.all(interpreters.map(item => this.suggestionToQuickPickItem(item, resourceUri)));
46+
}
47+
3748
private async getWorkspaceToSetPythonPath(): Promise<WorkspacePythonPath | undefined> {
3849
if (!Array.isArray(this.workspaceService.workspaceFolders) || this.workspaceService.workspaceFolders.length === 0) {
3950
return undefined;
@@ -47,6 +58,7 @@ export class InterpreterSelector implements IInterpreterSelector {
4758
const workspaceFolder = await applicationShell.showWorkspaceFolderPick({ placeHolder: 'Select a workspace' });
4859
return workspaceFolder ? { folderUri: workspaceFolder.uri, configTarget: ConfigurationTarget.WorkspaceFolder } : undefined;
4960
}
61+
5062
private async suggestionToQuickPickItem(suggestion: PythonInterpreter, workspaceUri?: Uri): Promise<IInterpreterQuickPickItem> {
5163
let detail = suggestion.path;
5264
if (workspaceUri && suggestion.path.startsWith(workspaceUri.fsPath)) {
@@ -62,11 +74,19 @@ export class InterpreterSelector implements IInterpreterSelector {
6274
};
6375
}
6476

65-
private async getSuggestions(resourceUri?: Uri) {
66-
const interpreters = await this.interpreterManager.getInterpreters(resourceUri);
67-
// tslint:disable-next-line:no-non-null-assertion
68-
interpreters.sort((a, b) => a.displayName! > b.displayName! ? 1 : -1);
69-
return Promise.all(interpreters.map(item => this.suggestionToQuickPickItem(item, resourceUri)));
77+
private async removeDuplicates(interpreters: PythonInterpreter[]): Promise<PythonInterpreter[]> {
78+
const result: PythonInterpreter[] = [];
79+
await Promise.all(interpreters.filter(async x => {
80+
x.realPath = await this.fileSystem.getRealPathAsync(x.path);
81+
return true;
82+
}));
83+
interpreters.forEach(x => {
84+
if (result.findIndex(a => a.displayName === x.displayName
85+
&& a.type === x.type && this.fileSystem.arePathsSame(a.realPath!, x.realPath!)) < 0) {
86+
result.push(x);
87+
}
88+
});
89+
return result;
7090
}
7191

7292
private async setInterpreter() {
@@ -95,7 +115,8 @@ export class InterpreterSelector implements IInterpreterSelector {
95115

96116
const selection = await this.applicationShell.showQuickPick(suggestions, quickPickOptions);
97117
if (selection !== undefined) {
98-
await this.pythonPathUpdaterService.updatePythonPath(selection.path, configTarget, 'ui', wkspace);
118+
const pythonPathUpdaterService = this.serviceContainer.get<IPythonPathUpdaterServiceManager>(IPythonPathUpdaterServiceManager);
119+
await pythonPathUpdaterService.updatePythonPath(selection.path, configTarget, 'ui', wkspace);
99120
}
100121
}
101122

@@ -110,16 +131,17 @@ export class InterpreterSelector implements IInterpreterSelector {
110131
const workspaceFolder = this.workspaceService.getWorkspaceFolder(this.documentManager.activeTextEditor!.document.uri);
111132
const isWorkspaceChange = Array.isArray(this.workspaceService.workspaceFolders) && this.workspaceService.workspaceFolders.length === 1;
112133

134+
const pythonPathUpdaterService = this.serviceContainer.get<IPythonPathUpdaterServiceManager>(IPythonPathUpdaterServiceManager);
113135
if (isGlobalChange) {
114-
await this.pythonPathUpdaterService.updatePythonPath(shebang, ConfigurationTarget.Global, 'shebang');
136+
await pythonPathUpdaterService.updatePythonPath(shebang, ConfigurationTarget.Global, 'shebang');
115137
return;
116138
}
117139

118140
if (isWorkspaceChange || !workspaceFolder) {
119-
await this.pythonPathUpdaterService.updatePythonPath(shebang, ConfigurationTarget.Workspace, 'shebang', this.workspaceService.workspaceFolders![0].uri);
141+
await pythonPathUpdaterService.updatePythonPath(shebang, ConfigurationTarget.Workspace, 'shebang', this.workspaceService.workspaceFolders![0].uri);
120142
return;
121143
}
122144

123-
await this.pythonPathUpdaterService.updatePythonPath(shebang, ConfigurationTarget.WorkspaceFolder, 'shebang', workspaceFolder.uri);
145+
await pythonPathUpdaterService.updatePythonPath(shebang, ConfigurationTarget.WorkspaceFolder, 'shebang', workspaceFolder.uri);
124146
}
125147
}

src/client/interpreter/contracts.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,7 @@ export interface ICondaService {
5353
export enum InterpreterType {
5454
Unknown = 1,
5555
Conda = 2,
56-
VirtualEnv = 4,
57-
VEnv = 8
56+
VirtualEnv = 4
5857
}
5958

6059
export type PythonInterpreter = {
@@ -67,6 +66,7 @@ export type PythonInterpreter = {
6766
envName?: string;
6867
envPath?: string;
6968
cachedEntry?: boolean;
69+
realPath?: string;
7070
};
7171

7272
export type WorkspacePythonPath = {

src/client/interpreter/display/index.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,7 @@ export class InterpreterDisplay implements IInterpreterDisplay {
8181
}
8282
this.statusBar.show();
8383
}
84-
private async getVirtualEnvironmentName(pythonPath: string) {
85-
return this.virtualEnvMgr
86-
.detect(pythonPath)
87-
.then(env => env ? env.name : '');
84+
private async getVirtualEnvironmentName(pythonPath: string): Promise<string> {
85+
return this.virtualEnvMgr.getEnvironmentName(pythonPath);
8886
}
8987
}

src/client/interpreter/index.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,13 @@ export class InterpreterManager implements Disposable, IInterpreterService {
102102
const pythonExecutableName = path.basename(fullyQualifiedPath);
103103
const versionInfo = await this.serviceContainer.get<IInterpreterVersionService>(IInterpreterVersionService).getVersion(fullyQualifiedPath, pythonExecutableName);
104104
const virtualEnvManager = this.serviceContainer.get<IVirtualEnvironmentManager>(IVirtualEnvironmentManager);
105-
const virtualEnv = await virtualEnvManager.detect(fullyQualifiedPath);
106-
const virtualEnvName = virtualEnv ? virtualEnv.name : '';
105+
const virtualEnvName = await virtualEnvManager.getEnvironmentName(fullyQualifiedPath);
107106
const dislayNameSuffix = virtualEnvName.length > 0 ? ` (${virtualEnvName})` : '';
108107
const displayName = `${versionInfo}${dislayNameSuffix}`;
109108
return {
110109
displayName,
111110
path: fullyQualifiedPath,
112-
type: virtualEnv ? virtualEnv.type : InterpreterType.Unknown,
111+
type: virtualEnvName.length > 0 ? InterpreterType.VirtualEnv : InterpreterType.Unknown,
113112
version: versionInfo
114113
};
115114
}

src/client/interpreter/locators/services/baseVirtualEnvService.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,14 @@ export class BaseVirtualEnvService extends CacheableLocatorService {
6767
private async getVirtualEnvDetails(interpreter: string): Promise<PythonInterpreter> {
6868
return Promise.all([
6969
this.versionProvider.getVersion(interpreter, path.basename(interpreter)),
70-
this.virtualEnvMgr.detect(interpreter)
70+
this.virtualEnvMgr.getEnvironmentName(interpreter)
7171
])
72-
.then(([displayName, virtualEnv]) => {
73-
const virtualEnvSuffix = virtualEnv ? virtualEnv.name : this.getVirtualEnvironmentRootDirectory(interpreter);
72+
.then(([displayName, virtualEnvName]) => {
73+
const virtualEnvSuffix = virtualEnvName.length ? virtualEnvName : this.getVirtualEnvironmentRootDirectory(interpreter);
7474
return {
7575
displayName: `${displayName} (${virtualEnvSuffix})`.trim(),
7676
path: interpreter,
77-
type: virtualEnv ? virtualEnv.type : InterpreterType.Unknown
77+
type: virtualEnvName.length > 0 ? InterpreterType.VirtualEnv : InterpreterType.Unknown
7878
};
7979
});
8080
}

src/client/interpreter/locators/services/currentPathService.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ export class CurrentPathService extends CacheableLocatorService {
3737
private async getInterpreterDetails(interpreter: string): Promise<PythonInterpreter> {
3838
return Promise.all([
3939
this.versionProvider.getVersion(interpreter, path.basename(interpreter)),
40-
this.virtualEnvMgr.detect(interpreter)
40+
this.virtualEnvMgr.getEnvironmentName(interpreter)
4141
]).
42-
then(([displayName, virtualEnv]) => {
43-
displayName += virtualEnv ? ` (${virtualEnv.name})` : '';
42+
then(([displayName, virtualEnvName]) => {
43+
displayName += virtualEnvName.length > 0 ? ` (${virtualEnvName})` : '';
4444
return {
4545
displayName,
4646
path: interpreter,
47-
type: virtualEnv ? virtualEnv.type : InterpreterType.Unknown
47+
type: virtualEnvName ? InterpreterType.VirtualEnv : InterpreterType.Unknown
4848
};
4949
});
5050
}

0 commit comments

Comments
 (0)