@vemjs/plugin-api 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/README.md +151 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/package.json +17 -0
- package/src/index.test.ts +102 -0
- package/src/index.ts +54 -0
- package/tsconfig.json +13 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# @vemjs/plugin-api
|
|
2
|
+
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 0498765: chore: release infrastructure, package metadata, and documentation scaffolding
|
|
8
|
+
|
|
9
|
+
This changeset covers all release preparation work for the initial 0.1.0 publish:
|
|
10
|
+
|
|
11
|
+
**Package metadata** — Added `license`, `repository`, `keywords`, and `publishConfig` fields to
|
|
12
|
+
all four packages so they display correctly on npmjs.com with proper source links, license badges,
|
|
13
|
+
and searchable tags.
|
|
14
|
+
|
|
15
|
+
**CI/CD pipeline** — Rewrote `.github/workflows/ci.yml` and `release.yml`:
|
|
16
|
+
|
|
17
|
+
- `quality` job: build → test → lint (oxlint) → dead-code scan (knip) on every PR and push
|
|
18
|
+
- `publish` job: automatic `changeset publish` to npm on every merge to `main` via
|
|
19
|
+
`changesets/action@v1` using the `NPM_TOKEN` org secret
|
|
20
|
+
|
|
21
|
+
**Changesets** — Initialized `.changeset/` with a `config.json` configured for public access and
|
|
22
|
+
patch-level internal dependency updates, enabling a fully automated release flow.
|
|
23
|
+
|
|
24
|
+
**Tooling** — Added `knip.config.ts` (dead-code detection), `oxlintrc.json` (TypeScript-aware
|
|
25
|
+
lint rules), `.lintstagedrc.json` (auto-fix staged files on commit), and updated root
|
|
26
|
+
`package.json` scripts: `build`, `test`, `lint`, `knip`, `changeset`, `version-packages`,
|
|
27
|
+
`release`.
|
|
28
|
+
|
|
29
|
+
**Dependabot** — Configured weekly npm dependency scanning with dev/prod groups and
|
|
30
|
+
`@vectojs/*` major-version pin to avoid upstream breaking changes.
|
|
31
|
+
|
|
32
|
+
**Repository** — Updated root `README.md` with CI/npm/license badges and package table.
|
|
33
|
+
Updated `SECURITY.md` with all four `@vemjs/*` packages and coordinated-disclosure guidance.
|
|
34
|
+
Added GitHub topics (vim, editor, typescript, vectojs, canvas, modal-editing, lsp) and branch
|
|
35
|
+
protection requiring the `quality` status check before merging to `main`.
|
|
36
|
+
|
|
37
|
+
**Build hygiene** — Cleaned all `dist/` directories and rebuilt from source to ensure no
|
|
38
|
+
test artefacts are included in published tarballs. Verified `knip` reports zero issues.
|
|
39
|
+
|
|
40
|
+
- Updated dependencies [0498765]
|
|
41
|
+
- Updated dependencies [3fa4848]
|
|
42
|
+
- @vemjs/core@0.2.0
|
|
43
|
+
|
|
44
|
+
## 0.1.0
|
|
45
|
+
|
|
46
|
+
### Features
|
|
47
|
+
|
|
48
|
+
- `PluginRegistry` — plugin activation lifecycle manager
|
|
49
|
+
- `PluginContext` — plugin SDK: `registerKeybinding`, `registerCommand`, `onDidOpenBuffer`, `onDidChangeBuffer`, `onDidChangeMode`
|
|
50
|
+
- Chord-based custom keybinding with prefix-match buffering and Vim-native keystroke replay fallback
|
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# @vemjs/plugin-api
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@vemjs/plugin-api)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
The official Plugin SDK and life-cycle registration layer for the **Vem Editor**. It allows third-party developers to register custom keybindings, hook into buffer updates, intercept mode transitions, and build complex chords or commands.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **Standardized Plugin Interface**: Clean activation/deactivation lifecycles matching the `@vemjs/core` environment.
|
|
11
|
+
- **Keybinding Registration**: Dynamically override editor shortcuts or register command chords in specific modes.
|
|
12
|
+
- **Event Observers**: Hook into buffer creation (`onDidOpenBuffer`), text mutations (`onDidChangeBuffer`), and mode changes (`onDidChangeMode`).
|
|
13
|
+
- **Command Management**: Define global editor commands that can be invoked via the Command Bar or mapped to shortcuts.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bun add @vemjs/plugin-api
|
|
19
|
+
# or via npm
|
|
20
|
+
npm install @vemjs/plugin-api
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
Create and activate a plugin that adds a custom keystroke in NORMAL mode:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { VemEditorState } from '@vemjs/core';
|
|
29
|
+
import { PluginRegistry, type VemPlugin } from '@vemjs/plugin-api';
|
|
30
|
+
|
|
31
|
+
const editor = new VemEditorState('initial text');
|
|
32
|
+
const registry = new PluginRegistry(editor);
|
|
33
|
+
|
|
34
|
+
// Define a plugin
|
|
35
|
+
const myPlugin: VemPlugin = {
|
|
36
|
+
name: 'my-custom-shortcuts',
|
|
37
|
+
version: '1.0.0',
|
|
38
|
+
activate(context) {
|
|
39
|
+
// Register a command
|
|
40
|
+
context.registerCommand('custom.hello', () => {
|
|
41
|
+
console.log('Hello from command!');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Bind keys (e.g. pressing 'gh' in NORMAL mode fires command)
|
|
45
|
+
context.registerKeybinding('NORMAL', 'gh', 'custom.hello');
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Register & Activate
|
|
50
|
+
registry.register(myPlugin);
|
|
51
|
+
|
|
52
|
+
// Pressing keys in sequence triggers the command
|
|
53
|
+
editor.input('g');
|
|
54
|
+
editor.input('h'); // Console prints: "Hello from command!"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## API Reference
|
|
58
|
+
|
|
59
|
+
### `VemPlugin`
|
|
60
|
+
|
|
61
|
+
Interface representing a loadable plugin.
|
|
62
|
+
|
|
63
|
+
- `name: string`: Unique package identifier.
|
|
64
|
+
- `version: string`: Semantic version string.
|
|
65
|
+
- `activate(context: PluginContext): void`: Called by the engine when loading the plugin.
|
|
66
|
+
- `deactivate?(): void`: Optional cleanup callback.
|
|
67
|
+
|
|
68
|
+
### `PluginContext`
|
|
69
|
+
|
|
70
|
+
Provided to `activate(context)` to interact with the editor.
|
|
71
|
+
|
|
72
|
+
- `editorState: VemEditorState`: Access to core editor properties.
|
|
73
|
+
- `registerCommand(commandName: string, callback: () => void): void`: Registers a function callback globally.
|
|
74
|
+
- `registerKeybinding(mode: EditorMode, keys: string, commandName: string): void`: Binds a specific key combination (or chord prefix) in the specified mode to a registered command.
|
|
75
|
+
- `onDidOpenBuffer(cb: () => void): void`: Subscribes to buffer open events.
|
|
76
|
+
- `onDidChangeBuffer(cb: () => void): void`: Subscribes to buffer content mutation events.
|
|
77
|
+
- `onDidChangeMode(cb: (mode: EditorMode) => void): void`: Subscribes to mode change notifications.
|
|
78
|
+
|
|
79
|
+
### `PluginRegistry`
|
|
80
|
+
|
|
81
|
+
Manager executing plugin installation.
|
|
82
|
+
|
|
83
|
+
- `constructor(editorState: VemEditorState)`: Creates the registry bound to an editor instance.
|
|
84
|
+
- `register(plugin: VemPlugin): void`: Registers context callbacks and triggers the plugin's `activate` method.
|
|
85
|
+
- `executeCommand(name: string): void`: Runs the callback associated with a command.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Plugin Development Guide
|
|
90
|
+
|
|
91
|
+
### Naming Conventions
|
|
92
|
+
|
|
93
|
+
For consistency in the ecosystem, plugins should be named using the prefix `vem-plugin-` when published as standalone packages, or `@vemjs/plugin-<name>` if they are officially maintained packages:
|
|
94
|
+
|
|
95
|
+
- Third-party: `vem-plugin-format-on-save`
|
|
96
|
+
- Official: `@vemjs/plugin-treesitter`
|
|
97
|
+
|
|
98
|
+
### Complete Example: Format-on-Save
|
|
99
|
+
|
|
100
|
+
Here is a full plugin implementation that automatically trims trailing whitespace whenever a buffer changes (an automated linter/formatter behavior):
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { type VemPlugin } from '@vemjs/plugin-api';
|
|
104
|
+
|
|
105
|
+
export const FormatOnSavePlugin: VemPlugin = {
|
|
106
|
+
name: 'format-on-save',
|
|
107
|
+
version: '1.0.0',
|
|
108
|
+
activate(context) {
|
|
109
|
+
let isFormatting = false;
|
|
110
|
+
|
|
111
|
+
context.onDidChangeBuffer(() => {
|
|
112
|
+
// Avoid recursive change loops
|
|
113
|
+
if (isFormatting) return;
|
|
114
|
+
|
|
115
|
+
const editor = context.editorState;
|
|
116
|
+
const originalText = editor.getText();
|
|
117
|
+
const lines = originalText.split('\n');
|
|
118
|
+
|
|
119
|
+
// Trim trailing spaces
|
|
120
|
+
const formattedLines = lines.map((line) => line.trimEnd());
|
|
121
|
+
const formattedText = formattedLines.join('\n');
|
|
122
|
+
|
|
123
|
+
if (formattedText !== originalText) {
|
|
124
|
+
isFormatting = true;
|
|
125
|
+
// Access buffer to set raw text
|
|
126
|
+
editor.getBuffer().setText(formattedText);
|
|
127
|
+
isFormatting = false;
|
|
128
|
+
console.log('[FormatOnSave] Trimmed trailing whitespaces');
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Architecture
|
|
136
|
+
|
|
137
|
+
```mermaid
|
|
138
|
+
graph LR
|
|
139
|
+
Engine[VemEditorState] <--> Context[PluginContext]
|
|
140
|
+
Context --> Registry[PluginRegistry]
|
|
141
|
+
Registry --> Plugin1[VemPlugin A]
|
|
142
|
+
Registry --> Plugin2[VemPlugin B]
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Contributing
|
|
146
|
+
|
|
147
|
+
Please review [CONTRIBUTING.md](../../CONTRIBUTING.md) for details on our workflow and engineering guidelines.
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
This package is licensed under the MIT License - see the LICENSE file for details.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { VemEditorState } from '@vemjs/core';
|
|
2
|
+
import type { EditorMode } from '@vemjs/core';
|
|
3
|
+
export interface VemPlugin {
|
|
4
|
+
name: string;
|
|
5
|
+
version: string;
|
|
6
|
+
activate(context: PluginContext): void;
|
|
7
|
+
deactivate?(): void;
|
|
8
|
+
}
|
|
9
|
+
export interface PluginContext {
|
|
10
|
+
editorState: VemEditorState;
|
|
11
|
+
registerCommand(commandName: string, callback: () => void): void;
|
|
12
|
+
registerKeybinding(mode: EditorMode, keys: string, commandName: string): void;
|
|
13
|
+
onDidOpenBuffer(cb: () => void): void;
|
|
14
|
+
onDidChangeBuffer(cb: () => void): void;
|
|
15
|
+
onDidChangeMode(cb: (mode: EditorMode) => void): void;
|
|
16
|
+
}
|
|
17
|
+
export declare class PluginRegistry {
|
|
18
|
+
private plugins;
|
|
19
|
+
private commands;
|
|
20
|
+
private editorState;
|
|
21
|
+
constructor(editorState: VemEditorState);
|
|
22
|
+
register(plugin: VemPlugin): void;
|
|
23
|
+
executeCommand(name: string): void;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IACvC,UAAU,CAAC,IAAI,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,cAAc,CAAC;IAC5B,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IACjE,kBAAkB,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9E,eAAe,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IACtC,iBAAiB,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IACxC,eAAe,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;CACvD;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,WAAW,CAAiB;gBAExB,WAAW,EAAE,cAAc;IAIhC,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI;IAiBjC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;CAQ1C"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { VemEditorState } from '@vemjs/core';
|
|
2
|
+
export class PluginRegistry {
|
|
3
|
+
plugins = new Map();
|
|
4
|
+
commands = new Map();
|
|
5
|
+
editorState;
|
|
6
|
+
constructor(editorState) {
|
|
7
|
+
this.editorState = editorState;
|
|
8
|
+
}
|
|
9
|
+
register(plugin) {
|
|
10
|
+
const context = {
|
|
11
|
+
editorState: this.editorState,
|
|
12
|
+
registerCommand: (name, cb) => this.commands.set(name, cb),
|
|
13
|
+
registerKeybinding: (mode, keys, commandName) => {
|
|
14
|
+
this.editorState.registerKeybinding(mode, keys, commandName);
|
|
15
|
+
},
|
|
16
|
+
onDidOpenBuffer: (cb) => this.editorState.onDidOpenBuffer(cb),
|
|
17
|
+
onDidChangeBuffer: (cb) => this.editorState.onDidChangeBuffer(cb),
|
|
18
|
+
onDidChangeMode: (cb) => this.editorState.onDidChangeMode(cb),
|
|
19
|
+
};
|
|
20
|
+
plugin.activate(context);
|
|
21
|
+
this.plugins.set(plugin.name, plugin);
|
|
22
|
+
console.log(`Plugin [${plugin.name}] activated.`);
|
|
23
|
+
}
|
|
24
|
+
executeCommand(name) {
|
|
25
|
+
const cmd = this.commands.get(name);
|
|
26
|
+
if (cmd) {
|
|
27
|
+
cmd();
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
console.warn(`Command [${name}] not found.`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAmB7C,MAAM,OAAO,cAAc;IACjB,OAAO,GAA2B,IAAI,GAAG,EAAE,CAAC;IAC5C,QAAQ,GAA4B,IAAI,GAAG,EAAE,CAAC;IAC9C,WAAW,CAAiB;IAEpC,YAAY,WAA2B;QACrC,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACjC,CAAC;IAEM,QAAQ,CAAC,MAAiB;QAC/B,MAAM,OAAO,GAAkB;YAC7B,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,eAAe,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;YAC1D,kBAAkB,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE;gBAC9C,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;YAC/D,CAAC;YACD,eAAe,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,eAAe,CAAC,EAAE,CAAC;YAC7D,iBAAiB,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,iBAAiB,CAAC,EAAE,CAAC;YACjE,eAAe,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,eAAe,CAAC,EAAE,CAAC;SAC9D,CAAC;QAEF,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACzB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACtC,OAAO,CAAC,GAAG,CAAC,WAAW,MAAM,CAAC,IAAI,cAAc,CAAC,CAAC;IACpD,CAAC;IAEM,cAAc,CAAC,IAAY;QAChC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,GAAG,EAAE,CAAC;YACR,GAAG,EAAE,CAAC;QACR,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC,YAAY,IAAI,cAAc,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vemjs/plugin-api",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "SDK interfaces for 3rd-party plugins on the Vem editor",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@vemjs/core": "workspace:*"
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, it, expect } from 'bun:test';
|
|
2
|
+
import { VemEditorState } from '@vemjs/core';
|
|
3
|
+
import { PluginRegistry, type VemPlugin } from './index';
|
|
4
|
+
|
|
5
|
+
describe('Plugin System', () => {
|
|
6
|
+
it('should register and activate a plugin', () => {
|
|
7
|
+
const editor = new VemEditorState('test content');
|
|
8
|
+
const registry = new PluginRegistry(editor);
|
|
9
|
+
|
|
10
|
+
let activated = false;
|
|
11
|
+
const testPlugin: VemPlugin = {
|
|
12
|
+
name: 'test-plugin',
|
|
13
|
+
version: '1.0.0',
|
|
14
|
+
activate(_context) {
|
|
15
|
+
activated = true;
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
registry.register(testPlugin);
|
|
20
|
+
expect(activated).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should execute registered custom commands and intercept custom keybindings', () => {
|
|
24
|
+
const editor = new VemEditorState('hello');
|
|
25
|
+
const registry = new PluginRegistry(editor);
|
|
26
|
+
|
|
27
|
+
let executed = false;
|
|
28
|
+
const testPlugin: VemPlugin = {
|
|
29
|
+
name: 'chord-plugin',
|
|
30
|
+
version: '1.0.0',
|
|
31
|
+
activate(context) {
|
|
32
|
+
context.registerCommand('testCmd', () => {
|
|
33
|
+
executed = true;
|
|
34
|
+
});
|
|
35
|
+
context.registerKeybinding('NORMAL', ' gw', 'testCmd');
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
registry.register(testPlugin);
|
|
40
|
+
|
|
41
|
+
editor.onExecutePluginCommand((name) => {
|
|
42
|
+
registry.executeCommand(name);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Type chord: Space, g, w
|
|
46
|
+
editor.handleKey(' ');
|
|
47
|
+
editor.handleKey('g');
|
|
48
|
+
editor.handleKey('w');
|
|
49
|
+
|
|
50
|
+
expect(executed).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should support buffer change event listeners', () => {
|
|
54
|
+
const editor = new VemEditorState('line1');
|
|
55
|
+
const registry = new PluginRegistry(editor);
|
|
56
|
+
|
|
57
|
+
let changeCount = 0;
|
|
58
|
+
const testPlugin: VemPlugin = {
|
|
59
|
+
name: 'buffer-listener',
|
|
60
|
+
version: '1.0.0',
|
|
61
|
+
activate(context) {
|
|
62
|
+
context.onDidChangeBuffer(() => {
|
|
63
|
+
changeCount++;
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
registry.register(testPlugin);
|
|
69
|
+
|
|
70
|
+
// Modify the buffer
|
|
71
|
+
editor.handleKey('i');
|
|
72
|
+
editor.handleKey('a');
|
|
73
|
+
editor.handleKey('Escape');
|
|
74
|
+
|
|
75
|
+
expect(changeCount).toBe(1);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should support mode change event listeners', () => {
|
|
79
|
+
const editor = new VemEditorState('line1');
|
|
80
|
+
const registry = new PluginRegistry(editor);
|
|
81
|
+
|
|
82
|
+
let lastMode = 'NORMAL';
|
|
83
|
+
const testPlugin: VemPlugin = {
|
|
84
|
+
name: 'mode-listener',
|
|
85
|
+
version: '1.0.0',
|
|
86
|
+
activate(context) {
|
|
87
|
+
context.onDidChangeMode((mode) => {
|
|
88
|
+
lastMode = mode;
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
registry.register(testPlugin);
|
|
94
|
+
|
|
95
|
+
// Change mode
|
|
96
|
+
editor.handleKey('i');
|
|
97
|
+
expect(lastMode).toBe('INSERT');
|
|
98
|
+
|
|
99
|
+
editor.handleKey('Escape');
|
|
100
|
+
expect(lastMode).toBe('NORMAL');
|
|
101
|
+
});
|
|
102
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { VemEditorState } from '@vemjs/core';
|
|
2
|
+
import type { EditorMode } from '@vemjs/core';
|
|
3
|
+
|
|
4
|
+
export interface VemPlugin {
|
|
5
|
+
name: string;
|
|
6
|
+
version: string;
|
|
7
|
+
activate(context: PluginContext): void;
|
|
8
|
+
deactivate?(): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface PluginContext {
|
|
12
|
+
editorState: VemEditorState;
|
|
13
|
+
registerCommand(commandName: string, callback: () => void): void;
|
|
14
|
+
registerKeybinding(mode: EditorMode, keys: string, commandName: string): void;
|
|
15
|
+
onDidOpenBuffer(cb: () => void): void;
|
|
16
|
+
onDidChangeBuffer(cb: () => void): void;
|
|
17
|
+
onDidChangeMode(cb: (mode: EditorMode) => void): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class PluginRegistry {
|
|
21
|
+
private plugins: Map<string, VemPlugin> = new Map();
|
|
22
|
+
private commands: Map<string, () => void> = new Map();
|
|
23
|
+
private editorState: VemEditorState;
|
|
24
|
+
|
|
25
|
+
constructor(editorState: VemEditorState) {
|
|
26
|
+
this.editorState = editorState;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public register(plugin: VemPlugin): void {
|
|
30
|
+
const context: PluginContext = {
|
|
31
|
+
editorState: this.editorState,
|
|
32
|
+
registerCommand: (name, cb) => this.commands.set(name, cb),
|
|
33
|
+
registerKeybinding: (mode, keys, commandName) => {
|
|
34
|
+
this.editorState.registerKeybinding(mode, keys, commandName);
|
|
35
|
+
},
|
|
36
|
+
onDidOpenBuffer: (cb) => this.editorState.onDidOpenBuffer(cb),
|
|
37
|
+
onDidChangeBuffer: (cb) => this.editorState.onDidChangeBuffer(cb),
|
|
38
|
+
onDidChangeMode: (cb) => this.editorState.onDidChangeMode(cb),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
plugin.activate(context);
|
|
42
|
+
this.plugins.set(plugin.name, plugin);
|
|
43
|
+
console.log(`Plugin [${plugin.name}] activated.`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public executeCommand(name: string): void {
|
|
47
|
+
const cmd = this.commands.get(name);
|
|
48
|
+
if (cmd) {
|
|
49
|
+
cmd();
|
|
50
|
+
} else {
|
|
51
|
+
console.warn(`Command [${name}] not found.`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"noEmit": false,
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"declarationMap": true,
|
|
7
|
+
"sourceMap": true,
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"outDir": "./dist"
|
|
10
|
+
},
|
|
11
|
+
"include": ["src/**/*"],
|
|
12
|
+
"exclude": ["src/**/*.test.ts"]
|
|
13
|
+
}
|