", "exec"),
30 | ${globalsName}
31 | )
32 | except Exception as e:
33 | ${printName} (e, file=sys.stderr)
34 | finally:
35 | ${printName} ("${finishSigil}", end="")
36 |
37 | `;
38 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { App, Component, MarkdownRenderer, MarkdownView, Plugin, } from 'obsidian';
2 |
3 | import type { ExecutorSettings } from "./settings/Settings";
4 | import { DEFAULT_SETTINGS } from "./settings/Settings";
5 | import { SettingsTab } from "./settings/SettingsTab";
6 | import { applyLatexBodyClasses } from "./transforms/LatexTransformer"
7 |
8 | import ExecutorContainer from './ExecutorContainer';
9 | import ExecutorManagerView, {
10 | EXECUTOR_MANAGER_OPEN_VIEW_COMMAND_ID,
11 | EXECUTOR_MANAGER_VIEW_ID
12 | } from './ExecutorManagerView';
13 |
14 | import runAllCodeBlocks from './runAllCodeBlocks';
15 | import { ReleaseNoteModel } from "./ReleaseNoteModal";
16 | import * as runButton from './RunButton';
17 |
18 | export const languageAliases = ["javascript", "typescript", "bash", "csharp", "wolfram", "nb", "wl", "hs", "py", "tex"] as const;
19 | export const canonicalLanguages = ["js", "ts", "cs", "latex", "lean", "lua", "python", "cpp", "prolog", "shell", "groovy", "r",
20 | "go", "rust", "java", "powershell", "kotlin", "mathematica", "haskell", "scala", "swift", "racket", "fsharp", "c", "dart",
21 | "ruby", "batch", "sql", "octave", "maxima", "applescript", "zig", "ocaml", "php"] as const;
22 | export const supportedLanguages = [...languageAliases, ...canonicalLanguages] as const;
23 | export type LanguageId = typeof canonicalLanguages[number];
24 |
25 | export interface PluginContext {
26 | app: App;
27 | settings: ExecutorSettings;
28 | executors: ExecutorContainer;
29 | }
30 |
31 | export default class ExecuteCodePlugin extends Plugin {
32 | settings: ExecutorSettings;
33 | executors: ExecutorContainer;
34 |
35 | /**
36 | * Preparations for the plugin (adding buttons, html elements and event listeners).
37 | */
38 | async onload() {
39 | await this.loadSettings();
40 | this.addSettingTab(new SettingsTab(this.app, this));
41 |
42 | this.executors = new ExecutorContainer(this);
43 |
44 | const context: PluginContext = {
45 | app: this.app,
46 | settings: this.settings,
47 | executors: this.executors,
48 | }
49 | runButton.addInOpenFiles(context);
50 | this.registerMarkdownPostProcessor((element, _context) => {
51 | runButton.addToAllCodeBlocks(element, _context.sourcePath, this.app.workspace.getActiveViewOfType(MarkdownView), context);
52 | });
53 |
54 | // live preview renderers
55 | supportedLanguages.forEach(l => {
56 | console.debug(`Registering renderer for ${l}.`)
57 | this.registerMarkdownCodeBlockProcessor(`run-${l}`, async (src, el, _ctx) => {
58 | await MarkdownRenderer.render(this.app, '```' + l + '\n' + src + (src.endsWith('\n') ? '' : '\n') + '```', el, _ctx.sourcePath, new Component());
59 | });
60 | });
61 |
62 | //executor manager
63 |
64 | this.registerView(
65 | EXECUTOR_MANAGER_VIEW_ID, (leaf) => new ExecutorManagerView(leaf, this.executors)
66 | );
67 | this.addCommand({
68 | id: EXECUTOR_MANAGER_OPEN_VIEW_COMMAND_ID,
69 | name: "Open Code Runtime Management",
70 | callback: () => ExecutorManagerView.activate(this.app.workspace)
71 | });
72 |
73 | this.addCommand({
74 | id: "run-all-code-blocks-in-file",
75 | name: "Run all Code Blocks in Current File",
76 | callback: () => runAllCodeBlocks(this.app.workspace)
77 | })
78 |
79 | if (!this.settings.releaseNote2_1_0wasShowed) {
80 | this.app.workspace.onLayoutReady(() => {
81 | new ReleaseNoteModel(this.app).open();
82 | })
83 |
84 | // Set to true to prevent the release note from showing again
85 | this.settings.releaseNote2_1_0wasShowed = true;
86 | this.saveSettings();
87 | }
88 |
89 | applyLatexBodyClasses(this.app, this.settings);
90 | }
91 |
92 | /**
93 | * Remove all generated html elements (run & clear buttons, output elements) when the plugin is disabled.
94 | */
95 | onunload() {
96 | document
97 | .querySelectorAll("pre > code")
98 | .forEach((codeBlock: HTMLElement) => {
99 | const pre = codeBlock.parentElement as HTMLPreElement;
100 | const parent = pre.parentElement as HTMLDivElement;
101 |
102 | if (parent.hasClass(runButton.codeBlockHasButtonClass)) {
103 | parent.removeClass(runButton.codeBlockHasButtonClass);
104 | }
105 | });
106 |
107 | document
108 | .querySelectorAll("." + runButton.buttonClass)
109 | .forEach((button: HTMLButtonElement) => button.remove());
110 |
111 | document
112 | .querySelectorAll("." + runButton.disabledClass)
113 | .forEach((button: HTMLButtonElement) => button.remove());
114 |
115 | document
116 | .querySelectorAll(".clear-button")
117 | .forEach((button: HTMLButtonElement) => button.remove());
118 |
119 | document
120 | .querySelectorAll(".language-output")
121 | .forEach((out: HTMLElement) => out.remove());
122 |
123 | for (const executor of this.executors) {
124 | executor.stop().then(_ => { /* do nothing */
125 | });
126 | }
127 |
128 | console.log("Unloaded plugin: Execute Code");
129 | }
130 |
131 | /**
132 | * Loads the settings for this plugin from the corresponding save file and stores them in {@link settings}.
133 | */
134 | async loadSettings() {
135 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
136 | if (process.platform !== "win32") {
137 | this.settings.wslMode = false;
138 | }
139 | }
140 |
141 | /**
142 | * Saves the settings in {@link settings} to the corresponding save file.
143 | */
144 | async saveSettings() {
145 | await this.saveData(this.settings);
146 | }
147 | }
--------------------------------------------------------------------------------
/src/output/FileAppender.ts:
--------------------------------------------------------------------------------
1 | import { EditorPosition, EditorRange, MarkdownView } from "obsidian";
2 |
3 | export default class FileAppender {
4 | view: MarkdownView;
5 | codeBlockElement: HTMLPreElement
6 | codeBlockRange: EditorRange
7 | outputPosition: EditorPosition;
8 |
9 | public constructor(view: MarkdownView, blockElem: HTMLPreElement) {
10 | this.view = view;
11 |
12 | this.codeBlockElement = blockElem;
13 |
14 | try {
15 | this.codeBlockRange = this.getRangeOfCodeBlock(blockElem);
16 | } catch (e) {
17 | console.error("Error finding code block range: Probably because of 'run-' prefix");
18 | this.codeBlockRange = null
19 | }
20 | }
21 |
22 | public clearOutput() {
23 | if (this.codeBlockRange && this.outputPosition) {
24 |
25 | const editor = this.view.editor;
26 |
27 | //Offset this.outputPosition by "\n```"
28 | const afterEndOfOutputCodeBlock: EditorPosition = {
29 | line: this.outputPosition.line + 1,
30 | ch: "```".length + 1
31 | };
32 |
33 | editor.replaceRange("", this.codeBlockRange.to, afterEndOfOutputCodeBlock);
34 | this.view.setViewData(editor.getValue(), false);
35 |
36 | this.outputPosition = null;
37 | }
38 | }
39 |
40 | public addOutput(output: string) {
41 | try {
42 | this.findOutputTarget();
43 | } catch (e) {
44 | console.error("Error finding output target: Probably because of 'run-' prefix");
45 | this.view.setViewData(this.view.editor.getValue(), false);
46 | return;
47 | }
48 |
49 | const editor = this.view.editor;
50 |
51 | editor.replaceRange(output, this.outputPosition);
52 |
53 | const lines = output.split("\n");
54 | this.outputPosition = {
55 | line: this.outputPosition.line + (lines.length - 1), //if the addition is only 1 line, don't change current line pos
56 | ch: (lines.length == 1 ? //if the addition is only 1 line, then offset from the existing position.
57 | this.outputPosition.ch : 0 //If it's not, ignore it.
58 | ) + lines[lines.length - 1].length
59 | }
60 |
61 | this.view.setViewData(this.view.editor.getValue(), false);
62 | }
63 |
64 | /**
65 | * Finds where output should be appended to and sets the `outputPosition` property to reflect it.
66 | * @param addIfNotExist Add an `output` code block if one doesn't exist already
67 | */
68 | findOutputTarget(addIfNotExist = true) {
69 | const editor = this.view.editor;
70 |
71 | const EXPECTED_SUFFIX = "\n```output\n";
72 |
73 | const sigilEndIndex = editor.posToOffset(this.codeBlockRange.to) + EXPECTED_SUFFIX.length;
74 |
75 | const outputBlockSigilRange: EditorRange = {
76 | from: this.codeBlockRange.to,
77 | to: {
78 | ch: 0, //since the suffix ends with a newline, it'll be column 0
79 | line: this.codeBlockRange.to.line + 2 // the suffix adds 2 lines
80 | }
81 | }
82 |
83 | const hasOutput = editor.getRange(outputBlockSigilRange.from, outputBlockSigilRange.to) == EXPECTED_SUFFIX;
84 |
85 | if (hasOutput) {
86 | //find the first code block end that occurs after the ```output sigil
87 | const index = editor.getValue().indexOf("\n```\n", sigilEndIndex);
88 |
89 | //bail out if we didn't find an end
90 | if(index == -1) {
91 | this.outputPosition = outputBlockSigilRange.to;
92 | } else {
93 | //subtract 1 so output appears before the newline
94 | this.outputPosition = editor.offsetToPos(index - 1);
95 | }
96 | } else if (addIfNotExist) {
97 | editor.replaceRange(EXPECTED_SUFFIX + "```\n", this.codeBlockRange.to);
98 | this.view.data = this.view.editor.getValue();
99 | //We need to recalculate the outputPosition because the insertion will've changed the lines.
100 | //The expected suffix ends with a newline, so the column will always be 0;
101 | //the row will be the current row + 2: the suffix adds 2 lines
102 | this.outputPosition = {
103 | ch: 0,
104 | line: this.codeBlockRange.to.line + 2
105 | };
106 |
107 | } else {
108 | this.outputPosition = outputBlockSigilRange.to;
109 | }
110 | }
111 |
112 | /**
113 | * With a starting line, ending line, and number of codeblocks in-between those, find the exact EditorRange of a code block.
114 | *
115 | * @param startLine The line to start searching at
116 | * @param endLine The line to end searching AFTER (i.e. it is inclusive)
117 | * @param searchBlockIndex The index of code block, within the startLine-endLine range, to search for
118 | * @returns an EditorRange representing the range occupied by the given block, or null if it couldn't be found
119 | */
120 | findExactCodeBlockRange(startLine: number, endLine: number, searchBlockIndex: number): EditorRange | null {
121 | const editor = this.view.editor;
122 | const textContent = editor.getValue();
123 |
124 | const startIndex = editor.posToOffset({ ch: 0, line: startLine });
125 | const endIndex = editor.posToOffset({ ch: 0, line: endLine + 1 });
126 |
127 | //Start the parsing with a given amount of padding.
128 | //This helps us if the section begins directly with "```".
129 | //At the end, it iterates through the padding again.
130 | const PADDING = "\n\n\n\n\n";
131 |
132 |
133 | /*
134 | escaped: whether we are currently in an escape character
135 | inBlock: whether we are currently inside a code block
136 | last5: a rolling buffer of the last 5 characters.
137 | It could technically work with 4, but it's easier to do 5
138 | and it leaves open future advanced parsing.
139 | blockStart: the start of the last code block we entered
140 |
141 | */
142 | let escaped, inBlock, blockI = 0, last5 = PADDING, blockStart
143 | for (let i = startIndex; i < endIndex + PADDING.length; i++) {
144 | const char = i < endIndex ? textContent[i] : PADDING[0];
145 |
146 | last5 = last5.substring(1) + char;
147 | if (escaped) {
148 | escaped = false;
149 | continue;
150 | }
151 | if (char == "\\") {
152 | escaped = true;
153 | continue;
154 | }
155 | if (last5.substring(0, 4) == "\n```") {
156 | inBlock = !inBlock;
157 | //If we are entering a block, set the block start
158 | if (inBlock) {
159 | blockStart = i - 4;
160 | } else {
161 | //if we're leaving a block, check if its index is the searched index
162 | if (blockI == searchBlockIndex) {
163 | return {
164 | from: this.view.editor.offsetToPos(blockStart),
165 | to: this.view.editor.offsetToPos(i)
166 | }
167 | } else {// if it isn't, just increase the block index
168 | blockI++;
169 | }
170 | }
171 | }
172 | }
173 | return null;
174 | }
175 |
176 | /**
177 | * Uses an undocumented API to find the EditorRange that corresponds to a given codeblock's element.
178 | * Returns null if it wasn't able to find the range.
179 | * @param codeBlock element of the desired code block
180 | * @returns the corresponding EditorRange, or null
181 | */
182 | getRangeOfCodeBlock(codeBlock: HTMLPreElement): EditorRange | null {
183 | const parent = codeBlock.parentElement;
184 | const index = Array.from(parent.children).indexOf(codeBlock);
185 |
186 | //@ts-ignore
187 | const section: null | { lineStart: number, lineEnd: number } = this.view.previewMode.renderer.sections.find(x => x.el == parent);
188 |
189 | if (section) {
190 | return this.findExactCodeBlockRange(section.lineStart, section.lineEnd, index);
191 | } else {
192 | return null;
193 | }
194 | }
195 | }
--------------------------------------------------------------------------------
/src/output/LatexInserter.ts:
--------------------------------------------------------------------------------
1 | import { App, setIcon, TFile, Vault } from "obsidian";
2 | import { Outputter } from "./Outputter";
3 | import { settingsInstance } from "src/transforms/LatexTransformer";
4 | import { FIGURE_FILENAME_EXTENSIONS, TEMP_FIGURE_NAME } from 'src/transforms/LatexFigureName';
5 | import { generalizeFigureTitle } from 'src/transforms/LatexFigureName';
6 | import * as r from "./RegExpUtilities";
7 | import * as path from "path";
8 |
9 | const LINK_ALIAS = /\|[^\]]*/;
10 | const ANY_WIKILINK_EMBEDDING = r.concat(/!\[\[.*?/, FIGURE_FILENAME_EXTENSIONS, r.optional(LINK_ALIAS), /\]\]/);
11 | const ANY_MARKDOWN_EMBEDDING = r.concat(/!\[.*?\]\(.*?/, FIGURE_FILENAME_EXTENSIONS, /\)/);
12 | const ANY_FIGURE_EMBEDDING: RegExp = r.alternate(ANY_WIKILINK_EMBEDDING, ANY_MARKDOWN_EMBEDDING);
13 |
14 | const SAFE_ANY: RegExp = /([^`]|`[^`]|``[^`])*?/; // Match any text, that does not cross the ``` boundary of code blocks
15 | const EMPTY_LINES: RegExp = /[\s\n]*/;
16 |
17 | interface FigureContext {
18 | app: App;
19 | figureName: string;
20 | link: () => string; // evaluates at button click, to let Obsidian index the file
21 | file: TFile;
22 | }
23 |
24 | /** Forces an image to reload by appending a cache-busting timestamp to its URL */
25 | export function updateImage(image: HTMLImageElement) {
26 | const baseUrl = image.src.split('?')[0];
27 | image.src = `${baseUrl}?cache=${Date.now()}`;
28 | }
29 |
30 | /**
31 | * Adds an obsidian link and clickable insertion icons to the output.
32 | * @param figureName - The name of the figure file with extension that was saved
33 | * @param figurePath - The path where the figure was saved
34 | * @param outputter - The Outputter instance used to write content
35 | */
36 | export async function writeFileLink(figureName: string, figurePath: string, outputter: Outputter): Promise {
37 | await outputter.writeMarkdown(`Saved [[${figureName}]]`);
38 |
39 | const isTempFigure = TEMP_FIGURE_NAME.test(figureName);
40 | if (isTempFigure) return outputter.write('\n');
41 |
42 | const file: TFile | null = outputter.app.vault.getFileByPath(outputter.srcFile);
43 | if (!file) throw new Error(`File not found: ${outputter.srcFile}`);
44 |
45 | const link = () => createObsidianLink(outputter.app, figurePath, outputter.srcFile);
46 | const figure: FigureContext = { app: outputter.app, figureName: figureName, link: link, file: file };
47 | const buttonClass = 'insert-figure-icon';
48 |
49 | const insertAbove: HTMLAnchorElement = outputter.writeIcon('image-up', 'Click to embed file above codeblock.\nCtrl + Click to replace previous embedding.', buttonClass);
50 | insertAbove.addEventListener('click', (event: MouseEvent) => insertEmbedding('above', event.ctrlKey, figure));
51 |
52 | const insertBelow: HTMLAnchorElement = outputter.writeIcon('image-down', 'Click to embed file below codeblock.\nCtrl + Click to replace next embedding.', buttonClass);
53 | insertBelow.addEventListener('click', (event: MouseEvent) => insertEmbedding('below', event.ctrlKey, figure));
54 |
55 | const copyLink: HTMLAnchorElement = outputter.writeIcon('copy', 'Copy the markdown link.', buttonClass);
56 | copyLink.addEventListener('click', () => navigator.clipboard.writeText(link()));
57 |
58 | outputter.write('\n');
59 | }
60 |
61 | /** * Inserts an embedded link to the figure above or below the current code blocks. */
62 | async function insertEmbedding(pastePosition: 'above' | 'below', doReplace: boolean, figure: FigureContext): Promise {
63 | try {
64 | const vault = figure.app.vault;
65 | const content: string = await vault.read(figure.file);
66 |
67 | const identifierSrc: string = settingsInstance.latexFigureTitlePattern
68 | .replace(/\(\?[^)]*\)/, generalizeFigureTitle(figure.figureName).source);
69 | const identifier: RegExp = r.parse(identifierSrc);
70 | if (!identifier) return;
71 |
72 | const codeBlocks: RegExpMatchArray[] = findMatchingCodeBlocks(content, /(la)?tex/, identifier, figure.link(), doReplace);
73 | if (codeBlocks.length === 0) return false;
74 |
75 | codeBlocks.forEach(async (block: RegExpExecArray) => {
76 | await insertAtCodeBlock(block, pastePosition, figure);
77 | });
78 | return true;
79 | } catch (error) {
80 | console.error('Error inserting embedding:', error);
81 | throw error;
82 | }
83 | }
84 |
85 | /** Locates LaTeX code blocks containing the specified figure identifier and their surrounding embeddings */
86 | function findMatchingCodeBlocks(content: string, language: RegExp, identifier: RegExp, link: string, doReplace?: boolean): RegExpMatchArray[] {
87 | const alreadyLinked: RegExp = r.group(r.escape(link));
88 | const codeblock: RegExp = r.concat(
89 | /```(run-)?/, r.group(language), /[\s\n]/,
90 | SAFE_ANY, r.group(identifier), SAFE_ANY,
91 | /```/);
92 |
93 | const previous: RegExp = r.capture(r.concat(ANY_FIGURE_EMBEDDING, EMPTY_LINES), 'replacePrevious');
94 | const above: RegExp = r.capture(r.concat(alreadyLinked, EMPTY_LINES), 'alreadyAbove');
95 |
96 | const below: RegExp = r.capture(r.concat(EMPTY_LINES, alreadyLinked), 'alreadyBelow');
97 | const next: RegExp = r.capture(r.concat(EMPTY_LINES, ANY_FIGURE_EMBEDDING), 'replaceNext');
98 |
99 | const blocksWithEmbeds: RegExp = new RegExp(r.concat(
100 | (doReplace) ? r.optional(previous) : null,
101 | r.optional(above),
102 | r.capture(codeblock, 'codeblock'),
103 | r.optional(below),
104 | (doReplace) ? r.optional(next) : null,
105 | ), 'g');
106 |
107 | const matches: RegExpMatchArray[] = Array.from(content.matchAll(blocksWithEmbeds));
108 | console.debug(`Searching markdown for`, blocksWithEmbeds, `resulted in `, matches.length, `codeblock(s)`, matches.map(match => match.groups));
109 | return matches;
110 | }
111 |
112 | /** Updates markdown source file to insert or replace a figure embedding relative to a code block */
113 | async function insertAtCodeBlock(block: RegExpExecArray, pastePosition: 'above' | 'below', figure: FigureContext): Promise {
114 | const vault = figure.app.vault;
115 | const groups = block.groups;
116 | if (!groups || !groups.codeblock) return;
117 |
118 | const canReplace: Boolean = (pastePosition === 'above')
119 | ? groups.replacePrevious?.length > 0
120 | : groups.replaceNext?.length > 0;
121 |
122 | const isAlreadyEmbedded: boolean = (pastePosition === 'above')
123 | ? groups.alreadyAbove?.length > 0
124 | : groups.alreadyBelow?.length > 0;
125 | if (isAlreadyEmbedded && !canReplace) return;
126 |
127 | const newText: string = (pastePosition === 'above')
128 | ? figure.link() + '\n\n' + groups.codeblock
129 | : groups.codeblock + '\n\n' + figure.link();
130 |
131 | if (!canReplace) {
132 | await vault.process(figure.file, data => data.replace(groups.codeblock, newText));
133 | return;
134 | }
135 |
136 | const oldTexts: string[] = (pastePosition === 'above')
137 | ? [groups.replacePrevious, groups.alreadyAbove, groups.codeblock]
138 | : [groups.codeblock, groups.alreadyBelow, groups.replaceNext];
139 | const oldCombined = oldTexts.filter(Boolean).join('');
140 | await vault.process(figure.file, data => data.replace(oldCombined, newText));
141 | }
142 |
143 | /** Let Obsidian generate a link adhering to preferences */
144 | export function createObsidianLink(app: App, filePath: string, sourcePath: string, subpath?: string, alias?: string): string {
145 | const relative = getPathRelativeToVault(filePath);
146 | try {
147 | const file: TFile | null = app.vault.getFileByPath(relative);
148 | return app.fileManager.generateMarkdownLink(file, sourcePath, subpath, alias);
149 | } catch (error) {
150 | console.error(`File not found: ${relative}`);
151 | return '![[' + path.basename(filePath) + ']]';
152 | }
153 |
154 | }
155 |
156 | function getPathRelativeToVault(absolutePath: string) {
157 | const vaultPath = (this.app.vault.adapter as any).basePath;
158 | absolutePath = path.normalize(absolutePath);
159 |
160 | if (!absolutePath.startsWith(vaultPath)) return absolutePath;
161 | return absolutePath.slice(vaultPath.length)
162 | .replace(/^[\\\/]/, '')
163 | .replace(/\\/g, '/')
164 | .replace(/['"`]/, '')
165 | .trim();
166 | }
--------------------------------------------------------------------------------
/src/output/RegExpUtilities.ts:
--------------------------------------------------------------------------------
1 | /** Escapes special regex characters in a string to create a RegExp that matches it literally */
2 | export function escape(str: string): RegExp {
3 | return new RegExp(str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); // $& means the whole matched string
4 | }
5 |
6 | /** Converts "/regex/" into RegExp */
7 | export function parse(pattern: string): RegExp | undefined {
8 | try {
9 | const trimmedSlashes: string = pattern.replace(/^\/|\/$/g, '');
10 | return RegExp(trimmedSlashes);
11 | } catch {
12 | return undefined;
13 | }
14 | }
15 |
16 | /** Makes a pattern optional by adding ? quantifier, equivalent to (pattern)? */
17 | export function optional(pattern: RegExp): RegExp {
18 | return new RegExp(group(pattern).source + '?');
19 | }
20 |
21 | /** Creates a named capture group from the pattern, equivalent to (?pattern) */
22 | export function capture(pattern: RegExp, groupName: string): RegExp {
23 | return group(pattern, { name: groupName });
24 | }
25 |
26 | /** Express unit?/scope?/encapsulated?/unbreakable? of inner pattern */
27 | export function group(inner: RegExp, options?: { name?: string }): RegExp {
28 | let identifier = '';
29 | if (options?.name) identifier = `?<${options.name}>`;
30 | return new RegExp('(' + identifier + inner.source + ')');
31 | }
32 |
33 | /** Combines multiple patterns sequentially into a single pattern */
34 | export function concat(...chain: RegExp[]): RegExp {
35 | const combined: string = chain
36 | .filter(Boolean)
37 | .map(pattern => pattern.source)
38 | .join('');
39 | return new RegExp(combined);
40 | }
41 |
42 | /** Creates an alternation (OR) group from multiple patterns, equivalent to (pattern1|pattern2) */
43 | export function alternate(...options: RegExp[]): RegExp {
44 | const alternated: string = options
45 | .filter(Boolean)
46 | .map(pattern => pattern.source)
47 | .join('|');
48 | return group(new RegExp(alternated));
49 | }
--------------------------------------------------------------------------------
/src/runAllCodeBlocks.ts:
--------------------------------------------------------------------------------
1 | import { TextFileView, Workspace } from "obsidian";
2 | import { buttonClass } from './RunButton';
3 |
4 | export default function runAllCodeBlocks(workspace: Workspace) {
5 | const lastActiveView = workspace.getMostRecentLeaf().view;
6 |
7 | if (lastActiveView instanceof TextFileView) {
8 | lastActiveView.containerEl.querySelectorAll("button." + buttonClass).forEach((button: HTMLButtonElement) => {
9 | button.click();
10 | });
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/settings/Settings.ts:
--------------------------------------------------------------------------------
1 | import { LanguageId } from "src/main";
2 |
3 | /**
4 | * Interface that contains all the settings for the extension.
5 | */
6 | export interface ExecutorSettings {
7 | lastOpenLanguageTab: LanguageId | undefined;
8 | releaseNote2_1_0wasShowed: boolean;
9 | persistentOuput: boolean;
10 | timeout: number;
11 | allowInput: boolean;
12 | wslMode: boolean;
13 | shellWSLMode: boolean;
14 | onlyCurrentBlock: boolean;
15 | nodePath: string;
16 | nodeArgs: string;
17 | jsInject: string;
18 | jsFileExtension: string;
19 | tsPath: string;
20 | tsArgs: string;
21 | tsInject: string;
22 | latexCompilerPath: string;
23 | latexCompilerArgs: string;
24 | latexDoFilter: boolean;
25 | latexTexfotPath: string;
26 | latexTexfotArgs: string;
27 | latexDocumentclass: string;
28 | latexAdaptFont: '' | 'obsidian' | 'system';
29 | latexKeepLog: boolean;
30 | latexSubprocessesUseShell: boolean;
31 | latexMaxFigures: number;
32 | latexFigureTitlePattern: string;
33 | latexDoCrop: boolean;
34 | latexCropPath: string;
35 | latexCropArgs: string;
36 | latexCropNoStandalone: boolean;
37 | latexCropNoPagenum: boolean;
38 | latexSaveSvg: '' | 'poppler' | 'inkscape';
39 | latexSvgPath: string;
40 | latexSvgArgs: string;
41 | latexInkscapePath: string;
42 | latexInkscapeArgs: string;
43 | latexSavePdf: boolean;
44 | latexSavePng: boolean;
45 | latexPngPath: string;
46 | latexPngArgs: string;
47 | latexOutputEmbeddings: boolean;
48 | latexInvertFigures: boolean;
49 | latexCenterFigures: boolean;
50 |
51 | latexInject: string;
52 | leanPath: string;
53 | leanArgs: string;
54 | leanInject: string;
55 | luaPath: string;
56 | luaArgs: string;
57 | luaFileExtension: string;
58 | luaInject: string;
59 | dartPath: string;
60 | dartArgs: string;
61 | dartFileExtension: string;
62 | dartInject: string;
63 | csPath: string;
64 | csArgs: string;
65 | csFileExtension: string;
66 | csInject: string;
67 | pythonPath: string;
68 | pythonArgs: string;
69 | pythonEmbedPlots: boolean;
70 | pythonFileExtension: string;
71 | pythonInject: string;
72 | shellPath: string;
73 | shellArgs: string;
74 | shellFileExtension: string;
75 | shellInject: string;
76 | batchPath: string;
77 | batchArgs: string;
78 | batchFileExtension: string;
79 | batchInject: string;
80 | groovyPath: string;
81 | groovyArgs: string;
82 | groovyFileExtension: string;
83 | groovyInject: string;
84 | golangPath: string,
85 | golangArgs: string,
86 | golangFileExtension: string,
87 | goInject: string;
88 | javaPath: string,
89 | javaArgs: string,
90 | javaFileExtension: string,
91 | javaInject: string;
92 | maxPrologAnswers: number;
93 | prologInject: string;
94 | powershellPath: string;
95 | powershellArgs: string;
96 | powershellFileExtension: string;
97 | powershellInject: string;
98 | powershellEncoding: BufferEncoding;
99 | octavePath: string;
100 | octaveArgs: string;
101 | octaveFileExtension: string;
102 | octaveInject: string;
103 | maximaPath: string;
104 | maximaArgs: string;
105 | maximaFileExtension: string;
106 | maximaInject: string;
107 | cargoPath: string;
108 | cargoEvalArgs: string;
109 | rustInject: string;
110 | cppRunner: string;
111 | cppFileExtension: string;
112 | cppInject: string;
113 | cppArgs: string;
114 | cppUseMain: boolean;
115 | clingPath: string;
116 | clingArgs: string;
117 | clingStd: string;
118 | rustFileExtension: string,
119 | RPath: string;
120 | RArgs: string;
121 | REmbedPlots: boolean;
122 | RFileExtension: string;
123 | rInject: string;
124 | kotlinPath: string;
125 | kotlinArgs: string;
126 | kotlinFileExtension: string;
127 | kotlinInject: string;
128 | swiftPath: string;
129 | swiftArgs: string;
130 | swiftFileExtension: string;
131 | swiftInject: string;
132 | runghcPath: string;
133 | ghcPath: string;
134 | ghciPath: string;
135 | haskellInject: string;
136 | useGhci: boolean;
137 | mathematicaPath: string;
138 | mathematicaArgs: string;
139 | mathematicaFileExtension: string;
140 | mathematicaInject: string;
141 | phpPath: string;
142 | phpArgs: string;
143 | phpFileExtension: string;
144 | phpInject: string;
145 | scalaPath: string;
146 | scalaArgs: string;
147 | scalaFileExtension: string;
148 | scalaInject: string;
149 | racketPath: string;
150 | racketArgs: string;
151 | racketFileExtension: string;
152 | racketInject: string;
153 | fsharpPath: string;
154 | fsharpArgs: string;
155 | fsharpInject: "";
156 | fsharpFileExtension: string;
157 | cArgs: string;
158 | cUseMain: boolean;
159 | cInject: string;
160 | rubyPath: string;
161 | rubyArgs: string;
162 | rubyFileExtension: string;
163 | rubyInject: string;
164 | sqlPath: string;
165 | sqlArgs: string;
166 | sqlInject: string;
167 | applescriptPath: string;
168 | applescriptArgs: string;
169 | applescriptFileExtension: string;
170 | applescriptInject: string;
171 | zigPath: string;
172 | zigArgs: string;
173 | zigInject: string;
174 | ocamlPath: string;
175 | ocamlArgs: string;
176 | ocamlInject: string;
177 |
178 | jsInteractive: boolean;
179 | tsInteractive: boolean;
180 | csInteractive: boolean;
181 | latexInteractive: boolean;
182 | leanInteractive: boolean;
183 | luaInteractive: boolean;
184 | dartInteractive: boolean;
185 | pythonInteractive: boolean;
186 | cppInteractive: boolean;
187 | prologInteractive: boolean;
188 | shellInteractive: boolean;
189 | batchInteractive: boolean;
190 | bashInteractive: boolean;
191 | groovyInteractive: boolean;
192 | rInteractive: boolean;
193 | goInteractive: boolean;
194 | rustInteractive: boolean;
195 | javaInteractive: boolean;
196 | powershellInteractive: boolean;
197 | kotlinInteractive: boolean;
198 | swiftInteractive: boolean;
199 | mathematicaInteractive: boolean;
200 | haskellInteractive: boolean;
201 | scalaInteractive: boolean;
202 | racketInteractive: boolean;
203 | fsharpInteractive: boolean;
204 | cInteractive: boolean;
205 | rubyInteractive: boolean;
206 | sqlInteractive: boolean;
207 | octaveInteractive: boolean;
208 | maximaInteractive: boolean;
209 | applescriptInteractive: boolean;
210 | zigInteractive: boolean;
211 | ocamlInteractive: boolean;
212 | phpInteractive: boolean;
213 | }
214 |
215 |
216 | /**
217 | * The default settings for the extensions as implementation of the ExecutorSettings interface.
218 | */
219 | export const DEFAULT_SETTINGS: ExecutorSettings = {
220 | lastOpenLanguageTab: undefined,
221 |
222 | releaseNote2_1_0wasShowed: false,
223 | persistentOuput: false,
224 | timeout: 10000,
225 | allowInput: true,
226 | wslMode: false,
227 | shellWSLMode: false,
228 | onlyCurrentBlock: false,
229 | nodePath: "node",
230 | nodeArgs: "",
231 | jsFileExtension: "js",
232 | jsInject: "",
233 | tsPath: "ts-node",
234 | tsArgs: "",
235 | tsInject: "",
236 | latexCompilerPath: "lualatex",
237 | latexCompilerArgs: "-interaction=nonstopmode",
238 | latexDoFilter: true,
239 | latexTexfotPath: "texfot",
240 | latexTexfotArgs: "--quiet",
241 | latexDocumentclass: "article",
242 | latexAdaptFont: "obsidian",
243 | latexKeepLog: false,
244 | latexSubprocessesUseShell: false,
245 | latexMaxFigures: 10,
246 | latexFigureTitlePattern: /[^\n][^%`]*\\title\s*\{(?[^\}]*)\}/.source,
247 | latexDoCrop: false,
248 | latexCropPath: "pdfcrop",
249 | latexCropArgs: "--quiet",
250 | latexCropNoStandalone: true,
251 | latexCropNoPagenum: true,
252 | latexSaveSvg: "poppler",
253 | latexSvgPath: "pdftocairo",
254 | latexSvgArgs: "-svg",
255 | latexInkscapePath: "inkscape",
256 | latexInkscapeArgs: '--pages=all --export-plain-svg',
257 | latexSavePdf: true,
258 | latexSavePng: false,
259 | latexPngPath: "pdftocairo",
260 | latexPngArgs: "-singlefile -png",
261 | latexOutputEmbeddings: true,
262 | latexInvertFigures: true,
263 | latexCenterFigures: true,
264 | latexInject: "",
265 | leanPath: "lean",
266 | leanArgs: "",
267 | leanInject: "",
268 | luaPath: "lua",
269 | luaArgs: "",
270 | luaFileExtension: "lua",
271 | luaInject: "",
272 | dartPath: "dart",
273 | dartArgs: "",
274 | dartFileExtension: "dart",
275 | dartInject: "",
276 | csPath: "dotnet-script",
277 | csArgs: "",
278 | csFileExtension: "csx",
279 | csInject: "",
280 | pythonPath: "python",
281 | pythonArgs: "",
282 | pythonEmbedPlots: true,
283 | pythonFileExtension: "py",
284 | pythonInject: "",
285 | shellPath: "bash",
286 | shellArgs: "",
287 | shellFileExtension: "sh",
288 | shellInject: "",
289 | batchPath: "call",
290 | batchArgs: "",
291 | batchFileExtension: "bat",
292 | batchInject: "",
293 | groovyPath: "groovy",
294 | groovyArgs: "",
295 | groovyFileExtension: "groovy",
296 | groovyInject: "",
297 | golangPath: "go",
298 | golangArgs: "run",
299 | golangFileExtension: "go",
300 | goInject: "",
301 | javaPath: "java",
302 | javaArgs: "-ea",
303 | javaFileExtension: "java",
304 | javaInject: "",
305 | maxPrologAnswers: 15,
306 | prologInject: "",
307 | powershellPath: "powershell",
308 | powershellArgs: "-file",
309 | powershellFileExtension: "ps1",
310 | powershellInject: "$OutputEncoding = [console]::InputEncoding = [console]::OutputEncoding = New-Object System.Text.UTF8Encoding",
311 | powershellEncoding: "latin1",
312 | cargoPath: "cargo",
313 | cargoEvalArgs: "",
314 | rustInject: "",
315 | cppRunner: "cling",
316 | cppFileExtension: "cpp",
317 | cppInject: "",
318 | cppArgs: "",
319 | cppUseMain: false,
320 | clingPath: "cling",
321 | clingArgs: "",
322 | clingStd: "c++17",
323 | rustFileExtension: "rs",
324 | RPath: "Rscript",
325 | RArgs: "",
326 | REmbedPlots: true,
327 | RFileExtension: "R",
328 | rInject: "",
329 | kotlinPath: "kotlinc",
330 | kotlinArgs: "-script",
331 | kotlinFileExtension: "kts",
332 | kotlinInject: "",
333 | swiftPath: "swift",
334 | swiftArgs: "",
335 | swiftFileExtension: "swift",
336 | swiftInject: "",
337 | runghcPath: "runghc",
338 | ghcPath: "ghc",
339 | ghciPath: "ghci",
340 | useGhci: false,
341 | haskellInject: "",
342 | mathematicaPath: "wolframscript",
343 | mathematicaArgs: "-file",
344 | mathematicaFileExtension: "wls",
345 | mathematicaInject: "",
346 | scalaPath: "scala",
347 | scalaArgs: "",
348 | scalaFileExtension: "scala",
349 | scalaInject: "",
350 | racketPath: "racket",
351 | racketArgs: "",
352 | racketFileExtension: "rkt",
353 | racketInject: "#lang racket",
354 | fsharpPath: "dotnet",
355 | fsharpArgs: "fsi",
356 | fsharpInject: "",
357 | fsharpFileExtension: "fsx",
358 | cArgs: "",
359 | cUseMain: true,
360 | cInject: "",
361 | rubyPath: "ruby",
362 | rubyArgs: "",
363 | rubyFileExtension: "rb",
364 | rubyInject: "",
365 | sqlPath: "psql",
366 | sqlArgs: "-d -U -f",
367 | sqlInject: "",
368 | octavePath: "octave",
369 | octaveArgs: "-q",
370 | octaveFileExtension: "m",
371 | octaveInject: "figure('visible','off') # Necessary to embed plots",
372 | maximaPath: "maxima",
373 | maximaArgs: "-qb",
374 | maximaFileExtension: "mx",
375 | maximaInject: "",
376 | applescriptPath: "osascript",
377 | applescriptArgs: "",
378 | applescriptFileExtension: "scpt",
379 | applescriptInject: "",
380 | zigPath: "zig",
381 | zigArgs: "run",
382 | zigInject: "",
383 | ocamlPath: "ocaml",
384 | ocamlArgs: "",
385 | ocamlInject: "",
386 | phpPath: "php",
387 | phpArgs: "",
388 | phpFileExtension: "php",
389 | phpInject: "",
390 | jsInteractive: true,
391 | tsInteractive: false,
392 | csInteractive: false,
393 | latexInteractive: false,
394 | leanInteractive: false,
395 | luaInteractive: false,
396 | dartInteractive: false,
397 | pythonInteractive: true,
398 | cppInteractive: false,
399 | prologInteractive: false,
400 | shellInteractive: false,
401 | batchInteractive: false,
402 | bashInteractive: false,
403 | groovyInteractive: false,
404 | rInteractive: false,
405 | goInteractive: false,
406 | rustInteractive: false,
407 | javaInteractive: false,
408 | powershellInteractive: false,
409 | kotlinInteractive: false,
410 | swiftInteractive: false,
411 | mathematicaInteractive: false,
412 | haskellInteractive: false,
413 | scalaInteractive: false,
414 | fsharpInteractive: false,
415 | cInteractive: false,
416 | racketInteractive: false,
417 | rubyInteractive: false,
418 | sqlInteractive: false,
419 | octaveInteractive: false,
420 | maximaInteractive: false,
421 | applescriptInteractive: false,
422 | zigInteractive: false,
423 | ocamlInteractive: false,
424 | phpInteractive: false,
425 | }
426 |
--------------------------------------------------------------------------------
/src/settings/SettingsTab.ts:
--------------------------------------------------------------------------------
1 | import { App, PluginSettingTab, Setting } from "obsidian";
2 | import ExecuteCodePlugin, { canonicalLanguages, LanguageId } from "src/main";
3 | import { DISPLAY_NAMES } from "./languageDisplayName";
4 | import makeCppSettings from "./per-lang/makeCppSettings";
5 | import makeCSettings from "./per-lang/makeCSettings.js";
6 | import makeCsSettings from "./per-lang/makeCsSettings";
7 | import makeFSharpSettings from "./per-lang/makeFSharpSettings";
8 | import makeGoSettings from "./per-lang/makeGoSettings";
9 | import makeGroovySettings from "./per-lang/makeGroovySettings";
10 | import makeHaskellSettings from "./per-lang/makeHaskellSettings";
11 | import makeJavaSettings from "./per-lang/makeJavaSettings";
12 | import makeJsSettings from "./per-lang/makeJsSettings";
13 | import makeKotlinSettings from "./per-lang/makeKotlinSettings";
14 | import makeLatexSettings from "./per-lang/makeLatexSettings";
15 | import makeLeanSettings from "./per-lang/makeLeanSettings";
16 | import makeLuaSettings from "./per-lang/makeLuaSettings";
17 | import makeDartSettings from "./per-lang/makeDartSettings";
18 | import makeMathematicaSettings from "./per-lang/makeMathematicaSettings";
19 | import makePhpSettings from "./per-lang/makePhpSettings";
20 | import makePowershellSettings from "./per-lang/makePowershellSettings";
21 | import makePrologSettings from "./per-lang/makePrologSettings";
22 | import makePythonSettings from "./per-lang/makePythonSettings";
23 | import makeRSettings from "./per-lang/makeRSettings";
24 | import makeRubySettings from "./per-lang/makeRubySettings";
25 | import makeRustSettings from "./per-lang/makeRustSettings";
26 | import makeScalaSettings from "./per-lang/makeScalaSettings.js";
27 | import makeRacketSettings from "./per-lang/makeRacketSettings.js";
28 | import makeShellSettings from "./per-lang/makeShellSettings";
29 | import makeBatchSettings from "./per-lang/makeBatchSettings";
30 | import makeTsSettings from "./per-lang/makeTsSettings";
31 | import { ExecutorSettings } from "./Settings";
32 | import makeSQLSettings from "./per-lang/makeSQLSettings";
33 | import makeOctaviaSettings from "./per-lang/makeOctaveSettings";
34 | import makeMaximaSettings from "./per-lang/makeMaximaSettings";
35 | import makeApplescriptSettings from "./per-lang/makeApplescriptSettings";
36 | import makeZigSettings from "./per-lang/makeZigSettings";
37 | import makeOCamlSettings from "./per-lang/makeOCamlSettings";
38 | import makeSwiftSettings from "./per-lang/makeSwiftSettings";
39 |
40 |
41 | /**
42 | * This class is responsible for creating a settings tab in the settings menu. The settings tab is showed in the
43 | * regular obsidian settings menu.
44 | *
45 | * The {@link display} functions build the html page that is showed in the settings.
46 | */
47 | export class SettingsTab extends PluginSettingTab {
48 | plugin: ExecuteCodePlugin;
49 |
50 | languageContainers: Partial>;
51 | activeLanguageContainer: HTMLDivElement | undefined;
52 |
53 | constructor(app: App, plugin: ExecuteCodePlugin) {
54 | super(app, plugin);
55 | this.plugin = plugin;
56 |
57 | this.languageContainers = {}
58 | }
59 |
60 | /**
61 | * Builds the html page that is showed in the settings.
62 | */
63 | display() {
64 | const { containerEl } = this;
65 | containerEl.empty();
66 |
67 | containerEl.createEl('h2', { text: 'Settings for the Code Execution Plugin.' });
68 |
69 |
70 | // ========== General ==========
71 | containerEl.createEl('h3', { text: 'General Settings' });
72 | new Setting(containerEl)
73 | .setName('Timeout (in seconds)')
74 | .setDesc('The time after which a program gets shut down automatically. This is to prevent infinite loops. ')
75 | .addText(text => text
76 | .setValue("" + this.plugin.settings.timeout / 1000)
77 | .onChange(async (value) => {
78 | if (Number(value) * 1000) {
79 | console.log('Timeout set to: ' + value);
80 | this.plugin.settings.timeout = Number(value) * 1000;
81 | }
82 | await this.plugin.saveSettings();
83 | }));
84 |
85 | new Setting(containerEl)
86 | .setName('Allow Input')
87 | .setDesc('Whether or not to include a stdin input box when running blocks. In order to apply changes to this, Obsidian must be refreshed. ')
88 | .addToggle(text => text
89 | .setValue(this.plugin.settings.allowInput)
90 | .onChange(async (value) => {
91 | console.log('Allow Input set to: ' + value);
92 | this.plugin.settings.allowInput = value
93 | await this.plugin.saveSettings();
94 | }));
95 |
96 | if (process.platform === "win32") {
97 | new Setting(containerEl)
98 | .setName('WSL Mode')
99 | .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!")
100 | .addToggle(text => text
101 | .setValue(this.plugin.settings.wslMode)
102 | .onChange(async (value) => {
103 | console.log('WSL Mode set to: ' + value);
104 | this.plugin.settings.wslMode = value
105 | await this.plugin.saveSettings();
106 | }));
107 | }
108 |
109 | new Setting(containerEl)
110 | .setName('[Experimental] Persistent Output')
111 | .setDesc('If enabled, the output of the code block is written into the markdown file. This feature is ' +
112 | 'experimental and may not work as expected.')
113 | .addToggle(text => text
114 | .setValue(this.plugin.settings.persistentOuput)
115 | .onChange(async (value) => {
116 | console.log('Allow Input set to: ' + value);
117 | this.plugin.settings.persistentOuput = value
118 | await this.plugin.saveSettings();
119 | }));
120 |
121 | // TODO setting per language that requires main function if main function should be implicitly made or not, if not, non-main blocks will not have a run button
122 |
123 | containerEl.createEl("hr");
124 |
125 | new Setting(containerEl)
126 | .setName("Language-Specific Settings")
127 | .setDesc("Pick a language to edit its language-specific settings")
128 | .addDropdown((dropdown) => dropdown
129 | .addOptions(Object.fromEntries(
130 | canonicalLanguages.map(lang => [lang, DISPLAY_NAMES[lang]])
131 | ))
132 | .setValue(this.plugin.settings.lastOpenLanguageTab || canonicalLanguages[0])
133 | .onChange(async (value: LanguageId) => {
134 | this.focusContainer(value);
135 | this.plugin.settings.lastOpenLanguageTab = value;
136 | await this.plugin.saveSettings();
137 | })
138 | )
139 | .settingEl.style.borderTop = "0";
140 |
141 | makeJsSettings(this, this.makeContainerFor("js")); // JavaScript / Node
142 | makeTsSettings(this, this.makeContainerFor("ts")); // TypeScript
143 | makeLeanSettings(this, this.makeContainerFor("lean"));
144 | makeLuaSettings(this, this.makeContainerFor("lua"));
145 | makeDartSettings(this, this.makeContainerFor("dart"));
146 | makeCsSettings(this, this.makeContainerFor("cs")); // CSharp
147 | makeJavaSettings(this, this.makeContainerFor("java"));
148 | makePythonSettings(this, this.makeContainerFor("python"));
149 | makeGoSettings(this, this.makeContainerFor("go")); // Golang
150 | makeRustSettings(this, this.makeContainerFor("rust"));
151 | makeCppSettings(this, this.makeContainerFor("cpp")); // C++
152 | makeCSettings(this, this.makeContainerFor("c"));
153 | makeBatchSettings(this, this.makeContainerFor("batch"));
154 | makeShellSettings(this, this.makeContainerFor("shell"));
155 | makePowershellSettings(this, this.makeContainerFor("powershell"));
156 | makePrologSettings(this, this.makeContainerFor("prolog"));
157 | makeGroovySettings(this, this.makeContainerFor("groovy"));
158 | makeRSettings(this, this.makeContainerFor("r"));
159 | makeKotlinSettings(this, this.makeContainerFor("kotlin"));
160 | makeMathematicaSettings(this, this.makeContainerFor("mathematica"));
161 | makeHaskellSettings(this, this.makeContainerFor("haskell"));
162 | makeScalaSettings(this, this.makeContainerFor("scala"));
163 | makeSwiftSettings(this, this.makeContainerFor("swift"));
164 | makeRacketSettings(this, this.makeContainerFor("racket"));
165 | makeFSharpSettings(this, this.makeContainerFor("fsharp"));
166 | makeRubySettings(this, this.makeContainerFor("ruby"));
167 | makeSQLSettings(this, this.makeContainerFor("sql"));
168 | makeOctaviaSettings(this, this.makeContainerFor("octave"));
169 | makeMaximaSettings(this, this.makeContainerFor("maxima"));
170 | makeApplescriptSettings(this, this.makeContainerFor("applescript"));
171 | makeZigSettings(this, this.makeContainerFor("zig"));
172 | makeOCamlSettings(this, this.makeContainerFor("ocaml"));
173 | makePhpSettings(this, this.makeContainerFor("php"));
174 | makeLatexSettings(this, this.makeContainerFor("latex"));
175 |
176 | this.focusContainer(this.plugin.settings.lastOpenLanguageTab || canonicalLanguages[0]);
177 | }
178 |
179 | private makeContainerFor(language: LanguageId) {
180 | const container = this.containerEl.createDiv();
181 |
182 | container.style.display = "none";
183 |
184 | this.languageContainers[language] = container;
185 |
186 | return container;
187 | }
188 |
189 | private focusContainer(language: LanguageId) {
190 | if (this.activeLanguageContainer)
191 | this.activeLanguageContainer.style.display = "none";
192 |
193 | if (language in this.languageContainers) {
194 | this.activeLanguageContainer = this.languageContainers[language];
195 | this.activeLanguageContainer.style.display = "block";
196 | }
197 | }
198 |
199 | sanitizePath(path: string): string {
200 | path = path.replace(/\\/g, '/');
201 | path = path.replace(/['"`]/, '');
202 | path = path.trim();
203 |
204 | return path
205 | }
206 |
207 | makeInjectSetting(containerEl: HTMLElement, language: LanguageId) {
208 | const languageAlt = DISPLAY_NAMES[language];
209 |
210 | new Setting(containerEl)
211 | .setName(`Inject ${languageAlt} code`)
212 | .setDesc(`Code to add to the top of every ${languageAlt} code block before running.`)
213 | .setClass('settings-code-input-box')
214 | .addTextArea(textarea => {
215 | // @ts-ignore
216 | const val = this.plugin.settings[`${language}Inject` as keyof ExecutorSettings as string]
217 | return textarea
218 | .setValue(val)
219 | .onChange(async (value) => {
220 | (this.plugin.settings[`${language}Inject` as keyof ExecutorSettings] as string) = value;
221 | console.log(`${language} inject set to ${value}`);
222 | await this.plugin.saveSettings();
223 | });
224 | });
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/src/settings/languageDisplayName.ts:
--------------------------------------------------------------------------------
1 | import {LanguageId} from "src/main";
2 |
3 | export const DISPLAY_NAMES: Record = {
4 | cpp: "C++",
5 | cs: "C#",
6 | go: "Golang",
7 | groovy: "Groovy",
8 | haskell: "Haskell",
9 | java: "Java",
10 | js: "Javascript",
11 | kotlin: "Kotlin",
12 | latex: "LaTeX",
13 | lua: "Lua",
14 | mathematica: "Mathematica",
15 | php: "PHP",
16 | powershell: "Powershell",
17 | prolog: "Prolog",
18 | python: "Python",
19 | r: "R",
20 | rust: "Rust",
21 | shell: "Shell",
22 | batch: "Batch",
23 | ts: "Typescript",
24 | scala: "Scala",
25 | swift: "Swift",
26 | racket: "Racket",
27 | c: "C",
28 | fsharp: "F#",
29 | ruby: "Ruby",
30 | dart: "Dart",
31 | lean: "Lean",
32 | sql: "SQL",
33 | octave: "Octave",
34 | maxima: "Maxima",
35 | applescript: "Applescript",
36 | zig: "Zig",
37 | ocaml: "OCaml",
38 | } as const;
39 |
--------------------------------------------------------------------------------
/src/settings/per-lang/makeApplescriptSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Applescript Settings' });
6 | new Setting(containerEl)
7 | .setName('Osascript path')
8 | .setDesc('The path to your osascript installation (only available on MacOS).')
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.applescriptPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.applescriptPath = sanitized;
14 | console.log('Applescript path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | new Setting(containerEl)
18 | .setName('Applescript arguments')
19 | .addText(text => text
20 | .setValue(tab.plugin.settings.applescriptArgs)
21 | .onChange(async (value) => {
22 | tab.plugin.settings.applescriptArgs = value;
23 | console.log('Applescript args set to: ' + value);
24 | await tab.plugin.saveSettings();
25 | }));
26 | tab.makeInjectSetting(containerEl, "applescript");
27 | }
28 |
--------------------------------------------------------------------------------
/src/settings/per-lang/makeBatchSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Batch Settings' });
6 | new Setting(containerEl)
7 | .setName('Batch path')
8 | .setDesc('The path to the terminal. Default is command prompt.')
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.batchPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.batchPath = sanitized;
14 | console.log('Batch path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | new Setting(containerEl)
18 | .setName('Batch arguments')
19 | .addText(text => text
20 | .setValue(tab.plugin.settings.batchArgs)
21 | .onChange(async (value) => {
22 | tab.plugin.settings.batchArgs = value;
23 | console.log('Batch args set to: ' + value);
24 | await tab.plugin.saveSettings();
25 | }));
26 | new Setting(containerEl)
27 | .setName('Batch file extension')
28 | .setDesc('Changes the file extension for generated batch scripts. Default is .bat')
29 | .addText(text => text
30 | .setValue(tab.plugin.settings.batchFileExtension)
31 | .onChange(async (value) => {
32 | tab.plugin.settings.batchFileExtension = value;
33 | console.log('Batch file extension set to: ' + value);
34 | await tab.plugin.saveSettings();
35 | }));
36 | tab.makeInjectSetting(containerEl, "batch");
37 | }
38 |
--------------------------------------------------------------------------------
/src/settings/per-lang/makeCSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'C Settings' });
6 | new Setting(containerEl)
7 | .setName('gcc / Cling path')
8 | .setDesc('The path to your gcc / Cling installation.')
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.clingPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.clingPath = sanitized;
14 | console.log('gcc / Cling path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | new Setting(containerEl)
18 | .setName('gcc / Cling arguments for C')
19 | .addText(text => text
20 | .setValue(tab.plugin.settings.cArgs)
21 | .onChange(async (value) => {
22 | tab.plugin.settings.cArgs = value;
23 | console.log('gcc / Cling args set to: ' + value);
24 | await tab.plugin.saveSettings();
25 | }));
26 | new Setting(containerEl)
27 | .setName('Cling std (ignored for gcc)')
28 | .addDropdown(dropdown => dropdown
29 | .addOption('c++98', 'C++ 98')
30 | .addOption('c++11', 'C++ 11')
31 | .addOption('c++14', 'C++ 14')
32 | .addOption('c++17', 'C++ 17')
33 | .addOption('c++2a', 'C++ 20')
34 | .setValue(tab.plugin.settings.clingStd)
35 | .onChange(async (value) => {
36 | tab.plugin.settings.clingStd = value;
37 | console.log('Cling std set to: ' + value);
38 | await tab.plugin.saveSettings();
39 | }));
40 | new Setting(containerEl)
41 | .setName('Use main function (mandatory for gcc)')
42 | .setDesc('If enabled, will use a main() function as the code block entrypoint.')
43 | .addToggle((toggle) => toggle
44 | .setValue(tab.plugin.settings.cUseMain)
45 | .onChange(async (value) => {
46 | tab.plugin.settings.cUseMain = value;
47 | console.log('C use main set to: ' + value);
48 | await tab.plugin.saveSettings();
49 | }));
50 | tab.makeInjectSetting(containerEl, "c");
51 | }
52 |
--------------------------------------------------------------------------------
/src/settings/per-lang/makeCppSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'C++ Settings' });
6 | new Setting(containerEl)
7 | .setName('Cling path')
8 | .setDesc('The path to your Cling installation.')
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.clingPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.clingPath = sanitized;
14 | console.log('Cling path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | new Setting(containerEl)
18 | .setName('Cling arguments for C++')
19 | .addText(text => text
20 | .setValue(tab.plugin.settings.cppArgs)
21 | .onChange(async (value) => {
22 | tab.plugin.settings.cppArgs = value;
23 | console.log('CPP args set to: ' + value);
24 | await tab.plugin.saveSettings();
25 | }));
26 | new Setting(containerEl)
27 | .setName('Cling std')
28 | .addDropdown(dropdown => dropdown
29 | .addOption('c++98', 'C++ 98')
30 | .addOption('c++11', 'C++ 11')
31 | .addOption('c++14', 'C++ 14')
32 | .addOption('c++17', 'C++ 17')
33 | .addOption('c++2a', 'C++ 20')
34 | .setValue(tab.plugin.settings.clingStd)
35 | .onChange(async (value) => {
36 | tab.plugin.settings.clingStd = value;
37 | console.log('Cling std set to: ' + value);
38 | await tab.plugin.saveSettings();
39 | }));
40 | new Setting(containerEl)
41 | .setName('Use main function')
42 | .setDesc('If enabled, will use a main() function as the code block entrypoint.')
43 | .addToggle((toggle) => toggle
44 | .setValue(tab.plugin.settings.cppUseMain)
45 | .onChange(async (value) => {
46 | tab.plugin.settings.cppUseMain = value;
47 | console.log('Cpp use main set to: ' + value);
48 | await tab.plugin.saveSettings();
49 | }));
50 | tab.makeInjectSetting(containerEl, "cpp");
51 | }
52 |
--------------------------------------------------------------------------------
/src/settings/per-lang/makeCsSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'CSharp Settings' });
6 | new Setting(containerEl)
7 | .setName('dotnet path')
8 | .addText(text => text
9 | .setValue(tab.plugin.settings.csPath)
10 | .onChange(async (value) => {
11 | const sanitized = tab.sanitizePath(value);
12 | tab.plugin.settings.csPath = sanitized;
13 | console.log('dotnet path set to: ' + sanitized);
14 | await tab.plugin.saveSettings();
15 | }));
16 | new Setting(containerEl)
17 | .setName('CSharp arguments')
18 | .addText(text => text
19 | .setValue(tab.plugin.settings.csArgs)
20 | .onChange(async (value) => {
21 | tab.plugin.settings.csArgs = value;
22 | console.log('CSharp args set to: ' + value);
23 | await tab.plugin.saveSettings();
24 | }));
25 | tab.makeInjectSetting(containerEl, "cs");
26 | }
--------------------------------------------------------------------------------
/src/settings/per-lang/makeDartSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Dart Settings' });
6 | new Setting(containerEl)
7 | .setName('dart path')
8 | .addText(text => text
9 | .setValue(tab.plugin.settings.dartPath)
10 | .onChange(async (value) => {
11 | const sanitized = tab.sanitizePath(value);
12 | tab.plugin.settings.dartPath = sanitized;
13 | console.log('dart path set to: ' + sanitized);
14 | await tab.plugin.saveSettings();
15 | }));
16 | new Setting(containerEl)
17 | .setName('Dart arguments')
18 | .addText(text => text
19 | .setValue(tab.plugin.settings.dartArgs)
20 | .onChange(async (value) => {
21 | tab.plugin.settings.dartArgs = value;
22 | console.log('Dart args set to: ' + value);
23 | await tab.plugin.saveSettings();
24 | }));
25 | tab.makeInjectSetting(containerEl, "dart");
26 | }
27 |
--------------------------------------------------------------------------------
/src/settings/per-lang/makeFSharpSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'F# Settings' });
6 | new Setting(containerEl)
7 | .setName('F# path')
8 | .setDesc('The path to dotnet.')
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.fsharpPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.fsharpPath = sanitized;
14 | console.log('F# path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | new Setting(containerEl)
18 | .setName('F# arguments')
19 | .addText(text => text
20 | .setValue(tab.plugin.settings.fsharpArgs)
21 | .onChange(async (value) => {
22 | tab.plugin.settings.fsharpArgs = value;
23 | console.log('F# args set to: ' + value);
24 | await tab.plugin.saveSettings();
25 | }));
26 | new Setting(containerEl)
27 | .setName('F# file extension')
28 | .setDesc('Changes the file extension for generated F# scripts.')
29 | .addText(text => text
30 | .setValue(tab.plugin.settings.fsharpFileExtension)
31 | .onChange(async (value) => {
32 | tab.plugin.settings.fsharpFileExtension = value;
33 | console.log('F# file extension set to: ' + value);
34 | await tab.plugin.saveSettings();
35 | }));
36 | tab.makeInjectSetting(containerEl, "fsharp");
37 | }
--------------------------------------------------------------------------------
/src/settings/per-lang/makeGoSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Golang Settings' });
6 | new Setting(containerEl)
7 | .setName('Golang Path')
8 | .setDesc('The path to your Golang installation.')
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.golangPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.golangPath = sanitized;
14 | console.log('Golang path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | tab.makeInjectSetting(containerEl, "go");
18 | }
--------------------------------------------------------------------------------
/src/settings/per-lang/makeGroovySettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Groovy Settings' });
6 | new Setting(containerEl)
7 | .setName('Groovy path')
8 | .setDesc('The path to your Groovy installation.')
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.groovyPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.groovyPath = sanitized;
14 | console.log('Groovy path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | new Setting(containerEl)
18 | .setName('Groovy arguments')
19 | .addText(text => text
20 | .setValue(tab.plugin.settings.groovyArgs)
21 | .onChange(async (value) => {
22 | tab.plugin.settings.groovyArgs = value;
23 | console.log('Groovy args set to: ' + value);
24 | await tab.plugin.saveSettings();
25 | }));
26 | tab.makeInjectSetting(containerEl, "groovy");
27 | }
--------------------------------------------------------------------------------
/src/settings/per-lang/makeHaskellSettings.ts:
--------------------------------------------------------------------------------
1 | import {Setting} from "obsidian";
2 | import {SettingsTab} from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', {text: 'Haskell Settings'});
6 | new Setting(containerEl)
7 | .setName('Use Ghci')
8 | .setDesc('Run haskell code with ghci instead of runghc')
9 | .addToggle(toggle => toggle
10 | .setValue(tab.plugin.settings.useGhci)
11 | .onChange(async (value) => {
12 | tab.plugin.settings.useGhci = value;
13 | console.log(value ? 'Now using ghci for haskell' : "Now using runghc for haskell.");
14 | await tab.plugin.saveSettings();
15 | }));
16 | new Setting(containerEl)
17 | .setName('Ghci path')
18 | .setDesc('The path to your ghci installation.')
19 | .addText(text => text
20 | .setValue(tab.plugin.settings.ghciPath)
21 | .onChange(async (value) => {
22 | const sanitized = tab.sanitizePath(value);
23 | tab.plugin.settings.ghciPath = sanitized;
24 | console.log('ghci path set to: ' + sanitized);
25 | await tab.plugin.saveSettings();
26 | }));
27 | new Setting(containerEl)
28 | .setName('Runghc path')
29 | .setDesc('The path to your runghc installation.')
30 | .addText(text => text
31 | .setValue(tab.plugin.settings.runghcPath)
32 | .onChange(async (value) => {
33 | const sanitized = tab.sanitizePath(value);
34 | tab.plugin.settings.runghcPath = sanitized;
35 | console.log('runghc path set to: ' + sanitized);
36 | await tab.plugin.saveSettings();
37 | }));
38 | new Setting(containerEl)
39 | .setName('Ghc path')
40 | .setDesc('The Ghc path your runghc installation will call.')
41 | .addText(text => text
42 | .setValue(tab.plugin.settings.ghcPath)
43 | .onChange(async (value) => {
44 | const sanitized = tab.sanitizePath(value);
45 | tab.plugin.settings.ghcPath = sanitized;
46 | console.log('ghc path set to: ' + sanitized);
47 | await tab.plugin.saveSettings();
48 | }));
49 | tab.makeInjectSetting(containerEl, "haskell");
50 | }
51 |
--------------------------------------------------------------------------------
/src/settings/per-lang/makeJavaSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Java Settings' });
6 | new Setting(containerEl)
7 | .setName('Java path (Java 11 or higher)')
8 | .setDesc('The path to your Java installation.')
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.javaPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.javaPath = sanitized;
14 | console.log('Java path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | new Setting(containerEl)
18 | .setName('Java arguments')
19 | .addText(text => text
20 | .setValue(tab.plugin.settings.javaArgs)
21 | .onChange(async (value) => {
22 | tab.plugin.settings.javaArgs = value;
23 | console.log('Java args set to: ' + value);
24 | await tab.plugin.saveSettings();
25 | }));
26 | tab.makeInjectSetting(containerEl, "java");
27 | }
--------------------------------------------------------------------------------
/src/settings/per-lang/makeJsSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'JavaScript / Node Settings' });
6 | new Setting(containerEl)
7 | .setName('Node path')
8 | .addText(text => text
9 | .setValue(tab.plugin.settings.nodePath)
10 | .onChange(async (value) => {
11 | const sanitized = tab.sanitizePath(value);
12 | tab.plugin.settings.nodePath = sanitized;
13 | console.log('Node path set to: ' + sanitized);
14 | await tab.plugin.saveSettings();
15 | }));
16 | new Setting(containerEl)
17 | .setName('Node arguments')
18 | .addText(text => text
19 | .setValue(tab.plugin.settings.nodeArgs)
20 | .onChange(async (value) => {
21 | tab.plugin.settings.nodeArgs = value;
22 | console.log('Node args set to: ' + value);
23 | await tab.plugin.saveSettings();
24 | }));
25 | new Setting(containerEl)
26 | .setName("Run Javascript blocks in Notebook Mode")
27 | .addToggle((toggle) => toggle
28 | .setValue(tab.plugin.settings.jsInteractive)
29 | .onChange(async (value) => {
30 | tab.plugin.settings.jsInteractive = value;
31 | await tab.plugin.saveSettings();
32 | })
33 | )
34 | tab.makeInjectSetting(containerEl, "js");
35 | }
--------------------------------------------------------------------------------
/src/settings/per-lang/makeKotlinSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Kotlin Settings' });
6 | new Setting(containerEl)
7 | .setName('Kotlin path')
8 | .setDesc('The path to your Kotlin installation.')
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.kotlinPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.kotlinPath = sanitized;
14 | console.log('Kotlin path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | new Setting(containerEl)
18 | .setName('Kotlin arguments')
19 | .addText(text => text
20 | .setValue(tab.plugin.settings.kotlinArgs)
21 | .onChange(async (value) => {
22 | tab.plugin.settings.kotlinArgs = value;
23 | console.log('Kotlin args set to: ' + value);
24 | await tab.plugin.saveSettings();
25 | }));
26 | tab.makeInjectSetting(containerEl, "kotlin");
27 | }
--------------------------------------------------------------------------------
/src/settings/per-lang/makeLeanSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Lean Settings' });
6 | new Setting(containerEl)
7 | .setName('lean path')
8 | .addText(text => text
9 | .setValue(tab.plugin.settings.leanPath)
10 | .onChange(async (value) => {
11 | const sanitized = tab.sanitizePath(value);
12 | tab.plugin.settings.leanPath = sanitized;
13 | console.log('lean path set to: ' + sanitized);
14 | await tab.plugin.saveSettings();
15 | }));
16 | new Setting(containerEl)
17 | .setName('Lean arguments')
18 | .addText(text => text
19 | .setValue(tab.plugin.settings.leanArgs)
20 | .onChange(async (value) => {
21 | tab.plugin.settings.leanArgs = value;
22 | console.log('Lean args set to: ' + value);
23 | await tab.plugin.saveSettings();
24 | }));
25 | tab.makeInjectSetting(containerEl, "lean");
26 | }
27 |
--------------------------------------------------------------------------------
/src/settings/per-lang/makeLuaSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Lua Settings' });
6 | new Setting(containerEl)
7 | .setName('lua path')
8 | .addText(text => text
9 | .setValue(tab.plugin.settings.luaPath)
10 | .onChange(async (value) => {
11 | const sanitized = tab.sanitizePath(value);
12 | tab.plugin.settings.luaPath = sanitized;
13 | console.log('lua path set to: ' + sanitized);
14 | await tab.plugin.saveSettings();
15 | }));
16 | new Setting(containerEl)
17 | .setName('Lua arguments')
18 | .addText(text => text
19 | .setValue(tab.plugin.settings.luaArgs)
20 | .onChange(async (value) => {
21 | tab.plugin.settings.luaArgs = value;
22 | console.log('Lua args set to: ' + value);
23 | await tab.plugin.saveSettings();
24 | }));
25 | tab.makeInjectSetting(containerEl, "lua");
26 | }
--------------------------------------------------------------------------------
/src/settings/per-lang/makeMathematicaSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Wolfram Mathematica Settings' });
6 | new Setting(containerEl)
7 | .setName('Mathematica path')
8 | .setDesc('The path to your Mathematica installation.')
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.mathematicaPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.mathematicaPath = sanitized;
14 | console.log('Mathematica path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | new Setting(containerEl)
18 | .setName('Mathematica arguments')
19 | .addText(text => text
20 | .setValue(tab.plugin.settings.mathematicaArgs)
21 | .onChange(async (value) => {
22 | tab.plugin.settings.mathematicaArgs = value;
23 | console.log('Mathematica args set to: ' + value);
24 | await tab.plugin.saveSettings();
25 | }));
26 | tab.makeInjectSetting(containerEl, "mathematica");
27 | }
--------------------------------------------------------------------------------
/src/settings/per-lang/makeMaximaSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Maxima Settings' });
6 | new Setting(containerEl)
7 | .setName('Maxima path')
8 | .setDesc('The path to your Maxima installation.')
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.maximaPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.maximaPath = sanitized;
14 | console.log('Maxima path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | new Setting(containerEl)
18 | .setName('Maxima arguments')
19 | .addText(text => text
20 | .setValue(tab.plugin.settings.maximaArgs)
21 | .onChange(async (value) => {
22 | tab.plugin.settings.maximaArgs = value;
23 | console.log('Maxima args set to: ' + value);
24 | await tab.plugin.saveSettings();
25 | }));
26 | tab.makeInjectSetting(containerEl, "maxima");
27 | }
28 |
--------------------------------------------------------------------------------
/src/settings/per-lang/makeOCamlSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'OCaml Settings' });
6 | new Setting(containerEl)
7 | .setName('ocaml path')
8 | .setDesc("Path to your ocaml installation")
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.ocamlPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.ocamlPath = sanitized;
14 | console.log('ocaml path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | new Setting(containerEl)
18 | .setName('ocaml arguments')
19 | .addText(text => text
20 | .setValue(tab.plugin.settings.ocamlArgs)
21 | .onChange(async (value) => {
22 | tab.plugin.settings.ocamlArgs = value;
23 | console.log('ocaml args set to: ' + value);
24 | await tab.plugin.saveSettings();
25 | }));
26 | tab.makeInjectSetting(containerEl, "ocaml");
27 | }
28 |
--------------------------------------------------------------------------------
/src/settings/per-lang/makeOctaveSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Octave Settings' });
6 | new Setting(containerEl)
7 | .setName('Octave path')
8 | .setDesc('The path to your Octave installation.')
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.octavePath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.octavePath = sanitized;
14 | console.log('Octave path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | new Setting(containerEl)
18 | .setName('Octave arguments')
19 | .addText(text => text
20 | .setValue(tab.plugin.settings.octaveArgs)
21 | .onChange(async (value) => {
22 | tab.plugin.settings.octaveArgs = value;
23 | console.log('Octave args set to: ' + value);
24 | await tab.plugin.saveSettings();
25 | }));
26 | tab.makeInjectSetting(containerEl, "octave");
27 | }
28 |
--------------------------------------------------------------------------------
/src/settings/per-lang/makePhpSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'PHP Settings' });
6 | new Setting(containerEl)
7 | .setName('php path')
8 | .setDesc("Path to your php installation")
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.phpPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.phpPath = sanitized;
14 | console.log('php path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | new Setting(containerEl)
18 | .setName('php arguments')
19 | .addText(text => text
20 | .setValue(tab.plugin.settings.phpArgs)
21 | .onChange(async (value) => {
22 | tab.plugin.settings.phpArgs = value;
23 | console.log('php args set to: ' + value);
24 | await tab.plugin.saveSettings();
25 | }));
26 | tab.makeInjectSetting(containerEl, "php");
27 | }
28 |
--------------------------------------------------------------------------------
/src/settings/per-lang/makePowershellSettings.ts:
--------------------------------------------------------------------------------
1 | import {Notice, Setting} from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Powershell Settings' });
6 | new Setting(containerEl)
7 | .setName('Powershell path')
8 | .setDesc('The path to Powershell.')
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.powershellPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.powershellPath = sanitized;
14 | console.log('Powershell path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | new Setting(containerEl)
18 | .setName('Powershell arguments')
19 | .addText(text => text
20 | .setValue(tab.plugin.settings.powershellArgs)
21 | .onChange(async (value) => {
22 | tab.plugin.settings.powershellArgs = value;
23 | console.log('Powershell args set to: ' + value);
24 | await tab.plugin.saveSettings();
25 | }));
26 | new Setting(containerEl)
27 | .setName('Powershell file extension')
28 | .setDesc('Changes the file extension for generated shell scripts. This is useful if you don\'t want to use PowerShell.')
29 | .addText(text => text
30 | .setValue(tab.plugin.settings.powershellFileExtension)
31 | .onChange(async (value) => {
32 | tab.plugin.settings.powershellFileExtension = value;
33 | console.log('Powershell file extension set to: ' + value);
34 | await tab.plugin.saveSettings();
35 | }));
36 | new Setting(containerEl)
37 | .setName('PowerShell script encoding')
38 | .setDesc('Windows still uses windows-1252 as default encoding on most systems for legacy reasons. If you change your encodings systemwide' +
39 | ' to UTF-8, you can change this setting to UTF-8 as well. Only use one of the following encodings: ' +
40 | '"ascii", "utf8", "utf-8", "utf16le", "ucs2", "ucs-2", "base64", "latin1", "binary", "hex" (default: "latin1")')
41 | .addText(text => text
42 | .setValue(tab.plugin.settings.powershellEncoding)
43 | .onChange(async (value) => {
44 | value = value.replace(/["'`´]/, "").trim().toLowerCase();
45 | if (["ascii", "utf8", "utf-8", "utf16le", "ucs2", "ucs-2", "base64", "latin1", "binary", "hex"].includes(value)) {
46 | tab.plugin.settings.powershellEncoding = value as BufferEncoding;
47 | console.log('Powershell file extension set to: ' + value);
48 | await tab.plugin.saveSettings();
49 | } else {
50 | console.error("Invalid encoding. " + value + "Please use one of the following encodings: " +
51 | '"ascii", "utf8", "utf-8", "utf16le", "ucs2", "ucs-2", "base64", "latin1", "binary", "hex"');
52 | }
53 | }));
54 | tab.makeInjectSetting(containerEl, "powershell");
55 | }
56 |
--------------------------------------------------------------------------------
/src/settings/per-lang/makePrologSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Prolog Settings' });
6 | new Setting(containerEl)
7 | .setName('Prolog Answer Limit')
8 | .setDesc('Maximal number of answers to be returned by the Prolog engine. tab is to prevent creating too huge texts in the notebook.')
9 | .addText(text => text
10 | .setValue("" + tab.plugin.settings.maxPrologAnswers)
11 | .onChange(async (value) => {
12 | if (Number(value) * 1000) {
13 | console.log('Prolog answer limit set to: ' + value);
14 | tab.plugin.settings.maxPrologAnswers = Number(value);
15 | }
16 | await tab.plugin.saveSettings();
17 | }));
18 | tab.makeInjectSetting(containerEl, "prolog");
19 | }
--------------------------------------------------------------------------------
/src/settings/per-lang/makePythonSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Python Settings' });
6 | new Setting(containerEl)
7 | .setName('Embed Python Plots')
8 | .addToggle(toggle => toggle
9 | .setValue(tab.plugin.settings.pythonEmbedPlots)
10 | .onChange(async (value) => {
11 | tab.plugin.settings.pythonEmbedPlots = value;
12 | console.log(value ? 'Embedding Plots into Notes.' : "Not embedding Plots into Notes.");
13 | await tab.plugin.saveSettings();
14 | }));
15 | new Setting(containerEl)
16 | .setName('Python path')
17 | .setDesc('The path to your Python installation.')
18 | .addText(text => text
19 | .setValue(tab.plugin.settings.pythonPath)
20 | .onChange(async (value) => {
21 | const sanitized = tab.sanitizePath(value);
22 | tab.plugin.settings.pythonPath = sanitized;
23 | console.log('Python path set to: ' + sanitized);
24 | await tab.plugin.saveSettings();
25 | }));
26 | new Setting(containerEl)
27 | .setName('Python arguments')
28 | .addText(text => text
29 | .setValue(tab.plugin.settings.pythonArgs)
30 | .onChange(async (value) => {
31 | tab.plugin.settings.pythonArgs = value;
32 | console.log('Python args set to: ' + value);
33 | await tab.plugin.saveSettings();
34 | }));
35 | new Setting(containerEl)
36 | .setName("Run Python blocks in Notebook Mode")
37 | .addToggle((toggle) => toggle
38 | .setValue(tab.plugin.settings.pythonInteractive)
39 | .onChange(async (value) => {
40 | tab.plugin.settings.pythonInteractive = value;
41 | await tab.plugin.saveSettings();
42 | }));
43 | tab.makeInjectSetting(containerEl, "python");
44 | }
--------------------------------------------------------------------------------
/src/settings/per-lang/makeRSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'R Settings' });
6 | new Setting(containerEl)
7 | .setName('Embed R Plots created via `plot()` into Notes')
8 | .addToggle(toggle => toggle
9 | .setValue(tab.plugin.settings.REmbedPlots)
10 | .onChange(async (value) => {
11 | tab.plugin.settings.REmbedPlots = value;
12 | console.log(value ? 'Embedding R Plots into Notes.' : "Not embedding R Plots into Notes.");
13 | await tab.plugin.saveSettings();
14 | }));
15 | new Setting(containerEl)
16 | .setName('Rscript path')
17 | .setDesc('The path to your Rscript installation. Ensure you provide the Rscript binary instead of the ordinary R binary.')
18 | .addText(text => text
19 | .setValue(tab.plugin.settings.RPath)
20 | .onChange(async (value) => {
21 | const sanitized = tab.sanitizePath(value);
22 | tab.plugin.settings.RPath = sanitized;
23 | console.log('R path set to: ' + sanitized);
24 | await tab.plugin.saveSettings();
25 | }));
26 | new Setting(containerEl)
27 | .setName('R arguments')
28 | .addText(text => text
29 | .setValue(tab.plugin.settings.RArgs)
30 | .onChange(async (value) => {
31 | tab.plugin.settings.RArgs = value;
32 | console.log('R args set to: ' + value);
33 | await tab.plugin.saveSettings();
34 | }));
35 | new Setting(containerEl)
36 | .setName("Run R blocks in Notebook Mode")
37 | .addToggle((toggle) => toggle
38 | .setValue(tab.plugin.settings.rInteractive)
39 | .onChange(async (value) => {
40 | tab.plugin.settings.rInteractive = value;
41 | await tab.plugin.saveSettings();
42 | }));
43 | tab.makeInjectSetting(containerEl, "r");
44 | }
--------------------------------------------------------------------------------
/src/settings/per-lang/makeRacketSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Racket Settings' });
6 | new Setting(containerEl)
7 | .setName('racket path')
8 | .setDesc("Path to your racket installation")
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.racketPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.racketPath = sanitized;
14 | console.log('racket path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | new Setting(containerEl)
18 | .setName('Racket arguments')
19 | .addText(text => text
20 | .setValue(tab.plugin.settings.racketArgs)
21 | .onChange(async (value) => {
22 | tab.plugin.settings.racketArgs = value;
23 | console.log('Racket args set to: ' + value);
24 | await tab.plugin.saveSettings();
25 | }));
26 | tab.makeInjectSetting(containerEl, "racket");
27 | }
28 |
--------------------------------------------------------------------------------
/src/settings/per-lang/makeRubySettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Ruby Settings' });
6 | new Setting(containerEl)
7 | .setName('ruby path')
8 | .setDesc("Path to your ruby installation")
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.rubyPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.rubyPath = sanitized;
14 | console.log('ruby path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | new Setting(containerEl)
18 | .setName('ruby arguments')
19 | .addText(text => text
20 | .setValue(tab.plugin.settings.rubyArgs)
21 | .onChange(async (value) => {
22 | tab.plugin.settings.rubyArgs = value;
23 | console.log('ruby args set to: ' + value);
24 | await tab.plugin.saveSettings();
25 | }));
26 | tab.makeInjectSetting(containerEl, "ruby");
27 | }
28 |
--------------------------------------------------------------------------------
/src/settings/per-lang/makeRustSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Rust Settings' });
6 | new Setting(containerEl)
7 | .setName('Cargo Path')
8 | .setDesc('The path to your Cargo installation.')
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.cargoPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.cargoPath = sanitized;
14 | console.log('Cargo path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | tab.makeInjectSetting(containerEl, "rust");
18 | }
--------------------------------------------------------------------------------
/src/settings/per-lang/makeSQLSettings.ts:
--------------------------------------------------------------------------------
1 | import {Setting} from "obsidian";
2 | import {SettingsTab} from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', {text: 'SQL Settings'});
6 | new Setting(containerEl)
7 | .setName('SQL path')
8 | .setDesc("Path to your SQL installation. You can select the SQL dialect you prefer but you need to set the right arguments by yourself.")
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.sqlPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.sqlPath = sanitized;
14 | console.log('ruby path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | new Setting(containerEl)
18 | .setName('SQL arguments')
19 | .setDesc('Set the right arguments for your database.')
20 | .addText(text => text
21 | .setValue(tab.plugin.settings.sqlArgs)
22 | .onChange(async (value) => {
23 | tab.plugin.settings.sqlArgs = value;
24 | console.log('SQL args set to: ' + value);
25 | await tab.plugin.saveSettings();
26 | }));
27 | tab.makeInjectSetting(containerEl, "sql");
28 | }
29 |
--------------------------------------------------------------------------------
/src/settings/per-lang/makeScalaSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Scala Settings' });
6 | new Setting(containerEl)
7 | .setName('scala path')
8 | .setDesc("Path to your scala installation")
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.scalaPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.scalaPath = sanitized;
14 | console.log('scala path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | new Setting(containerEl)
18 | .setName('Scala arguments')
19 | .addText(text => text
20 | .setValue(tab.plugin.settings.scalaArgs)
21 | .onChange(async (value) => {
22 | tab.plugin.settings.scalaArgs = value;
23 | console.log('Scala args set to: ' + value);
24 | await tab.plugin.saveSettings();
25 | }));
26 | tab.makeInjectSetting(containerEl, "scala");
27 | }
28 |
--------------------------------------------------------------------------------
/src/settings/per-lang/makeShellSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Shell Settings' });
6 | new Setting(containerEl)
7 | .setName('Shell path')
8 | .setDesc('The path to shell. Default is Bash but you can use any shell you want, e.g. bash, zsh, fish, ...')
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.shellPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.shellPath = sanitized;
14 | console.log('Shell path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | new Setting(containerEl)
18 | .setName('Shell arguments')
19 | .addText(text => text
20 | .setValue(tab.plugin.settings.shellArgs)
21 | .onChange(async (value) => {
22 | tab.plugin.settings.shellArgs = value;
23 | console.log('Shell args set to: ' + value);
24 | await tab.plugin.saveSettings();
25 | }));
26 | new Setting(containerEl)
27 | .setName('Shell file extension')
28 | .setDesc('Changes the file extension for generated shell scripts. This is useful if you want to use a shell other than bash.')
29 | .addText(text => text
30 | .setValue(tab.plugin.settings.shellFileExtension)
31 | .onChange(async (value) => {
32 | tab.plugin.settings.shellFileExtension = value;
33 | console.log('Shell file extension set to: ' + value);
34 | await tab.plugin.saveSettings();
35 | }));
36 |
37 | new Setting(containerEl)
38 | .setName('Shell WSL mode')
39 | .setDesc('Run the shell script in Windows Subsystem for Linux. This option is used if the global "WSL Mode" is disabled.')
40 | .addToggle((toggle) =>
41 | toggle
42 | .setValue(tab.plugin.settings.shellWSLMode)
43 | .onChange(async (value) => {
44 | tab.plugin.settings.shellWSLMode = value;
45 | await tab.plugin.saveSettings();
46 | })
47 | );
48 | tab.makeInjectSetting(containerEl, "shell");
49 | }
50 |
--------------------------------------------------------------------------------
/src/settings/per-lang/makeSwiftSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Swift Settings' });
6 | new Setting(containerEl)
7 | .setName('Swift path')
8 | .setDesc('The path to your Swift installation.')
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.swiftPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.swiftPath = sanitized;
14 | console.log('Swift path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | new Setting(containerEl)
18 | .setName('Swift arguments')
19 | .addText(text => text
20 | .setValue(tab.plugin.settings.swiftArgs)
21 | .onChange(async (value) => {
22 | tab.plugin.settings.swiftArgs = value;
23 | console.log('Swift args set to: ' + value);
24 | await tab.plugin.saveSettings();
25 | }));
26 | tab.makeInjectSetting(containerEl, "swift");
27 | }
--------------------------------------------------------------------------------
/src/settings/per-lang/makeTsSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'TypeScript Settings' });
6 | new Setting(containerEl)
7 | .setName('ts-node path')
8 | .addText(text => text
9 | .setValue(tab.plugin.settings.tsPath)
10 | .onChange(async (value) => {
11 | const sanitized = tab.sanitizePath(value);
12 | tab.plugin.settings.tsPath = sanitized;
13 | console.log('ts-node path set to: ' + sanitized);
14 | await tab.plugin.saveSettings();
15 | }));
16 | new Setting(containerEl)
17 | .setName('TypeScript arguments')
18 | .addText(text => text
19 | .setValue(tab.plugin.settings.tsArgs)
20 | .onChange(async (value) => {
21 | tab.plugin.settings.tsArgs = value;
22 | console.log('TypeScript args set to: ' + value);
23 | await tab.plugin.saveSettings();
24 | }));
25 | tab.makeInjectSetting(containerEl, "ts");
26 | }
--------------------------------------------------------------------------------
/src/settings/per-lang/makeZigSettings.ts:
--------------------------------------------------------------------------------
1 | import { Setting } from "obsidian";
2 | import { SettingsTab } from "../SettingsTab";
3 |
4 | export default (tab: SettingsTab, containerEl: HTMLElement) => {
5 | containerEl.createEl('h3', { text: 'Zig Settings' });
6 | new Setting(containerEl)
7 | .setName('zig path')
8 | .setDesc("Path to your zig installation")
9 | .addText(text => text
10 | .setValue(tab.plugin.settings.zigPath)
11 | .onChange(async (value) => {
12 | const sanitized = tab.sanitizePath(value);
13 | tab.plugin.settings.zigPath = sanitized;
14 | console.log('zig path set to: ' + sanitized);
15 | await tab.plugin.saveSettings();
16 | }));
17 | new Setting(containerEl)
18 | .setName('zig arguments')
19 | .addText(text => text
20 | .setValue(tab.plugin.settings.zigArgs)
21 | .onChange(async (value) => {
22 | tab.plugin.settings.zigArgs = value;
23 | console.log('zig args set to: ' + value);
24 | await tab.plugin.saveSettings();
25 | }));
26 | tab.makeInjectSetting(containerEl, "zig");
27 | }
28 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | /* @settings
2 |
3 | name: Execute Code Settings
4 | id: obsidian-execute-code
5 | settings:
6 | -
7 | id: color-section-title
8 | title: Color Settings
9 | type: heading
10 | level: 3
11 | -
12 | id: use-custom-output-color
13 | title: Custom Code Output Color
14 | description: Use a custom color for the output of code blocks
15 | type: class-toggle
16 | default: false
17 | -
18 | id: code-output-text-color
19 | title: Output Text Color
20 | type: variable-color
21 | format: hex
22 | opacity: false
23 | default: '#FFFFFF'
24 | -
25 | id: use-custom-error-color
26 | title: Custom Code Error Color
27 | description: Use a custom color for the error output of code blocks
28 | type: class-toggle
29 | default: false
30 | -
31 | id: code-error-text-color
32 | title: Error Text Color
33 | type: variable-color
34 | format: hex
35 | opacity: false
36 | default: '#FF0000'
37 | */
38 |
39 | button.run-code-button {
40 | display: none;
41 | color: var(--text-muted);
42 | position: absolute;
43 | bottom: 0;
44 | right: 0;
45 | margin: 5px;
46 | padding: 5px 20px 5px 20px;
47 | z-index: 100;
48 | }
49 |
50 | button.clear-button {
51 | display: none;
52 | color: var(--text-muted);
53 | position: absolute;
54 | bottom: 0;
55 | left: 0;
56 | margin: 5px;
57 | padding: 5px 20px 5px 20px;
58 | z-index: 100;
59 | }
60 |
61 | pre:hover .run-code-button,
62 | pre:hover .clear-button {
63 | display: block;
64 | }
65 |
66 | pre:hover .run-button-disabled,
67 | pre:hover .clear-button-disabled {
68 | display: none;
69 | }
70 |
71 | .run-button-disabled,
72 | .clear-button-disabled {
73 | display: none;
74 | }
75 |
76 | pre:hover code.language-output {
77 | margin-bottom: 28px;
78 | }
79 |
80 | :not(.use-custom-output-color) code.language-output span.stdout {
81 | color: var(--text-muted) !important;
82 | }
83 |
84 | .use-custom-output-color code.language-output span.stdout {
85 | color: var(--code-output-text-color) !important;
86 | }
87 |
88 | :not(.use-custom-error-color) code.language-output span.stderr {
89 | color: red !important;
90 | }
91 |
92 | .use-custom-error-color code.language-output span.stderr {
93 | color: var(--code-error-text-color) !important;
94 | }
95 |
96 | code.language-output hr {
97 | margin: 0 0 1em;
98 | }
99 |
100 | .settings-code-input-box textarea,
101 | .settings-code-input-box input {
102 | min-width: 400px;
103 | min-height: 100px;
104 | font-family: monospace;
105 | resize: vertical;
106 | }
107 |
108 | input.interactive-stdin {
109 | font: inherit;
110 | }
111 |
112 | .manage-executors-view h3 {
113 | margin: 1em;
114 | }
115 |
116 | .manage-executors-view ul {
117 | margin: 1em;
118 | padding: 0;
119 | list-style-type: none;
120 | }
121 |
122 | .manage-executors-view ul li {
123 | padding: 0.5em;
124 | background: var(--background-primary-alt);
125 | border-radius: 4px;
126 | display: grid;
127 | flex-direction: column;
128 | margin-bottom: 0.5em;
129 | }
130 |
131 | .manage-executors-view small {
132 | text-transform: uppercase;
133 | font-weight: bold;
134 | letter-spacing: 0.1ch;
135 | grid-row: 1;
136 | }
137 |
138 | .manage-executors-view .filename {
139 | grid-row: 2;
140 | }
141 |
142 | .manage-executors-view li button {
143 | grid-column: 2;
144 | grid-row: 1 / 3;
145 | margin: 0;
146 | padding: 0.25em;
147 | display: flex;
148 | align-items: center;
149 | justify-content: center;
150 | color: var(--text-muted);
151 | background: none;
152 | }
153 |
154 | .manage-executors-view li button:hover {
155 | background: var(--background-tertiary);
156 | color: var(--icon-color-hover);
157 | }
158 |
159 | .manage-executors-view>div {
160 | position: relative;
161 | }
162 |
163 | .manage-executors-view .empty-state {
164 | color: var(--text-muted);
165 | padding: 0.5em;
166 | }
167 |
168 | .has-run-code-button {
169 | position: relative;
170 | }
171 |
172 | .load-state-indicator {
173 | position: absolute;
174 | top: 0.1em;
175 | left: -2em;
176 | width: 2em;
177 | height: 2em;
178 | background: var(--background-primary-alt);
179 | border-top-left-radius: 4px;
180 | border-bottom-left-radius: 4px;
181 | color: var(--tx1);
182 | transform: translateX(2em);
183 | transition: transform 0.25s, opacity 0.25s;
184 | opacity: 0;
185 | pointer-events: none;
186 | cursor: pointer;
187 | }
188 |
189 | .load-state-indicator svg {
190 | width: 1.5em;
191 | height: 1.5em;
192 | margin: 0.25em;
193 | }
194 |
195 | .load-state-indicator.visible {
196 | transform: translateX(0);
197 | opacity: 1;
198 | pointer-events: all;
199 | }
200 |
201 | .load-state-indicator::before {
202 | content: "";
203 | box-shadow: -1em 0 1em -0.75em inset var(--background-modifier-box-shadow);
204 | position: absolute;
205 | display: block;
206 | width: 100%;
207 | height: 100%;
208 | transform: translateX(-2em);
209 | opacity: 0;
210 | transition: transform 0.25s, opacity 0.25s;
211 | pointer-events: none;
212 | }
213 |
214 | .load-state-indicator.visible::before {
215 | transform: translateX(0);
216 | opacity: 1;
217 | }
218 |
219 | /* Hide code blocks with language-output only in markdown view using "markdown-preview-view"*/
220 | .markdown-preview-view pre.language-output {
221 | display: none;
222 | }
223 |
224 | .markdown-rendered pre.language-output {
225 | display: none;
226 | }
227 |
228 | /* Do not hide code block when exporting to PDF */
229 | @media print {
230 | pre.language-output {
231 | display: block;
232 | }
233 |
234 | /* Hide code blocks with language-output only in markdown view using "markdown-preview-view"*/
235 | .markdown-preview-view pre.language-output {
236 | display: block;
237 | }
238 |
239 | .markdown-rendered pre.language-output {
240 | display: block;
241 | }
242 | }
243 |
244 | /* Center LaTeX vector graphics, confine to text width */
245 | .center-latex-figures img[src*="/figure%20"][src$=".svg"],
246 | .center-latex-figures img[src*="/figure%20"][src*=".svg?"],
247 | .center-latex-figures .stdout img[src*=".svg?"] {
248 | display: block;
249 | margin: auto;
250 | max-width: 100%;
251 | }
252 |
253 | /* Invert LaTeX vector graphics in dark mode */
254 | .theme-dark.invert-latex-figures img[src*="/figure%20"][src$=".svg"],
255 | .theme-dark.invert-latex-figures img[src*="/figure%20"][src*=".svg?"],
256 | .theme-dark.invert-latex-figures .stdout img[src*=".svg?"] {
257 | filter: invert(1);
258 | }
259 |
260 | /* Allow descriptions in LaTeX settings to be selected and copied. */
261 | .selectable-description-text {
262 | -moz-user-select: text;
263 | -khtml-user-select: text;
264 | -webkit-user-select: text;
265 | -ms-user-select: text;
266 | user-select: text;
267 | }
268 |
269 | .insert-figure-icon {
270 | margin-left: 0.5em;
271 | }
272 |
273 | /* Try to keep description of cmd arguments in LaTeX settings on the same line. */
274 | code.selectable-description-text {
275 | white-space: nowrap;
276 | }
--------------------------------------------------------------------------------
/src/svgs/loadEllipses.ts:
--------------------------------------------------------------------------------
1 | import parseHTML from "./parseHTML";
2 |
3 | const svg = parseHTML(``);
15 |
16 | export default () => {
17 | return svg.cloneNode(true);
18 | }
--------------------------------------------------------------------------------
/src/svgs/loadSpinner.ts:
--------------------------------------------------------------------------------
1 | import parseHTML from "./parseHTML";
2 |
3 | const svg = parseHTML(``);
7 |
8 | export default () => {
9 | return svg.cloneNode(true);
10 | }
--------------------------------------------------------------------------------
/src/svgs/parseHTML.ts:
--------------------------------------------------------------------------------
1 | export default (html: string) => {
2 | let container = document.createElement("div");
3 | container.innerHTML = html;
4 | return container.firstElementChild;
5 | }
--------------------------------------------------------------------------------
/src/transforms/CodeInjector.ts:
--------------------------------------------------------------------------------
1 | import type {App} from "obsidian";
2 | import {MarkdownView, Notice} from "obsidian";
3 | import {ExecutorSettings} from "src/settings/Settings";
4 | import {getCodeBlockLanguage, getLanguageAlias, transformMagicCommands} from './TransformCode';
5 | import {getArgs} from "src/CodeBlockArgs";
6 | import type {LanguageId} from "src/main";
7 | import type {CodeBlockArgs} from '../CodeBlockArgs';
8 |
9 | /**
10 | * Inject code and run code transformations on a source code block
11 | */
12 | export class CodeInjector {
13 | private readonly app: App;
14 | private readonly settings: ExecutorSettings;
15 | private readonly language: LanguageId;
16 |
17 | private prependSrcCode = "";
18 | private appendSrcCode = "";
19 | private namedImportSrcCode = "";
20 |
21 | private mainArgs: CodeBlockArgs = {};
22 |
23 | private namedExports: Record = {};
24 |
25 | /**
26 | * @param app The current app handle (this.app from ExecuteCodePlugin).
27 | * @param settings The current app settings.
28 | * @param language The language of the code block e.g. python, js, cpp.
29 | */
30 | constructor(app: App, settings: ExecutorSettings, language: LanguageId) {
31 | this.app = app;
32 | this.settings = settings;
33 | this.language = language;
34 | }
35 |
36 | /**
37 | * Takes the source code of a code block and adds all relevant pre-/post-blocks and global code injections.
38 | *
39 | * @param srcCode The source code of the code block.
40 | * @returns The source code of a code block with all relevant pre/post blocks and global code injections.
41 | */
42 | public async injectCode(srcCode: string) {
43 | const language = getLanguageAlias(this.language);
44 |
45 | // We need to get access to all code blocks on the page so we can grab the pre / post blocks above
46 | // Obsidian unloads code blocks not in view, so instead we load the raw document file and traverse line-by-line
47 | const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
48 | if (activeView === null)
49 | return srcCode;
50 |
51 | // Is await necessary here? Some object variables get changed in this call -> await probably necessary
52 | await this.parseFile(activeView.data, srcCode, language);
53 |
54 | const realLanguage = /[^-]*$/.exec(language)[0];
55 | const globalInject = this.settings[`${realLanguage}Inject` as keyof ExecutorSettings];
56 | let injectedCode = `${this.namedImportSrcCode}\n${srcCode}`;
57 | if (!this.mainArgs.ignore)
58 | injectedCode = `${globalInject}\n${this.prependSrcCode}\n${injectedCode}\n${this.appendSrcCode}`;
59 | else {
60 | // Handle single ignore
61 | if (!Array.isArray(this.mainArgs.ignore) && this.mainArgs.ignore !== "all")
62 | this.mainArgs.ignore = [this.mainArgs.ignore];
63 | if (this.mainArgs.ignore !== "all") {
64 | if (!this.mainArgs.ignore.contains("pre"))
65 | injectedCode = `${this.prependSrcCode}\n${injectedCode}`;
66 | if (!this.mainArgs.ignore.contains("post"))
67 | injectedCode = `${injectedCode}\n${this.appendSrcCode}`;
68 | if (!this.mainArgs.ignore.contains("global"))
69 | injectedCode = `${globalInject}\n${injectedCode}`;
70 | }
71 | }
72 | return transformMagicCommands(this.app, injectedCode);
73 | }
74 |
75 | /**
76 | * Handles adding named imports to code blocks
77 | *
78 | * @param namedImports Populate prependable source code with named imports
79 | * @returns If an error occurred
80 | */
81 | private async handleNamedImports(namedImports: CodeBlockArgs['import']) {
82 | const handleNamedImport = (namedImport: string) => {
83 | // Named export doesn't exist
84 | if (!this.namedExports.hasOwnProperty(namedImport)) {
85 | new Notice(`Named export "${namedImport}" does not exist but was imported`);
86 | return true;
87 | }
88 | this.namedImportSrcCode += `${this.disable_print(this.namedExports[namedImport])}\n`;
89 | return false;
90 | };
91 | // Single import
92 | if (!Array.isArray(namedImports))
93 | return handleNamedImport(namedImports);
94 | // Multiple imports
95 | for (const namedImport of namedImports) {
96 | const err = handleNamedImport(namedImport);
97 | if (err) return true;
98 | }
99 | return false;
100 | }
101 |
102 | /**
103 | * Parse a markdown file
104 | *
105 | * @param fileContents The contents of the file to parse
106 | * @param srcCode The original source code of the code block being run
107 | * @param language The programming language of the code block being run
108 | * @returns
109 | */
110 | private async parseFile(fileContents: string, srcCode: string, language: LanguageId) {
111 | let currentArgs: CodeBlockArgs = {};
112 | let insideCodeBlock = false;
113 | let isLanguageEqual = false;
114 | let currentLanguage = "";
115 | let currentCode = "";
116 | let currentFirstLine = "";
117 |
118 | for (const line of fileContents.split("\n")) {
119 | if (line.startsWith("```")) {
120 | // Reached end of code block
121 | if (insideCodeBlock) {
122 | // Stop traversal once we've reached the code block being run
123 | // Only do this for the original file the user is running
124 | const srcCodeTrimmed = srcCode.trim();
125 | const currentCodeTrimmed = currentCode.trim();
126 | if (isLanguageEqual && srcCodeTrimmed.length === currentCodeTrimmed.length && srcCodeTrimmed === currentCodeTrimmed) {
127 | this.mainArgs = getArgs(currentFirstLine);
128 | // Get named imports
129 | if (this.mainArgs.import) {
130 | const err = this.handleNamedImports(this.mainArgs.import);
131 | if (err) return "";
132 | }
133 | break;
134 | }
135 | // Named export
136 | if (currentArgs.label) {
137 | // Export already exists
138 | if (this.namedExports.hasOwnProperty(currentArgs.label)) {
139 | new Notice(`Error: named export ${currentArgs.label} exported more than once`);
140 | return "";
141 | }
142 | this.namedExports[currentArgs.label] = currentCode;
143 | }
144 | // Pre / post export
145 | if (!Array.isArray(currentArgs.export))
146 | currentArgs.export = [currentArgs.export];
147 | if (currentArgs.export.contains("pre"))
148 | this.prependSrcCode += `${this.disable_print(currentCode)}\n`;
149 | if (currentArgs.export.contains("post"))
150 | this.appendSrcCode += `${this.disable_print(currentCode)}\n`;
151 | currentLanguage = "";
152 | currentCode = "";
153 | insideCodeBlock = false;
154 | currentArgs = {};
155 | }
156 |
157 | // reached start of code block
158 | else {
159 | currentLanguage = getCodeBlockLanguage(line);
160 | // Don't check code blocks from a different language
161 | isLanguageEqual = /[^-]*$/.exec(language)[0] === /[^-]*$/.exec(currentLanguage)[0];
162 | if (isLanguageEqual) {
163 | currentArgs = getArgs(line);
164 | currentFirstLine = line;
165 | }
166 | insideCodeBlock = true;
167 | }
168 | } else if (insideCodeBlock && isLanguageEqual) {
169 | currentCode += `${line}\n`;
170 | }
171 | }
172 | }
173 |
174 | private disable_print(code: String): String {
175 | if (!this.settings.onlyCurrentBlock) {
176 | return code;
177 | }
178 | const pattern: RegExp = /^print\s*(.*)/gm;
179 | // 使用正则表达式替换函数将符合条件的内容注释掉
180 | return code.replace(pattern, ' ');
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/src/transforms/LatexFigureName.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import * as r from 'src/output/RegExpUtilities';
3 | import { ExecutorSettings } from 'src/settings/Settings';
4 |
5 | export const ILLEGAL_FILENAME_CHARS: RegExp = /[<>:"/\\|?*]+/g;
6 | export const WHITESPACE_AND_ILLEGAL_CHARS: RegExp = /[<>:"/\\|?*\s]+/;
7 | export const MAYBE_WHITESPACE_AND_ILLEGAL: RegExp = /[<>:"/\\|?*\s]*/;
8 | export const FIGURE_FILENAME_EXTENSIONS: RegExp = /(.pdf|.svg|.png)/;
9 | export const FILENAME_PREFIX: RegExp = /figure /;
10 | export const UNNAMED_PREFIX: RegExp = /temp /;
11 | export const TEMP_FIGURE_NAME: RegExp = /figure temp \d+/;
12 |
13 | let latexFilenameIndex = 0;
14 |
15 | export async function retrieveFigurePath(codeblockContent: string, titlePattern: string, srcFile: string, settings: ExecutorSettings): Promise {
16 | const vaultAbsolutePath = (this.app.vault.adapter as any).basePath;
17 | const vaultAttachmentPath = await this.app.fileManager.getAvailablePathForAttachment("test", srcFile);
18 | const vaultAttachmentDir = path.dirname(vaultAttachmentPath);
19 | const figureDir = path.join(vaultAbsolutePath, vaultAttachmentDir);
20 | let figureTitle = captureFigureTitle(codeblockContent, titlePattern);
21 | if (!figureTitle) {
22 | const index = nextLatexFilenameIndex(settings.latexMaxFigures);
23 | figureTitle = UNNAMED_PREFIX.source + index;
24 | }
25 | return path.join(figureDir, FILENAME_PREFIX.source + figureTitle);
26 | }
27 |
28 | function captureFigureTitle(codeblockContent: string, titlePattern: string): string | undefined {
29 | const pattern = r.parse(titlePattern);
30 | if (!pattern) return undefined;
31 | const match = codeblockContent.match(pattern);
32 | const title = match?.[1];
33 | if (!title) return undefined;
34 | return sanitizeFilename(title);
35 | }
36 |
37 | function sanitizeFilename(input: string): string {
38 | const trailingFilenames: RegExp = r.concat(FIGURE_FILENAME_EXTENSIONS, /$/);
39 | return input
40 | .replace(ILLEGAL_FILENAME_CHARS, ' ') // Remove illegal filename characters
41 | .replace(/\s+/g, ' ') // Normalize whitespace
42 | .trim()
43 | .replace(r.concat(/^/, FILENAME_PREFIX), '') // Remove prefix
44 | .replace(trailingFilenames, ''); // Remove file extension
45 | }
46 |
47 | export function generalizeFigureTitle(figureName: string): RegExp {
48 | const normalized: string = sanitizeFilename(figureName);
49 | const escaped: RegExp = r.escape(normalized);
50 | const whitespaced = new RegExp(escaped.source
51 | .replace(/\s+/g, WHITESPACE_AND_ILLEGAL_CHARS.source)); // Also allow illegal filename characters in whitespace
52 | return r.concat(
53 | MAYBE_WHITESPACE_AND_ILLEGAL,
54 | r.optional(FILENAME_PREFIX), // Optional prefix
55 | MAYBE_WHITESPACE_AND_ILLEGAL,
56 | whitespaced,
57 | MAYBE_WHITESPACE_AND_ILLEGAL,
58 | r.optional(FIGURE_FILENAME_EXTENSIONS), // Optional file extension
59 | MAYBE_WHITESPACE_AND_ILLEGAL);
60 | }
61 |
62 | function nextLatexFilenameIndex(maxIndex: number): number {
63 | latexFilenameIndex %= maxIndex;
64 | return latexFilenameIndex++;
65 | }
66 |
--------------------------------------------------------------------------------
/src/transforms/LatexFontHandler.ts:
--------------------------------------------------------------------------------
1 | import { Platform } from 'obsidian';
2 | import * as path from 'path';
3 | import { ExecutorSettings } from 'src/settings/Settings';
4 |
5 | let validFonts = new Set;
6 | let invalidFonts = new Set;
7 |
8 | interface FontNames {
9 | main: string;
10 | sans: string;
11 | mono: string;
12 | }
13 |
14 | /** Generates LaTeX font configuration based on system or Obsidian fonts. */
15 | export function addFontSpec(settings: ExecutorSettings): string {
16 | const isPdflatex = path.basename(settings.latexCompilerPath).toLowerCase().includes('pdflatex');
17 | if (isPdflatex || settings.latexAdaptFont === '') return '';
18 |
19 | const platformFonts = getPlatformFonts();
20 | const fontSpec = buildFontCommand(settings, platformFonts);
21 | if (!fontSpec) return '';
22 |
23 | const packageSrc = `\\usepackage{fontspec}\n`;
24 | return packageSrc + fontSpec;
25 | }
26 |
27 | /** Retrieves Obsidian's font settings from CSS variables. */
28 | function getObsidianFonts(cssVariable: string): string {
29 | const cssDeclarations = getComputedStyle(document.body);
30 | const fonts = cssDeclarations.getPropertyValue(cssVariable).split(`'??'`)[0];
31 | return sanitizeCommaList(fonts);
32 | }
33 |
34 | /** Constructs LaTeX font commands based on the provided settings and platform-specific fonts. */
35 | function buildFontCommand(settings: ExecutorSettings, fonts: FontNames): string {
36 | if (settings.latexAdaptFont === 'obsidian') {
37 | fonts.main = [getObsidianFonts('--font-text'), fonts.main].join(',');
38 | fonts.sans = [getObsidianFonts('--font-interface'), fonts.sans].join(',');
39 | fonts.mono = [getObsidianFonts('--font-monospace'), fonts.mono].join(',');
40 | }
41 | const mainSrc = buildSetfont('main', fonts.main);
42 | const sansSrc = buildSetfont('sans', fonts.sans);
43 | const monoSrc = buildSetfont('mono', fonts.mono);
44 | return mainSrc + sansSrc + monoSrc;
45 | }
46 |
47 | /** Returns default system fonts based on current platform */
48 | function getPlatformFonts(): FontNames {
49 | if (Platform.isWin) return { main: 'Segoe UI', sans: 'Segoe UI', mono: 'Consolas' };
50 | if (Platform.isMacOS) return { main: 'SF Pro', sans: 'SF Pro', mono: 'SF Mono' };
51 | if (Platform.isLinux) return { main: 'DejaVu Sans', sans: 'DejaVu Sans', mono: 'DejaVu Sans Mono' };
52 | return { main: '', sans: '', mono: '' };
53 | }
54 |
55 | /** Generates LuaLaTeX setfont command for specified font type. */
56 | function buildSetfont(type: 'main' | 'mono' | 'sans', fallbackList: string): string {
57 | const font = firstValidFont(fallbackList);
58 | return (font) ? `\\set${type}font{${font}}\n` : '';
59 | }
60 |
61 | function firstValidFont(fallbackList: string): string {
62 | return sanitizeCommaList(fallbackList)
63 | .split(', ')
64 | .reduce((result, font) => result || (cachedTestFont(font) ? font : undefined), undefined);
65 | }
66 |
67 | /** For performance, do not retest a font during the app's lifetime. */
68 | function cachedTestFont(fontName: string): boolean {
69 | if (validFonts.has(fontName)) return true;
70 | if (invalidFonts.has(fontName)) return false;
71 | if (!testFont(fontName)) {
72 | invalidFonts.add(fontName);
73 | return false;
74 | }
75 | validFonts.add(fontName);
76 | return true;
77 | }
78 |
79 | /** Tests if a font is available by comparing text measurements on canvas. */
80 | function testFont(fontName: string): boolean {
81 | const canvas = document.createElement('canvas');
82 | const context = canvas.getContext('2d');
83 | if (!context) return false;
84 |
85 | const text = 'abcdefghijklmnopqrstuvwxyz';
86 | context.font = `16px monospace`;
87 | const baselineWidth = context.measureText(text).width;
88 |
89 | context.font = `16px "${fontName}", monospace`;
90 | const testWidth = context.measureText(text).width;
91 |
92 | const isFontAvailable = baselineWidth !== testWidth;
93 | console.debug((isFontAvailable) ? `Font ${fontName} accepted.` : `Font ${fontName} ignored.`);
94 | return isFontAvailable;
95 | }
96 |
97 | /** Cleans and normalizes comma-separated font family lists */
98 | function sanitizeCommaList(commaList: string): string {
99 | return commaList
100 | .split(',')
101 | .map(font => font.trim().replace(/^["']|["']$/g, ''))
102 | .filter(Boolean)
103 | .join(', ');
104 | }
105 |
--------------------------------------------------------------------------------
/src/transforms/LatexTransformer.ts:
--------------------------------------------------------------------------------
1 | import { App } from 'obsidian';
2 | import { ExecutorSettings } from 'src/settings/Settings';
3 | import { addFontSpec } from './LatexFontHandler';
4 |
5 | export let appInstance: App;
6 | export let settingsInstance: ExecutorSettings;
7 |
8 | const DOCUMENT_CLASS: RegExp = /^[^%]*(?\\documentclass\s*(\[(?[^\]]*?)\])?\s*{\s*(?[^}]*?)\s*})/;
9 | interface DocumentClass {
10 | src: string,
11 | class: string,
12 | options: string,
13 | }
14 |
15 | export function modifyLatexCode(latexSrc: string, settings: ExecutorSettings): string {
16 | const documentClass: DocumentClass = captureDocumentClass(latexSrc)
17 | const injectSrc = ''
18 | + provideDocumentClass(documentClass?.class, settings.latexDocumentclass)
19 | + addFontSpec(settings)
20 | + disablePageNumberForCropping(settings);
21 | latexSrc = injectSrc + latexSrc;
22 | console.debug(`Injected LaTeX code:`, documentClass, injectSrc);
23 |
24 | latexSrc = moveDocumentClassToBeginning(latexSrc, documentClass);
25 | return latexSrc;
26 | }
27 |
28 | function disablePageNumberForCropping(settings: ExecutorSettings): string {
29 | return (settings.latexDoCrop && settings.latexCropNoPagenum)
30 | ? `\\pagestyle{empty}\n` : '';
31 | }
32 |
33 | function provideDocumentClass(currentClass: string, defaultClass: string): string {
34 | return (currentClass || defaultClass === "") ? ''
35 | : `\\documentclass{${defaultClass}}\n`;
36 | }
37 |
38 | function moveDocumentClassToBeginning(latexSrc: string, documentClass: DocumentClass): string {
39 | return (!documentClass?.src) ? latexSrc
40 | : documentClass.src + '\n' + latexSrc.replace(documentClass.src, '');
41 | }
42 |
43 | function captureDocumentClass(latexSrc: string): DocumentClass | undefined {
44 | const match: RegExpMatchArray = latexSrc.match(DOCUMENT_CLASS);
45 | if (!match) return undefined;
46 | return { src: match.groups?.src, class: match.groups?.class, options: match.groups?.options };
47 | }
48 |
49 | export function isStandaloneClass(latexSrc: string): boolean {
50 | const className = captureDocumentClass(latexSrc)?.class;
51 | return className === "standalone";
52 | }
53 |
54 | export function updateBodyClass(className: string, isActive: boolean) {
55 | if (isActive) {
56 | document.body.classList.add(className);
57 | } else {
58 | document.body.classList.remove(className);
59 | }
60 | }
61 |
62 | export function applyLatexBodyClasses(app: App, settings: ExecutorSettings) {
63 | updateBodyClass('center-latex-figures', settings.latexCenterFigures);
64 | updateBodyClass('invert-latex-figures', settings.latexInvertFigures);
65 | appInstance = app;
66 | settingsInstance = settings;
67 | }
68 |
--------------------------------------------------------------------------------
/src/transforms/Magic.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Adds functions that parse source code for magic commands and transpile them to the target language.
3 | *
4 | * List of Magic Commands:
5 | * - `@show(ImagePath)`: Displays an image at the given path in the note.
6 | * - `@show(ImagePath, Width, Height)`: Displays an image at the given path in the note.
7 | * - `@show(ImagePath, Width, Height, Alignment)`: Displays an image at the given path in the note.
8 | * - `@vault`: Inserts the vault path as string.
9 | * - `@note`: Inserts the note path as string.
10 | * - `@title`: Inserts the note title as string.
11 | * - `@theme`: Inserts the color theme; either `"light"` or `"dark"`. For use with images, inline plots, and `@html()`.
12 | */
13 |
14 | import * as os from "os";
15 | import { Platform } from 'obsidian';
16 | import { TOGGLE_HTML_SIGIL } from "src/output/Outputter";
17 | import { ExecutorSettings } from "src/settings/Settings";
18 |
19 | // Regex for all languages.
20 | const SHOW_REGEX = /@show\(["'](?[^<>?*=!\n#()\[\]{}]+)["'](,\s*(?\d+[\w%]+),?\s*(?\d+[\w%]+))?(,\s*(?left|center|right))?\)/g;
21 | const HTML_REGEX = /@html\((?[^)]+)\)/g;
22 | const VAULT_REGEX = /@vault/g
23 | const VAULT_PATH_REGEX = /@vault_path/g
24 | const VAULT_URL_REGEX = /@vault_url/g
25 | const CURRENT_NOTE_REGEX = /@note/g;
26 | const CURRENT_NOTE_PATH_REGEX = /@note_path/g;
27 | const CURRENT_NOTE_URL_REGEX = /@note_url/g;
28 | const NOTE_TITLE_REGEX = /@title/g;
29 | const NOTE_CONTENT_REGEX = /@content/g;
30 | const COLOR_THEME_REGEX = /@theme/g;
31 |
32 | // Regex that are only used by one language.
33 | const PYTHON_PLOT_REGEX = /^(plt|matplotlib.pyplot|pyplot)\.show\(\)/gm;
34 | const R_PLOT_REGEX = /^plot\(.*\)/gm;
35 | const OCTAVE_PLOT_REGEX = /^plot\s*\(.*\);/gm;
36 | const MAXIMA_PLOT_REGEX = /^plot2d\s*\(.*\[.+\]\)\s*[$;]/gm;
37 |
38 | /**
39 | * Parses the source code for the @vault command and replaces it with the vault path.
40 | *
41 | * @param source The source code to parse.
42 | * @param vaultPath The path of the vault.
43 | * @returns The transformed source code.
44 | */
45 | export function expandVaultPath(source: string, vaultPath: string): string {
46 | // Remove the leading slash (if it is there) and replace all backslashes with forward slashes.
47 | let vaultPathClean = vaultPath.replace(/\\/g, "/").replace(/^\//, "");
48 |
49 | source = source.replace(VAULT_PATH_REGEX, `"${vaultPath.replace(/\\/g, "/")}"`);
50 | source = source.replace(VAULT_URL_REGEX, `"${Platform.resourcePathPrefix + vaultPathClean}"`);
51 | source = source.replace(VAULT_REGEX, `"${Platform.resourcePathPrefix + vaultPathClean}"`);
52 |
53 | return source;
54 | }
55 |
56 |
57 | /**
58 | * Parses the source code for the @note command and replaces it with the note path.
59 | *
60 | * @param source The source code to parse.
61 | * @param notePath The path of the vault.
62 | * @returns The transformed source code.
63 | */
64 | export function expandNotePath(source: string, notePath: string): string {
65 | // Remove the leading slash (if it is there) and replace all backslashes with forward slashes.
66 | let notePathClean = notePath.replace(/\\/g, "/").replace(/^\//, "");
67 |
68 | source = source.replace(CURRENT_NOTE_PATH_REGEX, `"${notePath.replace(/\\/g, "/")}"`);
69 | source = source.replace(CURRENT_NOTE_URL_REGEX, `"${Platform.resourcePathPrefix + notePathClean}"`);
70 | source = source.replace(CURRENT_NOTE_REGEX, `"${Platform.resourcePathPrefix + notePathClean}"`);
71 |
72 | return source;
73 | }
74 |
75 |
76 | /**
77 | * Parses the source code for the @title command and replaces it with the vault path.
78 | *
79 | * @param source The source code to parse.
80 | * @param noteTitle The path of the vault.
81 | * @returns The transformed source code.
82 | */
83 | export function expandNoteTitle(source: string, noteTitle: string): string {
84 | let t = "";
85 | if (noteTitle.contains("."))
86 | t = noteTitle.split(".").slice(0, -1).join(".");
87 |
88 | return source.replace(NOTE_TITLE_REGEX, `"${t}"`);
89 | }
90 |
91 | /**
92 | * Parses the source code and replaces the NOTE_CONTENT_REGEX with the file content.
93 | *
94 | * @param source The source code to parse.
95 | * @param content The content of the note.
96 | * @returns The transformed source code.
97 | */
98 | export function insertNoteContent(source: string, content: string): string {
99 | const escaped_content = JSON.stringify(content)
100 | return source.replace(NOTE_CONTENT_REGEX, `${escaped_content}`)
101 | }
102 |
103 | /**
104 | * Parses the source code for the @theme command and replaces it with the colour theme.
105 | *
106 | * @param source The source code to parse.
107 | * @param noteTitle The current colour theme.
108 | * @returns The transformed source code.
109 | */
110 | export function expandColorTheme(source: string, theme: string): string {
111 | return source.replace(COLOR_THEME_REGEX, `"${theme}"`);
112 | }
113 |
114 | /**
115 | * Add the @show command to python. @show is only supported in python and javascript.
116 | *
117 | * @param source The source code to parse.
118 | * @returns The transformed source code.
119 | */
120 | export function expandPython(source: string, settings: ExecutorSettings): string {
121 | if (settings.pythonEmbedPlots) {
122 | source = expandPythonPlots(source, TOGGLE_HTML_SIGIL);
123 | }
124 | source = expandPythonShowImage(source);
125 | source = expandPythonHtmlMacro(source);
126 | return source;
127 | }
128 |
129 |
130 | /**
131 | * Add the @show command to javascript. @show is only supported in python and javascript.
132 | *
133 | * @param source The source code to parse.
134 | * @returns The transformed source code.
135 | */
136 | export function expandJS(source: string): string {
137 | source = expandJsShowImage(source);
138 | source = expandJsHtmlMacro(source);
139 | return source;
140 | }
141 |
142 |
143 | /**
144 | * Parses some python code and changes it to display plots in the note instead of opening a new window.
145 | * Only supports normal plots generated with the `plt.show(...)` function.
146 | *
147 | * @param source The source code to parse.
148 | * @param toggleHtmlSigil The meta-command to allow and disallow HTML
149 | * @returns The transformed source code.
150 | */
151 | export function expandPythonPlots(source: string, toggleHtmlSigil: string): string {
152 | 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()`;
153 | return source.replace(PYTHON_PLOT_REGEX, showPlot);
154 | }
155 |
156 |
157 | /**
158 | * Parses some R code and changes it to display plots in the note instead of opening a new window.
159 | * Only supports normal plots generated with the `plot(...)` function.
160 | *
161 | * @param source The source code to parse.
162 | * @returns The transformed source code.
163 | */
164 | export function expandRPlots(source: string): string {
165 | const matches = source.matchAll(R_PLOT_REGEX);
166 | for (const match of matches) {
167 | const tempFile = `${os.tmpdir()}/temp_${Date.now()}.png`.replace(/\\/g, "/").replace(/^\//, "");
168 | const substitute = `png("${tempFile}"); ${match[0]}; dev.off(); cat('${TOGGLE_HTML_SIGIL}
${TOGGLE_HTML_SIGIL}')`;
169 |
170 | source = source.replace(match[0], substitute);
171 | }
172 |
173 | return source;
174 | }
175 |
176 |
177 | /**
178 | * Parses the PYTHON code for the @show command and replaces it with the image.
179 | * @param source The source code to parse.
180 | */
181 | function expandPythonShowImage(source: string): string {
182 | const matches = source.matchAll(SHOW_REGEX);
183 | for (const match of matches) {
184 | const imagePath = match.groups.path;
185 | const width = match.groups.width;
186 | const height = match.groups.height;
187 | const alignment = match.groups.align;
188 |
189 | const image = expandShowImage(imagePath.replace(/\\/g, "\\\\"), width, height, alignment);
190 | source = source.replace(match[0], "print(\'" + TOGGLE_HTML_SIGIL + image + TOGGLE_HTML_SIGIL + "\')");
191 | }
192 |
193 | return source;
194 | }
195 |
196 | /**
197 | * Parses the PYTHON code for the @html command and surrounds it with the toggle-escaoe token.
198 | * @param source
199 | */
200 | function expandPythonHtmlMacro(source: string): string {
201 | const matches = source.matchAll(HTML_REGEX);
202 | for (const match of matches) {
203 | const html = match.groups.html;
204 |
205 | const toggle = JSON.stringify(TOGGLE_HTML_SIGIL);
206 |
207 | source = source.replace(match[0], `print(${toggle}); print(${html}); print(${toggle})`)
208 | }
209 | return source;
210 | }
211 |
212 |
213 | /**
214 | * Parses the JAVASCRIPT code for the @show command and replaces it with the image.
215 | * @param source The source code to parse.
216 | */
217 | function expandJsShowImage(source: string): string {
218 | const matches = source.matchAll(SHOW_REGEX);
219 | for (const match of matches) {
220 | const imagePath = match.groups.path;
221 | const width = match.groups.width;
222 | const height = match.groups.height;
223 | const alignment = match.groups.align;
224 |
225 | const image = expandShowImage(imagePath.replace(/\\/g, "\\\\").replace(/^\//, ""), width, height, alignment);
226 |
227 | source = source.replace(match[0], "console.log(\'" + TOGGLE_HTML_SIGIL + image + TOGGLE_HTML_SIGIL + "\')");
228 | console.log(source);
229 | }
230 |
231 | return source;
232 | }
233 |
234 | function expandJsHtmlMacro(source: string): string {
235 | const matches = source.matchAll(HTML_REGEX);
236 | for (const match of matches) {
237 | const html = match.groups.html;
238 |
239 | const toggle = JSON.stringify(TOGGLE_HTML_SIGIL);
240 |
241 | source = source.replace(match[0], `console.log(${toggle}); console.log(${html}); console.log(${toggle})`)
242 | }
243 | return source;
244 | }
245 |
246 |
247 | /**
248 | * Builds the image string that is used to display the image in the note based on the configurations for
249 | * height, width and alignment.
250 | *
251 | * @param imagePath The path to the image.
252 | * @param width The image width.
253 | * @param height The image height.
254 | * @param alignment The image alignment.
255 | */
256 | function expandShowImage(imagePath: string, width: string = "0", height: string = "0", alignment: string = "center"): string {
257 | if (imagePath.contains("+")) {
258 | let splittedPath = imagePath.replace(/['"]/g, "").split("+");
259 | splittedPath = splittedPath.map(element => element.trim())
260 | imagePath = splittedPath.join("");
261 | }
262 |
263 | if (width == "0" || height == "0")
264 | return `
`;
265 |
266 | return `
`;
267 | }
268 |
269 | export function expandOctavePlot(source: string): string {
270 | const matches = source.matchAll(OCTAVE_PLOT_REGEX);
271 | for (const match of matches) {
272 | const tempFile = `${os.tmpdir()}/temp_${Date.now()}.png`.replace(/\\/g, "/").replace(/^\//, "");
273 | const substitute = `${match[0]}; print -dpng ${tempFile}; disp('${TOGGLE_HTML_SIGIL}
${TOGGLE_HTML_SIGIL}');`;
274 |
275 | source = source.replace(match[0], substitute);
276 | }
277 |
278 | return source;
279 | }
280 |
281 | export function expandMaximaPlot(source: string): string {
282 | const matches = source.matchAll(MAXIMA_PLOT_REGEX);
283 | for (const match of matches) {
284 | const tempFile = `${os.tmpdir()}/temp_${Date.now()}.png`.replace(/\\/g, "/").replace(/^\//, "");
285 | const updated_plot_call = match[0].substring(0, match[0].lastIndexOf(')')) + `, [png_file, "${tempFile}"])`;
286 | const substitute = `${updated_plot_call}; print ('${TOGGLE_HTML_SIGIL}
${TOGGLE_HTML_SIGIL}');`;
287 |
288 | source = source.replace(match[0], substitute);
289 | }
290 |
291 | return source;
292 | }
293 |
294 |
--------------------------------------------------------------------------------
/src/transforms/TransformCode.ts:
--------------------------------------------------------------------------------
1 | import { expandColorTheme, expandNotePath, expandNoteTitle, expandVaultPath, insertNoteContent } from "./Magic";
2 | import { getVaultVariables } from "src/Vault";
3 | import { canonicalLanguages } from 'src/main';
4 | import type { App } from "obsidian";
5 | import type { LanguageId } from "src/main";
6 |
7 | /**
8 | * Transform a language name, to enable working with multiple language aliases, for example "js" and "javascript".
9 | *
10 | * @param language A language name or shortcut (e.g. 'js', 'python' or 'shell').
11 | * @returns The same language shortcut for every alias of the language.
12 | */
13 | export function getLanguageAlias(language: string | undefined): LanguageId | undefined {
14 | if (language === undefined) return undefined;
15 | switch(language) {
16 | case "javascript": return "js";
17 | case "typescript": return "ts";
18 | case "csharp": return "cs";
19 | case "bash": return "shell";
20 | case "py": return "python";
21 | case "wolfram": return "mathematica";
22 | case "nb": return "mathematica";
23 | case "wl": "mathematica";
24 | case "hs": return "haskell";
25 | }
26 | if ((canonicalLanguages as readonly string[]).includes(language))
27 | return language as LanguageId;
28 | return undefined;
29 | }
30 |
31 | /**
32 | * Perform magic on source code (parse the magic commands) to insert note path, title, vault path, etc.
33 | *
34 | * @param app The current app handle (this.app from ExecuteCodePlugin).
35 | * @param srcCode Code with magic commands.
36 | * @returns The input code with magic commands replaced.
37 | */
38 | export function transformMagicCommands(app: App, srcCode: string) {
39 | let ret = srcCode;
40 | const vars = getVaultVariables(app);
41 | if (vars) {
42 | ret = expandVaultPath(ret, vars.vaultPath);
43 | ret = expandNotePath(ret, vars.filePath);
44 | ret = expandNoteTitle(ret, vars.fileName);
45 | ret = expandColorTheme(ret, vars.theme);
46 | ret = insertNoteContent(ret, vars.fileContent);
47 | } else {
48 | console.warn(`Could not load all Vault variables! ${vars}`)
49 | }
50 | return ret;
51 | }
52 |
53 | /**
54 | * Extract the language from the first line of a code block.
55 | *
56 | * @param firstLineOfCode The first line of a code block that contains the language name.
57 | * @returns The language of the code block.
58 | */
59 | export function getCodeBlockLanguage(firstLineOfCode: string) {
60 | let currentLanguage: string = firstLineOfCode.split("```")[1].trim().split(" ")[0].split("{")[0];
61 | if (isStringNotEmpty(currentLanguage) && currentLanguage.startsWith("run-")) {
62 | currentLanguage = currentLanguage.replace("run-", "");
63 | }
64 | return getLanguageAlias(currentLanguage);
65 | }
66 |
67 | /**
68 | * Check if a string is not empty
69 | *
70 | * @param str Input string
71 | * @returns True when string not empty, False when the string is Empty
72 | */
73 | export function isStringNotEmpty(str: string): boolean {
74 | return !!str && str.trim().length > 0;
75 | }
76 |
--------------------------------------------------------------------------------
/src/transforms/windowsPathToWsl.ts:
--------------------------------------------------------------------------------
1 | import { join } from "path/posix";
2 | import { sep } from "path";
3 |
4 | export default (windowsPath: string) => {
5 | const driveLetter = windowsPath[0].toLowerCase();
6 | const posixyPath = windowsPath.replace(/^[^:]*:/, "") //remove drive letter
7 | .split(sep).join("/"); //force / as separator
8 |
9 | return join("/mnt/", driveLetter, posixyPath);
10 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./",
4 | "inlineSourceMap": true,
5 | "inlineSources": true,
6 | "module": "ESNext",
7 | "target": "ES2018",
8 | "allowJs": true,
9 | "noImplicitAny": true,
10 | "moduleResolution": "node",
11 | "importHelpers": true,
12 | "isolatedModules": true,
13 | "lib": [
14 | "DOM",
15 | "ES5",
16 | "ES6",
17 | "ES7",
18 | "ES2018"
19 | ],
20 | "types": ["obsidian-typings"]
21 | },
22 | "include": [
23 | "**/*.ts",
24 | "src/*ts"
25 | ]
26 | }
--------------------------------------------------------------------------------
/version-bump.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * This script updates the version in manifest.json, package-lock.json, versions.json and CHANGELOG.md
3 | * with the version specified in the package.json.
4 | */
5 |
6 | import {readFileSync, writeFileSync} from "fs";
7 |
8 | // READ TARGET VERSION FROM NPM package.json
9 | const targetVersion = process.env.npm_package_version;
10 |
11 | // read minAppVersion from manifest.json and bump version to target version
12 | let manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
13 | const {minAppVersion} = manifest;
14 | manifest.version = targetVersion;
15 | writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t"));
16 |
17 | let package_lock = JSON.parse(readFileSync("package-lock.json", "utf8"));
18 | package_lock.version = targetVersion;
19 | manifest.version = targetVersion;
20 | writeFileSync("package-lock.json", JSON.stringify(package_lock, null, "\t"));
21 |
22 | // update versions.json with target version and minAppVersion from manifest.json
23 | let versions = JSON.parse(readFileSync("versions.json", "utf8"));
24 | versions[targetVersion] = minAppVersion;
25 | writeFileSync("versions.json", JSON.stringify(versions, null, "\t"));
26 |
27 | // Update version in CHANGELOG
28 | const changelog = readFileSync("CHANGELOG.md", "utf8");
29 | const newChangelog = changelog.replace(/^## \[Unreleased\]/m, `## [${targetVersion}]`);
30 | writeFileSync("CHANGELOG.md", newChangelog);
31 |
32 | console.log(`Updated version to ${targetVersion} and minAppVersion to ${minAppVersion} in manifest.json, versions.json and CHANGELOG.md`);
33 |
--------------------------------------------------------------------------------
/versions.json:
--------------------------------------------------------------------------------
1 | {
2 | "0.1,0": "0.12.0",
3 | "0.2.0": "0.12.0",
4 | "0.3.0": "0.12.0",
5 | "0.3.1": "0.12.0",
6 | "0.3.2": "0.12.0",
7 | "0.4.0": "0.12.0",
8 | "0.5.0": "0.12.0",
9 | "0.5.1": "0.12.0",
10 | "0.5.2": "0.12.0",
11 | "0.5.3": "0.12.0",
12 | "0.6.0": "0.12.0",
13 | "0.7.0": "0.12.0",
14 | "0.8.0": "0.12.0",
15 | "0.8.1": "0.12.0",
16 | "0.9.0": "0.12.0",
17 | "0.9.1": "0.12.0",
18 | "0.9.2": "0.12.0",
19 | "0.10.0": "0.12.0",
20 | "0.11.0": "0.12.0",
21 | "0.12.0": "0.12.0",
22 | "0.12.1": "0.12.0",
23 | "0.13.0": "0.12.0",
24 | "0.14.0": "0.12.0",
25 | "0.15.0": "0.12.0",
26 | "0.15.1": "0.12.0",
27 | "0.15.2": "0.12.0",
28 | "0.16.0": "0.12.0",
29 | "0.17.0": "0.12.0",
30 | "0.18.0": "0.12.0",
31 | "1.0.0": "0.12.0",
32 | "1.1.0": "0.12.0",
33 | "1.1.1": "0.12.0",
34 | "1.2.0": "0.12.0",
35 | "1.3.0": "0.12.0",
36 | "1.4.0": "0.12.0",
37 | "1.5.0": "0.12.0",
38 | "1.6.0": "0.12.0",
39 | "1.6.1": "0.12.0",
40 | "1.6.2": "0.12.0",
41 | "1.7.0": "0.12.0",
42 | "1.7.1": "0.12.0",
43 | "1.8.0": "0.12.0",
44 | "1.8.1": "0.12.0",
45 | "1.9.0": "1.2.8",
46 | "1.9.1": "1.2.8",
47 | "1.10.0": "1.2.8",
48 | "1.11.0": "1.2.8",
49 | "1.11.1": "1.2.8",
50 | "1.12.0": "1.2.8",
51 | "2.0.0": "1.7.2",
52 | "2.1.0": "1.7.2",
53 | "2.1.1": "1.7.2",
54 | "undefined": "1.7.2",
55 | "2.1.2": "1.7.2"
56 | }
--------------------------------------------------------------------------------