Version 0.6.0

This commit is contained in:
Erez Shermer 2022-04-03 21:28:17 +03:00
parent 9937ecf51b
commit 50e18decbd
5 changed files with 182 additions and 13 deletions

61
JsSnippets.md Normal file
View file

@ -0,0 +1,61 @@
# Useful JS Snippets for `jscommand` and `jsfile`
In this document I will collect some of my and user-contributed ideas for how to utilize JS commands in the Vimrc plugin.
If you have interesting snippets, please contribute by opening a pull request!
## Jump to Next/Prev Markdown Header
To map `]]` and `[[` to next/prev markdown header, I use the following.
In a file I call `mdHelpers.js`, put this:
```js
// Taken from https://stackoverflow.com/questions/273789/is-there-a-version-of-javascripts-string-indexof-that-allows-for-regular-expr
function regexIndexOf(string, regex, startpos) {
var indexOf = string.substring(startpos || 0).search(regex);
return (indexOf >= 0) ? (indexOf + (startpos || 0)) : indexOf;
}
function regexLastIndexOf(string, regex, startpos) {
regex = (regex.global) ? regex : new RegExp(regex.source, "g" + (regex.ignoreCase ? "i" : "") + (regex.multiLine ? "m" : ""));
if(typeof (startpos) == "undefined") {
startpos = string.length;
} else if(startpos < 0) {
startpos = 0;
}
var stringToWorkWith = string.substring(0, startpos + 1);
var lastIndexOf = -1;
var nextStop = 0;
while((result = regex.exec(stringToWorkWith)) != null) {
lastIndexOf = result.index;
regex.lastIndex = ++nextStop;
}
return lastIndexOf;
}
function jumpHeading(isForward) {
const editor = view.editor;
let posToSearchFrom = editor.getCursor();
posToSearchFrom.line += isForward ? 1 : -1;
const cursorOffset = editor.posToOffset(posToSearchFrom);
const lookupToUse = isForward ? regexIndexOf : regexLastIndexOf;
let headingOffset = lookupToUse(editor.getValue(), /^#(#*) /gm, cursorOffset);
// If not found from the cursor position, try again from the document beginning (or reverse beginning)
if (headingOffset === -1)
headingOffset = lookupToUse(editor.getValue(), /^#(#*) /gm);
const newPos = editor.offsetToPos(headingOffset);
editor.setCursor(newPos);
}
```
Then in your `.obsidian.vimrc` file add the following:
```
exmap nextHeading jsfile mdHelpers.js {jumpHeading(true)}
exmap prevHeading jsfile mdHelpers.js {jumpHeading(false)}
nmap ]] :nextHeading
nmap [[ :prevHeading
```

View file

