@fresh-editor/fresh-editor 0.2.25 → 0.3.1

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.
Files changed (82) hide show
  1. package/CHANGELOG.md +216 -0
  2. package/README.md +6 -0
  3. package/package.json +1 -1
  4. package/plugins/astro-lsp.ts +6 -12
  5. package/plugins/audit_mode.i18n.json +14 -14
  6. package/plugins/audit_mode.ts +182 -146
  7. package/plugins/bash-lsp.ts +15 -22
  8. package/plugins/clangd-lsp.ts +15 -24
  9. package/plugins/clojure-lsp.ts +9 -12
  10. package/plugins/cmake-lsp.ts +9 -12
  11. package/plugins/code-tour.ts +15 -16
  12. package/plugins/config-schema.json +79 -6
  13. package/plugins/csharp_support.ts +25 -30
  14. package/plugins/css-lsp.ts +15 -22
  15. package/plugins/dart-lsp.ts +9 -12
  16. package/plugins/dashboard.ts +1903 -0
  17. package/plugins/devcontainer.i18n.json +1472 -0
  18. package/plugins/devcontainer.ts +2793 -0
  19. package/plugins/diagnostics_panel.ts +10 -17
  20. package/plugins/elixir-lsp.ts +9 -12
  21. package/plugins/erlang-lsp.ts +9 -12
  22. package/plugins/examples/bookmarks.ts +10 -16
  23. package/plugins/find_references.ts +5 -9
  24. package/plugins/flash.ts +577 -0
  25. package/plugins/fsharp-lsp.ts +9 -12
  26. package/plugins/git_explorer.ts +16 -20
  27. package/plugins/git_gutter.ts +65 -79
  28. package/plugins/git_log.i18n.json +14 -42
  29. package/plugins/git_log.ts +19 -9
  30. package/plugins/gleam-lsp.ts +9 -12
  31. package/plugins/go-lsp.ts +15 -22
  32. package/plugins/graphql-lsp.ts +9 -12
  33. package/plugins/haskell-lsp.ts +9 -12
  34. package/plugins/html-lsp.ts +15 -24
  35. package/plugins/java-lsp.ts +9 -12
  36. package/plugins/json-lsp.ts +15 -24
  37. package/plugins/julia-lsp.ts +9 -12
  38. package/plugins/kotlin-lsp.ts +15 -22
  39. package/plugins/latex-lsp.ts +9 -12
  40. package/plugins/lib/fresh.d.ts +603 -0
  41. package/plugins/lua-lsp.ts +15 -22
  42. package/plugins/markdown_compose.ts +132 -128
  43. package/plugins/markdown_source.ts +8 -10
  44. package/plugins/marksman-lsp.ts +9 -12
  45. package/plugins/merge_conflict.ts +15 -17
  46. package/plugins/nim-lsp.ts +9 -12
  47. package/plugins/nix-lsp.ts +9 -12
  48. package/plugins/nushell-lsp.ts +9 -12
  49. package/plugins/ocaml-lsp.ts +9 -12
  50. package/plugins/odin-lsp.ts +15 -22
  51. package/plugins/path_complete.ts +5 -6
  52. package/plugins/perl-lsp.ts +9 -12
  53. package/plugins/php-lsp.ts +15 -22
  54. package/plugins/pkg.ts +10 -21
  55. package/plugins/protobuf-lsp.ts +9 -12
  56. package/plugins/python-lsp.ts +15 -24
  57. package/plugins/r-lsp.ts +9 -12
  58. package/plugins/ruby-lsp.ts +15 -22
  59. package/plugins/rust-lsp.ts +18 -28
  60. package/plugins/scala-lsp.ts +9 -12
  61. package/plugins/schemas/theme.schema.json +126 -0
  62. package/plugins/search_replace.ts +10 -13
  63. package/plugins/solidity-lsp.ts +9 -12
  64. package/plugins/sql-lsp.ts +9 -12
  65. package/plugins/svelte-lsp.ts +9 -12
  66. package/plugins/swift-lsp.ts +9 -12
  67. package/plugins/tailwindcss-lsp.ts +9 -12
  68. package/plugins/templ-lsp.ts +9 -12
  69. package/plugins/terraform-lsp.ts +9 -12
  70. package/plugins/theme_editor.i18n.json +98 -14
  71. package/plugins/theme_editor.ts +156 -209
  72. package/plugins/toml-lsp.ts +15 -22
  73. package/plugins/tsconfig.json +100 -0
  74. package/plugins/typescript-lsp.ts +15 -24
  75. package/plugins/typst-lsp.ts +15 -22
  76. package/plugins/vi_mode.ts +77 -290
  77. package/plugins/vue-lsp.ts +9 -12
  78. package/plugins/yaml-lsp.ts +15 -22
  79. package/plugins/zig-lsp.ts +9 -12
  80. package/themes/high-contrast.json +2 -2
  81. package/themes/nord.json +4 -0
  82. package/themes/solarized-dark.json +4 -0
