From 3c4f56453f4b9d3845723fbb844c1a0b337c4bac Mon Sep 17 00:00:00 2001 From: Judro <49918183+Judro@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:57:40 +0100 Subject: [PATCH 01/34] fix typo makeHaskellSettings.ts --- src/settings/per-lang/makeHaskellSettings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/per-lang/makeHaskellSettings.ts b/src/settings/per-lang/makeHaskellSettings.ts index 2feb8721..991dabe9 100644 --- a/src/settings/per-lang/makeHaskellSettings.ts +++ b/src/settings/per-lang/makeHaskellSettings.ts @@ -25,7 +25,7 @@ export default (tab: SettingsTab, containerEl: HTMLElement) => { await tab.plugin.saveSettings(); })); new Setting(containerEl) - .setName('Rungch path') + .setName('Runghc path') .setDesc('The path to your runghc installation.') .addText(text => text .setValue(tab.plugin.settings.runghcPath) From f46936033bb13fd3cfdc5d43c17153fe453e112a Mon Sep 17 00:00:00 2001 From: Paul Eibensteiner Date: Sat, 14 Dec 2024 13:00:22 +0100 Subject: [PATCH 02/34] add content as markdown string --- src/Vault.ts | 8 +++++--- src/transforms/Magic.ts | 13 +++++++++++++ src/transforms/TransformCode.ts | 3 ++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/Vault.ts b/src/Vault.ts index f2d2264d..e1024f08 100644 --- a/src/Vault.ts +++ b/src/Vault.ts @@ -6,8 +6,8 @@ import {MarkdownView} from "obsidian"; * file path, and vault path of the currently opened / focused note. * * @param app The current app handle (this.app from ExecuteCodePlugin) - * @returns { fileName: string; folder: string; filePath: string; vaultPath: string } A dictionary containing the - * file name, folder path, file path, and vault path of the currently opened / focused note. + * @returns { fileName: string; folder: string; filePath: string; vaultPath: string; fileContent: string } A dictionary containing the + * file name, folder path, file path, vault pat, and file content of the currently opened / focused note. */ export function getVaultVariables(app: App) { const activeView = app.workspace.getActiveViewOfType(MarkdownView); @@ -20,6 +20,7 @@ export function getVaultVariables(app: App) { const folder = activeView.file.parent.path; const fileName = activeView.file.name const filePath = activeView.file.path + const fileContent = activeView.editor.getValue(); const theme = document.body.classList.contains("theme-light") ? "light" : "dark"; @@ -28,6 +29,7 @@ export function getVaultVariables(app: App) { folder: folder, fileName: fileName, filePath: filePath, - theme: theme + theme: theme, + fileContent: fileContent } } diff --git a/src/transforms/Magic.ts b/src/transforms/Magic.ts index 91087082..7d5f4490 100644 --- a/src/transforms/Magic.ts +++ b/src/transforms/Magic.ts @@ -25,6 +25,7 @@ const CURRENT_NOTE_REGEX = /@note/g; const CURRENT_NOTE_PATH_REGEX = /@note_path/g; const CURRENT_NOTE_URL_REGEX = /@note_url/g; const NOTE_TITLE_REGEX = /@title/g; +const NOTE_CONTENT_REGEX = /@note_content/g; const COLOR_THEME_REGEX = /@theme/g; // Regex that are only used by one language. @@ -80,6 +81,18 @@ export function insertNoteTitle(source: string, noteTitle: string): string { return source.replace(NOTE_TITLE_REGEX, `"${t}"`); } +/** + * Parses the source code and replaces the NOTE_CONTENT_REGEX with the file content. + * + * @param source The source code to parse. + * @param content The content of the note. + * @returns The transformed source code. + */ +export function insertNoteContent(source: string, content: string): string { + const escaped_content = JSON.stringify(content) + return source.replace(NOTE_CONTENT_REGEX, `${escaped_content}`) +} + /** * Parses the source code for the @theme command and replaces it with the colour theme. * diff --git a/src/transforms/TransformCode.ts b/src/transforms/TransformCode.ts index f3ae7ccc..a6beba34 100644 --- a/src/transforms/TransformCode.ts +++ b/src/transforms/TransformCode.ts @@ -1,4 +1,4 @@ -import {insertColorTheme, insertNotePath, insertNoteTitle, insertVaultPath} from "./Magic"; +import {insertColorTheme, insertNotePath, insertNoteTitle, insertVaultPath, insertNoteContent} from "./Magic"; import {getVaultVariables} from "src/Vault"; import {canonicalLanguages} from 'src/main'; import type {App} from "obsidian"; @@ -43,6 +43,7 @@ export function transformMagicCommands(app: App, srcCode: string) { ret = insertNotePath(ret, vars.filePath); ret = insertNoteTitle(ret, vars.fileName); ret = insertColorTheme(ret, vars.theme); + ret = insertNoteContent(ret, vars.fileContent); } else { console.warn(`Could not load all Vault variables! ${vars}`) } From f5bbb99a8b1a95a6a7c09e622408f35c629dfafd Mon Sep 17 00:00:00 2001 From: Paul Eibensteiner Date: Sat, 14 Dec 2024 13:55:42 +0100 Subject: [PATCH 03/34] add obsidian typings --- package-lock.json | 948 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + tsconfig.json | 3 +- 3 files changed, 945 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 21f2bd54..fab237df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,11 +22,50 @@ "builtin-modules": "^3.3.0", "esbuild": "0.15.8", "obsidian": "latest", + "obsidian-typings": "^2.3.1-beta.1", "parallelshell": "^3.0.1", "tslib": "2.4.0", "typescript": "^4.8.3" } }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@capacitor/core": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-6.2.0.tgz", + "integrity": "sha512-B9IlJtDpUqhhYb+T8+cp2Db/3RETX36STgjeU2kQZBs/SLAcFiMama227o+msRjLeo3DO+7HJjWVA1+XlyyPEg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@codemirror/search": { + "version": "6.5.8", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.8.tgz", + "integrity": "sha512-PoWtZvo7c1XFeZWmmyaOp2G0XVbOnm+fJzvghqGAktBW3cufwJUWvSCcNG0ppXiBEM05mZu6RhMtXPv2hpllig==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, "node_modules/@codemirror/state": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz", @@ -48,6 +87,40 @@ "w3c-keyname": "^2.2.4" } }, + "node_modules/@electron/get": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", + "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@electron/get/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@esbuild/android-arm": { "version": "0.15.8", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.8.tgz", @@ -229,6 +302,48 @@ "node": ">= 8" } }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, "node_modules/@types/codemirror": { "version": "5.60.8", "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", @@ -246,6 +361,14 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -253,6 +376,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "18.19.64", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.64.tgz", @@ -263,6 +397,17 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -280,6 +425,26 @@ "@types/estree": "*" } }, + "node_modules/@types/turndown": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.5.tgz", + "integrity": "sha512-TL2IgGgc7B5j78rIccBtlYAnkuv8nUQqhQc+DSYV5j9Be9XOcm/SKOVRuA47xAVI3680Tk9B1d8flK2GWT2+4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", @@ -581,6 +746,16 @@ "license": "MIT", "peer": true }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -606,6 +781,17 @@ "node": ">=8" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "*" + } + }, "node_modules/builtin-modules": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", @@ -619,6 +805,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -648,6 +865,20 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -678,6 +909,14 @@ "license": "MIT", "peer": true }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/cross-spawn": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", @@ -712,6 +951,37 @@ } } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -720,6 +990,66 @@ "license": "MIT", "peer": true }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -747,6 +1077,100 @@ "node": ">=6.0.0" } }, + "node_modules/electron": { + "version": "33.2.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-33.2.1.tgz", + "integrity": "sha512-SG/nmSsK9Qg1p6wAW+ZfqU+AV8cmXMTIklUL18NnOKfZLlum4ZsDoVdmmmlL39ZmeCaq27dr7CgslRPahfoVJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^20.9.0", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/electron/node_modules/@types/node": { + "version": "20.17.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.10.tgz", + "integrity": "sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/electron/node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/esbuild": { "version": "0.15.8", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.8.tgz", @@ -1358,13 +1782,35 @@ "node": ">=0.10.0" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, - "license": "MIT", - "peer": true + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT", + "peer": true }, "node_modules/fast-glob": { "version": "3.3.2", @@ -1422,6 +1868,17 @@ "reusify": "^1.0.4" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -1491,6 +1948,22 @@ "license": "ISC", "peer": true }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1505,6 +1978,23 @@ "integrity": "sha512-Fi6Ng5fZ/ANLQ15H11hCe+09sgUoNvDEBevVgx3KoYOhsH5iLNPn54hx0jPZ+3oSWr+xajnp2Qau9VmPsc7hTA==", "license": "MIT" }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -1542,6 +2032,26 @@ "node": ">=10.13.0" } }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, "node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -1559,6 +2069,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -1580,6 +2109,56 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC", + "peer": true + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -1598,6 +2177,69 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/i18next": { + "version": "23.16.8", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz", + "integrity": "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -1748,6 +2390,15 @@ "license": "MIT", "peer": true }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -1760,6 +2411,17 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "peer": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -1811,6 +2473,32 @@ "license": "MIT", "peer": true }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -1835,6 +2523,17 @@ "node": ">=8.6" } }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1880,6 +2579,32 @@ "dev": true, "license": "MIT" }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/obsidian": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.7.2.tgz", @@ -1895,6 +2620,23 @@ "@codemirror/view": "^6.0.0" } }, + "node_modules/obsidian-typings": { + "version": "2.3.1-beta.1", + "resolved": "https://registry.npmjs.org/obsidian-typings/-/obsidian-typings-2.3.1-beta.1.tgz", + "integrity": "sha512-0S82fAa20gKeaxoAR/WgwarazIu8Uidjm7EggWlmtN46gPGI0nzsKIzQKDt8YonDuW202W554QXRbqbCB90wDg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@capacitor/core": "^6.1.2", + "@codemirror/search": "^6.5.6", + "@codemirror/state": "^6.4.1", + "@types/node": ">=14.0.0", + "@types/turndown": "^5.0.5", + "electron": ">=1.6.10", + "i18next": "^23.15.1", + "obsidian": "^1.7.2" + } + }, "node_modules/obsidian/node_modules/moment": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", @@ -1941,6 +2683,17 @@ "integrity": "sha512-IGo+qFumpIV65oDchJrqL0BOk9kr82fObnTesNJt8t3YgP6vfqcmRs0ofPzg3D9PKMeBHt7lrg1k/6L+oFdS8g==", "license": "Unlicense" }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -2042,6 +2795,14 @@ "node": ">=8" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -2066,6 +2827,29 @@ "node": ">= 0.8.0" } }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2098,6 +2882,20 @@ ], "license": "MIT" }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/readline-sync": { "version": "1.4.9", "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.9.tgz", @@ -2107,6 +2905,22 @@ "node": ">= 0.8.0" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2118,6 +2932,20 @@ "node": ">=4" } }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -2147,6 +2975,26 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -2184,6 +3032,48 @@ "node": ">=10" } }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -2219,6 +3109,15 @@ "node": ">=8" } }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -2255,6 +3154,20 @@ "license": "MIT", "peer": true }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -2378,6 +3291,17 @@ "dev": true, "license": "MIT" }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -2433,6 +3357,18 @@ "license": "ISC", "peer": true }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 1dd45d14..6a37f4e6 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "builtin-modules": "^3.3.0", "esbuild": "0.15.8", "obsidian": "latest", + "obsidian-typings": "^2.3.1-beta.1", "parallelshell": "^3.0.1", "tslib": "2.4.0", "typescript": "^4.8.3" diff --git a/tsconfig.json b/tsconfig.json index 4a1cd438..47a3b198 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,8 @@ "ES5", "ES6", "ES7" - ] + ], + "types": ["obsidian-typings"] }, "include": [ "**/*.ts", From dcb76bebb03ec2e72843b1eef08d7c78258a6214 Mon Sep 17 00:00:00 2001 From: Paul Eibensteiner Date: Sat, 14 Dec 2024 13:59:49 +0100 Subject: [PATCH 04/34] change from markdown string to html string --- src/Vault.ts | 18 +++++++++++++++++- src/transforms/Magic.ts | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Vault.ts b/src/Vault.ts index e1024f08..a45e08aa 100644 --- a/src/Vault.ts +++ b/src/Vault.ts @@ -1,6 +1,22 @@ import type {App, FileSystemAdapter} from "obsidian"; import {MarkdownView} from "obsidian"; +/** +* Get the full HTML content of the current MarkdownView +* +* @param view - The MarkdownView to get the HTML from +* @returns The full HTML of the MarkdownView +*/ +function getFullContentHtml(view: MarkdownView): string { + const codeMirror = view.editor.cm; + codeMirror.viewState.printing = true; + codeMirror.measure(); + const html = view.contentEl.innerHTML; + codeMirror.viewState.printing = false; + codeMirror.measure(); + return html; +} + /** * Tries to get the active view from obsidian and returns a dictionary containing the file name, folder path, * file path, and vault path of the currently opened / focused note. @@ -20,7 +36,7 @@ export function getVaultVariables(app: App) { const folder = activeView.file.parent.path; const fileName = activeView.file.name const filePath = activeView.file.path - const fileContent = activeView.editor.getValue(); + const fileContent = getFullContentHtml(activeView); const theme = document.body.classList.contains("theme-light") ? "light" : "dark"; diff --git a/src/transforms/Magic.ts b/src/transforms/Magic.ts index 7d5f4490..5dac9f1e 100644 --- a/src/transforms/Magic.ts +++ b/src/transforms/Magic.ts @@ -25,7 +25,7 @@ const CURRENT_NOTE_REGEX = /@note/g; const CURRENT_NOTE_PATH_REGEX = /@note_path/g; const CURRENT_NOTE_URL_REGEX = /@note_url/g; const NOTE_TITLE_REGEX = /@title/g; -const NOTE_CONTENT_REGEX = /@note_content/g; +const NOTE_CONTENT_REGEX = /@content/g; const COLOR_THEME_REGEX = /@theme/g; // Regex that are only used by one language. From 181153970e63c007517252df1f4321d323160196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C5=A1a=20=C4=86etkovi=C4=87?= Date: Fri, 3 Jan 2025 01:25:10 +0100 Subject: [PATCH 05/34] Fix F# support --- src/executors/FSharpExecutor.ts | 2 +- src/main.ts | 9 ++++++++- src/settings/Settings.ts | 6 +++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/executors/FSharpExecutor.ts b/src/executors/FSharpExecutor.ts index 6e55e053..19310539 100644 --- a/src/executors/FSharpExecutor.ts +++ b/src/executors/FSharpExecutor.ts @@ -8,6 +8,6 @@ export default class FSharpExecutor extends NonInteractiveCodeExecutor { } override run(codeBlockContent: string, outputter: Outputter, cmd: string, args: string, ext: string) { - return super.run(codeBlockContent, outputter, cmd, `fsi ${args}`, "cpp"); + return super.run(codeBlockContent, outputter, cmd, args, ext); } } diff --git a/src/main.ts b/src/main.ts index 7f769a2d..123e5683 100644 --- a/src/main.ts +++ b/src/main.ts @@ -358,6 +358,13 @@ export default class ExecuteCodePlugin extends Plugin { this.runCodeInShell(transformedCode, out, button, this.settings.csPath, this.settings.csArgs, this.settings.csFileExtension, language, file); }); + } else if (language === "fsharp") { + button.addEventListener("click", async () => { + button.className = runButtonDisabledClass; + const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); + this.runCodeInShell(transformedCode, out, button, this.settings.fsharpPath, this.settings.fsharpArgs, this.settings.fsharpFileExtension, language, file); + }); + } else if (language === "haskell") { button.addEventListener("click", async () => { button.className = runButtonDisabledClass; @@ -416,7 +423,7 @@ export default class ExecuteCodePlugin extends Plugin { transformedCode = addInlinePlotsToMaxima(transformedCode); this.runCodeInShell(transformedCode, out, button, this.settings.maximaPath, this.settings.maximaArgs, this.settings.maximaFileExtension, language, file); }) - }else if (language === "racket") { + } else if (language === "racket") { button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 51abf950..46d9d47d 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -112,7 +112,7 @@ export interface ExecutorSettings { phpPath: string; phpArgs: string; phpFileExtension: string; - phpInject: string; + phpInject: string; scalaPath: string; scalaArgs: string; scalaFileExtension: string; @@ -293,7 +293,7 @@ export const DEFAULT_SETTINGS: ExecutorSettings = { racketFileExtension: "rkt", racketInject: "#lang racket", fsharpPath: "dotnet", - fsharpArgs: "", + fsharpArgs: "fsi", fsharpInject: "", fsharpFileExtension: "fsx", cArgs: "", @@ -327,7 +327,7 @@ export const DEFAULT_SETTINGS: ExecutorSettings = { phpPath: "php", phpArgs: "", phpFileExtension: "php", - phpInject: "", + phpInject: "", jsInteractive: true, tsInteractive: false, csInteractive: false, From 4b82a44780ec3f0b1ddbb09ac832e9a847ac1d9a Mon Sep 17 00:00:00 2001 From: Yetenol Date: Tue, 28 Jan 2025 16:41:15 +0100 Subject: [PATCH 06/34] output rendered markdown to stdout --- src/main.ts | 4 ++-- src/output/Outputter.ts | 38 ++++++++++++++++++++++++++++++-------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/main.ts b/src/main.ts index 7f769a2d..69f3dd89 100644 --- a/src/main.ts +++ b/src/main.ts @@ -73,7 +73,7 @@ export default class ExecuteCodePlugin extends Plugin { supportedLanguages.forEach(l => { console.debug(`Registering renderer for ${l}.`) this.registerMarkdownCodeBlockProcessor(`run-${l}`, async (src, el, _ctx) => { - await MarkdownRenderer.renderMarkdown('```' + l + '\n' + src + (src.endsWith('\n') ? '' : '\n') + '```', el, _ctx.sourcePath, null); + await MarkdownRenderer.render(this.app, '```' + l + '\n' + src + (src.endsWith('\n') ? '' : '\n') + '```', el, _ctx.sourcePath, new Component()); }); }); @@ -206,7 +206,7 @@ export default class ExecuteCodePlugin extends Plugin { if (canonicalLanguage // if the language is supported && !parent.classList.contains(hasButtonClass)) { // & this block hasn't been buttonified already - const out = new Outputter(codeBlock, this.settings, view); + const out = new Outputter(codeBlock, this.settings, view, this.app, file); parent.classList.add(hasButtonClass); const button = this.createRunButton(); pre.appendChild(button); diff --git a/src/output/Outputter.ts b/src/output/Outputter.ts index 7a26912f..84fc981a 100644 --- a/src/output/Outputter.ts +++ b/src/output/Outputter.ts @@ -1,9 +1,9 @@ -import {EventEmitter} from "events"; +import { EventEmitter } from "events"; import loadEllipses from "../svgs/loadEllipses"; import loadSpinner from "../svgs/loadSpinner"; import FileAppender from "./FileAppender"; -import {MarkdownView, Setting} from "obsidian"; -import {ExecutorSettings} from "../settings/Settings"; +import { App, Component, MarkdownRenderer, MarkdownView, normalizePath } from "obsidian"; +import { ExecutorSettings } from "../settings/Settings"; export const TOGGLE_HTML_SIGIL = `TOGGLE_HTML_${Math.random().toString(16).substring(2)}`; @@ -28,10 +28,14 @@ export class Outputter extends EventEmitter { saveToFile: FileAppender; settings: ExecutorSettings; + app: App; + srcFile: string; - constructor(codeBlock: HTMLElement, settings: ExecutorSettings, view: MarkdownView) { + constructor(codeBlock: HTMLElement, settings: ExecutorSettings, view: MarkdownView, app: App, srcFile: string) { super(); this.settings = settings; + this.app = app; + this.srcFile = srcFile; this.inputState = this.settings.allowInput ? "INACTIVE" : "NOT_DOING"; this.codeBlockElement = codeBlock; @@ -95,6 +99,24 @@ export class Outputter extends EventEmitter { } + /** + * Add a segment of rendered markdown as stdout data to the outputter + * @param markdown The Markdown source code to be rendered as HTML + * @param addLineBreak whether to start a new line in stdout afterwards + * @param relativeFile Path of the markdown file. Used to resolve relative internal links. + */ + async writeMarkdown(markdown: string, addLineBreak?: boolean, relativeFile = this.srcFile) { + if (relativeFile !== this.srcFile) { + relativeFile = normalizePath(relativeFile); + } + const renderedEl = document.createElement("div"); + await MarkdownRenderer.render(this.app, markdown, renderedEl, relativeFile, new Component()); + for (const child of Array.from(renderedEl.children)) { + this.write(TOGGLE_HTML_SIGIL + child.innerHTML + TOGGLE_HTML_SIGIL); + } + if (addLineBreak) this.write(`\n`); + } + /** * Add a segment of stdout data to the outputter, * processing `toggleHtmlSigil`s along the way. @@ -167,9 +189,9 @@ export class Outputter extends EventEmitter { * Mark the block as running */ startBlock() { - if(!this.loadStateIndicatorElement) this.addLoadStateIndicator(); + if (!this.loadStateIndicatorElement) this.addLoadStateIndicator(); setTimeout(() => { - if(this.blockRunState !== "FINISHED") + if (this.blockRunState !== "FINISHED") this.loadStateIndicatorElement.classList.add("visible"); }, 100); @@ -328,7 +350,7 @@ export class Outputter extends EventEmitter { * @param text text to append */ private escapeAwareAppend(element: HTMLElement, text: string) { - if(this.escapeHTML) { + if (this.escapeHTML) { // If we're escaping HTML, just append the text element.appendChild(document.createTextNode(text)); @@ -348,7 +370,7 @@ export class Outputter extends EventEmitter { * @param element element to append to */ private writeHTMLBuffer(element: HTMLElement) { - if(this.htmlBuffer !== "") { + if (this.htmlBuffer !== "") { this.makeOutputVisible(); const content = document.createElement("div"); From 28e80b1e2a1fc2375414351f7d7f1cc73dc7f18b Mon Sep 17 00:00:00 2001 From: Yetenol Date: Tue, 28 Jan 2025 16:46:50 +0100 Subject: [PATCH 07/34] allow killBlock() to terminate spawned child processes --- src/output/Outputter.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/output/Outputter.ts b/src/output/Outputter.ts index 84fc981a..cf84bd53 100644 --- a/src/output/Outputter.ts +++ b/src/output/Outputter.ts @@ -4,6 +4,7 @@ import loadSpinner from "../svgs/loadSpinner"; import FileAppender from "./FileAppender"; import { App, Component, MarkdownRenderer, MarkdownView, normalizePath } from "obsidian"; import { ExecutorSettings } from "../settings/Settings"; +import { ChildProcess } from "child_process"; export const TOGGLE_HTML_SIGIL = `TOGGLE_HTML_${Math.random().toString(16).substring(2)}`; @@ -28,6 +29,7 @@ export class Outputter extends EventEmitter { saveToFile: FileAppender; settings: ExecutorSettings; + runningSubprocesses = new Set(); app: App; srcFile: string; @@ -71,14 +73,14 @@ export class Outputter extends EventEmitter { this.saveToFile.clearOutput(); // Kill code block - this.killBlock(); + this.killBlock(this.runningSubprocesses); } /** * Kills the code block. * To be overwritten in an executor's run method */ - killBlock() {} + killBlock(subprocesses?: Set) { } /** * Hides the output and clears the log. Visually, restores the code block to its initial state. @@ -237,7 +239,7 @@ export class Outputter extends EventEmitter { this.loadStateIndicatorElement.classList.add("load-state-indicator"); // Kill code block on clicking load state indicator - this.loadStateIndicatorElement.addEventListener('click', () => this.killBlock()); + this.loadStateIndicatorElement.addEventListener('click', () => this.killBlock(this.runningSubprocesses)); this.getParentElement().parentElement.appendChild(this.loadStateIndicatorElement); } @@ -410,7 +412,7 @@ export class Outputter extends EventEmitter { * @see {@link clear()} */ private makeOutputVisible() { - this.closeInput(); + this.closeInput(); if (!this.clearButton) this.addClearButton(); if (!this.outputElement) this.addOutputElement(); From c7b66f9bc5ea415d8872f89b5c1b46b3a66566ca Mon Sep 17 00:00:00 2001 From: Yetenol Date: Sat, 1 Feb 2025 00:38:05 +0100 Subject: [PATCH 08/34] output lucide icon to stdout --- src/output/Outputter.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/output/Outputter.ts b/src/output/Outputter.ts index cf84bd53..a28a8df7 100644 --- a/src/output/Outputter.ts +++ b/src/output/Outputter.ts @@ -2,7 +2,7 @@ import { EventEmitter } from "events"; import loadEllipses from "../svgs/loadEllipses"; import loadSpinner from "../svgs/loadSpinner"; import FileAppender from "./FileAppender"; -import { App, Component, MarkdownRenderer, MarkdownView, normalizePath } from "obsidian"; +import { App, Component, MarkdownRenderer, MarkdownView, normalizePath, setIcon } from "obsidian"; import { ExecutorSettings } from "../settings/Settings"; import { ChildProcess } from "child_process"; @@ -29,6 +29,7 @@ export class Outputter extends EventEmitter { saveToFile: FileAppender; settings: ExecutorSettings; + runningSubprocesses = new Set(); app: App; srcFile: string; @@ -101,8 +102,22 @@ export class Outputter extends EventEmitter { } + + /** + * Add an icon to the outputter. + * @param icon Name of the icon from the lucide library {@link https://lucide.dev/} + * @param hoverTooltip Title to display on mouseover + * @param styleClass CSS class for design tweaks + * @returns HTMLAnchorElement to add a click listener, for instance + */ + writeIcon(icon: string, hoverTooltip?: string, styleClass?: string | string[]): HTMLAnchorElement { + const button: HTMLAnchorElement = this.lastPrintElem.createEl('a', { title: hoverTooltip, cls: styleClass }); + setIcon(button, icon); + return button; + } + /** - * Add a segment of rendered markdown as stdout data to the outputter + * Add a segment of rendered markdown to the outputter * @param markdown The Markdown source code to be rendered as HTML * @param addLineBreak whether to start a new line in stdout afterwards * @param relativeFile Path of the markdown file. Used to resolve relative internal links. From ba53ed8b25ee5ccbd880dc4799024f291c819092 Mon Sep 17 00:00:00 2001 From: Yetenol Date: Sun, 2 Feb 2025 10:28:02 +0100 Subject: [PATCH 09/34] fix load-state-indicator invisible on Minimal theme --- src/styles.css | 1 - 1 file changed, 1 deletion(-) diff --git a/src/styles.css b/src/styles.css index 04c61581..6e02c89d 100644 --- a/src/styles.css +++ b/src/styles.css @@ -194,7 +194,6 @@ input.interactive-stdin { .load-state-indicator.visible { transform: translateX(0); - transform: translateX(var(--folding-offset, 0)); opacity: 1; pointer-events: all; } From 582d297037e6ae01fc46aea4207f50f6db70b70f Mon Sep 17 00:00:00 2001 From: Yetenol Date: Sun, 2 Feb 2025 10:29:00 +0100 Subject: [PATCH 10/34] fix overlapping elements on image zoom (:active) in Minimal theme --- src/styles.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/styles.css b/src/styles.css index 6e02c89d..deb46b38 100644 --- a/src/styles.css +++ b/src/styles.css @@ -165,10 +165,6 @@ input.interactive-stdin { position: relative; } -.has-run-code-button pre { - z-index: 1; -} - .load-state-indicator { position: absolute; top: 0.1em; From 15e0927459e393b943c33df463754e27e100f5f0 Mon Sep 17 00:00:00 2001 From: Yetenol Date: Sun, 2 Feb 2025 10:48:27 +0100 Subject: [PATCH 11/34] remove unused imports in main, auto-format --- src/main.ts | 29 ++++++++++------------------- src/styles.css | 16 ++++++++++------ 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/main.ts b/src/main.ts index 69f3dd89..b16182b1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,21 +1,16 @@ import { - App, Component, - FileSystemAdapter, - FileView, + Component, MarkdownRenderer, MarkdownView, - Modal, - normalizePath, Plugin, - TFile } from 'obsidian'; -import {Outputter, TOGGLE_HTML_SIGIL} from "./output/Outputter"; -import type {ExecutorSettings} from "./settings/Settings"; -import {DEFAULT_SETTINGS} from "./settings/Settings"; -import {SettingsTab} from "./settings/SettingsTab"; -import {getLanguageAlias} from './transforms/TransformCode'; -import {CodeInjector} from "./transforms/CodeInjector"; +import { Outputter, TOGGLE_HTML_SIGIL } from "./output/Outputter"; +import type { ExecutorSettings } from "./settings/Settings"; +import { DEFAULT_SETTINGS } from "./settings/Settings"; +import { SettingsTab } from "./settings/SettingsTab"; +import { getLanguageAlias } from './transforms/TransformCode'; +import { CodeInjector } from "./transforms/CodeInjector"; import { addInlinePlotsToPython, addInlinePlotsToR, @@ -32,7 +27,7 @@ import ExecutorManagerView, { } from './ExecutorManagerView'; import runAllCodeBlocks from './runAllCodeBlocks'; -import {ReleaseNoteModel} from "./ReleaseNoteModal"; +import { ReleaseNoteModel } from "./ReleaseNoteModal"; export const languageAliases = ["javascript", "typescript", "bash", "csharp", "wolfram", "nb", "wl", "hs", "py"] as const; export const canonicalLanguages = ["js", "ts", "cs", "lean", "lua", "python", "cpp", "prolog", "shell", "groovy", "r", @@ -47,10 +42,6 @@ export const runButtonClass = "run-code-button"; const runButtonDisabledClass = "run-button-disabled"; const hasButtonClass = "has-run-code-button"; - - - - export default class ExecuteCodePlugin extends Plugin { settings: ExecutorSettings; executors: ExecutorContainer; @@ -416,12 +407,12 @@ export default class ExecuteCodePlugin extends Plugin { transformedCode = addInlinePlotsToMaxima(transformedCode); this.runCodeInShell(transformedCode, out, button, this.settings.maximaPath, this.settings.maximaArgs, this.settings.maximaFileExtension, language, file); }) - }else if (language === "racket") { + } else if (language === "racket") { button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.racketPath, this.settings.racketArgs, this.settings.racketFileExtension, language, file); - }) + }) } else if (language === "applescript") { button.addEventListener("click", async () => { button.className = runButtonDisabledClass; diff --git a/src/styles.css b/src/styles.css index deb46b38..cb0ba5b6 100644 --- a/src/styles.css +++ b/src/styles.css @@ -58,15 +58,18 @@ button.clear-button { z-index: 100; } -pre:hover .run-code-button, pre:hover .clear-button { +pre:hover .run-code-button, +pre:hover .clear-button { display: block; } -pre:hover .run-button-disabled, pre:hover .clear-button-disabled { +pre:hover .run-button-disabled, +pre:hover .clear-button-disabled { display: none; } -.run-button-disabled, .clear-button-disabled { +.run-button-disabled, +.clear-button-disabled { display: none; } @@ -79,7 +82,7 @@ pre:hover code.language-output { } .use-custom-output-color code.language-output span.stdout { - color: var(--code-output-text-color) !important; + color: var(--code-output-text-color) !important; } :not(.use-custom-error-color) code.language-output span.stderr { @@ -94,7 +97,8 @@ code.language-output hr { margin: 0 0 1em; } -.settings-code-input-box textarea, .settings-code-input-box input { +.settings-code-input-box textarea, +.settings-code-input-box input { min-width: 400px; min-height: 100px; font-family: monospace; @@ -152,7 +156,7 @@ input.interactive-stdin { color: var(--icon-color-hover); } -.manage-executors-view > div { +.manage-executors-view>div { position: relative; } From 2fe289c2eae139b0c19bb9b1372b8eb2baef8981 Mon Sep 17 00:00:00 2001 From: Yetenol Date: Sun, 2 Feb 2025 20:16:41 +0100 Subject: [PATCH 12/34] compile LaTeX and convert to image formats --- src/ExecutorContainer.ts | 4 +- src/executors/LatexExecutor.ts | 254 +++++++++++++ src/main.ts | 21 +- src/output/LatexInserter.ts | 166 +++++++++ src/output/RegExpUtilities.ts | 49 +++ src/settings/Settings.ts | 67 +++- src/settings/SettingsTab.ts | 7 +- src/settings/languageDisplayName.ts | 1 + src/settings/per-lang/makeLatexSettings.ts | 399 +++++++++++++++++++++ src/styles.css | 35 ++ src/transforms/LatexFigureName.ts | 65 ++++ src/transforms/LatexFontHandler.ts | 104 ++++++ src/transforms/LatexTransformer.ts | 67 ++++ 13 files changed, 1231 insertions(+), 8 deletions(-) create mode 100644 src/executors/LatexExecutor.ts create mode 100644 src/output/LatexInserter.ts create mode 100644 src/output/RegExpUtilities.ts create mode 100644 src/settings/per-lang/makeLatexSettings.ts create mode 100644 src/transforms/LatexFigureName.ts create mode 100644 src/transforms/LatexFontHandler.ts create mode 100644 src/transforms/LatexTransformer.ts diff --git a/src/ExecutorContainer.ts b/src/ExecutorContainer.ts index 59698969..58e8b56d 100644 --- a/src/ExecutorContainer.ts +++ b/src/ExecutorContainer.ts @@ -9,6 +9,7 @@ import ExecuteCodePlugin, {LanguageId} from "./main"; import RExecutor from "./executors/RExecutor.js"; import CExecutor from "./executors/CExecutor"; import FSharpExecutor from "./executors/FSharpExecutor"; +import LatexExecutor from "./executors/LatexExecutor"; const interactiveExecutors: Partial> = { "js": NodeJSExecutor, @@ -20,7 +21,8 @@ const nonInteractiveExecutors: Partial> = { "prolog": PrologExecutor, "cpp": CppExecutor, "c": CExecutor, - "fsharp": FSharpExecutor + "fsharp": FSharpExecutor, + "latex" : LatexExecutor, }; export default class ExecutorContainer extends EventEmitter implements Iterable { diff --git a/src/executors/LatexExecutor.ts b/src/executors/LatexExecutor.ts new file mode 100644 index 00000000..18da6527 --- /dev/null +++ b/src/executors/LatexExecutor.ts @@ -0,0 +1,254 @@ +import NonInteractiveCodeExecutor from "./NonInteractiveCodeExecutor"; +import { Outputter } from "../output/Outputter"; +import * as fs from "fs/promises"; +import * as fsSync from "fs"; +import * as path from "path"; +import { ChildProcess, spawn } from "child_process"; +import * as os from "os"; +import { isStandaloneClass } from "src/transforms/LatexTransformer"; +import { updateImage, writeFileLink } from "src/output/LatexInserter"; + +const REQUEST_RERUN_WARNING: RegExp = /LaTeX Warning: .* Rerun to get cross-references right./ +const MAX_COMPILER_RERUNS = 10; + +interface ExecutionContext { + workingDir?: string; + outputDir: string; + outputter: Outputter; +} + +export default class LaTeXExecutor extends NonInteractiveCodeExecutor { + compilerRequestsRerun = new Set; + removeBuildDir: () => void; + + /** Main entry point for LaTeX compilation that handles PDF generation and conversion to other formats. */ + async run(latexSrc: string, outputter: Outputter, compilerPath: string, compilerArgs: string, exportPath: string): Promise { + outputter.clear(); + outputter.killBlock = (subprocesses) => this.killSubprocesses(subprocesses); + const s = this.settings; + const exec: ExecutionContext = { + outputter: outputter, + outputDir: path.dirname(exportPath), + }; + const figureName = path.basename(exportPath, path.extname(exportPath)); + const errorHandler = (error: any) => { + outputter.writeErr(error + '\n'); + outputter.closeInput(); + }; + + // Synchronous compilation + let pdfFile: string; + try { + exec.workingDir = await this.createTempDirectory(outputter); + pdfFile = await this.compileAndCrop(latexSrc, compilerArgs, compilerPath, exec); + } catch (error) { + errorHandler(error); + this.cleanupBuildDir(exec); + return; + } + + // Asynchronous convertion + if (s.latexSavePdf) { + this.savePdf(pdfFile, exportPath, `${figureName}.pdf`, exec).catch(errorHandler); + } + if (s.latexSaveSvg === "poppler") { + this.runChildProcess(s.latexSvgPath, [s.latexSvgArgs, pdfFile], `${figureName}.svg`, exec) + .catch(errorHandler); + } + if (s.latexSaveSvg === "inkscape") { + this.runChildProcess(s.latexInkscapePath, [s.latexInkscapeArgs, pdfFile, '--export-filename'], `${figureName}.svg`, exec) + .catch(errorHandler); + } + if (s.latexSavePng) { + this.runChildProcess(s.latexPngPath, [s.latexPngArgs, pdfFile], `${figureName}.png`, exec, { outFileArg: figureName }) + .catch(errorHandler); + } + + this.cleanupBuildDir(exec); + } + + /** Compiles LaTeX source and crops PDF to content if enabled. */ + private async compileAndCrop(latexSrc: string, compilerArgs: string, compilerPath: string, exec: ExecutionContext) { + const s = this.settings; + const buildDir = exec.workingDir; + const texFile = await this.writeContentToTexFile(latexSrc, buildDir, 'o.tex'); + compilerArgs = [compilerArgs, `-output-directory=${buildDir}`, texFile].join(' '); + const allowIncludesRelativeToAttachmentDir: ExecutionContext = { outputDir: buildDir, workingDir: exec.outputDir, outputter: exec.outputter, } + const cropInBuildDir: ExecutionContext = { outputDir: buildDir, workingDir: buildDir, outputter: exec.outputter, } + + let pdfFile = await this.compileAndRerun(compilerPath, compilerArgs, allowIncludesRelativeToAttachmentDir); + if (s.latexKeepLog) { + const logFile = path.join(buildDir, 'o.log'); + await exec.outputter.writeMarkdown(`Log: [${logFile}](file://${logFile})`, true); + } + const isExcludedDocumentclass = s.latexCropNoStandalone && isStandaloneClass(latexSrc); + if (s.latexDoCrop && !isExcludedDocumentclass) { + await fs.rename(path.join(buildDir, pdfFile), path.join(buildDir, 'original.pdf')); + pdfFile = await this.runChildProcess(s.latexCropPath, [s.latexCropArgs, 'original.pdf'], pdfFile, cropInBuildDir, { skipWriteFileLink: true }); + } + return pdfFile; + } + + /** Handles LaTeX compilation with automatic reruns for cross-references. */ + private async compileAndRerun(compilerPath: string, args: string, exec: ExecutionContext): Promise { + for (let attempt = 0; attempt <= MAX_COMPILER_RERUNS; attempt++) { + const isLastAttempt = attempt == MAX_COMPILER_RERUNS; + const result = await this.runChildProcess(compilerPath, args.split(' '), 'o.pdf', exec, { skipWriteFileLink: true, outFileArg: "", doDetectRerun: !isLastAttempt }); + const hasRequestedRerun = result === undefined; + if (!hasRequestedRerun) return result; + } + throw `LaTeX compilation did not result in a PDF file.`; + } + + /** Stop running child processes on clicking Clear or Run again. */ + private killSubprocesses(subprocesses: Set) { + for (const child of subprocesses) { + child.kill('SIGINT'); + console.log(`Subprocess ${path.basename(child.spawnfile)} killed.`); + subprocesses.delete(child); + } + } + + /** Save PDF as vault attachment */ + private async savePdf(pdfFile: string, exportPath: string, figureName: string, exec: ExecutionContext): Promise { + const pdfPath = path.join(exec.workingDir, pdfFile); + const destinationPath = `${exportPath}.pdf`; + + try { + await fs.copyFile(pdfPath, destinationPath); + } catch (error) { + throw `Failed to copy ${pdfPath} to ${destinationPath}: ${error}`; + } + + await writeFileLink(figureName, destinationPath, exec.outputter); + } + + /** Executes a child process and capture its output. */ + private async runChildProcess(cmd: string, args: string[], outName: string, exec: ExecutionContext, options?: { outFileArg?: string, skipWriteFileLink?: boolean, doDetectRerun?: boolean }): Promise { + const outDir = exec.outputDir; + const cwdDir = exec.workingDir; + const outputter = exec.outputter; + let outFileArg: string = options?.outFileArg || outName; + const outFile = path.join(outDir, outFileArg); + if (outFileArg) { + if (outDir !== cwdDir) { + outFileArg = outFile; + } + args.push(`"${outFileArg}"`); + } + + const child = spawn(cmd, args, { + env: process.env, + windowsVerbatimArguments: true, + cwd: cwdDir, + shell: Boolean(this.settings.latexSubprocessesUseShell) + }); + outputter.runningSubprocesses.add(child); + const description = `Subprocess ${cmd} ${args.join(" ")}`; + + + child.stdout.on('data', (data) => outputter.write(data.toString())); + child.stderr.on('data', (data) => outputter.writeErr(data.toString())); + outputter.on('data', (data) => child.stdin.write(data)); + if (options?.doDetectRerun) { + child.stdout.on('data', (data) => this.detectWarningRequestingRerun(data, child)); + } + + return new Promise((resolve, reject) => { + setTimeout(() => { + if (!outputter.runningSubprocesses.has(child)) return; + child.kill('SIGTERM'); + reject(new Error(`${description} timed out after ${this.settings.timeout} ms.`)); + }, this.settings.timeout); + + child.on('error', (err) => reject(`${description} throws:\n${err}`)); + child.on('close', async (code) => { + const resultedInFile = fsSync.existsSync(path.join(outDir, outName)); + outputter.runningSubprocesses.delete(child); + + if (code != 0) return reject(`${description} failed with code ${code}.`); + if (!resultedInFile) return reject(`${description} failed to create ${outFile}.`); + + if (options?.doDetectRerun && this.compilerRequestsRerun.has(child)) { + this.compilerRequestsRerun.delete(child); + return resolve(undefined); + } + + if (options?.skipWriteFileLink) return resolve(outName); + const figureEmbeddings: NodeListOf = document.querySelectorAll(`img[alt="${outName}"]`); + figureEmbeddings.forEach(img => updateImage(img)); + + await writeFileLink(outName, path.join(outDir, outName), outputter); + await sleep(200); // Wait for obsidian to index attachment + if (this.settings.latexOutputEmbeddings) { + await outputter.writeMarkdown(`![[${outName}]]`, true); + } + outputter.closeInput(); + return resolve(outName); + }); + }); + } + + /** Checks LaTeX output for warnings indicating need for additional compilation pass */ + private detectWarningRequestingRerun(data: any, child: ChildProcess) { + if (REQUEST_RERUN_WARNING.test(data.toString())) { + this.compilerRequestsRerun.add(child); + }; + } + + private async writeContentToTexFile(codeBlockContent: string, buildDir: string, filename: string): Promise { + const texFile = path.join(buildDir, filename) + await fs.writeFile(texFile, codeBlockContent); + return filename; + } + + /** Creates a unique temporary directory for the current LaTeX compilation */ + private async createTempDirectory(outputter: Outputter): Promise { + const millisToday = Date.now() - new Date().setHours(0, 0, 0, 0); + const prefix = "tex" + millisToday.toString(); + const prefixPath = path.join(os.tmpdir(), prefix) + const buildDir = await fs.mkdtemp(prefixPath); + this.removeBuildDir = async () => { + const success = await this.removeLockedFolder(buildDir); + if (success) outputter.closeInput(); + }; + return buildDir; + } + + /** Attempts to clean up the build directory with retry logic for locked files */ + private async cleanupBuildDir(exec: ExecutionContext) { + const path: string | null = exec.workingDir; + if (!path || this.settings.latexKeepLog) return; + + await sleep(100); // Wait for subprocesses to start + if (!fsSync.existsSync(path)) return; + + const maxRetries = 10; + for (let attempt = 0; attempt < maxRetries; attempt++) { + await sleep(500); // Wait for subprocesses to unlock folder + if (!fsSync.existsSync(path)) return; + if (exec.outputter.runningSubprocesses.size > 0) continue; + const success = await this.removeLockedFolder(path, `Build folder busy, retrying (${attempt + 1}/${maxRetries}).`); + if (success) { + exec.outputter.closeInput(); + return; + } + } + throw new Error('Failed to delete folder after multiple attempts.'); + } + + /** Safely removes a directory that might be locked by the system */ + private async removeLockedFolder(path: string, comment?: string): Promise { + try { + await fs.rmdir(path, { recursive: true }); + console.debug(`Removed build folder: ${path}`); + return true; + } catch (error) { + if (error.code != 'EBUSY' && error.code != 'ENOENT') throw error; + if (comment) { + console.log(comment); + } + } + return false; + } +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index b16182b1..28225f83 100644 --- a/src/main.ts +++ b/src/main.ts @@ -19,6 +19,8 @@ import { addInlinePlotsToOctave, addInlinePlotsToMaxima } from "./transforms/Magic"; +import { modifyLatexCode, applyLatexBodyClasses } from "./transforms/LatexTransformer" +import { retrieveFigurePath } from './transforms/LatexFigureName'; import ExecutorContainer from './ExecutorContainer'; import ExecutorManagerView, { @@ -29,8 +31,8 @@ import ExecutorManagerView, { import runAllCodeBlocks from './runAllCodeBlocks'; import { ReleaseNoteModel } from "./ReleaseNoteModal"; -export const languageAliases = ["javascript", "typescript", "bash", "csharp", "wolfram", "nb", "wl", "hs", "py"] as const; -export const canonicalLanguages = ["js", "ts", "cs", "lean", "lua", "python", "cpp", "prolog", "shell", "groovy", "r", +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; export const supportedLanguages = [...languageAliases, ...canonicalLanguages] as const; @@ -94,6 +96,8 @@ export default class ExecuteCodePlugin extends Plugin { this.settings.releaseNote2_0_0wasShowed = true; this.saveSettings(); } + + applyLatexBodyClasses(this.app, this.settings); } /** @@ -437,8 +441,19 @@ export default class ExecuteCodePlugin extends Plugin { const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.phpPath, this.settings.phpArgs, this.settings.phpFileExtension, language, file); }) + } else if (language === "latex") { + button.addEventListener("click", async () => { + button.className = runButtonDisabledClass; + let transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); + transformedCode = modifyLatexCode(transformedCode, this.settings); + const outputPath = await retrieveFigurePath(srcCode, this.settings.latexFigureTitlePattern, file, this.settings); + if (!this.settings.latexDoFilter) { + this.runCode(transformedCode, out, button, this.settings.latexCompilerPath, this.settings.latexCompilerArgs, outputPath, language, file); + } else { + this.runCode(transformedCode, out, button, this.settings.latexTexfotPath, [this.settings.latexTexfotArgs, this.settings.latexCompilerPath, this.settings.latexCompilerArgs].join(" "), outputPath, language, file); + } + }); } - } /** diff --git a/src/output/LatexInserter.ts b/src/output/LatexInserter.ts new file mode 100644 index 00000000..1604dbdb --- /dev/null +++ b/src/output/LatexInserter.ts @@ -0,0 +1,166 @@ +import { App, setIcon, TFile, Vault } from "obsidian"; +import { Outputter } from "./Outputter"; +import { settingsInstance } from "src/transforms/LatexTransformer"; +import { FIGURE_FILENAME_EXTENSIONS, TEMP_FIGURE_NAME } from 'src/transforms/LatexFigureName'; +import { generalizeFigureTitle } from 'src/transforms/LatexFigureName'; +import * as r from "./RegExpUtilities"; +import * as path from "path"; + +const LINK_ALIAS = /\|[^\]]*/; +const ANY_WIKILINK_EMBEDDING = r.concat(/!\[\[.*?/, FIGURE_FILENAME_EXTENSIONS, r.optional(LINK_ALIAS), /\]\]/); +const ANY_MARKDOWN_EMBEDDING = r.concat(/!\[.*?\]\(.*?/, FIGURE_FILENAME_EXTENSIONS, /\)/); +const ANY_FIGURE_EMBEDDING: RegExp = r.alternate(ANY_WIKILINK_EMBEDDING, ANY_MARKDOWN_EMBEDDING); + +const SAFE_ANY: RegExp = /([^`]|`[^`]|``[^`])*?/; // Match any text, that does not cross the ``` boundary of code blocks +const EMPTY_LINES: RegExp = /[\s\n]*/; + +interface FigureContext { + app: App; + figureName: string; + link: () => string; // evaluates at button click, to let Obsidian index the file + file: TFile; +} + +/** Forces an image to reload by appending a cache-busting timestamp to its URL */ +export function updateImage(image: HTMLImageElement) { + const baseUrl = image.src.split('?')[0]; + image.src = `${baseUrl}?cache=${Date.now()}`; +} + +/** + * Adds an obsidian link and clickable insertion icons to the output. + * @param figureName - The name of the figure file with extension that was saved + * @param figurePath - The path where the figure was saved + * @param outputter - The Outputter instance used to write content + */ +export async function writeFileLink(figureName: string, figurePath: string, outputter: Outputter): Promise { + await outputter.writeMarkdown(`Saved [[${figureName}]]`); + + const isTempFigure = TEMP_FIGURE_NAME.test(figureName); + if (isTempFigure) return outputter.write('\n'); + + const file: TFile | null = outputter.app.vault.getFileByPath(outputter.srcFile); + if (!file) throw new Error(`File not found: ${outputter.srcFile}`); + + const link = () => createObsidianLink(outputter.app, figurePath, outputter.srcFile); + const figure: FigureContext = { app: outputter.app, figureName: figureName, link: link, file: file }; + const buttonClass = 'insert-figure-icon'; + + const insertAbove: HTMLAnchorElement = outputter.writeIcon('image-up', 'Click to embed file above codeblock.\nCtrl + Click to replace previous embedding.', buttonClass); + insertAbove.addEventListener('click', (event: MouseEvent) => insertEmbedding('above', event.ctrlKey, figure)); + + const insertBelow: HTMLAnchorElement = outputter.writeIcon('image-down', 'Click to embed file below codeblock.\nCtrl + Click to replace next embedding.', buttonClass); + insertBelow.addEventListener('click', (event: MouseEvent) => insertEmbedding('below', event.ctrlKey, figure)); + + const copyLink: HTMLAnchorElement = outputter.writeIcon('copy', 'Copy the markdown link.', buttonClass); + copyLink.addEventListener('click', () => navigator.clipboard.writeText(link())); + + outputter.write('\n'); +} + +/** * Inserts an embedded link to the figure above or below the current code blocks. */ +async function insertEmbedding(pastePosition: 'above' | 'below', doReplace: boolean, figure: FigureContext): Promise { + try { + const vault = figure.app.vault; + const content: string = await vault.read(figure.file); + + const identifierSrc: string = settingsInstance.latexFigureTitlePattern + .replace(/\(\?[^)]*\)/, generalizeFigureTitle(figure.figureName).source); + const identifier: RegExp = r.parse(identifierSrc); + if (!identifier) return; + + const codeBlocks: RegExpExecArray[] = findMatchingCodeBlocks(content, /(la)?tex/, identifier, figure.link(), doReplace); + if (codeBlocks.length === 0) return false; + + codeBlocks.forEach(async (block: RegExpExecArray) => { + await insertAtCodeBlock(block, pastePosition, figure); + }); + return true; + } catch (error) { + console.error('Error inserting embedding:', error); + throw error; + } +} + +/** Locates LaTeX code blocks containing the specified figure identifier and their surrounding embeddings */ +function findMatchingCodeBlocks(content: string, language: RegExp, identifier: RegExp, link: string, doReplace?: boolean): RegExpExecArray[] { + const alreadyLinked: RegExp = r.group(r.escape(link)); + const codeblock: RegExp = r.concat( + /```(run-)?/, r.group(language), /[\s\n]/, + SAFE_ANY, r.group(identifier), SAFE_ANY, + /```/); + + const previous: RegExp = r.capture(r.concat(ANY_FIGURE_EMBEDDING, EMPTY_LINES), 'replacePrevious'); + const above: RegExp = r.capture(r.concat(alreadyLinked, EMPTY_LINES), 'alreadyAbove'); + + const below: RegExp = r.capture(r.concat(EMPTY_LINES, alreadyLinked), 'alreadyBelow'); + const next: RegExp = r.capture(r.concat(EMPTY_LINES, ANY_FIGURE_EMBEDDING), 'replaceNext'); + + const blocksWithEmbeds: RegExp = new RegExp(r.concat( + (doReplace) ? r.optional(previous) : null, + r.optional(above), + r.capture(codeblock, 'codeblock'), + r.optional(below), + (doReplace) ? r.optional(next) : null, + ), 'g'); + + const matches: RegExpExecArray[] = Array.from(content.matchAll(blocksWithEmbeds)); + console.debug(`Searching markdown for`, blocksWithEmbeds, `resulted in `, matches.length, `codeblock(s)`, matches.map(match => match.groups)); + return matches; +} + +/** Updates markdown source file to insert or replace a figure embedding relative to a code block */ +async function insertAtCodeBlock(block: RegExpExecArray, pastePosition: 'above' | 'below', figure: FigureContext): Promise { + const vault = figure.app.vault; + const groups = block.groups; + if (!groups || !groups.codeblock) return; + + const canReplace: Boolean = (pastePosition === 'above') + ? groups.replacePrevious?.length > 0 + : groups.replaceNext?.length > 0; + + const isAlreadyEmbedded: boolean = (pastePosition === 'above') + ? groups.alreadyAbove?.length > 0 + : groups.alreadyBelow?.length > 0; + if (isAlreadyEmbedded && !canReplace) return; + + const newText: string = (pastePosition === 'above') + ? figure.link() + '\n\n' + groups.codeblock + : groups.codeblock + '\n\n' + figure.link(); + + if (!canReplace) { + await vault.process(figure.file, data => data.replace(groups.codeblock, newText)); + return; + } + + const oldTexts: string[] = (pastePosition === 'above') + ? [groups.replacePrevious, groups.alreadyAbove, groups.codeblock] + : [groups.codeblock, groups.alreadyBelow, groups.replaceNext]; + const oldCombined = oldTexts.filter(Boolean).join(''); + await vault.process(figure.file, data => data.replace(oldCombined, newText)); +} + +/** Let Obsidian generate a link adhering to preferences */ +export function createObsidianLink(app: App, filePath: string, sourcePath: string, subpath?: string, alias?: string): string { + const relative = getPathRelativeToVault(filePath); + try { + const file: TFile | null = app.vault.getFileByPath(relative); + return app.fileManager.generateMarkdownLink(file, sourcePath, subpath, alias); + } catch (error) { + console.error(`File not found: ${relative}`); + return '![[' + path.basename(filePath) + ']]'; + } + +} + +function getPathRelativeToVault(absolutePath: string) { + const vaultPath = (this.app.vault.adapter as any).basePath; + absolutePath = path.normalize(absolutePath); + + if (!absolutePath.startsWith(vaultPath)) return absolutePath; + return absolutePath.slice(vaultPath.length) + .replace(/^[\\\/]/, '') + .replace(/\\/g, '/') + .replace(/['"`]/, '') + .trim(); +} \ No newline at end of file diff --git a/src/output/RegExpUtilities.ts b/src/output/RegExpUtilities.ts new file mode 100644 index 00000000..8037659a --- /dev/null +++ b/src/output/RegExpUtilities.ts @@ -0,0 +1,49 @@ +/** Escapes special regex characters in a string to create a RegExp that matches it literally */ +export function escape(str: string): RegExp { + return new RegExp(str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); // $& means the whole matched string +} + +/** Converts "/regex/" into RegExp */ +export function parse(pattern: string): RegExp | undefined { + try { + const trimmedSlashes: string = pattern.replace(/^\/|\/$/g, ''); + return RegExp(trimmedSlashes); + } catch { + return undefined; + } +} + +/** Makes a pattern optional by adding ? quantifier, equivalent to (pattern)? */ +export function optional(pattern: RegExp): RegExp { + return new RegExp(group(pattern).source + '?'); +} + +/** Creates a named capture group from the pattern, equivalent to (?pattern) */ +export function capture(pattern: RegExp, groupName: string): RegExp { + return group(pattern, { name: groupName }); +} + +/** Express unit?/scope?/encapsulated?/unbreakable? of inner pattern */ +export function group(inner: RegExp, options?: { name?: string }): RegExp { + let identifier = ''; + if (options?.name) identifier = `?<${options.name}>`; + return new RegExp('(' + identifier + inner.source + ')'); +} + +/** Combines multiple patterns sequentially into a single pattern */ +export function concat(...chain: RegExp[]): RegExp { + const combined: string = chain + .filter(Boolean) + .map(pattern => pattern.source) + .join(''); + return new RegExp(combined); +} + +/** Creates an alternation (OR) group from multiple patterns, equivalent to (pattern1|pattern2) */ +export function alternate(...options: RegExp[]): RegExp { + const alternated: string = options + .filter(Boolean) + .map(pattern => pattern.source) + .join('|'); + return group(new RegExp(alternated)); +} \ No newline at end of file diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 51abf950..7695759b 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -1,4 +1,4 @@ -import {LanguageId} from "src/main"; +import { LanguageId } from "src/main"; /** * Interface that contains all the settings for the extension. @@ -20,6 +20,36 @@ export interface ExecutorSettings { tsPath: string; tsArgs: string; tsInject: string; + latexCompilerPath: string; + latexCompilerArgs: string; + latexDoFilter: boolean; + latexTexfotPath: string; + latexTexfotArgs: string; + latexDocumentclass: string; + latexAdaptFont: '' | 'obsidian' | 'system'; + latexKeepLog: boolean; + latexSubprocessesUseShell: boolean; + latexMaxFigures: number; + latexFigureTitlePattern: string; + latexDoCrop: boolean; + latexCropPath: string; + latexCropArgs: string; + latexCropNoStandalone: boolean; + latexCropNoPagenum: boolean; + latexSaveSvg: '' | 'poppler' | 'inkscape'; + latexSvgPath: string; + latexSvgArgs: string; + latexInkscapePath: string; + latexInkscapeArgs: string; + latexSavePdf: boolean; + latexSavePng: boolean; + latexPngPath: string; + latexPngArgs: string; + latexOutputEmbeddings: boolean; + latexInvertFigures: boolean; + latexCenterFigures: boolean; + + latexInject: string; leanPath: string; leanArgs: string; leanInject: string; @@ -112,7 +142,7 @@ export interface ExecutorSettings { phpPath: string; phpArgs: string; phpFileExtension: string; - phpInject: string; + phpInject: string; scalaPath: string; scalaArgs: string; scalaFileExtension: string; @@ -149,6 +179,7 @@ export interface ExecutorSettings { jsInteractive: boolean; tsInteractive: boolean; csInteractive: boolean; + latexInteractive: boolean; leanInteractive: boolean; luaInteractive: boolean; dartInteractive: boolean; @@ -203,6 +234,35 @@ export const DEFAULT_SETTINGS: ExecutorSettings = { tsPath: "ts-node", tsArgs: "", tsInject: "", + latexCompilerPath: "lualatex", + latexCompilerArgs: "-interaction=nonstopmode", + latexDoFilter: true, + latexTexfotPath: "texfot", + latexTexfotArgs: "--quiet", + latexDocumentclass: "article", + latexAdaptFont: "obsidian", + latexKeepLog: false, + latexSubprocessesUseShell: false, + latexMaxFigures: 10, + latexFigureTitlePattern: /[^\n][^%`]*\\title\s*\{(?[^\}]*)\}/.source, + latexDoCrop: false, + latexCropPath: "pdfcrop", + latexCropArgs: "--quiet", + latexCropNoStandalone: true, + latexCropNoPagenum: true, + latexSaveSvg: "poppler", + latexSvgPath: "pdftocairo", + latexSvgArgs: "-svg", + latexInkscapePath: "inkscape", + latexInkscapeArgs: '--pages=all --export-plain-svg', + latexSavePdf: true, + latexSavePng: false, + latexPngPath: "pdftocairo", + latexPngArgs: "-singlefile -png", + latexOutputEmbeddings: true, + latexInvertFigures: true, + latexCenterFigures: true, + latexInject: "", leanPath: "lean", leanArgs: "", leanInject: "", @@ -327,10 +387,11 @@ export const DEFAULT_SETTINGS: ExecutorSettings = { phpPath: "php", phpArgs: "", phpFileExtension: "php", - phpInject: "", + phpInject: "", jsInteractive: true, tsInteractive: false, csInteractive: false, + latexInteractive: false, leanInteractive: false, luaInteractive: false, dartInteractive: false, diff --git a/src/settings/SettingsTab.ts b/src/settings/SettingsTab.ts index 69b99d2a..93839368 100644 --- a/src/settings/SettingsTab.ts +++ b/src/settings/SettingsTab.ts @@ -11,6 +11,7 @@ import makeHaskellSettings from "./per-lang/makeHaskellSettings"; import makeJavaSettings from "./per-lang/makeJavaSettings"; import makeJsSettings from "./per-lang/makeJsSettings"; import makeKotlinSettings from "./per-lang/makeKotlinSettings"; +import makeLatexSettings from "./per-lang/makeLatexSettings"; import makeLeanSettings from "./per-lang/makeLeanSettings"; import makeLuaSettings from "./per-lang/makeLuaSettings"; import makeDartSettings from "./per-lang/makeDartSettings"; @@ -244,8 +245,12 @@ export class SettingsTab extends PluginSettingTab { // ========== OCaml ============ makeOCamlSettings(this, this.makeContainerFor("ocaml")); - // ========== Ruby ============ + // ========== Php ============ makePhpSettings(this, this.makeContainerFor("php")); + + // ========== LaTeX ============ + makeLatexSettings(this, this.makeContainerFor("latex")); + this.focusContainer(this.plugin.settings.lastOpenLanguageTab || canonicalLanguages[0]); } diff --git a/src/settings/languageDisplayName.ts b/src/settings/languageDisplayName.ts index 490947c1..2fc88a96 100644 --- a/src/settings/languageDisplayName.ts +++ b/src/settings/languageDisplayName.ts @@ -9,6 +9,7 @@ export const DISPLAY_NAMES: Record = { java: "Java", js: "Javascript", kotlin: "Kotlin", + latex: "LaTeX", lua: "Lua", mathematica: "Mathematica", php: "PHP", diff --git a/src/settings/per-lang/makeLatexSettings.ts b/src/settings/per-lang/makeLatexSettings.ts new file mode 100644 index 00000000..592b6529 --- /dev/null +++ b/src/settings/per-lang/makeLatexSettings.ts @@ -0,0 +1,399 @@ +import { Setting } from "obsidian"; +import { SettingsTab } from "../SettingsTab"; +import { DEFAULT_SETTINGS } from "../Settings"; +import { updateBodyClass } from "src/transforms/LatexTransformer"; +import { parse } from "src/output/RegExpUtilities"; + +export default (tab: SettingsTab, containerEl: HTMLElement) => { + const s = tab.plugin.settings; + const linkTexDistributions = "Distributed through MiKTeX or TeX Live."; + const linkInkscape = "Download Inkscape."; + containerEl.createEl('h3', { text: 'LaTeX Settings' }); + + containerEl.createEl('h4', { text: 'Code injection' }); + new Setting(containerEl) + .setName('Default document class') + .addText(text => text + .setPlaceholder('disabled') + .setValue(s.latexDocumentclass) + .onChange(async (value) => { + const sanitized = value.trim() + s.latexDocumentclass = sanitized; + console.log(`Default documentclass set to: ${sanitized}`); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `Inject ${selectableText('\\documentclass{}')} if no class is specified. The document class macro is always moved to the very top of code blocks. + Set empty to disable, default is ${selectableText(DEFAULT_SETTINGS.latexDocumentclass, true)}.`; + new Setting(containerEl) + .setName('Adopt fonts') + .addDropdown(dropdown => dropdown + .addOptions({ '': 'Disabled', system: "Use system default", obsidian: 'Same as Obsidian' }) + .setValue(s.latexAdaptFont) + .onChange(async (value: typeof s.latexAdaptFont) => { + s.latexAdaptFont = value; + console.log(value ? `Now using ${value} fonts.` : 'Now keeping default fonts.'); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `Inject fontspec ${selectableText('\\setmainfont{}')}, ${selectableText('\\setsansfont{}')}, ${selectableText('\\setmonofont{}')} to the top of code blocks. + Ignores fonts that can not be loaded by CSS. Skipped if PdfLaTeX is used. Default is ${DEFAULT_SETTINGS.latexAdaptFont === "" ? 'disabled' : DEFAULT_SETTINGS.latexAdaptFont}.`; + tab.makeInjectSetting(containerEl, "latex"); + + + containerEl.createEl('h4', { text: 'LaTeX Compiler' }); + new Setting(containerEl) + .setName('Compiler path') + .addText(text => text + .setPlaceholder(`Example: ${DEFAULT_SETTINGS.latexCompilerPath}`) + .setValue(s.latexCompilerPath) + .onChange(async (value) => { + const sanitized = tab.sanitizePath(value); + s.latexCompilerPath = sanitized; + console.log(`latex compiler path set to: ${sanitized}`); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `The path to your LuaLaTeX installation. Or use PdfLaTeX, XeLaTeX. ${linkTexDistributions}`; + new Setting(containerEl.createDiv()) + .setName('Compiler arguments') + .addText(text => text + .setValue(s.latexCompilerArgs) + .onChange(async (value) => { + const sanitized = value.trim(); + s.latexCompilerArgs = sanitized; + console.log(`LaTeX args set to: ${sanitized}`); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `${selectableText('-shell-escape')} Allow LaTeX packages to execute external programs. + Default is ${selectableText(DEFAULT_SETTINGS.latexCompilerArgs)}.`; + + + containerEl.createEl('h4', { text: 'Post-processing' }); + new Setting(containerEl) + .setName('Crop to content') + .addToggle(toggle => toggle + .setValue(s.latexDoCrop) + .onChange(async (value) => { + s.latexDoCrop = value; + showSubSettings(requiresCrop, value) + console.log(value ? 'Now cropping pdf to content.' : "Now keeping entire page."); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `Crop PDF to visible content area with pdfcrop. Default is ${DEFAULT_SETTINGS.latexDoCrop ? 'on' : 'off'}.`; + const requiresCrop = containerEl.createDiv(); + showSubSettings(requiresCrop, s.latexDoCrop); + new Setting(requiresCrop.createDiv()) + .setName('Pdfcrop path') + .addText(text => text + .setPlaceholder(`Example: ${DEFAULT_SETTINGS.latexCropPath}`) + .setValue(s.latexCropPath) + .onChange(async (value) => { + const sanitized = tab.sanitizePath(value); + s.latexCropPath = sanitized; + console.log(`latex compiler path set to: ${sanitized}`); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `The path to your pdfcrop installation. ${linkTexDistributions}`; + new Setting(requiresCrop.createDiv()) + .setName('Pdfcrop arguments') + .addText(text => text + .setPlaceholder("Example: --margins 10") + .setValue(s.latexCropArgs) + .onChange(async (value) => { + const sanitized = value.trim(); + s.latexCropArgs = sanitized; + console.log(`LaTeX args set to: ${sanitized}`); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `${selectableText('--margins 10')} Whitespace in all directions. ${selectableText('--margins "left top right bottom"')} Specify margins. + Default is ${selectableText(DEFAULT_SETTINGS.latexCropArgs)}.`; + new Setting(requiresCrop.createDiv()) + .setName('Disable page number') + .addToggle(toggle => toggle + .setValue(s.latexCropNoPagenum) + .onChange(async (value) => { + s.latexCropNoPagenum = value; + console.log(value ? 'Now disabling page number for cropping.' : "Now keeping page number for cropping."); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `Inject ${selectableText('\\pagestyle{empty}')} to reduce the height of the content. + Default is ${DEFAULT_SETTINGS.latexCropNoPagenum ? 'on' : 'off'}.`; + new Setting(requiresCrop.createDiv()) + .setName('Exclude standalone') + .addToggle(toggle => toggle + .setValue(s.latexCropNoStandalone) + .onChange(async (value) => { + s.latexCropNoStandalone = value; + console.log(value ? 'Now excluding standalone for cropping.' : "Now including standalone for cropping."); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `Skip if document class ${selectableText('standalone')} is used, because it is already cropped. + Default is ${DEFAULT_SETTINGS.latexCropNoStandalone ? 'on' : 'off'}.`; + + new Setting(containerEl) + .setName('Save PDF') + .addToggle(toggle => toggle + .setValue(s.latexSavePdf) + .onChange(async (value) => { + s.latexSavePdf = value; + console.log(value ? 'Now saving PDFs.' : "Now discarding PDFs."); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `Save the generated PDF as attachment. Default is ${DEFAULT_SETTINGS.latexSavePdf ? 'on' : 'off'}.`; + + new Setting(containerEl) + .setName('Convert to SVG') + .addDropdown(dropdown => dropdown + .addOptions({ '': 'Disabled', poppler: 'Poppler: draw fonts perfectly', inkscape: 'Inkscape: keep text editable' }) + .setValue(s.latexSaveSvg) + .onChange(async (value: typeof s.latexSaveSvg) => { + s.latexSaveSvg = value; + showSubSettings(requiresSvg, value === 'poppler') + showSubSettings(requiresInkscape, value === 'inkscape') + console.log(value === "" ? 'Now discarding SVGs.' : `Svg converter set to: ${value}`); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `Convert the PDF to SVG and save it as attachment. Background is transparant. + Default is ${DEFAULT_SETTINGS.latexSaveSvg === "" ? 'disabled' : DEFAULT_SETTINGS.latexSaveSvg}.`; + const requiresSvg = containerEl.createDiv(); + showSubSettings(requiresSvg, s.latexSaveSvg === 'poppler') + new Setting(requiresSvg.createDiv()) + .setName('SVG converter path') + .addText(text => text + .setPlaceholder(`Example: ${DEFAULT_SETTINGS.latexSvgPath}`) + .setValue(s.latexSvgPath) + .onChange(async (value) => { + const sanitized = tab.sanitizePath(value); + s.latexSvgPath = sanitized; + console.log(`Pdftocairo path for svg set to: ${sanitized}`); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `The path to your pdftocairo installation. ${linkTexDistributions}`; + new Setting(requiresSvg.createDiv()) + .setName('SVG converter arguments') + .addText(text => text + .setValue(s.latexSvgArgs) + .onChange(async (value) => { + const sanitized = value.trim(); + s.latexSvgArgs = sanitized; + console.log(`Pdftocairo args for svg set to: ${sanitized}`); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `Default is ${selectableText(DEFAULT_SETTINGS.latexSvgArgs)}.`; + const requiresInkscape = containerEl.createDiv(); + showSubSettings(requiresInkscape, s.latexSaveSvg === 'inkscape') + new Setting(requiresInkscape.createDiv()) + .setName('Inkscape path') + .addText(text => text + .setPlaceholder(`Example: ${DEFAULT_SETTINGS.latexInkscapePath}`) + .setValue(s.latexInkscapePath) + .onChange(async (value) => { + const sanitized = tab.sanitizePath(value); + s.latexInkscapePath = sanitized; + console.log(`latex compiler path set to: ${sanitized}`); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `The path to your Inkscape installation. ${linkInkscape}`; + new Setting(requiresInkscape.createDiv()) + .setName('Inkscape arguments') + .addText(text => text + .setValue(s.latexInkscapeArgs) + .onChange(async (value) => { + const sanitized = value.trim(); + s.latexInkscapeArgs = sanitized; + console.log(`LaTeX args set to: ${sanitized}`); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `${selectableText('--pdf-font-strategy=draw-missing|substitute|keep|…')} How fonts are parsed in the internal PDF importer. + Default is ${selectableText(DEFAULT_SETTINGS.latexInkscapeArgs)}.`; + + new Setting(containerEl) + .setName('Convert to PNG') + .addToggle(toggle => toggle + .setValue(s.latexSavePng) + .onChange(async (value) => { + s.latexSavePng = value; + showSubSettings(requiresPng, value) + console.log(value ? 'Now generation PNGs.' : "Now discarding PNGs."); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `Convert the PDF to PNG and save it as attachment. Default is ${DEFAULT_SETTINGS.latexSavePng ? 'on' : 'off'}.`; + const requiresPng = containerEl.createDiv(); + showSubSettings(requiresPng, s.latexSavePng); + new Setting(requiresPng.createDiv()) + .setName('PNG converter path') + .addText(text => text + .setPlaceholder(`Example: ${DEFAULT_SETTINGS.latexPngPath}`) + .setValue(s.latexPngPath) + .onChange(async (value) => { + const sanitized = tab.sanitizePath(value); + s.latexPngPath = sanitized; + console.log(`Pdftocairo args for png set to: ${sanitized}`); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `The path to your pdftocairo installation. ${linkTexDistributions}`; + new Setting(requiresPng.createDiv()) + .setName('PNG converter arguments') + .addText(text => text + .setValue(s.latexPngArgs) + .onChange(async (value) => { + const sanitized = value.trim(); + s.latexPngArgs = sanitized; + console.log(`Pdftocairo args for png set to: ${sanitized}`); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `${selectableText('-transp')} Transparent background. ${selectableText('-gray')} Grayscale. ${selectableText('-mono')} Monochrome. + ${selectableText('-f int')} Page to save. Default is ${selectableText(DEFAULT_SETTINGS.latexPngArgs)}.`; + + containerEl.createEl('h4', { text: 'Appearance' }); + new Setting(containerEl) + .setName('Output embeddings') + .addToggle(toggle => toggle + .setValue(s.latexOutputEmbeddings) + .onChange(async (value) => { + s.latexOutputEmbeddings = value; + console.log(value ? 'Now embedding figures.' : `Now linking figures.`) + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `When running a LaTeX code block, show embeddings of saved figures. Default is ${DEFAULT_SETTINGS.latexOutputEmbeddings ? 'on' : 'off'}.`; + new Setting(containerEl) + .setName('Center SVGs') + .addToggle(toggle => toggle + .setValue(s.latexCenterFigures) + .onChange(async (value) => { + s.latexCenterFigures = value; + console.log(value ? 'Now centering SVGs.' : `Now left aligning SVGs.`) + updateBodyClass('center-latex-figures', value); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `Horizontally align SVGs whose filename starts with ${selectableText('figure')}. + Default is ${DEFAULT_SETTINGS.latexCenterFigures ? 'on' : 'off'}.`; + new Setting(containerEl) + .setName('Invert SVGs in dark mode') + .addToggle(toggle => toggle + .setValue(s.latexInvertFigures) + .onChange(async (value) => { + s.latexInvertFigures = value; + console.log(value ? 'Now inverting SVGs.' : `Now not inverting SVGs.`) + updateBodyClass('invert-latex-figures', value); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `If dark mode is enabled, invert the color of SVGs whose filename starts with ${selectableText('figure')}. + Default is ${DEFAULT_SETTINGS.latexInvertFigures ? 'on' : 'off'}.`; + + containerEl.createEl('h4', { text: 'Troubleshooting' }); + const maxFigures = containerEl.createDiv(); + new Setting(maxFigures) + .setName('Keep last n unnamed figures') + .addText(text => text + .setPlaceholder('unlimited') + .setValue(s.latexMaxFigures === Infinity ? "" : `${s.latexMaxFigures}`) + .onChange(async (value) => { + const numValue = value === "" ? Infinity : Number(value); + const isValid = isIntegerOrInfinity(numValue) && numValue > 0; + updateTextColor(maxFigures, isValid); + if (isValid) { + s.latexMaxFigures = numValue; + console.log(`max number of figures set to: ${numValue}`); + await tab.plugin.saveSettings(); + } + })) + .descEl.innerHTML = `Generated attachments receive an increasing index. To prevent too many files from piling up, jump back to zero after n executions. + Set empty for unlimited. Default is ${selectableText(DEFAULT_SETTINGS.latexMaxFigures.toString(), true)}.`; + maxFigures.querySelector('input').type = "number"; + const captureFigureName = containerEl.createDiv(); + new Setting(captureFigureName) + .setName('Capture figure name') + .addText(text => text + .setPlaceholder('/regex/') + .setValue(`${s.latexFigureTitlePattern}`) + .onChange(async (value) => { + const pattern = parse(value); + const isValid = pattern != undefined; + updateTextColor(captureFigureName, isValid); + if (isValid) { + s.latexFigureTitlePattern = pattern.toString(); + console.log('capture figure name pattern set to: ' + pattern); + await tab.plugin.saveSettings(); + } + })) + .descEl.innerHTML = `Search LaTeX code block for ${selectableText('\\title{…}')} to retrieve the figure name: + ${selectableText(/[^\n][^%`]*/.source)} Ignore comments after % symbol. ${selectableText(/(?.*?)/.source)} Capture group for figure name. + Default is ${selectableText(DEFAULT_SETTINGS.latexFigureTitlePattern)}.`; + new Setting(containerEl) + .setName('Filter output') + .addToggle(toggle => toggle + .setValue(s.latexDoFilter) + .onChange(async (value) => { + s.latexDoFilter = value; + showSubSettings(requiresTexfot, value); + console.log(value ? 'Now filtering latex stdout with texfot.' : 'Now showing full latex stdout.'); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `Filtering stdout to relevant messages with texfot. Default is ${DEFAULT_SETTINGS.latexKeepLog ? 'on' : 'off'}.` + const requiresTexfot = containerEl.createDiv(); + showSubSettings(requiresTexfot, s.latexDoFilter); + new Setting(requiresTexfot.createDiv()) + .setName('Texfot path') + .addText(text => text + .setPlaceholder(`Example: ${DEFAULT_SETTINGS.latexTexfotPath}`) + .setValue(s.latexTexfotPath) + .onChange(async (value) => { + const sanitized = tab.sanitizePath(value); + s.latexTexfotPath = sanitized; + console.log(`texfot path set to: ${sanitized}`); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `The path to your texfot installation. ${linkTexDistributions}`; + new Setting(requiresTexfot.createDiv()) + .setName('Texfot arguments') + .addText(text => text + .setPlaceholder(`Example: ${DEFAULT_SETTINGS.latexTexfotArgs}`) + .setValue(s.latexTexfotArgs) + .onChange(async (value) => { + const sanitized = value.trim(); + s.latexTexfotArgs = sanitized; + console.log(`texfot arguments set to: ${sanitized}`); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `${selectableText('--accept regex')}, ${selectableText('--ignore regex')} Filter lines in the TeX output matching RegExp. + Default is ${selectableText(DEFAULT_SETTINGS.latexTexfotArgs)}.`; + new Setting(containerEl) + .setName('Keep log') + .addToggle(toggle => toggle + .setValue(s.latexKeepLog) + .onChange(async (value) => { + s.latexKeepLog = value; + console.log(value ? 'Now preserving latex build folder.' : "Now clearing latex build folder."); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `Prevent deletion of temporary build folder. Default is ${DEFAULT_SETTINGS.latexKeepLog ? 'on' : 'off'}.`; + new Setting(containerEl) + .setName('Run subprocesses in shell') + .addToggle(toggle => toggle + .setValue(s.latexSubprocessesUseShell) + .onChange(async (value) => { + s.latexSubprocessesUseShell = value; + console.log(value ? 'Now running subprocesses in shell.' : 'Now running subprocesses directly.'); + await tab.plugin.saveSettings(); + })) + .descEl.innerHTML = `Run compilation and conversion tools in shell environment. Default is ${DEFAULT_SETTINGS.latexSubprocessesUseShell ? 'on' : 'off'}.`; +} + +function showSubSettings(settingsDiv: HTMLDivElement, doShow: boolean) { + settingsDiv.setAttr('style', doShow ? 'display: block' : 'display: none'); +} + +function updateTextColor(containerEl: HTMLElement, isValid: boolean) { + const inputEl = containerEl.querySelector('input') as HTMLInputElement; + inputEl.style.color = isValid ? '' : 'red'; +} + +function isIntegerOrInfinity(value: number): boolean { + return Number.isInteger(value) || value === Infinity; +} + +function selectableText(text: string, noMonospace?: boolean) { + if (noMonospace) return `${text}`; + + const escapedAngleBrackets = text.replace(//g, '>'); + return `${escapedAngleBrackets}`; +} \ No newline at end of file diff --git a/src/styles.css b/src/styles.css index cb0ba5b6..dfdf0c44 100644 --- a/src/styles.css +++ b/src/styles.css @@ -230,6 +230,7 @@ input.interactive-stdin { pre.language-output { display: block; } + /* Hide code blocks with language-output only in markdown view using "markdown-preview-view"*/ .markdown-preview-view pre.language-output { display: block; @@ -238,4 +239,38 @@ input.interactive-stdin { .markdown-rendered pre.language-output { display: block; } +} + +/* Center LaTeX vector graphics, confine to text width */ +.center-latex-figures img[src*="/figure%20"][src$=".svg"], +.center-latex-figures img[src*="/figure%20"][src*=".svg?"], +.center-latex-figures .stdout img[src*=".svg?"] { + display: block; + margin: auto; + max-width: 100%; +} + +/* Invert LaTeX vector graphics in dark mode */ +.theme-dark.invert-latex-figures img[src*="/figure%20"][src$=".svg"], +.theme-dark.invert-latex-figures img[src*="/figure%20"][src*=".svg?"], +.theme-dark.invert-latex-figures .stdout img[src*=".svg?"] { + filter: invert(1); +} + +/* Allow descriptions in LaTeX settings to be selected and copied. */ +.selectable-description-text { + -moz-user-select: text; + -khtml-user-select: text; + -webkit-user-select: text; + -ms-user-select: text; + user-select: text; +} + +.insert-figure-icon { + margin-left: 0.5em; +} + +/* Try to keep description of cmd arguments in LaTeX settings on the same line. */ +code.selectable-description-text { + white-space: nowrap; } \ No newline at end of file diff --git a/src/transforms/LatexFigureName.ts b/src/transforms/LatexFigureName.ts new file mode 100644 index 00000000..ce04f795 --- /dev/null +++ b/src/transforms/LatexFigureName.ts @@ -0,0 +1,65 @@ +import * as path from 'path'; +import * as r from 'src/output/RegExpUtilities'; +import { ExecutorSettings } from 'src/settings/Settings'; + +export const ILLEGAL_FILENAME_CHARS: RegExp = /[<>:"/\\|?*]+/g; +export const WHITESPACE_AND_ILLEGAL_CHARS: RegExp = /[<>:"/\\|?*\s]+/; +export const MAYBE_WHITESPACE_AND_ILLEGAL: RegExp = /[<>:"/\\|?*\s]*/; +export const FIGURE_FILENAME_EXTENSIONS: RegExp = /(.pdf|.svg|.png)/; +export const FILENAME_PREFIX: RegExp = /figure /; +export const UNNAMED_PREFIX: RegExp = /temp /; +export const TEMP_FIGURE_NAME: RegExp = /figure temp \d+/; + +let latexFilenameIndex = 0; + +export async function retrieveFigurePath(codeblockContent: string, titlePattern: string, srcFile: string, settings: ExecutorSettings): Promise { + const vaultAbsolutePath = (this.app.vault.adapter as any).basePath; + const vaultAttachmentPath = await this.app.fileManager.getAvailablePathForAttachment("test", srcFile); + const vaultAttachmentDir = path.dirname(vaultAttachmentPath); + const figureDir = path.join(vaultAbsolutePath, vaultAttachmentDir); + let figureTitle = captureFigureTitle(codeblockContent, titlePattern); + if (!figureTitle) { + const index = nextLatexFilenameIndex(settings.latexMaxFigures); + figureTitle = UNNAMED_PREFIX.source + index; + } + return path.join(figureDir, FILENAME_PREFIX.source + figureTitle); +} + +function captureFigureTitle(codeblockContent: string, titlePattern: string): string | undefined { + const pattern = r.parse(titlePattern); + if (!pattern) return undefined; + const match = codeblockContent.match(pattern); + const title = match?.[1]; + if (!title) return undefined; + return sanitizeFilename(title); +} + +function sanitizeFilename(input: string): string { + const trailingFilenames: RegExp = r.concat(FIGURE_FILENAME_EXTENSIONS, /$/); + return input + .replace(ILLEGAL_FILENAME_CHARS, ' ') // Remove illegal filename characters + .replace(/\s+/g, ' ') // Normalize whitespace + .trim() + .replace(r.concat(/^/, FILENAME_PREFIX), '') // Remove prefix + .replace(trailingFilenames, ''); // Remove file extension +} + +export function generalizeFigureTitle(figureName: string): RegExp { + const normalized: string = sanitizeFilename(figureName); + const escaped: RegExp = r.escape(normalized); + const whitespaced = new RegExp(escaped.source + .replace(/\s+/g, WHITESPACE_AND_ILLEGAL_CHARS.source)); // Also allow illegal filename characters in whitespace + return r.concat( + MAYBE_WHITESPACE_AND_ILLEGAL, + r.optional(FILENAME_PREFIX), // Optional prefix + MAYBE_WHITESPACE_AND_ILLEGAL, + whitespaced, + MAYBE_WHITESPACE_AND_ILLEGAL, + r.optional(FIGURE_FILENAME_EXTENSIONS), // Optional file extension + MAYBE_WHITESPACE_AND_ILLEGAL); +} + +function nextLatexFilenameIndex(maxIndex: number): number { + latexFilenameIndex %= maxIndex; + return latexFilenameIndex++; +} diff --git a/src/transforms/LatexFontHandler.ts b/src/transforms/LatexFontHandler.ts new file mode 100644 index 00000000..c589bdf5 --- /dev/null +++ b/src/transforms/LatexFontHandler.ts @@ -0,0 +1,104 @@ +import { Platform } from 'obsidian'; +import * as path from 'path'; +import { ExecutorSettings } from 'src/settings/Settings'; + +let validFonts = new Set; +let invalidFonts = new Set; + +interface FontNames { + main: string; + sans: string; + mono: string; +} + +/** Generates LaTeX font configuration based on system or Obsidian fonts. */ +export function addFontSpec(settings: ExecutorSettings): string { + const isPdflatex = path.basename(settings.latexCompilerPath).toLowerCase().includes('pdflatex'); + if (isPdflatex || settings.latexAdaptFont === '') return ''; + + const platformFonts = getPlatformFonts(); + const fontSpec = buildFontCommand(settings, platformFonts); + if (!fontSpec) return ''; + + const packageSrc = `\\usepackage{fontspec}\n`; + return packageSrc + fontSpec; +} + +/** Retrieves Obsidian's font settings from CSS variables. */ +function getObsidianFonts(cssVariable: string): string { + const cssDeclarations = getComputedStyle(document.body); + const fonts = cssDeclarations.getPropertyValue(cssVariable).split(`'??'`)[0]; + return sanitizeCommaList(fonts); +} + +/** Constructs LaTeX font commands based on the provided settings and platform-specific fonts. */ +function buildFontCommand(settings: ExecutorSettings, fonts: FontNames): string { + if (settings.latexAdaptFont === 'obsidian') { + fonts.main = [getObsidianFonts('--font-text'), fonts.main].join(','); + fonts.sans = [getObsidianFonts('--font-interface'), fonts.sans].join(','); + fonts.mono = [getObsidianFonts('--font-monospace'), fonts.mono].join(','); + } + const mainSrc = buildSetfont('main', fonts.main); + const sansSrc = buildSetfont('sans', fonts.sans); + const monoSrc = buildSetfont('mono', fonts.mono); + return mainSrc + sansSrc + monoSrc; +} + +/** Returns default system fonts based on current platform */ +function getPlatformFonts(): FontNames { + if (Platform.isWin) return { main: 'Segoe UI', sans: 'Segoe UI', mono: 'Consolas' }; + if (Platform.isMacOS) return { main: 'SF Pro', sans: 'SF Pro', mono: 'SF Mono' }; + if (Platform.isLinux) return { main: 'DejaVu Sans', sans: 'DejaVu Sans', mono: 'DejaVu Sans Mono' }; + return { main: '', sans: '', mono: '' }; +} + +/** Generates LuaLaTeX setfont command for specified font type. */ +function buildSetfont(type: 'main' | 'mono' | 'sans', fallbackList: string): string { + const font = firstValidFont(fallbackList); + return (font) ? `\\set${type}font{${font}}\n` : ''; +} + +function firstValidFont(fallbackList: string): string { + return sanitizeCommaList(fallbackList) + .split(', ') + .reduce((result, font) => result || (cachedTestFont(font) ? font : undefined), undefined); +} + +/** For performance, do not retest a font during the app's lifetime. */ +function cachedTestFont(fontName: string): boolean { + if (validFonts.has(fontName)) return true; + if (invalidFonts.has(fontName)) return false; + if (!testFont(fontName)) { + invalidFonts.add(fontName); + return false; + } + validFonts.add(fontName); + return true; +} + +/** Tests if a font is available by comparing text measurements on canvas. */ +function testFont(fontName: string): boolean { + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + if (!context) return false; + + const text = 'abcdefghijklmnopqrstuvwxyz'; + context.font = `16px monospace`; + const baselineWidth = context.measureText(text).width; + + context.font = `16px "${fontName}", monospace`; + const testWidth = context.measureText(text).width; + + const isFontAvailable = baselineWidth !== testWidth; + console.debug((isFontAvailable) ? `Font ${fontName} accepted.` : `Font ${fontName} ignored.`); + return isFontAvailable; +} + +/** Cleans and normalizes comma-separated font family lists */ +function sanitizeCommaList(commaList: string): string { + return commaList + .split(',') + .map(font => font.trim().replace(/^["']|["']$/g, '')) + .filter(Boolean) + .join(', '); +} diff --git a/src/transforms/LatexTransformer.ts b/src/transforms/LatexTransformer.ts new file mode 100644 index 00000000..a08e4737 --- /dev/null +++ b/src/transforms/LatexTransformer.ts @@ -0,0 +1,67 @@ +import { App } from 'obsidian'; +import { ExecutorSettings } from 'src/settings/Settings'; +import { addFontSpec } from './LatexFontHandler'; + +export let appInstance: App; +export let settingsInstance: ExecutorSettings; + +const DOCUMENT_CLASS: RegExp = /^[^%]*(?\\documentclass\s*(\[(?[^\]]*?)\])?\s*{\s*(?[^}]*?)\s*})/; +interface DocumentClass { + src: string, + class: string, + options: string, +} + +export function modifyLatexCode(latexSrc: string, settings: ExecutorSettings): string { + const documentClass: DocumentClass = captureDocumentClass(latexSrc) + const injectSrc = '' + + provideDocumentClass(documentClass?.class, settings.latexDocumentclass) + + addFontSpec(settings) + + disablePageNumberForCropping(settings); + latexSrc = injectSrc + latexSrc; + console.debug(`Injected LaTeX code:`, documentClass, injectSrc); + + latexSrc = moveDocumentClassToBeginning(latexSrc, documentClass); + return latexSrc; +} + +function disablePageNumberForCropping(settings: ExecutorSettings): string { + return (settings.latexDoCrop && settings.latexCropNoPagenum) + ? `\\pagestyle{empty}\n` : ''; +} + +function provideDocumentClass(currentClass: string, defaultClass: string): string { + return (currentClass || defaultClass === "") ? '' + : `\\documentclass{${defaultClass}}\n`; +} + +function moveDocumentClassToBeginning(latexSrc: string, documentClass: DocumentClass): string { + return (!documentClass?.src) ? latexSrc + : documentClass.src + '\n' + latexSrc.replace(documentClass.src, ''); +} + +function captureDocumentClass(latexSrc: string): DocumentClass | undefined { + const match: RegExpMatchArray = latexSrc.match(DOCUMENT_CLASS); + if (!match) return undefined; + return { src: match.groups?.src, class: match.groups?.class, options: match.groups?.options }; +} + +export function isStandaloneClass(latexSrc: string): boolean { + const className = captureDocumentClass(latexSrc)?.class; + return className === "standalone"; +} + +export function updateBodyClass(className: string, isActive: boolean) { + if (isActive) { + document.body.classList.add(className); + } else { + document.body.classList.remove(className); + } +} + +export function applyLatexBodyClasses(app: App, settings: ExecutorSettings) { + updateBodyClass('center-latex-figures', settings.latexCenterFigures); + updateBodyClass('invert-latex-figures', settings.latexInvertFigures); + appInstance = app; + settingsInstance = settings; +} From 1f119e12de1a03875fb7207158461225630d87eb Mon Sep 17 00:00:00 2001 From: Yetenol Date: Sun, 2 Feb 2025 20:39:38 +0100 Subject: [PATCH 13/34] describe LaTeX features with README examples --- CHANGELOG.md | 7 +- README.md | 94 +- images/figure_include_attachments.svg | 60 ++ images/figure_minimal_example.svg | 39 + images/figure_sidenotes_comparison.svg | 426 ++++++++ ...igure_sum_of_two_poisson_distributions.svg | 927 ++++++++++++++++++ images/figure_time_of_day.svg | 26 + 7 files changed, 1577 insertions(+), 2 deletions(-) create mode 100644 images/figure_include_attachments.svg create mode 100644 images/figure_minimal_example.svg create mode 100644 images/figure_sidenotes_comparison.svg create mode 100644 images/figure_sum_of_two_poisson_distributions.svg create mode 100644 images/figure_time_of_day.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index 492ceeb5..c2349476 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,12 @@ 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/), - +## [Unreleased] +### Added +- Support for LaTeX with image conversion to PDF, SVG, PNG (Thanks to @yetenol) +- LaTeX: Generated graphics are saved as attachment to your vault +- LaTeX: Option to crop PDF to content to remove excess whitespace +- LaTeX: Option to adopt Obsidian's font settings for PDF generation ## [2.0.0] ### Added diff --git a/README.md b/README.md index 83206fb4..f991788f 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ In blogs: Are you featuring this plugin in your content? Let me know and I will add it here. -## Supported programming languages 💻 +## Supported programming, typesetting languages 💻
JavaScript @@ -173,6 +173,98 @@ public class HelloWorld { ```
+
+LaTeX + +## LaTeX + +Requirements: LaTeX distribution like [MiKTeX](https://miktex.org/) or [TeX Live](https://www.tug.org/texlive/) is installed and the correct paths are set in the settings. + +### Minimal example + +Injects default document class. Consider setting *Crop to content*. + +```latex +\begin{document} +Hello World! +\end{document} +``` + +

Minimal example

+ + +### Name output file + +Set filename with `\title{…}`. Adds prefix `figure ` to avoid file name collisions, group all generated files, and for appearance CSS selectors. Click run again will overwrite the file, and refresh its embeddings in the active view. + +``` +![[figure time of day.svg]] +``` +```latex +\documentclass[border=2pt]{standalone} \title{time of day} +\usepackage{datetime2} +\begin{document} +The time is \DTMcurrenttime. +\end{document} +``` + +

Time of day

+ +### Include attachments + +Include files relative to the vault's attachment folder. Consider [listings](http://mirrors.ctan.org/macros/latex/contrib/listings/listings.pdf) for source code listings, [markdown](http://mirrors.ctan.org/macros/generic/markdown/markdown.html) for inputting markdown files as LaTeX code, and `\input{…}` to paste plaintext or LaTeX source files. +Layout with [graphbox](http://mirrors.ctan.org/macros/latex/contrib/graphbox/graphbox.pdf) for vertical alignment, or [tabularray](http://mirrors.ctan.org/macros/latex/contrib/tabularray/tabularray.pdf) for more complex alignment. + +```latex +\documentclass{standalone} \title{include_attachments} +\usepackage{graphicx} +\begin{document} +\includegraphics{figure time of day.pdf} \quad +\includegraphics{figure time of day.pdf} +\end{document} +``` + +

Include attachments

+ +### Automatically reruns to get cross-references right. + +For instance reference a label that appears later in the document. The plugin detects `LaTeX Warning: Label(s) may have changed. Rerun to get cross-references right.` during compilation and reruns until resolved. + +```latex +\documentclass{article} \title{sum of two poisson distribution} +\usepackage{mathtools,amsfonts} +\begin{document} +As seen in \eqref{eq:poisson}, we use convolutions of probability distributions for two independent poisson distributed random variables. +\begin{align*} \MoveEqLeft +\mathbb{P}(X + Y = k) = \sum_{m = 0}^\infty \mathbb{P}(X = m)\, \mathbb{P}(X = m - k) \tag{1}\label{eq:poisson} \\& += \sum_{m = 0}^k \frac{\lambda^m\, e^{-\lambda}}{m!} \cdot \frac{\mu^{k - m}\, e^{-\mu}}{(k - m)!} = \ldots +\end{align*} +\end{document} +``` + +

Sum of two poisson distributions

+ +Not all rerun requirements are easy to detect. Consider adding the package [lastpage](http://mirrors.ctan.org/macros/latex/contrib/lastpage/lastpage.pdf) to force a rerun, by creating an unresolved reference. + +```latex +\documentclass{article} \title{rerun sidenotes table} +\usepackage{sidenotes,tabularray,lipsum,lastpage} +\begin{document} \SetTblrInner{hlines} +\sidenotetext{This is a marginal note.} \lipsum[1][1-3] +\begin{table*} +\begin{tblr}[tall, caption={Expand table into page margins}]{X} + \lipsum[3][1-4] +\end{tblr} +\end{table*} +\end{document} +``` + +

Sum of two poisson distributions

+ +Explore [more LaTeX examples](https://antonpusch.de/latex), consult [package documentations](https://texdoc.org/), or learn about [LuaLaTeX](http://mirrors.ctan.org/obsolete/info/luatex/lualatex-doc/lualatex-doc.pdf). + +
+
Lua diff --git a/images/figure_include_attachments.svg b/images/figure_include_attachments.svg new file mode 100644 index 00000000..83961d36 --- /dev/null +++ b/images/figure_include_attachments.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + Thetimeis13:27:48. + + + Thetimeis13:27:48. + + + diff --git a/images/figure_minimal_example.svg b/images/figure_minimal_example.svg new file mode 100644 index 00000000..9caf696d --- /dev/null +++ b/images/figure_minimal_example.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + HelloWorld! + + + diff --git a/images/figure_sidenotes_comparison.svg b/images/figure_sidenotes_comparison.svg new file mode 100644 index 00000000..a4e6d6b3 --- /dev/null +++ b/images/figure_sidenotes_comparison.svg @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1Compileonce-Wrongleftmarginused + 1 + Thisisa + marginalnote. + Loremipsumdolorsitamet,consectetueradipiscingelit.Utpuruselit,vestibu- + lumut,placeratac,adipiscingvitae,felis.Curabiturdictumgravidamauris. + Table1:Expandtableintopagemargins + Nullamalesuadaporttitordiam.Donecfeliserat,conguenon,volutpatat,tincidunttristique, + libero.Vivamusviverrafermentumfelis.Donecnonummypellentesqueante. + + + + + + + 2Compiletwice-Correctlyusesrightmargin + 1 + Thisisa + marginalnote. + Loremipsumdolorsitamet,consectetueradipiscingelit.Utpuruselit,vestibu- + lumut,placeratac,adipiscingvitae,felis.Curabiturdictumgravidamauris. + Table1:Expandtableintopagemargins + Nullamalesuadaporttitordiam.Donecfeliserat,conguenon,volutpatat,tincidunttristique, + libero.Vivamusviverrafermentumfelis.Donecnonummypellentesqueante. + + + + + + diff --git a/images/figure_sum_of_two_poisson_distributions.svg b/images/figure_sum_of_two_poisson_distributions.svg new file mode 100644 index 00000000..a1ced1f2 --- /dev/null +++ b/images/figure_sum_of_two_poisson_distributions.svg @@ -0,0 +1,927 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Asseenin(1),weuseconvolutionsofprobabilitydistributionsfortwoinde- + pendentpoissondistributedrandomvariables. + + + + + + + + + + + + + + + + + + + + + + + + + + + + (1) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/figure_time_of_day.svg b/images/figure_time_of_day.svg new file mode 100644 index 00000000..e8df0902 --- /dev/null +++ b/images/figure_time_of_day.svg @@ -0,0 +1,26 @@ + + + + + + + Thetimeis13:27:48. + + From 2c246c6e286e46e8dfe2f3c9a57488e2a36e7870 Mon Sep 17 00:00:00 2001 From: Yetenol Date: Sun, 2 Feb 2025 02:28:25 +0100 Subject: [PATCH 14/34] refactor: extract forEach function for modularity, denesting --- src/main.ts | 50 ++++++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/main.ts b/src/main.ts index 28225f83..e96e98fb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -178,36 +178,38 @@ export default class ExecuteCodePlugin extends Plugin { */ private addRunButtons(element: HTMLElement, file: string, view: MarkdownView) { Array.from(element.getElementsByTagName("code")) - .forEach((codeBlock: HTMLElement) => { - if (codeBlock.className.match(/^language-\{\w+/i)) { - codeBlock.className = codeBlock.className.replace(/^language-\{(\w+)/i, "language-$1 {"); - codeBlock.parentElement.className = codeBlock.className; - } + .forEach((codeBlock: HTMLElement) => this.addRunButton(codeBlock, file, view)); + } - const language = codeBlock.className.toLowerCase(); + private addRunButton(codeBlock: HTMLElement, file: string, view: MarkdownView) { + if (codeBlock.className.match(/^language-\{\w+/i)) { + codeBlock.className = codeBlock.className.replace(/^language-\{(\w+)/i, "language-$1 {"); + codeBlock.parentElement.className = codeBlock.className; + } - if (!language || !language.contains("language-")) - return; + const language = codeBlock.className.toLowerCase(); - const pre = codeBlock.parentElement as HTMLPreElement; - const parent = pre.parentElement as HTMLDivElement; + if (!language || !language.contains("language-")) + return; - const srcCode = codeBlock.getText(); - let sanitizedClassList = this.sanitizeClassListOfCodeBlock(codeBlock); + const pre = codeBlock.parentElement as HTMLPreElement; + const parent = pre.parentElement as HTMLDivElement; - const canonicalLanguage = getLanguageAlias( - supportedLanguages.find(lang => sanitizedClassList.contains(`language-${lang}`)) - ) as LanguageId; + const srcCode = codeBlock.getText(); + let sanitizedClassList = this.sanitizeClassListOfCodeBlock(codeBlock); - if (canonicalLanguage // if the language is supported - && !parent.classList.contains(hasButtonClass)) { // & this block hasn't been buttonified already - const out = new Outputter(codeBlock, this.settings, view, this.app, file); - parent.classList.add(hasButtonClass); - const button = this.createRunButton(); - pre.appendChild(button); - this.addListenerToButton(canonicalLanguage, srcCode, button, out, file); - } - }); + const canonicalLanguage = getLanguageAlias( + supportedLanguages.find(lang => sanitizedClassList.contains(`language-${lang}`)) + ) as LanguageId; + + if (canonicalLanguage // if the language is supported + && !parent.classList.contains(hasButtonClass)) { // & this block hasn't been buttonified already + const out = new Outputter(codeBlock, this.settings, view, this.app, file); + parent.classList.add(hasButtonClass); + const button = this.createRunButton(); + pre.appendChild(button); + this.addListenerToButton(canonicalLanguage, srcCode, button, out, file); + } } private sanitizeClassListOfCodeBlock(codeBlock: HTMLElement) { From 20882a09065bb28d35319a5d798a7cf8eb487127 Mon Sep 17 00:00:00 2001 From: Yetenol Date: Sun, 2 Feb 2025 02:31:37 +0100 Subject: [PATCH 15/34] refactor: extract boolean logic for readability, denesting --- src/main.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main.ts b/src/main.ts index e96e98fb..221e84c7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -202,14 +202,15 @@ export default class ExecuteCodePlugin extends Plugin { supportedLanguages.find(lang => sanitizedClassList.contains(`language-${lang}`)) ) as LanguageId; - if (canonicalLanguage // if the language is supported - && !parent.classList.contains(hasButtonClass)) { // & this block hasn't been buttonified already - const out = new Outputter(codeBlock, this.settings, view, this.app, file); - parent.classList.add(hasButtonClass); - const button = this.createRunButton(); - pre.appendChild(button); - this.addListenerToButton(canonicalLanguage, srcCode, button, out, file); - } + const isLanguageSupported: Boolean = canonicalLanguage !== undefined; + const hasBlockBeenButtonifiedAlready = parent.classList.contains(hasButtonClass); + if (!isLanguageSupported || hasBlockBeenButtonifiedAlready) return; + + const out = new Outputter(codeBlock, this.settings, view, this.app, file); + parent.classList.add(hasButtonClass); + const button = this.createRunButton(); + pre.appendChild(button); + this.addListenerToButton(canonicalLanguage, srcCode, button, out, file); } private sanitizeClassListOfCodeBlock(codeBlock: HTMLElement) { From 85f3a5fb720bc5a236855407c76ecdc4baba4917 Mon Sep 17 00:00:00 2001 From: Yetenol Date: Sun, 2 Feb 2025 11:28:26 +0100 Subject: [PATCH 16/34] refactor: extract addEventListener to addRunButton() --- src/main.ts | 154 ++++++++++++---------------------------------------- 1 file changed, 35 insertions(+), 119 deletions(-) diff --git a/src/main.ts b/src/main.ts index 221e84c7..d0e1875e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -210,7 +210,7 @@ export default class ExecuteCodePlugin extends Plugin { parent.classList.add(hasButtonClass); const button = this.createRunButton(); pre.appendChild(button); - this.addListenerToButton(canonicalLanguage, srcCode, button, out, file); + button.addEventListener("click", () => this.handleExecution(canonicalLanguage, srcCode, button, out, file)); } private sanitizeClassListOfCodeBlock(codeBlock: HTMLElement) { @@ -219,33 +219,25 @@ export default class ExecuteCodePlugin extends Plugin { } /** - * Add a listener to the run button that executes the code block on click. - * Adds a different kind of listener for each supported language. - * + * Handles the execution of code blocks based on the selected programming language. + * Injects any required code, transforms the source if needed, and manages button state. * @param language The programming language used in the code block. * @param srcCode The code in the code block. * @param button The button element to which the listener is added. * @param out The {@link Outputter} object that is used to display the output of the code. * @param file The file that the code originates in */ - private addListenerToButton(language: LanguageId, srcCode: string, button: HTMLButtonElement, out: Outputter, file: string) { + private async handleExecution(language: LanguageId, srcCode: string, button: HTMLButtonElement, out: Outputter, file: string) { if (language === "js") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; let transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); transformedCode = addMagicToJS(transformedCode); - this.runCode(transformedCode, out, button, this.settings.nodePath, this.settings.nodeArgs, this.settings.jsFileExtension, language, file); - }); - + this.runCode(transformedCode, out, button, this.settings.nodePath, this.settings.nodeArgs, this.settings.jsFileExtension, language, file); } else if (language === "java") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCode(transformedCode, out, button, this.settings.javaPath, this.settings.javaArgs, this.settings.javaFileExtension, language, file); - }); - + this.runCode(transformedCode, out, button, this.settings.javaPath, this.settings.javaArgs, this.settings.javaFileExtension, language, file); } else if (language === "python") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; let transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); @@ -253,199 +245,124 @@ export default class ExecuteCodePlugin extends Plugin { transformedCode = addInlinePlotsToPython(transformedCode, TOGGLE_HTML_SIGIL); transformedCode = addMagicToPython(transformedCode); - this.runCode(transformedCode, out, button, this.settings.pythonPath, this.settings.pythonArgs, this.settings.pythonFileExtension, language, file); - }); - + this.runCode(transformedCode, out, button, this.settings.pythonPath, this.settings.pythonArgs, this.settings.pythonFileExtension, language, file); } else if (language === "shell") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCodeInShell(transformedCode, out, button, this.settings.shellPath, this.settings.shellArgs, this.settings.shellFileExtension, language, file); - }); - + this.runCodeInShell(transformedCode, out, button, this.settings.shellPath, this.settings.shellArgs, this.settings.shellFileExtension, language, file); } else if (language === "batch") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCodeInShell(transformedCode, out, button, this.settings.batchPath, this.settings.batchArgs, this.settings.batchFileExtension, language, file); - }); - + this.runCodeInShell(transformedCode, out, button, this.settings.batchPath, this.settings.batchArgs, this.settings.batchFileExtension, language, file); } else if (language === "powershell") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCodeInShell(transformedCode, out, button, this.settings.powershellPath, this.settings.powershellArgs, this.settings.powershellFileExtension, language, file); - }); - + this.runCodeInShell(transformedCode, out, button, this.settings.powershellPath, this.settings.powershellArgs, this.settings.powershellFileExtension, language, file); } else if (language === "cpp") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCode(transformedCode, out, button, this.settings.clingPath, `-std=${this.settings.clingStd} ${this.settings.clingArgs}`, this.settings.cppFileExtension, language, file); - }); - + this.runCode(transformedCode, out, button, this.settings.clingPath, `-std=${this.settings.clingStd} ${this.settings.clingArgs}`, this.settings.cppFileExtension, language, file); } else if (language === "prolog") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = (await new CodeInjector(this.app, this.settings, language).injectCode(srcCode)); this.runCode(transformedCode, out, button, "", "", "", language, file); - button.className = runButtonClass; - }); - + button.className = runButtonClass; } else if (language === "groovy") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCodeInShell(transformedCode, out, button, this.settings.groovyPath, this.settings.groovyArgs, this.settings.groovyFileExtension, language, file); - }); - + this.runCodeInShell(transformedCode, out, button, this.settings.groovyPath, this.settings.groovyArgs, this.settings.groovyFileExtension, language, file); } else if (language === "rust") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCode(transformedCode, out, button, this.settings.cargoPath, "eval" + this.settings.cargoEvalArgs, this.settings.rustFileExtension, language, file); - }); - + this.runCode(transformedCode, out, button, this.settings.cargoPath, "eval" + this.settings.cargoEvalArgs, this.settings.rustFileExtension, language, file); } else if (language === "r") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; let transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); transformedCode = addInlinePlotsToR(transformedCode); - this.runCode(transformedCode, out, button, this.settings.RPath, this.settings.RArgs, this.settings.RFileExtension, language, file); - }); - + this.runCode(transformedCode, out, button, this.settings.RPath, this.settings.RArgs, this.settings.RFileExtension, language, file); } else if (language === "go") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCode(transformedCode, out, button, this.settings.golangPath, this.settings.golangArgs, this.settings.golangFileExtension, language, file); - }); - + this.runCode(transformedCode, out, button, this.settings.golangPath, this.settings.golangArgs, this.settings.golangFileExtension, language, file); } else if (language === "kotlin") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCodeInShell(transformedCode, out, button, this.settings.kotlinPath, this.settings.kotlinArgs, this.settings.kotlinFileExtension, language, file); - }); - + this.runCodeInShell(transformedCode, out, button, this.settings.kotlinPath, this.settings.kotlinArgs, this.settings.kotlinFileExtension, language, file); } else if (language === "ts") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCodeInShell(transformedCode, out, button, this.settings.tsPath, this.settings.tsArgs, "ts", language, file); - }); - + this.runCodeInShell(transformedCode, out, button, this.settings.tsPath, this.settings.tsArgs, "ts", language, file); } else if (language === "lua") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCodeInShell(transformedCode, out, button, this.settings.luaPath, this.settings.luaArgs, this.settings.luaFileExtension, language, file); - }); - + this.runCodeInShell(transformedCode, out, button, this.settings.luaPath, this.settings.luaArgs, this.settings.luaFileExtension, language, file); } else if (language === "dart") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCodeInShell(transformedCode, out, button, this.settings.dartPath, this.settings.dartArgs, this.settings.dartFileExtension, language, file); - }); - + this.runCodeInShell(transformedCode, out, button, this.settings.dartPath, this.settings.dartArgs, this.settings.dartFileExtension, language, file); } else if (language === "cs") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCodeInShell(transformedCode, out, button, this.settings.csPath, this.settings.csArgs, this.settings.csFileExtension, language, file); - }); - + this.runCodeInShell(transformedCode, out, button, this.settings.csPath, this.settings.csArgs, this.settings.csFileExtension, language, file); } else if (language === "haskell") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, "haskell").injectCode(srcCode); - this.runCodeInShell(transformedCode, out, button, this.settings.useGhci ? this.settings.ghciPath : this.settings.runghcPath, this.settings.useGhci ? "" : "-f " + this.settings.ghcPath, "hs", language, file); - }); - + this.runCodeInShell(transformedCode, out, button, this.settings.useGhci ? this.settings.ghciPath : this.settings.runghcPath, this.settings.useGhci ? "" : "-f " + this.settings.ghcPath, "hs", language, file); } else if (language === "mathematica") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCodeInShell(transformedCode, out, button, this.settings.mathematicaPath, this.settings.mathematicaArgs, this.settings.mathematicaFileExtension, language, file); - }); + this.runCodeInShell(transformedCode, out, button, this.settings.mathematicaPath, this.settings.mathematicaArgs, this.settings.mathematicaFileExtension, language, file); } else if (language === "scala") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCodeInShell(transformedCode, out, button, this.settings.scalaPath, this.settings.scalaArgs, this.settings.scalaFileExtension, language, file); - }); + this.runCodeInShell(transformedCode, out, button, this.settings.scalaPath, this.settings.scalaArgs, this.settings.scalaFileExtension, language, file); } else if (language === "swift") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCodeInShell(transformedCode, out, button, this.settings.swiftPath, this.settings.swiftArgs, this.settings.swiftFileExtension, language, file); - }); - + this.runCodeInShell(transformedCode, out, button, this.settings.swiftPath, this.settings.swiftArgs, this.settings.swiftFileExtension, language, file); } else if (language === "c") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCodeInShell(transformedCode, out, button, this.settings.clingPath, this.settings.clingArgs, "c", language, file); - }) + this.runCodeInShell(transformedCode, out, button, this.settings.clingPath, this.settings.clingArgs, "c", language, file); } else if (language === "ruby") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCodeInShell(transformedCode, out, button, this.settings.rubyPath, this.settings.rubyArgs, this.settings.rubyFileExtension, language, file); - }) + this.runCodeInShell(transformedCode, out, button, this.settings.rubyPath, this.settings.rubyArgs, this.settings.rubyFileExtension, language, file); } else if (language === "sql") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCodeInShell(transformedCode, out, button, this.settings.sqlPath, this.settings.sqlArgs, "sql", language, file); - }) + this.runCodeInShell(transformedCode, out, button, this.settings.sqlPath, this.settings.sqlArgs, "sql", language, file); } else if (language === "octave") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; let transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); transformedCode = addInlinePlotsToOctave(transformedCode); - this.runCodeInShell(transformedCode, out, button, this.settings.octavePath, this.settings.octaveArgs, this.settings.octaveFileExtension, language, file); - }) + this.runCodeInShell(transformedCode, out, button, this.settings.octavePath, this.settings.octaveArgs, this.settings.octaveFileExtension, language, file); } else if (language === "maxima") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; let transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); transformedCode = addInlinePlotsToMaxima(transformedCode); - this.runCodeInShell(transformedCode, out, button, this.settings.maximaPath, this.settings.maximaArgs, this.settings.maximaFileExtension, language, file); - }) + this.runCodeInShell(transformedCode, out, button, this.settings.maximaPath, this.settings.maximaArgs, this.settings.maximaFileExtension, language, file); } else if (language === "racket") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCodeInShell(transformedCode, out, button, this.settings.racketPath, this.settings.racketArgs, this.settings.racketFileExtension, language, file); - }) + this.runCodeInShell(transformedCode, out, button, this.settings.racketPath, this.settings.racketArgs, this.settings.racketFileExtension, language, file); } else if (language === "applescript") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCodeInShell(transformedCode, out, button, this.settings.applescriptPath, this.settings.applescriptArgs, this.settings.applescriptFileExtension, language, file); - }) + this.runCodeInShell(transformedCode, out, button, this.settings.applescriptPath, this.settings.applescriptArgs, this.settings.applescriptFileExtension, language, file); } else if (language === "zig") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCodeInShell(transformedCode, out, button, this.settings.zigPath, this.settings.zigArgs, "zig", language, file); - }) + this.runCodeInShell(transformedCode, out, button, this.settings.zigPath, this.settings.zigArgs, "zig", language, file); } else if (language === "ocaml") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCodeInShell(transformedCode, out, button, this.settings.ocamlPath, this.settings.ocamlArgs, "ocaml", language, file); - }) + this.runCodeInShell(transformedCode, out, button, this.settings.ocamlPath, this.settings.ocamlArgs, "ocaml", language, file); } else if (language === "php") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - this.runCodeInShell(transformedCode, out, button, this.settings.phpPath, this.settings.phpArgs, this.settings.phpFileExtension, language, file); - }) + this.runCodeInShell(transformedCode, out, button, this.settings.phpPath, this.settings.phpArgs, this.settings.phpFileExtension, language, file); } else if (language === "latex") { - button.addEventListener("click", async () => { button.className = runButtonDisabledClass; let transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); transformedCode = modifyLatexCode(transformedCode, this.settings); @@ -455,7 +372,6 @@ export default class ExecuteCodePlugin extends Plugin { } else { this.runCode(transformedCode, out, button, this.settings.latexTexfotPath, [this.settings.latexTexfotArgs, this.settings.latexCompilerPath, this.settings.latexCompilerArgs].join(" "), outputPath, language, file); } - }); } } From b8c58ee537d4a01123e6290620d496ee798ec4d9 Mon Sep 17 00:00:00 2001 From: Yetenol Date: Sun, 2 Feb 2025 11:54:23 +0100 Subject: [PATCH 17/34] refactor: extract common commands from language cases --- src/main.ts | 99 ++++++++++------------------------------------------- 1 file changed, 18 insertions(+), 81 deletions(-) diff --git a/src/main.ts b/src/main.ts index d0e1875e..354a22bf 100644 --- a/src/main.ts +++ b/src/main.ts @@ -228,150 +228,87 @@ export default class ExecuteCodePlugin extends Plugin { * @param file The file that the code originates in */ private async handleExecution(language: LanguageId, srcCode: string, button: HTMLButtonElement, out: Outputter, file: string) { + button.className = runButtonDisabledClass; + let transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); + if (language === "js") { - button.className = runButtonDisabledClass; - let transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - transformedCode = addMagicToJS(transformedCode); + transformedCode = addMagicToJS(transformedCode); this.runCode(transformedCode, out, button, this.settings.nodePath, this.settings.nodeArgs, this.settings.jsFileExtension, language, file); } else if (language === "java") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCode(transformedCode, out, button, this.settings.javaPath, this.settings.javaArgs, this.settings.javaFileExtension, language, file); } else if (language === "python") { - button.className = runButtonDisabledClass; - let transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - - if (this.settings.pythonEmbedPlots) // embed plots into html which shows them in the note - transformedCode = addInlinePlotsToPython(transformedCode, TOGGLE_HTML_SIGIL); - transformedCode = addMagicToPython(transformedCode); - + if (this.settings.pythonEmbedPlots) // embed plots into html which shows them in the note + transformedCode = addInlinePlotsToPython(transformedCode, TOGGLE_HTML_SIGIL); + transformedCode = addMagicToPython(transformedCode); this.runCode(transformedCode, out, button, this.settings.pythonPath, this.settings.pythonArgs, this.settings.pythonFileExtension, language, file); } else if (language === "shell") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.shellPath, this.settings.shellArgs, this.settings.shellFileExtension, language, file); } else if (language === "batch") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.batchPath, this.settings.batchArgs, this.settings.batchFileExtension, language, file); } else if (language === "powershell") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.powershellPath, this.settings.powershellArgs, this.settings.powershellFileExtension, language, file); } else if (language === "cpp") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCode(transformedCode, out, button, this.settings.clingPath, `-std=${this.settings.clingStd} ${this.settings.clingArgs}`, this.settings.cppFileExtension, language, file); } else if (language === "prolog") { - button.className = runButtonDisabledClass; - const transformedCode = (await new CodeInjector(this.app, this.settings, language).injectCode(srcCode)); - this.runCode(transformedCode, out, button, "", "", "", language, file); + this.runCode(transformedCode, out, button, "", "", "", language, file); button.className = runButtonClass; } else if (language === "groovy") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.groovyPath, this.settings.groovyArgs, this.settings.groovyFileExtension, language, file); } else if (language === "rust") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCode(transformedCode, out, button, this.settings.cargoPath, "eval" + this.settings.cargoEvalArgs, this.settings.rustFileExtension, language, file); } else if (language === "r") { - button.className = runButtonDisabledClass; - let transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - transformedCode = addInlinePlotsToR(transformedCode); + transformedCode = addInlinePlotsToR(transformedCode); this.runCode(transformedCode, out, button, this.settings.RPath, this.settings.RArgs, this.settings.RFileExtension, language, file); } else if (language === "go") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCode(transformedCode, out, button, this.settings.golangPath, this.settings.golangArgs, this.settings.golangFileExtension, language, file); } else if (language === "kotlin") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.kotlinPath, this.settings.kotlinArgs, this.settings.kotlinFileExtension, language, file); } else if (language === "ts") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.tsPath, this.settings.tsArgs, "ts", language, file); } else if (language === "lua") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.luaPath, this.settings.luaArgs, this.settings.luaFileExtension, language, file); } else if (language === "dart") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.dartPath, this.settings.dartArgs, this.settings.dartFileExtension, language, file); } else if (language === "cs") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.csPath, this.settings.csArgs, this.settings.csFileExtension, language, file); } else if (language === "haskell") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, "haskell").injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.useGhci ? this.settings.ghciPath : this.settings.runghcPath, this.settings.useGhci ? "" : "-f " + this.settings.ghcPath, "hs", language, file); } else if (language === "mathematica") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.mathematicaPath, this.settings.mathematicaArgs, this.settings.mathematicaFileExtension, language, file); } else if (language === "scala") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.scalaPath, this.settings.scalaArgs, this.settings.scalaFileExtension, language, file); } else if (language === "swift") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.swiftPath, this.settings.swiftArgs, this.settings.swiftFileExtension, language, file); } else if (language === "c") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.clingPath, this.settings.clingArgs, "c", language, file); } else if (language === "ruby") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.rubyPath, this.settings.rubyArgs, this.settings.rubyFileExtension, language, file); } else if (language === "sql") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.sqlPath, this.settings.sqlArgs, "sql", language, file); } else if (language === "octave") { - button.className = runButtonDisabledClass; - let transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - transformedCode = addInlinePlotsToOctave(transformedCode); + transformedCode = addInlinePlotsToOctave(transformedCode); this.runCodeInShell(transformedCode, out, button, this.settings.octavePath, this.settings.octaveArgs, this.settings.octaveFileExtension, language, file); } else if (language === "maxima") { - button.className = runButtonDisabledClass; - let transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - transformedCode = addInlinePlotsToMaxima(transformedCode); + transformedCode = addInlinePlotsToMaxima(transformedCode); this.runCodeInShell(transformedCode, out, button, this.settings.maximaPath, this.settings.maximaArgs, this.settings.maximaFileExtension, language, file); } else if (language === "racket") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.racketPath, this.settings.racketArgs, this.settings.racketFileExtension, language, file); } else if (language === "applescript") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.applescriptPath, this.settings.applescriptArgs, this.settings.applescriptFileExtension, language, file); } else if (language === "zig") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.zigPath, this.settings.zigArgs, "zig", language, file); } else if (language === "ocaml") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.ocamlPath, this.settings.ocamlArgs, "ocaml", language, file); } else if (language === "php") { - button.className = runButtonDisabledClass; - const transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); this.runCodeInShell(transformedCode, out, button, this.settings.phpPath, this.settings.phpArgs, this.settings.phpFileExtension, language, file); } else if (language === "latex") { - button.className = runButtonDisabledClass; - let transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); - transformedCode = modifyLatexCode(transformedCode, this.settings); - const outputPath = await retrieveFigurePath(srcCode, this.settings.latexFigureTitlePattern, file, this.settings); - if (!this.settings.latexDoFilter) { - this.runCode(transformedCode, out, button, this.settings.latexCompilerPath, this.settings.latexCompilerArgs, outputPath, language, file); - } else { - this.runCode(transformedCode, out, button, this.settings.latexTexfotPath, [this.settings.latexTexfotArgs, this.settings.latexCompilerPath, this.settings.latexCompilerArgs].join(" "), outputPath, language, file); - } + transformedCode = modifyLatexCode(transformedCode, this.settings); + const outputPath = await retrieveFigurePath(srcCode, this.settings.latexFigureTitlePattern, file, this.settings); + if (!this.settings.latexDoFilter) { + this.runCode(transformedCode, out, button, this.settings.latexCompilerPath, this.settings.latexCompilerArgs, outputPath, language, file); + } else { + this.runCode(transformedCode, out, button, this.settings.latexTexfotPath, [this.settings.latexTexfotArgs, this.settings.latexCompilerPath, this.settings.latexCompilerArgs].join(" "), outputPath, language, file); + } } } From 1edb1f5cb5949dbe766baacfb0a462d83407c8f0 Mon Sep 17 00:00:00 2001 From: Yetenol Date: Sun, 2 Feb 2025 12:17:44 +0100 Subject: [PATCH 18/34] refactor: group common code block parameters into shared interface --- src/main.ts | 149 ++++++++++++++++++++++++++++------------------------ 1 file changed, 79 insertions(+), 70 deletions(-) diff --git a/src/main.ts b/src/main.ts index 354a22bf..66672343 100644 --- a/src/main.ts +++ b/src/main.ts @@ -44,6 +44,14 @@ export const runButtonClass = "run-code-button"; const runButtonDisabledClass = "run-button-disabled"; const hasButtonClass = "has-run-code-button"; +interface CodeBlockContext { + srcCode: string; + outputter: Outputter; + button: HTMLButtonElement; + language: LanguageId; + markdownFile: string; +} + export default class ExecuteCodePlugin extends Plugin { settings: ExecutorSettings; executors: ExecutorContainer; @@ -210,7 +218,16 @@ export default class ExecuteCodePlugin extends Plugin { parent.classList.add(hasButtonClass); const button = this.createRunButton(); pre.appendChild(button); - button.addEventListener("click", () => this.handleExecution(canonicalLanguage, srcCode, button, out, file)); + + const block: CodeBlockContext = { + srcCode: srcCode, + outputter: out, + language: canonicalLanguage, + markdownFile: file, + button: button, + }; + + button.addEventListener("click", () => this.handleExecution(block)); } private sanitizeClassListOfCodeBlock(codeBlock: HTMLElement) { @@ -218,96 +235,96 @@ export default class ExecuteCodePlugin extends Plugin { return sanitizedClassList.map(c => c.toLowerCase()); } - /** +/** * Handles the execution of code blocks based on the selected programming language. * Injects any required code, transforms the source if needed, and manages button state. - * @param language The programming language used in the code block. - * @param srcCode The code in the code block. - * @param button The button element to which the listener is added. - * @param out The {@link Outputter} object that is used to display the output of the code. - * @param file The file that the code originates in + * @param block Contains context needed for execution including source code, output handler, and UI elements */ - private async handleExecution(language: LanguageId, srcCode: string, button: HTMLButtonElement, out: Outputter, file: string) { + private async handleExecution(block: CodeBlockContext) { + const language: LanguageId = block.language; + const button: HTMLButtonElement = block.button; + const srcCode: string = block.srcCode; + button.className = runButtonDisabledClass; - let transformedCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); + block.srcCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); if (language === "js") { - transformedCode = addMagicToJS(transformedCode); - this.runCode(transformedCode, out, button, this.settings.nodePath, this.settings.nodeArgs, this.settings.jsFileExtension, language, file); + block.srcCode = addMagicToJS(block.srcCode); + this.runCode(this.settings.nodePath, this.settings.nodeArgs, this.settings.jsFileExtension, block); } else if (language === "java") { - this.runCode(transformedCode, out, button, this.settings.javaPath, this.settings.javaArgs, this.settings.javaFileExtension, language, file); + this.runCode(this.settings.javaPath, this.settings.javaArgs, this.settings.javaFileExtension, block); } else if (language === "python") { if (this.settings.pythonEmbedPlots) // embed plots into html which shows them in the note - transformedCode = addInlinePlotsToPython(transformedCode, TOGGLE_HTML_SIGIL); - transformedCode = addMagicToPython(transformedCode); - this.runCode(transformedCode, out, button, this.settings.pythonPath, this.settings.pythonArgs, this.settings.pythonFileExtension, language, file); + block.srcCode = addInlinePlotsToPython(block.srcCode, TOGGLE_HTML_SIGIL); + block.srcCode = addMagicToPython(block.srcCode); + this.runCode(this.settings.pythonPath, this.settings.pythonArgs, this.settings.pythonFileExtension, block); } else if (language === "shell") { - this.runCodeInShell(transformedCode, out, button, this.settings.shellPath, this.settings.shellArgs, this.settings.shellFileExtension, language, file); + this.runCodeInShell(this.settings.shellPath, this.settings.shellArgs, this.settings.shellFileExtension, block); } else if (language === "batch") { - this.runCodeInShell(transformedCode, out, button, this.settings.batchPath, this.settings.batchArgs, this.settings.batchFileExtension, language, file); + this.runCodeInShell(this.settings.batchPath, this.settings.batchArgs, this.settings.batchFileExtension, block); } else if (language === "powershell") { - this.runCodeInShell(transformedCode, out, button, this.settings.powershellPath, this.settings.powershellArgs, this.settings.powershellFileExtension, language, file); + this.runCodeInShell(this.settings.powershellPath, this.settings.powershellArgs, this.settings.powershellFileExtension, block); } else if (language === "cpp") { - this.runCode(transformedCode, out, button, this.settings.clingPath, `-std=${this.settings.clingStd} ${this.settings.clingArgs}`, this.settings.cppFileExtension, language, file); + this.runCode(this.settings.clingPath, `-std=${this.settings.clingStd} ${this.settings.clingArgs}`, this.settings.cppFileExtension, block); } else if (language === "prolog") { - this.runCode(transformedCode, out, button, "", "", "", language, file); + this.runCode("", "", "", block); button.className = runButtonClass; } else if (language === "groovy") { - this.runCodeInShell(transformedCode, out, button, this.settings.groovyPath, this.settings.groovyArgs, this.settings.groovyFileExtension, language, file); + this.runCodeInShell(this.settings.groovyPath, this.settings.groovyArgs, this.settings.groovyFileExtension, block); } else if (language === "rust") { - this.runCode(transformedCode, out, button, this.settings.cargoPath, "eval" + this.settings.cargoEvalArgs, this.settings.rustFileExtension, language, file); + this.runCode(this.settings.cargoPath, "eval" + this.settings.cargoEvalArgs, this.settings.rustFileExtension, block); } else if (language === "r") { - transformedCode = addInlinePlotsToR(transformedCode); - this.runCode(transformedCode, out, button, this.settings.RPath, this.settings.RArgs, this.settings.RFileExtension, language, file); + block.srcCode = addInlinePlotsToR(block.srcCode); + this.runCode(this.settings.RPath, this.settings.RArgs, this.settings.RFileExtension, block); } else if (language === "go") { - this.runCode(transformedCode, out, button, this.settings.golangPath, this.settings.golangArgs, this.settings.golangFileExtension, language, file); + this.runCode(this.settings.golangPath, this.settings.golangArgs, this.settings.golangFileExtension, block); } else if (language === "kotlin") { - this.runCodeInShell(transformedCode, out, button, this.settings.kotlinPath, this.settings.kotlinArgs, this.settings.kotlinFileExtension, language, file); + this.runCodeInShell(this.settings.kotlinPath, this.settings.kotlinArgs, this.settings.kotlinFileExtension, block); } else if (language === "ts") { - this.runCodeInShell(transformedCode, out, button, this.settings.tsPath, this.settings.tsArgs, "ts", language, file); + this.runCodeInShell(this.settings.tsPath, this.settings.tsArgs, "ts", block); } else if (language === "lua") { - this.runCodeInShell(transformedCode, out, button, this.settings.luaPath, this.settings.luaArgs, this.settings.luaFileExtension, language, file); + this.runCodeInShell(this.settings.luaPath, this.settings.luaArgs, this.settings.luaFileExtension, block); } else if (language === "dart") { - this.runCodeInShell(transformedCode, out, button, this.settings.dartPath, this.settings.dartArgs, this.settings.dartFileExtension, language, file); + this.runCodeInShell(this.settings.dartPath, this.settings.dartArgs, this.settings.dartFileExtension, block); } else if (language === "cs") { - this.runCodeInShell(transformedCode, out, button, this.settings.csPath, this.settings.csArgs, this.settings.csFileExtension, language, file); + this.runCodeInShell(this.settings.csPath, this.settings.csArgs, this.settings.csFileExtension, block); } else if (language === "haskell") { - this.runCodeInShell(transformedCode, out, button, this.settings.useGhci ? this.settings.ghciPath : this.settings.runghcPath, this.settings.useGhci ? "" : "-f " + this.settings.ghcPath, "hs", language, file); + this.runCodeInShell(this.settings.useGhci ? this.settings.ghciPath : this.settings.runghcPath, this.settings.useGhci ? "" : "-f " + this.settings.ghcPath, "hs", block); } else if (language === "mathematica") { - this.runCodeInShell(transformedCode, out, button, this.settings.mathematicaPath, this.settings.mathematicaArgs, this.settings.mathematicaFileExtension, language, file); + this.runCodeInShell(this.settings.mathematicaPath, this.settings.mathematicaArgs, this.settings.mathematicaFileExtension, block); } else if (language === "scala") { - this.runCodeInShell(transformedCode, out, button, this.settings.scalaPath, this.settings.scalaArgs, this.settings.scalaFileExtension, language, file); + this.runCodeInShell(this.settings.scalaPath, this.settings.scalaArgs, this.settings.scalaFileExtension, block); } else if (language === "swift") { - this.runCodeInShell(transformedCode, out, button, this.settings.swiftPath, this.settings.swiftArgs, this.settings.swiftFileExtension, language, file); + this.runCodeInShell(this.settings.swiftPath, this.settings.swiftArgs, this.settings.swiftFileExtension, block); } else if (language === "c") { - this.runCodeInShell(transformedCode, out, button, this.settings.clingPath, this.settings.clingArgs, "c", language, file); + this.runCodeInShell(this.settings.clingPath, this.settings.clingArgs, "c", block); } else if (language === "ruby") { - this.runCodeInShell(transformedCode, out, button, this.settings.rubyPath, this.settings.rubyArgs, this.settings.rubyFileExtension, language, file); + this.runCodeInShell(this.settings.rubyPath, this.settings.rubyArgs, this.settings.rubyFileExtension, block); } else if (language === "sql") { - this.runCodeInShell(transformedCode, out, button, this.settings.sqlPath, this.settings.sqlArgs, "sql", language, file); + this.runCodeInShell(this.settings.sqlPath, this.settings.sqlArgs, "sql", block); } else if (language === "octave") { - transformedCode = addInlinePlotsToOctave(transformedCode); - this.runCodeInShell(transformedCode, out, button, this.settings.octavePath, this.settings.octaveArgs, this.settings.octaveFileExtension, language, file); + block.srcCode = addInlinePlotsToOctave(block.srcCode); + this.runCodeInShell(this.settings.octavePath, this.settings.octaveArgs, this.settings.octaveFileExtension, block); } else if (language === "maxima") { - transformedCode = addInlinePlotsToMaxima(transformedCode); - this.runCodeInShell(transformedCode, out, button, this.settings.maximaPath, this.settings.maximaArgs, this.settings.maximaFileExtension, language, file); + block.srcCode = addInlinePlotsToMaxima(block.srcCode); + this.runCodeInShell(this.settings.maximaPath, this.settings.maximaArgs, this.settings.maximaFileExtension, block); } else if (language === "racket") { - this.runCodeInShell(transformedCode, out, button, this.settings.racketPath, this.settings.racketArgs, this.settings.racketFileExtension, language, file); + this.runCodeInShell(this.settings.racketPath, this.settings.racketArgs, this.settings.racketFileExtension, block); } else if (language === "applescript") { - this.runCodeInShell(transformedCode, out, button, this.settings.applescriptPath, this.settings.applescriptArgs, this.settings.applescriptFileExtension, language, file); + this.runCodeInShell(this.settings.applescriptPath, this.settings.applescriptArgs, this.settings.applescriptFileExtension, block); } else if (language === "zig") { - this.runCodeInShell(transformedCode, out, button, this.settings.zigPath, this.settings.zigArgs, "zig", language, file); + this.runCodeInShell(this.settings.zigPath, this.settings.zigArgs, "zig", block); } else if (language === "ocaml") { - this.runCodeInShell(transformedCode, out, button, this.settings.ocamlPath, this.settings.ocamlArgs, "ocaml", language, file); + this.runCodeInShell(this.settings.ocamlPath, this.settings.ocamlArgs, "ocaml", block); } else if (language === "php") { - this.runCodeInShell(transformedCode, out, button, this.settings.phpPath, this.settings.phpArgs, this.settings.phpFileExtension, language, file); + this.runCodeInShell(this.settings.phpPath, this.settings.phpArgs, this.settings.phpFileExtension, block); } else if (language === "latex") { - transformedCode = modifyLatexCode(transformedCode, this.settings); - const outputPath = await retrieveFigurePath(srcCode, this.settings.latexFigureTitlePattern, file, this.settings); + block.srcCode = modifyLatexCode(block.srcCode, this.settings); + const outputPath = await retrieveFigurePath(block.srcCode, this.settings.latexFigureTitlePattern, block.markdownFile, this.settings); if (!this.settings.latexDoFilter) { - this.runCode(transformedCode, out, button, this.settings.latexCompilerPath, this.settings.latexCompilerArgs, outputPath, language, file); + this.runCode(this.settings.latexCompilerPath, this.settings.latexCompilerArgs, outputPath, block); } else { - this.runCode(transformedCode, out, button, this.settings.latexTexfotPath, [this.settings.latexTexfotArgs, this.settings.latexCompilerPath, this.settings.latexCompilerArgs].join(" "), outputPath, language, file); + this.runCode(this.settings.latexTexfotPath, [this.settings.latexTexfotArgs, this.settings.latexCompilerPath, this.settings.latexCompilerArgs].join(" "), outputPath, block); } } } @@ -330,21 +347,18 @@ export default class ExecuteCodePlugin extends Plugin { * The output of the code is displayed in the output panel ({@link Outputter}). * If the code execution fails, an error message is displayed and logged. * After the code execution, the temporary file is deleted and the run button is re-enabled. - * - * @param codeBlockContent The content of the code block that should be executed. - * @param outputter The {@link Outputter} that should be used to display the output of the code. - * @param button The button that was clicked to execute the code. Is re-enabled after the code execution. * @param cmd The command that should be used to execute the code. (e.g. python, java, ...) * @param cmdArgs Additional arguments that should be passed to the command. * @param ext The file extension of the temporary file. Should correspond to the language of the code. (e.g. py, ...) - * @param language The canonical ID of the language being run - * @param file The address of the file which the code originates from + * @param block Contains context needed for execution including source code, output handler, and UI elements */ - private runCode(codeBlockContent: string, outputter: Outputter, button: HTMLButtonElement, cmd: string, cmdArgs: string, ext: string, language: LanguageId, file: string) { + private runCode(cmd: string, cmdArgs: string, ext: string, block: CodeBlockContext) { + const outputter: Outputter = block.outputter; + outputter.startBlock(); - const executor = this.executors.getExecutorFor(file, language, false); - executor.run(codeBlockContent, outputter, cmd, cmdArgs, ext).then(() => { - button.className = runButtonClass; + const executor = this.executors.getExecutorFor(block.markdownFile, block.language, false); + executor.run(block.srcCode, outputter, cmd, cmdArgs, ext).then(() => { + block.button.className = runButtonClass; outputter.closeInput(); outputter.finishBlock(); }); @@ -353,20 +367,15 @@ export default class ExecuteCodePlugin extends Plugin { /** * Executes the code with the given command and arguments. The code is written to a temporary file and then executed. * This is equal to {@link runCode} but the code is executed in a shell. This is necessary for some languages like groovy. - * - * @param codeBlockContent The content of the code block that should be executed. - * @param outputter The {@link Outputter} that should be used to display the output of the code. - * @param button The button that was clicked to execute the code. Is re-enabled after the code execution. * @param cmd The command that should be used to execute the code. (e.g. python, java, ...) * @param cmdArgs Additional arguments that should be passed to the command. * @param ext The file extension of the temporary file. Should correspond to the language of the code. (e.g. py, ...) - * @param language The canonical ID of the language being run - * @param file The address of the file which the code originates from + * @param block Contains context needed for execution including source code, output handler, and UI elements */ - private runCodeInShell(codeBlockContent: string, outputter: Outputter, button: HTMLButtonElement, cmd: string, cmdArgs: string, ext: string, language: LanguageId, file: string) { - const executor = this.executors.getExecutorFor(file, language, true); - executor.run(codeBlockContent, outputter, cmd, cmdArgs, ext).then(() => { - button.className = runButtonClass; + private runCodeInShell(cmd: string, cmdArgs: string, ext: string, block: CodeBlockContext) { + const executor = this.executors.getExecutorFor(block.markdownFile, block.language, true); + executor.run(block.srcCode, block.outputter, cmd, cmdArgs, ext).then(() => { + block.button.className = runButtonClass; }); } } From 95e54796aeb3d0ba100ca05ca8ca3b4fad86a7e3 Mon Sep 17 00:00:00 2001 From: Yetenol Date: Sun, 2 Feb 2025 03:10:19 +0100 Subject: [PATCH 19/34] refactor: alias this.settings --- src/main.ts | 85 +++++++++++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/src/main.ts b/src/main.ts index 66672343..083fe2c7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -235,96 +235,97 @@ export default class ExecuteCodePlugin extends Plugin { return sanitizedClassList.map(c => c.toLowerCase()); } -/** - * Handles the execution of code blocks based on the selected programming language. - * Injects any required code, transforms the source if needed, and manages button state. - * @param block Contains context needed for execution including source code, output handler, and UI elements - */ + /** + * Handles the execution of code blocks based on the selected programming language. + * Injects any required code, transforms the source if needed, and manages button state. + * @param block Contains context needed for execution including source code, output handler, and UI elements + */ private async handleExecution(block: CodeBlockContext) { const language: LanguageId = block.language; const button: HTMLButtonElement = block.button; const srcCode: string = block.srcCode; + const s: ExecutorSettings = this.settings; button.className = runButtonDisabledClass; - block.srcCode = await new CodeInjector(this.app, this.settings, language).injectCode(srcCode); + block.srcCode = await new CodeInjector(this.app, s, language).injectCode(srcCode); if (language === "js") { block.srcCode = addMagicToJS(block.srcCode); - this.runCode(this.settings.nodePath, this.settings.nodeArgs, this.settings.jsFileExtension, block); + this.runCode(s.nodePath, s.nodeArgs, s.jsFileExtension, block); } else if (language === "java") { - this.runCode(this.settings.javaPath, this.settings.javaArgs, this.settings.javaFileExtension, block); + this.runCode(s.javaPath, s.javaArgs, s.javaFileExtension, block); } else if (language === "python") { - if (this.settings.pythonEmbedPlots) // embed plots into html which shows them in the note + if (s.pythonEmbedPlots) // embed plots into html which shows them in the note block.srcCode = addInlinePlotsToPython(block.srcCode, TOGGLE_HTML_SIGIL); block.srcCode = addMagicToPython(block.srcCode); - this.runCode(this.settings.pythonPath, this.settings.pythonArgs, this.settings.pythonFileExtension, block); + this.runCode(s.pythonPath, s.pythonArgs, s.pythonFileExtension, block); } else if (language === "shell") { - this.runCodeInShell(this.settings.shellPath, this.settings.shellArgs, this.settings.shellFileExtension, block); + this.runCodeInShell(s.shellPath, s.shellArgs, s.shellFileExtension, block); } else if (language === "batch") { - this.runCodeInShell(this.settings.batchPath, this.settings.batchArgs, this.settings.batchFileExtension, block); + this.runCodeInShell(s.batchPath, s.batchArgs, s.batchFileExtension, block); } else if (language === "powershell") { - this.runCodeInShell(this.settings.powershellPath, this.settings.powershellArgs, this.settings.powershellFileExtension, block); + this.runCodeInShell(s.powershellPath, s.powershellArgs, s.powershellFileExtension, block); } else if (language === "cpp") { - this.runCode(this.settings.clingPath, `-std=${this.settings.clingStd} ${this.settings.clingArgs}`, this.settings.cppFileExtension, block); + this.runCode(s.clingPath, `-std=${s.clingStd} ${s.clingArgs}`, s.cppFileExtension, block); } else if (language === "prolog") { this.runCode("", "", "", block); button.className = runButtonClass; } else if (language === "groovy") { - this.runCodeInShell(this.settings.groovyPath, this.settings.groovyArgs, this.settings.groovyFileExtension, block); + this.runCodeInShell(s.groovyPath, s.groovyArgs, s.groovyFileExtension, block); } else if (language === "rust") { - this.runCode(this.settings.cargoPath, "eval" + this.settings.cargoEvalArgs, this.settings.rustFileExtension, block); + this.runCode(s.cargoPath, "eval" + s.cargoEvalArgs, s.rustFileExtension, block); } else if (language === "r") { block.srcCode = addInlinePlotsToR(block.srcCode); - this.runCode(this.settings.RPath, this.settings.RArgs, this.settings.RFileExtension, block); + this.runCode(s.RPath, s.RArgs, s.RFileExtension, block); } else if (language === "go") { - this.runCode(this.settings.golangPath, this.settings.golangArgs, this.settings.golangFileExtension, block); + this.runCode(s.golangPath, s.golangArgs, s.golangFileExtension, block); } else if (language === "kotlin") { - this.runCodeInShell(this.settings.kotlinPath, this.settings.kotlinArgs, this.settings.kotlinFileExtension, block); + this.runCodeInShell(s.kotlinPath, s.kotlinArgs, s.kotlinFileExtension, block); } else if (language === "ts") { - this.runCodeInShell(this.settings.tsPath, this.settings.tsArgs, "ts", block); + this.runCodeInShell(s.tsPath, s.tsArgs, "ts", block); } else if (language === "lua") { - this.runCodeInShell(this.settings.luaPath, this.settings.luaArgs, this.settings.luaFileExtension, block); + this.runCodeInShell(s.luaPath, s.luaArgs, s.luaFileExtension, block); } else if (language === "dart") { - this.runCodeInShell(this.settings.dartPath, this.settings.dartArgs, this.settings.dartFileExtension, block); + this.runCodeInShell(s.dartPath, s.dartArgs, s.dartFileExtension, block); } else if (language === "cs") { - this.runCodeInShell(this.settings.csPath, this.settings.csArgs, this.settings.csFileExtension, block); + this.runCodeInShell(s.csPath, s.csArgs, s.csFileExtension, block); } else if (language === "haskell") { - this.runCodeInShell(this.settings.useGhci ? this.settings.ghciPath : this.settings.runghcPath, this.settings.useGhci ? "" : "-f " + this.settings.ghcPath, "hs", block); + this.runCodeInShell(s.useGhci ? s.ghciPath : s.runghcPath, s.useGhci ? "" : "-f " + s.ghcPath, "hs", block); } else if (language === "mathematica") { - this.runCodeInShell(this.settings.mathematicaPath, this.settings.mathematicaArgs, this.settings.mathematicaFileExtension, block); + this.runCodeInShell(s.mathematicaPath, s.mathematicaArgs, s.mathematicaFileExtension, block); } else if (language === "scala") { - this.runCodeInShell(this.settings.scalaPath, this.settings.scalaArgs, this.settings.scalaFileExtension, block); + this.runCodeInShell(s.scalaPath, s.scalaArgs, s.scalaFileExtension, block); } else if (language === "swift") { - this.runCodeInShell(this.settings.swiftPath, this.settings.swiftArgs, this.settings.swiftFileExtension, block); + this.runCodeInShell(s.swiftPath, s.swiftArgs, s.swiftFileExtension, block); } else if (language === "c") { - this.runCodeInShell(this.settings.clingPath, this.settings.clingArgs, "c", block); + this.runCodeInShell(s.clingPath, s.clingArgs, "c", block); } else if (language === "ruby") { - this.runCodeInShell(this.settings.rubyPath, this.settings.rubyArgs, this.settings.rubyFileExtension, block); + this.runCodeInShell(s.rubyPath, s.rubyArgs, s.rubyFileExtension, block); } else if (language === "sql") { - this.runCodeInShell(this.settings.sqlPath, this.settings.sqlArgs, "sql", block); + this.runCodeInShell(s.sqlPath, s.sqlArgs, "sql", block); } else if (language === "octave") { block.srcCode = addInlinePlotsToOctave(block.srcCode); - this.runCodeInShell(this.settings.octavePath, this.settings.octaveArgs, this.settings.octaveFileExtension, block); + this.runCodeInShell(s.octavePath, s.octaveArgs, s.octaveFileExtension, block); } else if (language === "maxima") { block.srcCode = addInlinePlotsToMaxima(block.srcCode); - this.runCodeInShell(this.settings.maximaPath, this.settings.maximaArgs, this.settings.maximaFileExtension, block); + this.runCodeInShell(s.maximaPath, s.maximaArgs, s.maximaFileExtension, block); } else if (language === "racket") { - this.runCodeInShell(this.settings.racketPath, this.settings.racketArgs, this.settings.racketFileExtension, block); + this.runCodeInShell(s.racketPath, s.racketArgs, s.racketFileExtension, block); } else if (language === "applescript") { - this.runCodeInShell(this.settings.applescriptPath, this.settings.applescriptArgs, this.settings.applescriptFileExtension, block); + this.runCodeInShell(s.applescriptPath, s.applescriptArgs, s.applescriptFileExtension, block); } else if (language === "zig") { - this.runCodeInShell(this.settings.zigPath, this.settings.zigArgs, "zig", block); + this.runCodeInShell(s.zigPath, s.zigArgs, "zig", block); } else if (language === "ocaml") { - this.runCodeInShell(this.settings.ocamlPath, this.settings.ocamlArgs, "ocaml", block); + this.runCodeInShell(s.ocamlPath, s.ocamlArgs, "ocaml", block); } else if (language === "php") { - this.runCodeInShell(this.settings.phpPath, this.settings.phpArgs, this.settings.phpFileExtension, block); + this.runCodeInShell(s.phpPath, s.phpArgs, s.phpFileExtension, block); } else if (language === "latex") { - block.srcCode = modifyLatexCode(block.srcCode, this.settings); - const outputPath = await retrieveFigurePath(block.srcCode, this.settings.latexFigureTitlePattern, block.markdownFile, this.settings); - if (!this.settings.latexDoFilter) { - this.runCode(this.settings.latexCompilerPath, this.settings.latexCompilerArgs, outputPath, block); + block.srcCode = modifyLatexCode(block.srcCode, s); + const outputPath = await retrieveFigurePath(block.srcCode, s.latexFigureTitlePattern, block.markdownFile, s); + if (!s.latexDoFilter) { + this.runCode(s.latexCompilerPath, s.latexCompilerArgs, outputPath, block); } else { - this.runCode(this.settings.latexTexfotPath, [this.settings.latexTexfotArgs, this.settings.latexCompilerPath, this.settings.latexCompilerArgs].join(" "), outputPath, block); + this.runCode(s.latexTexfotPath, [s.latexTexfotArgs, s.latexCompilerPath, s.latexCompilerArgs].join(" "), outputPath, block); } } } From f2fef685de864ae7a6baac3f14273bd7e356b93e Mon Sep 17 00:00:00 2001 From: Yetenol Date: Sun, 2 Feb 2025 12:32:58 +0100 Subject: [PATCH 20/34] refactor: move pythonEmbedPlots() into addMagicToPython() --- src/main.ts | 19 ++++++++----------- src/transforms/Magic.ts | 6 +++++- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/main.ts b/src/main.ts index 083fe2c7..7c0cdf29 100644 --- a/src/main.ts +++ b/src/main.ts @@ -239,7 +239,7 @@ export default class ExecuteCodePlugin extends Plugin { * Handles the execution of code blocks based on the selected programming language. * Injects any required code, transforms the source if needed, and manages button state. * @param block Contains context needed for execution including source code, output handler, and UI elements - */ + */ private async handleExecution(block: CodeBlockContext) { const language: LanguageId = block.language; const button: HTMLButtonElement = block.button; @@ -255,9 +255,7 @@ export default class ExecuteCodePlugin extends Plugin { } else if (language === "java") { this.runCode(s.javaPath, s.javaArgs, s.javaFileExtension, block); } else if (language === "python") { - if (s.pythonEmbedPlots) // embed plots into html which shows them in the note - block.srcCode = addInlinePlotsToPython(block.srcCode, TOGGLE_HTML_SIGIL); - block.srcCode = addMagicToPython(block.srcCode); + block.srcCode = addMagicToPython(block.srcCode, s); this.runCode(s.pythonPath, s.pythonArgs, s.pythonFileExtension, block); } else if (language === "shell") { this.runCodeInShell(s.shellPath, s.shellArgs, s.shellFileExtension, block); @@ -290,7 +288,8 @@ export default class ExecuteCodePlugin extends Plugin { } else if (language === "cs") { this.runCodeInShell(s.csPath, s.csArgs, s.csFileExtension, block); } else if (language === "haskell") { - this.runCodeInShell(s.useGhci ? s.ghciPath : s.runghcPath, s.useGhci ? "" : "-f " + s.ghcPath, "hs", block); + if (s.useGhci) this.runCodeInShell(s.ghciPath, "", "hs", block); + else this.runCodeInShell(s.runghcPath, "-f " + s.ghcPath, "hs", block); } else if (language === "mathematica") { this.runCodeInShell(s.mathematicaPath, s.mathematicaArgs, s.mathematicaFileExtension, block); } else if (language === "scala") { @@ -321,12 +320,10 @@ export default class ExecuteCodePlugin extends Plugin { this.runCodeInShell(s.phpPath, s.phpArgs, s.phpFileExtension, block); } else if (language === "latex") { block.srcCode = modifyLatexCode(block.srcCode, s); - const outputPath = await retrieveFigurePath(block.srcCode, s.latexFigureTitlePattern, block.markdownFile, s); - if (!s.latexDoFilter) { - this.runCode(s.latexCompilerPath, s.latexCompilerArgs, outputPath, block); - } else { - this.runCode(s.latexTexfotPath, [s.latexTexfotArgs, s.latexCompilerPath, s.latexCompilerArgs].join(" "), outputPath, block); - } + const outputPath: string = await retrieveFigurePath(block.srcCode, s.latexFigureTitlePattern, block.markdownFile, s); + const invokeCompiler: string = [s.latexTexfotArgs, s.latexCompilerPath, s.latexCompilerArgs].join(" "); + if (!s.latexDoFilter) this.runCode(s.latexCompilerPath, s.latexCompilerArgs, outputPath, block); + else this.runCode(s.latexTexfotPath, invokeCompiler, outputPath, block); } } diff --git a/src/transforms/Magic.ts b/src/transforms/Magic.ts index 91087082..62ebfa0c 100644 --- a/src/transforms/Magic.ts +++ b/src/transforms/Magic.ts @@ -14,6 +14,7 @@ import * as os from "os"; import {Platform} from 'obsidian'; import { TOGGLE_HTML_SIGIL } from "src/output/Outputter"; +import { ExecutorSettings } from "src/settings/Settings"; // Regex for all languages. const SHOW_REGEX = /@show\(["'](?[^<>?*=!\n#()\[\]{}]+)["'](,\s*(?\d+[\w%]+),?\s*(?\d+[\w%]+))?(,\s*(?left|center|right))?\)/g; @@ -97,7 +98,10 @@ export function insertColorTheme(source: string, theme: string): string { * @param source The source code to parse. * @returns The transformed source code. */ -export function addMagicToPython(source: string): string { +export function addMagicToPython(source: string, settings: ExecutorSettings): string { + if (settings.pythonEmbedPlots) { + source = addInlinePlotsToPython(source, TOGGLE_HTML_SIGIL); + } source = pythonParseShowImage(source); source = pythonParseHtmlFunction(source); return source; From b4cc1b91b274056dad53c7421d2f37aeda3307d0 Mon Sep 17 00:00:00 2001 From: Yetenol Date: Sun, 2 Feb 2025 12:53:05 +0100 Subject: [PATCH 21/34] refactor: combine runCode() and runCodeInShell(), integrate code transformation --- src/main.ts | 102 ++++++++++++++++++++++------------------------------ 1 file changed, 42 insertions(+), 60 deletions(-) diff --git a/src/main.ts b/src/main.ts index 7c0cdf29..dd84165a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -236,10 +236,10 @@ export default class ExecuteCodePlugin extends Plugin { } /** - * Handles the execution of code blocks based on the selected programming language. - * Injects any required code, transforms the source if needed, and manages button state. - * @param block Contains context needed for execution including source code, output handler, and UI elements - */ + * Handles the execution of code blocks based on the selected programming language. + * Injects any required code, transforms the source if needed, and manages button state. + * @param block Contains context needed for execution including source code, output handler, and UI elements + */ private async handleExecution(block: CodeBlockContext) { const language: LanguageId = block.language; const button: HTMLButtonElement = block.button; @@ -250,80 +250,74 @@ export default class ExecuteCodePlugin extends Plugin { block.srcCode = await new CodeInjector(this.app, s, language).injectCode(srcCode); if (language === "js") { - block.srcCode = addMagicToJS(block.srcCode); - this.runCode(s.nodePath, s.nodeArgs, s.jsFileExtension, block); + this.runCode(s.nodePath, s.nodeArgs, s.jsFileExtension, block, { transform: (code) => addMagicToJS(code) }); } else if (language === "java") { this.runCode(s.javaPath, s.javaArgs, s.javaFileExtension, block); } else if (language === "python") { - block.srcCode = addMagicToPython(block.srcCode, s); - this.runCode(s.pythonPath, s.pythonArgs, s.pythonFileExtension, block); + this.runCode(s.pythonPath, s.pythonArgs, s.pythonFileExtension, block, { transform: (code) => addMagicToPython(code, s) }); } else if (language === "shell") { - this.runCodeInShell(s.shellPath, s.shellArgs, s.shellFileExtension, block); + this.runCode(s.shellPath, s.shellArgs, s.shellFileExtension, block, { shell: true }); } else if (language === "batch") { - this.runCodeInShell(s.batchPath, s.batchArgs, s.batchFileExtension, block); + this.runCode(s.batchPath, s.batchArgs, s.batchFileExtension, block, { shell: true }); } else if (language === "powershell") { - this.runCodeInShell(s.powershellPath, s.powershellArgs, s.powershellFileExtension, block); + this.runCode(s.powershellPath, s.powershellArgs, s.powershellFileExtension, block, { shell: true }); } else if (language === "cpp") { this.runCode(s.clingPath, `-std=${s.clingStd} ${s.clingArgs}`, s.cppFileExtension, block); } else if (language === "prolog") { this.runCode("", "", "", block); button.className = runButtonClass; } else if (language === "groovy") { - this.runCodeInShell(s.groovyPath, s.groovyArgs, s.groovyFileExtension, block); + this.runCode(s.groovyPath, s.groovyArgs, s.groovyFileExtension, block, { shell: true }); } else if (language === "rust") { this.runCode(s.cargoPath, "eval" + s.cargoEvalArgs, s.rustFileExtension, block); } else if (language === "r") { - block.srcCode = addInlinePlotsToR(block.srcCode); - this.runCode(s.RPath, s.RArgs, s.RFileExtension, block); + this.runCode(s.RPath, s.RArgs, s.RFileExtension, block, { transform: (code) => addInlinePlotsToR(code) }); } else if (language === "go") { this.runCode(s.golangPath, s.golangArgs, s.golangFileExtension, block); } else if (language === "kotlin") { - this.runCodeInShell(s.kotlinPath, s.kotlinArgs, s.kotlinFileExtension, block); + this.runCode(s.kotlinPath, s.kotlinArgs, s.kotlinFileExtension, block, { shell: true }); } else if (language === "ts") { - this.runCodeInShell(s.tsPath, s.tsArgs, "ts", block); + this.runCode(s.tsPath, s.tsArgs, "ts", block, { shell: true }); } else if (language === "lua") { - this.runCodeInShell(s.luaPath, s.luaArgs, s.luaFileExtension, block); + this.runCode(s.luaPath, s.luaArgs, s.luaFileExtension, block, { shell: true }); } else if (language === "dart") { - this.runCodeInShell(s.dartPath, s.dartArgs, s.dartFileExtension, block); + this.runCode(s.dartPath, s.dartArgs, s.dartFileExtension, block, { shell: true }); } else if (language === "cs") { - this.runCodeInShell(s.csPath, s.csArgs, s.csFileExtension, block); + this.runCode(s.csPath, s.csArgs, s.csFileExtension, block, { shell: true }); } else if (language === "haskell") { - if (s.useGhci) this.runCodeInShell(s.ghciPath, "", "hs", block); - else this.runCodeInShell(s.runghcPath, "-f " + s.ghcPath, "hs", block); + if (s.useGhci) this.runCode(s.ghciPath, "", "hs", block, { shell: true }); + else this.runCode(s.runghcPath, "-f " + s.ghcPath, "hs", block, { shell: true }); } else if (language === "mathematica") { - this.runCodeInShell(s.mathematicaPath, s.mathematicaArgs, s.mathematicaFileExtension, block); + this.runCode(s.mathematicaPath, s.mathematicaArgs, s.mathematicaFileExtension, block, { shell: true }); } else if (language === "scala") { - this.runCodeInShell(s.scalaPath, s.scalaArgs, s.scalaFileExtension, block); + this.runCode(s.scalaPath, s.scalaArgs, s.scalaFileExtension, block, { shell: true }); } else if (language === "swift") { - this.runCodeInShell(s.swiftPath, s.swiftArgs, s.swiftFileExtension, block); + this.runCode(s.swiftPath, s.swiftArgs, s.swiftFileExtension, block, { shell: true }); } else if (language === "c") { - this.runCodeInShell(s.clingPath, s.clingArgs, "c", block); + this.runCode(s.clingPath, s.clingArgs, "c", block, { shell: true }); } else if (language === "ruby") { - this.runCodeInShell(s.rubyPath, s.rubyArgs, s.rubyFileExtension, block); + this.runCode(s.rubyPath, s.rubyArgs, s.rubyFileExtension, block, { shell: true }); } else if (language === "sql") { - this.runCodeInShell(s.sqlPath, s.sqlArgs, "sql", block); + this.runCode(s.sqlPath, s.sqlArgs, "sql", block, { shell: true }); } else if (language === "octave") { - block.srcCode = addInlinePlotsToOctave(block.srcCode); - this.runCodeInShell(s.octavePath, s.octaveArgs, s.octaveFileExtension, block); + this.runCode(s.octavePath, s.octaveArgs, s.octaveFileExtension, block, { shell: true, transform: (code) => addInlinePlotsToOctave(code) }); } else if (language === "maxima") { - block.srcCode = addInlinePlotsToMaxima(block.srcCode); - this.runCodeInShell(s.maximaPath, s.maximaArgs, s.maximaFileExtension, block); + this.runCode(s.maximaPath, s.maximaArgs, s.maximaFileExtension, block, { shell: true, transform: (code) => addInlinePlotsToMaxima(code) }); } else if (language === "racket") { - this.runCodeInShell(s.racketPath, s.racketArgs, s.racketFileExtension, block); + this.runCode(s.racketPath, s.racketArgs, s.racketFileExtension, block, { shell: true }); } else if (language === "applescript") { - this.runCodeInShell(s.applescriptPath, s.applescriptArgs, s.applescriptFileExtension, block); + this.runCode(s.applescriptPath, s.applescriptArgs, s.applescriptFileExtension, block, { shell: true }); } else if (language === "zig") { - this.runCodeInShell(s.zigPath, s.zigArgs, "zig", block); + this.runCode(s.zigPath, s.zigArgs, "zig", block, { shell: true }); } else if (language === "ocaml") { - this.runCodeInShell(s.ocamlPath, s.ocamlArgs, "ocaml", block); + this.runCode(s.ocamlPath, s.ocamlArgs, "ocaml", block, { shell: true }); } else if (language === "php") { - this.runCodeInShell(s.phpPath, s.phpArgs, s.phpFileExtension, block); + this.runCode(s.phpPath, s.phpArgs, s.phpFileExtension, block, { shell: true }); } else if (language === "latex") { - block.srcCode = modifyLatexCode(block.srcCode, s); const outputPath: string = await retrieveFigurePath(block.srcCode, s.latexFigureTitlePattern, block.markdownFile, s); const invokeCompiler: string = [s.latexTexfotArgs, s.latexCompilerPath, s.latexCompilerArgs].join(" "); - if (!s.latexDoFilter) this.runCode(s.latexCompilerPath, s.latexCompilerArgs, outputPath, block); - else this.runCode(s.latexTexfotPath, invokeCompiler, outputPath, block); + if (!s.latexDoFilter) this.runCode(s.latexCompilerPath, s.latexCompilerArgs, outputPath, block, { transform: (code) => modifyLatexCode(code, s) }); + else this.runCode(s.latexTexfotPath, invokeCompiler, outputPath, block, { transform: (code) => modifyLatexCode(code, s) }); } } @@ -350,30 +344,18 @@ export default class ExecuteCodePlugin extends Plugin { * @param ext The file extension of the temporary file. Should correspond to the language of the code. (e.g. py, ...) * @param block Contains context needed for execution including source code, output handler, and UI elements */ - private runCode(cmd: string, cmdArgs: string, ext: string, block: CodeBlockContext) { - const outputter: Outputter = block.outputter; + private runCode(cmd: string, cmdArgs: string, ext: string, block: CodeBlockContext, options?: { shell?: boolean, transform?: (code: string) => string }) { + const useShell: boolean = (options?.shell) ? options.shell : false; + if (options?.transform) block.srcCode = options.transform(block.srcCode); + if (!useShell) block.outputter.startBlock(); - outputter.startBlock(); - const executor = this.executors.getExecutorFor(block.markdownFile, block.language, false); - executor.run(block.srcCode, outputter, cmd, cmdArgs, ext).then(() => { - block.button.className = runButtonClass; - outputter.closeInput(); - outputter.finishBlock(); - }); - } - - /** - * Executes the code with the given command and arguments. The code is written to a temporary file and then executed. - * This is equal to {@link runCode} but the code is executed in a shell. This is necessary for some languages like groovy. - * @param cmd The command that should be used to execute the code. (e.g. python, java, ...) - * @param cmdArgs Additional arguments that should be passed to the command. - * @param ext The file extension of the temporary file. Should correspond to the language of the code. (e.g. py, ...) - * @param block Contains context needed for execution including source code, output handler, and UI elements - */ - private runCodeInShell(cmd: string, cmdArgs: string, ext: string, block: CodeBlockContext) { - const executor = this.executors.getExecutorFor(block.markdownFile, block.language, true); + const executor = this.executors.getExecutorFor(block.markdownFile, block.language, useShell); executor.run(block.srcCode, block.outputter, cmd, cmdArgs, ext).then(() => { block.button.className = runButtonClass; + if (!useShell) { + block.outputter.closeInput(); + block.outputter.finishBlock(); + } }); } } From ecd59d853d465f92370ebae012b88ab350522b8b Mon Sep 17 00:00:00 2001 From: Yetenol Date: Sun, 2 Feb 2025 13:35:01 +0100 Subject: [PATCH 22/34] refactor: use compact switch statement --- src/main.ts | 113 ++++++++++++++++++++-------------------------------- 1 file changed, 44 insertions(+), 69 deletions(-) diff --git a/src/main.ts b/src/main.ts index dd84165a..3fbd7413 100644 --- a/src/main.ts +++ b/src/main.ts @@ -249,75 +249,50 @@ export default class ExecuteCodePlugin extends Plugin { button.className = runButtonDisabledClass; block.srcCode = await new CodeInjector(this.app, s, language).injectCode(srcCode); - if (language === "js") { - this.runCode(s.nodePath, s.nodeArgs, s.jsFileExtension, block, { transform: (code) => addMagicToJS(code) }); - } else if (language === "java") { - this.runCode(s.javaPath, s.javaArgs, s.javaFileExtension, block); - } else if (language === "python") { - this.runCode(s.pythonPath, s.pythonArgs, s.pythonFileExtension, block, { transform: (code) => addMagicToPython(code, s) }); - } else if (language === "shell") { - this.runCode(s.shellPath, s.shellArgs, s.shellFileExtension, block, { shell: true }); - } else if (language === "batch") { - this.runCode(s.batchPath, s.batchArgs, s.batchFileExtension, block, { shell: true }); - } else if (language === "powershell") { - this.runCode(s.powershellPath, s.powershellArgs, s.powershellFileExtension, block, { shell: true }); - } else if (language === "cpp") { - this.runCode(s.clingPath, `-std=${s.clingStd} ${s.clingArgs}`, s.cppFileExtension, block); - } else if (language === "prolog") { - this.runCode("", "", "", block); - button.className = runButtonClass; - } else if (language === "groovy") { - this.runCode(s.groovyPath, s.groovyArgs, s.groovyFileExtension, block, { shell: true }); - } else if (language === "rust") { - this.runCode(s.cargoPath, "eval" + s.cargoEvalArgs, s.rustFileExtension, block); - } else if (language === "r") { - this.runCode(s.RPath, s.RArgs, s.RFileExtension, block, { transform: (code) => addInlinePlotsToR(code) }); - } else if (language === "go") { - this.runCode(s.golangPath, s.golangArgs, s.golangFileExtension, block); - } else if (language === "kotlin") { - this.runCode(s.kotlinPath, s.kotlinArgs, s.kotlinFileExtension, block, { shell: true }); - } else if (language === "ts") { - this.runCode(s.tsPath, s.tsArgs, "ts", block, { shell: true }); - } else if (language === "lua") { - this.runCode(s.luaPath, s.luaArgs, s.luaFileExtension, block, { shell: true }); - } else if (language === "dart") { - this.runCode(s.dartPath, s.dartArgs, s.dartFileExtension, block, { shell: true }); - } else if (language === "cs") { - this.runCode(s.csPath, s.csArgs, s.csFileExtension, block, { shell: true }); - } else if (language === "haskell") { - if (s.useGhci) this.runCode(s.ghciPath, "", "hs", block, { shell: true }); - else this.runCode(s.runghcPath, "-f " + s.ghcPath, "hs", block, { shell: true }); - } else if (language === "mathematica") { - this.runCode(s.mathematicaPath, s.mathematicaArgs, s.mathematicaFileExtension, block, { shell: true }); - } else if (language === "scala") { - this.runCode(s.scalaPath, s.scalaArgs, s.scalaFileExtension, block, { shell: true }); - } else if (language === "swift") { - this.runCode(s.swiftPath, s.swiftArgs, s.swiftFileExtension, block, { shell: true }); - } else if (language === "c") { - this.runCode(s.clingPath, s.clingArgs, "c", block, { shell: true }); - } else if (language === "ruby") { - this.runCode(s.rubyPath, s.rubyArgs, s.rubyFileExtension, block, { shell: true }); - } else if (language === "sql") { - this.runCode(s.sqlPath, s.sqlArgs, "sql", block, { shell: true }); - } else if (language === "octave") { - this.runCode(s.octavePath, s.octaveArgs, s.octaveFileExtension, block, { shell: true, transform: (code) => addInlinePlotsToOctave(code) }); - } else if (language === "maxima") { - this.runCode(s.maximaPath, s.maximaArgs, s.maximaFileExtension, block, { shell: true, transform: (code) => addInlinePlotsToMaxima(code) }); - } else if (language === "racket") { - this.runCode(s.racketPath, s.racketArgs, s.racketFileExtension, block, { shell: true }); - } else if (language === "applescript") { - this.runCode(s.applescriptPath, s.applescriptArgs, s.applescriptFileExtension, block, { shell: true }); - } else if (language === "zig") { - this.runCode(s.zigPath, s.zigArgs, "zig", block, { shell: true }); - } else if (language === "ocaml") { - this.runCode(s.ocamlPath, s.ocamlArgs, "ocaml", block, { shell: true }); - } else if (language === "php") { - this.runCode(s.phpPath, s.phpArgs, s.phpFileExtension, block, { shell: true }); - } else if (language === "latex") { - const outputPath: string = await retrieveFigurePath(block.srcCode, s.latexFigureTitlePattern, block.markdownFile, s); - const invokeCompiler: string = [s.latexTexfotArgs, s.latexCompilerPath, s.latexCompilerArgs].join(" "); - if (!s.latexDoFilter) this.runCode(s.latexCompilerPath, s.latexCompilerArgs, outputPath, block, { transform: (code) => modifyLatexCode(code, s) }); - else this.runCode(s.latexTexfotPath, invokeCompiler, outputPath, block, { transform: (code) => modifyLatexCode(code, s) }); + switch (language) { + case "js": return this.runCode(s.nodePath, s.nodeArgs, s.jsFileExtension, block, { transform: (code) => addMagicToJS(code) }); + case "java": return this.runCode(s.javaPath, s.javaArgs, s.javaFileExtension, block); + case "python": return this.runCode(s.pythonPath, s.pythonArgs, s.pythonFileExtension, block, { transform: (code) => addMagicToPython(code, s) }); + case "shell": return this.runCode(s.shellPath, s.shellArgs, s.shellFileExtension, block, { shell: true }); + case "batch": return this.runCode(s.batchPath, s.batchArgs, s.batchFileExtension, block, { shell: true }); + case "powershell": return this.runCode(s.powershellPath, s.powershellArgs, s.powershellFileExtension, block, { shell: true }); + case "cpp": return this.runCode(s.clingPath, `-std=${s.clingStd} ${s.clingArgs}`, s.cppFileExtension, block); + case "prolog": + this.runCode("", "", "", block); + button.className = runButtonClass; + break; + case "groovy": return this.runCode(s.groovyPath, s.groovyArgs, s.groovyFileExtension, block, { shell: true }); + case "rust": return this.runCode(s.cargoPath, "eval" + s.cargoEvalArgs, s.rustFileExtension, block); + case "r": return this.runCode(s.RPath, s.RArgs, s.RFileExtension, block, { transform: (code) => addInlinePlotsToR(code) }); + case "go": return this.runCode(s.golangPath, s.golangArgs, s.golangFileExtension, block); + case "kotlin": return this.runCode(s.kotlinPath, s.kotlinArgs, s.kotlinFileExtension, block, { shell: true }); + case "ts": return this.runCode(s.tsPath, s.tsArgs, "ts", block, { shell: true }); + case "lua": return this.runCode(s.luaPath, s.luaArgs, s.luaFileExtension, block, { shell: true }); + case "dart": return this.runCode(s.dartPath, s.dartArgs, s.dartFileExtension, block, { shell: true }); + case "cs": return this.runCode(s.csPath, s.csArgs, s.csFileExtension, block, { shell: true }); + case "haskell": return (s.useGhci) + ? this.runCode(s.ghciPath, "", "hs", block, { shell: true }) + : this.runCode(s.runghcPath, "-f " + s.ghcPath, "hs", block, { shell: true }); + case "mathematica": return this.runCode(s.mathematicaPath, s.mathematicaArgs, s.mathematicaFileExtension, block, { shell: true }); + case "scala": return this.runCode(s.scalaPath, s.scalaArgs, s.scalaFileExtension, block, { shell: true }); + case "swift": return this.runCode(s.swiftPath, s.swiftArgs, s.swiftFileExtension, block, { shell: true }); + case "c": return this.runCode(s.clingPath, s.clingArgs, "c", block, { shell: true }); + case "ruby": return this.runCode(s.rubyPath, s.rubyArgs, s.rubyFileExtension, block, { shell: true }); + case "sql": return this.runCode(s.sqlPath, s.sqlArgs, "sql", block, { shell: true }); + case "octave": return this.runCode(s.octavePath, s.octaveArgs, s.octaveFileExtension, block, { shell: true, transform: (code) => addInlinePlotsToOctave(code) }); + case "maxima": return this.runCode(s.maximaPath, s.maximaArgs, s.maximaFileExtension, block, { shell: true, transform: (code) => addInlinePlotsToMaxima(code) }); + case "racket": return this.runCode(s.racketPath, s.racketArgs, s.racketFileExtension, block, { shell: true }); + case "applescript": return this.runCode(s.applescriptPath, s.applescriptArgs, s.applescriptFileExtension, block, { shell: true }); + case "zig": return this.runCode(s.zigPath, s.zigArgs, "zig", block, { shell: true }); + case "ocaml": return this.runCode(s.ocamlPath, s.ocamlArgs, "ocaml", block, { shell: true }); + case "php": return this.runCode(s.phpPath, s.phpArgs, s.phpFileExtension, block, { shell: true }); + case "latex": + const outputPath: string = await retrieveFigurePath(block.srcCode, s.latexFigureTitlePattern, block.markdownFile, s); + const invokeCompiler: string = [s.latexTexfotArgs, s.latexCompilerPath, s.latexCompilerArgs].join(" "); + return (!s.latexDoFilter) + ? this.runCode(s.latexCompilerPath, s.latexCompilerArgs, outputPath, block, { transform: (code) => modifyLatexCode(code, s) }) + : this.runCode(s.latexTexfotPath, invokeCompiler, outputPath, block, { transform: (code) => modifyLatexCode(code, s) }); + default: break; } } From 89d846106fb09c2ceb9a1bdd0e437ff916474780 Mon Sep 17 00:00:00 2001 From: Yetenol Date: Sun, 2 Feb 2025 17:08:37 +0100 Subject: [PATCH 23/34] move run button functions outside plugin class --- src/main.ts | 329 +++++++++++++++++++++++++++------------------------- 1 file changed, 173 insertions(+), 156 deletions(-) diff --git a/src/main.ts b/src/main.ts index 3fbd7413..92ae9927 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,10 @@ import { + App, Component, MarkdownRenderer, MarkdownView, Plugin, + Workspace, } from 'obsidian'; import { Outputter, TOGGLE_HTML_SIGIL } from "./output/Outputter"; @@ -46,10 +48,17 @@ const hasButtonClass = "has-run-code-button"; interface CodeBlockContext { srcCode: string; - outputter: Outputter; button: HTMLButtonElement; language: LanguageId; markdownFile: string; + outputter: Outputter; + executors: ExecutorContainer; +} + +export interface PluginContext { + app: App; + settings: ExecutorSettings; + executors: ExecutorContainer; } export default class ExecuteCodePlugin extends Plugin { @@ -65,9 +74,14 @@ export default class ExecuteCodePlugin extends Plugin { this.executors = new ExecutorContainer(this); - this.iterateOpenFilesAndAddRunButtons(); + const context: PluginContext = { + app: this.app, + settings: this.settings, + executors: this.executors, + } + iterateOpenFilesAndAddRunButtons(context); this.registerMarkdownPostProcessor((element, _context) => { - this.addRunButtons(element, _context.sourcePath, this.app.workspace.getActiveViewOfType(MarkdownView)); + addRunButtons(element, _context.sourcePath, this.app.workspace.getActiveViewOfType(MarkdownView), context); }); // live preview renderers @@ -163,174 +177,177 @@ export default class ExecuteCodePlugin extends Plugin { async saveSettings() { await this.saveData(this.settings); } +} - /** - * Adds run buttons to each open file. This is more robust and quicker than scanning - * the entire document, even though it requires more iteration, because it doesn't - * search the whole document. - */ - private iterateOpenFilesAndAddRunButtons() { - this.app.workspace.iterateRootLeaves(leaf => { - if (leaf.view instanceof MarkdownView) { - this.addRunButtons(leaf.view.contentEl, leaf.view.file.path, leaf.view); - } - }) - } +/** + * Adds run buttons to each open file. This is more robust and quicker than scanning + * the entire document, even though it requires more iteration, because it doesn't + * search the whole document. + */ +function iterateOpenFilesAndAddRunButtons(plugin: PluginContext) { + const workspace: Workspace = plugin.app.workspace; + workspace.iterateRootLeaves(leaf => { + if (leaf.view instanceof MarkdownView) { + addRunButtons(leaf.view.contentEl, leaf.view.file.path, leaf.view, plugin); + } + }) +} - /** - * Add a button to each code block that allows the user to run the code. The button is only added if the code block - * utilizes a language that is supported by this plugin. - * - * @param element The parent element (i.e. the currently showed html page / note). - * @param file An identifier for the currently showed note - */ - private addRunButtons(element: HTMLElement, file: string, view: MarkdownView) { - Array.from(element.getElementsByTagName("code")) - .forEach((codeBlock: HTMLElement) => this.addRunButton(codeBlock, file, view)); - } +/** + * Add a button to each code block that allows the user to run the code. The button is only added if the code block + * utilizes a language that is supported by this plugin. + * + * @param element The parent element (i.e. the currently showed html page / note). + * @param file An identifier for the currently showed note + */ +function addRunButtons(element: HTMLElement, file: string, view: MarkdownView, plugin: PluginContext) { + Array.from(element.getElementsByTagName("code")) + .forEach((codeBlock: HTMLElement) => addRunButton(codeBlock, file, view, plugin)); +} - private addRunButton(codeBlock: HTMLElement, file: string, view: MarkdownView) { - if (codeBlock.className.match(/^language-\{\w+/i)) { - codeBlock.className = codeBlock.className.replace(/^language-\{(\w+)/i, "language-$1 {"); - codeBlock.parentElement.className = codeBlock.className; - } +function addRunButton(codeBlock: HTMLElement, file: string, view: MarkdownView, plugin: PluginContext) { + if (codeBlock.className.match(/^language-\{\w+/i)) { + codeBlock.className = codeBlock.className.replace(/^language-\{(\w+)/i, "language-$1 {"); + codeBlock.parentElement.className = codeBlock.className; + } - const language = codeBlock.className.toLowerCase(); + const language = codeBlock.className.toLowerCase(); - if (!language || !language.contains("language-")) - return; + if (!language || !language.contains("language-")) + return; - const pre = codeBlock.parentElement as HTMLPreElement; - const parent = pre.parentElement as HTMLDivElement; + const pre = codeBlock.parentElement as HTMLPreElement; + const parent = pre.parentElement as HTMLDivElement; - const srcCode = codeBlock.getText(); - let sanitizedClassList = this.sanitizeClassListOfCodeBlock(codeBlock); + const srcCode = codeBlock.getText(); + let sanitizedClassList = sanitizeClassListOfCodeBlock(codeBlock); - const canonicalLanguage = getLanguageAlias( - supportedLanguages.find(lang => sanitizedClassList.contains(`language-${lang}`)) - ) as LanguageId; + const canonicalLanguage = getLanguageAlias( + supportedLanguages.find(lang => sanitizedClassList.contains(`language-${lang}`)) + ) as LanguageId; - const isLanguageSupported: Boolean = canonicalLanguage !== undefined; - const hasBlockBeenButtonifiedAlready = parent.classList.contains(hasButtonClass); - if (!isLanguageSupported || hasBlockBeenButtonifiedAlready) return; + const isLanguageSupported: Boolean = canonicalLanguage !== undefined; + const hasBlockBeenButtonifiedAlready = parent.classList.contains(hasButtonClass); + if (!isLanguageSupported || hasBlockBeenButtonifiedAlready) return; - const out = new Outputter(codeBlock, this.settings, view, this.app, file); - parent.classList.add(hasButtonClass); - const button = this.createRunButton(); - pre.appendChild(button); + const outputter = new Outputter(codeBlock, plugin.settings, view, plugin.app, file); + parent.classList.add(hasButtonClass); + const button = createRunButton(); + pre.appendChild(button); - const block: CodeBlockContext = { - srcCode: srcCode, - outputter: out, - language: canonicalLanguage, - markdownFile: file, - button: button, - }; + const block: CodeBlockContext = { + srcCode: srcCode, + language: canonicalLanguage, + markdownFile: file, + button: button, + outputter: outputter, + executors: plugin.executors, + }; - button.addEventListener("click", () => this.handleExecution(block)); - } + button.addEventListener("click", () => handleExecution(block)); +} - private sanitizeClassListOfCodeBlock(codeBlock: HTMLElement) { - let sanitizedClassList = Array.from(codeBlock.classList); - return sanitizedClassList.map(c => c.toLowerCase()); - } +function sanitizeClassListOfCodeBlock(codeBlock: HTMLElement) { + let sanitizedClassList = Array.from(codeBlock.classList); + return sanitizedClassList.map(c => c.toLowerCase()); +} - /** - * Handles the execution of code blocks based on the selected programming language. - * Injects any required code, transforms the source if needed, and manages button state. - * @param block Contains context needed for execution including source code, output handler, and UI elements - */ - private async handleExecution(block: CodeBlockContext) { - const language: LanguageId = block.language; - const button: HTMLButtonElement = block.button; - const srcCode: string = block.srcCode; - const s: ExecutorSettings = this.settings; - - button.className = runButtonDisabledClass; - block.srcCode = await new CodeInjector(this.app, s, language).injectCode(srcCode); - - switch (language) { - case "js": return this.runCode(s.nodePath, s.nodeArgs, s.jsFileExtension, block, { transform: (code) => addMagicToJS(code) }); - case "java": return this.runCode(s.javaPath, s.javaArgs, s.javaFileExtension, block); - case "python": return this.runCode(s.pythonPath, s.pythonArgs, s.pythonFileExtension, block, { transform: (code) => addMagicToPython(code, s) }); - case "shell": return this.runCode(s.shellPath, s.shellArgs, s.shellFileExtension, block, { shell: true }); - case "batch": return this.runCode(s.batchPath, s.batchArgs, s.batchFileExtension, block, { shell: true }); - case "powershell": return this.runCode(s.powershellPath, s.powershellArgs, s.powershellFileExtension, block, { shell: true }); - case "cpp": return this.runCode(s.clingPath, `-std=${s.clingStd} ${s.clingArgs}`, s.cppFileExtension, block); - case "prolog": - this.runCode("", "", "", block); - button.className = runButtonClass; - break; - case "groovy": return this.runCode(s.groovyPath, s.groovyArgs, s.groovyFileExtension, block, { shell: true }); - case "rust": return this.runCode(s.cargoPath, "eval" + s.cargoEvalArgs, s.rustFileExtension, block); - case "r": return this.runCode(s.RPath, s.RArgs, s.RFileExtension, block, { transform: (code) => addInlinePlotsToR(code) }); - case "go": return this.runCode(s.golangPath, s.golangArgs, s.golangFileExtension, block); - case "kotlin": return this.runCode(s.kotlinPath, s.kotlinArgs, s.kotlinFileExtension, block, { shell: true }); - case "ts": return this.runCode(s.tsPath, s.tsArgs, "ts", block, { shell: true }); - case "lua": return this.runCode(s.luaPath, s.luaArgs, s.luaFileExtension, block, { shell: true }); - case "dart": return this.runCode(s.dartPath, s.dartArgs, s.dartFileExtension, block, { shell: true }); - case "cs": return this.runCode(s.csPath, s.csArgs, s.csFileExtension, block, { shell: true }); - case "haskell": return (s.useGhci) - ? this.runCode(s.ghciPath, "", "hs", block, { shell: true }) - : this.runCode(s.runghcPath, "-f " + s.ghcPath, "hs", block, { shell: true }); - case "mathematica": return this.runCode(s.mathematicaPath, s.mathematicaArgs, s.mathematicaFileExtension, block, { shell: true }); - case "scala": return this.runCode(s.scalaPath, s.scalaArgs, s.scalaFileExtension, block, { shell: true }); - case "swift": return this.runCode(s.swiftPath, s.swiftArgs, s.swiftFileExtension, block, { shell: true }); - case "c": return this.runCode(s.clingPath, s.clingArgs, "c", block, { shell: true }); - case "ruby": return this.runCode(s.rubyPath, s.rubyArgs, s.rubyFileExtension, block, { shell: true }); - case "sql": return this.runCode(s.sqlPath, s.sqlArgs, "sql", block, { shell: true }); - case "octave": return this.runCode(s.octavePath, s.octaveArgs, s.octaveFileExtension, block, { shell: true, transform: (code) => addInlinePlotsToOctave(code) }); - case "maxima": return this.runCode(s.maximaPath, s.maximaArgs, s.maximaFileExtension, block, { shell: true, transform: (code) => addInlinePlotsToMaxima(code) }); - case "racket": return this.runCode(s.racketPath, s.racketArgs, s.racketFileExtension, block, { shell: true }); - case "applescript": return this.runCode(s.applescriptPath, s.applescriptArgs, s.applescriptFileExtension, block, { shell: true }); - case "zig": return this.runCode(s.zigPath, s.zigArgs, "zig", block, { shell: true }); - case "ocaml": return this.runCode(s.ocamlPath, s.ocamlArgs, "ocaml", block, { shell: true }); - case "php": return this.runCode(s.phpPath, s.phpArgs, s.phpFileExtension, block, { shell: true }); - case "latex": - const outputPath: string = await retrieveFigurePath(block.srcCode, s.latexFigureTitlePattern, block.markdownFile, s); - const invokeCompiler: string = [s.latexTexfotArgs, s.latexCompilerPath, s.latexCompilerArgs].join(" "); - return (!s.latexDoFilter) - ? this.runCode(s.latexCompilerPath, s.latexCompilerArgs, outputPath, block, { transform: (code) => modifyLatexCode(code, s) }) - : this.runCode(s.latexTexfotPath, invokeCompiler, outputPath, block, { transform: (code) => modifyLatexCode(code, s) }); - default: break; - } +/** + * Handles the execution of code blocks based on the selected programming language. + * Injects any required code, transforms the source if needed, and manages button state. + * @param block Contains context needed for execution including source code, output handler, and UI elements + */ +async function handleExecution(block: CodeBlockContext) { + const language: LanguageId = block.language; + const button: HTMLButtonElement = block.button; + const srcCode: string = block.srcCode; + const app: App = block.outputter.app; + const s: ExecutorSettings = block.outputter.settings; + + button.className = runButtonDisabledClass; + block.srcCode = await new CodeInjector(app, s, language).injectCode(srcCode); + + switch (language) { + case "js": return runCode(s.nodePath, s.nodeArgs, s.jsFileExtension, block, { transform: (code) => addMagicToJS(code) }); + case "java": return runCode(s.javaPath, s.javaArgs, s.javaFileExtension, block); + case "python": return runCode(s.pythonPath, s.pythonArgs, s.pythonFileExtension, block, { transform: (code) => addMagicToPython(code, s) }); + case "shell": return runCode(s.shellPath, s.shellArgs, s.shellFileExtension, block, { shell: true }); + case "batch": return runCode(s.batchPath, s.batchArgs, s.batchFileExtension, block, { shell: true }); + case "powershell": return runCode(s.powershellPath, s.powershellArgs, s.powershellFileExtension, block, { shell: true }); + case "cpp": return runCode(s.clingPath, `-std=${s.clingStd} ${s.clingArgs}`, s.cppFileExtension, block); + case "prolog": + runCode("", "", "", block); + button.className = runButtonClass; + break; + case "groovy": return runCode(s.groovyPath, s.groovyArgs, s.groovyFileExtension, block, { shell: true }); + case "rust": return runCode(s.cargoPath, "eval" + s.cargoEvalArgs, s.rustFileExtension, block); + case "r": return runCode(s.RPath, s.RArgs, s.RFileExtension, block, { transform: (code) => addInlinePlotsToR(code) }); + case "go": return runCode(s.golangPath, s.golangArgs, s.golangFileExtension, block); + case "kotlin": return runCode(s.kotlinPath, s.kotlinArgs, s.kotlinFileExtension, block, { shell: true }); + case "ts": return runCode(s.tsPath, s.tsArgs, "ts", block, { shell: true }); + case "lua": return runCode(s.luaPath, s.luaArgs, s.luaFileExtension, block, { shell: true }); + case "dart": return runCode(s.dartPath, s.dartArgs, s.dartFileExtension, block, { shell: true }); + case "cs": return runCode(s.csPath, s.csArgs, s.csFileExtension, block, { shell: true }); + case "haskell": return (s.useGhci) + ? runCode(s.ghciPath, "", "hs", block, { shell: true }) + : runCode(s.runghcPath, "-f " + s.ghcPath, "hs", block, { shell: true }); + case "mathematica": return runCode(s.mathematicaPath, s.mathematicaArgs, s.mathematicaFileExtension, block, { shell: true }); + case "scala": return runCode(s.scalaPath, s.scalaArgs, s.scalaFileExtension, block, { shell: true }); + case "swift": return runCode(s.swiftPath, s.swiftArgs, s.swiftFileExtension, block, { shell: true }); + case "c": return runCode(s.clingPath, s.clingArgs, "c", block, { shell: true }); + case "ruby": return runCode(s.rubyPath, s.rubyArgs, s.rubyFileExtension, block, { shell: true }); + case "sql": return runCode(s.sqlPath, s.sqlArgs, "sql", block, { shell: true }); + case "octave": return runCode(s.octavePath, s.octaveArgs, s.octaveFileExtension, block, { shell: true, transform: (code) => addInlinePlotsToOctave(code) }); + case "maxima": return runCode(s.maximaPath, s.maximaArgs, s.maximaFileExtension, block, { shell: true, transform: (code) => addInlinePlotsToMaxima(code) }); + case "racket": return runCode(s.racketPath, s.racketArgs, s.racketFileExtension, block, { shell: true }); + case "applescript": return runCode(s.applescriptPath, s.applescriptArgs, s.applescriptFileExtension, block, { shell: true }); + 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 "latex": + const outputPath: string = await retrieveFigurePath(block.srcCode, s.latexFigureTitlePattern, block.markdownFile, s); + const invokeCompiler: string = [s.latexTexfotArgs, s.latexCompilerPath, s.latexCompilerArgs].join(" "); + return (!s.latexDoFilter) + ? runCode(s.latexCompilerPath, s.latexCompilerArgs, outputPath, block, { transform: (code) => modifyLatexCode(code, s) }) + : runCode(s.latexTexfotPath, invokeCompiler, outputPath, block, { transform: (code) => modifyLatexCode(code, s) }); + default: break; } +} - /** - * Creates a new run button and returns it. - * - * @returns { HTMLButtonElement } The newly created run button. - */ - private createRunButton() { - console.debug("Add run button"); - const button = document.createElement("button"); - button.classList.add(runButtonClass); - button.setText(buttonText); - return button; - } +/** + * Creates a new run button and returns it. + * + * @returns { HTMLButtonElement } The newly created run button. + */ +function createRunButton(): HTMLButtonElement { + console.debug("Add run button"); + const button = document.createElement("button"); + button.classList.add(runButtonClass); + button.setText(buttonText); + return button; +} - /** - * Executes the code with the given command and arguments. The code is written to a temporary file and then executed. - * The output of the code is displayed in the output panel ({@link Outputter}). - * If the code execution fails, an error message is displayed and logged. - * After the code execution, the temporary file is deleted and the run button is re-enabled. - * @param cmd The command that should be used to execute the code. (e.g. python, java, ...) - * @param cmdArgs Additional arguments that should be passed to the command. - * @param ext The file extension of the temporary file. Should correspond to the language of the code. (e.g. py, ...) - * @param block Contains context needed for execution including source code, output handler, and UI elements - */ - private runCode(cmd: string, cmdArgs: string, ext: string, block: CodeBlockContext, options?: { shell?: boolean, transform?: (code: string) => string }) { - const useShell: boolean = (options?.shell) ? options.shell : false; - if (options?.transform) block.srcCode = options.transform(block.srcCode); - if (!useShell) block.outputter.startBlock(); - - const executor = this.executors.getExecutorFor(block.markdownFile, block.language, useShell); - executor.run(block.srcCode, block.outputter, cmd, cmdArgs, ext).then(() => { - block.button.className = runButtonClass; - if (!useShell) { - block.outputter.closeInput(); - block.outputter.finishBlock(); - } - }); - } +/** + * Executes the code with the given command and arguments. The code is written to a temporary file and then executed. + * The output of the code is displayed in the output panel ({@link Outputter}). + * If the code execution fails, an error message is displayed and logged. + * After the code execution, the temporary file is deleted and the run button is re-enabled. + * @param cmd The command that should be used to execute the code. (e.g. python, java, ...) + * @param cmdArgs Additional arguments that should be passed to the command. + * @param ext The file extension of the temporary file. Should correspond to the language of the code. (e.g. py, ...) + * @param block Contains context needed for execution including source code, output handler, and UI elements + */ +function runCode(cmd: string, cmdArgs: string, ext: string, block: CodeBlockContext, options?: { shell?: boolean, transform?: (code: string) => string }) { + const useShell: boolean = (options?.shell) ? options.shell : false; + if (options?.transform) block.srcCode = options.transform(block.srcCode); + if (!useShell) block.outputter.startBlock(); + + const executor = block.executors.getExecutorFor(block.markdownFile, block.language, useShell); + executor.run(block.srcCode, block.outputter, cmd, cmdArgs, ext).then(() => { + block.button.className = runButtonClass; + if (!useShell) { + block.outputter.closeInput(); + block.outputter.finishBlock(); + } + }); } From abaa0e08935952d0fc68a23978543eab28246308 Mon Sep 17 00:00:00 2001 From: Yetenol Date: Sun, 2 Feb 2025 18:21:28 +0100 Subject: [PATCH 24/34] move run button functions into separate file --- src/RunButton.ts | 198 +++++++++++++++++++++++++++++++++++ src/main.ts | 226 ++-------------------------------------- src/runAllCodeBlocks.ts | 4 +- 3 files changed, 210 insertions(+), 218 deletions(-) create mode 100644 src/RunButton.ts diff --git a/src/RunButton.ts b/src/RunButton.ts new file mode 100644 index 00000000..805ee2ea --- /dev/null +++ b/src/RunButton.ts @@ -0,0 +1,198 @@ +import { App, Workspace, MarkdownView } from 'obsidian'; +import ExecutorContainer from './ExecutorContainer'; +import { LanguageId, PluginContext, supportedLanguages } from './main'; +import { Outputter } from './output/Outputter'; +import type { ExecutorSettings } from './settings/Settings'; +import { CodeInjector } from './transforms/CodeInjector'; +import { retrieveFigurePath } from './transforms/LatexFigureName'; +import { modifyLatexCode } from './transforms/LatexTransformer'; +import { addMagicToJS, addMagicToPython, addInlinePlotsToR, addInlinePlotsToOctave, addInlinePlotsToMaxima } from './transforms/Magic'; +import { getLanguageAlias } from './transforms/TransformCode'; + +const buttonText = "Run"; + +export const runButtonClass = "run-code-button"; +export const runButtonDisabledClass = "run-button-disabled"; +export const hasButtonClass = "has-run-code-button"; + +interface CodeBlockContext { + srcCode: string; + button: HTMLButtonElement; + language: LanguageId; + markdownFile: string; + outputter: Outputter; + executors: ExecutorContainer; +} + +/** + * Handles the execution of code blocks based on the selected programming language. + * Injects any required code, transforms the source if needed, and manages button state. + * @param block Contains context needed for execution including source code, output handler, and UI elements + */ +async function handleExecution(block: CodeBlockContext) { + const language: LanguageId = block.language; + const button: HTMLButtonElement = block.button; + const srcCode: string = block.srcCode; + const app: App = block.outputter.app; + const s: ExecutorSettings = block.outputter.settings; + + button.className = runButtonDisabledClass; + block.srcCode = await new CodeInjector(app, s, language).injectCode(srcCode); + + switch (language) { + case "js": return runCode(s.nodePath, s.nodeArgs, s.jsFileExtension, block, { transform: (code) => addMagicToJS(code) }); + case "java": return runCode(s.javaPath, s.javaArgs, s.javaFileExtension, block); + case "python": return runCode(s.pythonPath, s.pythonArgs, s.pythonFileExtension, block, { transform: (code) => addMagicToPython(code, s) }); + case "shell": return runCode(s.shellPath, s.shellArgs, s.shellFileExtension, block, { shell: true }); + case "batch": return runCode(s.batchPath, s.batchArgs, s.batchFileExtension, block, { shell: true }); + case "powershell": return runCode(s.powershellPath, s.powershellArgs, s.powershellFileExtension, block, { shell: true }); + case "cpp": return runCode(s.clingPath, `-std=${s.clingStd} ${s.clingArgs}`, s.cppFileExtension, block); + case "prolog": + runCode("", "", "", block); + button.className = runButtonClass; + break; + case "groovy": return runCode(s.groovyPath, s.groovyArgs, s.groovyFileExtension, block, { shell: true }); + case "rust": return runCode(s.cargoPath, "eval" + s.cargoEvalArgs, s.rustFileExtension, block); + case "r": return runCode(s.RPath, s.RArgs, s.RFileExtension, block, { transform: (code) => addInlinePlotsToR(code) }); + case "go": return runCode(s.golangPath, s.golangArgs, s.golangFileExtension, block); + case "kotlin": return runCode(s.kotlinPath, s.kotlinArgs, s.kotlinFileExtension, block, { shell: true }); + case "ts": return runCode(s.tsPath, s.tsArgs, "ts", block, { shell: true }); + case "lua": return runCode(s.luaPath, s.luaArgs, s.luaFileExtension, block, { shell: true }); + case "dart": return runCode(s.dartPath, s.dartArgs, s.dartFileExtension, block, { shell: true }); + case "cs": return runCode(s.csPath, s.csArgs, s.csFileExtension, block, { shell: true }); + case "haskell": return (s.useGhci) + ? runCode(s.ghciPath, "", "hs", block, { shell: true }) + : runCode(s.runghcPath, "-f " + s.ghcPath, "hs", block, { shell: true }); + case "mathematica": return runCode(s.mathematicaPath, s.mathematicaArgs, s.mathematicaFileExtension, block, { shell: true }); + case "scala": return runCode(s.scalaPath, s.scalaArgs, s.scalaFileExtension, block, { shell: true }); + case "swift": return runCode(s.swiftPath, s.swiftArgs, s.swiftFileExtension, block, { shell: true }); + case "c": return runCode(s.clingPath, s.clingArgs, "c", block, { shell: true }); + case "ruby": return runCode(s.rubyPath, s.rubyArgs, s.rubyFileExtension, block, { shell: true }); + case "sql": return runCode(s.sqlPath, s.sqlArgs, "sql", block, { shell: true }); + case "octave": return runCode(s.octavePath, s.octaveArgs, s.octaveFileExtension, block, { shell: true, transform: (code) => addInlinePlotsToOctave(code) }); + case "maxima": return runCode(s.maximaPath, s.maximaArgs, s.maximaFileExtension, block, { shell: true, transform: (code) => addInlinePlotsToMaxima(code) }); + case "racket": return runCode(s.racketPath, s.racketArgs, s.racketFileExtension, block, { shell: true }); + case "applescript": return runCode(s.applescriptPath, s.applescriptArgs, s.applescriptFileExtension, block, { shell: true }); + 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 "latex": + const outputPath: string = await retrieveFigurePath(block.srcCode, s.latexFigureTitlePattern, block.markdownFile, s); + const invokeCompiler: string = [s.latexTexfotArgs, s.latexCompilerPath, s.latexCompilerArgs].join(" "); + return (!s.latexDoFilter) + ? runCode(s.latexCompilerPath, s.latexCompilerArgs, outputPath, block, { transform: (code) => modifyLatexCode(code, s) }) + : runCode(s.latexTexfotPath, invokeCompiler, outputPath, block, { transform: (code) => modifyLatexCode(code, s) }); + default: break; + } +} + +/** + * Adds run buttons to each open file. This is more robust and quicker than scanning + * the entire document, even though it requires more iteration, because it doesn't + * search the whole document. + */ +export function iterateOpenFilesAndAddRunButtons(plugin: PluginContext) { + const workspace: Workspace = plugin.app.workspace; + workspace.iterateRootLeaves(leaf => { + if (leaf.view instanceof MarkdownView) { + addRunButtons(leaf.view.contentEl, leaf.view.file.path, leaf.view, plugin); + } + }); +} + +/** + * Add a button to each code block that allows the user to run the code. The button is only added if the code block + * utilizes a language that is supported by this plugin. + * + * @param element The parent element (i.e. the currently showed html page / note). + * @param file An identifier for the currently showed note + */ +export function addRunButtons(element: HTMLElement, file: string, view: MarkdownView, plugin: PluginContext) { + Array.from(element.getElementsByTagName("code")) + .forEach((codeBlock: HTMLElement) => addRunButton(codeBlock, file, view, plugin)); +} + +function addRunButton(codeBlock: HTMLElement, file: string, view: MarkdownView, plugin: PluginContext) { + if (codeBlock.className.match(/^language-\{\w+/i)) { + codeBlock.className = codeBlock.className.replace(/^language-\{(\w+)/i, "language-$1 {"); + codeBlock.parentElement.className = codeBlock.className; + } + + const language = codeBlock.className.toLowerCase(); + + if (!language || !language.contains("language-")) + return; + + const pre = codeBlock.parentElement as HTMLPreElement; + const parent = pre.parentElement as HTMLDivElement; + + const srcCode = codeBlock.getText(); + let sanitizedClassList = sanitizeClassListOfCodeBlock(codeBlock); + + const canonicalLanguage = getLanguageAlias( + supportedLanguages.find(lang => sanitizedClassList.contains(`language-${lang}`)) + ) as LanguageId; + + const isLanguageSupported: Boolean = canonicalLanguage !== undefined; + const hasBlockBeenButtonifiedAlready = parent.classList.contains(hasButtonClass); + if (!isLanguageSupported || hasBlockBeenButtonifiedAlready) return; + + const outputter = new Outputter(codeBlock, plugin.settings, view, plugin.app, file); + parent.classList.add(hasButtonClass); + const button = createRunButton(); + pre.appendChild(button); + + const block: CodeBlockContext = { + srcCode: srcCode, + language: canonicalLanguage, + markdownFile: file, + button: button, + outputter: outputter, + executors: plugin.executors, + }; + + button.addEventListener("click", () => handleExecution(block)); +} + +function sanitizeClassListOfCodeBlock(codeBlock: HTMLElement) { + let sanitizedClassList = Array.from(codeBlock.classList); + return sanitizedClassList.map(c => c.toLowerCase()); +} + +/** + * Creates a new run button and returns it. + * + * @returns { HTMLButtonElement } The newly created run button. + */ +function createRunButton(): HTMLButtonElement { + console.debug("Add run button"); + const button = document.createElement("button"); + button.classList.add(runButtonClass); + button.setText(buttonText); + return button; +} + +/** + * Executes the code with the given command and arguments. The code is written to a temporary file and then executed. + * The output of the code is displayed in the output panel ({@link Outputter}). + * If the code execution fails, an error message is displayed and logged. + * After the code execution, the temporary file is deleted and the run button is re-enabled. + * @param cmd The command that should be used to execute the code. (e.g. python, java, ...) + * @param cmdArgs Additional arguments that should be passed to the command. + * @param ext The file extension of the temporary file. Should correspond to the language of the code. (e.g. py, ...) + * @param block Contains context needed for execution including source code, output handler, and UI elements + */ +function runCode(cmd: string, cmdArgs: string, ext: string, block: CodeBlockContext, options?: { shell?: boolean; transform?: (code: string) => string; }) { + const useShell: boolean = (options?.shell) ? options.shell : false; + if (options?.transform) block.srcCode = options.transform(block.srcCode); + if (!useShell) block.outputter.startBlock(); + + const executor = block.executors.getExecutorFor(block.markdownFile, block.language, useShell); + executor.run(block.srcCode, block.outputter, cmd, cmdArgs, ext).then(() => { + block.button.className = runButtonClass; + if (!useShell) { + block.outputter.closeInput(); + block.outputter.finishBlock(); + } + }); +} diff --git a/src/main.ts b/src/main.ts index 92ae9927..1c367bba 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,28 +1,9 @@ -import { - App, - Component, - MarkdownRenderer, - MarkdownView, - Plugin, - Workspace, -} from 'obsidian'; +import { App, Component, MarkdownRenderer, MarkdownView, Plugin, } from 'obsidian'; -import { Outputter, TOGGLE_HTML_SIGIL } from "./output/Outputter"; import type { ExecutorSettings } from "./settings/Settings"; import { DEFAULT_SETTINGS } from "./settings/Settings"; import { SettingsTab } from "./settings/SettingsTab"; -import { getLanguageAlias } from './transforms/TransformCode'; -import { CodeInjector } from "./transforms/CodeInjector"; -import { - addInlinePlotsToPython, - addInlinePlotsToR, - addMagicToJS, - addMagicToPython, - addInlinePlotsToOctave, - addInlinePlotsToMaxima -} from "./transforms/Magic"; -import { modifyLatexCode, applyLatexBodyClasses } from "./transforms/LatexTransformer" -import { retrieveFigurePath } from './transforms/LatexFigureName'; +import { applyLatexBodyClasses } from "./transforms/LatexTransformer" import ExecutorContainer from './ExecutorContainer'; import ExecutorManagerView, { @@ -32,6 +13,7 @@ import ExecutorManagerView, { import runAllCodeBlocks from './runAllCodeBlocks'; import { ReleaseNoteModel } from "./ReleaseNoteModal"; +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", @@ -40,21 +22,6 @@ export const canonicalLanguages = ["js", "ts", "cs", "latex", "lean", "lua", "py export const supportedLanguages = [...languageAliases, ...canonicalLanguages] as const; export type LanguageId = typeof canonicalLanguages[number]; -const buttonText = "Run"; - -export const runButtonClass = "run-code-button"; -const runButtonDisabledClass = "run-button-disabled"; -const hasButtonClass = "has-run-code-button"; - -interface CodeBlockContext { - srcCode: string; - button: HTMLButtonElement; - language: LanguageId; - markdownFile: string; - outputter: Outputter; - executors: ExecutorContainer; -} - export interface PluginContext { app: App; settings: ExecutorSettings; @@ -79,9 +46,9 @@ export default class ExecuteCodePlugin extends Plugin { settings: this.settings, executors: this.executors, } - iterateOpenFilesAndAddRunButtons(context); + runButton.iterateOpenFilesAndAddRunButtons(context); this.registerMarkdownPostProcessor((element, _context) => { - addRunButtons(element, _context.sourcePath, this.app.workspace.getActiveViewOfType(MarkdownView), context); + runButton.addRunButtons(element, _context.sourcePath, this.app.workspace.getActiveViewOfType(MarkdownView), context); }); // live preview renderers @@ -132,17 +99,17 @@ export default class ExecuteCodePlugin extends Plugin { const pre = codeBlock.parentElement as HTMLPreElement; const parent = pre.parentElement as HTMLDivElement; - if (parent.hasClass(hasButtonClass)) { - parent.removeClass(hasButtonClass); + if (parent.hasClass(runButton.hasButtonClass)) { + parent.removeClass(runButton.hasButtonClass); } }); document - .querySelectorAll("." + runButtonClass) + .querySelectorAll("." + runButton.runButtonClass) .forEach((button: HTMLButtonElement) => button.remove()); document - .querySelectorAll("." + runButtonDisabledClass) + .querySelectorAll("." + runButton.runButtonDisabledClass) .forEach((button: HTMLButtonElement) => button.remove()); document @@ -177,177 +144,4 @@ export default class ExecuteCodePlugin extends Plugin { async saveSettings() { await this.saveData(this.settings); } -} - -/** - * Adds run buttons to each open file. This is more robust and quicker than scanning - * the entire document, even though it requires more iteration, because it doesn't - * search the whole document. - */ -function iterateOpenFilesAndAddRunButtons(plugin: PluginContext) { - const workspace: Workspace = plugin.app.workspace; - workspace.iterateRootLeaves(leaf => { - if (leaf.view instanceof MarkdownView) { - addRunButtons(leaf.view.contentEl, leaf.view.file.path, leaf.view, plugin); - } - }) -} - -/** - * Add a button to each code block that allows the user to run the code. The button is only added if the code block - * utilizes a language that is supported by this plugin. - * - * @param element The parent element (i.e. the currently showed html page / note). - * @param file An identifier for the currently showed note - */ -function addRunButtons(element: HTMLElement, file: string, view: MarkdownView, plugin: PluginContext) { - Array.from(element.getElementsByTagName("code")) - .forEach((codeBlock: HTMLElement) => addRunButton(codeBlock, file, view, plugin)); -} - -function addRunButton(codeBlock: HTMLElement, file: string, view: MarkdownView, plugin: PluginContext) { - if (codeBlock.className.match(/^language-\{\w+/i)) { - codeBlock.className = codeBlock.className.replace(/^language-\{(\w+)/i, "language-$1 {"); - codeBlock.parentElement.className = codeBlock.className; - } - - const language = codeBlock.className.toLowerCase(); - - if (!language || !language.contains("language-")) - return; - - const pre = codeBlock.parentElement as HTMLPreElement; - const parent = pre.parentElement as HTMLDivElement; - - const srcCode = codeBlock.getText(); - let sanitizedClassList = sanitizeClassListOfCodeBlock(codeBlock); - - const canonicalLanguage = getLanguageAlias( - supportedLanguages.find(lang => sanitizedClassList.contains(`language-${lang}`)) - ) as LanguageId; - - const isLanguageSupported: Boolean = canonicalLanguage !== undefined; - const hasBlockBeenButtonifiedAlready = parent.classList.contains(hasButtonClass); - if (!isLanguageSupported || hasBlockBeenButtonifiedAlready) return; - - const outputter = new Outputter(codeBlock, plugin.settings, view, plugin.app, file); - parent.classList.add(hasButtonClass); - const button = createRunButton(); - pre.appendChild(button); - - const block: CodeBlockContext = { - srcCode: srcCode, - language: canonicalLanguage, - markdownFile: file, - button: button, - outputter: outputter, - executors: plugin.executors, - }; - - button.addEventListener("click", () => handleExecution(block)); -} - -function sanitizeClassListOfCodeBlock(codeBlock: HTMLElement) { - let sanitizedClassList = Array.from(codeBlock.classList); - return sanitizedClassList.map(c => c.toLowerCase()); -} - -/** - * Handles the execution of code blocks based on the selected programming language. - * Injects any required code, transforms the source if needed, and manages button state. - * @param block Contains context needed for execution including source code, output handler, and UI elements - */ -async function handleExecution(block: CodeBlockContext) { - const language: LanguageId = block.language; - const button: HTMLButtonElement = block.button; - const srcCode: string = block.srcCode; - const app: App = block.outputter.app; - const s: ExecutorSettings = block.outputter.settings; - - button.className = runButtonDisabledClass; - block.srcCode = await new CodeInjector(app, s, language).injectCode(srcCode); - - switch (language) { - case "js": return runCode(s.nodePath, s.nodeArgs, s.jsFileExtension, block, { transform: (code) => addMagicToJS(code) }); - case "java": return runCode(s.javaPath, s.javaArgs, s.javaFileExtension, block); - case "python": return runCode(s.pythonPath, s.pythonArgs, s.pythonFileExtension, block, { transform: (code) => addMagicToPython(code, s) }); - case "shell": return runCode(s.shellPath, s.shellArgs, s.shellFileExtension, block, { shell: true }); - case "batch": return runCode(s.batchPath, s.batchArgs, s.batchFileExtension, block, { shell: true }); - case "powershell": return runCode(s.powershellPath, s.powershellArgs, s.powershellFileExtension, block, { shell: true }); - case "cpp": return runCode(s.clingPath, `-std=${s.clingStd} ${s.clingArgs}`, s.cppFileExtension, block); - case "prolog": - runCode("", "", "", block); - button.className = runButtonClass; - break; - case "groovy": return runCode(s.groovyPath, s.groovyArgs, s.groovyFileExtension, block, { shell: true }); - case "rust": return runCode(s.cargoPath, "eval" + s.cargoEvalArgs, s.rustFileExtension, block); - case "r": return runCode(s.RPath, s.RArgs, s.RFileExtension, block, { transform: (code) => addInlinePlotsToR(code) }); - case "go": return runCode(s.golangPath, s.golangArgs, s.golangFileExtension, block); - case "kotlin": return runCode(s.kotlinPath, s.kotlinArgs, s.kotlinFileExtension, block, { shell: true }); - case "ts": return runCode(s.tsPath, s.tsArgs, "ts", block, { shell: true }); - case "lua": return runCode(s.luaPath, s.luaArgs, s.luaFileExtension, block, { shell: true }); - case "dart": return runCode(s.dartPath, s.dartArgs, s.dartFileExtension, block, { shell: true }); - case "cs": return runCode(s.csPath, s.csArgs, s.csFileExtension, block, { shell: true }); - case "haskell": return (s.useGhci) - ? runCode(s.ghciPath, "", "hs", block, { shell: true }) - : runCode(s.runghcPath, "-f " + s.ghcPath, "hs", block, { shell: true }); - case "mathematica": return runCode(s.mathematicaPath, s.mathematicaArgs, s.mathematicaFileExtension, block, { shell: true }); - case "scala": return runCode(s.scalaPath, s.scalaArgs, s.scalaFileExtension, block, { shell: true }); - case "swift": return runCode(s.swiftPath, s.swiftArgs, s.swiftFileExtension, block, { shell: true }); - case "c": return runCode(s.clingPath, s.clingArgs, "c", block, { shell: true }); - case "ruby": return runCode(s.rubyPath, s.rubyArgs, s.rubyFileExtension, block, { shell: true }); - case "sql": return runCode(s.sqlPath, s.sqlArgs, "sql", block, { shell: true }); - case "octave": return runCode(s.octavePath, s.octaveArgs, s.octaveFileExtension, block, { shell: true, transform: (code) => addInlinePlotsToOctave(code) }); - case "maxima": return runCode(s.maximaPath, s.maximaArgs, s.maximaFileExtension, block, { shell: true, transform: (code) => addInlinePlotsToMaxima(code) }); - case "racket": return runCode(s.racketPath, s.racketArgs, s.racketFileExtension, block, { shell: true }); - case "applescript": return runCode(s.applescriptPath, s.applescriptArgs, s.applescriptFileExtension, block, { shell: true }); - 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 "latex": - const outputPath: string = await retrieveFigurePath(block.srcCode, s.latexFigureTitlePattern, block.markdownFile, s); - const invokeCompiler: string = [s.latexTexfotArgs, s.latexCompilerPath, s.latexCompilerArgs].join(" "); - return (!s.latexDoFilter) - ? runCode(s.latexCompilerPath, s.latexCompilerArgs, outputPath, block, { transform: (code) => modifyLatexCode(code, s) }) - : runCode(s.latexTexfotPath, invokeCompiler, outputPath, block, { transform: (code) => modifyLatexCode(code, s) }); - default: break; - } -} - -/** - * Creates a new run button and returns it. - * - * @returns { HTMLButtonElement } The newly created run button. - */ -function createRunButton(): HTMLButtonElement { - console.debug("Add run button"); - const button = document.createElement("button"); - button.classList.add(runButtonClass); - button.setText(buttonText); - return button; -} - -/** - * Executes the code with the given command and arguments. The code is written to a temporary file and then executed. - * The output of the code is displayed in the output panel ({@link Outputter}). - * If the code execution fails, an error message is displayed and logged. - * After the code execution, the temporary file is deleted and the run button is re-enabled. - * @param cmd The command that should be used to execute the code. (e.g. python, java, ...) - * @param cmdArgs Additional arguments that should be passed to the command. - * @param ext The file extension of the temporary file. Should correspond to the language of the code. (e.g. py, ...) - * @param block Contains context needed for execution including source code, output handler, and UI elements - */ -function runCode(cmd: string, cmdArgs: string, ext: string, block: CodeBlockContext, options?: { shell?: boolean, transform?: (code: string) => string }) { - const useShell: boolean = (options?.shell) ? options.shell : false; - if (options?.transform) block.srcCode = options.transform(block.srcCode); - if (!useShell) block.outputter.startBlock(); - - const executor = block.executors.getExecutorFor(block.markdownFile, block.language, useShell); - executor.run(block.srcCode, block.outputter, cmd, cmdArgs, ext).then(() => { - block.button.className = runButtonClass; - if (!useShell) { - block.outputter.closeInput(); - block.outputter.finishBlock(); - } - }); -} +} \ No newline at end of file diff --git a/src/runAllCodeBlocks.ts b/src/runAllCodeBlocks.ts index e6f783b8..6d82176e 100644 --- a/src/runAllCodeBlocks.ts +++ b/src/runAllCodeBlocks.ts @@ -1,5 +1,5 @@ -import {TextFileView, Workspace} from "obsidian"; -import {runButtonClass} from "./main"; +import { TextFileView, Workspace } from "obsidian"; +import { runButtonClass } from './RunButton'; export default function runAllCodeBlocks(workspace: Workspace) { const lastActiveView = workspace.getMostRecentLeaf().view; From b3ef5292380f78564e0ceb8827338023dbbf1d4f Mon Sep 17 00:00:00 2001 From: Yetenol Date: Sun, 2 Feb 2025 18:36:40 +0100 Subject: [PATCH 25/34] refactor: use expressive, non-generic function names --- src/RunButton.ts | 57 ++++++++++++++++++++++++----------------- src/main.ts | 12 ++++----- src/runAllCodeBlocks.ts | 4 +-- 3 files changed, 42 insertions(+), 31 deletions(-) diff --git a/src/RunButton.ts b/src/RunButton.ts index 805ee2ea..673b1fa1 100644 --- a/src/RunButton.ts +++ b/src/RunButton.ts @@ -11,9 +11,9 @@ import { getLanguageAlias } from './transforms/TransformCode'; const buttonText = "Run"; -export const runButtonClass = "run-code-button"; -export const runButtonDisabledClass = "run-button-disabled"; -export const hasButtonClass = "has-run-code-button"; +export const buttonClass: string = "run-code-button"; +export const disabledClass: string = "run-button-disabled"; +export const codeBlockHasButtonClass: string = "has-run-code-button"; interface CodeBlockContext { srcCode: string; @@ -36,7 +36,7 @@ async function handleExecution(block: CodeBlockContext) { const app: App = block.outputter.app; const s: ExecutorSettings = block.outputter.settings; - button.className = runButtonDisabledClass; + button.className = disabledClass; block.srcCode = await new CodeInjector(app, s, language).injectCode(srcCode); switch (language) { @@ -49,7 +49,7 @@ async function handleExecution(block: CodeBlockContext) { case "cpp": return runCode(s.clingPath, `-std=${s.clingStd} ${s.clingArgs}`, s.cppFileExtension, block); case "prolog": runCode("", "", "", block); - button.className = runButtonClass; + button.className = buttonClass; break; case "groovy": return runCode(s.groovyPath, s.groovyArgs, s.groovyFileExtension, block, { shell: true }); case "rust": return runCode(s.cargoPath, "eval" + s.cargoEvalArgs, s.rustFileExtension, block); @@ -87,15 +87,15 @@ async function handleExecution(block: CodeBlockContext) { } /** - * Adds run buttons to each open file. This is more robust and quicker than scanning - * the entire document, even though it requires more iteration, because it doesn't - * search the whole document. + * Adds run buttons to code blocks in all currently open Markdown files. + * More efficient than scanning entire documents since it only processes visible content. + * @param plugin Contains context needed for execution. */ -export function iterateOpenFilesAndAddRunButtons(plugin: PluginContext) { +export function addInOpenFiles(plugin: PluginContext) { const workspace: Workspace = plugin.app.workspace; workspace.iterateRootLeaves(leaf => { if (leaf.view instanceof MarkdownView) { - addRunButtons(leaf.view.contentEl, leaf.view.file.path, leaf.view, plugin); + addToAllCodeBlocks(leaf.view.contentEl, leaf.view.file.path, leaf.view, plugin); } }); } @@ -103,16 +103,24 @@ export function iterateOpenFilesAndAddRunButtons(plugin: PluginContext) { /** * Add a button to each code block that allows the user to run the code. The button is only added if the code block * utilizes a language that is supported by this plugin. - * * @param element The parent element (i.e. the currently showed html page / note). * @param file An identifier for the currently showed note - */ -export function addRunButtons(element: HTMLElement, file: string, view: MarkdownView, plugin: PluginContext) { + * @param view The current markdown view + * @param plugin Contains context needed for execution. +*/ +export function addToAllCodeBlocks(element: HTMLElement, file: string, view: MarkdownView, plugin: PluginContext) { Array.from(element.getElementsByTagName("code")) - .forEach((codeBlock: HTMLElement) => addRunButton(codeBlock, file, view, plugin)); + .forEach((codeBlock: HTMLElement) => addToCodeBlock(codeBlock, file, view, plugin)); } -function addRunButton(codeBlock: HTMLElement, file: string, view: MarkdownView, plugin: PluginContext) { +/** + * Processes a code block to add execution capabilities. Ensures buttons aren't duplicated on already processed blocks. + * @param codeBlock The code block element to process + * @param file Path to the current markdown file + * @param view The current markdown view + * @param plugin Contains context needed for execution. + */ +function addToCodeBlock(codeBlock: HTMLElement, file: string, view: MarkdownView, plugin: PluginContext) { if (codeBlock.className.match(/^language-\{\w+/i)) { codeBlock.className = codeBlock.className.replace(/^language-\{(\w+)/i, "language-$1 {"); codeBlock.parentElement.className = codeBlock.className; @@ -134,12 +142,12 @@ function addRunButton(codeBlock: HTMLElement, file: string, view: MarkdownView, ) as LanguageId; const isLanguageSupported: Boolean = canonicalLanguage !== undefined; - const hasBlockBeenButtonifiedAlready = parent.classList.contains(hasButtonClass); + const hasBlockBeenButtonifiedAlready = parent.classList.contains(codeBlockHasButtonClass); if (!isLanguageSupported || hasBlockBeenButtonifiedAlready) return; const outputter = new Outputter(codeBlock, plugin.settings, view, plugin.app, file); - parent.classList.add(hasButtonClass); - const button = createRunButton(); + parent.classList.add(codeBlockHasButtonClass); + const button = createButton(); pre.appendChild(button); const block: CodeBlockContext = { @@ -154,6 +162,11 @@ function addRunButton(codeBlock: HTMLElement, file: string, view: MarkdownView, button.addEventListener("click", () => handleExecution(block)); } +/** + * Normalizes language class names to ensure consistent processing. + * @param codeBlock - The code block element whose classes need to be sanitized + * @returns Array of normalized class names + */ function sanitizeClassListOfCodeBlock(codeBlock: HTMLElement) { let sanitizedClassList = Array.from(codeBlock.classList); return sanitizedClassList.map(c => c.toLowerCase()); @@ -161,13 +174,11 @@ function sanitizeClassListOfCodeBlock(codeBlock: HTMLElement) { /** * Creates a new run button and returns it. - * - * @returns { HTMLButtonElement } The newly created run button. */ -function createRunButton(): HTMLButtonElement { +function createButton(): HTMLButtonElement { console.debug("Add run button"); const button = document.createElement("button"); - button.classList.add(runButtonClass); + button.classList.add(buttonClass); button.setText(buttonText); return button; } @@ -189,7 +200,7 @@ function runCode(cmd: string, cmdArgs: string, ext: string, block: CodeBlockCont const executor = block.executors.getExecutorFor(block.markdownFile, block.language, useShell); executor.run(block.srcCode, block.outputter, cmd, cmdArgs, ext).then(() => { - block.button.className = runButtonClass; + block.button.className = buttonClass; if (!useShell) { block.outputter.closeInput(); block.outputter.finishBlock(); diff --git a/src/main.ts b/src/main.ts index 1c367bba..49cf6b83 100644 --- a/src/main.ts +++ b/src/main.ts @@ -46,9 +46,9 @@ export default class ExecuteCodePlugin extends Plugin { settings: this.settings, executors: this.executors, } - runButton.iterateOpenFilesAndAddRunButtons(context); + runButton.addInOpenFiles(context); this.registerMarkdownPostProcessor((element, _context) => { - runButton.addRunButtons(element, _context.sourcePath, this.app.workspace.getActiveViewOfType(MarkdownView), context); + runButton.addToAllCodeBlocks(element, _context.sourcePath, this.app.workspace.getActiveViewOfType(MarkdownView), context); }); // live preview renderers @@ -99,17 +99,17 @@ export default class ExecuteCodePlugin extends Plugin { const pre = codeBlock.parentElement as HTMLPreElement; const parent = pre.parentElement as HTMLDivElement; - if (parent.hasClass(runButton.hasButtonClass)) { - parent.removeClass(runButton.hasButtonClass); + if (parent.hasClass(runButton.codeBlockHasButtonClass)) { + parent.removeClass(runButton.codeBlockHasButtonClass); } }); document - .querySelectorAll("." + runButton.runButtonClass) + .querySelectorAll("." + runButton.buttonClass) .forEach((button: HTMLButtonElement) => button.remove()); document - .querySelectorAll("." + runButton.runButtonDisabledClass) + .querySelectorAll("." + runButton.disabledClass) .forEach((button: HTMLButtonElement) => button.remove()); document diff --git a/src/runAllCodeBlocks.ts b/src/runAllCodeBlocks.ts index 6d82176e..3561bac7 100644 --- a/src/runAllCodeBlocks.ts +++ b/src/runAllCodeBlocks.ts @@ -1,11 +1,11 @@ import { TextFileView, Workspace } from "obsidian"; -import { runButtonClass } from './RunButton'; +import { buttonClass } from './RunButton'; export default function runAllCodeBlocks(workspace: Workspace) { const lastActiveView = workspace.getMostRecentLeaf().view; if (lastActiveView instanceof TextFileView) { - lastActiveView.containerEl.querySelectorAll("button." + runButtonClass).forEach((button: HTMLButtonElement) => { + lastActiveView.containerEl.querySelectorAll("button." + buttonClass).forEach((button: HTMLButtonElement) => { button.click(); }); } From d05ec0e2de9e1a97dbba409f90a550bd5d32dd14 Mon Sep 17 00:00:00 2001 From: Yetenol Date: Sun, 2 Feb 2025 13:10:30 +0100 Subject: [PATCH 26/34] fix Named capturing groups are only available when targeting 'ES2018' or later --- esbuild.config.mjs | 2 +- tsconfig.json | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/esbuild.config.mjs b/esbuild.config.mjs index 645372f0..b7c3a2df 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -44,7 +44,7 @@ esbuild.build({ ...builtins], format: 'cjs', watch: !prod, - target: 'es2016', + target: 'es2018', logLevel: "info", sourcemap: prod ? false : 'inline', treeShaking: true, diff --git a/tsconfig.json b/tsconfig.json index 4a1cd438..80f4a6d3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "inlineSourceMap": true, "inlineSources": true, "module": "ESNext", - "target": "ES6", + "target": "ES2018", "allowJs": true, "noImplicitAny": true, "moduleResolution": "node", @@ -14,11 +14,12 @@ "DOM", "ES5", "ES6", - "ES7" + "ES7", + "ES2018" ] }, "include": [ "**/*.ts", "src/*ts" ] -} +} \ No newline at end of file From 424ff37ef43b0ef9eaf4ce90af949adc25bc5048 Mon Sep 17 00:00:00 2001 From: Yetenol Date: Sun, 2 Feb 2025 16:31:09 +0100 Subject: [PATCH 27/34] refactor: use technical term for macro expansion --- src/RunButton.ts | 12 +++---- src/transforms/Magic.ts | 56 ++++++++++++++++----------------- src/transforms/TransformCode.ts | 20 ++++++------ 3 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/RunButton.ts b/src/RunButton.ts index 673b1fa1..a3dae51d 100644 --- a/src/RunButton.ts +++ b/src/RunButton.ts @@ -6,7 +6,7 @@ import type { ExecutorSettings } from './settings/Settings'; import { CodeInjector } from './transforms/CodeInjector'; import { retrieveFigurePath } from './transforms/LatexFigureName'; import { modifyLatexCode } from './transforms/LatexTransformer'; -import { addMagicToJS, addMagicToPython, addInlinePlotsToR, addInlinePlotsToOctave, addInlinePlotsToMaxima } from './transforms/Magic'; +import * as macro from './transforms/Magic'; import { getLanguageAlias } from './transforms/TransformCode'; const buttonText = "Run"; @@ -40,9 +40,9 @@ async function handleExecution(block: CodeBlockContext) { block.srcCode = await new CodeInjector(app, s, language).injectCode(srcCode); switch (language) { - case "js": return runCode(s.nodePath, s.nodeArgs, s.jsFileExtension, block, { transform: (code) => addMagicToJS(code) }); + case "js": return runCode(s.nodePath, s.nodeArgs, s.jsFileExtension, block, { transform: (code) => macro.expandJS(code) }); case "java": return runCode(s.javaPath, s.javaArgs, s.javaFileExtension, block); - case "python": return runCode(s.pythonPath, s.pythonArgs, s.pythonFileExtension, block, { transform: (code) => addMagicToPython(code, s) }); + case "python": return runCode(s.pythonPath, s.pythonArgs, s.pythonFileExtension, block, { transform: (code) => macro.expandPython(code, s) }); case "shell": return runCode(s.shellPath, s.shellArgs, s.shellFileExtension, block, { shell: true }); case "batch": return runCode(s.batchPath, s.batchArgs, s.batchFileExtension, block, { shell: true }); case "powershell": return runCode(s.powershellPath, s.powershellArgs, s.powershellFileExtension, block, { shell: true }); @@ -53,7 +53,7 @@ async function handleExecution(block: CodeBlockContext) { break; case "groovy": return runCode(s.groovyPath, s.groovyArgs, s.groovyFileExtension, block, { shell: true }); case "rust": return runCode(s.cargoPath, "eval" + s.cargoEvalArgs, s.rustFileExtension, block); - case "r": return runCode(s.RPath, s.RArgs, s.RFileExtension, block, { transform: (code) => addInlinePlotsToR(code) }); + case "r": return runCode(s.RPath, s.RArgs, s.RFileExtension, block, { transform: (code) => macro.expandRPlots(code) }); case "go": return runCode(s.golangPath, s.golangArgs, s.golangFileExtension, block); case "kotlin": return runCode(s.kotlinPath, s.kotlinArgs, s.kotlinFileExtension, block, { shell: true }); case "ts": return runCode(s.tsPath, s.tsArgs, "ts", block, { shell: true }); @@ -69,8 +69,8 @@ async function handleExecution(block: CodeBlockContext) { case "c": return runCode(s.clingPath, s.clingArgs, "c", block, { shell: true }); case "ruby": return runCode(s.rubyPath, s.rubyArgs, s.rubyFileExtension, block, { shell: true }); case "sql": return runCode(s.sqlPath, s.sqlArgs, "sql", block, { shell: true }); - case "octave": return runCode(s.octavePath, s.octaveArgs, s.octaveFileExtension, block, { shell: true, transform: (code) => addInlinePlotsToOctave(code) }); - case "maxima": return runCode(s.maximaPath, s.maximaArgs, s.maximaFileExtension, block, { shell: true, transform: (code) => addInlinePlotsToMaxima(code) }); + case "octave": return runCode(s.octavePath, s.octaveArgs, s.octaveFileExtension, block, { shell: true, transform: (code) => macro.expandOctavePlot(code) }); + case "maxima": return runCode(s.maximaPath, s.maximaArgs, s.maximaFileExtension, block, { shell: true, transform: (code) => macro.expandMaximaPlot(code) }); case "racket": return runCode(s.racketPath, s.racketArgs, s.racketFileExtension, block, { shell: true }); case "applescript": return runCode(s.applescriptPath, s.applescriptArgs, s.applescriptFileExtension, block, { shell: true }); case "zig": return runCode(s.zigPath, s.zigArgs, "zig", block, { shell: true }); diff --git a/src/transforms/Magic.ts b/src/transforms/Magic.ts index 62ebfa0c..50ec4c1a 100644 --- a/src/transforms/Magic.ts +++ b/src/transforms/Magic.ts @@ -12,7 +12,7 @@ */ import * as os from "os"; -import {Platform} from 'obsidian'; +import { Platform } from 'obsidian'; import { TOGGLE_HTML_SIGIL } from "src/output/Outputter"; import { ExecutorSettings } from "src/settings/Settings"; @@ -41,7 +41,7 @@ const MAXIMA_PLOT_REGEX = /^plot2d\s*\(.*\[.+\]\)\s*[$;]/gm; * @param vaultPath The path of the vault. * @returns The transformed source code. */ -export function insertVaultPath(source: string, vaultPath: string): string { +export function expandVaultPath(source: string, vaultPath: string): string { source = source.replace(VAULT_PATH_REGEX, `"${vaultPath.replace(/\\/g, "/")}"`); source = source.replace(VAULT_URL_REGEX, `"${Platform.resourcePathPrefix + vaultPath.replace(/\\/g, "/")}"`); source = source.replace(VAULT_REGEX, `"${Platform.resourcePathPrefix + vaultPath.replace(/\\/g, "/")}"`); @@ -57,7 +57,7 @@ export function insertVaultPath(source: string, vaultPath: string): string { * @param notePath The path of the vault. * @returns The transformed source code. */ -export function insertNotePath(source: string, notePath: string): string { +export function expandNotePath(source: string, notePath: string): string { source = source.replace(CURRENT_NOTE_PATH_REGEX, `"${notePath.replace(/\\/g, "/")}"`); source = source.replace(CURRENT_NOTE_URL_REGEX, `"${Platform.resourcePathPrefix + notePath.replace(/\\/g, "/")}"`); source = source.replace(CURRENT_NOTE_REGEX, `"${Platform.resourcePathPrefix + notePath.replace(/\\/g, "/")}"`); @@ -67,13 +67,13 @@ export function insertNotePath(source: string, notePath: string): string { /** - * Parses the source code for the @vault command and replaces it with the vault path. + * Parses the source code for the @title command and replaces it with the vault path. * * @param source The source code to parse. * @param noteTitle The path of the vault. * @returns The transformed source code. */ -export function insertNoteTitle(source: string, noteTitle: string): string { +export function expandNoteTitle(source: string, noteTitle: string): string { let t = ""; if (noteTitle.contains(".")) t = noteTitle.split(".").slice(0, -1).join("."); @@ -88,7 +88,7 @@ export function insertNoteTitle(source: string, noteTitle: string): string { * @param noteTitle The current colour theme. * @returns The transformed source code. */ -export function insertColorTheme(source: string, theme: string): string { +export function expandColorTheme(source: string, theme: string): string { return source.replace(COLOR_THEME_REGEX, `"${theme}"`); } @@ -98,12 +98,12 @@ export function insertColorTheme(source: string, theme: string): string { * @param source The source code to parse. * @returns The transformed source code. */ -export function addMagicToPython(source: string, settings: ExecutorSettings): string { +export function expandPython(source: string, settings: ExecutorSettings): string { if (settings.pythonEmbedPlots) { - source = addInlinePlotsToPython(source, TOGGLE_HTML_SIGIL); + source = expandPythonPlots(source, TOGGLE_HTML_SIGIL); } - source = pythonParseShowImage(source); - source = pythonParseHtmlFunction(source); + source = expandPythonShowImage(source); + source = expandPythonHtmlMacro(source); return source; } @@ -114,9 +114,9 @@ export function addMagicToPython(source: string, settings: ExecutorSettings): st * @param source The source code to parse. * @returns The transformed source code. */ -export function addMagicToJS(source: string): string { - source = jsParseShowImage(source); - source = jsParseHtmlFunction(source); +export function expandJS(source: string): string { + source = expandJsShowImage(source); + source = expandJsHtmlMacro(source); return source; } @@ -129,7 +129,7 @@ export function addMagicToJS(source: string): string { * @param toggleHtmlSigil The meta-command to allow and disallow HTML * @returns The transformed source code. */ -export function addInlinePlotsToPython(source: string, toggleHtmlSigil: string): string { +export function expandPythonPlots(source: string, toggleHtmlSigil: string): string { const showPlot = `import io; import sys; __obsidian_execute_code_temp_pyplot_var__=io.BytesIO(); plt.plot(); plt.savefig(__obsidian_execute_code_temp_pyplot_var__, format='svg'); plt.close(); sys.stdout.write(${JSON.stringify(toggleHtmlSigil)}); sys.stdout.flush(); sys.stdout.buffer.write(__obsidian_execute_code_temp_pyplot_var__.getvalue()); sys.stdout.flush(); sys.stdout.write(${JSON.stringify(toggleHtmlSigil)}); sys.stdout.flush()`; return source.replace(PYTHON_PLOT_REGEX, showPlot); } @@ -142,7 +142,7 @@ export function addInlinePlotsToPython(source: string, toggleHtmlSigil: string): * @param source The source code to parse. * @returns The transformed source code. */ -export function addInlinePlotsToR(source: string): string { +export function expandRPlots(source: string): string { const matches = source.matchAll(R_PLOT_REGEX); for (const match of matches) { const tempFile = `${os.tmpdir()}/temp_${Date.now()}.png`.replace(/\\/g, "/"); @@ -159,7 +159,7 @@ export function addInlinePlotsToR(source: string): string { * Parses the PYTHON code for the @show command and replaces it with the image. * @param source The source code to parse. */ -function pythonParseShowImage(source: string): string { +function expandPythonShowImage(source: string): string { const matches = source.matchAll(SHOW_REGEX); for (const match of matches) { const imagePath = match.groups.path; @@ -167,7 +167,7 @@ function pythonParseShowImage(source: string): string { const height = match.groups.height; const alignment = match.groups.align; - const image = buildMagicShowImage(imagePath.replace(/\\/g, "\\\\"), width, height, alignment); + const image = expandShowImage(imagePath.replace(/\\/g, "\\\\"), width, height, alignment); source = source.replace(match[0], "print(\'" + TOGGLE_HTML_SIGIL + image + TOGGLE_HTML_SIGIL + "\')"); } @@ -178,15 +178,15 @@ function pythonParseShowImage(source: string): string { * Parses the PYTHON code for the @html command and surrounds it with the toggle-escaoe token. * @param source */ -function pythonParseHtmlFunction(source: string): string { +function expandPythonHtmlMacro(source: string): string { const matches = source.matchAll(HTML_REGEX); - for(const match of matches) { + for (const match of matches) { const html = match.groups.html; - + const toggle = JSON.stringify(TOGGLE_HTML_SIGIL); - + source = source.replace(match[0], `print(${toggle}); print(${html}); print(${toggle})`) - } + } return source; } @@ -195,7 +195,7 @@ function pythonParseHtmlFunction(source: string): string { * Parses the JAVASCRIPT code for the @show command and replaces it with the image. * @param source The source code to parse. */ -function jsParseShowImage(source: string): string { +function expandJsShowImage(source: string): string { const matches = source.matchAll(SHOW_REGEX); for (const match of matches) { const imagePath = match.groups.path; @@ -203,7 +203,7 @@ function jsParseShowImage(source: string): string { const height = match.groups.height; const alignment = match.groups.align; - const image = buildMagicShowImage(imagePath.replace(/\\/g, "\\\\"), width, height, alignment); + const image = expandShowImage(imagePath.replace(/\\/g, "\\\\"), width, height, alignment); source = source.replace(match[0], "console.log(\'" + TOGGLE_HTML_SIGIL + image + TOGGLE_HTML_SIGIL + "\')"); console.log(source); @@ -212,7 +212,7 @@ function jsParseShowImage(source: string): string { return source; } -function jsParseHtmlFunction(source: string): string { +function expandJsHtmlMacro(source: string): string { const matches = source.matchAll(HTML_REGEX); for (const match of matches) { const html = match.groups.html; @@ -234,7 +234,7 @@ function jsParseHtmlFunction(source: string): string { * @param height The image height. * @param alignment The image alignment. */ -function buildMagicShowImage(imagePath: string, width: string = "0", height: string = "0", alignment: string = "center"): string { +function expandShowImage(imagePath: string, width: string = "0", height: string = "0", alignment: string = "center"): string { if (imagePath.contains("+")) { let splittedPath = imagePath.replace(/['"]/g, "").split("+"); splittedPath = splittedPath.map(element => element.trim()) @@ -247,7 +247,7 @@ function buildMagicShowImage(imagePath: string, width: string = "0", height: str return `Image found at path ${imagePath}.`; } -export function addInlinePlotsToOctave(source: string): string { +export function expandOctavePlot(source: string): string { const matches = source.matchAll(OCTAVE_PLOT_REGEX); for (const match of matches) { const tempFile = `${os.tmpdir()}/temp_${Date.now()}.png`.replace(/\\/g, "/"); @@ -259,7 +259,7 @@ export function addInlinePlotsToOctave(source: string): string { return source; } -export function addInlinePlotsToMaxima(source: string): string { +export function expandMaximaPlot(source: string): string { const matches = source.matchAll(MAXIMA_PLOT_REGEX); for (const match of matches) { const tempFile = `${os.tmpdir()}/temp_${Date.now()}.png`.replace(/\\/g, "/"); diff --git a/src/transforms/TransformCode.ts b/src/transforms/TransformCode.ts index f3ae7ccc..d075181c 100644 --- a/src/transforms/TransformCode.ts +++ b/src/transforms/TransformCode.ts @@ -1,8 +1,8 @@ -import {insertColorTheme, insertNotePath, insertNoteTitle, insertVaultPath} from "./Magic"; -import {getVaultVariables} from "src/Vault"; -import {canonicalLanguages} from 'src/main'; -import type {App} from "obsidian"; -import type {LanguageId} from "src/main"; +import { expandColorTheme, expandNotePath, expandNoteTitle, expandVaultPath } from "./Magic"; +import { getVaultVariables } from "src/Vault"; +import { canonicalLanguages } from 'src/main'; +import type { App } from "obsidian"; +import type { LanguageId } from "src/main"; /** * Transform a language name, to enable working with multiple language aliases, for example "js" and "javascript". @@ -12,7 +12,7 @@ import type {LanguageId} from "src/main"; */ export function getLanguageAlias(language: string | undefined): LanguageId | undefined { if (language === undefined) return undefined; - switch(language) { + switch (language) { case "javascript": return "js"; case "typescript": return "ts"; case "csharp": return "cs"; @@ -39,10 +39,10 @@ export function transformMagicCommands(app: App, srcCode: string) { let ret = srcCode; const vars = getVaultVariables(app); if (vars) { - ret = insertVaultPath(ret, vars.vaultPath); - ret = insertNotePath(ret, vars.filePath); - ret = insertNoteTitle(ret, vars.fileName); - ret = insertColorTheme(ret, vars.theme); + ret = expandVaultPath(ret, vars.vaultPath); + ret = expandNotePath(ret, vars.filePath); + ret = expandNoteTitle(ret, vars.fileName); + ret = expandColorTheme(ret, vars.theme); } else { console.warn(`Could not load all Vault variables! ${vars}`) } From ec9533c337e75ff8d1f89a19019ca02d28d0cd11 Mon Sep 17 00:00:00 2001 From: Yetenol Date: Sun, 2 Feb 2025 19:00:34 +0100 Subject: [PATCH 28/34] remove redundant settings tab comments --- src/settings/SettingsTab.ts | 139 ++++++++---------------------------- 1 file changed, 30 insertions(+), 109 deletions(-) diff --git a/src/settings/SettingsTab.ts b/src/settings/SettingsTab.ts index 93839368..2b32a8eb 100644 --- a/src/settings/SettingsTab.ts +++ b/src/settings/SettingsTab.ts @@ -1,6 +1,6 @@ -import {App, PluginSettingTab, Setting} from "obsidian"; -import ExecuteCodePlugin, {canonicalLanguages, LanguageId} from "src/main"; -import {DISPLAY_NAMES} from "./languageDisplayName"; +import { App, PluginSettingTab, Setting } from "obsidian"; +import ExecuteCodePlugin, { canonicalLanguages, LanguageId } from "src/main"; +import { DISPLAY_NAMES } from "./languageDisplayName"; import makeCppSettings from "./per-lang/makeCppSettings"; import makeCSettings from "./per-lang/makeCSettings.js"; import makeCsSettings from "./per-lang/makeCsSettings"; @@ -28,7 +28,7 @@ import makeRacketSettings from "./per-lang/makeRacketSettings.js"; import makeShellSettings from "./per-lang/makeShellSettings"; import makeBatchSettings from "./per-lang/makeBatchSettings"; import makeTsSettings from "./per-lang/makeTsSettings"; -import {ExecutorSettings} from "./Settings"; +import { ExecutorSettings } from "./Settings"; import makeSQLSettings from "./per-lang/makeSQLSettings"; import makeOctaviaSettings from "./per-lang/makeOctaveSettings"; import makeMaximaSettings from "./per-lang/makeMaximaSettings"; @@ -61,14 +61,14 @@ export class SettingsTab extends PluginSettingTab { * Builds the html page that is showed in the settings. */ display() { - const {containerEl} = this; + const { containerEl } = this; containerEl.empty(); - containerEl.createEl('h2', {text: 'Settings for the Code Execution Plugin.'}); + containerEl.createEl('h2', { text: 'Settings for the Code Execution Plugin.' }); // ========== General ========== - containerEl.createEl('h3', {text: 'General Settings'}); + containerEl.createEl('h3', { text: 'General Settings' }); new Setting(containerEl) .setName('Timeout (in seconds)') .setDesc('The time after which a program gets shut down automatically. This is to prevent infinite loops. ') @@ -92,8 +92,8 @@ export class SettingsTab extends PluginSettingTab { this.plugin.settings.allowInput = value await this.plugin.saveSettings(); })); - - if(process.platform === "win32") { + + if (process.platform === "win32") { new Setting(containerEl) .setName('WSL Mode') .setDesc("Whether or not to run code in the Windows Subsystem for Linux. If you don't have WSL installed, don't turn this on!") @@ -106,17 +106,6 @@ export class SettingsTab extends PluginSettingTab { })); } - // new Setting(containerEl) - // .setName('Only Current Log') - // .setDesc("Whether or not show print log only in current code block.") - // .addToggle(text => text - // .setValue(this.plugin.settings.onlyCurrentBlock) - // .onChange(async (value) => { - // console.log('Only Show Current Block Log set to: ' + value); - // this.plugin.settings.onlyCurrentBlock = value - // await this.plugin.saveSettings(); - // })); - new Setting(containerEl) .setName('[Experimental] Persistent Output') .setDesc('If enabled, the output of the code block is written into the markdown file. This feature is ' + @@ -134,123 +123,55 @@ export class SettingsTab extends PluginSettingTab { containerEl.createEl("hr"); new Setting(containerEl) - .setName("Language-Specific Settings") - .setDesc("Pick a language to edit its language-specific settings") - .addDropdown((dropdown) => dropdown - .addOptions(Object.fromEntries( - canonicalLanguages.map(lang => [lang, DISPLAY_NAMES[lang]]) - )) - .setValue(this.plugin.settings.lastOpenLanguageTab || canonicalLanguages[0]) - .onChange(async (value: LanguageId)=> { - this.focusContainer(value); - this.plugin.settings.lastOpenLanguageTab = value; - await this.plugin.saveSettings(); - }) - ) - .settingEl.style.borderTop = "0"; - - - // ========== JavaScript / Node ========== - makeJsSettings(this, this.makeContainerFor("js")); - - // ========== TypeScript ========== - makeTsSettings(this, this.makeContainerFor("ts")); + .setName("Language-Specific Settings") + .setDesc("Pick a language to edit its language-specific settings") + .addDropdown((dropdown) => dropdown + .addOptions(Object.fromEntries( + canonicalLanguages.map(lang => [lang, DISPLAY_NAMES[lang]]) + )) + .setValue(this.plugin.settings.lastOpenLanguageTab || canonicalLanguages[0]) + .onChange(async (value: LanguageId) => { + this.focusContainer(value); + this.plugin.settings.lastOpenLanguageTab = value; + await this.plugin.saveSettings(); + }) + ) + .settingEl.style.borderTop = "0"; - // ========== Lean ========== + makeJsSettings(this, this.makeContainerFor("js")); // JavaScript / Node + makeTsSettings(this, this.makeContainerFor("ts")); // TypeScript makeLeanSettings(this, this.makeContainerFor("lean")); - - // ========== Lua ========== makeLuaSettings(this, this.makeContainerFor("lua")); - - // ========== Dart ========== makeDartSettings(this, this.makeContainerFor("dart")); - - // ========== CSharp ========== - makeCsSettings(this, this.makeContainerFor("cs")); - - // ========== Java ========== + makeCsSettings(this, this.makeContainerFor("cs")); // CSharp makeJavaSettings(this, this.makeContainerFor("java")); - - // ========== Python ========== makePythonSettings(this, this.makeContainerFor("python")); - - // ========== Golang ========= - makeGoSettings(this, this.makeContainerFor("go")); - - // ========== Rust =========== + makeGoSettings(this, this.makeContainerFor("go")); // Golang makeRustSettings(this, this.makeContainerFor("rust")); - - // ========== C++ =========== - makeCppSettings(this, this.makeContainerFor("cpp")); - - // ========== C =========== + makeCppSettings(this, this.makeContainerFor("cpp")); // C++ makeCSettings(this, this.makeContainerFor("c")); - - // ========== Batch ========== makeBatchSettings(this, this.makeContainerFor("batch")); - // ========== Shell ========== makeShellSettings(this, this.makeContainerFor("shell")); - - // ========== Powershell ========== makePowershellSettings(this, this.makeContainerFor("powershell")); - - // ========== Prolog ========== makePrologSettings(this, this.makeContainerFor("prolog")); - - // ========== Groovy ========== makeGroovySettings(this, this.makeContainerFor("groovy")); - - // ========== R ========== makeRSettings(this, this.makeContainerFor("r")); - - // ========== Kotlin ========== makeKotlinSettings(this, this.makeContainerFor("kotlin")); - - // ========== Mathematica ========== makeMathematicaSettings(this, this.makeContainerFor("mathematica")); - - // ========== Haskell =========== makeHaskellSettings(this, this.makeContainerFor("haskell")); - - // ========== Scala =========== makeScalaSettings(this, this.makeContainerFor("scala")); - - // ========== Swift =========== makeSwiftSettings(this, this.makeContainerFor("swift")); - - // ========== Racket =========== makeRacketSettings(this, this.makeContainerFor("racket")); - - // ========== FSharp =========== makeFSharpSettings(this, this.makeContainerFor("fsharp")); - - // ========== Ruby ============ makeRubySettings(this, this.makeContainerFor("ruby")); - - // ========== SQL ============ makeSQLSettings(this, this.makeContainerFor("sql")); - - // ========== Octavia ============ makeOctaviaSettings(this, this.makeContainerFor("octave")); - - // ========== Maxima ============ makeMaximaSettings(this, this.makeContainerFor("maxima")); - - // ========== Applescript ============ makeApplescriptSettings(this, this.makeContainerFor("applescript")); - - // ========== Zig ============ makeZigSettings(this, this.makeContainerFor("zig")); - - // ========== OCaml ============ makeOCamlSettings(this, this.makeContainerFor("ocaml")); - - // ========== Php ============ makePhpSettings(this, this.makeContainerFor("php")); - - // ========== LaTeX ============ makeLatexSettings(this, this.makeContainerFor("latex")); - this.focusContainer(this.plugin.settings.lastOpenLanguageTab || canonicalLanguages[0]); } @@ -266,10 +187,10 @@ export class SettingsTab extends PluginSettingTab { } private focusContainer(language: LanguageId) { - if(this.activeLanguageContainer) + if (this.activeLanguageContainer) this.activeLanguageContainer.style.display = "none"; - if(language in this.languageContainers) { + if (language in this.languageContainers) { this.activeLanguageContainer = this.languageContainers[language]; this.activeLanguageContainer.style.display = "block"; } From 2d55d001ab9c45a7bf031ff85b1833893bc8d010 Mon Sep 17 00:00:00 2001 From: Tim Wibiral Date: Mon, 24 Feb 2025 23:40:14 +0100 Subject: [PATCH 29/34] refactor: minor changes and fixes --- package-lock.json | 876 +++++++++++++++++++++++++++++++++++- package.json | 2 +- src/output/LatexInserter.ts | 6 +- 3 files changed, 872 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index fab237df..30f9b178 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "builtin-modules": "^3.3.0", "esbuild": "0.15.8", "obsidian": "latest", - "obsidian-typings": "^2.3.1-beta.1", + "obsidian-typings": "^2.34.0", "parallelshell": "^3.0.1", "tslib": "2.4.0", "typescript": "^4.8.3" @@ -302,6 +302,473 @@ "node": ">= 8" } }, + "node_modules/@pixi/accessibility": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/accessibility/-/accessibility-7.2.4.tgz", + "integrity": "sha512-EVjuqUqv9FeYFXCv0S0qj1hgCtbAMNBPCbOGEtiMogpM++/IySxBZvcOYg3rRgo9inwt2s4Bi7kUiqMPD8hItw==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4", + "@pixi/display": "7.2.4", + "@pixi/events": "7.2.4" + } + }, + "node_modules/@pixi/app": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/app/-/app-7.2.4.tgz", + "integrity": "sha512-eJ2jpu5P28ip07nLItw6sETXn45P4KR/leMJ6zPHRlhT1m8t5zTsWr3jK4Uj8LF2E+6KlPNzLQh5Alf/unn/aQ==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4", + "@pixi/display": "7.2.4" + } + }, + "node_modules/@pixi/assets": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/assets/-/assets-7.2.4.tgz", + "integrity": "sha512-7199re3wvMAlVqXLaCyAr8IkJSXqkeVAxcYyB2rBu4Id5m2hhlGX1dQsdMBiCXLwu6/LLVqDvJggSNVQBzL6ZQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/css-font-loading-module": "^0.0.7" + }, + "peerDependencies": { + "@pixi/core": "7.2.4", + "@pixi/utils": "7.2.4" + } + }, + "node_modules/@pixi/assets/node_modules/@types/css-font-loading-module": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz", + "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@pixi/color": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/color/-/color-7.2.4.tgz", + "integrity": "sha512-B/+9JRcXe2uE8wQfsueFRPZVayF2VEMRB7XGeRAsWCryOX19nmWhv0Nt3nOU2rvzI0niz9XgugJXsB6vVmDFSg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "colord": "^2.9.3" + } + }, + "node_modules/@pixi/compressed-textures": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/compressed-textures/-/compressed-textures-7.2.4.tgz", + "integrity": "sha512-atnWyw/ot/Wg69qhgskKiuTYCZx15IxV35sa0KyXMthyjyvDLCIvOn0nczM6wCBy9H96SjJbfgynVWhVrip6qw==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/assets": "7.2.4", + "@pixi/core": "7.2.4" + } + }, + "node_modules/@pixi/constants": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/constants/-/constants-7.2.4.tgz", + "integrity": "sha512-hKuHBWR6N4Q0Sf5MGF3/9l+POg/G5rqhueHfzofiuelnKg7aBs3BVjjZ+6hZbd6M++vOUmxYelEX/NEFBxrheA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@pixi/core": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/core/-/core-7.2.4.tgz", + "integrity": "sha512-0XtvrfxHlS2T+beBBSpo7GI8+QLyyTqMVQpNmPqB4woYxzrOEJ9JaUFBaBfCvycLeUkfVih1u6HAbtF+2d1EjQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@pixi/color": "7.2.4", + "@pixi/constants": "7.2.4", + "@pixi/extensions": "7.2.4", + "@pixi/math": "7.2.4", + "@pixi/runner": "7.2.4", + "@pixi/settings": "7.2.4", + "@pixi/ticker": "7.2.4", + "@pixi/utils": "7.2.4", + "@types/offscreencanvas": "^2019.6.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/pixijs" + } + }, + "node_modules/@pixi/display": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/display/-/display-7.2.4.tgz", + "integrity": "sha512-w5tqb8cWEO5qIDaO9GEqRvxYhL0iMk0Wsngw23bbLm1gLEQmrFkB2tpJlRAqd7H82C3DrDDeWvkrrxW6+m4apg==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4" + } + }, + "node_modules/@pixi/events": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/events/-/events-7.2.4.tgz", + "integrity": "sha512-/JtmoB98fzIU8giN9xvlRvmvOi6u4MaD2DnKNOMHkQ1MBraj3pmrXM9fZ0JbNzi+324GraAAY76QidgHjIYoYQ==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4", + "@pixi/display": "7.2.4" + } + }, + "node_modules/@pixi/extensions": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/extensions/-/extensions-7.2.4.tgz", + "integrity": "sha512-Mnqv9scbL1ARD3QFKfOWs2aSVJJfP1dL8g5UiqGImYO3rZbz/9QCzXOeMVIZ5n3iaRyKMNhFFr84/zUja2H7Dw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@pixi/extract": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/extract/-/extract-7.2.4.tgz", + "integrity": "sha512-wlXZg+J2L/1jQhRi5nZQP/cXshovhjksjss91eAKMvY5aGxNAQovCP4xotJ/XJjfTvPMpeRzHPFYzm3PrOPQ7g==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4" + } + }, + "node_modules/@pixi/filter-alpha": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/filter-alpha/-/filter-alpha-7.2.4.tgz", + "integrity": "sha512-UTUMSGyktUr+I9vmigqJo9iUhb0nwGyqTTME2xBWZvVGCnl5z+/wHxvIBBCe5pNZ66IM15pGXQ4cDcfqCuP2kA==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4" + } + }, + "node_modules/@pixi/filter-blur": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/filter-blur/-/filter-blur-7.2.4.tgz", + "integrity": "sha512-aLyXIoxy14bTansCPtbY8x7Sdn2OrrqkF/pcKiRXHJGGhi7wPacvB/NcmYJdnI/n2ExQ6V5Njuj/nfrsejVwcA==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4" + } + }, + "node_modules/@pixi/filter-color-matrix": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/filter-color-matrix/-/filter-color-matrix-7.2.4.tgz", + "integrity": "sha512-DFtayybYXoUh73eHUFRK5REbi1t3FZuVUnaQTj+euHKF9L7EaYc3Q9wctpx1WPRcwkqEX50M4SNFhxpA7Pxtaw==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4" + } + }, + "node_modules/@pixi/filter-displacement": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/filter-displacement/-/filter-displacement-7.2.4.tgz", + "integrity": "sha512-Simq3IBJKt7+Gvk4kK7OFkfoeYUMhNhIyATCdeT+Jkdkq5WV7pYnH5hqO0YW7eAHrgjV13yn6t4H/GC4+6LhEA==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4" + } + }, + "node_modules/@pixi/filter-fxaa": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/filter-fxaa/-/filter-fxaa-7.2.4.tgz", + "integrity": "sha512-qzKjdL+Ih18uGTJLg8tT/H+YCsTeGkw2uF7lyKnw/lxGLJQhLWIhM95M9qSNgxbXyW1vp7SbG81a9aAEz2HAhA==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4" + } + }, + "node_modules/@pixi/filter-noise": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/filter-noise/-/filter-noise-7.2.4.tgz", + "integrity": "sha512-QAU9Ybj2ZQrWM9ZEjTTC0iLnQcuyNoZNRinxSbg1G0yacpmsSb9wvV5ltIZ66+hfY+90+u2Nudt/v9g6pvOdGg==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4" + } + }, + "node_modules/@pixi/graphics": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/graphics/-/graphics-7.2.4.tgz", + "integrity": "sha512-3A2EumTjWJgXlDLOyuBrl9b6v1Za/E+/IjOGUIX843HH4NYaf1a2sfDfljx6r3oiDvy+VhuBFmgynRcV5IyA0Q==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4", + "@pixi/display": "7.2.4", + "@pixi/sprite": "7.2.4" + } + }, + "node_modules/@pixi/math": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/math/-/math-7.2.4.tgz", + "integrity": "sha512-LJB+mozyEPllxa0EssFZrKNfVwysfaBun4b2dJKQQInp0DafgbA0j7A+WVg0oe51KhFULTJMpDqbLn/ITFc41A==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@pixi/mesh": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/mesh/-/mesh-7.2.4.tgz", + "integrity": "sha512-wiALIqcRKib2BqeH9kOA5fOKWN352nqAspgbDa8gA7OyWzmNwqIedIlElixd0oLFOrIN5jOZAdzeKnoYQlt9Aw==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4", + "@pixi/display": "7.2.4" + } + }, + "node_modules/@pixi/mesh-extras": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/mesh-extras/-/mesh-extras-7.2.4.tgz", + "integrity": "sha512-Lxqq/1E2EmDgjZX8KzjhBy3VvITIQ00arr2ikyHYF1d0XtQTKEYpr8VKzhchqZ5/9DuyTDbDMYGhcxoNXQmZrQ==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4", + "@pixi/mesh": "7.2.4" + } + }, + "node_modules/@pixi/mixin-cache-as-bitmap": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/mixin-cache-as-bitmap/-/mixin-cache-as-bitmap-7.2.4.tgz", + "integrity": "sha512-95L/9nzfLHw6GoeqqRl/RjSloKvRt0xrc2inCmjMZvMsFUEtHN2F8IWd1k5vcv0S+83NCreFkJg6nJm1m5AZqg==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4", + "@pixi/display": "7.2.4", + "@pixi/sprite": "7.2.4" + } + }, + "node_modules/@pixi/mixin-get-child-by-name": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/mixin-get-child-by-name/-/mixin-get-child-by-name-7.2.4.tgz", + "integrity": "sha512-9g17KgSBEEhkinnKk4dqmxagzHOCPSTvGB6lOopBq4yyXmr/2WVv+QGjuzE0O+p80szQeBJjPBQxzrfBILaSRw==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/display": "7.2.4" + } + }, + "node_modules/@pixi/mixin-get-global-position": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/mixin-get-global-position/-/mixin-get-global-position-7.2.4.tgz", + "integrity": "sha512-UrAUF2BXCeWtFgR2m+er41Ky7zShT7r228cZkB6ZfYwMeThhwqG5mH68UeCyP6p68JMpT1gjI2DPfeSRY3ecnA==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4", + "@pixi/display": "7.2.4" + } + }, + "node_modules/@pixi/particle-container": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/particle-container/-/particle-container-7.2.4.tgz", + "integrity": "sha512-tpSzilZGFtAoi8XhzL0TecLPNRQAbY8nWV9XNGXJDw+nxXp18GCe8L6eEmnHLlAug67BRHl65DtrdvTknPX+4g==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4", + "@pixi/display": "7.2.4", + "@pixi/sprite": "7.2.4" + } + }, + "node_modules/@pixi/prepare": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/prepare/-/prepare-7.2.4.tgz", + "integrity": "sha512-Yff5Sh4kTLdKc5VkkM44LW9gpj7Izw8ns3P1TzWxqeGjzPZ3folr/tQujGL+Qw+8A9VESp+hX9MSIHyw+jpyrg==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4", + "@pixi/display": "7.2.4", + "@pixi/graphics": "7.2.4", + "@pixi/text": "7.2.4" + } + }, + "node_modules/@pixi/runner": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/runner/-/runner-7.2.4.tgz", + "integrity": "sha512-YtyqPk1LA+0guEFKSFx6t/YSvbEQwajFwi4Ft8iDhioa6VK2MmTir1GjWwy7JQYLcDmYSAcQjnmFtVTZohyYSw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@pixi/settings": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/settings/-/settings-7.2.4.tgz", + "integrity": "sha512-ZPKRar9EwibijGmH8EViu4Greq1I/O7V/xQx2rNqN23XA7g09Qo6yfaeQpufu5xl8+/lZrjuHtQSnuY7OgG1CA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@pixi/constants": "7.2.4", + "@types/css-font-loading-module": "^0.0.7", + "ismobilejs": "^1.1.0" + } + }, + "node_modules/@pixi/settings/node_modules/@types/css-font-loading-module": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz", + "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@pixi/sprite": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/sprite/-/sprite-7.2.4.tgz", + "integrity": "sha512-DhR1B+/d0eXpxHIesJMXcVPrKFwQ+zRA1LvEIFfzewqfaRN3X6PMIuoKX8SIb6tl+Hq8Ba9Pe28zI7d2rmRzrA==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4", + "@pixi/display": "7.2.4" + } + }, + "node_modules/@pixi/sprite-animated": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/sprite-animated/-/sprite-animated-7.2.4.tgz", + "integrity": "sha512-9eRriPSC0QVS7U9zQlrG3uEI5+h3fi+mqofXy+yjk1sGCmXSIJME5p2wg2mzxoJk3qkSMagQA9QHtL26Fti8Iw==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4", + "@pixi/sprite": "7.2.4" + } + }, + "node_modules/@pixi/sprite-tiling": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/sprite-tiling/-/sprite-tiling-7.2.4.tgz", + "integrity": "sha512-nGfxQoACRx49dUN0oW1vFm3141M+7gkAbzoNJym2Pljd2dpLME9fb5E6Lyahu0yWMaPRhhGorn6z9VIGmTF3Jw==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4", + "@pixi/display": "7.2.4", + "@pixi/sprite": "7.2.4" + } + }, + "node_modules/@pixi/spritesheet": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/spritesheet/-/spritesheet-7.2.4.tgz", + "integrity": "sha512-LNmlavyiMQeCF0U4S+yhzxUYmPmat6EpLjLnkGukQTZV5CZkxDCVgXM9uKoRF2DvNydj4yuwZ6+JjK8QssHI8Q==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/assets": "7.2.4", + "@pixi/core": "7.2.4" + } + }, + "node_modules/@pixi/text": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/text/-/text-7.2.4.tgz", + "integrity": "sha512-DGu7ktpe+zHhqR2sG9NsJt4mgvSObv5EqXTtUxD4Z0li1gmqF7uktpLyn5I6vSg1TTEL4TECClRDClVDGiykWw==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4", + "@pixi/sprite": "7.2.4" + } + }, + "node_modules/@pixi/text-bitmap": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/text-bitmap/-/text-bitmap-7.2.4.tgz", + "integrity": "sha512-3u2CP4VN+muCaq/jtj7gn0hb3DET/X2S04zTBcgc2WVGufJc62yz+UDzS9jC+ellotVdt9c8U74++vpz3zJGfw==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/assets": "7.2.4", + "@pixi/core": "7.2.4", + "@pixi/display": "7.2.4", + "@pixi/mesh": "7.2.4", + "@pixi/text": "7.2.4" + } + }, + "node_modules/@pixi/text-html": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/text-html/-/text-html-7.2.4.tgz", + "integrity": "sha512-0NfLAE/w51ZtatxVqLvDS62iO0VLKsSdctqTAVv4Zlgdk9TKJmX1WUucHJboTvbm2SbDjNDGfZ6qXM5nAslIDQ==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@pixi/core": "7.2.4", + "@pixi/display": "7.2.4", + "@pixi/sprite": "7.2.4", + "@pixi/text": "7.2.4" + } + }, + "node_modules/@pixi/ticker": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/ticker/-/ticker-7.2.4.tgz", + "integrity": "sha512-hQQHIHvGeFsP4GNezZqjzuhUgNQEVgCH9+qU05UX1Mc5UHC9l6OJnY4VTVhhcHxZjA6RnyaY+1zBxCnoXuazpg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@pixi/extensions": "7.2.4", + "@pixi/settings": "7.2.4", + "@pixi/utils": "7.2.4" + } + }, + "node_modules/@pixi/utils": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@pixi/utils/-/utils-7.2.4.tgz", + "integrity": "sha512-VUGQHBOINIS4ePzoqafwxaGPVRTa3oM/mEutIIHbNGI3b+QvSO+1Dnk40M0zcH6Bo+MxQZbOZK5X/wO9oU5+LQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@pixi/color": "7.2.4", + "@pixi/constants": "7.2.4", + "@pixi/settings": "7.2.4", + "@types/earcut": "^2.1.0", + "earcut": "^2.2.4", + "eventemitter3": "^4.0.0", + "url": "^0.11.0" + } + }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -354,6 +821,22 @@ "@types/tern": "*" } }, + "node_modules/@types/css-font-loading-module": { + "version": "0.0.14", + "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.14.tgz", + "integrity": "sha512-+EwJ/RW2vPqbYn0JXRHy593huPCtgmLF/kg57iLK9KUn6neTqGGOTZ0CbssP8Uou/gqT/5XmWKQ8A7ve7xNV6A==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/earcut": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.4.tgz", + "integrity": "sha512-qp3m9PPz4gULB9MhjGID7wpo3gJ4bTGXm7ltNDsmOvsPduTeHp8wSW9YckBj3mljeOh4F0m2z/0JKAALRKbmLQ==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -397,6 +880,14 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/offscreencanvas": { + "version": "2019.7.3", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", + "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/responselike": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", @@ -836,6 +1327,39 @@ "node": ">=8" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -901,6 +1425,14 @@ "license": "MIT", "peer": true }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1077,6 +1609,30 @@ "node": ">=6.0.0" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/earcut": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", + "dev": true, + "license": "ISC", + "peer": true + }, "node_modules/electron": { "version": "33.2.1", "resolved": "https://registry.npmjs.org/electron/-/electron-33.2.1.tgz", @@ -1144,7 +1700,6 @@ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, "license": "MIT", - "optional": true, "peer": true, "engines": { "node": ">= 0.4" @@ -1156,12 +1711,25 @@ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, "license": "MIT", - "optional": true, "peer": true, "engines": { "node": ">= 0.4" } }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -1782,6 +2350,14 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -1972,12 +2548,64 @@ "license": "ISC", "peer": true }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/g": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/g/-/g-2.0.1.tgz", "integrity": "sha512-Fi6Ng5fZ/ANLQ15H11hCe+09sgUoNvDEBevVgx3KoYOhsH5iLNPn54hx0jPZ+3oSWr+xajnp2Qau9VmPsc7hTA==", "license": "MIT" }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -2115,7 +2743,6 @@ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, "license": "MIT", - "optional": true, "peer": true, "engines": { "node": ">= 0.4" @@ -2192,6 +2819,34 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", @@ -2352,6 +3007,14 @@ "license": "ISC", "peer": true }, + "node_modules/ismobilejs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ismobilejs/-/ismobilejs-1.1.1.tgz", + "integrity": "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -2499,6 +3162,17 @@ "node": ">=10" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2593,6 +3267,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -2621,20 +3309,28 @@ } }, "node_modules/obsidian-typings": { - "version": "2.3.1-beta.1", - "resolved": "https://registry.npmjs.org/obsidian-typings/-/obsidian-typings-2.3.1-beta.1.tgz", - "integrity": "sha512-0S82fAa20gKeaxoAR/WgwarazIu8Uidjm7EggWlmtN46gPGI0nzsKIzQKDt8YonDuW202W554QXRbqbCB90wDg==", + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/obsidian-typings/-/obsidian-typings-2.34.0.tgz", + "integrity": "sha512-y4/8D6dCrCgoeAZ4n7JJvsYXmJWYDoUJXLOpSt9GYu1+XoiHXyfvjpxC/ITg3J9XOqv8tyeb2N4o/wdsFuMVnw==", "dev": true, + "hasInstallScript": true, "license": "MIT", "peerDependencies": { "@capacitor/core": "^6.1.2", "@codemirror/search": "^6.5.6", "@codemirror/state": "^6.4.1", + "@pixi/color": "7.2.4", + "@pixi/events": "7.2.4", + "@pixi/settings": "7.2.4", + "@types/css-font-loading-module": "^0.0.14", "@types/node": ">=14.0.0", "@types/turndown": "^5.0.5", "electron": ">=1.6.10", "i18next": "^23.15.1", - "obsidian": "^1.7.2" + "moment": "^2.30.1", + "obsidian": "^1.7.2", + "pixi.js": "7.2.4", + "style-mod": "4.1.2" } }, "node_modules/obsidian/node_modules/moment": { @@ -2816,6 +3512,50 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pixi.js": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-7.2.4.tgz", + "integrity": "sha512-nBH60meoLnHxoMFz17HoMxXS4uJpG5jwIdL+Gx2S11TzWgP3iKF+/WLOTrkSdyuQoQSdIBxVqpnYii0Wiox15A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@pixi/accessibility": "7.2.4", + "@pixi/app": "7.2.4", + "@pixi/assets": "7.2.4", + "@pixi/compressed-textures": "7.2.4", + "@pixi/core": "7.2.4", + "@pixi/display": "7.2.4", + "@pixi/events": "7.2.4", + "@pixi/extensions": "7.2.4", + "@pixi/extract": "7.2.4", + "@pixi/filter-alpha": "7.2.4", + "@pixi/filter-blur": "7.2.4", + "@pixi/filter-color-matrix": "7.2.4", + "@pixi/filter-displacement": "7.2.4", + "@pixi/filter-fxaa": "7.2.4", + "@pixi/filter-noise": "7.2.4", + "@pixi/graphics": "7.2.4", + "@pixi/mesh": "7.2.4", + "@pixi/mesh-extras": "7.2.4", + "@pixi/mixin-cache-as-bitmap": "7.2.4", + "@pixi/mixin-get-child-by-name": "7.2.4", + "@pixi/mixin-get-global-position": "7.2.4", + "@pixi/particle-container": "7.2.4", + "@pixi/prepare": "7.2.4", + "@pixi/sprite": "7.2.4", + "@pixi/sprite-animated": "7.2.4", + "@pixi/sprite-tiling": "7.2.4", + "@pixi/spritesheet": "7.2.4", + "@pixi/text": "7.2.4", + "@pixi/text-bitmap": "7.2.4", + "@pixi/text-html": "7.2.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/pixijs" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -2861,6 +3601,23 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3099,6 +3856,86 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -3313,6 +4150,29 @@ "punycode": "^2.1.0" } }, + "node_modules/url": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", + "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.12.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", diff --git a/package.json b/package.json index 6a37f4e6..28927691 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "builtin-modules": "^3.3.0", "esbuild": "0.15.8", "obsidian": "latest", - "obsidian-typings": "^2.3.1-beta.1", + "obsidian-typings": "^2.34.0", "parallelshell": "^3.0.1", "tslib": "2.4.0", "typescript": "^4.8.3" diff --git a/src/output/LatexInserter.ts b/src/output/LatexInserter.ts index 1604dbdb..bc60935c 100644 --- a/src/output/LatexInserter.ts +++ b/src/output/LatexInserter.ts @@ -69,7 +69,7 @@ async function insertEmbedding(pastePosition: 'above' | 'below', doReplace: bool const identifier: RegExp = r.parse(identifierSrc); if (!identifier) return; - const codeBlocks: RegExpExecArray[] = findMatchingCodeBlocks(content, /(la)?tex/, identifier, figure.link(), doReplace); + const codeBlocks: RegExpMatchArray[] = findMatchingCodeBlocks(content, /(la)?tex/, identifier, figure.link(), doReplace); if (codeBlocks.length === 0) return false; codeBlocks.forEach(async (block: RegExpExecArray) => { @@ -83,7 +83,7 @@ async function insertEmbedding(pastePosition: 'above' | 'below', doReplace: bool } /** Locates LaTeX code blocks containing the specified figure identifier and their surrounding embeddings */ -function findMatchingCodeBlocks(content: string, language: RegExp, identifier: RegExp, link: string, doReplace?: boolean): RegExpExecArray[] { +function findMatchingCodeBlocks(content: string, language: RegExp, identifier: RegExp, link: string, doReplace?: boolean): RegExpMatchArray[] { const alreadyLinked: RegExp = r.group(r.escape(link)); const codeblock: RegExp = r.concat( /```(run-)?/, r.group(language), /[\s\n]/, @@ -104,7 +104,7 @@ function findMatchingCodeBlocks(content: string, language: RegExp, identifier: R (doReplace) ? r.optional(next) : null, ), 'g'); - const matches: RegExpExecArray[] = Array.from(content.matchAll(blocksWithEmbeds)); + const matches: RegExpMatchArray[] = Array.from(content.matchAll(blocksWithEmbeds)); console.debug(`Searching markdown for`, blocksWithEmbeds, `resulted in `, matches.length, `codeblock(s)`, matches.map(match => match.groups)); return matches; } From 2d18b29a756c7b625f8299aad3c7e297dea7ee9b Mon Sep 17 00:00:00 2001 From: Tim Wibiral Date: Mon, 24 Feb 2025 23:53:50 +0100 Subject: [PATCH 30/34] refactor: Update the release note modal --- src/ReleaseNoteModal.ts | 30 ++++++++++++++++++------------ src/main.ts | 4 ++-- src/settings/Settings.ts | 7 +++---- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/ReleaseNoteModal.ts b/src/ReleaseNoteModal.ts index 45cd23ee..8c94a699 100644 --- a/src/ReleaseNoteModal.ts +++ b/src/ReleaseNoteModal.ts @@ -9,18 +9,24 @@ export class ReleaseNoteModel extends Modal { } onOpen() { - let text = '# Release Note: Execute Code Plugin v2.0.0\n\n'+ - 'We are happy to announce the release of version 2.0.0. This release brings a special change: You can now make ' + - 'the output of your code blocks persistent.' + - 'If enabled, the output of your code blocks will be saved in the markdown file and will also be exported to PDF.' + - '\n\n\n' + - 'You can enable this in the settings. Be aware that this feature is still experimental and might not work as expected. ' + - 'Check the [github page](https://github.com/twibiral/obsidian-execute-code) for more information.' + - '\n\n\n' + - 'Thank you for using the Execute Code Plugin! ' + - '[Here you can find a detailed change log.](https://github.com/twibiral/obsidian-execute-code/blob/master/CHANGELOG.md)' + - '\n\n\n' + - 'If you enjoy using the plugin, consider supporting the development via [PayPal](https://www.paypal.com/paypalme/timwibiral) or [Buy Me a Coffee](https://www.buymeacoffee.com/twibiral).' + let text = '# Release Note: Execute Code Plugin v2.1.0\n\n'+ + 'Thank you for updating to version 2.1.0! This update includes some bug fixes and improvements and brings two new features:\n' + + '- [LaTeX Support](https://github.com/twibiral/obsidian-execute-code/pull/400): You can now render LaTeX code in your code blocks. Just add the language tag `latex` to your code block.\n' + + '- New Magic command: [@content](https://github.com/twibiral/obsidian-execute-code/pull/390) allows you to load the file content of the open note into your code block.\n' + + + '\n\n\n' + + '[Here you can find a detailed change log.](https://github.com/twibiral/obsidian-execute-code/blob/master/CHANGELOG.md)' + + '\n\n\n' + + 'If you enjoy using the plugin, consider supporting the development via [PayPal](https://www.paypal.com/paypalme/timwibiral) or [Buy Me a Coffee](https://www.buymeacoffee.com/twibiral).' + + + '\n\n\n---\n\n\n[OLD] Release Notes v2.0.0\n\n' + + 'We are happy to announce the release of version 2.0.0. This release brings a special change: You can now make ' + + 'the output of your code blocks persistent.' + + 'If enabled, the output of your code blocks will be saved in the markdown file and will also be exported to PDF.' + + '\n\n\n' + + 'You can enable this in the settings. Be aware that this feature is still experimental and might not work as expected. ' + + 'Check the [github page](https://github.com/twibiral/obsidian-execute-code) for more information.'; + this.component.load(); MarkdownRenderer.render(this.app, text, this.contentEl, this.app.workspace.getActiveFile().path, this.component); diff --git a/src/main.ts b/src/main.ts index 49cf6b83..6dcbb63d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -76,13 +76,13 @@ export default class ExecuteCodePlugin extends Plugin { callback: () => runAllCodeBlocks(this.app.workspace) }) - if (!this.settings.releaseNote2_0_0wasShowed) { + if (!this.settings.releaseNote2_1_0wasShowed) { this.app.workspace.onLayoutReady(() => { new ReleaseNoteModel(this.app).open(); }) // Set to true to prevent the release note from showing again - this.settings.releaseNote2_0_0wasShowed = true; + this.settings.releaseNote2_1_0wasShowed = true; this.saveSettings(); } diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 16b1bc68..d67e3798 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -4,9 +4,8 @@ import { LanguageId } from "src/main"; * Interface that contains all the settings for the extension. */ export interface ExecutorSettings { - lastOpenLanguageTab: LanguageId | undefined - - releaseNote2_0_0wasShowed: boolean; + lastOpenLanguageTab: LanguageId | undefined; + releaseNote2_1_0wasShowed: boolean; persistentOuput: boolean; timeout: number; allowInput: boolean; @@ -220,7 +219,7 @@ export interface ExecutorSettings { export const DEFAULT_SETTINGS: ExecutorSettings = { lastOpenLanguageTab: undefined, - releaseNote2_0_0wasShowed: false, + releaseNote2_1_0wasShowed: false, persistentOuput: false, timeout: 10000, allowInput: true, From ed1b9883909b91cc2e2ea3de4193c503a23939ba Mon Sep 17 00:00:00 2001 From: Tim Wibiral Date: Mon, 24 Feb 2025 23:56:04 +0100 Subject: [PATCH 31/34] docs: update CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2349476..42028ad2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - LaTeX: Generated graphics are saved as attachment to your vault - LaTeX: Option to crop PDF to content to remove excess whitespace - LaTeX: Option to adopt Obsidian's font settings for PDF generation +- New magic command @content (Thanks to @ChiIIBiII) + +### Changed +- Fix typo in settings (Thanks to @Judro) +- Fix F# support (Thanks to @SasaCetkovic) ## [2.0.0] ### Added From 0ebeb538af21a19fbf46e13d1b52d277571e95ff Mon Sep 17 00:00:00 2001 From: Tim Wibiral Date: Tue, 25 Feb 2025 00:20:18 +0100 Subject: [PATCH 32/34] docs: improve readme --- CHANGELOG.md | 1 + README.md | 179 +++++++++++++++++++++++++++----------------------- manifest.json | 2 +- 3 files changed, 97 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42028ad2..6fdd0d64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Changed - Fix typo in settings (Thanks to @Judro) - Fix F# support (Thanks to @SasaCetkovic) +- Update the README.md to improve the overview over supported languages. ## [2.0.0] ### Added diff --git a/README.md b/README.md index f991788f..0d5609e7 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, CPP, Dart, Golang, Groovy, Kotlin, Java, JavaScript, TypeScript, Lean, Lua, CSharp, Prolog, Rust, Python, R, Ruby, Wolfram Mathematica, Haskell, Scala, Racket, F#, Batch, Shell & Powershell, Octave, Maxima, Zig and OCaml. +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. 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, Rust, 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, 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-). @@ -54,7 +54,7 @@ In blogs: Are you featuring this plugin in your content? Let me know and I will add it here. -## Supported programming, typesetting languages 💻 +## Supported programming languages 💻
JavaScript @@ -85,28 +85,6 @@ console.log(message); ```
-
-CSharp - -- Requirements: install dotnet core sdk and run in command line `dotnet tool install -g dotnet-script`, then config dotnet-script fullpath. - -```cs -Console.WriteLine("Hello, World!"); -``` -
- -
-Dart - -- Requirements: dart sdk is installed and the correct path is set in the settings. - -```dart -void main() { - print("Hello World"); -} -``` -
-
Python @@ -121,7 +99,7 @@ if __name__ == "__main__": ``` - By default, Python runs in Notebook Mode. You can turn this off in the settings. -- Plots with matplotlib/seaborn are embedded in the note by default. You can turn this off in the settings. +- Plots with matplotlib/seaborn are embedded in the note by default. You can turn this off in the settings. Note that plots are only embedded when `plt.show()` is called. ```python import seaborn as sns @@ -159,6 +137,61 @@ plot(x, y, type="l") ```
+
+C++ + +- Requirements: [Cling](https://github.com/root-project/cling) is installed and correct path is set in the settings. +- Code will be executed line-by-line without needing a main function. + +```cpp +#include +#include + +using namespace std; + +void hello(string name) { + cout << "Hello " << name << "!\n"; +} + +hello("Alice); +``` + +- Main functions can be used as an entrypoint by toggling the option in settings. + +```cpp +#include + +void main() { + std::cout << "Hello, World!" << std::endl; +} +``` +
+ +
+C + +- Requirements: [Cling](https://github.com/root-project/cling) is installed and correct path is set in the settings. +- Code will be executed line-by-line without needing a main function. + +```c +#include + +printf("Hello, World!"); +``` + +- Main functions can be used as an entrypoint by toggling the option in settings. + +```c +#include + +int main() { + printf("Hello, World!"); + return 0; +} +``` + +
+
Java @@ -173,6 +206,15 @@ public class HelloWorld { ```
+
+SQL +- Requirements: Have some SQL database installed and the correct path is set in the settings. The default is set to PostgreSQL (`psql`). +- Make sure you adapt the settings to your database (`-d mydatabase -U myuser -f`)The default is set to . +```sql +SELECT * FROM table_name; +``` +
+
LaTeX @@ -265,83 +307,56 @@ Explore [more LaTeX examples](https://antonpusch.de/latex), consult [package doc
-
-Lua - -- Requirements: install lua and config lua path. -```lua -print('Hello, World!') -``` -
-Lean - -- Requirements: install lean and config lean path. +More supported languages... +
+CSharp -```lean -def main : IO Unit := - IO.println s!"Hello, World!" +- Requirements: install dotnet core sdk and run in command line `dotnet tool install -g dotnet-script`, then config dotnet-script fullpath. -#eval main -``` +```cs +Console.WriteLine("Hello, World!"); +```
-C++ - -- Requirements: [Cling](https://github.com/root-project/cling) is installed and correct path is set in the settings. -- Code will be executed line-by-line without needing a main function. - -```cpp -#include -#include +Dart -using namespace std; +- Requirements: dart sdk is installed and the correct path is set in the settings. -void hello(string name) { - cout << "Hello " << name << "!\n"; +```dart +void main() { + print("Hello World"); } - -hello("Alice); ``` +
-- Main functions can be used as an entrypoint by toggling the option in settings. +
+Lua -```cpp -#include +- Requirements: install lua and config lua path. -void main() { - std::cout << "Hello, World!" << std::endl; -} +```lua +print('Hello, World!') ```
-C +Lean -- Requirements: [Cling](https://github.com/root-project/cling) is installed and correct path is set in the settings. -- Code will be executed line-by-line without needing a main function. +- Requirements: install lean and config lean path. -```c -#include +```lean +def main : IO Unit := + IO.println s!"Hello, World!" -printf("Hello, World!"); +#eval main ``` +
-- Main functions can be used as an entrypoint by toggling the option in settings. - -```c -#include - -int main() { - printf("Hello, World!"); - return 0; -} -``` -
Shell @@ -586,16 +601,11 @@ print_endline "Hello, OCaml!" print("Hello, world!") ```
- +
Squiggle: For Squiggle support take a look at the [Obsidian Squiggle plugin](https://github.com/jqhoogland/obsidian-squiggle) by @jqhoogland. -Support for the following is planned: - -- Matlab -- Julia Lang -Open for suggestions. ## Magic Commands 🪄 @@ -612,6 +622,7 @@ The following magic commands are supported: - `@show(ImagePath, Width, Height)`: Displays an image at the given path in the note. - `@show(ImagePath, Width, Height, Alignment[center|left|right])`: Displays an image at the given path in the note. - `@html(HtmlSource)`: Displays HTML in the note +- `@content`: Inserts the html content of the note in the code block. ([Here you find a nice example for the usage of this command.](https://github.com/twibiral/obsidian-execute-code/pull/390)) (`@show(...)` and `@html(...)` are only supported for JavaScript and Python yet.) (The old commands `@note` and `@vault` are still supported, but may be removed in the future.) diff --git a/manifest.json b/manifest.json index d92c75c2..b7d4194e 100644 --- a/manifest.json +++ b/manifest.json @@ -3,7 +3,7 @@ "name": "Execute Code", "version": "2.0.0", "minAppVersion": "1.7.2", - "description": "Allows to execute code snippets within a note. Supported programming languages: C, CPP, Dart, Golang, Groovy, Kotlin, Java, JavaScript, TypeScript, Lean, Lua, CSharp, Prolog, Rust, Python, R, Ruby, Wolfram Mathematica, Haskell, Scala, Racket, F#, Batch, Shell & Powershell.", + "description": "Allows you to execute code snippets within a note. Support C, C++, Python, R, JavaScript, TypeScript, LaTeX, SQL, and many more.", "author": "twibiral", "authorUrl": "https://www.github.com/twibiral", "isDesktopOnly": true From 68cba60040ceebb85ebfc8590a6a0a284a5e73bf Mon Sep 17 00:00:00 2001 From: Tim Wibiral Date: Tue, 25 Feb 2025 00:26:16 +0100 Subject: [PATCH 33/34] docs: improve readme --- README.md | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 0d5609e7..ab309ce2 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ -This plugin allows you to execute code snippets in code blocks in your notes. The plugin adds a 'run' button for code blocks in supported languages. Clicking them results in the code of the block being executed locally. After the execution the result of the execution is showed. An interactive input element is created when your code snippets reads expects user input. +This plugin allows you to execute code snippets in code blocks in your notes. The plugin adds a 'run' button for code blocks in supported languages. Clicking them results in the code of the block being executed locally. After the execution the result of the execution is shown. An interactive input element is created when your code snippets reads expects user input. The result is shown only after the execution is finished. It is not possible to enter text on the command line into the executed program now. @@ -33,7 +33,7 @@ Take a look at the [changelog](CHANGELOG.md) to see what has changed in recent v
-If you like this plugin and use it a lot, please consider supporting me in continuing the development of this plugin. You can also sponsor a new feature or language integration directly, if you want to speed-up the development for a specific feature you need. +If you like this plugin and use it a lot, please consider supporting me in continuing the development of this plugin. You can also sponsor a new feature or language integration directly, if you want to speed up the development for a specific feature you need. [![GitHub Sponsors](https://img.shields.io/badge/sponsor-30363D?style=for-the-badge&logo=GitHub-Sponsors&logoColor=white)](https://github.com/sponsors/twibiral) [![Buy me a coffee](https://img.shields.io/badge/Buy_Me_A_Coffee-FFDD00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black)](https://www.buymeacoffee.com/timwibiral) @@ -76,8 +76,8 @@ hello("Bob")
TypeScript -- Requirements: Node.js installed then run in command line `npm install typescript -g` and `npm install ts-node -g`. (`-g` means global install) -- Problems: If you use your global node.js installation and it doesn't work try to set your `ts-node` path in the settings to `npx ts-node` instead of `ts-node`. +- Requirements: Node.js installed then run in command line `npm install typescript -g` and `npm install ts-node -g`. (`-g` means global installation) +- Problems: If you use your global node.js installation, and it doesn't work try to set your `ts-node` path in the settings to `npx ts-node` instead of `ts-node`. ```ts let message: string = 'Hello, World!'; @@ -373,7 +373,7 @@ ls -la Powershell - Requirements: Used to execute shell commands on Windows. Default is Powershell but can be set to your preferred shell in the settings. -- On MacOS: You probably need to change the command to use from `powershell` to `pwsh` in the plugin settings. Make sure you set the right path. +- On macOS: You probably need to change the command to use from `powershell` to `pwsh` in the plugin settings. Make sure you set the right path. ```powershell echo "Hello World!" @@ -713,9 +713,9 @@ This code block is added before each python block you define below in the note a `post` blocks work the same way, but the code in post blocks is executed _after_ your other code blocks. -Pre/Post blocks will only apply to code blocks defined below them, and will only apply to code blocks from the same language. +Pre-/post-blocks will only apply to code blocks defined below them, and will only apply to code blocks from the same language. -You can also have a pre and post block at the same time by specifying `{pre, post}` +You can also have a pre- and post-block at the same time by specifying `{pre, post}` Note, the `pre`/`post` arguments are special in that you don't need to explicitly state a key/value pair, however you can do so if you wish: @@ -723,7 +723,7 @@ Note, the `pre`/`post` arguments are special in that you don't need to explicitl ### Labelled Code Blocks -You can label specific code blocks with the `label='string'` argument, then import them explicitly in other blocks with the `import='string'` or `import=['string1', 'string2', ...]` argument so they aren't automatically imported as with pre / post blocks: +You can label specific code blocks with the `label='string'` argument, then import them explicitly in other blocks with the `import='string'` or `import=['string1', 'string2', ...]` argument so they aren't automatically imported as with pre-/post-blocks: ````` ```python {label='block 1'} @@ -791,7 +791,7 @@ preview mode. To enable this feature, you have to enable the setting `Persistent Output` in the plugin settings. We recommend reopening open notes that contain code blocks after enabling this feature. -This feature is still experimental and may not work as expected in all cases. +⚠ This feature is still experimental and may not work as expected in all cases! We recommend that you disable this feature if you encounter any problems. @@ -848,19 +848,14 @@ Do not execute code from sources you don't know or code you don't understand. Ex - On Linux, Snap/Flatpak/AppImage installations of Obsidian run in an isolated environment. As such, they will not have access to any of your installed programs. If you are on Linux, make sure to install the `.deb` version of Obsidian. If your distro isn't compatible with `.deb` files, you may see issues. - Missing when `run` button after switching the theme: Try to close and reopen your notes and wait for a few minutes. It seems like obsidian doesn't call the postprocessors after the theme switch. -- Pre- / Post-blocks may not be executed if the file contains duplicate code blocks. +- Pre-/Post-blocks may not be executed if the file contains duplicate code blocks. - In Python, Embed Plots may not be off while Notebook Mode is on -## Future Work 📑 - -- Error warning when the execution fails (e.g. when python isn't installed) -- Test if this plugin works in combination with dataview. - ## Contribution 🤝 -All contributions are welcome. Just create a merge request or email me: tim.wibiral(at)uni-ulm.de +All contributions are welcome. Just create a merge request or email me: contact(at)tim-wibiral.de -The bullet points in Future Work are a good starting point if you want to help. +The [open issues](https://github.com/twibiral/obsidian-execute-code/issues) are a good starting point to find something to work on. Some are marked as ["good first issue"](https://github.com/twibiral/obsidian-execute-code/labels/good%20first%20issue) and are easier to solve. ## Contributors ♥ From e3a1a011a83f49e69709b7cd6286c5e5743270cb Mon Sep 17 00:00:00 2001 From: Tim Wibiral Date: Tue, 25 Feb 2025 00:32:45 +0100 Subject: [PATCH 34/34] chore: bump version --- CHANGELOG.md | 2 +- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 4 ++-- versions.json | 3 ++- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fdd0d64..57fb89f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ 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/), -## [Unreleased] +## [2.1.0] ### Added - Support for LaTeX with image conversion to PDF, SVG, PNG (Thanks to @yetenol) - LaTeX: Generated graphics are saved as attachment to your vault diff --git a/manifest.json b/manifest.json index b7d4194e..c9c5978b 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "execute-code", "name": "Execute Code", - "version": "2.0.0", + "version": "2.1.0", "minAppVersion": "1.7.2", "description": "Allows you to execute code snippets within a note. Support C, C++, Python, R, JavaScript, TypeScript, LaTeX, SQL, and many more.", "author": "twibiral", diff --git a/package-lock.json b/package-lock.json index 30f9b178..82981a71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "execute-code", - "version": "2.0.0", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, "packages": { @@ -4244,4 +4244,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 28927691..cb7c6118 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "execute-code", - "version": "2.0.0", - "description": "This is a sample plugin for Obsidian (https://obsidian.md)", + "version": "2.1.0", + "description": "Allows you to execute code snippets within a note. Support C, C++, Python, R, JavaScript, TypeScript, LaTeX, SQL, and many more.", "main": "src/main.js", "scripts": { "dev": "node esbuild.config.mjs", diff --git a/versions.json b/versions.json index 493b49b6..b249a948 100644 --- a/versions.json +++ b/versions.json @@ -48,5 +48,6 @@ "1.11.0": "1.2.8", "1.11.1": "1.2.8", "1.12.0": "1.2.8", - "2.0.0": "1.7.2" + "2.0.0": "1.7.2", + "2.1.0": "1.7.2" } \ No newline at end of file