Skip to content

Commit 14ef606

Browse files
KavakuoDonJayamanne
authored andcommitted
CodeLens for Shebangs (#1267)
* CodeLense for Shebangs * added shebangCodeLens unittests * fixes travis * Changed CodeLens title * make use of async and await
1 parent ba2ccab commit 14ef606

File tree

7 files changed

+144
-1
lines changed

7 files changed

+144
-1
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"onCommand:python.runtests",
5252
"onCommand:python.debugtests",
5353
"onCommand:python.setInterpreter",
54+
"onCommand:python.setShebangInterpreter",
5455
"onCommand:python.viewTestUI",
5556
"onCommand:python.viewTestOutput",
5657
"onCommand:python.selectAndRunTestMethod",

src/client/extension.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { PythonDefinitionProvider } from './providers/definitionProvider';
77
import { PythonReferenceProvider } from './providers/referenceProvider';
88
import { PythonRenameProvider } from './providers/renameProvider';
99
import { PythonFormattingEditProvider } from './providers/formatProvider';
10+
import { ShebangCodeLensProvider } from './providers/shebangCodeLensProvider'
1011
import * as sortImports from './sortImports';
1112
import { LintProvider } from './providers/lintProvider';
1213
import { PythonSymbolProvider } from './providers/symbolProvider';
@@ -105,6 +106,7 @@ export async function activate(context: vscode.ExtensionContext) {
105106
context.subscriptions.push(vscode.languages.registerHoverProvider(PYTHON, new PythonHoverProvider(context, jediProx)));
106107
context.subscriptions.push(vscode.languages.registerReferenceProvider(PYTHON, new PythonReferenceProvider(context, jediProx)));
107108
context.subscriptions.push(vscode.languages.registerCompletionItemProvider(PYTHON, new PythonCompletionItemProvider(context, jediProx), '.'));
109+
context.subscriptions.push(vscode.languages.registerCodeLensProvider(PYTHON, new ShebangCodeLensProvider()))
108110

109111
const symbolProvider = new PythonSymbolProvider(context, jediProx);
110112
context.subscriptions.push(vscode.languages.registerDocumentSymbolProvider(PYTHON, symbolProvider));

src/client/providers/setInterpreterProvider.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as vscode from 'vscode';
44
import * as settings from './../common/configSettings';
55
import { InterpreterManager } from '../interpreter';
66
import { PythonInterpreter } from '../interpreter/contracts';
7+
import { ShebangCodeLensProvider } from './shebangCodeLensProvider';
78

89

910
interface PythonPathQuickPickItem extends vscode.QuickPickItem {
@@ -14,6 +15,7 @@ export class SetInterpreterProvider implements vscode.Disposable {
1415
private disposables: vscode.Disposable[] = [];
1516
constructor(private interpreterManager: InterpreterManager) {
1617
this.disposables.push(vscode.commands.registerCommand("python.setInterpreter", this.setInterpreter.bind(this)));
18+
this.disposables.push(vscode.commands.registerCommand("python.setShebangInterpreter", this.setShebangInterpreter.bind(this)));
1719
}
1820
public dispose() {
1921
this.disposables.forEach(disposable => disposable.dispose());
@@ -60,4 +62,11 @@ export class SetInterpreterProvider implements vscode.Disposable {
6062
private setInterpreter() {
6163
this.presentQuickPick();
6264
}
63-
}
65+
66+
private setShebangInterpreter() {
67+
const shebang = ShebangCodeLensProvider.detectShebang(vscode.window.activeTextEditor.document);
68+
if (shebang) {
69+
this.interpreterManager.setPythonPath(shebang);
70+
}
71+
}
72+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"use strict";
2+
import * as vscode from 'vscode'
3+
import { TextDocument, CodeLens, CancellationToken } from 'vscode'
4+
5+
export class ShebangCodeLensProvider implements vscode.CodeLensProvider {
6+
private settings;
7+
8+
// reload codeLenses on every configuration change.
9+
onDidChangeCodeLenses: vscode.Event<void> = vscode.workspace.onDidChangeConfiguration;
10+
11+
public provideCodeLenses(document: TextDocument, token: CancellationToken): Thenable<CodeLens[]> {
12+
this.settings = vscode.workspace.getConfiguration('python');
13+
const codeLenses = this.createShebangCodeLens(document);
14+
15+
return Promise.resolve(codeLenses);
16+
}
17+
18+
private createShebangCodeLens(document: TextDocument) {
19+
const shebang = ShebangCodeLensProvider.detectShebang(document)
20+
if (!shebang || shebang === this.settings.get('pythonPath')) {
21+
// no shebang detected or interpreter is already set to shebang
22+
return;
23+
}
24+
25+
// create CodeLens
26+
const firstLine = document.lineAt(0);
27+
const startOfShebang = new vscode.Position(0, 0);
28+
const endOfShebang = new vscode.Position(0, firstLine.text.length - 1);
29+
const shebangRange = new vscode.Range(startOfShebang, endOfShebang);
30+
31+
const cmd : vscode.Command = {
32+
command: 'python.setShebangInterpreter',
33+
title: 'Set as interpreter'
34+
}
35+
36+
const codeLenses = [(new CodeLens(shebangRange, cmd))];
37+
return codeLenses;
38+
}
39+
40+
public static detectShebang(document: TextDocument) {
41+
let error = false;
42+
43+
let firstLine = document.lineAt(0);
44+
if (firstLine.isEmptyOrWhitespace) {
45+
error = true;
46+
}
47+
48+
if (!error && "#!" === firstLine.text.substr(0, 2)) {
49+
// Shebang detected
50+
const shebang = firstLine.text.substr(2).trim();
51+
return shebang;
52+
}
53+
54+
return null;
55+
}
56+
57+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import * as assert from 'assert';
2+
import * as path from 'path';
3+
import * as vscode from 'vscode';
4+
import { ShebangCodeLensProvider } from '../../client/providers/shebangCodeLensProvider'
5+
6+
import { initialize, IS_TRAVIS, closeActiveWindows } from '../initialize';
7+
8+
const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'shebang');
9+
const fileShebang = path.join(autoCompPath, 'shebang.py');
10+
const filePlain = path.join(autoCompPath, 'plain.py');
11+
12+
var settings = vscode.workspace.getConfiguration("python");
13+
const origPythonPath = settings.get("pythonPath");
14+
15+
suite("Shebang detection", () => {
16+
suiteSetup(async () => {
17+
await initialize();
18+
});
19+
20+
suiteTeardown(async () => {
21+
await vscode.workspace.getConfiguration("python").update("pythonPath", origPythonPath);
22+
});
23+
24+
teardown(() => closeActiveWindows());
25+
setup(() => {
26+
settings = vscode.workspace.getConfiguration("python");
27+
});
28+
29+
test("Shebang available, CodeLens showing", async () => {
30+
await settings.update("pythonPath", "python");
31+
const editor = await openFile(fileShebang);
32+
const codeLenses = await setupCodeLens(editor);
33+
34+
assert.equal(codeLenses.length, 1, "No CodeLens available");
35+
let codeLens = codeLenses[0];
36+
assert(codeLens.range.isSingleLine, 'Invalid CodeLens Range');
37+
assert.equal(codeLens.command.command, 'python.setShebangInterpreter');
38+
39+
});
40+
41+
test("Shebang available, CodeLens hiding", async () => {
42+
await settings.update("pythonPath", "/usr/bin/test");
43+
const editor = await openFile(fileShebang);
44+
const codeLenses = await setupCodeLens(editor);
45+
assert(!codeLenses, "CodeLens available although interpreters are equal");
46+
47+
});
48+
49+
test("Shebang missing, CodeLens hiding", async () => {
50+
const editor = await openFile(filePlain);
51+
const codeLenses = await setupCodeLens(editor);
52+
assert(!codeLenses, "CodeLens available although no shebang");
53+
54+
});
55+
56+
async function openFile(fileName: string) {
57+
const document = await vscode.workspace.openTextDocument(fileName);
58+
const editor = await vscode.window.showTextDocument(document);
59+
assert(vscode.window.activeTextEditor, 'No active editor');
60+
return editor;
61+
}
62+
63+
async function setupCodeLens(editor: vscode.TextEditor) {
64+
const document = editor.document;
65+
const codeLensProvider = new ShebangCodeLensProvider();
66+
const codeLenses = await codeLensProvider.provideCodeLenses(document, null);
67+
return codeLenses;
68+
}
69+
});
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
print("dummy")
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/usr/bin/test
2+
3+
print("dummy")

0 commit comments

Comments
 (0)