diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e031951..e2b50874 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +## [1.4.0] +### Added +- Notebook mode for R (Thanks to @chlohal) +- Improved hiding of running indicator (Thanks to @chlohal and @ZackYJz) + +### Changed +- Fix problem with the output boxes showing up (Thanks to @qiaogaojian) + ## [1.3.0] ### Added - WSL support (thanks to @clohal) diff --git a/README.md b/README.md index e00e848d..622d338d 100644 --- a/README.md +++ b/README.md @@ -441,7 +441,7 @@ undefined 3 ``` -To manage the open runtimes for Notebook Mode, you can use the `Open Code Runtime Management` command in the command palette. From this sidebar window, you can stop kernels. +To manage the open runtimes for Notebook Mode, you can use the `Open Code Runtime Management` command in the command palette. From this sidebar window, you can stop kernels. **Note: force-stopping requires `taskkill` on Windows and `pkill` on Unix. 99% of systems should have these preinstalled: if yours doesn't, please [file an issue](https://github.com/twibiral/obsidian-execute-code/issues/new/choose)** ## Style Settings diff --git a/manifest.json b/manifest.json index 590d612f..f6de5ff2 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "execute-code", "name": "Execute Code", - "version": "1.3.0", + "version": "1.4.0", "minAppVersion": "0.12.0", "description": "Allows to execute code snippets within a note.", "author": "twibiral", diff --git a/package.json b/package.json index 2b0c8e1f..a68b97cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "execute-code", - "version": "1.3.0", + "version": "1.4.0", "description": "This is a sample plugin for Obsidian (https://obsidian.md)", "main": "src/main.js", "scripts": { diff --git a/src/ExecutorContainer.ts b/src/ExecutorContainer.ts index e06edb67..ea9ee0d6 100644 --- a/src/ExecutorContainer.ts +++ b/src/ExecutorContainer.ts @@ -6,10 +6,12 @@ import PrologExecutor from "./executors/PrologExecutor"; import PythonExecutor from "./executors/python/PythonExecutor"; import CppExecutor from './executors/CppExecutor'; import ExecuteCodePlugin, {LanguageId} from "./main"; +import RExecutor from "./executors/RExecutor.js"; const interactiveExecutors: Partial> = { "js": NodeJSExecutor, - "python": PythonExecutor + "python": PythonExecutor, + "r": RExecutor }; const nonInteractiveExecutors: Partial> = { diff --git a/src/Outputter.ts b/src/Outputter.ts index ad44e0c2..de1acf98 100644 --- a/src/Outputter.ts +++ b/src/Outputter.ts @@ -355,6 +355,8 @@ export class Outputter extends EventEmitter { * @see {@link clear()} */ private makeOutputVisible() { + this.closeInput(); + if (!this.clearButton) this.addClearButton(); if (!this.outputElement) this.addOutputElement(); diff --git a/src/executors/NodeJSExecutor.ts b/src/executors/NodeJSExecutor.ts index 6f0b8e54..76ffcd95 100644 --- a/src/executors/NodeJSExecutor.ts +++ b/src/executors/NodeJSExecutor.ts @@ -17,20 +17,6 @@ export default class NodeJSExecutor extends ReplExecutor { super(settings, settings.nodePath, args, file, "js"); } - /** - * Close the runtime. - * @returns A promise that resolves once the runtime is fully closed - */ - stop(): Promise { - return new Promise((resolve, reject) => { - this.process.on("close", () => { - resolve(); - }); - this.process.kill(); - this.process = null; - }); - } - /** * Writes a single newline to ensure that the stdin is set up correctly. */ diff --git a/src/executors/RExecutor.ts b/src/executors/RExecutor.ts new file mode 100644 index 00000000..6a694294 --- /dev/null +++ b/src/executors/RExecutor.ts @@ -0,0 +1,59 @@ +import {ChildProcessWithoutNullStreams, spawn} from "child_process"; +import {Outputter} from "src/Outputter"; +import {ExecutorSettings} from "src/settings/Settings"; +import AsyncExecutor from "./AsyncExecutor"; +import ReplExecutor from "./ReplExecutor.js"; + + +export default class RExecutor extends ReplExecutor { + + process: ChildProcessWithoutNullStreams + + constructor(settings: ExecutorSettings, file: string) { + //use empty array for empty string, instead of [""] + const args = settings.RArgs ? settings.RArgs.split(" ") : []; + + let conArgName = `notebook_connection_${Math.random().toString(16).substring(2)}`; + + // This is the R repl. + // It's coded by itself because Rscript has no REPL, and adding an additional dep on R would be lazy. + //It doesn't handle printing by itself because of the need to print the sigil, so + // it's really more of a REL. + args.unshift(`-e`, + /*R*/ + `${conArgName}=file("stdin", "r"); while(1) { eval(parse(text=tail(readLines(con = ${conArgName}, n=1)))) }` + ) + + + super(settings, settings.RPath, args, file, "r"); + } + + /** + * Writes a single newline to ensure that the stdin is set up correctly. + */ + async setup() { + console.log("setup"); + //this.process.stdin.write("\n"); + } + + wrapCode(code: string, finishSigil: string): string { + return `tryCatch({ + cat(sprintf("%s", + eval(parse(text = ${JSON.stringify(code)} )) + )) + }, + error = function(e){ + cat(sprintf("%s", e), file=stderr()) + }, + finally = { + cat(${JSON.stringify(finishSigil)}); + flush.console() + })`.replace(/\r?\n/g, "") + + "\n"; + } + + removePrompts(output: string, source: "stdout" | "stderr"): string { + return output; + } + +} diff --git a/src/executors/ReplExecutor.ts b/src/executors/ReplExecutor.ts index 3abb7df2..4051d5bc 100644 --- a/src/executors/ReplExecutor.ts +++ b/src/executors/ReplExecutor.ts @@ -4,6 +4,7 @@ import { LanguageId } from "../main.js"; import { Outputter } from "../Outputter.js"; import { ExecutorSettings } from "../settings/Settings.js"; import AsyncExecutor from "./AsyncExecutor.js"; +import killWithChildren from "./killWithChildren.js"; export default abstract class ReplExecutor extends AsyncExecutor { process: ChildProcessWithoutNullStreams; @@ -103,8 +104,9 @@ export default abstract class ReplExecutor extends AsyncExecutor { return new Promise((resolve, reject) => { this.process.on("close", () => { resolve(); - }); - this.process.kill(); + }); + + killWithChildren(this.process.pid); this.process = null; }); } diff --git a/src/executors/killWithChildren.ts b/src/executors/killWithChildren.ts new file mode 100644 index 00000000..a0171a91 --- /dev/null +++ b/src/executors/killWithChildren.ts @@ -0,0 +1,10 @@ +import { execSync } from "child_process" + +export default (pid: number) => { + if(process.platform === "win32") { + execSync(`taskkill /pid ${pid} /T /F`) + } else { + execSync(`pkill -P ${pid}`) + process.kill(pid); + } +} \ No newline at end of file diff --git a/src/executors/python/PythonExecutor.ts b/src/executors/python/PythonExecutor.ts index 671cd3e9..b8dd102b 100644 --- a/src/executors/python/PythonExecutor.ts +++ b/src/executors/python/PythonExecutor.ts @@ -38,19 +38,6 @@ export default class PythonExecutor extends ReplExecutor { this.globalsDictionaryName = `__globals_${Math.random().toString().substring(2)}_${Date.now()}`; } - /** - * Close the runtime. - * @returns A promise that resolves once the runtime is fully closed - */ - stop(): Promise { - return new Promise((resolve, reject) => { - this.process.on("close", () => { - resolve(); - }); - this.process.kill(); - this.process = null; - }); - } /** * Swallows and does not output the "Welcome to Python v..." message that shows at startup. diff --git a/src/settings/per-lang/makeRSettings.ts b/src/settings/per-lang/makeRSettings.ts index f00c318e..7f0788b5 100644 --- a/src/settings/per-lang/makeRSettings.ts +++ b/src/settings/per-lang/makeRSettings.ts @@ -32,5 +32,13 @@ export default (tab: SettingsTab, containerEl: HTMLElement) => { console.log('R args set to: ' + value); await tab.plugin.saveSettings(); })); + new Setting(containerEl) + .setName("Run R blocks in Notebook Mode") + .addToggle((toggle) => toggle + .setValue(tab.plugin.settings.rInteractive) + .onChange(async (value) => { + tab.plugin.settings.rInteractive = value; + await tab.plugin.saveSettings(); + })); tab.makeInjectSetting(containerEl, "r"); } \ No newline at end of file diff --git a/versions.json b/versions.json index 7ddfc3b0..5e268922 100644 --- a/versions.json +++ b/versions.json @@ -32,5 +32,6 @@ "1.1.0": "0.12.0", "1.1.1": "0.12.0", "1.2.0": "0.12.0", - "1.3.0": "0.12.0" + "1.3.0": "0.12.0", + "1.4.0": "0.12.0" } \ No newline at end of file