@@ -70,6 +70,35 @@ interface MouseClickHookArgs {
70
70
  /** 0-indexed byte column inside the buffer row. */
71
71
  buffer_col: number | null;
72
72
  }
73
+ /**
74
+ * Registry of typed plugin APIs surfaced through
75
+ * `editor.exportPluginApi` / `editor.getPluginApi`.
76
+ *
77
+ * Plugins that want their surface to be typed for downstream
78
+ * consumers augment this interface in their own source:
79
+ *
80
+ * ```ts
81
+ * // in my_plugin.ts
82
+ * export type MyPluginApi = { doThing(): void };
83
+ * declare global {
84
+ * interface FreshPluginRegistry {
85
+ * "my-plugin": MyPluginApi;
86
+ * }
87
+ * }
88
+ * ```
89
+ *
90
+ * `editor.getPluginApi("my-plugin")` then returns
91
+ * `MyPluginApi | null` without any `as`-cast on the consumer side.
92
+ * Plugins that skip the augmentation still work — the untyped
93
+ * `getPluginApi<T = unknown>(name: string): T | null` overload
94
+ * takes over.
95
+ *
96
+ * Each plugin's augmentation is emitted to
97
+ * `<config_dir>/types/plugins.d.ts` at load time (via oxc's
98
+ * isolated-declarations), so init.ts sees every loaded plugin's
99
+ * registry entry automatically.
100
+ */
101
+ interface FreshPluginRegistry {}
73
102
  type TextPropertyEntry = {
74
103
  /**
75
104
  * Text content for this entry
@@ -211,6 +240,44 @@ type ViewportInfo = {
211
240
  */
212
241
  height: number;
213
242
  };
243
+ type KeyEventPayload = {
244
+ /**
245
+ * Key name (e.g. `"a"`, `"escape"`, `"f1"`).
246
+ */
247
+ key: string;
248
+ /**
249
+ * Ctrl held.
250
+ */
251
+ ctrl: boolean;
252
+ /**
253
+ * Alt held.
254
+ */
255
+ alt: boolean;
256
+ /**
257
+ * Shift held (only meaningful for non-character keys; for
258
+ * printable characters the case is already encoded in `key`).
259
+ */
260
+ shift: boolean;
261
+ /**
262
+ * Super / Cmd / Meta held.
263
+ */
264
+ meta: boolean;
265
+ };
266
+ type SplitSnapshot = {
267
+ /**
268
+ * Stable split identifier; matches the values used by
269
+ * `setSplitBuffer`, `focusSplit`, `getSplitByLabel`, etc.
270
+ */
271
+ splitId: number;
272
+ /**
273
+ * Buffer currently shown in this split.
274
+ */
275
+ bufferId: BufferId;
276
+ /**
277
+ * Viewport (top byte / dimensions) for this split's active buffer.
278
+ */
279
+ viewport: ViewportInfo;
280
+ };
214
281
  type LayoutHints = {
215
282
  /**
216
283
  * Optional compose width for centering/wrapping
@@ -343,6 +410,15 @@ type BufferInfo = {
343
410
  * refreshing itself for a preview tab.
344
411
  */
345
412
  is_preview: boolean;
413
+ /**
414
+ * Split ids that currently hold this buffer (empty when the buffer is
415
+ * open but not visible in any split — e.g. background-opened tabs
416
+ * that haven't been focused). Lets plugins implement "focus existing
417
+ * buffer if visible, else open new" without having to track split
418
+ * ids across editor restarts (which reassign them). The list is a
419
+ * snapshot at the last `update_plugin_state_snapshot` tick.
420
+ */
421
+ splits: number[];
346
422
  };
