UNPKG

arepl

Version:
253 lines (197 loc) 9.08 kB
"use strict" import * as path from "path"; import * as vscode from "vscode" import {Limit} from "./throttle" import Utilities from "./utilities" /** * very simple read-only html content * https://code.visualstudio.com/docs/extensionAPI/vscode-api#_a-nametextdocumentcontentprovider */ export default class PythonPreview implements vscode.TextDocumentContentProvider { static readonly scheme = "pythonPreview" static readonly PREVIEW_URI = PythonPreview.scheme + "://authority/preview" public throttledUpdate: () => void private _onDidChange: vscode.EventEmitter<vscode.Uri>; private settings: vscode.WorkspaceConfiguration; private lastTime: number = 999999999; private html; private readonly landingPage = ` <br> <p style="font-size:14px">Start typing or make a change and your code will be evaluated.</p> <p style="font-size:14px">⚠ <b style="color:red">WARNING:</b> code is evaluated WHILE YOU TYPE - don't mess around with your file system! ⚠</p> <p>evaluation while you type can be turned off or adjusted in the settings</p> <br> <h3>New Features with version 1.3!</h3> Don't like real-time execution? In the settings you can turn on execute on save or execute on keybinding. <br> <h3>Examples</h3> <h4>Simple List</h4> <code style="white-space:pre-wrap"> x = [1,2,3] y = [num*2 for num in x] print(y) </code> <h4>Dumping</h4> <code style="white-space:pre-wrap"> from arepldump import dump def milesToKilometers(miles): kilometers = miles*1.60934 dump() # dumps all the vars in your function # or dump when function is called for a second time dump(None,1) milesToKilometers(2*2) milesToKilometers(3*3) for char in ['a','b','c']: dump(char,2) # dump a var at a specific iteration a=1 dump(a) # dump specific vars at any point in your program a=2 </code> <h4>Turtle</h4> <code style="white-space:pre-wrap"> import turtle # window in right hand side of screen turtle.setup(500,500,-1,0) turtle.forward(100) turtle.left(90) </code> <h4>Web call</h4> <code style="white-space:pre-wrap"> import requests import datetime as dt r = requests.get("https://api.github.com") #$save # #$save saves state so request is not re-executed when modifying below now = dt.datetime.now() if r.status_code == 200: print("API up at " + str(now)) </code>`; private readonly footer = `<br><br> <div id="footer"> <p style="margin:0px;"> report an <a href="https://github.com/almenon/arepl-vscode/issues">issue</a> | ⭐ <a href="https://marketplace.visualstudio.com/items?itemName=almenon.arepl#review-details">rate me</a> ⭐ | talk on <a href="https://gitter.im/arepl/lobby">gitter</a> | <a href="https://twitter.com/intent/tweet?button_hashtag=arepl" id="twitterButton"> <i id="twitterIcon"></i>Tweet #arepl</a> </p> </div>` private css: string private jsonRendererScript: string; private errorContainer = "" private jsonRendererCode = `<div id="results"></div>`; private emptyPrint = `<br><b>Print Output:</b><div id="print"></div>` private printContainer = this.emptyPrint; private timeContainer = "" constructor(private context: vscode.ExtensionContext) { this._onDidChange = new vscode.EventEmitter<vscode.Uri>(); this.css = `<link rel="stylesheet" type="text/css" href="${this.getMediaPath("pythonPreview.css")}">` this.jsonRendererScript = `<script src="${this.getMediaPath("jsonRenderer.js")}"></script>` this.settings = vscode.workspace.getConfiguration("AREPL"); this.html = this.landingPage; // refreshing html too much can freeze vscode... lets avoid that const l = new Limit() this.throttledUpdate = l.throttledUpdate(this.updateContent, 50) } provideTextDocumentContent(uri: vscode.Uri): string { return this.html; }; public updateVars(vars: object){ let userVarsCode = `userVars = ${JSON.stringify(vars)};` // escape end script tag or else the content will escape its container and WREAK HAVOC userVarsCode = userVarsCode.replace(/<\/script>/g, "<\\/script>") this.jsonRendererCode = `<script> window.onload = function(){ ${userVarsCode} let jsonRenderer = renderjson.set_icons('+', '-') // default icons look a bit wierd, overriding .set_show_to_level(${this.settings.get("show_to_level")}) .set_max_string_length(${this.settings.get("max_string_length")}); document.getElementById("results").appendChild(jsonRenderer(userVars)); } </script>` } public updateTime(time: number){ let color: "green"|"red"; time = Math.floor(time) // we dont care about anything smaller than ms if(time > this.lastTime) color = "red" else color = "green" this.lastTime = time; this.timeContainer = `<p style="position:fixed;left:90%;top:90%;color:${color};">${time} ms</p>`; } /** * @param refresh if true updates page immediately. otherwise error will show up whenever updateContent is called */ public updateError(err: string, refresh=false){ // escape the <module> err = Utilities.escapeHtml(err) err = this.makeErrorGoogleable(err) this.errorContainer = `<div id="error">${err}</div>` if(refresh) this.throttledUpdate() } public handlePrint(printResults: string){ // escape any accidental html printResults = Utilities.escapeHtml(printResults); this.printContainer = `<br><b>Print Output:</b><div id="print">${printResults}</div>` this.throttledUpdate(); } clearPrint(){ this.printContainer = this.emptyPrint } public handleSpawnError(pythonCommand: string, pythonPath: string, err: string){ let errMsg = `Error in the AREPL extension!\nWhile running python ${pythonCommand} ${pythonPath} we got ${err}` if(err.includes("ENOENT")){ errMsg = errMsg + "\n\nAre you sure you have installed python 3 and it is in your PATH?" } this.updateError(errMsg) this.throttledUpdate() } private makeErrorGoogleable(err: string){ if(err && err.trim().length > 0){ let errLines = err.split("\n") // exception usually on last line so start from bottom for(let i=errLines.length-1; i>=0; i--){ // most exceptions follow format ERROR: explanation // ex: json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) // so we can identify them by a single word at start followed by colon const errRegex = /(^[\w\.]+): / if(errLines[i].match(errRegex)){ const googleLink = "https://www.google.com/search?q=python " errLines[i] = errLines[i].link(googleLink + errLines[i]) } } return errLines.join("\n") } else return err } private update() { this._onDidChange.fire(vscode.Uri.parse(PythonPreview.PREVIEW_URI)); } get onDidChange(): vscode.Event<vscode.Uri> { return this._onDidChange.event; } private getMediaPath(mediaFile: string): string { // stolen from https://github.com/Microsoft/vscode/tree/master/extensions/markdown return vscode.Uri.file(this.context.asAbsolutePath(path.join('media', mediaFile))).toString(); } private updateContent(){ const printPlacement = this.settings.get<string>("printResultPlacement") const showFooter = this.settings.get<boolean>("showFooter") // todo: handle different themes. check body class: https://code.visualstudio.com/updates/June_2016 this.html = `<head> ${this.css} ${this.jsonRendererScript} ${this.jsonRendererCode} </head> <body> ${this.errorContainer} ${printPlacement == "bottom" ? '<div id="results"></div>' + this.printContainer : this.printContainer + '<div id="results"></div>'} ${this.timeContainer} ${showFooter ? this.footer : ""} </body>` // issue #1: need to make sure html is new each time or wierd crap happens this.html += `<div id="${Math.random()}" style="display:none"></div>` this.update(); } }