@ -51,6 +51,7 @@ In addition to that:
- `cmcommand` - execute arbitrary CodeMirror commands, see details below.
- `surround` - surround your selected text in visual mode or word in normal mode with text.
- `pasteinto` - paste your current clipboard into your selected text in visual mode or word in normal mode. Useful for creating hyperlinks.
- `jscommand` and `jsfile` - extend Vim mode using JavaScript snippets.
Commands that fail don't generate any visible error for now.
@ -138,8 +139,6 @@ The full list of CodeMirror commands is available [here](https://codemirror.net/
## Surround Text with `surround`
**Note:** this is currently unsupported for the new (CM6-based) editor.
The plugin defines a custom Ex command named `surround` to surround either your currently selected text in visual mode or the word your cursor is over in normal mode with text.
This is particularly useful for creating wikilinks in Obsidian `[[WikiLink]]`.
@ -160,8 +159,6 @@ map [[ :wiki
## Inserting Links/Hyperlinks with `pasteinto`
**Note:** this is currently unsupported for the new (CM6-based) editor.
The plugin defines a custom Ex command named `pasteinto` to paste text into your currently selected text in visual mode, or the word your cursor is over in normal mode.
This is particularly useful for creating links/hyperlinks `[selected-text](paste)`.
@ -197,8 +194,62 @@ When you enter insert mode, you will type in your actual current system layout/l
Relative line numbers work very nicely with [this](https://github.com/nadavspi/obsidian-relative-line-numbers) Obsidian plugin (thank you @piotryordanov for bringing it to my attention!)
## Extending Vim Commands with JavaScript Snippets
The plugin allows to define Vim commands that map to JavaScript snippets, which adds exciting new possibilities to what you can achieve with Vim key bindings. **But -- this comes with a price of a security risk, and is therefore disabled by default.**
> :warning: **Security Warning**
>
> Before using this feature, you **must be sure** that you understand its potential security implications.
>
> Running JavaScript snippets with Vim commands stored in your vault means that anyone who gains access to
> your notes can run arbitrary code inside your Obsidian app.
If you understand the risks and choose to use this feature, turn on "Support JS Commands" in the plugin settings and continue reading.
### JavaScript Command Usage
There are two ways to define JS-based commands.
**The `jscommand` Ex command** defines a JS function that has an `editor: Editor` and `view: MarkdownView` arguments (see the [Obsidian API](https://github.com/obsidianmd/obsidian-api/blob/master/obsidian.d.ts) if you're not sure what these are).
You define only the syntax of the function, in a single line wrapped by curly braces, e.g.:
```
jscommand { console.log(editor.getCursor()); }
```
This will immediately log your current cursor position to the developer console.
If you want, you can make this an Ex command using `exmap`:
```
exmap logCursor jscommand { console.log(editor.getCursor()); }
nmap <C-q> :logCursor
```
Another version of the same functionality is **the `jsfile` Ex command**, which executes code from a file you give as a parameter, then appends another optional piece of code to it (e.g. in case you want to store several helper methods in a file and launch different ones as part of different commands).
As above, the code running as part of `jsfile` has `editor: Editor` and `view: MarkdownView` arguments.
Here's an example from my own `.obsidian.vimrc` that maps `]]` and `[[` to jump to the next/previous Markdown header:
```
exmap nextHeading jsfile mdHelpers.js {jumpHeading(true)}
exmap prevHeading jsfile mdHelpers.js {jumpHeading(false)}
nmap ]] :nextHeading
nmap [[ :prevHeading
```
See [here](JsSnippets.md) for the full example, and please contribute your own!
## Changelog
### 0.6.0
- The `surround` and `pasteinto` commands now work with the new (CM6-based) editor.
- Made the existence of the Vimrc file not required for other plugin features to work (https://github.com/esm7/obsidian-vimrc-support/issues/89)
- New exciting, but dangerous, `jscommand` and `jsfile` commands, that allow extending the plugin with JavaScript snippets.
### 0.5.2
- Fixed wrong detection of the editor (legacy vs new) on some occasions, leading to the plugin not really working in these situations.

71
main.ts
View file

@ -8,7 +8,8 @@ interface Settings {
displayChord: boolean,
displayVimMode: boolean,
fixedNormalModeLayout: boolean,
capturedKeyboardMap: Record<string, string>
capturedKeyboardMap: Record<string, string>,
supportJsCommands?: boolean
}
const DEFAULT_SETTINGS: Settings = {
@ -16,7 +17,8 @@ const DEFAULT_SETTINGS: Settings = {
displayChord: false,
displayVimMode: false,
fixedNormalModeLayout: false,
capturedKeyboardMap: {}
capturedKeyboardMap: {},
supportJsCommands: false
}
const enum vimStatus {
@ -111,9 +113,13 @@ export default class VimrcPlugin extends Plugin {
if (!this.initialized)
await this.initialize();
const VIMRC_FILE_NAME = this.settings.vimrcFileName;
this.app.vault.adapter.read(VIMRC_FILE_NAME).
then((lines) => this.readVimInit(lines)).
catch(error => { console.log('Error loading vimrc file', VIMRC_FILE_NAME, 'from the vault root', error) });
let vimrcContent = '';
try {
vimrcContent = await this.app.vault.adapter.read(VIMRC_FILE_NAME);
} catch (e) {
console.log('Error loading vimrc file', VIMRC_FILE_NAME, 'from the vault root', e.message)
}
this.readVimInit(vimrcContent);
});
}
@ -174,6 +180,8 @@ export default class VimrcPlugin extends Plugin {
this.defineObCommand(this.codeMirrorVimObject);
this.defineCmCommand(this.codeMirrorVimObject);
this.defineSurround(this.codeMirrorVimObject);
this.defineJsCommand(this.codeMirrorVimObject);
this.defineJsFile(this.codeMirrorVimObject);
// Record the position of selections
CodeMirror.on(cmEditor, "cursorActivity", async (cm: any) => {
@ -344,8 +352,6 @@ export default class VimrcPlugin extends Plugin {
defineSurround(vimObject: any) {
// Function to surround selected text or highlighted word.
var surroundFunc = (params: string[]) => {
if (this.editorMode === 'cm6')
throw new Error("surround is not yet supported in the new editor. To be added soon.");
var editor = this.getActiveView().editor;
if (!params.length) {
throw new Error("surround requires exactly 2 parameters: prefix and postfix text.");
@ -512,6 +518,46 @@ export default class VimrcPlugin extends Plugin {
}
});
}
defineJsCommand(vimObject: any) {
vimObject.defineEx('jscommand', '', (cm: any, params: any) => {
if (!this.settings.supportJsCommands)
throw new Error("JS commands are turned off; enable them via the Vimrc plugin configuration if you're sure you know what you're doing");
const jsCode = params.argString.trim() as string;
if (jsCode[0] != '{' || jsCode[jsCode.length - 1] != '}')
throw new Error("Expected an argument which is JS code surrounded by curly brackets: {...}");
const command = Function('editor', 'view', jsCode);
const view = this.getActiveView();
command(view.editor, view);
});
}
defineJsFile(vimObject: any) {
vimObject.defineEx('jsfile', '', async (cm: any, params: any) => {
if (!this.settings.supportJsCommands)
throw new Error("JS commands are turned off; enable them via the Vimrc plugin configuration if you're sure you know what you're doing");
if (params?.args?.length < 1)
throw new Error("Expected format: fileName {extraCode}");
let extraCode = '';
const fileName = params.args[0];
if (params.args.length > 1) {
params.args.shift();
extraCode = params.args.join(' ').trim() as string;
if (extraCode[0] != '{' || extraCode[extraCode.length - 1] != '}')
throw new Error("Expected an extra code argument which is JS code surrounded by curly brackets: {...}");
}
let content = '';
try {
content = await this.app.vault.adapter.read(fileName);
} catch (e) {
throw new Error(`Cannot read file ${params.args[0]} from vault root: ${e.message}`);
}
const command = Function('editor', 'view', content + extraCode);
const view = this.getActiveView();
command(view.editor, view);
});
}
}
class SettingsTab extends PluginSettingTab {
@ -582,5 +628,16 @@ class SettingsTab extends PluginSettingTab {
this.plugin.saveSettings();
});
})
new Setting(containerEl)
.setName('Support JS commands (beware!)')
.setDesc("Support the 'jscommand' and 'jsfile' commands, which allow defining Ex commands using Javascript. WARNING! Review the README to understand why this may be dangerous before enabling.")
.addToggle(toggle => {
toggle.setValue(this.plugin.settings.supportJsCommands ?? DEFAULT_SETTINGS.supportJsCommands);
toggle.onChange(value => {
this.plugin.settings.supportJsCommands = value;
this.plugin.saveSettings();
})
});
}
}

View file

@ -1,7 +1,7 @@
{
"id": "obsidian-vimrc-support",
"name": "Vimrc Support",
"version": "0.5.2",
"version": "0.6.0",
"description": "Auto-load a startup file with Obsidian Vim commands.",
"author": "esm",
"authorUrl": "",

View file

@ -1,6 +1,6 @@
{
"name": "obsidian-vimrc-support",
"version": "0.5.2",
"version": "0.6.0",
"description": "Auto-load a startup file with Obsidian Vim commands.",
"main": "main.js",
"scripts": {