347
423
  type JsDiagnostic = {
348
424
  /**
@@ -497,6 +573,15 @@ type CreateTerminalOptions = {
497
573
  * Whether to focus the new terminal split (default: true)
498
574
  */
499
575
  focus?: boolean;
576
+ /**
577
+ * Whether this terminal is part of the user's persisted workspace.
578
+ * Defaults to `false` for plugin-created terminals — they are typically
579
+ * one-off tool UIs (rebuilds, exec shells, build output) and should
580
+ * start with empty scrollback on each invocation. Set to `true` only
581
+ * when the plugin owns a terminal that the user should see restored
582
+ * across editor restarts.
583
+ */
584
+ persistent?: boolean;
500
585
  };
501
586
  type CursorInfo = {
502
587
  /**
@@ -584,6 +669,56 @@ type GrammarInfoSnapshot = {
584
669
  */
585
670
  short_name: string | null;
586
671
  };
672
+ type AnimationRect = {
673
+ x: number;
674
+ y: number;
675
+ width: number;
676
+ height: number;
677
+ };
678
+ type PluginAnimationEdge = "top" | "bottom" | "left" | "right";
679
+ type PluginAnimationKind = {
680
+ "kind": "slideIn";
681
+ from: PluginAnimationEdge;
682
+ durationMs: number;
683
+ delayMs: number;
684
+ };
685
+ type AuthorityFilesystem = {
686
+ kind: "local";
687
+ };
688
+ type AuthoritySpawner = {
689
+ kind: "local";
690
+ } | {
691
+ kind: "docker-exec";
692
+ container_id: string;
693
+ user?: string | null;
694
+ workspace?: string | null;
695
+ env?: [string, string][];
696
+ };
697
+ type AuthorityTerminalWrapper = {
698
+ kind: "host-shell";
699
+ } | {
700
+ kind: "explicit";
701
+ command: string;
702
+ args: string[];
703
+ manages_cwd?: boolean;
704
+ };
705
+ type AuthorityPayload = {
706
+ filesystem: AuthorityFilesystem;
707
+ spawner: AuthoritySpawner;
708
+ terminal_wrapper: AuthorityTerminalWrapper;
709
+ display_label?: string;
710
+ /**
711
+ * Optional host↔remote workspace path mapping. The dev-container
712
+ * authority sets both roots (editor.getCwd() on host;
713
+ * remoteWorkspaceFolder on container) so LSP URIs translate at the
714
+ * host/container boundary. Local and SSH authorities omit it.
715
+ */
716
+ path_translation?: PathTranslationSpec;
717
+ };
718
+ type PathTranslationSpec = {
719
+ host_root: string;
720
+ remote_root: string;
721
+ };
587
722
  type BackgroundProcessResult = {
588
723
  /**
589
724
  * Unique process ID for later reference
@@ -808,6 +943,21 @@ type LspServerPackConfig = {
808
943
  */
809
944
  processLimits: ProcessLimitsPackConfig | null;
810
945
  };
