diff --git a/README.md b/README.md index ab309ce2..f41633a8 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,11 @@ The result is shown only after the execution is finished. It is not possible to
-The following [languages are supported](#supported-programming-languages-): C, C++, CSharp, Dart, F#, Golang, Groovy, Haskell, Java, JavaScript, Kotlin, Lean, Lua, Maxima, OCaml, Octave, Prolog, Python, R, Racket, Ruby, Rust, Scala, Shell (including Batch & Powershell), SQL, TypeScript, Wolfram Mathematica, Zig. +The following [languages are supported](#supported-programming-languages-): C, C++, CSharp, Dart, F#, Golang, Groovy, Haskell, Java, JavaScript, Julia, Kotlin, Lean, Lua, Maxima, OCaml, Octave, Prolog, Python, R, Racket, Ruby, Rust, Scala, Shell (including Batch & Powershell), SQL, TypeScript, Wolfram Mathematica, Zig. If you are new to MarkDown or Obsidian.md, you can go to the [Quickstart Guide](#quickstart-guide-) or take a look in to [some blogs and videos that feature this plugin](#featured-in) -Python, R, and Octave support embedded plots. All languages support ["magic" commands](#magic-commands-) that help you to access paths in obsidian or show images in your notes. +Python, R, Julia, and Octave support embedded plots. All languages support ["magic" commands](#magic-commands-) that help you to access paths in obsidian or show images in your notes. You can create code blocks that are executed before or after each code block of the same language and define [global code injections](#global-code-injection-and-reusing-code-blocks-). @@ -137,6 +137,38 @@ plot(x, y, type="l") ``` +
+Julia + +- Requirements: Julia is installed and the correct path is set in the settings. Recommend using [juliaup](https://julialang.org/install/). +```julia +println("Hello, world!") +``` +- By default, Julia runs in Notebook Mode. You can turn this off in the settings. +- Plots with Plots.jl are embedded in the note by default. You can turn this off in the settings. Note that plots are only embedded when `display(current())` is called. +```julia +using Plots +x = range(0, 10, length=100); y = sin.(x); plot(x, y) +display(current()) +``` +- Note: the `ans` output from the last expression is always printed; this is a limitation of interacting with Julia over `stdin`. You can put expressions on the same line separated by semicolons (though the last expression's result will be printed unless you make it `nothing`), and you can also enclose the statements in a `begin ... end` block: +```julia +using Plots +begin + x = range(0, 10, length=100) + y = sin.(x) + plot(x, y) +end +display(current()) +``` +- Note: you will have to manually load the Plots package the first time (or any other package you use). You may want to put these in a separate block that you only execute on the first use: +```julia +import Pkg +Pkg.add("Plots") +``` + +
+
C++ @@ -757,7 +789,7 @@ print('should not run any pre blocks or global injects') ### Notebook Mode -A few languages (currently JS and Python) support *Notebook Mode*. If a language is using Notebook Mode (configurable in Settings), then all code blocks in a given file will execute in the same environment. +A few languages (currently JS, Python, and Julia) support *Notebook Mode*. If a language is using Notebook Mode (configurable in Settings), then all code blocks in a given file will execute in the same environment. Variables functions, etc. defined in one code block will be available in other code blocks. Code blocks are executed on demand; the order of code blocks in the file does not affect the order in which they are executed: diff --git a/package-lock.json b/package-lock.json index 98f37e22..5e7eb31b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,12 @@ { "name": "execute-code", + "version": "2.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "execute-code", - "version": "2.0.0", + "version": "2.1.2", "license": "MIT", "dependencies": { "g": "^2.0.1", @@ -4242,6 +4243,5 @@ "url": "https://github.com/sponsors/sindresorhus" } } - }, - "version": "2.1.2" -} \ No newline at end of file + } +} diff --git a/src/ExecutorContainer.ts b/src/ExecutorContainer.ts index 58e8b56d..e569f7fc 100644 --- a/src/ExecutorContainer.ts +++ b/src/ExecutorContainer.ts @@ -10,11 +10,13 @@ import RExecutor from "./executors/RExecutor.js"; import CExecutor from "./executors/CExecutor"; import FSharpExecutor from "./executors/FSharpExecutor"; import LatexExecutor from "./executors/LatexExecutor"; +import JuliaExecutor from "./executors/JuliaExecutor"; const interactiveExecutors: Partial> = { "js": NodeJSExecutor, "python": PythonExecutor, - "r": RExecutor + "r": RExecutor, + "julia": JuliaExecutor }; const nonInteractiveExecutors: Partial> = { diff --git a/src/RunButton.ts b/src/RunButton.ts index a3dae51d..ca6b64bc 100644 --- a/src/RunButton.ts +++ b/src/RunButton.ts @@ -76,6 +76,7 @@ async function handleExecution(block: CodeBlockContext) { case "zig": return runCode(s.zigPath, s.zigArgs, "zig", block, { shell: true }); case "ocaml": return runCode(s.ocamlPath, s.ocamlArgs, "ocaml", block, { shell: true }); case "php": return runCode(s.phpPath, s.phpArgs, s.phpFileExtension, block, { shell: true }); + case "julia": return runCode(s.juliaPath, s.juliaArgs,s.juliaFileExtension, block, {transform: (code) => macro.expandJuliaPlot(code,s) }); case "latex": const outputPath: string = await retrieveFigurePath(block.srcCode, s.latexFigureTitlePattern, block.markdownFile, s); const invokeCompiler: string = [s.latexTexfotArgs, s.latexCompilerPath, s.latexCompilerArgs].join(" "); diff --git a/src/executors/JuliaExecutor.ts b/src/executors/JuliaExecutor.ts new file mode 100644 index 00000000..3f086353 --- /dev/null +++ b/src/executors/JuliaExecutor.ts @@ -0,0 +1,37 @@ +import {ChildProcessWithoutNullStreams} from "child_process"; +import {ExecutorSettings} from "src/settings/Settings"; +import ReplExecutor from "./ReplExecutor.js"; + + +export default class JuliaExecutor extends ReplExecutor { + + process: ChildProcessWithoutNullStreams + + constructor(settings: ExecutorSettings, file: string) { + const args = settings.juliaArgs ? settings.juliaArgs.split(" ") : []; + + //args.unshift(`--interactive`); + + super(settings, settings.juliaPath, args, file, "julia"); + } + + /** + * Writes a single newline to ensure that the stdin is set up correctly. + */ + async setup() { + this.process.stdin.write("\n"); + } + + wrapCode(code: string, finishSigil: string): string { + + //apparently some issue with Meta.parse: https://github.com/JuliaInterop/JuliaCall/issues/197 + //return `try \n eval(Meta.parse(${JSON.stringify(code)}))\n catch err; \n bt = catch_backtrace(); msg = sprint(showerror, err, bt); println(stderr,msg) \n finally \n print(${JSON.stringify(finishSigil)}) \n end\n` + // this works and will return an error if there is one, just not on stderr + return `${code} \n print(${JSON.stringify(finishSigil)}) \n` + } + + removePrompts(output: string, source: "stdout" | "stderr"): string { + return output; + } + +} diff --git a/src/main.ts b/src/main.ts index 6dcbb63d..31da376d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -18,7 +18,7 @@ import * as runButton from './RunButton'; export const languageAliases = ["javascript", "typescript", "bash", "csharp", "wolfram", "nb", "wl", "hs", "py", "tex"] as const; export const canonicalLanguages = ["js", "ts", "cs", "latex", "lean", "lua", "python", "cpp", "prolog", "shell", "groovy", "r", "go", "rust", "java", "powershell", "kotlin", "mathematica", "haskell", "scala", "swift", "racket", "fsharp", "c", "dart", - "ruby", "batch", "sql", "octave", "maxima", "applescript", "zig", "ocaml", "php"] as const; + "ruby", "batch", "sql", "octave", "maxima", "applescript", "zig", "ocaml", "php", "julia"] as const; export const supportedLanguages = [...languageAliases, ...canonicalLanguages] as const; export type LanguageId = typeof canonicalLanguages[number]; diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index d67e3798..99ff2a0b 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -142,6 +142,11 @@ export interface ExecutorSettings { phpArgs: string; phpFileExtension: string; phpInject: string; + juliaPath: string; + juliaArgs: string; + juliaFileExtension: string; + juliaEmbedPlots: boolean; + juliaInject: string; scalaPath: string; scalaArgs: string; scalaFileExtension: string; @@ -210,6 +215,7 @@ export interface ExecutorSettings { zigInteractive: boolean; ocamlInteractive: boolean; phpInteractive: boolean; + juliaInteractive: boolean; } @@ -387,6 +393,11 @@ export const DEFAULT_SETTINGS: ExecutorSettings = { phpArgs: "", phpFileExtension: "php", phpInject: "", + juliaPath: "julia", + juliaArgs: "", + juliaEmbedPlots: true, + juliaFileExtension: "jl", + juliaInject: "", jsInteractive: true, tsInteractive: false, csInteractive: false, @@ -422,4 +433,5 @@ export const DEFAULT_SETTINGS: ExecutorSettings = { zigInteractive: false, ocamlInteractive: false, phpInteractive: false, + juliaInteractive: true, } diff --git a/src/settings/SettingsTab.ts b/src/settings/SettingsTab.ts index 2b32a8eb..1d9a9c28 100644 --- a/src/settings/SettingsTab.ts +++ b/src/settings/SettingsTab.ts @@ -10,6 +10,7 @@ import makeGroovySettings from "./per-lang/makeGroovySettings"; import makeHaskellSettings from "./per-lang/makeHaskellSettings"; import makeJavaSettings from "./per-lang/makeJavaSettings"; import makeJsSettings from "./per-lang/makeJsSettings"; +import makeJuliaSettings from "./per-lang/makeJuliaSettings"; import makeKotlinSettings from "./per-lang/makeKotlinSettings"; import makeLatexSettings from "./per-lang/makeLatexSettings"; import makeLeanSettings from "./per-lang/makeLeanSettings"; @@ -172,6 +173,7 @@ export class SettingsTab extends PluginSettingTab { makeOCamlSettings(this, this.makeContainerFor("ocaml")); makePhpSettings(this, this.makeContainerFor("php")); makeLatexSettings(this, this.makeContainerFor("latex")); + makeJuliaSettings(this,this.makeContainerFor("julia")); this.focusContainer(this.plugin.settings.lastOpenLanguageTab || canonicalLanguages[0]); } diff --git a/src/settings/languageDisplayName.ts b/src/settings/languageDisplayName.ts index 2fc88a96..54411324 100644 --- a/src/settings/languageDisplayName.ts +++ b/src/settings/languageDisplayName.ts @@ -8,6 +8,7 @@ export const DISPLAY_NAMES: Record = { haskell: "Haskell", java: "Java", js: "Javascript", + julia: "Julia", kotlin: "Kotlin", latex: "LaTeX", lua: "Lua", diff --git a/src/settings/per-lang/makeJuliaSettings.ts b/src/settings/per-lang/makeJuliaSettings.ts new file mode 100644 index 00000000..fc016171 --- /dev/null +++ b/src/settings/per-lang/makeJuliaSettings.ts @@ -0,0 +1,42 @@ +import { Setting } from "obsidian"; +import { SettingsTab } from "../SettingsTab"; + +export default (tab: SettingsTab, containerEl: HTMLElement) => { + containerEl.createEl('h3', { text: 'Julia Settings' }); + new Setting(containerEl) + .setName('Embed Julia Plots') + .addToggle(toggle => toggle + .setValue(tab.plugin.settings.juliaEmbedPlots) + .onChange(async (value) => { + tab.plugin.settings.juliaEmbedPlots = value; + await tab.plugin.saveSettings(); + })); + new Setting(containerEl) + .setName('Julia path') + .addText(text => text + .setValue(tab.plugin.settings.juliaPath) + .onChange(async (value) => { + const sanitized = tab.sanitizePath(value); + tab.plugin.settings.juliaPath = sanitized; + console.log('julia path set to: ' + sanitized); + await tab.plugin.saveSettings(); + })); + new Setting(containerEl) + .setName('Julia arguments') + .addText(text => text + .setValue(tab.plugin.settings.juliaArgs) + .onChange(async (value) => { + tab.plugin.settings.juliaArgs = value; + console.log('Julia args set to: ' + value); + await tab.plugin.saveSettings(); + })); + new Setting(containerEl) + .setName("Run Julia blocks in Notebook Mode") + .addToggle((toggle) => toggle + .setValue(tab.plugin.settings.juliaInteractive) + .onChange(async (value) => { + tab.plugin.settings.juliaInteractive = value; + await tab.plugin.saveSettings(); + })); + tab.makeInjectSetting(containerEl, "julia"); +} \ No newline at end of file diff --git a/src/transforms/Magic.ts b/src/transforms/Magic.ts index 92bc3678..c55a0123 100644 --- a/src/transforms/Magic.ts +++ b/src/transforms/Magic.ts @@ -34,6 +34,7 @@ const PYTHON_PLOT_REGEX = /^(plt|matplotlib.pyplot|pyplot)\.show\(\)/gm; const R_PLOT_REGEX = /^plot\(.*\)/gm; const OCTAVE_PLOT_REGEX = /^plot\s*\(.*\);/gm; const MAXIMA_PLOT_REGEX = /^plot2d\s*\(.*\[.+\]\)\s*[$;]/gm; +const JULIA_PLOT_REGEX = /^display\(current\(\)\)/gm; /** * Parses the source code for the @vault command and replaces it with the vault path. @@ -291,3 +292,17 @@ export function expandMaximaPlot(source: string): string { return source; } + +export function expandJuliaPlot(source: string, settings: ExecutorSettings): string { + if (settings.juliaEmbedPlots) { + const matches = source.matchAll(JULIA_PLOT_REGEX); //replacing display(current()) + for (const match of matches) { + const tempFile = `${os.tmpdir()}/temp_${Date.now()}.png`.replace(/\\/g, "/").replace(/^\//, ""); + const substitute = `savefig("${tempFile}"); println(${JSON.stringify(`${TOGGLE_HTML_SIGIL}${TOGGLE_HTML_SIGIL}`)})`; + + source = source.replace(match[0], substitute); + } + } + return source; +} +