| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent ec5d69d commit b46ae28
22 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,7 +1,6 @@ | |||
| 1 | - import { PythonInterpreter } from "../index"; | ||
| 2 | - import { Architecture } from "../../common/registry"; | ||
| 1 | + import { Architecture } from "../common/registry"; | ||
| 3 | 2 | ||
| 4 | - export interface IInterpreterProvider { | ||
| 3 | + export interface IInterpreterLocatorService { | ||
| 5 | 4 | getInterpreters(): Promise<PythonInterpreter[]>; | |
| 6 | 5 | } | |
| 7 | 6 | export interface PythonInterpreter { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,17 +1,21 @@ | |||
| 1 | 1 | 'use strict'; | |
| 2 | + import * as path from 'path'; | ||
| 3 | + import * as utils from '../../common/utils'; | ||
| 4 | + import * as child_process from 'child_process'; | ||
| 2 | 5 | import { StatusBarItem, Disposable } from 'vscode'; | |
| 3 | 6 | import { PythonSettings } from '../../common/configSettings'; | |
| 4 | - import * as path from 'path'; | ||
| 5 | 7 | import { EOL } from 'os'; | |
| 6 | - import { IInterpreterProvider } from '../index'; | ||
| 7 | - import * as utils from '../../common/utils'; | ||
| 8 | + import { IInterpreterLocatorService } from '../contracts'; | ||
| 9 | + import { IInterpreterVersionService } from '../interpreterVersion'; | ||
| 8 | 10 | import { VirtualEnvironmentManager } from '../virtualEnvs/index'; | |
| 9 | - import { getFirstNonEmptyLineFromMultilineString } from '../sources/helpers'; | ||
| 10 | - import * as child_process from 'child_process'; | ||
| 11 | + import { getFirstNonEmptyLineFromMultilineString } from '../helpers'; | ||
| 11 | 12 | ||
| 12 | 13 | const settings = PythonSettings.getInstance(); | |
| 13 | 14 | export class InterpreterDisplay implements Disposable { | |
| 14 | - constructor(private statusBar: StatusBarItem, private interpreterProvoder: IInterpreterProvider, private virtualEnvMgr: VirtualEnvironmentManager) { | ||
| 15 | + constructor(private statusBar: StatusBarItem, | ||
| 16 | + private interpreterLocator: IInterpreterLocatorService, | ||
| 17 | + private virtualEnvMgr: VirtualEnvironmentManager, | ||
| 18 | + private versionProvider: IInterpreterVersionService) { | ||
| 15 | 19 | this.statusBar.command = 'python.setInterpreter'; | |
| 16 | 20 | } | |
| 17 | 21 | public dispose() { | |
@@ -21,7 +25,7 @@ export class InterpreterDisplay implements Disposable { | |||
| 21 | 25 | await this.updateDisplay(pythonPath); | |
| 22 | 26 | } | |
| 23 | 27 | private getInterpreters() { | |
| 24 | - return this.interpreterProvoder.getInterpreters(); | ||
| 28 | + return this.interpreterLocator.getInterpreters(); | ||
| 25 | 29 | } | |
| 26 | 30 | private async updateDisplay(pythonPath: string) { | |
| 27 | 31 | const interpreters = await this.getInterpreters(); | |
@@ -39,7 +43,7 @@ export class InterpreterDisplay implements Disposable { | |||
| 39 | 43 | else { | |
| 40 | 44 | const defaultDisplayName = `${path.basename(pythonPath)} [Environment]`; | |
| 41 | 45 | const interpreterExists = utils.fsExistsAsync(pythonPath); | |
| 42 | - const displayName = utils.getInterpreterDisplayName(pythonPath).catch(() => defaultDisplayName); | ||
| 46 | + const displayName = this.versionProvider.getVersion(pythonPath, defaultDisplayName); | ||
| 43 | 47 | const virtualEnvName = this.getVirtualEnvironmentName(pythonPath); | |
| 44 | 48 | await Promise.all([interpreterExists, displayName, virtualEnvName]) | |
| 45 | 49 | .then(([interpreterExists, displayName, virtualEnvName]) => { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,7 @@ | |||
| 1 | + export function getFirstNonEmptyLineFromMultilineString(stdout: string) { | ||
| 2 | + if (!stdout) { | ||
| 3 | + return ''; | ||
| 4 | + } | ||
| 5 | + const lines = stdout.split(/\r?\n/g).map(line => line.trim()).filter(line => line.length > 0); | ||
| 6 | + return lines.length > 0 ? lines[0] : ''; | ||
| 7 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,26 +1,27 @@ | |||
| 1 | 1 | 'use strict'; | |
| 2 | + import { InterpreterVersionService } from './interpreterVersion'; | ||
| 2 | 3 | import { VirtualEnv } from './virtualEnvs/virtualEnv'; | |
| 3 | 4 | import { VEnv } from './virtualEnvs/venv'; | |
| 4 | 5 | import { Disposable, window, StatusBarAlignment, workspace } from 'vscode'; | |
| 5 | 6 | import { PythonSettings } from '../common/configSettings'; | |
| 6 | 7 | import { InterpreterDisplay } from './display'; | |
| 7 | - import { PythonInterpreterProvider } from './sources'; | ||
| 8 | + import { PythonInterpreterLocatorService } from './locators'; | ||
| 8 | 9 | import { VirtualEnvironmentManager } from './virtualEnvs/index'; | |
| 9 | 10 | import { IS_WINDOWS } from '../common/utils'; | |
| 10 | 11 | import * as path from 'path'; | |
| 11 | - export * from './sources'; | ||
| 12 | 12 | ||
| 13 | 13 | const settings = PythonSettings.getInstance(); | |
| 14 | 14 | ||
| 15 | 15 | export class InterpreterManager implements Disposable { | |
| 16 | 16 | private disposables: Disposable[] = []; | |
| 17 | 17 | private display: InterpreterDisplay | null | undefined; | |
| 18 | - private interpreterProvider: PythonInterpreterProvider; | ||
| 18 | + private interpreterProvider: PythonInterpreterLocatorService; | ||
| 19 | 19 | constructor() { | |
| 20 | 20 | const virtualEnvMgr = new VirtualEnvironmentManager([new VEnv(), new VirtualEnv()]); | |
| 21 | 21 | const statusBar = window.createStatusBarItem(StatusBarAlignment.Left); | |
| 22 | - this.interpreterProvider = new PythonInterpreterProvider(virtualEnvMgr); | ||
| 23 | - this.display = new InterpreterDisplay(statusBar, this.interpreterProvider, virtualEnvMgr); | ||
| 22 | + this.interpreterProvider = new PythonInterpreterLocatorService(virtualEnvMgr); | ||
| 23 | + const versionService = new InterpreterVersionService(); | ||
| 24 | + this.display = new InterpreterDisplay(statusBar, this.interpreterProvider, virtualEnvMgr, versionService); | ||
| 24 | 25 | settings.addListener('change', this.onConfigChanged.bind(this)); | |
| 25 | 26 | this.display.refresh(); | |
| 26 | 27 | ||
@@ -41,9 +42,9 @@ export class InterpreterManager implements Disposable { | |||
| 41 | 42 | return; | |
| 42 | 43 | } | |
| 43 | 44 | ||
| 44 | - // Ensure this new environment is at the same level as the current workspace | ||
| 45 | - // In windows the interpreter is under scripts/python.exe on linux it is under bin/python | ||
| 46 | - // Meaning the sub directory must be either scripts, bin or other (but only one level deep) | ||
| 45 | + // Ensure this new environment is at the same level as the current workspace. | ||
| 46 | + // In windows the interpreter is under scripts/python.exe on linux it is under bin/python. | ||
| 47 | + // Meaning the sub directory must be either scripts, bin or other (but only one level deep). | ||
| 47 | 48 | const pythonPath = interpretersInWorkspace[0].path; | |
| 48 | 49 | const relativePath = path.dirname(pythonPath).substring(workspace.rootPath!.length); | |
| 49 | 50 | if (relativePath.split(path.sep).filter(l => l.length > 0).length === 2) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,13 @@ | |||
| 1 | + import { getInterpreterDisplayName } from '../common/utils'; | ||
| 2 | + | ||
| 3 | + export interface IInterpreterVersionService { | ||
| 4 | + getVersion(pythonPath: string, defaultValue: string): Promise<string>; | ||
| 5 | + } | ||
| 6 | + | ||
| 7 | + export class InterpreterVersionService implements IInterpreterVersionService { | ||
| 8 | + getVersion(pythonPath: string, defaultValue: string): Promise<string> { | ||
| 9 | + return getInterpreterDisplayName(pythonPath) | ||
| 10 | + .catch(() => defaultValue); | ||
| 11 | + } | ||
| 12 | + } | ||
| 13 | + | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,4 +1,4 @@ | |||
| 1 | - import { PythonInterpreter } from "../index"; | ||
| 1 | + import { PythonInterpreter } from "../contracts"; | ||
| 2 | 2 | import { IS_WINDOWS, fsReaddirAsync } from "../../common/utils"; | |
| 3 | 3 | import * as path from 'path'; | |
| 4 | 4 | import { getArchitectureDislayName } from "../../common/registry"; | |
@@ -19,17 +19,8 @@ export function fixInterpreterDisplayName(item: PythonInterpreter) { | |||
| 19 | 19 | return item; | |
| 20 | 20 | } | |
| 21 | 21 | export function fixInterpreterPath(item: PythonInterpreter) { | |
| 22 | - // For some reason anaconda seems to use \\ in the registry path | ||
| 22 | + // For some reason anaconda seems to use \\ in the registry path. | ||
| 23 | 23 | item.path = IS_WINDOWS ? item.path.replace(/\\\\/g, "\\") : item.path; | |
| 24 | - // Also ensure paths have back slashes instead of forward | ||
| 25 | 24 | item.path = IS_WINDOWS ? item.path.replace(/\//g, "\\") : item.path; | |
| 26 | 25 | return item; | |
| 27 | 26 | } | |
| 28 | - | ||
| 29 | - export function getFirstNonEmptyLineFromMultilineString(stdout: string) { | ||
| 30 | - if (stdout.length === 0) { | ||
| 31 | - return ''; | ||
| 32 | - } | ||
| 33 | - const lines = stdout.split(/\r?\n/g).filter(line => line.trim().length > 0); | ||
| 34 | - return lines.length > 0 ? lines[0] : ''; | ||
| 35 | - } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,61 @@ | |||
| 1 | + "use strict"; | ||
| 2 | + import * as _ from 'lodash'; | ||
| 3 | + import { fixInterpreterPath, fixInterpreterDisplayName } from './helpers'; | ||
| 4 | + import { IInterpreterLocatorService, PythonInterpreter } from '../contracts'; | ||
| 5 | + import { InterpreterVersionService } from '../interpreterVersion'; | ||
| 6 | + import { IS_WINDOWS, Is_64Bit, arePathsSame, areBasePathsSame } from '../../common/utils'; | ||
| 7 | + import { RegistryImplementation } from '../../common/registry'; | ||
| 8 | + import { CondaEnvService } from './services/condaEnvService'; | ||
| 9 | + import { VirtualEnvService, getKnownSearchPathsForVirtualEnvs } from './services/virtualEnvService'; | ||
| 10 | + import { KnownPathsService, getKnownSearchPathsForInterpreters } from './services/KnownPathsService'; | ||
| 11 | + import { CurrentPathService } from './services/currentPathService'; | ||
| 12 | + import { WindowsRegistryService } from './services/windowsRegistryService'; | ||
| 13 | + import { VirtualEnvironmentManager } from '../virtualEnvs'; | ||
| 14 | + import { CondaEnvFileService, getEnvironmentsFile as getCondaEnvFile } from './services/condaEnvFileService'; | ||
| 15 | + | ||
| 16 | + export class PythonInterpreterLocatorService implements IInterpreterLocatorService { | ||
| 17 | + private interpreters: PythonInterpreter[] = []; | ||
| 18 | + private locators: IInterpreterLocatorService[] = []; | ||
| 19 | + constructor(private virtualEnvMgr: VirtualEnvironmentManager) { | ||
| 20 | + const versionService = new InterpreterVersionService(); | ||
| 21 | + // The order of the services is important. | ||
| 22 | + if (IS_WINDOWS) { | ||
| 23 | + const windowsRegistryProvider = new WindowsRegistryService(new RegistryImplementation(), Is_64Bit); | ||
| 24 | + this.locators.push(windowsRegistryProvider); | ||
| 25 | + this.locators.push(new CondaEnvService(windowsRegistryProvider)); | ||
| 26 | + } | ||
| 27 | + else { | ||
| 28 | + this.locators.push(new CondaEnvService()); | ||
| 29 | + } | ||
| 30 | + // Supplements the above list of conda environments. | ||
| 31 | + this.locators.push(new CondaEnvFileService(getCondaEnvFile(), versionService)); | ||
| 32 | + this.locators.push(new VirtualEnvService(getKnownSearchPathsForVirtualEnvs(), this.virtualEnvMgr, versionService)); | ||
| 33 | + | ||
| 34 | + if (!IS_WINDOWS) { | ||
| 35 | + // This must be last, it is possible we have paths returned here that are already returned | ||
| 36 | + // in one of the above lists. | ||
| 37 | + this.locators.push(new KnownPathsService(getKnownSearchPathsForInterpreters(), versionService)); | ||
| 38 | + } | ||
| 39 | + // This must be last, it is possible we have paths returned here that are already returned | ||
| 40 | + // in one of the above lists. | ||
| 41 | + this.locators.push(new CurrentPathService(this.virtualEnvMgr, versionService)); | ||
| 42 | + } | ||
| 43 | + public async getInterpreters() { | ||
| 44 | + if (this.interpreters.length > 0) { | ||
| 45 | + return this.interpreters; | ||
| 46 | + } | ||
| 47 | + const promises = this.locators.map(provider => provider.getInterpreters()); | ||
| 48 | + return Promise.all(promises) | ||
| 49 | + .then(interpreters => _.flatten(interpreters)) | ||
| 50 | + .then(items => items.map(fixInterpreterDisplayName)) | ||
| 51 | + .then(items => items.map(fixInterpreterPath)) | ||
| 52 | + .then(items => items.reduce<PythonInterpreter[]>((accumulator, current) => { | ||
| 53 | + if (accumulator.findIndex(item => arePathsSame(item.path, current.path)) === -1 && | ||
| 54 | + accumulator.findIndex(item => areBasePathsSame(item.path, current.path)) === -1) { | ||
| 55 | + accumulator.push(current); | ||
| 56 | + } | ||
| 57 | + return accumulator; | ||
| 58 | + }, [])) | ||
| 59 | + .then(interpreters => this.interpreters = interpreters); | ||
| 60 | + } | ||
| 61 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,13 +1,15 @@ | |||
| 1 | 1 | "use strict"; | |
| 2 | 2 | import * as path from 'path'; | |
| 3 | 3 | import * as _ from 'lodash'; | |
| 4 | - import { IInterpreterProvider } from '../contracts'; | ||
| 5 | - import { fsExistsAsync, getInterpreterDisplayName, IS_WINDOWS } from '../../../common/utils'; | ||
| 4 | + import { IInterpreterLocatorService } from '../../contracts'; | ||
| 5 | + import { IInterpreterVersionService } from '../../interpreterVersion'; | ||
| 6 | + import { fsExistsAsync, IS_WINDOWS } from '../../../common/utils'; | ||
| 6 | 7 | import { lookForInterpretersInDirectory } from '../helpers'; | |
| 7 | 8 | const untildify = require('untildify'); | |
| 8 | 9 | ||
| 9 | - export class KnownPathsProvider implements IInterpreterProvider { | ||
| 10 | - public constructor(private knownSearchPaths: string[]) { } | ||
| 10 | + export class KnownPathsService implements IInterpreterLocatorService { | ||
| 11 | + public constructor(private knownSearchPaths: string[], | ||
| 12 | + private versionProvider: IInterpreterVersionService) { } | ||
| 11 | 13 | public getInterpreters() { | |
| 12 | 14 | return this.suggestionsFromKnownPaths(); | |
| 13 | 15 | } | |
@@ -20,7 +22,7 @@ export class KnownPathsProvider implements IInterpreterProvider { | |||
| 20 | 22 | .then(interpreters => Promise.all(interpreters.map(interpreter => this.getInterpreterDetails(interpreter)))); | |
| 21 | 23 | } | |
| 22 | 24 | private getInterpreterDetails(interpreter: string) { | |
| 23 | - return getInterpreterDisplayName(interpreter).catch(() => path.basename(interpreter)) | ||
| 25 | + return this.versionProvider.getVersion(interpreter, path.basename(interpreter)) | ||
| 24 | 26 | .then(displayName => { | |
| 25 | 27 | return { | |
| 26 | 28 | displayName, | |
@@ -42,7 +44,7 @@ export function getKnownSearchPathsForInterpreters(): string[] { | |||
| 42 | 44 | paths.forEach(p => { | |
| 43 | 45 | paths.push(untildify('~' + p)); | |
| 44 | 46 | }); | |
| 45 | - // Add support for paths such as /Users/xxx/anaconda/bin | ||
| 47 | + // Add support for paths such as /Users/xxx/anaconda/bin. | ||
| 46 | 48 | if (process.env['HOME']) { | |
| 47 | 49 | paths.push(path.join(process.env['HOME'], 'anaconda', 'bin')); | |
| 48 | 50 | paths.push(path.join(process.env['HOME'], 'python', 'bin')); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,7 @@ | |||
| 1 | + import { IS_WINDOWS } from "../../../common/utils"; | ||
| 2 | + | ||
| 3 | + // where to find the Python binary within a conda env. | ||
| 4 | + export const CONDA_RELATIVE_PY_PATH = IS_WINDOWS ? ['python.exe'] : ['bin', 'python']; | ||
| 5 | + export const AnacondaCompanyNames = ['Anaconda, Inc.', 'Continuum Analytics, Inc.']; | ||
| 6 | + export const AnacondaCompanyName = 'Anaconda, Inc.'; | ||
| 7 | + export const AnacondaDisplayName = 'Anaconda'; | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,66 @@ | |||
| 1 | + "use strict"; | ||
| 2 | + import * as fs from 'fs-extra'; | ||
| 3 | + import * as path from 'path'; | ||
| 4 | + import { IS_WINDOWS } from '../../../common/configSettings'; | ||
| 5 | + import { IInterpreterVersionService } from '../../interpreterVersion'; | ||
| 6 | + import { IInterpreterLocatorService, PythonInterpreter } from '../../contracts'; | ||
| 7 | + import { AnacondaDisplayName, AnacondaCompanyName, AnacondaCompanyNames, CONDA_RELATIVE_PY_PATH } from './conda'; | ||
| 8 | + | ||
| 9 | + export class CondaEnvFileService implements IInterpreterLocatorService { | ||
| 10 | + constructor(private condaEnvironmentFile: string, | ||
| 11 | + private versionService: IInterpreterVersionService) { | ||
| 12 | + } | ||
| 13 | + public getInterpreters() { | ||
| 14 | + return this.getSuggestionsFromConda(); | ||
| 15 | + } | ||
| 16 | + | ||
| 17 | + private getSuggestionsFromConda(): Promise<PythonInterpreter[]> { | ||
| 18 | + return fs.pathExists(this.condaEnvironmentFile) | ||
| 19 | + .then(exists => exists ? this.getEnvironmentsFromFile(this.condaEnvironmentFile) : Promise.resolve([])); | ||
| 20 | + } | ||
| 21 | + private getEnvironmentsFromFile(envFile: string) { | ||
| 22 | + return fs.readFile(envFile) | ||
| 23 | + .then(buffer => buffer.toString().split(/\r?\n/g)) | ||
| 24 | + .then(lines => lines.map(line => line.trim())) | ||
| 25 | + .then(lines => lines.map(line => path.join(line, ...CONDA_RELATIVE_PY_PATH))) | ||
| 26 | + .then(interpreterPaths => interpreterPaths.map(item => fs.pathExists(item).then(exists => exists ? item : ''))) | ||
| 27 | + .then(promises => Promise.all(promises)) | ||
| 28 | + .then(interpreterPaths => interpreterPaths.filter(item => item.length > 0)) | ||
| 29 | + .then(interpreterPaths => interpreterPaths.map(item => this.getInterpreterDetails(item))) | ||
| 30 | + .then(promises => Promise.all(promises)); | ||
| 31 | + } | ||
| 32 | + private getInterpreterDetails(interpreter: string) { | ||
| 33 | + return this.versionService.getVersion(interpreter, path.basename(interpreter)) | ||
| 34 | + .then(version => { | ||
| 35 | + version = this.stripCompanyName(version); | ||
| 36 | + const envName = this.getEnvironmentRootDirectory(interpreter); | ||
| 37 | + const info: PythonInterpreter = { | ||
| 38 | + displayName: `${AnacondaDisplayName} ${version} (${envName})`, | ||
| 39 | + path: interpreter, | ||
| 40 | + companyDisplayName: AnacondaCompanyName, | ||
| 41 | + version: version | ||
| 42 | + }; | ||
| 43 | + return info; | ||
| 44 | + }); | ||
| 45 | + } | ||
| 46 | + private stripCompanyName(content: string) { | ||
| 47 | + // Strip company name from version. | ||
| 48 | + const startOfCompanyName = AnacondaCompanyNames.reduce((index, companyName) => { | ||
| 49 | + if (index > 0) { | ||
| 50 | + return index; | ||
| 51 | + } | ||
| 52 | + return content.indexOf(`:: ${AnacondaCompanyName}`); | ||
| 53 | + }, -1); | ||
| 54 | + | ||
| 55 | + return startOfCompanyName > 0 ? content.substring(0, startOfCompanyName).trim() : content; | ||
| 56 | + } | ||
| 57 | + private getEnvironmentRootDirectory(interpreter: string) { | ||
| 58 | + const envDir = interpreter.substring(0, interpreter.length - path.join(...CONDA_RELATIVE_PY_PATH).length); | ||
| 59 | + return path.basename(envDir); | ||
| 60 | + } | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + export function getEnvironmentsFile() { | ||
| 64 | + const homeDir = IS_WINDOWS ? process.env.USERPROFILE : (process.env.HOME || process.env.HOMEPATH); | ||
| 65 | + return homeDir ? path.join(homeDir, '.conda', 'environments.txt') : ''; | ||
| 66 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments