@fresh-editor/fresh-editor 0.3.6 → 0.3.7
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 +91 -0
- package/package.json +1 -1
- package/plugins/audit_mode.i18n.json +84 -0
- package/plugins/audit_mode.ts +139 -3
- package/plugins/config-schema.json +27 -3
- package/plugins/dashboard.ts +18 -18
- package/plugins/flash.ts +22 -4
- package/plugins/git_blame.ts +10 -6
- package/plugins/git_log.ts +534 -124
- package/plugins/git_statusbar.i18n.json +72 -0
- package/plugins/git_statusbar.ts +133 -0
- package/plugins/lib/fresh.d.ts +305 -6
- package/plugins/lib/widgets.ts +111 -4
- package/plugins/live_diff.ts +156 -41
- package/plugins/merge_conflict.ts +89 -64
- package/plugins/orchestrator.ts +1982 -242
- package/plugins/pkg.ts +1 -1
- package/plugins/schemas/theme.schema.json +14 -0
- package/plugins/search_replace.i18n.json +140 -28
- package/plugins/search_replace.ts +674 -117
- package/plugins/tab_actions.i18n.json +212 -0
- package/plugins/tab_actions.ts +76 -0
- package/plugins/theme_editor.i18n.json +28 -0
- package/plugins/tsconfig.json +1 -0
- package/plugins/vi_mode.ts +11 -0
- package/themes/dark.json +1 -0
- package/themes/dracula.json +1 -0
- package/themes/high-contrast.json +1 -0
- package/themes/light.json +1 -0
- package/themes/nord.json +1 -0
- package/themes/nostalgia.json +1 -0
- package/themes/solarized-dark.json +1 -0
- package/themes/terminal.json +1 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cs": {
|
|
3
|
+
"status.detecting_branch": "Detekuji větev ...",
|
|
4
|
+
"status.not_in_git": "Není v git",
|
|
5
|
+
"status.git_branch": "Git: větev"
|
|
6
|
+
},
|
|
7
|
+
"de": {
|
|
8
|
+
"status.detecting_branch": "Branch erkennen ...",
|
|
9
|
+
"status.not_in_git": "Nicht in git",
|
|
10
|
+
"status.git_branch": "Git: Branch"
|
|
11
|
+
},
|
|
12
|
+
"en": {
|
|
13
|
+
"status.detecting_branch": "Detecting branch ...",
|
|
14
|
+
"status.not_in_git": "Not in git",
|
|
15
|
+
"status.git_branch": "Git: branch"
|
|
16
|
+
},
|
|
17
|
+
"es": {
|
|
18
|
+
"status.detecting_branch": "Detectando rama ...",
|
|
19
|
+
"status.not_in_git": "No está en git",
|
|
20
|
+
"status.git_branch": "Git: rama"
|
|
21
|
+
},
|
|
22
|
+
"fr": {
|
|
23
|
+
"status.detecting_branch": "Détection de la branche ...",
|
|
24
|
+
"status.not_in_git": "Pas dans git",
|
|
25
|
+
"status.git_branch": "Git : branche"
|
|
26
|
+
},
|
|
27
|
+
"it": {
|
|
28
|
+
"status.detecting_branch": "Rilevamento branch ...",
|
|
29
|
+
"status.not_in_git": "Non in git",
|
|
30
|
+
"status.git_branch": "Git: branch"
|
|
31
|
+
},
|
|
32
|
+
"ja": {
|
|
33
|
+
"status.detecting_branch": "ブランチを検出中...",
|
|
34
|
+
"status.not_in_git": "git外",
|
|
35
|
+
"status.git_branch": "Git: ブランチ"
|
|
36
|
+
},
|
|
37
|
+
"ko": {
|
|
38
|
+
"status.detecting_branch": "브랜치 감지 중...",
|
|
39
|
+
"status.not_in_git": "git 아님",
|
|
40
|
+
"status.git_branch": "Git: 브랜치"
|
|
41
|
+
},
|
|
42
|
+
"pt-BR": {
|
|
43
|
+
"status.detecting_branch": "Detectando ramo ...",
|
|
44
|
+
"status.not_in_git": "Não está em git",
|
|
45
|
+
"status.git_branch": "Git: ramo"
|
|
46
|
+
},
|
|
47
|
+
"ru": {
|
|
48
|
+
"status.detecting_branch": "Определение ветки ...",
|
|
49
|
+
"status.not_in_git": "Не в git",
|
|
50
|
+
"status.git_branch": "Git: ветка"
|
|
51
|
+
},
|
|
52
|
+
"th": {
|
|
53
|
+
"status.detecting_branch": "กำลังตรวจจับสาขา ...",
|
|
54
|
+
"status.not_in_git": "ไม่ได้อยู่ใน git",
|
|
55
|
+
"status.git_branch": "Git: สาขา"
|
|
56
|
+
},
|
|
57
|
+
"uk": {
|
|
58
|
+
"status.detecting_branch": "Визначення гілки ...",
|
|
59
|
+
"status.not_in_git": "Не в git",
|
|
60
|
+
"status.git_branch": "Git: гілка"
|
|
61
|
+
},
|
|
62
|
+
"vi": {
|
|
63
|
+
"status.detecting_branch": "Đang phát hiện nhánh ...",
|
|
64
|
+
"status.not_in_git": "Không trong git",
|
|
65
|
+
"status.git_branch": "Git: nhánh"
|
|
66
|
+
},
|
|
67
|
+
"zh-CN": {
|
|
68
|
+
"status.detecting_branch": "正在检测分支...",
|
|
69
|
+
"status.not_in_git": "不在 git 中",
|
|
70
|
+
"status.git_branch": "Git: 分支"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/// <reference path="./lib/fresh.d.ts" />
|
|
2
|
+
|
|
3
|
+
const editor = getEditor();
|
|
4
|
+
|
|
5
|
+
const GIT_BRANCH = "branch";
|
|
6
|
+
|
|
7
|
+
let lastDetectedBranch = editor.t("status.detecting_branch");
|
|
8
|
+
let inFlight: Promise<string> | null = null;
|
|
9
|
+
|
|
10
|
+
// HEAD-file watcher. Branch changes correspond exactly to mutations of
|
|
11
|
+
// the `HEAD` file inside the relevant git dir (resolved by
|
|
12
|
+
// `git rev-parse --git-path HEAD` so worktrees / submodules / `--git-dir`
|
|
13
|
+
// setups work). When HEAD changes we re-spawn `git rev-parse --abbrev-ref`
|
|
14
|
+
// and push the new value; otherwise we never spawn git on the hot path.
|
|
15
|
+
let headWatchHandle: number | null = null;
|
|
16
|
+
let watchedCwd: string | null = null;
|
|
17
|
+
let watchedHeadPath: string | null = null;
|
|
18
|
+
let ensureWatchInFlight: Promise<void> | null = null;
|
|
19
|
+
|
|
20
|
+
async function discoverHeadPath(cwd: string): Promise<string | null> {
|
|
21
|
+
const result = await editor.spawnProcess(
|
|
22
|
+
"git",
|
|
23
|
+
["rev-parse", "--git-path", "HEAD"],
|
|
24
|
+
cwd,
|
|
25
|
+
);
|
|
26
|
+
if (result.exit_code !== 0) return null;
|
|
27
|
+
const headPath = result.stdout.trim();
|
|
28
|
+
if (!headPath) return null;
|
|
29
|
+
// `--git-path` returns a path relative to cwd unless the git dir is
|
|
30
|
+
// outside (e.g. worktree). Make absolute so notify gets a stable target.
|
|
31
|
+
return headPath.startsWith("/") ? headPath : `${cwd}/${headPath}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function ensureHeadWatch(): Promise<void> {
|
|
35
|
+
const cwd = editor.getCwd();
|
|
36
|
+
if (watchedCwd === cwd && headWatchHandle !== null) return;
|
|
37
|
+
if (ensureWatchInFlight) return ensureWatchInFlight;
|
|
38
|
+
|
|
39
|
+
ensureWatchInFlight = (async () => {
|
|
40
|
+
try {
|
|
41
|
+
if (headWatchHandle !== null) {
|
|
42
|
+
editor.unwatchPath(headWatchHandle);
|
|
43
|
+
headWatchHandle = null;
|
|
44
|
+
watchedHeadPath = null;
|
|
45
|
+
}
|
|
46
|
+
watchedCwd = cwd;
|
|
47
|
+
const headPath = await discoverHeadPath(cwd);
|
|
48
|
+
if (!headPath) return;
|
|
49
|
+
try {
|
|
50
|
+
headWatchHandle = await editor.watchPath(headPath, false);
|
|
51
|
+
watchedHeadPath = headPath;
|
|
52
|
+
} catch (_e) {
|
|
53
|
+
// Watch registration failed (path missing, kernel limit). Fall
|
|
54
|
+
// back to event-driven refresh — getCurrentGitBranch is still
|
|
55
|
+
// gated by inFlight + the per-event invocation pattern below.
|
|
56
|
+
}
|
|
57
|
+
} finally {
|
|
58
|
+
ensureWatchInFlight = null;
|
|
59
|
+
}
|
|
60
|
+
})();
|
|
61
|
+
return ensureWatchInFlight;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function getCurrentGitBranch(): Promise<string> {
|
|
65
|
+
if (inFlight) return inFlight;
|
|
66
|
+
inFlight = (async () => {
|
|
67
|
+
try {
|
|
68
|
+
const cwd = editor.getCwd();
|
|
69
|
+
const result = await editor.spawnProcess(
|
|
70
|
+
"git",
|
|
71
|
+
["rev-parse", "--abbrev-ref", "HEAD"],
|
|
72
|
+
cwd,
|
|
73
|
+
);
|
|
74
|
+
if (result.exit_code === 0) {
|
|
75
|
+
const branch = result.stdout.trim();
|
|
76
|
+
lastDetectedBranch = branch || "HEAD";
|
|
77
|
+
} else {
|
|
78
|
+
lastDetectedBranch = editor.t("status.not_in_git");
|
|
79
|
+
}
|
|
80
|
+
return lastDetectedBranch;
|
|
81
|
+
} finally {
|
|
82
|
+
inFlight = null;
|
|
83
|
+
}
|
|
84
|
+
})();
|
|
85
|
+
return inFlight;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function refreshForActiveBuffer(): Promise<void> {
|
|
89
|
+
// Lazy: pick up cwd changes (Orchestrator window switch, etc.) the next
|
|
90
|
+
// time anything triggers us.
|
|
91
|
+
ensureHeadWatch();
|
|
92
|
+
const bufferId = editor.getActiveBufferId();
|
|
93
|
+
if (bufferId === 0) return;
|
|
94
|
+
const branch = await getCurrentGitBranch();
|
|
95
|
+
editor.setStatusBarValue(bufferId, GIT_BRANCH, branch);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
editor.registerStatusBarElement(GIT_BRANCH, editor.t("status.git_branch"));
|
|
99
|
+
|
|
100
|
+
// Refresh the branch label when:
|
|
101
|
+
// - The user switches to a different buffer (the per-buffer value may not
|
|
102
|
+
// be set yet for that buffer).
|
|
103
|
+
// - A file is freshly opened (same reason).
|
|
104
|
+
// - A file is saved (best-effort UX: the user may have committed via an
|
|
105
|
+
// external terminal between events; the watchPath below catches actual
|
|
106
|
+
// HEAD mutations).
|
|
107
|
+
// - The editor regains focus from another window — covers the case of
|
|
108
|
+
// running `git checkout` in an external terminal while fresh was unfocused.
|
|
109
|
+
//
|
|
110
|
+
// Notably *not* in this list (compared to the legacy version): render_start,
|
|
111
|
+
// cursor_moved, after_insert, after_delete, buffer_deactivated, buffer_closed.
|
|
112
|
+
// None of them can change the current branch, and render_start was being
|
|
113
|
+
// fired ~300/s — see #2009 for the feedback-loop investigation.
|
|
114
|
+
[
|
|
115
|
+
"buffer_activated",
|
|
116
|
+
"after_file_open",
|
|
117
|
+
"after_file_save",
|
|
118
|
+
"focus_gained",
|
|
119
|
+
].forEach((event) => {
|
|
120
|
+
editor.on(event, async () => {
|
|
121
|
+
await refreshForActiveBuffer();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// path_changed → HEAD file mutated → branch may have changed.
|
|
126
|
+
editor.on("path_changed", async (args) => {
|
|
127
|
+
if (args.handle !== headWatchHandle) return;
|
|
128
|
+
await refreshForActiveBuffer();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Kick off the first detection at load time so the status bar populates
|
|
132
|
+
// before any user event fires.
|
|
133
|
+
refreshForActiveBuffer();
|
package/plugins/lib/fresh.d.ts
CHANGED
|
@@ -252,6 +252,10 @@ type ViewportInfo = {
|
|
|
252
252
|
*/
|
|
253
253
|
height: number;
|
|
254
254
|
};
|
|
255
|
+
type ScreenSize = {
|
|
256
|
+
width: number;
|
|
257
|
+
height: number;
|
|
258
|
+
};
|
|
255
259
|
type KeyEventPayload = {
|
|
256
260
|
/**
|
|
257
261
|
* Key name (e.g. `"a"`, `"escape"`, `"f1"`).
|
|
@@ -319,15 +323,18 @@ type ViewTokenWireKind = {
|
|
|
319
323
|
} | "Newline" | "Space" | "Break" | {
|
|
320
324
|
"BinaryByte": number;
|
|
321
325
|
};
|
|
326
|
+
type TokenColor = [number, number, number] | string;
|
|
322
327
|
type ViewTokenStyle = {
|
|
323
328
|
/**
|
|
324
|
-
* Foreground color
|
|
329
|
+
* Foreground color. Either `[r, g, b]` or a named/theme string —
|
|
330
|
+
* see [`TokenColor`].
|
|
325
331
|
*/
|
|
326
|
-
fg:
|
|
332
|
+
fg: TokenColor | null;
|
|
327
333
|
/**
|
|
328
|
-
* Background color
|
|
334
|
+
* Background color. Either `[r, g, b]` or a named/theme string —
|
|
335
|
+
* see [`TokenColor`].
|
|
329
336
|
*/
|
|
330
|
-
bg:
|
|
337
|
+
bg: TokenColor | null;
|
|
331
338
|
/**
|
|
332
339
|
* Whether to render in bold
|
|
333
340
|
*/
|
|
@@ -336,6 +343,10 @@ type ViewTokenStyle = {
|
|
|
336
343
|
* Whether to render in italic
|
|
337
344
|
*/
|
|
338
345
|
italic: boolean;
|
|
346
|
+
/**
|
|
347
|
+
* Whether to render with underline
|
|
348
|
+
*/
|
|
349
|
+
underline: boolean;
|
|
339
350
|
};
|
|
340
351
|
type PromptSuggestion = {
|
|
341
352
|
/**
|
|
@@ -445,6 +456,23 @@ type WindowInfo = {
|
|
|
445
456
|
* Absolute project root.
|
|
446
457
|
*/
|
|
447
458
|
root: string;
|
|
459
|
+
/**
|
|
460
|
+
* Project this session belongs to — the canonical repo
|
|
461
|
+
* root (or arbitrary directory) the user pointed the
|
|
462
|
+
* new-session form at. `null` for legacy sessions that
|
|
463
|
+
* predate the Project Path field. The Orchestrator Open
|
|
464
|
+
* dialog filters by this so the "this project's sessions"
|
|
465
|
+
* view is one keystroke away from the all-projects view.
|
|
466
|
+
*/
|
|
467
|
+
project_path?: string | null;
|
|
468
|
+
/**
|
|
469
|
+
* `true` when the session shares its working tree with
|
|
470
|
+
* other sessions (worktree-creation was off at session
|
|
471
|
+
* time, or the session lives in a non-git directory).
|
|
472
|
+
* Persistence-only field; defaults to `false` and isn't
|
|
473
|
+
* emitted when false.
|
|
474
|
+
*/
|
|
475
|
+
shared_worktree?: boolean;
|
|
448
476
|
};
|
|
449
477
|
type JsDiagnostic = {
|
|
450
478
|
/**
|
|
@@ -629,6 +657,27 @@ type CreateTerminalOptions = {
|
|
|
629
657
|
* target session's membership set rather than the active one's.
|
|
630
658
|
*/
|
|
631
659
|
windowId?: WindowId;
|
|
660
|
+
/**
|
|
661
|
+
* Argv to spawn directly inside the PTY instead of the host's
|
|
662
|
+
* configured shell. `None` (default) keeps the historical
|
|
663
|
+
* behaviour: spawn the user's shell and let the caller type into
|
|
664
|
+
* it via `sendTerminalInput`. `Some([cmd, ...args])` runs that
|
|
665
|
+
* exact command as the PTY child — no shell middleman, so the
|
|
666
|
+
* process exits cleanly when the agent does and the
|
|
667
|
+
* terminal-buffer's `terminal_exit` plugin hook reflects the
|
|
668
|
+
* agent's real exit status. Used by Orchestrator so a session
|
|
669
|
+
* with agent `python3` is just python3 in the PTY rather than
|
|
670
|
+
* bash-running-python3-as-a-subshell-command.
|
|
671
|
+
*/
|
|
672
|
+
command?: Array<string>;
|
|
673
|
+
/**
|
|
674
|
+
* Tab title for the terminal buffer. Defaults to `command[0]`
|
|
675
|
+
* (when `command` is set) or `"Terminal N"` (the historical
|
|
676
|
+
* auto-numbered title). If another terminal in the same window
|
|
677
|
+
* already uses the requested title, the host appends `" (k)"`
|
|
678
|
+
* to disambiguate. Empty string is treated the same as `None`.
|
|
679
|
+
*/
|
|
680
|
+
title?: string;
|
|
632
681
|
};
|
|
633
682
|
type CursorInfo = {
|
|
634
683
|
/**
|
|
@@ -816,6 +865,15 @@ type WidgetSpec = {
|
|
|
816
865
|
focused: boolean;
|
|
817
866
|
intent: ButtonKind;
|
|
818
867
|
key?: string | null;
|
|
868
|
+
/**
|
|
869
|
+
* When true, the button renders in a muted style, is dropped
|
|
870
|
+
* from the Tab cycle, and clicks on it are ignored. Use for
|
|
871
|
+
* actions that aren't currently available against the
|
|
872
|
+
* surrounding state (e.g. "Archive" on the base session). The
|
|
873
|
+
* button still occupies its layout cell so the surrounding
|
|
874
|
+
* row doesn't reshuffle when the disabled flag flips.
|
|
875
|
+
*/
|
|
876
|
+
disabled: boolean;
|
|
819
877
|
} | {
|
|
820
878
|
"kind": "spacer";
|
|
821
879
|
cols: number;
|
|
@@ -923,6 +981,41 @@ type WidgetSpec = {
|
|
|
923
981
|
* `LabeledSection` or a flexible row.
|
|
924
982
|
*/
|
|
925
983
|
fullWidth: boolean;
|
|
984
|
+
/**
|
|
985
|
+
* Optional completion candidates. When non-empty AND
|
|
986
|
+
* `label` is non-empty (the chrome trigger), the
|
|
987
|
+
* renderer paints a popup directly under the input,
|
|
988
|
+
* inside a unified box: the input's normal `╰─...─╯`
|
|
989
|
+
* bottom border becomes a dimmed `┄` separator, the
|
|
990
|
+
* labeled section's side borders extend down through
|
|
991
|
+
* the candidate rows, and a single `╰─...─╯` bottom
|
|
992
|
+
* closes the whole block. Candidates render left-
|
|
993
|
+
* aligned with the input's text (the position right
|
|
994
|
+
* after `[`), with the host-managed selected index
|
|
995
|
+
* highlighted.
|
|
996
|
+
*
|
|
997
|
+
* Smart-key dispatch on a focused Text-with-completions:
|
|
998
|
+
* Up/Down moves selection (host-internal, no event),
|
|
999
|
+
* Tab fires `completion_accept` with the selected
|
|
1000
|
+
* candidate, Enter / Escape fire `completion_dismiss`
|
|
1001
|
+
* (the dispatcher's normal "Enter focus-advance / Esc
|
|
1002
|
+
* close panel" only runs once the popup is closed).
|
|
1003
|
+
*
|
|
1004
|
+
* Plugins push candidates in response to the text
|
|
1005
|
+
* widget's `change` event via
|
|
1006
|
+
* `WidgetMutation::SetCompletions`. An empty `items`
|
|
1007
|
+
* closes the popup.
|
|
1008
|
+
*/
|
|
1009
|
+
completions?: Array<string>;
|
|
1010
|
+
/**
|
|
1011
|
+
* How many candidate rows the popup paints at once
|
|
1012
|
+
* when it opens. Excess candidates stay reachable
|
|
1013
|
+
* via Up/Down (host auto-scrolls to keep selection
|
|
1014
|
+
* in view) or the mouse wheel; a thumb glyph paints
|
|
1015
|
+
* in the right edge of the popup whenever there's
|
|
1016
|
+
* more to scroll. `0` (default) falls back to `5`.
|
|
1017
|
+
*/
|
|
1018
|
+
completionsVisibleRows: number;
|
|
926
1019
|
key?: string | null;
|
|
927
1020
|
} | {
|
|
928
1021
|
"kind": "labeledSection";
|
|
@@ -967,6 +1060,10 @@ type WidgetSpec = {
|
|
|
967
1060
|
"kind": "raw";
|
|
968
1061
|
entries: Array<TextPropertyEntry>;
|
|
969
1062
|
key?: string | null;
|
|
1063
|
+
} | {
|
|
1064
|
+
"kind": "overlay";
|
|
1065
|
+
child: WidgetSpec;
|
|
1066
|
+
key?: string | null;
|
|
970
1067
|
};
|
|
971
1068
|
type WidgetAction = {
|
|
972
1069
|
"kind": "focusAdvance";
|
|
@@ -991,6 +1088,10 @@ type WidgetMutation = {
|
|
|
991
1088
|
widgetKey: string;
|
|
992
1089
|
value: string;
|
|
993
1090
|
cursorByte?: number | null;
|
|
1091
|
+
} | {
|
|
1092
|
+
"kind": "setCompletions";
|
|
1093
|
+
widgetKey: string;
|
|
1094
|
+
items: Array<string>;
|
|
994
1095
|
} | {
|
|
995
1096
|
"kind": "setChecked";
|
|
996
1097
|
widgetKey: string;
|
|
@@ -1013,6 +1114,18 @@ type WidgetMutation = {
|
|
|
1013
1114
|
widgetKey: string;
|
|
1014
1115
|
checked: boolean;
|
|
1015
1116
|
keys: Array<string>;
|
|
1117
|
+
} | {
|
|
1118
|
+
"kind": "appendTreeNodes";
|
|
1119
|
+
widgetKey: string;
|
|
1120
|
+
newNodes: Array<TreeNode>;
|
|
1121
|
+
newItemKeys: Array<string>;
|
|
1122
|
+
} | {
|
|
1123
|
+
"kind": "setRawEntries";
|
|
1124
|
+
widgetKey: string;
|
|
1125
|
+
entries: Array<TextPropertyEntry>;
|
|
1126
|
+
} | {
|
|
1127
|
+
"kind": "setFocusKey";
|
|
1128
|
+
widgetKey: string;
|
|
1016
1129
|
};
|
|
1017
1130
|
type SearchTakeResult = {
|
|
1018
1131
|
/**
|
|
@@ -1442,7 +1555,9 @@ interface EditorAPI {
|
|
|
1442
1555
|
* contexts only (e.g. `"tour-active"`, `"review-mode"`), not built-in
|
|
1443
1556
|
* editor modes.
|
|
1444
1557
|
*/
|
|
1445
|
-
registerCommand(name: string, description: string, handlerName: string, context?: string | null
|
|
1558
|
+
registerCommand(name: string, description: string, handlerName: string, context?: string | null, options?: {
|
|
1559
|
+
terminalBypass?: boolean;
|
|
1560
|
+
} | null): boolean;
|
|
1446
1561
|
/**
|
|
1447
1562
|
* Unregister a command by name
|
|
1448
1563
|
*/
|
|
@@ -1456,6 +1571,17 @@ interface EditorAPI {
|
|
|
1456
1571
|
*/
|
|
1457
1572
|
executeAction(actionName: string): boolean;
|
|
1458
1573
|
/**
|
|
1574
|
+
* Register a custom statusbar token.
|
|
1575
|
+
* Token will be named "plugin_name:token_name" where plugin_name is the current plugin.
|
|
1576
|
+
* Returns true if registration succeeded, false if invalid or already registered.
|
|
1577
|
+
*/
|
|
1578
|
+
registerStatusBarElement(tokenName: string, title: string): boolean;
|
|
1579
|
+
/**
|
|
1580
|
+
* Set the value of a status-bar token for a specific buffer.
|
|
1581
|
+
* The full token key sent to the editor is "plugin_name:token_name".
|
|
1582
|
+
*/
|
|
1583
|
+
setStatusBarValue(bufferId: number, tokenName: string, value: string): boolean;
|
|
1584
|
+
/**
|
|
1459
1585
|
* Translate a string - reads plugin name from __pluginName__ global
|
|
1460
1586
|
* Args is optional - can be omitted, undefined, null, or an object
|
|
1461
1587
|
*/
|
|
@@ -1502,6 +1628,13 @@ interface EditorAPI {
|
|
|
1502
1628
|
*/
|
|
1503
1629
|
getViewport(): ViewportInfo | null;
|
|
1504
1630
|
/**
|
|
1631
|
+
* Total terminal dimensions in cells. Unlike `getViewport()`
|
|
1632
|
+
* (which reports the active split, shrunk by any vertical
|
|
1633
|
+
* split layout), this reflects the full terminal — what a
|
|
1634
|
+
* floating overlay sized by `heightPct` actually gets.
|
|
1635
|
+
*/
|
|
1636
|
+
getScreenSize(): ScreenSize;
|
|
1637
|
+
/**
|
|
1505
1638
|
* List every split with its active buffer and viewport.
|
|
1506
1639
|
*
|
|
1507
1640
|
* Plugins that need to operate on every visible buffer
|
|
@@ -1585,6 +1718,27 @@ interface EditorAPI {
|
|
|
1585
1718
|
*/
|
|
1586
1719
|
openFileInSplit(splitId: number, path: string, line: number, column: number): boolean;
|
|
1587
1720
|
/**
|
|
1721
|
+
* Open `path` as a regular buffer in forced large-file (file-backed)
|
|
1722
|
+
* mode. The file is created (empty) if missing — designed for
|
|
1723
|
+
* buffers that will be filled by a concurrent `spawnProcess` with
|
|
1724
|
+
* `stdoutTo`. Resolves with the new buffer's id, or `null` on
|
|
1725
|
+
* failure.
|
|
1726
|
+
*
|
|
1727
|
+
* Pair with `refreshBufferFromDisk` to grow the buffer as the
|
|
1728
|
+
* streaming write advances.
|
|
1729
|
+
*/
|
|
1730
|
+
openFileStreaming(path: string): Promise<number | null>;
|
|
1731
|
+
/**
|
|
1732
|
+
* Re-stat the file backing `bufferId` and extend the buffer if the
|
|
1733
|
+
* file has grown. Resolves with the new total byte length, or
|
|
1734
|
+
* `null` if the buffer has no file path or doesn't exist.
|
|
1735
|
+
*
|
|
1736
|
+
* Used to drive a streaming display: while a `spawnProcess` writes
|
|
1737
|
+
* to a temp file, the plugin polls this on a timer so the buffer
|
|
1738
|
+
* length tracks the file length.
|
|
1739
|
+
*/
|
|
1740
|
+
refreshBufferFromDisk(bufferId: number): Promise<number | null>;
|
|
1741
|
+
/**
|
|
1588
1742
|
* Show a buffer in the current split
|
|
1589
1743
|
*/
|
|
1590
1744
|
showBuffer(bufferId: number): boolean;
|
|
@@ -1593,6 +1747,30 @@ interface EditorAPI {
|
|
|
1593
1747
|
*/
|
|
1594
1748
|
closeBuffer(bufferId: number): boolean;
|
|
1595
1749
|
/**
|
|
1750
|
+
* Close other buffers in split
|
|
1751
|
+
*/
|
|
1752
|
+
closeOtherBuffersInSplit(bufferId: number, splitId: number): boolean;
|
|
1753
|
+
/**
|
|
1754
|
+
* Close all buffers in split
|
|
1755
|
+
*/
|
|
1756
|
+
closeAllBuffersInSplit(splitId: number): boolean;
|
|
1757
|
+
/**
|
|
1758
|
+
* Close buffers to right in split
|
|
1759
|
+
*/
|
|
1760
|
+
closeBuffersToRightInSplit(bufferId: number, splitId: number): boolean;
|
|
1761
|
+
/**
|
|
1762
|
+
* Close buffers to left in split
|
|
1763
|
+
*/
|
|
1764
|
+
closeBuffersToLeftInSplit(bufferId: number, splitId: number): boolean;
|
|
1765
|
+
/**
|
|
1766
|
+
* Move the active tab to the left in the active split
|
|
1767
|
+
*/
|
|
1768
|
+
moveTabToLeft(): boolean;
|
|
1769
|
+
/**
|
|
1770
|
+
* Move the active tab to the right in the active split
|
|
1771
|
+
*/
|
|
1772
|
+
moveTabToRight(): boolean;
|
|
1773
|
+
/**
|
|
1596
1774
|
* Start a frame-buffer animation over an arbitrary screen region.
|
|
1597
1775
|
* Returns an animation id usable with `cancelAnimation`.
|
|
1598
1776
|
*/
|
|
@@ -1750,6 +1928,64 @@ interface EditorAPI {
|
|
|
1750
1928
|
*/
|
|
1751
1929
|
getUserConfig(): unknown;
|
|
1752
1930
|
/**
|
|
1931
|
+
* Declare a boolean config field for the calling plugin.
|
|
1932
|
+
*
|
|
1933
|
+
* Validates `options` synchronously: the JS call throws if any
|
|
1934
|
+
* unknown key is present or if `default` isn't a boolean. The
|
|
1935
|
+
* Settings UI grows a "Plugin Settings → <plugin>" sub-category
|
|
1936
|
+
* containing a toggle for this field. Returns the current value
|
|
1937
|
+
* (user-set if present, otherwise the declared `default`).
|
|
1938
|
+
*/
|
|
1939
|
+
defineConfigBoolean(name: string, options: {
|
|
1940
|
+
default: boolean;
|
|
1941
|
+
description?: string;
|
|
1942
|
+
}): boolean;
|
|
1943
|
+
/**
|
|
1944
|
+
* Declare an integer config field for the calling plugin. Throws on
|
|
1945
|
+
* invalid options or if the default falls outside `minimum/maximum`.
|
|
1946
|
+
*/
|
|
1947
|
+
defineConfigInteger(name: string, options: {
|
|
1948
|
+
default: number;
|
|
1949
|
+
description?: string;
|
|
1950
|
+
minimum?: number;
|
|
1951
|
+
maximum?: number;
|
|
1952
|
+
}): number;
|
|
1953
|
+
/**
|
|
1954
|
+
* Declare a floating-point number config field. Throws on bad
|
|
1955
|
+
* options or default outside `minimum/maximum`.
|
|
1956
|
+
*/
|
|
1957
|
+
defineConfigNumber(name: string, options: {
|
|
1958
|
+
default: number;
|
|
1959
|
+
description?: string;
|
|
1960
|
+
minimum?: number;
|
|
1961
|
+
maximum?: number;
|
|
1962
|
+
}): number;
|
|
1963
|
+
/**
|
|
1964
|
+
* Declare a free-form string config field.
|
|
1965
|
+
*/
|
|
1966
|
+
defineConfigString(name: string, options: {
|
|
1967
|
+
default: string;
|
|
1968
|
+
description?: string;
|
|
1969
|
+
}): string;
|
|
1970
|
+
/**
|
|
1971
|
+
* Declare an array-of-strings config field (e.g. a list of
|
|
1972
|
+
* patterns). The Settings UI renders this as a list editor.
|
|
1973
|
+
*/
|
|
1974
|
+
defineConfigStringArray(name: string, options: {
|
|
1975
|
+
default: string[];
|
|
1976
|
+
description?: string;
|
|
1977
|
+
}): string[];
|
|
1978
|
+
/**
|
|
1979
|
+
* Get the calling plugin's settings as a JS object.
|
|
1980
|
+
*
|
|
1981
|
+
* Returns the merged value at `config.plugins.<plugin_name>.settings`.
|
|
1982
|
+
* The shape comes from whatever the plugin declared via
|
|
1983
|
+
* `editor.definePluginConfig(...)` (defaults pre-populated by the
|
|
1984
|
+
* host, user overrides on top from the Settings UI). Returns `null`
|
|
1985
|
+
* if the plugin hasn't declared a schema and has no user-set value.
|
|
1986
|
+
*/
|
|
1987
|
+
getPluginConfig(): unknown;
|
|
1988
|
+
/**
|
|
1753
1989
|
* Reload configuration from file
|
|
1754
1990
|
*/
|
|
1755
1991
|
reloadConfig(): void;
|
|
@@ -1972,6 +2208,21 @@ interface EditorAPI {
|
|
|
1972
2208
|
*/
|
|
1973
2209
|
clearFolds(bufferId: number): boolean;
|
|
1974
2210
|
/**
|
|
2211
|
+
* Publish a set of toggleable fold ranges on the buffer. Same
|
|
2212
|
+
* shape an LSP `foldingRange` response would take. Unlike
|
|
2213
|
+
* `addFold`, this does *not* pre-collapse anything — the
|
|
2214
|
+
* standard fold-toggle keybinding finds the range under the
|
|
2215
|
+
* cursor and collapses or expands it on demand. Replacing call
|
|
2216
|
+
* replaces the prior set.
|
|
2217
|
+
*
|
|
2218
|
+
* `ranges` is a JS array of objects shaped
|
|
2219
|
+
* `{ startLine, endLine, kind? }` (lines are 0-indexed).
|
|
2220
|
+
* `kind` is one of `"comment"`, `"imports"`, `"region"` per
|
|
2221
|
+
* the LSP spec; omitted/unknown values are accepted as plain
|
|
2222
|
+
* folds.
|
|
2223
|
+
*/
|
|
2224
|
+
setFoldingRanges(bufferId: number, rangesArr: Record<string, unknown>[]): boolean;
|
|
2225
|
+
/**
|
|
1975
2226
|
* Add a soft break point for marker-based line wrapping
|
|
1976
2227
|
*/
|
|
1977
2228
|
addSoftBreak(bufferId: number, namespace: string, position: number, indent: number): boolean;
|
|
@@ -2046,6 +2297,13 @@ interface EditorAPI {
|
|
|
2046
2297
|
* theme-key string (e.g. `"editor.line_number_fg"`). Theme keys
|
|
2047
2298
|
* are resolved at render time so the line follows theme changes.
|
|
2048
2299
|
* Both default to `null` (no foreground / transparent background).
|
|
2300
|
+
* * `gutterGlyph` — optional single character (any short string)
|
|
2301
|
+
* rendered in the line-number column on this virtual line's
|
|
2302
|
+
* first visual row. Use to mark e.g. a deletion line with "-"
|
|
2303
|
+
* so the indicator sits next to the deleted content instead
|
|
2304
|
+
* of on the following source line.
|
|
2305
|
+
* * `gutterColor` — color for `gutterGlyph`, same shape as
|
|
2306
|
+
* `fg`/`bg`. Falls back to the theme's line-number fg.
|
|
2049
2307
|
*/
|
|
2050
2308
|
addVirtualLine(bufferId: number, position: number, text: string, options: Record<string, unknown>, above: boolean, namespace: string, priority: number): boolean;
|
|
2051
2309
|
/**
|
|
@@ -2409,6 +2667,15 @@ interface EditorAPI {
|
|
|
2409
2667
|
*/
|
|
2410
2668
|
focusBufferGroupPanel(groupId: number, panelName: string): boolean;
|
|
2411
2669
|
/**
|
|
2670
|
+
* Re-point a buffer-group's panel at a different buffer id.
|
|
2671
|
+
*
|
|
2672
|
+
* Streaming plugins (e.g. git-log) allocate one file-backed
|
|
2673
|
+
* buffer per item and call this on navigation to swap which
|
|
2674
|
+
* buffer the panel displays — instead of mutating a single
|
|
2675
|
+
* shared buffer's contents. Resolves with `true` on success.
|
|
2676
|
+
*/
|
|
2677
|
+
setBufferGroupPanelBuffer(groupId: number, panelName: string, bufferId: number): Promise<boolean>;
|
|
2678
|
+
/**
|
|
2412
2679
|
* Set virtual buffer content (takes array of entry objects)
|
|
2413
2680
|
*
|
|
2414
2681
|
* Note: entries should be TextPropertyEntry[] - uses manual parsing for HashMap support
|
|
@@ -2474,8 +2741,13 @@ interface EditorAPI {
|
|
|
2474
2741
|
unmountFloatingWidget(panelId: number): boolean;
|
|
2475
2742
|
/**
|
|
2476
2743
|
* Spawn a process (async, returns request_id)
|
|
2744
|
+
*
|
|
2745
|
+
* Optional 4th argument `stdoutTo: string` pipes the child's stdout
|
|
2746
|
+
* directly into the named file instead of buffering it. The
|
|
2747
|
+
* resolved `SpawnResult.stdout` is empty in that case; the bytes
|
|
2748
|
+
* land on disk for `openFile` to pick up as a file-backed buffer.
|
|
2477
2749
|
*/
|
|
2478
|
-
spawnProcess(command: string, args: string[], cwd?: string): ProcessHandle<SpawnResult>;
|
|
2750
|
+
spawnProcess(command: string, args: string[], cwd?: string, stdoutTo?: string): ProcessHandle<SpawnResult>;
|
|
2479
2751
|
/**
|
|
2480
2752
|
* Spawn a process on the host regardless of the active authority.
|
|
2481
2753
|
*
|
|
@@ -2635,6 +2907,33 @@ interface EditorAPI {
|
|
|
2635
2907
|
getPluginApi<K extends keyof FreshPluginRegistry>(name: K): FreshPluginRegistry[K] | null;
|
|
2636
2908
|
}
|
|
2637
2909
|
/**
|
|
2910
|
+
* Typed overload of `editor.defineConfigEnum`. The macro-generated
|
|
2911
|
+
* signature can't express `<E extends string>` propagating from the
|
|
2912
|
+
* `values` array into the return type, so it's declared here. Use
|
|
2913
|
+
* `as const` on the `values` array to get a literal-union return:
|
|
2914
|
+
*
|
|
2915
|
+
* ```ts
|
|
2916
|
+
* const mode = editor.defineConfigEnum("mode", {
|
|
2917
|
+
* values: ["normal", "insert"] as const,
|
|
2918
|
+
* default: "normal",
|
|
2919
|
+
* });
|
|
2920
|
+
* mode; // typed as "normal" | "insert"
|
|
2921
|
+
* ```
|
|
2922
|
+
*
|
|
2923
|
+
* Typed overload of `editor.getPluginConfig`. Plugins that declared
|
|
2924
|
+
* their fields via `defineConfigX` can pass the shape type explicitly:
|
|
2925
|
+
* `editor.getPluginConfig<{ autoEnable: boolean; ... }>()`. Without
|
|
2926
|
+
* the generic, falls back to `unknown`.
|
|
2927
|
+
*/
|
|
2928
|
+
interface EditorAPI {
|
|
2929
|
+
defineConfigEnum<E extends string>(name: string, options: {
|
|
2930
|
+
values: readonly E[];
|
|
2931
|
+
default: NoInfer<E>;
|
|
2932
|
+
description?: string;
|
|
2933
|
+
}): E;
|
|
2934
|
+
getPluginConfig<T = unknown>(): T;
|
|
2935
|
+
}
|
|
2936
|
+
/**
|
|
2638
2937
|
* Maps every hook event name to its payload type.
|
|
2639
2938
|
*
|
|
2640
2939
|
* Payloads match the flat JSON produced by `hook_args_to_json` on the Rust
|