From 4bd97b2111ef0fadd8f658a38d79f8439ef87d70 Mon Sep 17 00:00:00 2001 From: Philipp Nieting Date: Tue, 3 Oct 2017 17:49:52 +0200 Subject: [PATCH 1/5] CodeLense for Shebangs --- package.json | 1 + src/client/extension.ts | 2 + .../providers/setInterpreterProvider.ts | 11 +++- .../providers/shebangCodeLensProvider.ts | 57 +++++++++++++++++++ 4 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 src/client/providers/shebangCodeLensProvider.ts diff --git a/package.json b/package.json index ecc33854217f..789cbba0660d 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "onCommand:python.runtests", "onCommand:python.debugtests", "onCommand:python.setInterpreter", + "onCommand:python.setShebangInterpreter", "onCommand:python.viewTestUI", "onCommand:python.viewTestOutput", "onCommand:python.selectAndRunTestMethod", diff --git a/src/client/extension.ts b/src/client/extension.ts index f3ccbc7d5a31..4a468327b959 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -7,6 +7,7 @@ import { PythonDefinitionProvider } from './providers/definitionProvider'; import { PythonReferenceProvider } from './providers/referenceProvider'; import { PythonRenameProvider } from './providers/renameProvider'; import { PythonFormattingEditProvider } from './providers/formatProvider'; +import { ShebangCodeLensProvider } from './providers/shebangCodeLensProvider' import * as sortImports from './sortImports'; import { LintProvider } from './providers/lintProvider'; import { PythonSymbolProvider } from './providers/symbolProvider'; @@ -105,6 +106,7 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.languages.registerHoverProvider(PYTHON, new PythonHoverProvider(context, jediProx))); context.subscriptions.push(vscode.languages.registerReferenceProvider(PYTHON, new PythonReferenceProvider(context, jediProx))); context.subscriptions.push(vscode.languages.registerCompletionItemProvider(PYTHON, new PythonCompletionItemProvider(context, jediProx), '.')); + context.subscriptions.push(vscode.languages.registerCodeLensProvider(PYTHON, new ShebangCodeLensProvider())) const symbolProvider = new PythonSymbolProvider(context, jediProx); context.subscriptions.push(vscode.languages.registerDocumentSymbolProvider(PYTHON, symbolProvider)); diff --git a/src/client/providers/setInterpreterProvider.ts b/src/client/providers/setInterpreterProvider.ts index 6021a71be60f..62163420e306 100644 --- a/src/client/providers/setInterpreterProvider.ts +++ b/src/client/providers/setInterpreterProvider.ts @@ -4,6 +4,7 @@ import * as vscode from 'vscode'; import * as settings from './../common/configSettings'; import { InterpreterManager } from '../interpreter'; import { PythonInterpreter } from '../interpreter/contracts'; +import { ShebangCodeLensProvider } from './shebangCodeLensProvider'; interface PythonPathQuickPickItem extends vscode.QuickPickItem { @@ -14,6 +15,7 @@ export class SetInterpreterProvider implements vscode.Disposable { private disposables: vscode.Disposable[] = []; constructor(private interpreterManager: InterpreterManager) { this.disposables.push(vscode.commands.registerCommand("python.setInterpreter", this.setInterpreter.bind(this))); + this.disposables.push(vscode.commands.registerCommand("python.setShebangInterpreter", this.setShebangInterpreter.bind(this))); } public dispose() { this.disposables.forEach(disposable => disposable.dispose()); @@ -63,4 +65,11 @@ export class SetInterpreterProvider implements vscode.Disposable { } this.presentQuickPick(); } -} \ No newline at end of file + + private setShebangInterpreter() { + const shebang = ShebangCodeLensProvider.detectShebang(vscode.window.activeTextEditor.document); + if (shebang) { + this.interpreterManager.setPythonPath(shebang); + } + } +} diff --git a/src/client/providers/shebangCodeLensProvider.ts b/src/client/providers/shebangCodeLensProvider.ts new file mode 100644 index 000000000000..df3150c85e1c --- /dev/null +++ b/src/client/providers/shebangCodeLensProvider.ts @@ -0,0 +1,57 @@ +"use strict"; +import * as vscode from 'vscode' +import { TextDocument, CodeLens, CancellationToken } from 'vscode' + +export class ShebangCodeLensProvider implements vscode.CodeLensProvider { + private settings; + + // reload codeLenses on every configuration change. + onDidChangeCodeLenses: vscode.Event = vscode.workspace.onDidChangeConfiguration; + + public provideCodeLenses(document: TextDocument, token: CancellationToken): Thenable { + this.settings = vscode.workspace.getConfiguration('python'); + const codeLenses = this.createShebangCodeLens(document); + + return Promise.resolve(codeLenses); + } + + private createShebangCodeLens(document: TextDocument) { + const shebang = ShebangCodeLensProvider.detectShebang(document) + if (!shebang || shebang === this.settings.get('pythonPath')) { + // no shebang detected or interpreter is already set to shebang + return; + } + + // create CodeLens + const firstLine = document.lineAt(0); + const startOfShebang = new vscode.Position(0, 0); + const endOfShebang = new vscode.Position(0, firstLine.text.length - 1); + const shebangRange = new vscode.Range(startOfShebang, endOfShebang); + + const cmd : vscode.Command = { + command: 'python.setShebangInterpreter', + title: 'Set interpreter to shebang' + } + + const codeLenses = [(new CodeLens(shebangRange, cmd))]; + return codeLenses; + } + + public static detectShebang(document: TextDocument) { + let error = false; + + let firstLine = document.lineAt(0); + if (firstLine.isEmptyOrWhitespace) { + error = true; + } + + if (!error && "#!" === firstLine.text.substr(0, 2)) { + // Shebang detected + const shebang = firstLine.text.substr(2).trim(); + return shebang; + } + + return null; + } + +} From 74fb69ad6eae6f757ae509447ea81bd52ec28216 Mon Sep 17 00:00:00 2001 From: Philipp Nieting Date: Tue, 3 Oct 2017 21:56:37 +0200 Subject: [PATCH 2/5] added shebangCodeLens unittests --- .../shebangCodeLenseProvider.test.ts | 74 +++++++++++++++++++ src/test/pythonFiles/shebang/plain.py | 2 + src/test/pythonFiles/shebang/shebang.py | 3 + 3 files changed, 79 insertions(+) create mode 100644 src/test/providers/shebangCodeLenseProvider.test.ts create mode 100644 src/test/pythonFiles/shebang/plain.py create mode 100644 src/test/pythonFiles/shebang/shebang.py diff --git a/src/test/providers/shebangCodeLenseProvider.test.ts b/src/test/providers/shebangCodeLenseProvider.test.ts new file mode 100644 index 000000000000..153fb442edf1 --- /dev/null +++ b/src/test/providers/shebangCodeLenseProvider.test.ts @@ -0,0 +1,74 @@ +import * as assert from 'assert'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { ShebangCodeLensProvider } from '../../client/providers/shebangCodeLensProvider' + +import { initialize, IS_TRAVIS, closeActiveWindows } from '../initialize'; + +const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'shebang'); +const fileShebang = path.join(autoCompPath, 'shebang.py'); +const filePlain = path.join(autoCompPath, 'plain.py'); + +var settings = vscode.workspace.getConfiguration("python"); + +suite("Shebang detection", () => { + suiteSetup(async () => { + await initialize(); + }); + + teardown(() => closeActiveWindows()); + setup(() => { + settings = vscode.workspace.getConfiguration("python"); + }); + + test("Shebang available, CodeLens showing", done => { + settings.update("pythonPath", "python"); + + openFile(fileShebang).then(editor => { + let document = editor.document; + let codeLensProvider = new ShebangCodeLensProvider(); + + codeLensProvider.provideCodeLenses(document, null).then(lenses => { + assert.equal(lenses.length, 1, "No CodeLens available"); + let codeLens = lenses[0]; + + assert(codeLens.range.isSingleLine, 'Invalid CodeLens Range'); + assert.equal(codeLens.command.command, 'python.setShebangInterpreter'); + }); + }).then(done, done); + }); + + test("Shebang available, CodeLens hiding", done => { + settings.update("pythonPath", "/usr/bin/test"); + + openFile(fileShebang).then(editor => { + let document = editor.document; + let codeLensProvider = new ShebangCodeLensProvider(); + + codeLensProvider.provideCodeLenses(document, null).then(lenses => { + assert(!lenses, "CodeLens available although interpreters are equal"); + }); + }).then(done, done); + }); + + test("Shebang missing, CodeLens hiding", done => { + openFile(filePlain).then(editor => { + let document = editor.document; + let codeLensProvider = new ShebangCodeLensProvider(); + + codeLensProvider.provideCodeLenses(document, null).then(lenses => { + assert(!lenses, "CodeLens available although no shebang"); + }); + }).then(done, done); + }); + + function openFile(fileName) { + return vscode.workspace.openTextDocument(fileName).then(document => { + const textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + return editor; + }); + } +}); \ No newline at end of file diff --git a/src/test/pythonFiles/shebang/plain.py b/src/test/pythonFiles/shebang/plain.py new file mode 100644 index 000000000000..72f63e675db1 --- /dev/null +++ b/src/test/pythonFiles/shebang/plain.py @@ -0,0 +1,2 @@ + +print("dummy") diff --git a/src/test/pythonFiles/shebang/shebang.py b/src/test/pythonFiles/shebang/shebang.py new file mode 100644 index 000000000000..26611cb041df --- /dev/null +++ b/src/test/pythonFiles/shebang/shebang.py @@ -0,0 +1,3 @@ +#!/usr/bin/test + +print("dummy") From 347798f542344ede764ba6f6cdee7f21a57013b4 Mon Sep 17 00:00:00 2001 From: Philipp Nieting Date: Tue, 3 Oct 2017 22:58:30 +0200 Subject: [PATCH 3/5] fixes travis --- src/test/providers/shebangCodeLenseProvider.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/test/providers/shebangCodeLenseProvider.test.ts b/src/test/providers/shebangCodeLenseProvider.test.ts index 153fb442edf1..179b27560706 100644 --- a/src/test/providers/shebangCodeLenseProvider.test.ts +++ b/src/test/providers/shebangCodeLenseProvider.test.ts @@ -10,12 +10,17 @@ const fileShebang = path.join(autoCompPath, 'shebang.py'); const filePlain = path.join(autoCompPath, 'plain.py'); var settings = vscode.workspace.getConfiguration("python"); +const origPythonPath = settings.get("pythonPath"); suite("Shebang detection", () => { suiteSetup(async () => { await initialize(); }); + suiteTeardown(() => { + vscode.workspace.getConfiguration("python").update("pythonPath", origPythonPath); + }); + teardown(() => closeActiveWindows()); setup(() => { settings = vscode.workspace.getConfiguration("python"); From c394e4aff393a950f3af026ca40b1fae590fb290 Mon Sep 17 00:00:00 2001 From: Philipp Nieting Date: Tue, 3 Oct 2017 23:11:12 +0200 Subject: [PATCH 4/5] Changed CodeLens title --- src/client/providers/shebangCodeLensProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/providers/shebangCodeLensProvider.ts b/src/client/providers/shebangCodeLensProvider.ts index df3150c85e1c..9544976035d7 100644 --- a/src/client/providers/shebangCodeLensProvider.ts +++ b/src/client/providers/shebangCodeLensProvider.ts @@ -30,7 +30,7 @@ export class ShebangCodeLensProvider implements vscode.CodeLensProvider { const cmd : vscode.Command = { command: 'python.setShebangInterpreter', - title: 'Set interpreter to shebang' + title: 'Set as interpreter' } const codeLenses = [(new CodeLens(shebangRange, cmd))]; From 914d4882115c572a176d0b0148ee49603bea0688 Mon Sep 17 00:00:00 2001 From: Philipp Nieting Date: Wed, 4 Oct 2017 12:59:49 +0200 Subject: [PATCH 5/5] make use of async and await --- .../shebangCodeLenseProvider.test.ts | 72 ++++++++----------- 1 file changed, 31 insertions(+), 41 deletions(-) diff --git a/src/test/providers/shebangCodeLenseProvider.test.ts b/src/test/providers/shebangCodeLenseProvider.test.ts index 179b27560706..a3af897f4cd6 100644 --- a/src/test/providers/shebangCodeLenseProvider.test.ts +++ b/src/test/providers/shebangCodeLenseProvider.test.ts @@ -17,8 +17,8 @@ suite("Shebang detection", () => { await initialize(); }); - suiteTeardown(() => { - vscode.workspace.getConfiguration("python").update("pythonPath", origPythonPath); + suiteTeardown(async () => { + await vscode.workspace.getConfiguration("python").update("pythonPath", origPythonPath); }); teardown(() => closeActiveWindows()); @@ -26,54 +26,44 @@ suite("Shebang detection", () => { settings = vscode.workspace.getConfiguration("python"); }); - test("Shebang available, CodeLens showing", done => { - settings.update("pythonPath", "python"); + test("Shebang available, CodeLens showing", async () => { + await settings.update("pythonPath", "python"); + const editor = await openFile(fileShebang); + const codeLenses = await setupCodeLens(editor); - openFile(fileShebang).then(editor => { - let document = editor.document; - let codeLensProvider = new ShebangCodeLensProvider(); - - codeLensProvider.provideCodeLenses(document, null).then(lenses => { - assert.equal(lenses.length, 1, "No CodeLens available"); - let codeLens = lenses[0]; + assert.equal(codeLenses.length, 1, "No CodeLens available"); + let codeLens = codeLenses[0]; + assert(codeLens.range.isSingleLine, 'Invalid CodeLens Range'); + assert.equal(codeLens.command.command, 'python.setShebangInterpreter'); - assert(codeLens.range.isSingleLine, 'Invalid CodeLens Range'); - assert.equal(codeLens.command.command, 'python.setShebangInterpreter'); - }); - }).then(done, done); }); - test("Shebang available, CodeLens hiding", done => { - settings.update("pythonPath", "/usr/bin/test"); + test("Shebang available, CodeLens hiding", async () => { + await settings.update("pythonPath", "/usr/bin/test"); + const editor = await openFile(fileShebang); + const codeLenses = await setupCodeLens(editor); + assert(!codeLenses, "CodeLens available although interpreters are equal"); - openFile(fileShebang).then(editor => { - let document = editor.document; - let codeLensProvider = new ShebangCodeLensProvider(); - - codeLensProvider.provideCodeLenses(document, null).then(lenses => { - assert(!lenses, "CodeLens available although interpreters are equal"); - }); - }).then(done, done); }); - test("Shebang missing, CodeLens hiding", done => { - openFile(filePlain).then(editor => { - let document = editor.document; - let codeLensProvider = new ShebangCodeLensProvider(); + test("Shebang missing, CodeLens hiding", async () => { + const editor = await openFile(filePlain); + const codeLenses = await setupCodeLens(editor); + assert(!codeLenses, "CodeLens available although no shebang"); - codeLensProvider.provideCodeLenses(document, null).then(lenses => { - assert(!lenses, "CodeLens available although no shebang"); - }); - }).then(done, done); }); - function openFile(fileName) { - return vscode.workspace.openTextDocument(fileName).then(document => { - const textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - return editor; - }); + async function openFile(fileName: string) { + const document = await vscode.workspace.openTextDocument(fileName); + const editor = await vscode.window.showTextDocument(document); + assert(vscode.window.activeTextEditor, 'No active editor'); + return editor; + } + + async function setupCodeLens(editor: vscode.TextEditor) { + const document = editor.document; + const codeLensProvider = new ShebangCodeLensProvider(); + const codeLenses = await codeLensProvider.provideCodeLenses(document, null); + return codeLenses; } }); \ No newline at end of file