mirror of
https://github.com/esm7/obsidian-vimrc-support.git
synced 2026-07-22 05:00:25 +00:00
exmap and obcommand commands
This commit is contained in:
parent
06d4016524
commit
acf73336fa
5 changed files with 146 additions and 18 deletions
48
README.md
48
README.md
|
|
@ -39,6 +39,8 @@ In addition to that:
|
|||
- Special support for yanking to system clipboard can be activated by `set clipboard=unnamed` (`unnamedplus` will do the same thing).
|
||||
- Support for the `tabstop` Vim option (e.g. `set tabstop=4`).
|
||||
- Custom mapping/unmapping commands in addition to the defaults: `noremap` and `iunmap` (PRs are welcome to implement more :) )
|
||||
- `exmap [commandName] [command...]`: a command to map Ex commands. This should basically be supported in regular `:map`, but doesn't work with multi-argument commands due to a CodeMirror bug, so this is a workaround.
|
||||
- `obcommand` - execute Obsidian commands, see more details below.
|
||||
|
||||
Commands that fail don't generate any visible error for now.
|
||||
|
||||
|
|
@ -65,8 +67,54 @@ Things I'd love to add:
|
|||
- Implement some standard `vim-markdown` motions for Obsidian, e.g. `[[`, or implement for CodeMirror the 1-2 missing Ex commands required to define these keymaps in the Vimrc.
|
||||
- Relative line numbers.
|
||||
|
||||
## Change Vimrc File Location/Name
|
||||
|
||||
If you want the Vimrc file name or path to be different than the default, there is a plugin setting (under Settings | Plugin Options | Vimrc Support) to change that.
|
||||
|
||||
## Executing Obsidian Commands with `obcommand`
|
||||
|
||||
The plugin defines a custom Ex command named `obcommand` to execute various Obsidian commands as an Ex command.
|
||||
This is useful to map external functionality of Obsidian or other plugins to Vim shortcuts, but it's not as easy to use as one would hope.
|
||||
|
||||
If you just type `:obcommand` you'll get in the Developer Console (Ctrl+Shift+I) the list of commands that are currently defined by the app.
|
||||
The simple syntax `:obcommand [commandName]` will execute the named command.
|
||||
|
||||
Some useful examples:
|
||||
- `obcommand editor:insert-link`
|
||||
- `obcommand editor:toggle-comment`
|
||||
- `obcommand app:go-back`
|
||||
- `obcommand workspace:split-vertical`
|
||||
And countless more.
|
||||
|
||||
**WARNING:** this is not a formal API that Obsidian provides and is done in a rather hacky manner.
|
||||
It's definitely possible that some future version of Obsidian will break this functionality.
|
||||
|
||||
### Mapping Obsidian Commands Within Vim
|
||||
|
||||
Next thing you probably wanna ask is "how do I map the great Obsidian commands to Vim commands?"
|
||||
|
||||
The trivial answer should have been something along the line of `:nmap <C-o> :obcommand app:go-back`, but this **does not work** because of an annoying CodeMirror bug.
|
||||
Turns out that the various mapping commands of CodeMirror pass only the first argument, so when you execute your mapping if defined as above, `:obcommand` would execute with no arguments.
|
||||
|
||||
Here comes a custom command to the rescue, `exmap`, which you can use to "alias" Ex commands for longer Ex commands: `:exmap back obcommand app:go-back`.
|
||||
You now have a simple (0 argument) Ex command named `back` that goes back in Obsidian, and *that* is something you can map.
|
||||
|
||||
To summarize, here's how you map `C-o` to Back:
|
||||
```
|
||||
exmap back obcommand app:go-back
|
||||
nmap <C-o> :back
|
||||
```
|
||||
|
||||
Note how `exmap` lists command names without colons and in `nmap` the colon is required.
|
||||
|
||||
## Changelog
|
||||
|
||||
### 0.3.0
|
||||
|
||||
- Added a settings file for the Vimrc file name (thank you @SalmanAlSaigal!)
|
||||
- Added the `exmap` Ex command.
|
||||
- Added the `obcommand` Ex command.
|
||||
|
||||
### 0.2.4
|
||||
|
||||
Fixed a race condition with yank & paste: https://github.com/esm7/obsidian-vimrc-support/issues/11
|
||||
|
|
|
|||
108
main.ts
108
main.ts
|
|
@ -1,12 +1,18 @@
|
|||
import { App, Plugin, TFile, MarkdownView, PluginSettingTab, Setting } from 'obsidian';
|
||||
import * as keyFromAccelerator from 'keyboardevent-from-electron-accelerator';
|
||||
|
||||
declare const CodeMirror: any;
|
||||
|
||||
interface Settings {
|
||||
VIMRC_NAME: string;
|
||||
vimrcFileName: string
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: Settings = {
|
||||
VIMRC_NAME: ".obsidian.vimrc"
|
||||
vimrcFileName: ".obsidian.vimrc"
|
||||
}
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export default class VimrcPlugin extends Plugin {
|
||||
|
|
@ -23,7 +29,7 @@ export default class VimrcPlugin extends Plugin {
|
|||
this.addSettingTab(new SettingsTab(this.app, this))
|
||||
|
||||
this.registerEvent(this.app.workspace.on('file-open', (file: TFile) => {
|
||||
const VIMRC_FILE_NAME = this.settings.VIMRC_NAME;
|
||||
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') });
|
||||
|
|
@ -40,12 +46,12 @@ export default class VimrcPlugin extends Plugin {
|
|||
})
|
||||
}
|
||||
|
||||
async loadSettings(){
|
||||
async loadSettings() {
|
||||
const data = await this.loadData();
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, data);
|
||||
}
|
||||
|
||||
async saveSettings(){
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +65,7 @@ export default class VimrcPlugin extends Plugin {
|
|||
var markdownView = view as MarkdownView;
|
||||
var cmEditor = markdownView.sourceMode.cmEditor;
|
||||
if (cmEditor && !CodeMirror.Vim.loadedVimrc) {
|
||||
CodeMirror.Vim.defineOption('clipboard', '', 'string', ['clip'], (value, cm) => {
|
||||
CodeMirror.Vim.defineOption('clipboard', '', 'string', ['clip'], (value: string, cm: any) => {
|
||||
if (value) {
|
||||
if (value.trim() == 'unnamed' || value.trim() == 'unnamedplus') {
|
||||
if (!this.yankToSystemClipboard) {
|
||||
|
|
@ -71,19 +77,19 @@ export default class VimrcPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
});
|
||||
CodeMirror.Vim.defineOption('tabstop', 4, 'number', [], (value, cm) => {
|
||||
CodeMirror.Vim.defineOption('tabstop', 4, 'number', [], (value: number, cm: any) => {
|
||||
if (value) {
|
||||
cmEditor.setOption('tabSize', value);
|
||||
}
|
||||
});
|
||||
|
||||
CodeMirror.Vim.defineEx('iunmap', '', (cm, params) => {
|
||||
CodeMirror.Vim.defineEx('iunmap', '', (cm: any, params: any) => {
|
||||
if (params.argString.trim()) {
|
||||
CodeMirror.Vim.unmap(params.argString.trim(), 'insert');
|
||||
}
|
||||
});
|
||||
|
||||
CodeMirror.Vim.defineEx('noremap', '', (cm, params) => {
|
||||
CodeMirror.Vim.defineEx('noremap', '', (cm: any, params: any) => {
|
||||
if (!params?.args?.length) {
|
||||
throw new Error('Invalid mapping: noremap');
|
||||
}
|
||||
|
|
@ -93,6 +99,78 @@ export default class VimrcPlugin extends Plugin {
|
|||
}
|
||||
});
|
||||
|
||||
// Allow the user to register an Ex command
|
||||
CodeMirror.Vim.defineEx('exmap', '', (cm: any, params: any) => {
|
||||
if (params?.args?.length && params.args.length < 2) {
|
||||
throw new Error(`exmap requires at least 2 parameters: [name] [actions...]`);
|
||||
}
|
||||
let commandName = params.args[0];
|
||||
params.args.shift();
|
||||
let commandContent = params.args.join(' ');
|
||||
// The content of the user's Ex command is just the remaining parameters of the exmap command
|
||||
CodeMirror.Vim.defineEx(commandName, '', (cm: any, params: any) => {
|
||||
CodeMirror.Vim.handleEx(cm, commandContent);
|
||||
});
|
||||
});
|
||||
|
||||
CodeMirror.Vim.defineEx('sendkeys', '', async (cm: any, params: any) => {
|
||||
if (!params?.args?.length) {
|
||||
console.log(params);
|
||||
throw new Error(`The sendkeys command requires a list of keys, e.g. sendKeys Ctrl+p a b Enter`);
|
||||
}
|
||||
|
||||
let allGood = true;
|
||||
let events: KeyboardEvent[] = [];
|
||||
for (const key of params.args) {
|
||||
if (key.startsWith('wait')) {
|
||||
const delay = key.slice(4);
|
||||
await sleep(delay * 1000);
|
||||
}
|
||||
else {
|
||||
let keyEvent: KeyboardEvent = null;
|
||||
try {
|
||||
keyEvent = new KeyboardEvent('keydown', keyFromAccelerator.toKeyEvent(key));
|
||||
events.push(keyEvent);
|
||||
}
|
||||
catch (e) {
|
||||
allGood = false;
|
||||
throw new Error(`Key '${key}' couldn't be read as an Electron Accelerator`);
|
||||
}
|
||||
if (allGood) {
|
||||
for (keyEvent of events)
|
||||
window.postMessage(JSON.parse(JSON.stringify(keyEvent)), '*');
|
||||
// view.containerEl.dispatchEvent(keyEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
CodeMirror.Vim.defineEx('obcommand', '', async (cm: any, params: any) => {
|
||||
const availableCommands = (this.app as any).commands.commands;
|
||||
if (!params?.args?.length || params.args.length != 1) {
|
||||
console.log(`Available commands: ${Object.keys(availableCommands).join('\n')}`)
|
||||
throw new Error(`obcommand requires exactly 1 parameter`);
|
||||
}
|
||||
const command = params.args[0];
|
||||
if (command in availableCommands) {
|
||||
let callback = availableCommands[command].callback;
|
||||
let checkCallback = availableCommands[command].checkCallback;
|
||||
let editorCallback = availableCommands[command].editorCallback;
|
||||
let editorCheckCallback = availableCommands[command].editorCheckCallback;
|
||||
if (editorCheckCallback)
|
||||
editorCheckCallback(markdownView.editor, false);
|
||||
else if (editorCallback)
|
||||
editorCallback(markdownView.editor);
|
||||
else if (checkCallback)
|
||||
checkCallback(false);
|
||||
else if (callback)
|
||||
callback();
|
||||
else
|
||||
throw new Error(`Command ${command} doesn't have an Obsidian callback`);
|
||||
} else
|
||||
throw new Error(`Command ${command} was not found, try 'obcommand' with no params to see in the developer console what's available`);
|
||||
});
|
||||
|
||||
vimCommands.split("\n").forEach(
|
||||
function(line, index, arr) {
|
||||
if (line.trim().length > 0 && line.trim()[0] != '"') {
|
||||
|
|
@ -145,19 +223,19 @@ class SettingsTab extends PluginSettingTab {
|
|||
|
||||
containerEl.empty();
|
||||
|
||||
containerEl.createEl('h2', {text: 'Obsidian Vimrc Support Settings'});
|
||||
containerEl.createEl('h2', {text: 'Vimrc Settings'});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Vimrc file name')
|
||||
.setDesc('Relative to vault directory (requires restart)')
|
||||
.addText((text) => {
|
||||
text.setPlaceholder(DEFAULT_SETTINGS.VIMRC_NAME);
|
||||
if(this.plugin.settings.VIMRC_NAME !== DEFAULT_SETTINGS.VIMRC_NAME)
|
||||
text.setValue(this.plugin.settings.VIMRC_NAME)
|
||||
text.setPlaceholder(DEFAULT_SETTINGS.vimrcFileName);
|
||||
if (this.plugin.settings.vimrcFileName !== DEFAULT_SETTINGS.vimrcFileName)
|
||||
text.setValue(this.plugin.settings.vimrcFileName)
|
||||
text.onChange(value => {
|
||||
this.plugin.settings.VIMRC_NAME = value || DEFAULT_SETTINGS.VIMRC_NAME;
|
||||
this.plugin.settings.vimrcFileName = value || DEFAULT_SETTINGS.vimrcFileName;
|
||||
this.plugin.saveSettings();
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "obsidian-vimrc-support",
|
||||
"name": "Obsidian Vimrc Support",
|
||||
"version": "0.2.4",
|
||||
"name": "Vimrc Support",
|
||||
"version": "0.3.0",
|
||||
"description": "Auto-load a startup file with Obsidian Vim commands.",
|
||||
"author": "esm",
|
||||
"authorUrl": "",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-vimrc-support",
|
||||
"version": "0.2.4",
|
||||
"version": "0.3.0",
|
||||
"description": "Auto-load a startup file with Obsidian Vim commands.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
"@rollup/plugin-node-resolve": "^9.0.0",
|
||||
"@rollup/plugin-typescript": "^6.1.0",
|
||||
"@types/node": "^14.14.6",
|
||||
"keyboardevent-from-electron-accelerator": "*",
|
||||
"obsidian": "https://github.com/obsidianmd/obsidian-api/tarball/master",
|
||||
"rollup": "^2.33.0",
|
||||
"tslib": "^2.0.3",
|
||||
|
|
|
|||
1
types.d.ts
vendored
Normal file
1
types.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
declare module 'keyboardevent-from-electron-accelerator';
|
||||
Loading…
Reference in a new issue