myrcp 0.9.1__tar.gz
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.
- myrcp-0.9.1/.gitignore +9 -0
- myrcp-0.9.1/ARCHITECTURE.md +531 -0
- myrcp-0.9.1/CMakeLists.txt +156 -0
- myrcp-0.9.1/LICENSE +21 -0
- myrcp-0.9.1/PKG-INFO +288 -0
- myrcp-0.9.1/README.md +266 -0
- myrcp-0.9.1/cmake/myrcp.pc.in +9 -0
- myrcp-0.9.1/cmake/myrcpConfig.cmake.in +6 -0
- myrcp-0.9.1/core/include/rcp/app.hpp +61 -0
- myrcp-0.9.1/core/include/rcp/grid.hpp +124 -0
- myrcp-0.9.1/core/include/rcp/keys.hpp +53 -0
- myrcp-0.9.1/core/include/rcp/layout.hpp +91 -0
- myrcp-0.9.1/core/include/rcp/msgpack.hpp +104 -0
- myrcp-0.9.1/core/include/rcp/utf8.hpp +22 -0
- myrcp-0.9.1/core/src/app.cpp +218 -0
- myrcp-0.9.1/core/src/console.cpp +400 -0
- myrcp-0.9.1/core/src/console.hpp +87 -0
- myrcp-0.9.1/core/src/editor_link.hpp +135 -0
- myrcp-0.9.1/core/src/editor_links.cpp +260 -0
- myrcp-0.9.1/core/src/editor_links.hpp +85 -0
- myrcp-0.9.1/core/src/grid.cpp +274 -0
- myrcp-0.9.1/core/src/history.hpp +58 -0
- myrcp-0.9.1/core/src/host.cpp +108 -0
- myrcp-0.9.1/core/src/host.hpp +59 -0
- myrcp-0.9.1/core/src/hub.cpp +1091 -0
- myrcp-0.9.1/core/src/hub.hpp +341 -0
- myrcp-0.9.1/core/src/hub_prefs.cpp +284 -0
- myrcp-0.9.1/core/src/json.cpp +280 -0
- myrcp-0.9.1/core/src/json.hpp +17 -0
- myrcp-0.9.1/core/src/keys.cpp +251 -0
- myrcp-0.9.1/core/src/launcher.cpp +44 -0
- myrcp-0.9.1/core/src/layout.cpp +920 -0
- myrcp-0.9.1/core/src/msgpack.cpp +336 -0
- myrcp-0.9.1/core/src/nvim.cpp +200 -0
- myrcp-0.9.1/core/src/outline.cpp +473 -0
- myrcp-0.9.1/core/src/outline.hpp +26 -0
- myrcp-0.9.1/core/src/prefs.cpp +372 -0
- myrcp-0.9.1/core/src/prefs.hpp +56 -0
- myrcp-0.9.1/core/src/process.cpp +211 -0
- myrcp-0.9.1/core/src/process.hpp +45 -0
- myrcp-0.9.1/core/src/rpc.cpp +107 -0
- myrcp-0.9.1/core/src/rpc.hpp +51 -0
- myrcp-0.9.1/core/src/search.cpp +627 -0
- myrcp-0.9.1/core/src/search.hpp +45 -0
- myrcp-0.9.1/core/src/service.hpp +63 -0
- myrcp-0.9.1/core/src/term.cpp +276 -0
- myrcp-0.9.1/core/src/term.hpp +74 -0
- myrcp-0.9.1/core/src/terminal.cpp +51 -0
- myrcp-0.9.1/core/src/terminal.hpp +40 -0
- myrcp-0.9.1/core/src/utf8.cpp +90 -0
- myrcp-0.9.1/core/src/vim.cpp +391 -0
- myrcp-0.9.1/core/src/workbench.cpp +1725 -0
- myrcp-0.9.1/core/src/workbench.hpp +203 -0
- myrcp-0.9.1/core/tests/service_tests.cpp +257 -0
- myrcp-0.9.1/core/tests/term_tests.cpp +88 -0
- myrcp-0.9.1/core/tests/tests.cpp +640 -0
- myrcp-0.9.1/gtk/app_gtk.cpp +69 -0
- myrcp-0.9.1/gtk/gobj.hpp +60 -0
- myrcp-0.9.1/gtk/render.cpp +1141 -0
- myrcp-0.9.1/gtk/render.hpp +89 -0
- myrcp-0.9.1/gtk/workbench.cpp +1548 -0
- myrcp-0.9.1/gtk/workbench.hpp +180 -0
- myrcp-0.9.1/pyproject.toml +70 -0
- myrcp-0.9.1/python/myrcp/__init__.py +8 -0
- myrcp-0.9.1/python/myrcp/_msgpack.py +237 -0
- myrcp-0.9.1/python/myrcp/host.py +658 -0
- myrcp-0.9.1/python/myrcp/launch.py +72 -0
- myrcp-0.9.1/python/myrcp/plugin.py +283 -0
- myrcp-0.9.1/python/myrcp/selection.py +81 -0
- myrcp-0.9.1/python/myrcp/ui.py +566 -0
- myrcp-0.9.1/python/pyproject.toml +11 -0
- myrcp-0.9.1/tools/libvterm.sh +14 -0
- myrcp-0.9.1/tools/wheel_smoke.py +39 -0
myrcp-0.9.1/.gitignore
ADDED
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
# myrcp architecture
|
|
2
|
+
|
|
3
|
+
A terminal-first rich client platform: one workbench (views, commands, keybindings,
|
|
4
|
+
jobs, a uniform look), tools as plugins, their views rendered in a terminal. myrcp is a
|
|
5
|
+
framework: applications are built on it (libmyrcp, or a product directory the `myrcp`
|
|
6
|
+
launcher starts) and bring their own plugins.
|
|
7
|
+
|
|
8
|
+
## Principles
|
|
9
|
+
|
|
10
|
+
1. **Views describe, hosts render.** A plugin view returns a *semantic* UI tree (the UI IR):
|
|
11
|
+
panels, lists, fields, buttons, progress bars. It never draws cells. A host lays the
|
|
12
|
+
tree out its own way, as SWT used native widgets per platform.
|
|
13
|
+
2. **Plugins run out of process.** A plugin host process per language/venv speaks
|
|
14
|
+
msgpack-RPC to the core. A crashing plugin, a slow import or the GIL never freezes the
|
|
15
|
+
workbench, and `Ctrl+R` restarts the host while the workbench keeps its state.
|
|
16
|
+
3. **Lazy activation.** What a plugin contributes (views, commands, keys) is a static
|
|
17
|
+
`myrcp.toml`. The host reads manifests without importing plugin code, and imports a
|
|
18
|
+
plugin only when one of its views opens or one of its commands runs.
|
|
19
|
+
4. **Integrate, don't reimplement.** Editors are Vim/Neovim; tmux stays the multiplexer.
|
|
20
|
+
myrcp runs inside tmux and can open things in tmux windows and splits.
|
|
21
|
+
5. **Everything stays usable without myrcp.** Plugins adapt existing tools, which keep
|
|
22
|
+
their CLIs; an application's plugins live with the application, not in myrcp.
|
|
23
|
+
6. **What every workbench needs is built in.** Problems, Properties, Preferences,
|
|
24
|
+
Consoles, Search, Tasks and Outline are the hub's own (C++, no plugin host needed),
|
|
25
|
+
in every application.
|
|
26
|
+
|
|
27
|
+
## Processes
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
┌──────────────────────────── myrcp (C++20) ─────────────────────────────┐
|
|
31
|
+
│ Workbench: views · command registry + palette · keybindings · jobs · │
|
|
32
|
+
│ status · editor handoff │
|
|
33
|
+
│ UI IR renderer (layout → cell grid) · grid diff → ANSI · key decoder │
|
|
34
|
+
└──────────────▲─────────────────────────────────────────────────────────┘
|
|
35
|
+
│ msgpack-RPC over pipes (stdin/stdout)
|
|
36
|
+
┌──────────────┴────────────────┐
|
|
37
|
+
│ python -m myrcp.host │
|
|
38
|
+
│ manifests → contributions │
|
|
39
|
+
│ lazy import, View instances │
|
|
40
|
+
└───────────────────────────────┘
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Source layout:
|
|
44
|
+
|
|
45
|
+
| Path | What |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `core/include/rcp/msgpack.hpp` | `mp::Value`, encoder, streaming decoder (also what Neovim `--embed` speaks) |
|
|
48
|
+
| `core/include/rcp/grid.hpp` | `Grid` of cells with semantic `Style`s, `Theme`, `diff()` → ANSI |
|
|
49
|
+
| `core/include/rcp/keys.hpp` | `KeyDecoder`: bytes → key names (`ctrl+p`, `alt+1`, `pagedown`, `paste`) |
|
|
50
|
+
| `core/include/rcp/layout.hpp` | UI IR → grid: layout, widget state, focus order |
|
|
51
|
+
| `core/include/rcp/app.hpp` | the public API: `AppInfo`, `rcp::run()`, `rcp::run_gtk()`, `load_product()` |
|
|
52
|
+
| `core/src/app.cpp` | `rcp::run`: options, `--list`, the terminal workbench; `launcher.cpp` the `myrcp` program |
|
|
53
|
+
| `core/src/workbench.cpp` | the event loop, chrome, palette, key routing, host protocol |
|
|
54
|
+
| `core/src/hub.hpp` | `Hub`, the workbench model; `service.hpp` its built-in features with views (`search.cpp`, `outline.cpp`), `console.hpp` the consoles |
|
|
55
|
+
| `core/src/editor_link.hpp` | links to Neovim (`nvim.cpp`, msgpack-RPC) and Vim (`vim.cpp`, JSON channels; `json.cpp`) |
|
|
56
|
+
| `core/src/editor_links.hpp` | `EditorLinks`: a front-end's linked editors (start, poll, the live one, selection, markers) |
|
|
57
|
+
| `gtk/` | `libmyrcp-gtk` (`-DMYRCP_GTK=ON`): the workbench as a GTK 4 application; `render.cpp` the IR as GTK widgets, `workbench.cpp` the chrome |
|
|
58
|
+
| `python/myrcp/` | the SDK: `ui` builders, `Plugin`, `View`, `Context`, `Job`, the host |
|
|
59
|
+
| `examples/demo/` | the demo application (`-DMYRCP_DEMO=ON`): a `main.cpp` on libmyrcp and its plugin (files, jobs, a table, a gallery of every widget and service) |
|
|
60
|
+
|
|
61
|
+
`rcp_core` (msgpack, JSON, grid, keys, layout) does no I/O. `rcp_hub`
|
|
62
|
+
(`core/src/hub.hpp`) is the workbench model: plugin hosts and their delegation,
|
|
63
|
+
contributions, open views and their trees, jobs, plugin commands and events, and the
|
|
64
|
+
built-in services. The front-end (`core/src/workbench.cpp`, the IR into a cell grid)
|
|
65
|
+
owns presentation only (widget state, focus, drawing) and hears the hub through
|
|
66
|
+
`Hub::Listener`. A second front-end, the GTK one, is built on the same hub (below, "The
|
|
67
|
+
GTK workbench"). These are static libraries inside the shared ones, which export only
|
|
68
|
+
`rcp/app.hpp` (hidden visibility for the rest).
|
|
69
|
+
|
|
70
|
+
## Applications
|
|
71
|
+
|
|
72
|
+
An application is an `rcp::AppInfo`: an id (its state, `$XDG_STATE_HOME/<id>/`: the
|
|
73
|
+
perspectives as left, the host log), a name (the title bar, `--help`, `--version`), a
|
|
74
|
+
version and its plugin directories (searched after `$MYRCP_PLUGIN_PATH`). It starts the
|
|
75
|
+
workbench one of two ways:
|
|
76
|
+
|
|
77
|
+
- **A program on libmyrcp**, as `examples/demo/main.cpp` is: `find_package(myrcp)` and
|
|
78
|
+
`myrcp::myrcp` (or `pkg-config myrcp`), then `return rcp::run(argc, argv, app);`
|
|
79
|
+
(`rcp::run_gtk` with `myrcp::myrcp-gtk`).
|
|
80
|
+
- **A product directory** for the launcher, for an application in Python that wants no
|
|
81
|
+
C++ build: `myrcp --product DIR` (or `$MYRCP_PRODUCT`) reads `DIR/product.toml`:
|
|
82
|
+
|
|
83
|
+
```toml
|
|
84
|
+
[product]
|
|
85
|
+
id = "songs"
|
|
86
|
+
name = "Songs"
|
|
87
|
+
version = "1.2.0"
|
|
88
|
+
plugins = ["plugins"] # relative to DIR; default: DIR itself
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The SDK is found next to the library that runs (`<prefix>/share/myrcp/python` beside
|
|
92
|
+
`<prefix>/lib/libmyrcp.so`, by `dladdr`), else in the source tree of a build tree, and
|
|
93
|
+
`$MYRCP_SDK` wins over both. Each plugin's manifest may name the Python it runs under
|
|
94
|
+
(below, "One host per interpreter"), so an application's plugins run in its own venv
|
|
95
|
+
while the workbench stays the same program.
|
|
96
|
+
|
|
97
|
+
An install (`cmake --install`, `./build.sh install`) has `bin/myrcp` (and `myrcp-gtk`),
|
|
98
|
+
`lib/libmyrcp.so` (and `libmyrcp-gtk.so`), `include/rcp/app.hpp`,
|
|
99
|
+
`lib/cmake/myrcp/` (the CMake package), `lib/pkgconfig/myrcp.pc` and the SDK in
|
|
100
|
+
`share/myrcp/python`.
|
|
101
|
+
|
|
102
|
+
## UI IR
|
|
103
|
+
|
|
104
|
+
A node is a msgpack map with `t` (type) plus properties. Styles are semantic names
|
|
105
|
+
(`normal dim bold title accent error warning ok`), so every tool looks the same and each
|
|
106
|
+
host maps them to its own colours (terminal theme → 16 ANSI colours by default).
|
|
107
|
+
|
|
108
|
+
| `t` | Properties | Interaction |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| `text` | `text` (string, or `[[text, style], ...]` spans), `style`, `wrap` (spans wrap word by word, keeping their styles) | |
|
|
111
|
+
| `vbox`, `hbox` | `children`, `gap` | |
|
|
112
|
+
| `panel` | `title`, `child` | border highlights when focus is inside |
|
|
113
|
+
| `list` | `id`, `items` (`{key, text, detail, style}`), `empty`, `selected` | `select`, `activate` (value: item key) |
|
|
114
|
+
| `button` | `id`, `label` | `press` |
|
|
115
|
+
| `field` | `id`, `value`, `placeholder` | `submit` (value: text) |
|
|
116
|
+
| `progress` | `value` (0..1), `label` | |
|
|
117
|
+
| `table` | `id`, `columns` (`{title, width, grow, align, clickable, sortable}`), `rows` (`{key, cells, style, depth, expandable, expanded}`), `offset`, `total`, `cursor` (`[index, seq]`, applied when `seq` changes), `header`, `empty`, `sortable`, `sorted` (`[column, descending]`), `follow` (`[index, nonce]`), `tree` (a column index: the rows are a tree drawn in that column, indented by `depth`, with arrows) | `select`, `activate`, `window`, `cell` (clicks on `clickable` columns, Shift/Ctrl+clicks: `{key, column, shift, ctrl}`), `sort` (`[column, descending, key at the cursor]`; column -1: unsorted), `expand`/`collapse` (a `tree` table: →/←, arrow clicks) |
|
|
118
|
+
| `tree` | `id`, `rows` (`{key, text, detail, depth, expandable, expanded, check: all/some/none, style}`, flattened by the plugin), windowed like `table` | `select`, `activate`, `expand`, `collapse`, `toggle` |
|
|
119
|
+
| `checkbox` | `id`, `label`, `checked` | `toggle` (value: the new state) |
|
|
120
|
+
| `log` | `id` (optional: focusable, scrollable), `lines` | |
|
|
121
|
+
| `modal` | `id`, `title`, `child` (the base, dimmed), `dialog`, `dialog_width`, `dialog_height` | `dismiss` (Escape) |
|
|
122
|
+
|
|
123
|
+
Every node takes layout hints: `grow` (flex weight), `width`, `height`; focusable ones
|
|
124
|
+
take `autofocus` (the initial focus of the view). A view that shows only, say, a mode
|
|
125
|
+
button while it loads has that focus by default until the `autofocus` widget appears; a
|
|
126
|
+
focus chosen with Tab or a click stays.
|
|
127
|
+
|
|
128
|
+
**Windowed tables.** A table sends only rows `offset .. offset+len(rows)` of `total`.
|
|
129
|
+
When the cursor scrolls past them the workbench sends a `window` event `[first, count]`
|
|
130
|
+
and the SDK answers with those rows, so a plan of 100 000 items costs what a screenful
|
|
131
|
+
does (`ui.lazy_rows(count, make_row)` builds only the rows sent).
|
|
132
|
+
|
|
133
|
+
**Sortable tables.** `ui.table(..., sortable=True)`: a click on a column's title (or
|
|
134
|
+
Alt+S, the next column; Alt+R, the other way) sends a `sort` event, and the host keeps
|
|
135
|
+
how each table is sorted, as it keeps windows. The SDK sorts before it cuts the window,
|
|
136
|
+
so the front-ends never need all the rows: by the cells' text in natural order (numbers
|
|
137
|
+
by value, sizes with their units), stably, empty cells last. The event carries the key
|
|
138
|
+
at the cursor; the SDK answers with `follow`, that row's new index, which the front-end
|
|
139
|
+
applies once, so the cursor stays on its row. A plugin's own `cursor` (an index in its
|
|
140
|
+
order) is translated into the sorted one; front-ends apply it by its `seq`, so a sort
|
|
141
|
+
alone does not move it. A plugin with more rows than it wants to build sorts them itself
|
|
142
|
+
(`on_sort(column, descending)`), and its rows are shown as given. The workbench
|
|
143
|
+
remembers the sort it asked for until the tree catches up, so quick keys add up.
|
|
144
|
+
|
|
145
|
+
**Mouse.** The workbench reports presses and the wheel (SGR mode; `MYRCP_MOUSE=0` turns
|
|
146
|
+
it off; Shift+drag still selects text). Every render records where each widget was
|
|
147
|
+
drawn, and a click maps to the same semantic events as keys: tabs switch, buttons press,
|
|
148
|
+
checkboxes toggle, rows select (a click on the cursor row activates), tree arrows
|
|
149
|
+
expand/collapse and boxes toggle, the wheel moves the cursor or scrolls a log. Plugins
|
|
150
|
+
never see coordinates.
|
|
151
|
+
|
|
152
|
+
**Context menus.** A right click (or Shift+F10, at the focused row) opens a menu where
|
|
153
|
+
it was: on a row, the cursor goes there first and the menu lists the view's commands
|
|
154
|
+
(its manifest's, without their "View: " prefix, with their keys), Properties, sorting
|
|
155
|
+
for a sortable table, then the tab's items (move to another region, maximize, close); on
|
|
156
|
+
a tab or a terminal view whose program has no use for the mouse, the tab's items. The
|
|
157
|
+
commands run as from the palette, so they act on that row.
|
|
158
|
+
|
|
159
|
+
**Modals.** While a tree has a `modal`, only its dialog takes focus and the view's own
|
|
160
|
+
key bindings are off (workbench keys such as Ctrl+P still work).
|
|
161
|
+
|
|
162
|
+
**Button presses carry the form**: the press event's value is `{form: {field id: text}}`
|
|
163
|
+
for every field in the view, so a dialog reads what was typed without a round trip per key.
|
|
164
|
+
Defaults: in a `vbox`, lists (and containers holding one) grow; in an `hbox`, text and
|
|
165
|
+
buttons take their natural width and everything else shares the rest.
|
|
166
|
+
|
|
167
|
+
Transient widget state lives in the host, not the plugin: list cursor and scroll, field
|
|
168
|
+
text and caret, focus. The plugin hears only semantic events. Two props are applied only
|
|
169
|
+
when their value changes, so a plugin can steer without owning the state:
|
|
170
|
+
`field.value` (e.g. clearing a field after submit) and `list.selected` (move the cursor
|
|
171
|
+
to a key, e.g. the directory you came from).
|
|
172
|
+
|
|
173
|
+
## Protocol
|
|
174
|
+
|
|
175
|
+
msgpack-RPC framing, as Neovim uses it: `[0, id, method, [params]]` request,
|
|
176
|
+
`[1, id, error, result]` response, `[2, method, [params]]` notification. `params` is
|
|
177
|
+
always one map.
|
|
178
|
+
|
|
179
|
+
Core → host (and back: the host also sends requests, e.g. `editor.open`):
|
|
180
|
+
|
|
181
|
+
| Message | Params | Result |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| request `initialize` | `protocol` | `plugins: [{id, name, views, commands, perspectives}]`, `perspectives` (the user's), `delegates`, `errors` |
|
|
184
|
+
| request `view.open` | `view` | `tree` |
|
|
185
|
+
| notify `view.event` | `view, widget, kind, value` | (host answers with `view.update`) |
|
|
186
|
+
| notify `view.close` | `view` | |
|
|
187
|
+
| notify `command.run` | `command, view` (active view), `widget, key, index` (the focused row) | |
|
|
188
|
+
| notify `job.cancel` | `job` | |
|
|
189
|
+
| notify `preferences.changed` | `values: {id: value}` (all of them before a host's views open, then what changed) | |
|
|
190
|
+
| notify `selection.changed` | `view, title, items` (the workbench selection; sent to every host) | |
|
|
191
|
+
| notify `shutdown` | | |
|
|
192
|
+
|
|
193
|
+
Host → core: `view.update {view, tree, title}`, `view.show {view}` (open/switch to,
|
|
194
|
+
e.g. `tool.job#photos`), `view.close {view}`, `status {text, style}`,
|
|
195
|
+
request `editor.open {path, line}` → `{waited}` (true: the editor ran in the foreground
|
|
196
|
+
and has exited), `compare.open {left, right, title}` (notify, or a request → `{waited}`),
|
|
197
|
+
`console.run {argv, cwd, title, name}`, `terminal.open {argv, cwd, title}`,
|
|
198
|
+
`job.begin {job, title}`, `job.progress {job, value, label}`, `job.end {job, ok, text}`.
|
|
199
|
+
|
|
200
|
+
**Consoles** (`core/src/console.hpp`): programs run for their output, owned by the hub
|
|
201
|
+
so the workbench shows them and any plugin starts one (`rcp.run_in_console(argv)`) —
|
|
202
|
+
the output of an Ansible run, an ffmpeg encode, a build. Each is a view `console#<n>`
|
|
203
|
+
(not saved in the arrangement): the command in a field (edit it, Enter runs it), the
|
|
204
|
+
output as rows (stdout and stderr as they come, colour codes dropped, `\r` progress
|
|
205
|
+
lines kept as their last state, 20 000 lines at most), the cursor following the end
|
|
206
|
+
while it is there. A line naming an existing `path:line` (compilers, test runners,
|
|
207
|
+
Python's `File "x", line n`) is a link: Enter opens it, and it is the selection.
|
|
208
|
+
`Ctrl+C` stops it (SIGINT, then SIGTERM, then SIGKILL, to its process group), `r` runs
|
|
209
|
+
it again; a run with the same `name` reuses its console. "Console: Run a command…"
|
|
210
|
+
opens an empty one.
|
|
211
|
+
|
|
212
|
+
**View instances.** A view id with `#arg` is an instance of that view for `arg` (one tab
|
|
213
|
+
per job, say; the demo's `demo.note#<colour>`). The plugin's View gets `self.arg`; key bindings scoped to the view
|
|
214
|
+
apply to all its instances.
|
|
215
|
+
|
|
216
|
+
**One host per interpreter.** A manifest may name the Python it needs:
|
|
217
|
+
`python = ["$TOOL_PYTHON", "../../.venv/bin/python"]` (first existing wins, relative to
|
|
218
|
+
the plugin directory). The primary host does not serve such a plugin; it
|
|
219
|
+
lists it under `delegates` in its `initialize` answer, and the workbench starts a host
|
|
220
|
+
with that interpreter and `MYRCP_HOST_PLUGINS=<ids>`. Each tool keeps its own venv.
|
|
221
|
+
|
|
222
|
+
The host coalesces re-renders (a job reporting progress 1000 times a second causes at
|
|
223
|
+
most one render per batch) and skips `view.update` when the tree did not change.
|
|
224
|
+
|
|
225
|
+
## Plugins
|
|
226
|
+
|
|
227
|
+
```toml
|
|
228
|
+
# examples/demo/plugins/demo/myrcp.toml
|
|
229
|
+
[plugin]
|
|
230
|
+
id = "demo"
|
|
231
|
+
name = "Demo"
|
|
232
|
+
module = "myrcp_demo.plugin" # imported on first use, must define `plugin`
|
|
233
|
+
|
|
234
|
+
[[views]]
|
|
235
|
+
id = "demo.files"
|
|
236
|
+
title = "Files"
|
|
237
|
+
startup = true
|
|
238
|
+
# region = "bottom" # main (default), side or bottom: where it opens
|
|
239
|
+
|
|
240
|
+
[[commands]]
|
|
241
|
+
id = "demo.files.up"
|
|
242
|
+
title = "Files: Parent directory"
|
|
243
|
+
key = "backspace"
|
|
244
|
+
view = "demo.files" # binding and palette entry only while this view is active
|
|
245
|
+
|
|
246
|
+
[[perspectives]] # a named arrangement: Perspective: Files in the palette
|
|
247
|
+
id = "files"
|
|
248
|
+
title = "Files"
|
|
249
|
+
main = ["demo.files"] # view ids per region, in tab order
|
|
250
|
+
bottom = ["demo.jobs", "problems"]
|
|
251
|
+
bottom_size = 25 # percent of the height (side_size: of the width)
|
|
252
|
+
|
|
253
|
+
[[preferences]] # a setting: demo.hidden, in the Preferences view
|
|
254
|
+
id = "hidden"
|
|
255
|
+
title = "Show dotfiles"
|
|
256
|
+
type = "bool" # string, bool, int, choice (choices = [...]), paths
|
|
257
|
+
description = "Whether Files lists names starting with a dot when it opens."
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Discovery: every directory in `MYRCP_PLUGIN_PATH` whose subdirectories contain a
|
|
261
|
+
`myrcp.toml`, and installed packages registering the entry point group `myrcp.plugins`
|
|
262
|
+
(value: the package holding `myrcp.toml`).
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
from myrcp import Plugin, View, ui
|
|
266
|
+
|
|
267
|
+
plugin = Plugin()
|
|
268
|
+
|
|
269
|
+
@plugin.view("demo.files")
|
|
270
|
+
class Files(View):
|
|
271
|
+
def setup(self): self.cwd = Path.cwd()
|
|
272
|
+
def render(self):
|
|
273
|
+
return ui.panel(str(self.cwd), ui.listbox("entries", items, on_activate=self.open))
|
|
274
|
+
@plugin.command("demo.files.up")
|
|
275
|
+
def up(self): self.cwd = self.cwd.parent
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
All plugin code (setup, render, handlers, commands, job `on_done`) runs on the host's
|
|
279
|
+
main thread. `rcp.run_job(title, fn, on_done=...)` runs `fn(job)` in a thread
|
|
280
|
+
(`job.progress()`, `job.check()` for cancellation); `on_done` applies the result back on
|
|
281
|
+
the main thread.
|
|
282
|
+
|
|
283
|
+
## Regions
|
|
284
|
+
|
|
285
|
+
The workbench has three regions, after Eclipse's editor area and its view stacks: main
|
|
286
|
+
(top left), side (its right) and bottom (below both). Each is a stack of tabs. Which
|
|
287
|
+
region a view is in belongs to the hub (`ViewData::region`, from the manifest's `region`,
|
|
288
|
+
changed with `Hub::set_region`); which view each region shows
|
|
289
|
+
and which region has the focus belong to the front-end.
|
|
290
|
+
|
|
291
|
+
- The terminal shows the side from 140 columns and the bottom from 34 rows. Below that,
|
|
292
|
+
or while maximized (Alt+Z), a region folds into main: its views become main's tabs, so
|
|
293
|
+
a small terminal works exactly as before regions. Tabs keep the hub's numbering
|
|
294
|
+
(Alt+N), whichever region they are in; Alt+←/→ stays within the focused region.
|
|
295
|
+
- Main's tabs are in the top bar, the side's there too past the separator, the bottom's
|
|
296
|
+
in a bar of their own. The focused region's active tab is highlighted; an unfocused
|
|
297
|
+
view still shows its cursor row, inactive. Each visible view asks for its table
|
|
298
|
+
windows and is drawn at its own size (a terminal view is resized to its region).
|
|
299
|
+
- F6 / Shift+F6 cycle the focus; Ctrl+\ then an arrow moves it in that direction, also
|
|
300
|
+
out of a terminal view; a click focuses the region it lands in, the wheel scrolls one
|
|
301
|
+
without focusing it.
|
|
302
|
+
- Sizes are the hub's (`Hub::sizes`, percent of the width and the height): `Ctrl+\`
|
|
303
|
+
Shift+arrows or a drag of the separator or the bottom's tab bar (SGR button-event
|
|
304
|
+
mouse mode, 1002; saved on release).
|
|
305
|
+
|
|
306
|
+
## Preferences
|
|
307
|
+
|
|
308
|
+
Settings with a declared type, after Eclipse's preference store, kept where a user
|
|
309
|
+
expects configuration: `$XDG_CONFIG_HOME/myrcp/preferences.toml`, a table per plugin
|
|
310
|
+
(`[workbench]`, `[search]`, `[demo]`, ...).
|
|
311
|
+
|
|
312
|
+
- Declared by the front-end (`declare_workbench_prefs`: the theme, the editor mode and
|
|
313
|
+
the mouse) and by plugins (`[[preferences]]`: `id`, `title`, `type`
|
|
314
|
+
string/bool/int/choice/paths, `default`, `choices`, `env`, `description`; the id
|
|
315
|
+
becomes `<plugin>.<id>`). The hub owns the values, so they are there before any host
|
|
316
|
+
is: the terminal's theme and mouse apply before the first draw.
|
|
317
|
+
- A value is its `env` variable's while that is set (the variables myrcp read before
|
|
318
|
+
preferences keep working, and win), else the file's if it fits the type, else the
|
|
319
|
+
default. The front-end hears changes (`Listener::prefs_changed`); every host gets all
|
|
320
|
+
values (`preferences.changed`) right after it registers, before its views open, then
|
|
321
|
+
each change; `Context.pref(id)` reads them and `View.on_preferences(changed)` hears
|
|
322
|
+
them, after which every view renders again.
|
|
323
|
+
- The file is read and written by a TOML subset of the hub's own (`prefs.cpp`: tables,
|
|
324
|
+
strings, bools, integers, arrays, comments), which rewrites only the lines of what it
|
|
325
|
+
sets, so hand edits and comments survive; setting the default removes the line. A
|
|
326
|
+
file with a syntax error is not written to until it is fixed (the view says where).
|
|
327
|
+
Ctrl+R (restart the hosts) and "Preferences: Edit the file" read it again.
|
|
328
|
+
- The Preferences view is the hub's own, in the IR like Problems: a row per setting
|
|
329
|
+
with where its value comes from, its description below, a dialog to change it (a
|
|
330
|
+
field, or the list of choices; Default; a note when an environment variable wins).
|
|
331
|
+
Its rows carry selection items, so Properties shows a setting too.
|
|
332
|
+
|
|
333
|
+
## Perspectives
|
|
334
|
+
|
|
335
|
+
A named arrangement of views for a task, after Eclipse's: which views are open, in which
|
|
336
|
+
region and tab order, and the regions' sizes. The hub owns them.
|
|
337
|
+
|
|
338
|
+
- Defined by plugins (`[[perspectives]]` in `myrcp.toml`: `id`, `title`, `main`/`side`/
|
|
339
|
+
`bottom` view ids, `side_size`/`bottom_size`) and by the user
|
|
340
|
+
(`$XDG_CONFIG_HOME/myrcp/perspectives.toml`, read by the main host, the same tables;
|
|
341
|
+
the user's win on an id). "default" is the manifests' startup views.
|
|
342
|
+
- An `Arrangement` is a perspective as left: views with regions in tab order, sizes,
|
|
343
|
+
what each region shows and the focus (the front-end's part, asked for with
|
|
344
|
+
`Listener::front_state`), and the definition's views at the time (`known`: a view
|
|
345
|
+
added to the definition since, say by a new plugin, opens next time).
|
|
346
|
+
- Every change of the arrangement (opening, closing, moving a view; sizes) is saved at
|
|
347
|
+
once, msgpack in `$XDG_STATE_HOME/myrcp/workbench.state`, written to a temporary file
|
|
348
|
+
and renamed; the active perspective's entry is replaced, the others kept. Terminal
|
|
349
|
+
views are not part of it and stay through switches.
|
|
350
|
+
- Switching saves the current one, closes the plugin and hub views the next one lacks,
|
|
351
|
+
opens the rest where it puts them (`Hub::apply`) and tells the front-end what to show
|
|
352
|
+
(`Listener::arranged`). Views whose host is not up yet wait in `pending_` at their
|
|
353
|
+
index, so tabs keep the arrangement's order whichever host answers first; the same
|
|
354
|
+
path opens the saved arrangement at startup, the hub's own views at once.
|
|
355
|
+
- A plugin view closed by a switch is disposed in its host; switching back opens it
|
|
356
|
+
again (its `arg` included), not its scroll position or its widgets' state.
|
|
357
|
+
|
|
358
|
+
## The selection service
|
|
359
|
+
|
|
360
|
+
What the cursor of the focused view is on is the workbench selection, after Eclipse's
|
|
361
|
+
selection service and NetBeans' Lookup, made of data rather than objects since plugins
|
|
362
|
+
live in separate processes.
|
|
363
|
+
|
|
364
|
+
- A row says what it is: `ui.row(key, cells, selection=Item("demo.colour", "blue",
|
|
365
|
+
path, line, props={...}))` (also `ui.item`, `ui.tree_row`) puts `sel: [items]` on the
|
|
366
|
+
row in the IR. An item has a `type` ("file", "problem", "search.match",
|
|
367
|
+
"outline.function", ...), a `label`, an optional `path`/`line` and ordered
|
|
368
|
+
`props`; a tree's own `sel` stands for the whole view.
|
|
369
|
+
- The front-end picks it (`view_selection` in `layout.cpp`): the cursor row of the
|
|
370
|
+
focused rows widget, else of the first one whose rows carry any, else the tree's
|
|
371
|
+
`sel`. A row without one makes an empty selection; a view without any (a terminal,
|
|
372
|
+
Properties itself) leaves the selection as it was, so focusing Properties keeps what
|
|
373
|
+
it shows. The terminal sets it when what the focused view offers changed (or the
|
|
374
|
+
focus moved), so a linked Neovim outside the workbench keeps the selection it made
|
|
375
|
+
until something here moves; a Neovim tab offers its cursor.
|
|
376
|
+
- The hub keeps it (`Hub::set_selection`) and tells every host (`selection.changed`):
|
|
377
|
+
`View.on_selection(selection)` runs in every view that defines it, and
|
|
378
|
+
`rcp.selection` is the last one heard (`selection.of_type("demo")`). The hub's services
|
|
379
|
+
hear it too: Outline follows the file and line it names, so a linked editor's cursor
|
|
380
|
+
moves the Outline's.
|
|
381
|
+
- **Properties** (`properties`, a hub-owned view at the side, Alt+Enter) shows it: where
|
|
382
|
+
it is from, the item's properties (a table of items when several), and the source
|
|
383
|
+
around `path:line` with the line marked; Enter opens a line in the editor.
|
|
384
|
+
- Producers: Problems (each problem at its place), Search and Tasks (each match at its
|
|
385
|
+
line), Outline (each symbol at its line), Consoles (`path:line` lines), a linked
|
|
386
|
+
editor's cursor, and the demo's Files, table and gallery.
|
|
387
|
+
|
|
388
|
+
## Built-in services: Search, Tasks, Outline
|
|
389
|
+
|
|
390
|
+
Features every application wants are the hub's own, as `Service`s
|
|
391
|
+
(`core/src/service.hpp`): a service declares views, commands (with keys) and preferences
|
|
392
|
+
the way a manifest does, builds its views' trees as UI IR itself (so both front-ends show
|
|
393
|
+
them with no code of their own), runs programs whose output the hub's event loop reads,
|
|
394
|
+
and hears the selection and preference changes. `Hub::builtin_view` covers them and the
|
|
395
|
+
hub's other views, which need no plugin host and open from a saved arrangement at once.
|
|
396
|
+
|
|
397
|
+
- **Search** (`search`, at the bottom; `Ctrl+F`, "Search: Find in files"): text in the
|
|
398
|
+
files below the working directory, as a tree of files and their matching lines, by
|
|
399
|
+
ripgrep when it is installed, else `git grep` in a work tree, else `grep` (each leaves
|
|
400
|
+
out what version control ignores as well as it can; `core/src/search.cpp`). Regex and
|
|
401
|
+
match case are check boxes; `F5` searches again, `*` and `/` open and close every
|
|
402
|
+
file; Enter on a line opens it in the editor. At most 10 000 matches are kept.
|
|
403
|
+
- **Tasks** (`tasks`, at the bottom; "Tasks: Show"): the comments tagged with the
|
|
404
|
+
preference `search.tags` (default `FIXME, XXX, TODO, HACK`, the first ones the most
|
|
405
|
+
urgent), most urgent first; `r` scans again.
|
|
406
|
+
- **Outline** (`outline`, at the side; "Outline: Show"): the structure of the file the
|
|
407
|
+
selection is in, following its line (the symbol around it is the cursor's): Python by
|
|
408
|
+
its indentation outside strings and brackets, Markdown headings, TOML/INI sections,
|
|
409
|
+
YAML keys, and C/C++, JavaScript/TypeScript, Rust, Go and shell by the shapes of their
|
|
410
|
+
lines (`core/src/outline.cpp`). `f` stops it following; Enter goes to a symbol.
|
|
411
|
+
|
|
412
|
+
## Problem markers
|
|
413
|
+
|
|
414
|
+
A plugin reports problems as named sets (`rcp.set_markers(name, [Marker(...)])`,
|
|
415
|
+
`markers.set` on the wire): a message, a severity, a place in a file (path, 1-based line)
|
|
416
|
+
and/or a view that shows it. A set replaces the plugin's previous one of that name; a
|
|
417
|
+
host that exits takes its sets along.
|
|
418
|
+
|
|
419
|
+
The hub lists them in a **Problems** view (F8, or View: Open Problems) whose tree the hub
|
|
420
|
+
builds itself, as UI IR, so the front-end shows it with no code of their own. Errors come
|
|
421
|
+
first; paths under the working directory are relative. Enter opens the place: the view
|
|
422
|
+
that shows it, and the file at its line (`Listener::open_location`: in a linked Neovim or Vim when
|
|
423
|
+
there is one, else as an editor request). An editor that understands diagnostics gets the
|
|
424
|
+
markers too (Neovim and Vim, above).
|
|
425
|
+
|
|
426
|
+
The demo reports some for trying it out: "Files: Find TODOs" (TODO/FIXME lines at their
|
|
427
|
+
place) and "Demo: Problems that name views" (markers whose place is a view, one of them
|
|
428
|
+
a view instance).
|
|
429
|
+
|
|
430
|
+
## Terminal views
|
|
431
|
+
|
|
432
|
+
A terminal view is a native view: the hub lists it among the tabs (`Hub::open_native`), the
|
|
433
|
+
terminal front-end owns its process and screen. Keys go to the program, except Alt+1…9 and
|
|
434
|
+
Alt+←/→ (tabs); Ctrl+\ makes the next key the workbench's (Ctrl+\ Ctrl+P: commands,
|
|
435
|
+
Ctrl+\ Ctrl+W: close, Ctrl+\ ↑: the region above; Ctrl+\ twice sends one). Shells open
|
|
436
|
+
at the bottom, editors in the main area. Mouse presses go to programs that asked for
|
|
437
|
+
the mouse. Cells keep the program's colours (`Style::raw`); its default colours are the
|
|
438
|
+
theme's, so vim sits on blue in the classic theme. The tab closes when the program exits.
|
|
439
|
+
|
|
440
|
+
`editor.open` is a request the workbench may answer later (`Rpc::on_request_later`): an
|
|
441
|
+
editor in a tab answers `{waited: true}` when it exits, so `on_close` runs at the right time.
|
|
442
|
+
|
|
443
|
+
## Themes
|
|
444
|
+
|
|
445
|
+
Plugins name semantic styles only; a theme maps them to colours. Cells belong to a
|
|
446
|
+
layer (window or dialog) and each layer has its own palette, so dialogs can look
|
|
447
|
+
different from the windows under them. Modals and the command palette are dialog
|
|
448
|
+
layer and cast a shadow.
|
|
449
|
+
|
|
450
|
+
| Theme | Look |
|
|
451
|
+
|---|---|
|
|
452
|
+
| `terminal` (default) | the terminal's 16 colours on its own background |
|
|
453
|
+
| `classic` | blue windows, grey dialogs with drop shadows, grey bars with red hotkeys, green buttons, focused frames in double lines; after the 1990s text-mode IDEs |
|
|
454
|
+
|
|
455
|
+
`MYRCP_THEME=classic`, or "Theme: …" in the command palette.
|
|
456
|
+
|
|
457
|
+
## The GTK workbench
|
|
458
|
+
|
|
459
|
+
`libmyrcp-gtk` and `myrcp-gtk` (`cmake -DMYRCP_GTK=ON`, or `./build.sh gtk`; GTK 4.12
|
|
460
|
+
or later, VTE for GTK 4 optional; an application calls `rcp::run_gtk`) runs the same plugin hosts, views, commands, perspectives, preferences,
|
|
461
|
+
markers and editor links as the terminal workbench, through the same `Hub`. Plugins
|
|
462
|
+
need nothing new: their UI IR becomes GTK widgets. It translates the workbench's
|
|
463
|
+
concepts into GTK's own ways rather than imitating the terminal's look:
|
|
464
|
+
|
|
465
|
+
| Concept | In GTK |
|
|
466
|
+
|---|---|
|
|
467
|
+
| regions | three notebooks in panes: main and side above, the bottom below; empty ones hide. Tabs drag between them (the view's region changes), the panes' handles set the sizes kept with the perspective; maximize shows the focused region alone |
|
|
468
|
+
| `text` | a label; styles as Pango colours chosen for a light or dark theme; a line too long is cut with an ellipsis |
|
|
469
|
+
| `vbox` / `hbox`, `panel` | boxes (`grow` as expand; `width`/`height` in the UI font's characters and lines), a frame |
|
|
470
|
+
| `list`, `tree` | a `GtkListView`: the cursor is the selection, double click or Enter activates, tree rows have expander arrows and check buttons (`some`: inconsistent) |
|
|
471
|
+
| `table` | a `GtkColumnView`; a sortable one's column titles send `sort` (the plugin sorts; GTK shows the arrow). Windowed tables are a list model of `total` rows: a row not sent yet shows `…` and asks for its window |
|
|
472
|
+
| `field`, `button`, `checkbox`, `progress` | an entry (Enter submits), a button (the press carries the form), a check button, a progress bar |
|
|
473
|
+
| `log` | a monospace text view that stays at the end while it is there |
|
|
474
|
+
| `modal` | a modal dialog window over the view, whose base is insensitive meanwhile; closing it or Escape dismisses |
|
|
475
|
+
| palette, Go to Anything | a search window (Ctrl+P; Ctrl+O lists views, problems and the files from `rg --files`, else `git ls-files`, else `find`) |
|
|
476
|
+
| context menus | popover menus on a right click or Shift+F10/Menu, the commands' keys shown as accelerators |
|
|
477
|
+
| perspectives, views, plugins | the header bar: Back/Forward, a perspective menu, the palette button and the main menu |
|
|
478
|
+
| status, jobs | a status bar; the running jobs' progress and a cancel button at its right |
|
|
479
|
+
| terminal views | VTE tabs: shells (Ctrl+T), and files open in `$VISUAL`/`$EDITOR` there, a Vim or Neovim linked both ways as in the terminal's `tab` mode |
|
|
480
|
+
|
|
481
|
+
The tree is reconciled into the widgets as plugins send new ones (a node keeps its
|
|
482
|
+
widget while its type and id stay), so focus, scrolling, cursors and typed text survive
|
|
483
|
+
updates, and a dialog opening over a view keeps the view's widgets as its base. Keys
|
|
484
|
+
are bound as in the terminal workbench, except that navigation keys stay with the
|
|
485
|
+
widgets, plain keys are not commands while a field has the focus, and in a terminal
|
|
486
|
+
view the program has the keys but for Alt+1…9, Alt+←/→, Ctrl+PgUp/PgDn and
|
|
487
|
+
Ctrl+Shift+<key> (the workbench's Ctrl+<key>). The theme preference is the terminal's;
|
|
488
|
+
GTK follows the desktop's theme. Without VTE there are no terminal views, so files have
|
|
489
|
+
nowhere to open but a linked editor of your own.
|
|
490
|
+
|
|
491
|
+
## Editors
|
|
492
|
+
|
|
493
|
+
| Level | How | Status |
|
|
494
|
+
|---|---|---|
|
|
495
|
+
| 0 | Hand off to `$VISUAL`/`$EDITOR` (`+line path`). `MYRCP_EDITOR_MODE`: `suspend` (default; the TUI steps aside like git does), `tmux-window`, `tmux-split` | done |
|
|
496
|
+
| 1 | Terminal views: a pty + libvterm (the emulator Neovim uses) as a native view (`core/src/term.cpp`). `MYRCP_EDITOR_MODE=tab` opens files in a tab of their own, answered to the plugin when the editor exits; Ctrl+T opens a shell; plugins run programs with `rcp.open_terminal(argv)` | done |
|
|
497
|
+
| 2 | Neovim in a terminal view, also listening on a socket of ours (`--listen`): problem markers become `vim.diagnostic` entries (signs, virtual text) for every buffer, and opening a problem jumps in the open Neovim instead of starting another editor. Recognised by the editor command's name (`nvim…`) | done |
|
|
498
|
+
| 3 | The link both ways, in every editor mode (`core/src/editor_link.hpp`, `NvimLink` in `nvim.cpp`): Neovim in a tab, in a tmux window or split we open (`--listen` on our socket; the pane id from `tmux … -P` brings it forward), or the user's own (the `workbench.nvim_server` preference). Files and problems open in a linked Neovim rather than a new editor. Lua we send on connect (`nvim_get_api_info` gives our channel) notifies us where the cursor rests (debounced, only when the line or file changes) and its own diagnostics (all namespaces but ours, only when they change): the first become the workbench selection (from a tab only while it has the focus), the second a marker set `editor:<socket>` in Problems, never pushed back to it | done |
|
|
499
|
+
| compare | `rcp.compare(left, right)` (`compare.open`, a request when the plugin waits): `EditorLink::diff` in the linked editor (Neovim: `tabedit`, `diffthis`, `vertical diffsplit`; Vim: the same as a channel `ex` command), else `workbench.diff` (`vimdiff`) run as the editor would be: a tab, tmux, or in the foreground | done |
|
|
500
|
+
| 2+ | `nvim --embed` with `ext_linegrid` (Neovim's grid drawn straight into ours, without a pty) | planned |
|
|
501
|
+
| 2' + 3 | Vim, the same both ways (`VimLink` in `core/src/vim.cpp`). Vim has no server we can reach without `+clientserver`, so we are the server: a unix socket of ours per Vim we start (`--cmd "let g:myrcp_socket=…" -S <script>`), and a shared one (`VimListener`, `$XDG_RUNTIME_DIR/myrcp-vim.sock` or `workbench.vim_socket`) that the user's own Vims connect to with the same script installed as a plugin (`myrcp --vim-script`; it retries every two seconds). JSON channel messages, a line each (`core/src/json.cpp`): we send `["call", "MyrcpMarkers"/"MyrcpGoto", args]`; it sends `[n, {kind: hello/cursor/diagnostics}]`. Markers are signs plus virtual text (`prop_add(…, {text, text_align: 'after'})`); its own diagnostics are the quickfix list (`QuickFixCmdPost`). `EditorLink` is what the workbench sees of either kind | done |
|
|
502
|
+
|
|
503
|
+
## Roadmap
|
|
504
|
+
|
|
505
|
+
1. **Core + protocol + SDK**: terminal host, msgpack-RPC, the IR above, the Python host,
|
|
506
|
+
editor handoff, a demo plugin. Done.
|
|
507
|
+
2. **Widgets from a real port**: `table`, `checkbox`, `modal`, `log`, `tree`, mouse,
|
|
508
|
+
view instances, per-venv hosts, tables sorted by column. Done.
|
|
509
|
+
3. **Workbench services**: problem markers, terminal views, regions, the selection
|
|
510
|
+
service with a Properties view, perspectives, preferences, consoles, the Neovim and
|
|
511
|
+
Vim links both ways (editor levels 1–3), Search, Tasks and Outline built in, and the
|
|
512
|
+
GTK front-end. Done.
|
|
513
|
+
4. **A framework**: libmyrcp with a public API, the launcher and product directories,
|
|
514
|
+
the demo as an application on the library. Done. Next: `nvim --embed` (editor level
|
|
515
|
+
2+), Nodes/explorer views (NetBeans), a stable protocol version for plugins written
|
|
516
|
+
outside this repository.
|
|
517
|
+
|
|
518
|
+
## Ideas taken from Eclipse and NetBeans
|
|
519
|
+
|
|
520
|
+
- Declarative extensions with lazy activation (Eclipse `plugin.xml`, VS Code `contributes`).
|
|
521
|
+
- Commands separate from handlers and keybindings (Eclipse).
|
|
522
|
+
- Jobs with progress and cancel (Eclipse Jobs API).
|
|
523
|
+
- Selection service (Eclipse) and Lookup (NetBeans), as typed data items rather than
|
|
524
|
+
adapters; the Properties view (Eclipse); problem markers (Eclipse).
|
|
525
|
+
- Perspectives (Eclipse), as data the hub saves rather than layouts per window; the
|
|
526
|
+
preference store (Eclipse), as a TOML file the user may edit too.
|
|
527
|
+
- Planned: Nodes/explorer views (NetBeans).
|
|
528
|
+
- Products (Eclipse RCP): an application is a name and its plugins, on the same
|
|
529
|
+
workbench.
|
|
530
|
+
- Left out: OSGi class loading. Process isolation and a versioned protocol cover the
|
|
531
|
+
same need.
|