diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..551484b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "ms-python.vscode-pylance", + "ms-python.python" + ] +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 0916fd7..3c9821f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -34,6 +34,18 @@ "outFiles": ["${workspaceFolder}/out/test/**/*.js"], "preLaunchTask": "${defaultBuildTask}" }, + { + "name": "Launch Extension Dummy", + "type": "extensionHost", + "request": "launch", + "args": [ + //"--disable-extensions", + "--extensionDevelopmentPath=${workspaceFolder}/vscode_ext", + "${workspaceFolder}/temp-envs/project_dummy_needls_test", + ], + "outFiles": ["${workspaceFolder}/out/test_dummy/**/*.js"], + "preLaunchTask": "${defaultBuildTask}" + }, { "name": "Setup Envs", "type": "python", diff --git a/docs/arch.rst b/docs/arch.rst new file mode 100644 index 0000000..0eb0dc2 --- /dev/null +++ b/docs/arch.rst @@ -0,0 +1,49 @@ +Installation routine +==================== + +Process to install needls: + +.. uml:: + + @startuml + start + + :Trigger\n 1. open-needs-ide.load\n 2. open .rst file; + + :Check needls.pythonPath specified in workspace setting; + + if (needls.pythonPath specified and valid?) then (no) + + #palegreen:repeat :Prompt inputbox to ask user to specify; + if (user specify?) then (yes) + :user specify python path or use default; + :press ENTER; + #yellow:check and validate given python path; + else (no) + #darkorange:press ESC; + end + + endif + + repeat while (valid?) is (no) + ->yes; + + else (yes) + + endif + + :Use specified valid pythonPath to install needls; + if (needls installed?) then (no) + #yellowgreen:install needls; + if (user confirm?) then (yes) + :needls install success; + else (no) + end + endif + + else (yes) + + endif + + stop + @enduml diff --git a/docs/changelog.rst b/docs/changelog.rst index c570e44..fae050f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,11 +1,19 @@ Changelog ========= -0.0.15 +0.0.16 ------ **released**: under development +0.0.15 +------ + +**released**: 23.02.2022 + +* Improvement: Supporting ``pythonPath`` config in settings of Open-Needs IDE. Default is system python path. :issue:`38` +* Bugfix: Mutliple little changes + 0.0.14 ------ diff --git a/docs/conf.py b/docs/conf.py index 0dd36c9..130c6fd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,6 +28,7 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ + "sphinxcontrib.plantuml", "sphinxcontrib.needs", "sphinx_panels", "sphinx_issues", diff --git a/docs/index.rst b/docs/index.rst index 3052045..6ffdab0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -81,6 +81,7 @@ Content .. toctree:: :maxdepth: 2 + arch installation settings features diff --git a/docs/installation.rst b/docs/installation.rst index 0a806ae..a0a9da7 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -23,6 +23,7 @@ Installation of the extension #. Update the **Build Path**. E.g. ``/home/my-user/my-project/docs/_build/need``. #. Update the **Docs Root**. E.g. ``/home/my-user/my-project/docs`` + #. Update the **pythonPath**. E.g. ``/home/my-user/my-project/.venv/bin/python``. Default is system python path. E.g. ``/usr/bin/python``. #. Open a reStructuredText file (`*.rst`) in workspace to trigger the activation of the extension. diff --git a/docs/settings.rst b/docs/settings.rst index c68a1df..01001c9 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -9,6 +9,8 @@ This extension contributes the following settings: expected. This file is created using using the `sphinx-needs builder `__ +:needls.pythonPath: Python path used to install `Open-Needs-IDE:needls`. Default system python path `/usr/bin/python` will be used if this setting is not configured. + Supported variables ------------------- **Open-Needs IDE** supports the usage of template variables, which get replaced during runtime. @@ -26,6 +28,7 @@ Inside a ``.vscode/settings.json`` file a configuration can look like:: { "needls.docsRoot": "${workspaceFolder}/docs" "needls.buildPath": "${workspaceFolder}/docs/_build/need", + "needls.pythonPath": "${workspaceFolder}/.venv/bin/python", } Settings menu diff --git a/needls/version.py b/needls/version.py index 311f216..6561790 100644 --- a/needls/version.py +++ b/needls/version.py @@ -1 +1 @@ -__version__ = "0.0.14" +__version__ = "0.0.15" diff --git a/setup.py b/setup.py index 75496b1..33eb4c3 100644 --- a/setup.py +++ b/setup.py @@ -14,6 +14,7 @@ setup( name="open-needs-ls", + # Don't forget package.json, changelog and needls.version version=main_ns["__version__"], url="https://open-needs.org", author="Daniel Woste", diff --git a/test-envs/_basics/.vscode/settings.json b/test-envs/_basics/.vscode/settings.json index e283846..db66bc5 100644 --- a/test-envs/_basics/.vscode/settings.json +++ b/test-envs/_basics/.vscode/settings.json @@ -1,4 +1,5 @@ { "needls.buildPath": "{{build_path}}", - "needls.docsRoot": "{{docs_root}}" + "needls.docsRoot": "{{docs_root}}", + "needls.pythonPath": "{{pythonPath}}", } diff --git a/test-envs/setup_envs.py b/test-envs/setup_envs.py index 6d7d78b..f42fdca 100644 --- a/test-envs/setup_envs.py +++ b/test-envs/setup_envs.py @@ -23,9 +23,9 @@ def __init__(self, name: str, reuse: bool = False) -> None: self.test_envs_path = os.path.join(os.path.dirname(__file__)) self.basics_path = os.path.join(self.test_envs_path, "_basics") - + self.temp_env_path = os.path.join(self.test_envs_path, "../temp-envs") - + self.env_path = os.path.join(self.temp_env_path, name) def setup(self): @@ -75,6 +75,7 @@ def parse_templates(self): ".vscode/settings.json": { "build_path": "${workspaceFolder}/docs/_build", "docs_root": "${workspaceFolder}/docs", + "pythonPath": "", } } @@ -100,6 +101,9 @@ def start(): project1 = ProjectEnv("project_no_needls", reuse=True) project1.setup() + project2 = ProjectEnv("project_dummy_needls_test", reuse=True) + project2.setup() + if "main" in __name__: start() diff --git a/vscode_ext/package.json b/vscode_ext/package.json index 749adb4..e823f91 100644 --- a/vscode_ext/package.json +++ b/vscode_ext/package.json @@ -10,7 +10,7 @@ "url": "https://github.com/open-needs/open-needs-ide.git" }, "homepage": "https://open-needs.org/open-needs-ide/", - "version": "0.0.14", + "version": "0.0.15", "engines": { "vscode": "^1.52.0" }, @@ -68,6 +68,10 @@ "type": "string", "default": "/build", "description": "Sphinx build directory. This is an absolute path!" + }, + "needls.pythonPath": { + "type": "string", + "description": "Python path configured by the user. This is an absolute path!" } } } diff --git a/vscode_ext/src/extension.ts b/vscode_ext/src/extension.ts index b3f17bf..287ea0e 100644 --- a/vscode_ext/src/extension.ts +++ b/vscode_ext/src/extension.ts @@ -9,9 +9,7 @@ import * as fs from 'fs'; import { workspace, - extensions, commands, - Uri, ExtensionContext, window, OutputChannel, @@ -27,28 +25,10 @@ import { import { exec, ExecException } from 'child_process'; -let client: LanguageClient; - -async function getPythonPath(resource: Uri = null, outChannel: OutputChannel): Promise { - const extension = extensions.getExtension('ms-python.python'); - let pythonPath - if (extension) { - const usingNewInterpreterStorage = extension.packageJSON?.featureFlags?.usingNewInterpreterStorage; - if (usingNewInterpreterStorage) { - if (!extension.isActive) { - await extension.activate(); - } - pythonPath = extension.exports.settings.getExecutionDetails(resource).execCommand[0]; - } else { - pythonPath = workspace.getConfiguration('python', resource).get('pythonPath'); - } - }else { - pythonPath = exec_py('python', outChannel, '-c', 'import sys; print(sys.executable)') - } - return pythonPath +let client: LanguageClient; +const log_prefix = "Extension Open-Needs: "; -} function exec_py(pythonPath: string, outChannel: OutputChannel, ...args: string[]): Promise { const cmd = [pythonPath, ...args]; @@ -76,54 +56,83 @@ function exec_py(pythonPath: string, outChannel: OutputChannel, ...args: string[ }); } -async function installNeedls(pythonPath: string, outChannel: OutputChannel, version: string): Promise { - const install = await window.showInformationMessage( - `Install needls==${version} from PyPI?`, - 'Yes', - 'No' - ).then( (item) => { - if ( item === 'Yes' ) { - return true; +async function checkAndValidatePythonPath(pythonPath:string, outChannel: OutputChannel): Promise { + // check pythonPath if empty, ask user to config; if not, use the pythonPath from workspace setting + if (!pythonPath) { + return false + } + // check if pythonPath exists: run cmd to check python version to confirm + try { + await exec_py(pythonPath, outChannel, '--version'); + } catch (error) { + console.log(error) + outChannel.appendLine(error); + + return false + } + return true +} + +async function getUserInputPythonPath(pythonPathProposal: string, outChannel: OutputChannel): Promise { + window.showInformationMessage(`${log_prefix} please specify python path.`); + + let pythonPath = "" + while(await checkAndValidatePythonPath(pythonPath, outChannel) == false) { + // prompting window to ask user to config + const user_input_pythonPath = await window.showInputBox({ + placeHolder: "Python path for Extension Open-Needs", + prompt: "Example: ${workspaceFolder}/.venv/bin/python", + value: pythonPathProposal, + ignoreFocusOut: true, + }); + + if (user_input_pythonPath) { + pythonPath = user_input_pythonPath + + // update pythonPathProposal for inputbox + pythonPathProposal = pythonPath + + const currentWorkspaceFolderPath = workspace.getWorkspaceFolder(window.activeTextEditor.document.uri)?.uri.fsPath + pythonPath = pythonPath.replace('${workspaceFolder}', currentWorkspaceFolderPath) } else { - return false - } - }); - if (install === true) { - try { - await exec_py( - pythonPath, - outChannel, - '-m', - 'pip', - 'install', - 'pip', - '--upgrade' - ); - await exec_py( - pythonPath, - outChannel, - '-m', - 'pip', - 'uninstall', - 'open-needs-ls', - '-y' - ); - await exec_py( - pythonPath, - outChannel, - '-m', - 'pip', - 'install', - `open-needs-ls==${version}` - ); - window.showInformationMessage("Needls successfully installed."); - return true; - } catch (e){ - console.log(e) - window.showInformationMessage(`Needls could not be installed ${e}`); + // user canceled + pythonPath = undefined + break; } } - return false + + return pythonPath +} + +async function installNeedls(pythonPath: string, outChannel: OutputChannel, version: string): Promise { + await exec_py( + pythonPath, + outChannel, + '-m', + 'pip', + 'install', + 'pip', + '--upgrade' + ); + await exec_py( + pythonPath, + outChannel, + '-m', + 'pip', + 'uninstall', + 'open-needs-ls', + '-y' + ); + await exec_py( + pythonPath, + outChannel, + '-m', + 'pip', + 'install', + `open-needs-ls==${version}` + ); + window.showInformationMessage("Needls successfully installed."); + return true; } async function checkForNeedls(pythonPath: string, outChannel: OutputChannel, ext_version: string): Promise { @@ -136,30 +145,43 @@ async function checkForNeedls(pythonPath: string, outChannel: OutputChannel, ext ); needls_version = needls_version.trim(); if (ext_version != needls_version) { - window.showWarningMessage(`Needls found but wrong version: ${needls_version}\nVersion needed: ${ext_version}. - Needls should be reinstalled`); return false; } - return true; } catch (e) { console.warn(e) outChannel.appendLine(e); - window.showWarningMessage(`Error during detecting needls. Needls should be reinstalled.`); return false } + + + try { + await exec_py( + pythonPath, + outChannel, + '-c', + '"import needls.server"' + ); + } catch (e) { + console.warn(e) + outChannel.appendLine(e); + return false + } + + return true; } async function read_settings(_outChannel: OutputChannel) { let docs_root = workspace.getConfiguration('needls').get('docsRoot').toString(); let build_path = workspace.getConfiguration('needls').get('buildPath').toString(); + let pythonPath = workspace.getConfiguration('needls').get('pythonPath').toString(); const currentWorkspaceFolderPath = workspace.getWorkspaceFolder(window.activeTextEditor.document.uri)?.uri.fsPath docs_root = docs_root.replace('${workspaceFolder}', currentWorkspaceFolderPath) build_path = build_path.replace('${workspaceFolder}', currentWorkspaceFolderPath) + pythonPath = pythonPath.replace('${workspaceFolder}', currentWorkspaceFolderPath) - - commands.executeCommand('needls.update_settings', docs_root, build_path); + commands.executeCommand('needls.update_settings', docs_root, build_path, pythonPath); } async function make_needs(pythonPath: string, outChannel: OutputChannel) { @@ -200,8 +222,7 @@ export async function activate(context: ExtensionContext): Promise { const packageFile = JSON.parse(fs.readFileSync(extensionPath, 'utf8')); const ext_version = packageFile.version; console.log(`Extension version: ${ext_version}`) - - + const disposable = commands.registerCommand('open-needs-ide.load', () => { // The code you place here will be executed every time your command is executed // Display a message box to the user @@ -211,26 +232,78 @@ export async function activate(context: ExtensionContext): Promise { }); context.subscriptions.push(disposable); - //Create output channel for logging + //Create oversionutput channel for logging const outChannel = window.createOutputChannel("Open-Needs IDE"); const cwd = path.join(__dirname, "..", ".."); outChannel.appendLine("CWD: " + cwd); - const resource = window.activeTextEditor?.document.uri; - const pythonPath = await getPythonPath(resource, outChannel); + // + // PYTHON PATH AND USER CONF HANDLING + // + + // get pythonPath from workspace setting + let wk_pythonPath = workspace.getConfiguration('needls').get('pythonPath').toString(); + const currentWorkspaceFolderPath = workspace.getWorkspaceFolder(window.activeTextEditor.document.uri)?.uri.fsPath + wk_pythonPath = wk_pythonPath.replace('${workspaceFolder}', currentWorkspaceFolderPath) + + let pythonPath = "" + if (wk_pythonPath == "") { + let sysPythonPath = await exec_py('python', outChannel, '-c', '"import sys; print(sys.executable)"'); + sysPythonPath = sysPythonPath.trim(); + pythonPath = await getUserInputPythonPath(sysPythonPath, outChannel); + }else if (!checkAndValidatePythonPath(wk_pythonPath, outChannel)){ + pythonPath = await getUserInputPythonPath(wk_pythonPath, outChannel); + }else{ + pythonPath = wk_pythonPath + } + + if (pythonPath === undefined) { + window.showErrorMessage(`Python path not given. ${log_prefix} can not be loaded.`); + return + } + outChannel.appendLine("Python path: " + pythonPath); + // update pythonPath for workspace setting + workspace.getConfiguration('needls').update('pythonPath', pythonPath, false); + // Check for needls let needls_installed = await checkForNeedls(pythonPath, outChannel, ext_version); + + // Ask for needls installation if ( !needls_installed ) { - needls_installed = await installNeedls(pythonPath, outChannel, ext_version); + window.showWarningMessage("Invalid Open-Needs Server installation. Please reinstall..."); + const install = await window.showInformationMessage( + `Install needls==${ext_version} from PyPI?`, + 'Yes', + 'No' + ).then( (item) => { + if ( item === 'Yes' ) { + return true; + } else { + return false + } + }); + if (install) { + try { + needls_installed = await installNeedls(pythonPath, outChannel, ext_version); + } catch (e){ + console.log(e) + window.showInformationMessage(`Needls could not be installed. Error: ${e}`); + return + } + } } if ( !needls_installed ) { window.showErrorMessage("Python module needls not found! Needs extension can't start."); } + // + // REGISTER INTERNAL HANDLERS + // + // listen for changes of settings workspace.onDidChangeConfiguration( (_event) => { read_settings(outChannel); @@ -272,26 +345,21 @@ export async function activate(context: ExtensionContext): Promise { } }; - if (pythonPath) { - // Create the language client and start the client. - client = new LanguageClient( - 'open-needs-ls', - 'Open-Needs LS', - serverOptions, - clientOptions - ); - - // Start the client. This will also launch the server - client.start(); - - // set doc root and needs.json file - client.onReady().then(async () => { - await read_settings(outChannel); - }) - } else { - window.showErrorMessage("Python not found! Can't activate extension."); - outChannel.appendLine("Python not found! Can't activate extension."); - } + // Create the language client and start the client. + client = new LanguageClient( + 'open-needs-ls', + 'Open-Needs LS', + serverOptions, + clientOptions + ); + + // Start the client. This will also launch the server + client.start(); + + // set doc root and needs.json file + client.onReady().then(async () => { + await read_settings(outChannel); + }) } export function deactivate(): Thenable | undefined { @@ -300,3 +368,4 @@ export function deactivate(): Thenable | undefined { } return client.stop(); } +