arepl
Version:
real-time python scratchpad
156 lines (123 loc) • 6.21 kB
text/typescript
'use strict'
import {PythonEvaluator} from "arepl-backend"
import {EOL} from "os"
import * as vscode from "vscode"
import { PreviewContainer } from "./previewContainer";
import Reporter from "./telemetry"
import {ToAREPLLogic} from "./toAREPLLogic"
// This class initializes the previewmanager based on extension type and manages all the subscriptions
export default class PreviewManager {
reporter: Reporter;
disposable: vscode.Disposable;
pythonEditor: vscode.TextDocument;
pythonEvaluator: PythonEvaluator;
status: vscode.StatusBarItem;
settings: vscode.WorkspaceConfiguration;
toAREPLLogic: ToAREPLLogic
previewContainer: PreviewContainer
subscriptions: vscode.Disposable[] = [];
constructor(context: vscode.ExtensionContext) {
this.settings = vscode.workspace.getConfiguration("AREPL");
this.pythonEditor = vscode.window.activeTextEditor.document;
this.reporter = new Reporter(this.settings.get<boolean>("telemetry"))
this.status = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
this.status.text = "Running python..."
this.status.tooltip = "AREPL is currently running your python file. Close the AREPL preview to stop"
this.previewContainer = new PreviewContainer(this.reporter, context)
}
async startArepl(){
this.subscriptions.push(this.previewContainer.register())
this.startAndBindPython()
if(this.pythonEditor.isUntitled && this.pythonEditor.getText() == "") {
await this.insertDefaultImports(vscode.window.activeTextEditor)
// waiting for this to complete so i dont accidentily trigger
// the edit doc handler when i insert imports
}
this.subscribeHandlersToDoc()
}
runArepl(){
this.onAnyDocChange(vscode.window.activeTextEditor.document)
}
dispose() {
if(this.pythonEvaluator.pyshell != null && this.pythonEvaluator.pyshell.childProcess != null){
this.pythonEvaluator.stop()
}
this.disposable = vscode.Disposable.from(...this.subscriptions);
this.disposable.dispose();
this.status.dispose();
this.reporter.sendFinishedEvent(this.settings)
this.reporter.dispose();
}
/**
* starts AREPL python backend and binds print&result output to the handlers
*/
private startAndBindPython(){
const pythonPath = this.settings.get<string>("pythonPath")
const pythonOptions = this.settings.get<string[]>("pythonOptions")
this.pythonEvaluator = new PythonEvaluator(pythonPath, pythonOptions)
this.pythonEvaluator.startPython()
this.pythonEvaluator.pyshell.childProcess.on("error", err => {
const error: any = err; // typescript complains about type for some reason so defining to any
this.previewContainer.handleSpawnError(error.path, error.spawnargs[0], error.stack);
this.reporter.sendError("error starting python: " + error.path)
})
this.toAREPLLogic = new ToAREPLLogic(this.pythonEvaluator, this.previewContainer)
// binding this to the class so it doesn't get overwritten by PythonEvaluator
this.pythonEvaluator.onPrint = this.previewContainer.handlePrint.bind(this.previewContainer)
this.pythonEvaluator.onResult = result => {
this.status.hide()
// @ts-ignore todo: fix typing in backend
this.previewContainer.handleResult(result)
}
}
/**
* binds various funcs to activate upon edit of document / switching of active doc / etc...
*/
private subscribeHandlersToDoc(){
const debounce = this.settings.get<number>("delay");
const restartExtraDebounce = this.settings.get<number>("restartDelay");
if(this.settings.get<boolean>("skipLandingPage")){
this.onAnyDocChange(this.pythonEditor);
}
if(this.settings.get<string>("whenToExecute") == "onSave"){
vscode.workspace.onDidSaveTextDocument((e) => {
this.onAnyDocChange(e)
}, this, this.subscriptions)
}
else if(this.settings.get<string>("whenToExecute") == "afterDelay"){
vscode.workspace.onDidChangeTextDocument((e) => {
const delay = this.toAREPLLogic.restartMode ? debounce + restartExtraDebounce : debounce
this.pythonEvaluator.debounce(this.onAnyDocChange.bind(this, e.document), delay)
}, this, this.subscriptions)
}
else {} //third option is onKeybinding in which case user manually invokes arepl
vscode.workspace.onDidCloseTextDocument((e) => {
if(e == this.pythonEditor || e.uri.scheme == this.previewContainer.scheme) this.dispose()
}, this, this.subscriptions)
}
private insertDefaultImports(editor: vscode.TextEditor){
return editor.edit((editBuilder) => {
let imports = this.settings.get<string[]>("defaultImports")
imports = imports.filter(i => i.trim() != "")
if(imports.length == 0) return
imports = imports.map(i => {
const words = i.split(" ")
// python import syntax: "import library" or "from library import method"
// so if user didnt specify import we will do that for them :)
if(words[0] != "import" && words[0] != "from" && words[0].length > 0){
i = "import " + i
}
return i
})
editBuilder.insert(new vscode.Position(0, 0), imports.join(EOL) + EOL)
})
}
private onAnyDocChange(event: vscode.TextDocument){
if(event == this .pythonEditor){
this.status.show();
const text = event.getText()
const filePath = this.pythonEditor.isUntitled ? "" : this.pythonEditor.fileName
this.toAREPLLogic.onUserInput(text, filePath)
}
}
}