mirror of
https://github.com/talwrii/plugin-repl.git
synced 2026-07-22 11:40:27 +00:00
Various. Add various convenience function. Run repl.md on startup
This commit is contained in:
parent
0d51a44c2f
commit
a47a09d9d0
3 changed files with 261 additions and 36 deletions
41
README.md
41
README.md
|
|
@ -1,7 +1,11 @@
|
|||
# Obsidian REPL plugin
|
||||
This plugin adds an emacs-like read evaluate print loop (REPL) to obsidian.
|
||||
This lets you execute javascript in a document *and* importantly interact with editor objects as though you were writing a plugin.
|
||||
This can be useful when developing plugins, or for "light-weight" scripting without having to develop a plugin.
|
||||
This lets you execute javascript in a document *and* importantly interact with Obsidian's
|
||||
plugin api to perform actions in Obsidian.
|
||||
|
||||
This can be useful when developing plugins, or for "light-weight" scripting without having to develop a full plugin yourself.
|
||||
|
||||
A range of convenience functions partly inspired my emacs is also provided.
|
||||
|
||||
# Using
|
||||
Install the plugin. I would advise binding CTRL-J to the commamd (Execute the current line or selection).
|
||||
|
|
@ -18,17 +22,50 @@ You might like to [refer to the plugin documentation](https://docs.obsidian.md/P
|
|||
## Convenience functions provided
|
||||
Various convenience functions are provided:
|
||||
|
||||
* open(f:string) - Open a file in the current frame
|
||||
* dir(o:Object) - List the property in an object
|
||||
* message(s:string) - Print a message
|
||||
* command(s:string) - Run a command
|
||||
* lineNumber - return the line number of the current line
|
||||
* plugin(s:string) - Get the object for a plugin
|
||||
* bufferString() - Return a string containing the entire text of the buffer
|
||||
* bufferString(start, end) - Return the string between these two cursor positions (editor.getCursor())
|
||||
* point() - Return the current cursor position
|
||||
* mark() - Return the cursor position at the beginning of the selection
|
||||
* insert(s:string) - Insert a string into the buffer
|
||||
* pointMin() - Return the minimum cursor in the buffer
|
||||
* pointMax() - Return the maximun cursor in the buffer
|
||||
* forwardChar(count?) - Move count (or one) character forward
|
||||
* writeToFile(name, string) - Overwrite the markdown file called name with the given string
|
||||
* appendToFile(name, string) - Append to the markdown file called `name` with the given string
|
||||
* selection() - Get the text contained in the selection
|
||||
|
||||
## Defining commands
|
||||
<a name="commands"></a>
|
||||
The function `makeNewCommand` will create a new command from a function. You can then
|
||||
define a hotkey to this command.
|
||||
|
||||
```js
|
||||
makeNewCommand(function new_command_name() {
|
||||
...
|
||||
})
|
||||
```
|
||||
|
||||
creates a command with the name "new command name" (and the id `new_command_name`).
|
||||
You can use all repl's extra functions and variables (`app`, `editor` etc) in this function.
|
||||
|
||||
If you want to test this funtion by hand you can do the following
|
||||
|
||||
```js
|
||||
var f = makeNewCommand(function new_command_name() {
|
||||
...
|
||||
})
|
||||
```
|
||||
|
||||
You can then call f() using eval to test the function.
|
||||
|
||||
# Running code at startup
|
||||
If you want code to run at startup, such as for [defining commands](#commands) then you can place this code in a special file called `repl.md`. If this file exists it is read when obsidian starts (or is reloaded) and the code in it is executed.
|
||||
|
||||
## Importing modules
|
||||
I experimented with the [obsidian modules](https://github.com/polyipseity/obsidian-modules) plugin but had issues importing full modules.
|
||||
|
|
|
|||
20
evalScope.js
20
evalScope.js
|
|
@ -1,17 +1,28 @@
|
|||
function evalInScope (s, context) {
|
||||
var result;
|
||||
let _result;
|
||||
context["_s"] = s
|
||||
with (makeScope(context)) {
|
||||
if (/^ *\{/.test(_s)) {
|
||||
// object literal
|
||||
result = eval("(" + _s + ")")
|
||||
_result = eval("(" + _s + ")")
|
||||
} else {
|
||||
result = eval(_s)
|
||||
_result = eval(_s)
|
||||
}
|
||||
return result
|
||||
|
||||
return _result
|
||||
}
|
||||
}
|
||||
|
||||
function runInScope(f, context) {
|
||||
let _result;
|
||||
context["f"] = f
|
||||
with (makeScope(context)) {
|
||||
_result = f()
|
||||
return _result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function makeScope (target) {
|
||||
return new Proxy(target, {
|
||||
has(target, prop) { return true; },
|
||||
|
|
@ -21,3 +32,4 @@ function makeScope (target) {
|
|||
|
||||
exports["evalInScope"] = evalInScope
|
||||
exports["makeScope"] = makeScope
|
||||
exports["runInScope"] = runInScope
|
||||
|
|
|
|||
236
main.ts
236
main.ts
|
|
@ -5,6 +5,11 @@ import * as evalScope from "./evalScope"
|
|||
|
||||
// Remember to rename these classes and interfaces!
|
||||
|
||||
type Cursor = {
|
||||
line: number
|
||||
ch: number
|
||||
}
|
||||
|
||||
interface MyPluginSettings {
|
||||
mySetting: string;
|
||||
}
|
||||
|
|
@ -13,6 +18,13 @@ const DEFAULT_SETTINGS: MyPluginSettings = {
|
|||
mySetting: 'default'
|
||||
}
|
||||
|
||||
function makeOpen(app: any) {
|
||||
function open(name: string) {
|
||||
app.workspace.openLinkText(name)
|
||||
}
|
||||
return open
|
||||
}
|
||||
|
||||
function makePlugin(app: any) {
|
||||
function plugin(name: string) {
|
||||
return app.plugins.plugins[name]
|
||||
|
|
@ -36,8 +48,14 @@ class Scope {
|
|||
eval(s: string) {
|
||||
return evalScope.evalInScope(s, this.context)
|
||||
}
|
||||
|
||||
run(f: any) {
|
||||
return evalScope.runInScope(f, this.context)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function dir(obj: any) {
|
||||
let p = [];
|
||||
for (; obj != null; obj = Object.getPrototypeOf(obj)) {
|
||||
|
|
@ -61,6 +79,60 @@ function makeCommand(app: any) {
|
|||
return command
|
||||
}
|
||||
|
||||
function makeLineAtPoint(editor: Editor) {
|
||||
function lineAtPoint() {
|
||||
return editor.getLine(editor.getCursor().line)
|
||||
}
|
||||
return lineAtPoint
|
||||
}
|
||||
|
||||
function makeSelection(editor: Editor) {
|
||||
function selection() {
|
||||
let selection = editor.getSelection()
|
||||
if (selection === "") {
|
||||
throw Error("No text is selected")
|
||||
}
|
||||
return selection
|
||||
}
|
||||
return selection
|
||||
}
|
||||
|
||||
|
||||
function makeReadFile(app: any) {
|
||||
const readFile = function(name: string) {
|
||||
name = name + ".md"
|
||||
const file = app.vault.getAbstractFileByPath(name)
|
||||
return app.vault.read(file)
|
||||
}
|
||||
return readFile
|
||||
}
|
||||
|
||||
|
||||
function makeWriteToFile(app: any) {
|
||||
const writeToFile = function(name: string, text: string) {
|
||||
name = name + ".md"
|
||||
const file = app.vault.getAbstractFileByPath(name)
|
||||
return app.vault.modify(file, text)
|
||||
}
|
||||
return writeToFile
|
||||
}
|
||||
|
||||
function makeAppendToFile(app: any) {
|
||||
const writeToFile = makeWriteToFile(app)
|
||||
const readFile = makeReadFile(app)
|
||||
async function appendToFile(name: string, appended: string) {
|
||||
let existing = await readFile(name)
|
||||
if (existing === undefined) {
|
||||
existing = ""
|
||||
}
|
||||
const content = existing + appended
|
||||
await writeToFile(name, content)
|
||||
}
|
||||
return appendToFile
|
||||
}
|
||||
|
||||
|
||||
|
||||
function makeForwardChar(editor: Editor): (count: number | undefined) => void {
|
||||
function forwardChar(count: number | undefined): void {
|
||||
if (count === undefined) {
|
||||
|
|
@ -92,17 +164,33 @@ function makePointMax(editor: Editor): () => EditorPosition {
|
|||
return pointMax
|
||||
}
|
||||
|
||||
function pointMin() {
|
||||
function pointMin(): Cursor {
|
||||
return { line: 0, ch: 0 }
|
||||
}
|
||||
|
||||
function makeBufferString(editor: Editor): () => string {
|
||||
function bufferString() {
|
||||
return editor.getRange(pointMin(), makePointMax(editor)())
|
||||
function bufferString(start?: Cursor, end?: Cursor) {
|
||||
return editor.getRange(start || pointMin(), end || makePointMax(editor)())
|
||||
}
|
||||
return bufferString
|
||||
}
|
||||
|
||||
|
||||
function makePoint(editor: Editor) {
|
||||
function point(): Cursor {
|
||||
return editor.getCursor("to")
|
||||
}
|
||||
return point
|
||||
}
|
||||
|
||||
|
||||
function makeMark(editor: Editor) {
|
||||
function mark() {
|
||||
return editor.getCursor("from")
|
||||
}
|
||||
return mark
|
||||
}
|
||||
|
||||
function makeSaveExcursion(editor: Editor) {
|
||||
function saveExcursion(f: () => any): void {
|
||||
const point = editor.getCursor()
|
||||
|
|
@ -151,36 +239,84 @@ const makeEndOfLine = (editor: any) => {
|
|||
editor.setCursor(cursor.line, line.length)
|
||||
}
|
||||
}
|
||||
|
||||
export default class ReplPlugin extends Plugin {
|
||||
settings: MyPluginSettings;
|
||||
scope: Scope
|
||||
history: History
|
||||
initLoaded: boolean = false
|
||||
|
||||
updateScopeApp() {
|
||||
this.scope.add("repl", this)
|
||||
this.scope.add("dir", dir)
|
||||
this.scope.add("newCommand", this.makeNewCommand())
|
||||
this.scope.add("source", this.makeSource(this.app))
|
||||
this.scope.add("plugin", makePlugin(this.app))
|
||||
this.scope.add("command", makeCommand(this.app))
|
||||
this.scope.add("readFile", makeReadFile(this.app))
|
||||
this.scope.add("writeToFile", makeWriteToFile(this.app))
|
||||
this.scope.add("appendToFile", makeAppendToFile(this.app))
|
||||
this.scope.add("app", this.app)
|
||||
this.scope.add("message", message)
|
||||
this.scope.add("workspace", this.app.workspace)
|
||||
this.scope.add("open", makeOpen(this.app))
|
||||
}
|
||||
|
||||
updateScopeEditor(editor: Editor, view: MarkdownView) {
|
||||
this.scope.add("endOfLine", makeEndOfLine(editor))
|
||||
this.scope.add("saveExcursion", makeSaveExcursion(editor))
|
||||
this.scope.add("lineNumber", makeLineNumber(editor))
|
||||
this.scope.add("lineAtPoint", makeLineAtPoint(editor))
|
||||
this.scope.add("bufferString", makeBufferString(editor))
|
||||
this.scope.add("point", makePoint(editor))
|
||||
this.scope.add("mark", makeMark(editor))
|
||||
this.scope.add("insert", makeInsert(editor))
|
||||
this.scope.add("pointMin", pointMin)
|
||||
this.scope.add("pointMax", makePointMax(editor))
|
||||
this.scope.add("selection", makeSelection(editor))
|
||||
this.scope.add("forwardChar", makeForwardChar(editor))
|
||||
this.scope.add("editor", editor)
|
||||
this.scope.add("view", view)
|
||||
}
|
||||
|
||||
|
||||
makeNewCommand() {
|
||||
let plugin = this
|
||||
function newCommand(f: any) {
|
||||
plugin.addCommand({
|
||||
id: f.name,
|
||||
name: f.name.replace("_", " "),
|
||||
editorCallback: (editor: Editor, view: MarkdownView) => {
|
||||
plugin.updateScopeApp()
|
||||
plugin.updateScopeEditor(editor, view)
|
||||
plugin.scope.run(f)
|
||||
}
|
||||
});
|
||||
return f
|
||||
}
|
||||
return newCommand
|
||||
}
|
||||
|
||||
runCommand(editor: Editor, view: MarkdownView, region: string) {
|
||||
let output: string = "";
|
||||
|
||||
const endOfLine = makeEndOfLine(editor)
|
||||
this.updateScopeApp()
|
||||
this.updateScopeEditor(editor, view)
|
||||
|
||||
this.scope.add("saveExcursion", makeSaveExcursion(editor))
|
||||
|
||||
this.scope.add("lineNumber", makeLineNumber(editor))
|
||||
this.scope.add("command", makeCommand(this.app))
|
||||
this.scope.add("plugin", makePlugin(this.app))
|
||||
this.scope.add("bufferString", makeBufferString(editor))
|
||||
this.scope.add("insert", makeInsert(editor))
|
||||
this.scope.add("pointMin", pointMin)
|
||||
this.scope.add("pointMax", makePointMax(editor))
|
||||
this.scope.add("forwardChar", makeForwardChar(editor))
|
||||
this.scope.add("message", message)
|
||||
this.scope.add("repl", this)
|
||||
this.scope.add("workspace", this.app.workspace)
|
||||
this.scope.add("app", this.app)
|
||||
this.scope.add("editor", editor)
|
||||
this.scope.add("view", view)
|
||||
this.scope.add("endOfLine", endOfLine)
|
||||
this.scope.add("dir", dir)
|
||||
try {
|
||||
output = formatObj(this.scope.eval(region))
|
||||
let result = this.scope.eval(region)
|
||||
|
||||
const scope = this.scope
|
||||
|
||||
if (result instanceof Promise) {
|
||||
result.then((x) => {
|
||||
scope.add("_", x)
|
||||
}).catch((e) => {
|
||||
scope.add("_error", e)
|
||||
})
|
||||
}
|
||||
|
||||
output = formatObj(result)
|
||||
} catch (e) {
|
||||
new Notice(e.toString())
|
||||
throw e
|
||||
|
|
@ -191,21 +327,61 @@ export default class ReplPlugin extends Plugin {
|
|||
async onload() {
|
||||
this.scope = new Scope()
|
||||
this.history = new History()
|
||||
this.addCommands()
|
||||
|
||||
this.initLoaded = false
|
||||
let plugin = this
|
||||
this.app.workspace.on('active-leaf-change', () => { plugin.loadInit() })
|
||||
}
|
||||
|
||||
makeSource(app: any) {
|
||||
const plugin = this
|
||||
async function source(name: string) {
|
||||
plugin.updateScopeApp()
|
||||
let path = name + ".md"
|
||||
const file: string = await app.vault.getAbstractFileByPath(path)
|
||||
const contents = await app.vault.read(file)
|
||||
plugin.scope.eval(contents)
|
||||
}
|
||||
return source
|
||||
}
|
||||
|
||||
async loadInit() {
|
||||
if (!this.initLoaded) {
|
||||
let exists = await this.app.vault.exists("repl.md")
|
||||
let source = this.makeSource(this.app)
|
||||
if (exists) {
|
||||
source("repl")
|
||||
}
|
||||
this.initLoaded = true
|
||||
}
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
//if (await this.app.vault.exists("repl.md")) {
|
||||
// await source("repl")
|
||||
//}
|
||||
|
||||
|
||||
addCommands() {
|
||||
this.addCommand({
|
||||
id: 'repl-enter',
|
||||
name: 'Execute the current line or selection',
|
||||
editorCallback: (editor: Editor, view: MarkdownView) => {
|
||||
const cursor = editor.getCursor()
|
||||
const region = editor.getSelection() || editor.getLine(cursor.line)
|
||||
try {
|
||||
const cursor = editor.getCursor()
|
||||
const region = editor.getSelection() || editor.getLine(cursor.line)
|
||||
|
||||
const endOfLine = makeEndOfLine(editor)
|
||||
const endOfLine = makeEndOfLine(editor)
|
||||
|
||||
const output = this.runCommand(editor, view, region)
|
||||
const output = this.runCommand(editor, view, region)
|
||||
|
||||
editor.setCursor(cursor.line + 1, 0)
|
||||
editor.replaceRange(output + "\n", editor.getCursor())
|
||||
endOfLine()
|
||||
editor.setCursor(cursor.line + 1, 0)
|
||||
editor.replaceRange(output + "\n", editor.getCursor())
|
||||
endOfLine()
|
||||
} catch (e) {
|
||||
message(e.message)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue