From a101c2aff98e1b4e372168393502dd3bf011e80c Mon Sep 17 00:00:00 2001 From: Tal Wrii Date: Tue, 21 Jan 2025 11:46:25 +0100 Subject: [PATCH] various - add jump method. Change selection behaviour --- README.md | 21 +++++++++++++++++ main.ts | 70 ++++++++++++++++++++++++++++++++++++++----------------- popup.ts | 51 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 21 deletions(-) create mode 100644 popup.ts diff --git a/README.md b/README.md index 61c6a37..2ce0180 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ Various convenience functions are provided: * `mark()` - Return the cursor position at the beginning of the selection * `pointMin()` - Return the minimum cursor in the buffer * `pointMax()` - Return the maximun cursor in the buffer +* `jump(p: CursorPoint)` - Jump to this point * `forwardChar(count?: number)` - Move count (or one) character forward * `selection()` - Get the text contained in the selection @@ -165,6 +166,26 @@ dv = getDv() dv.pages().filter((x) => x.file.path == "templates/daily.md")[0].file.lists[0] ``` +## Templater support +The [Templater](https://github.com/SilentVoid13/Templater) provides functionality to insert javascript. If you have installed Templater you can use +the async `templater_expand` function to expand template strings. + +This creates a command that inserts a files tags using a template. +``` +newCommand(async function templater_example() { + insert(await templater_expand("The files tags: <% tp.file.tags %>")) +}) +``` + +If you want to expand a template for a file you can use `readFile` + +``` +newCommand(async function templater_from_file() { + insert(await templater_expand(await readFile("myTemplate"))) +}) +``` + + ## Importing modules Modules in Obsidian work in an interesting way that makes installing from NPM a little tricky. There is a [technical explanation here](https://github.com/talwrii/plugin-repl-imports#technical). diff --git a/main.ts b/main.ts index c94587f..d21f857 100644 --- a/main.ts +++ b/main.ts @@ -3,8 +3,8 @@ import { MarkdownPostProcessorContext, Command } from 'obsidian'; -import { execFileSync } from 'child_process' -import { parse as shellParse } from 'shell-quote'; +import { execFileSync, execSync } from 'child_process' +import { parse as shellParse, quote as shellQuote } from 'shell-quote'; import { expandRegionWithRegexp } from './editorUtils' @@ -22,6 +22,7 @@ import { promptString } from './prompt' import { promptCommand } from './promptCommand' import { popup } from './popup' import { openSetting } from './settings' +import { templater_expand } from './templater' export default class ReplPlugin extends Plugin { @@ -102,10 +103,15 @@ export default class ReplPlugin extends Plugin { } updateScopeApp() { - let path = this.app.workspace.getLeaf().view.path - this.scope.add("repl", this) // @ts-ignore path does exist + let path = this.app.workspace.getLeaf().view.path + // @ts-ignore + let frontmatter = this.app.metadataCache.getFileCache(this.app.workspace.getLeaf().view.file)["frontmatter"] + + this.scope.add("repl", this) this.scope.add("path", path) + + this.scope.add("frontmatter", frontmatter) //@ts-ignore let vaultPath = this.app.vault.adapter.basePath this.scope.add("vaultPath", vaultPath) @@ -153,7 +159,7 @@ export default class ReplPlugin extends Plugin { ) this.addToScopeWithDoc( "runProc", runProc, - "(s: string) or (['prog', 'arg1', 'args2']) run a program and return its output" + "(s: string) or (['prog', 'arg1', 'args2']) run a program and return its output. To send " ) this.addToScopeWithDoc( "newCommand", this.makeNewCommand(), @@ -194,11 +200,6 @@ export default class ReplPlugin extends Plugin { "(title: string, content: string) Add the content to the note with a titel" ) - this.addToScopeWithDoc( - "getDv", getDv.bind(null, this.app), - "Get hold of the dataview object for querying pages" - - ) this.addToScopeWithDoc( "message", message, "(msg: string) Popup a message with a notification message msg" @@ -216,6 +217,17 @@ export default class ReplPlugin extends Plugin { "openUrl", openUrl, "(url: string) Open this url in a browser " ) + + + this.addToScopeWithDoc( + "getDv", getDv.bind(null, this.app), + "Get hold of the dataview object for querying pages" + ) + this.addToScopeWithDoc( + "templater_expand", templater_expand.bind(null, this.app), + "If templater is installed, expand the templater string and return the result." + ) + } updateScopeEditor(editor: Editor, view: MarkdownView) { @@ -268,6 +280,10 @@ export default class ReplPlugin extends Plugin { "pointMax", pointMax.bind(null, editor), "Returns the cursor positoin at the beginning of the note" ) + this.addToScopeWithDoc( + "jump", jump.bind(null, editor), + "Jump to the given point." + ) this.addToScopeWithDoc( "selection", selection.bind(null, editor), "Returns the text of the selection." @@ -290,13 +306,18 @@ export default class ReplPlugin extends Plugin { makeNewCommand() { const plugin = this function newCommand(f: any) { + let commandName = f.name.replaceAll("_", " ") plugin.addCommand({ id: f.name, - name: f.name.replaceAll("_", " "), + name: commandName, editorCallback: (editor: Editor, view: MarkdownView) => { plugin.updateScopeApp() plugin.updateScopeEditor(editor, view) - plugin.scope.run(f) + try { + plugin.scope.run(f) + } catch (e) { + message(`${commandName} failed: ${e.message}`) + } } }); return f @@ -361,21 +382,27 @@ export default class ReplPlugin extends Plugin { } } -function openFile(app: any, name: string) { - app.workspace.openLinkText(name) +async function openFile(app: any, name: string) { + await app.workspace.openLinkText(name) } function plugin(app: any, name: string) { return app.plugins.plugins[name] } -function runProc(commandAndArgs: string | Array): string { +function runProc(commandAndArgs: string | Array, input?: string): string { if (typeof commandAndArgs === "string") { const command = shellParse(commandAndArgs) as string[] - return runProc(command) + return runProc(command, input) + } + + if (input == undefined) { + const [command, ...args] = commandAndArgs + return execFileSync(command, args).toString() + } else { + let quoted = shellQuote(commandAndArgs) + return execSync(quoted, { input: input }).toString() } - const [command, ...args] = commandAndArgs - return execFileSync(command, args).toString() } function dir(obj: any) { @@ -407,9 +434,6 @@ function lineAtPoint(editor: Editor) { function selection(editor: Editor) { const selection = editor.getSelection() - if (selection === "") { - throw Error("No text is selected") - } return selection } @@ -493,6 +517,10 @@ function pointMax(editor: Editor) { return { line: lastLine, ch: lastChar } } +function jump(editor: Editor, position: EditorPosition) { + return editor.setCursor(position) +} + function pointMin(): EditorPosition { return { line: 0, ch: 0 } } diff --git a/popup.ts b/popup.ts new file mode 100644 index 0000000..9cdb166 --- /dev/null +++ b/popup.ts @@ -0,0 +1,51 @@ +import { App, Editor, Modal, Setting } from 'obsidian' + +export function popup(app: App, editor: Editor, message: string): Promise { + const position = editor.getCursor() + return new Promise((resolve, reject) => { + try { + new Popup(app, message, resolve).open() + editor.setCursor(position) + } catch (e) { + reject(e) + } + }) +} + +export class Popup extends Modal { + constructor(app: App, msg: string, resolve: () => void) { + super(app); + + const el = new DocumentFragment() + const pre = el.createEl("pre") + pre.appendText(msg) + + this.setContent(el) + new Setting(this.contentEl).addButton((btn) => { + btn.setButtonText("OK") + + const popup = this + + let keyDown = false; + function done() { + popup.close() + resolve() + return true + } + + btn.buttonEl.addEventListener("keydown", (_) => { + keyDown = true + }) + + btn.buttonEl.addEventListener('click', (_) => { + return done() + }) + + btn.buttonEl.addEventListener("keyup", (_) => { + if (keyDown) { + return done() + } + }) + }) + } +}