946
+ type RemoteIndicatorStatePayload = {
947
+ kind: "local";
948
+ } | {
949
+ kind: "connecting";
950
+ label?: string | null;
951
+ } | {
952
+ kind: "connected";
953
+ label?: string | null;
954
+ } | {
955
+ kind: "failed_attach";
956
+ error?: string | null;
957
+ } | {
958
+ kind: "disconnected";
959
+ label?: string | null;
960
+ };
811
961
  type ReplaceResult = {
812
962
  /**
813
963
  * Number of replacements made
@@ -860,6 +1010,30 @@ interface EditorAPI {
860
1010
  */
861
1011
  apiVersion(): number;
862
1012
  /**
1013
+ * The name of the plugin this `editor` handle belongs to. Used by the
1014
+ * M3 plugin-API plane (`exportPluginApi` tags the exporter). Plugin
1015
+ * authors generally don't call this directly.
1016
+ */
1017
+ pluginName(): string;
1018
+ /**
1019
+ * Publish a typed API surface under `name`. Another plugin (typically
1020
+ * `init.ts`) can reach it later via `getPluginApi(name)`. Calling
1021
+ * again with the same `name` replaces the previous registration
1022
+ * (idempotent — reload works). Exports are auto-dropped when the
1023
+ * calling plugin is unloaded.
1024
+ *
1025
+ * Returns `true` on success. Rejects with a TypeError if `name` is
1026
+ * empty or `api` is not an object (functions and primitives are not
1027
+ * valid API surfaces — only objects).
1028
+ */
1029
+ exportPluginApi(name: string, api: unknown): boolean;
1030
+ /**
1031
+ * Look up a plugin API previously published via `exportPluginApi`.
1032
+ * Returns the api object (restored into the caller's context) or
1033
+ * `null` if no plugin exports under that name.
1034
+ */
1035
+ getPluginApi(name: string): unknown | null;
1036
+ /**
863
1037
  * Get the active buffer ID (0 if none)
864
1038
  */
865
1039
  getActiveBufferId(): number;
@@ -957,6 +1131,15 @@ interface EditorAPI {
957
1131
  */
958
1132
  getViewport(): ViewportInfo | null;
959
1133
  /**
1134
+ * List every split with its active buffer and viewport.
1135
+ *
1136
+ * Plugins that need to operate on every visible buffer
1137
+ * simultaneously (multi-split flash labels, syncing decorations
1138
+ * across panes, …) iterate this list rather than only seeing
1139
+ * `getViewport()`'s active-split data. Order is unspecified.
1140
+ */
1141
+ listSplits(): SplitSnapshot[];
1142
+ /**
960
1143
  * Get the line number (0-indexed) of the primary cursor
961
1144
  */
962
1145
  getCursorLine(): number;
@@ -1029,6 +1212,21 @@ interface EditorAPI {
1029
1212
  */
1030
1213
  closeBuffer(bufferId: number): boolean;
1031
1214
  /**
1215
+ * Start a frame-buffer animation over an arbitrary screen region.
1216
+ * Returns an animation id usable with `cancelAnimation`.
1217
+ */
1218
+ animateArea(rect: AnimationRect, kind: PluginAnimationKind): number;
1219
+ /**
1220
+ * Start an animation over the on-screen Rect currently occupied by a
1221
+ * virtual buffer. No-op if the buffer is not visible.
1222
+ */
1223
+ animateVirtualBuffer(bufferId: number, kind: PluginAnimationKind): number;
1224
+ /**
1225
+ * Cancel an animation previously started via `animateArea` or
1226
+ * `animateVirtualBuffer`. No-op if the ID is unknown or already done.
1227
+ */
1228
+ cancelAnimation(id: number): boolean;
1229
+ /**
1032
1230
  * Subscribe to an editor event
1033
1231
  */
1034
1232
  on(eventName: string, handlerName: string): void;
@@ -1045,8 +1243,26 @@ interface EditorAPI {
1045
1243
  */
1046
1244
  getCwd(): string;
1047
1245
  /**
1246
+ * Get the active authority's display label.
1247
+ *
1248
+ * Empty means the local (default) authority. A non-empty value
1249
+ * means a plugin-installed or SSH authority is in effect (e.g.
1250
+ * `"Container:abc123def456"` for a devcontainer). Intended as a
1251
+ * simple "am I already attached?" check that survives editor
1252
+ * restarts — the label lives on the `Editor` state snapshot so it
1253
+ * is fresh after the authority-transition restart flow.
1254
+ */
1255
+ getAuthorityLabel(): string;
1256
+ /**
1048
1257
  * Join path components (variadic - accepts multiple string arguments)
1049
1258
  * Always uses forward slashes for cross-platform consistency (like Node.js path.posix.join)
1259
+ *
1260
+ * Preserves up to 2 leading slashes, which matters on Windows: Rust's
1261
+ * `Path::canonicalize` returns `\\?\`-prefixed paths, and `editor.getCwd()`
1262
+ * surfaces that to plugin code verbatim. After the backslash→slash
1263
+ * normalization the prefix becomes `//?/C:/...`; collapsing the leading
1264
+ * `//` to a single `/` yields `/?/C:/...`, which every filesystem API on
1265
+ * Windows rejects, breaking `findConfig()`-style plugin logic.
1050
1266
  */
1051
1267
  pathJoin(...parts: string[]): string;
1052
1268
  /**
@@ -1129,6 +1345,17 @@ interface EditorAPI {
1129
1345
  */
1130
1346
  getTempDir(): string;
1131
1347
  /**
1348
+ * Parse a JSONC (JSON with comments) string into a JS value.
1349
+ *
1350
+ * Accepts the JSONC superset: line and block comments, trailing
1351
+ * commas, single-quoted strings, and unquoted object keys — matching
1352
+ * devcontainer.json / tsconfig.json / VS Code settings.json.
1353
+ *
1354
+ * Throws a JS error (catchable with try/catch) when the input is not
1355
+ * valid JSONC, like `JSON.parse` does for invalid JSON.
1356
+ */
1357
+ parseJsonc(text: string): unknown;
1358
+ /**
1132
1359
  * Get current config as JS object.
1133
1360
  *
1134
1361
  * The snapshot holds an `Arc<serde_json::Value>` that was serialized
@@ -1146,6 +1373,21 @@ interface EditorAPI {
1146
1373
  */
1147
1374
  reloadConfig(): void;
1148
1375
  /**
1376
+ * Set a single config setting in the runtime layer for this session.
1377
+ *
1378
+ * `path` is dot-separated (e.g. `"editor.tab_size"`). `value` is any JSON
1379
+ * value in the shape the setting expects. The write lives in an
1380
+ * in-memory layer scoped to the calling plugin — it does not modify
1381
+ * `config.json`, and unloading the plugin (or reloading init.ts) drops
1382
+ * it. Intended use is `init.ts` running a conditional:
1383
+ * `if (editor.getEnv("SSH_TTY")) editor.setSetting("terminal.mouse", false);`
1384
+ *
1385
+ * Returns `true` if the write was queued. The actual update is
1386
+ * asynchronous; a subsequent `getConfig()` will reflect it after the
1387
+ * editor processes the command.
1388
+ */
1389
+ setSetting(path: string, value: unknown): boolean;
1390
+ /**
1149
1391
  * Reload theme registry from disk
1150
1392
  * Call this after installing theme packages or saving new themes
1151
1393
  */
@@ -1197,6 +1439,17 @@ interface EditorAPI {
1197
1439
  */
1198
1440
  applyTheme(themeName: string): boolean;
1199
1441
  /**
1442
+ * Override theme colors in-memory for the running session. `overrides`
1443
+ * is a JS object mapping `"section.field"` keys (same namespace as
1444
+ * `getThemeSchema`) to `[r, g, b]` triplets (0–255 each).
1445
+ *
1446
+ * Unknown keys are dropped silently; out-of-range values are clamped
1447
+ * to `0..=255`. Overrides survive until the next `applyTheme` call
1448
+ * (which replaces the whole `Theme`). Intended for fast animation
1449
+ * loops from `init.ts` — no disk I/O, no theme-registry rescan.
1450
+ */
1451
+ overrideThemeColors(overrides: unknown): boolean;
1452
+ /**
1200
1453
  * Get theme schema as JS object
1201
1454
  */
1202
1455
  getThemeSchema(): unknown;
@@ -1380,6 +1633,14 @@ interface EditorAPI {
1380
1633
  */
1381
1634
  removeVirtualText(bufferId: number, virtualTextId: string): boolean;
1382
1635
  /**
1636
+ * Add styled virtual text — richer form of `addVirtualText` whose
1637
+ * `options` accepts an `addOverlay`-style record: `fg`/`bg` may
1638
+ * be RGB arrays or theme-key strings, plus `bold`/`italic`. Theme
1639
+ * keys are resolved at render time so the label follows theme
1640
+ * changes live.
1641
+ */
1642
+ addVirtualTextStyled(bufferId: number, virtualTextId: string, position: number, text: string, options: Record<string, unknown>, before: boolean): boolean;
1643
+ /**
1383
1644
  * Remove virtual texts whose ID starts with the given prefix
1384
1645
  */
1385
1646
  removeVirtualTextsByPrefix(bufferId: number, prefix: string): boolean;
@@ -1411,6 +1672,38 @@ interface EditorAPI {
1411
1672
  */
1412
1673
  startPrompt(label: string, promptType: string): boolean;
1413
1674
  /**
1675
+ * Begin a key-capture window for the calling plugin.
1676
+ *
1677
+ * Pair with `endKeyCapture()` around any `getNextKey()` loop.
1678
+ * While capture is active, keys arriving between two
1679
+ * `getNextKey()` calls are buffered in-order rather than
1680
+ * falling through to the buffer / mode bindings, so fast typing,
1681
+ * pastes, or held-key auto-repeat are delivered losslessly.
1682
+ * Without this, a plugin's input loop has a race where keys
1683
+ * typed while the plugin is mid-redraw can leak into the editor.
1684
+ */
1685
+ beginKeyCapture(): boolean;
1686
+ /**
1687
+ * End the key-capture window and discard any unconsumed buffered
1688
+ * keys. Call from a `finally` block so capture is released even
1689
+ * if the plugin's loop throws.
1690
+ */
1691
+ endKeyCapture(): boolean;
1692
+ /**
1693
+ * Wait for the next keypress and resolve with a `KeyEventPayload`.
1694
+ *
1695
+ * While the returned promise is pending the editor consumes the
1696
+ * next key and resolves it; the key does not propagate to mode
1697
+ * bindings or other dispatch. Multiple in-flight requests across
1698
+ * plugins are FIFO. Designed for short input loops (flash labels,
1699
+ * vi find-char, replace-char) that would otherwise need to bind
1700
+ * every printable key in `defineMode`.
1701
+ *
1702
+ * For lossless capture against fast typing or paste, wrap the
1703
+ * loop with `beginKeyCapture()` / `endKeyCapture()`.
1704
+ */
1705
+ getNextKey(): Promise<KeyEventPayload>;
1706
+ /**
1414
1707
  * Start a prompt with initial value
1415
1708
  */
1416
1709
  startPromptWithInitial(label: string, promptType: string, initialValue: string): boolean;
@@ -1610,6 +1903,56 @@ interface EditorAPI {
1610
1903
  */
1611
1904
  spawnProcess(command: string, args: string[], cwd?: string): ProcessHandle<SpawnResult>;
1612
1905
  /**
1906
+ * Spawn a process on the host regardless of the active authority.
1907
+ *
1908
+ * Intended for plugin internals that must run host-side work
1909
+ * (e.g. `devcontainer up`) before installing an authority that
1910
+ * would otherwise route the spawn elsewhere. Same calling shape
1911
+ * as `spawnProcess`.
1912
+ */
1913
+ spawnHostProcess(command: string, args: string[], cwd?: string): ProcessHandle<SpawnResult>;
1914
+ /**
1915
+ * Install a new authority via an opaque payload.
1916
+ *
1917
+ * The payload is a JS object describing filesystem + spawner +
1918
+ * terminal wrapper + display label. The canonical schema lives in
1919
+ * the `AuthorityPayload` type in `fresh-editor`; plugins should
1920
+ * hand-build objects that match it. Fire-and-forget: the editor
1921
+ * restarts as part of the transition, so the plugin is reloaded
1922
+ * before any follow-up work can run on this call's return value.
1923
+ */
1924
+ setAuthority(payload: AuthorityPayload): boolean;
1925
+ /**
1926
+ * Restore the default local authority. Same restart semantics as
1927
+ * `setAuthority`.
1928
+ */
1929
+ clearAuthority(): void;
1930
+ /**
1931
+ * Override the Remote Indicator's displayed state. Plugins call
1932
+ * this to surface lifecycle transitions that the authority layer
1933
+ * doesn't know about yet — "Connecting" while `devcontainer up`
1934
+ * runs, "FailedAttach" after a non-zero exit, etc.
1935
+ *
1936
+ * Accepts a tagged JS object:
1937
+ * ```ts
1938
+ * editor.setRemoteIndicatorState({ kind: "connecting", label: "Building" });
1939
+ * editor.setRemoteIndicatorState({ kind: "failed_attach", error: "exit 1" });
1940
+ * editor.setRemoteIndicatorState({ kind: "connected", label: "Container:abc" });
1941
+ * editor.setRemoteIndicatorState({ kind: "local" });
1942
+ * ```
1943
+ *
1944
+ * The override sticks until replaced or cleared via
1945
+ * `clearRemoteIndicatorState`. Editor restart (e.g. on
1946
+ * `setAuthority`) resets it — plugins must reassert after a
1947
+ * post-restart init if they want the override to persist.
1948
+ */
1949
+ setRemoteIndicatorState(state: RemoteIndicatorStatePayload): boolean;
1950
+ /**
1951
+ * Drop any active Remote Indicator override. Safe to call even
1952
+ * without a prior `setRemoteIndicatorState`.
1953
+ */
1954
+ clearRemoteIndicatorState(): void;
1955
+ /**
1613
1956
  * Wait for a process to complete and get its result (async)
1614
1957
  */
1615
1958
  spawnProcessWait(processId: number): Promise<SpawnResult>;
@@ -1700,3 +2043,263 @@ interface EditorAPI {
1700
2043
  enabled: boolean;
1701
2044
  }>>;
1702
2045
  }
2046
+ /**
2047
+ * Typed overload of `editor.getPluginApi`. When the caller passes a
2048
+ * key that some loaded plugin declared in `FreshPluginRegistry`, the
2049
+ * return type is narrowed to that plugin's API. Unknown names fall
2050
+ * through to the untyped `unknown | null` signature.
2051
+ */
2052
+ interface EditorAPI {
2053
+ getPluginApi<K extends keyof FreshPluginRegistry>(name: K): FreshPluginRegistry[K] | null;
2054
+ }
2055
+ /**
2056
+ * Maps every hook event name to its payload type.
2057
+ *
2058
+ * Payloads match the flat JSON produced by `hook_args_to_json` on the Rust
2059
+ * side (`HookArgs` is `#[serde(untagged)]`, so each variant serializes as its
2060
+ * fields only). The TypeScript types here are derived directly from the Rust
2061
+ * field definitions and must be kept in sync with `fresh-core/src/hooks.rs`.
2062
+ *
2063
+ * `action` in `pre_command`/`post_command` is the serde JSON of the `Action`
2064
+ * enum: unit variants serialize as a plain string (e.g. `"MoveLeft"`),
2065
+ * tuple variants as a single-key object (e.g. `{"InsertChar": "a"}`).
2066
+ */
2067
+ interface HookEventMap {
2068
+ // ── lifecycle ────────────────────────────────────────────────────────────
2069
+ editor_initialized: Record<string, never>;
2070
+ plugins_loaded: Record<string, never>;
2071
+ ready: Record<string, never>;
2072
+ focus_gained: Record<string, never>;
2073
+ authority_changed: {
2074
+ label: string;
2075
+ };
2076
+ // ── buffer lifecycle ─────────────────────────────────────────────────────
2077
+ buffer_activated: {
2078
+ buffer_id: number;
2079
+ };
2080
+ buffer_deactivated: {
2081
+ buffer_id: number;
2082
+ };
2083
+ buffer_closed: {
2084
+ buffer_id: number;
2085
+ };
2086
+ // ── file I/O ─────────────────────────────────────────────────────────────
2087
+ before_file_open: {
2088
+ path: string;
2089
+ };
2090
+ after_file_open: {
2091
+ path: string;
2092
+ buffer_id: number;
2093
+ };
2094
+ before_file_save: {
2095
+ path: string;
2096
+ buffer_id: number;
2097
+ };
2098
+ after_file_save: {
2099
+ path: string;
2100
+ buffer_id: number;
2101
+ };
2102
+ // ── text edits ───────────────────────────────────────────────────────────
2103
+ before_insert: {
2104
+ buffer_id: number;
2105
+ position: number;
2106
+ text: string;
2107
+ };
2108
+ after_insert: {
2109
+ buffer_id: number;
2110
+ position: number;
2111
+ text: string;
2112
+ affected_start: number;
2113
+ affected_end: number;
2114
+ start_line: number;
2115
+ end_line: number;
2116
+ lines_added: number;
2117
+ };
2118
+ before_delete: {
2119
+ buffer_id: number;
2120
+ start: number;
2121
+ end: number;
2122
+ };
2123
+ after_delete: {
2124
+ buffer_id: number;
2125
+ start: number;
2126
+ end: number;
2127
+ deleted_text: string;
2128
+ affected_start: number;
2129
+ deleted_len: number;
2130
+ start_line: number;
2131
+ end_line: number;
2132
+ lines_removed: number;
2133
+ };
2134
+ // ── cursor & viewport ────────────────────────────────────────────────────
2135
+ cursor_moved: {
2136
+ buffer_id: number;
2137
+ cursor_id: number;
2138
+ old_position: number;
2139
+ new_position: number;
2140
+ line: number;
2141
+ text_properties: Record<string, unknown>[];
2142
+ };
2143
+ viewport_changed: {
2144
+ split_id: number;
2145
+ buffer_id: number;
2146
+ top_byte: number;
2147
+ top_line: number | null;
2148
+ width: number;
2149
+ height: number;
2150
+ };
2151
+ // ── rendering ────────────────────────────────────────────────────────────
2152
+ render_start: {
2153
+ buffer_id: number;
2154
+ };
2155
+ render_line: {
2156
+ buffer_id: number;
2157
+ line_number: number;
2158
+ byte_start: number;
2159
+ byte_end: number;
2160
+ content: string;
2161
+ };
2162
+ lines_changed: {
2163
+ buffer_id: number;
2164
+ lines: {
2165
+ line_number: number;
2166
+ byte_start: number;
2167
+ byte_end: number;
2168
+ content: string;
2169
+ }[];
2170
+ };
2171
+ view_transform_request: {
2172
+ buffer_id: number;
2173
+ split_id: number;
2174
+ viewport_start: number;
2175
+ viewport_end: number;
2176
+ tokens: ViewTokenWire[];
2177
+ cursor_positions: number[];
2178
+ };
2179
+ // ── commands ─────────────────────────────────────────────────────────────
2180
+ pre_command: {
2181
+ action: string | Record<string, unknown>;
2182
+ };
2183
+ post_command: {
2184
+ action: string | Record<string, unknown>;
2185
+ };
2186
+ idle: {
2187
+ milliseconds: number;
2188
+ };
2189
+ resize: {
2190
+ width: number;
2191
+ height: number;
2192
+ };
2193
+ // ── prompts ──────────────────────────────────────────────────────────────
2194
+ prompt_changed: {
2195
+ prompt_type: string;
2196
+ input: string;
2197
+ };
2198
+ prompt_confirmed: {
2199
+ prompt_type: string;
2200
+ input: string;
2201
+ selected_index: number | null;
2202
+ };
2203
+ prompt_cancelled: {
2204
+ prompt_type: string;
2205
+ input: string;
2206
+ };
2207
+ prompt_selection_changed: {
2208
+ prompt_type: string;
2209
+ selected_index: number;
2210
+ };
2211
+ // ── mouse ────────────────────────────────────────────────────────────────
2212
+ mouse_click: MouseClickHookArgs;
2213
+ mouse_move: {
2214
+ column: number;
2215
+ row: number;
2216
+ content_x: number;
2217
+ content_y: number;
2218
+ };
2219
+ mouse_scroll: {
2220
+ buffer_id: number;
2221
+ delta: number;
2222
+ col: number;
2223
+ row: number;
2224
+ };
2225
+ // ── LSP ──────────────────────────────────────────────────────────────────
2226
+ diagnostics_updated: {
2227
+ uri: string;
2228
+ count: number;
2229
+ };
2230
+ lsp_references: {
2231
+ symbol: string;
2232
+ locations: {
2233
+ file: string;
2234
+ line: number;
2235
+ column: number;
2236
+ }[];
2237
+ };
2238
+ lsp_server_request: {
2239
+ language: string;
2240
+ method: string;
2241
+ server_command: string;
2242
+ params: string | null;
2243
+ };
2244
+ lsp_server_error: {
2245
+ language: string;
2246
+ server_command: string;
2247
+ error_type: string;
2248
+ message: string;
2249
+ };
2250
+ lsp_status_clicked: {
2251
+ language: string;
2252
+ has_error: boolean;
2253
+ missing_servers: string[];
2254
+ user_dismissed: boolean;
2255
+ };
2256
+ // ── UI events ────────────────────────────────────────────────────────────
2257
+ action_popup_result: {
2258
+ popup_id: string;
2259
+ action_id: string;
2260
+ };
2261
+ process_output: {
2262
+ process_id: number;
2263
+ data: string;
2264
+ };
2265
+ language_changed: {
2266
+ buffer_id: number;
2267
+ language: string;
2268
+ };
2269
+ theme_inspect_key: {
2270
+ theme_name: string;
2271
+ key: string;
2272
+ };
2273
+ keyboard_shortcuts: {
2274
+ bindings: {
2275
+ key: string;
2276
+ action: string;
2277
+ }[];
2278
+ };
2279
+ }
2280
+ /**
2281
+ * Typed overloads of `editor.on` / `editor.off`.
2282
+ *
2283
+ * When the event name is a key of `HookEventMap` the handler receives a
2284
+ * fully-typed payload — TypeScript will flag misspelled field accesses at
2285
+ * compile time. Unknown event names fall through to the untyped base
2286
+ * signatures in the EditorAPI interface.
2287
+ *
2288
+ * Both function-value and handler-name forms are supported:
2289
+ *
2290
+ * ```ts
2291
+ * editor.on("buffer_activated", (args) => { /* args.buffer_id is number *\/ });
2292
+ * editor.on("buffer_activated", "myHandler"); // registerHandler("myHandler", fn)
2293
+ * ```
2294
+ */
2295
+ interface EditorAPI {
2296
+ on<K extends keyof HookEventMap>(eventName: K, handler: (args: HookEventMap[K]) => boolean | void | Promise<boolean | void>): void;
2297
+ on<K extends keyof HookEventMap>(eventName: K, handlerName: string): void;
2298
+ off<K extends keyof HookEventMap>(eventName: K, handler: (args: HookEventMap[K]) => boolean | void | Promise<boolean | void>): void;
2299
+ off<K extends keyof HookEventMap>(eventName: K, handlerName: string): void;
2300
+ /**
2301
+ * Create a buffer group: multiple panels appearing as one tab.
2302
+ * This is an async runtime binding (not a direct #[qjs] method).
2303
+ */
2304
+ createBufferGroup(name: string, mode: string, layout: unknown): Promise<BufferGroupResult>;
2305
+ }