@fresh-editor/fresh-editor 0.3.7 → 0.3.9
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 +97 -0
- package/README.md +0 -1
- package/package.json +1 -1
- package/plugins/audit_mode.i18n.json +28 -0
- package/plugins/audit_mode.ts +34 -3
- package/plugins/config-schema.json +7 -0
- package/plugins/dashboard.ts +3 -3
- package/plugins/env-manager.ts +168 -0
- package/plugins/examples/bookmarks.ts +3 -2
- package/plugins/git_log.ts +58 -75
- package/plugins/lib/finder.ts +57 -10
- package/plugins/lib/fresh.d.ts +150 -3
- package/plugins/lib/widgets.ts +8 -0
- package/plugins/live_diff.ts +12 -17
- package/plugins/live_grep.i18n.json +349 -27
- package/plugins/live_grep.ts +596 -141
- package/plugins/orchestrator.ts +1506 -534
- package/plugins/pkg.ts +168 -3
- package/plugins/schemas/theme.schema.json +53 -14
- package/plugins/theme_editor.i18n.json +84 -84
- package/plugins/tsconfig.json +1 -0
- package/plugins/vi_mode.ts +3 -3
- package/themes/light.json +1 -1
- package/themes/terminal.json +3 -3
package/plugins/git_log.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
buildCommitLogEntries,
|
|
6
6
|
fetchGitLog,
|
|
7
7
|
} from "./lib/git_history.ts";
|
|
8
|
-
import { button, flexSpacer,
|
|
8
|
+
import { button, flexSpacer, list, row, WidgetPanel } from "./lib/index.ts";
|
|
9
9
|
|
|
10
10
|
const editor = getEditor();
|
|
11
11
|
|
|
@@ -105,20 +105,23 @@ const SELECT_DEBOUNCE_MS = 60;
|
|
|
105
105
|
// the log, and opens the file at the cursor when pressed in the detail).
|
|
106
106
|
// =============================================================================
|
|
107
107
|
|
|
108
|
-
// j/k/Up/Down/PageUp/PageDown
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
108
|
+
// The log pane is cursor-driven: j/k/Up/Down/PageUp/PageDown move the
|
|
109
|
+
// pane's real buffer cursor (normal editor movement), which scrolls via
|
|
110
|
+
// the standard `ensure_cursor_visible` wheel — only when the cursor
|
|
111
|
+
// crosses the top/bottom edge. The cursor is the source of truth for
|
|
112
|
+
// which commit is selected; a `cursor_moved` subscription mirrors its
|
|
113
|
+
// line into the List highlight + detail pane. On the detail pane the
|
|
114
|
+
// same keys scroll the diff. Other actions (q/r/y/Tab/Return) are direct
|
|
115
|
+
// bindings — they don't depend on the cursor row.
|
|
113
116
|
editor.defineMode(
|
|
114
117
|
"git-log",
|
|
115
118
|
[
|
|
116
|
-
["k", "
|
|
117
|
-
["j", "
|
|
118
|
-
["Up", "
|
|
119
|
-
["Down", "
|
|
120
|
-
["PageUp", "
|
|
121
|
-
["PageDown", "
|
|
119
|
+
["k", "move_up"],
|
|
120
|
+
["j", "move_down"],
|
|
121
|
+
["Up", "move_up"],
|
|
122
|
+
["Down", "move_down"],
|
|
123
|
+
["PageUp", "move_page_up"],
|
|
124
|
+
["PageDown", "move_page_down"],
|
|
122
125
|
["Return", "git_log_enter"],
|
|
123
126
|
["Tab", "git_log_tab"],
|
|
124
127
|
["q", "git_log_q"],
|
|
@@ -130,52 +133,6 @@ editor.defineMode(
|
|
|
130
133
|
true, // inherit Normal-context bindings for unbound keys
|
|
131
134
|
);
|
|
132
135
|
|
|
133
|
-
function git_log_select_up(): void {
|
|
134
|
-
if (isLogPanelActive()) {
|
|
135
|
-
state.logPanel?.command(key("Up"));
|
|
136
|
-
} else {
|
|
137
|
-
editor.executeAction("move_up");
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
function git_log_select_down(): void {
|
|
141
|
-
if (isLogPanelActive()) {
|
|
142
|
-
state.logPanel?.command(key("Down"));
|
|
143
|
-
} else {
|
|
144
|
-
editor.executeAction("move_down");
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
function git_log_select_page_up(): void {
|
|
148
|
-
if (isLogPanelActive()) {
|
|
149
|
-
state.logPanel?.command(key("PageUp"));
|
|
150
|
-
} else {
|
|
151
|
-
editor.executeAction("move_page_up");
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
function git_log_select_page_down(): void {
|
|
155
|
-
if (isLogPanelActive()) {
|
|
156
|
-
state.logPanel?.command(key("PageDown"));
|
|
157
|
-
} else {
|
|
158
|
-
editor.executeAction("move_page_down");
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/** True iff the log panel is the focused buffer in the group. The
|
|
163
|
-
* group's bindings (j/k/Up/Down/PageUp/PageDown) apply to all panels
|
|
164
|
-
* uniformly; we only want navigation to drive the List widget when
|
|
165
|
-
* the user is *on* the log panel. From the detail panel, the same
|
|
166
|
-
* keys must move the buffer cursor (so users can scroll the diff
|
|
167
|
-
* before pressing Enter on a diff line to open the file view). */
|
|
168
|
-
function isLogPanelActive(): boolean {
|
|
169
|
-
return (
|
|
170
|
-
state.logBufferId !== null &&
|
|
171
|
-
editor.getActiveBufferId() === state.logBufferId
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
registerHandler("git_log_select_up", git_log_select_up);
|
|
175
|
-
registerHandler("git_log_select_down", git_log_select_down);
|
|
176
|
-
registerHandler("git_log_select_page_up", git_log_select_page_up);
|
|
177
|
-
registerHandler("git_log_select_page_down", git_log_select_page_down);
|
|
178
|
-
|
|
179
136
|
// =============================================================================
|
|
180
137
|
// Panel layout
|
|
181
138
|
// =============================================================================
|
|
@@ -268,19 +225,13 @@ editor.on("widget_event", (data) => {
|
|
|
268
225
|
}
|
|
269
226
|
return;
|
|
270
227
|
}
|
|
271
|
-
// Log pane (List of commit rows)
|
|
272
|
-
//
|
|
273
|
-
//
|
|
228
|
+
// Log pane (List of commit rows). Selection is cursor-driven (see the
|
|
229
|
+
// `cursor_moved` handler), so the List's `select` event is ignored —
|
|
230
|
+
// a row click places the buffer cursor, and `cursor_moved` mirrors it
|
|
231
|
+
// into the selection. `activate` (Enter / double-click) still opens.
|
|
274
232
|
if (state.logPanel !== null && data.panel_id === state.logPanel.id()) {
|
|
275
|
-
if (data.event_type === "select") {
|
|
276
|
-
const idx =
|
|
277
|
-
typeof data.payload?.index === "number" ? data.payload.index : -1;
|
|
278
|
-
if (idx >= 0) void on_log_select(idx);
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
233
|
if (data.event_type === "activate") {
|
|
282
234
|
void git_log_enter();
|
|
283
|
-
return;
|
|
284
235
|
}
|
|
285
236
|
return;
|
|
286
237
|
}
|
|
@@ -300,6 +251,11 @@ function detailFooter(hash: string): string {
|
|
|
300
251
|
return editor.t("status.commit_ready", { hash });
|
|
301
252
|
}
|
|
302
253
|
|
|
254
|
+
/** Stable widget key for the log List. The host keys selection +
|
|
255
|
+
* scroll instance state off this; the plugin re-pins selection
|
|
256
|
+
* through it after click/keyboard `select` events. */
|
|
257
|
+
const LOG_LIST_KEY = "git-log-list";
|
|
258
|
+
|
|
303
259
|
function renderLog(): void {
|
|
304
260
|
if (state.logPanel === null) return;
|
|
305
261
|
// List takes the per-row entries directly. selectedIndex: -1 on the
|
|
@@ -321,7 +277,7 @@ function renderLog(): void {
|
|
|
321
277
|
// scroll handle viewport. Revisit if commit lists grow into the
|
|
322
278
|
// tens of thousands.
|
|
323
279
|
visibleRows: Math.max(1, state.commits.length),
|
|
324
|
-
key:
|
|
280
|
+
key: LOG_LIST_KEY,
|
|
325
281
|
}),
|
|
326
282
|
);
|
|
327
283
|
}
|
|
@@ -652,13 +608,20 @@ async function show_git_log(): Promise<void> {
|
|
|
652
608
|
|
|
653
609
|
renderToolbar();
|
|
654
610
|
renderLog();
|
|
655
|
-
//
|
|
656
|
-
//
|
|
657
|
-
//
|
|
611
|
+
// Cursor-driven selection: give the log pane a real, visible cursor and
|
|
612
|
+
// take ownership of it (`setBufferShowCursors` locks it so the widget
|
|
613
|
+
// runtime won't clear it on repaint). The cursor's line is the selected
|
|
614
|
+
// commit; `cursor_moved` mirrors it into the List highlight + detail.
|
|
615
|
+
// Start on HEAD (line 0). Scrolling is the normal cursor-follow wheel.
|
|
616
|
+
if (state.logBufferId !== null) {
|
|
617
|
+
editor.setBufferShowCursors(state.logBufferId, true);
|
|
618
|
+
editor.setBufferCursor(state.logBufferId, 0);
|
|
619
|
+
}
|
|
658
620
|
await refreshDetail();
|
|
659
621
|
|
|
660
622
|
editor.on("resize", on_git_log_resize);
|
|
661
623
|
editor.on("buffer_closed", on_git_log_buffer_closed);
|
|
624
|
+
editor.on("cursor_moved", on_git_log_cursor_moved);
|
|
662
625
|
|
|
663
626
|
editor.setStatus(
|
|
664
627
|
editor.t("status.log_ready", { count: String(state.commits.length) })
|
|
@@ -673,6 +636,7 @@ function git_log_cleanup(): void {
|
|
|
673
636
|
if (!state.isOpen) return;
|
|
674
637
|
editor.off("resize", on_git_log_resize);
|
|
675
638
|
editor.off("buffer_closed", on_git_log_buffer_closed);
|
|
639
|
+
editor.off("cursor_moved", on_git_log_cursor_moved);
|
|
676
640
|
// Kill any still-running `git show` spawns — we no longer care.
|
|
677
641
|
for (const [, handle] of state.inFlightSpawns) {
|
|
678
642
|
handle.kill?.();
|
|
@@ -1114,15 +1078,34 @@ function git_log_file_view_close(): void {
|
|
|
1114
1078
|
registerHandler("git_log_file_view_close", git_log_file_view_close);
|
|
1115
1079
|
|
|
1116
1080
|
// =============================================================================
|
|
1117
|
-
// Selection tracking —
|
|
1118
|
-
//
|
|
1081
|
+
// Selection tracking — the log pane is cursor-driven. The buffer cursor's
|
|
1082
|
+
// line (set by arrow-key movement or a click) is the selected commit; this
|
|
1083
|
+
// `cursor_moved` subscription mirrors it into the List highlight and the
|
|
1084
|
+
// detail pane. Scrolling is handled by the normal cursor-follow wheel, so
|
|
1085
|
+
// the viewport only moves when the cursor crosses the top/bottom edge.
|
|
1119
1086
|
// =============================================================================
|
|
1120
1087
|
|
|
1121
|
-
|
|
1088
|
+
function on_git_log_cursor_moved(data: { buffer_id: number; line: number }): void {
|
|
1089
|
+
if (!state.isOpen || state.logBufferId === null) return;
|
|
1090
|
+
if (data.buffer_id !== state.logBufferId) return;
|
|
1091
|
+
// `cursor_moved.line` is 1-based; commit rows are 0-based (no header),
|
|
1092
|
+
// so the selected commit index is `line - 1`.
|
|
1093
|
+
const idx = data.line - 1;
|
|
1094
|
+
if (idx < 0 || idx >= state.commits.length) return;
|
|
1095
|
+
void selectCommitLine(idx);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
async function selectCommitLine(idx: number): Promise<void> {
|
|
1122
1099
|
if (!state.isOpen) return;
|
|
1123
1100
|
if (idx === state.selectedIndex) return;
|
|
1124
1101
|
state.selectedIndex = idx;
|
|
1125
1102
|
|
|
1103
|
+
// Move the List's highlight bar to the cursor's row. The cursor itself
|
|
1104
|
+
// is the real (plugin-owned) buffer cursor, so it stays exactly where
|
|
1105
|
+
// the user moved or clicked it — this only repaints the row styling,
|
|
1106
|
+
// and the repaint preserves the cursor position.
|
|
1107
|
+
state.logPanel?.setSelectedIndex(LOG_LIST_KEY, idx);
|
|
1108
|
+
|
|
1126
1109
|
const commit = state.commits[state.selectedIndex];
|
|
1127
1110
|
if (commit) {
|
|
1128
1111
|
editor.setStatus(
|
package/plugins/lib/finder.ts
CHANGED
|
@@ -447,6 +447,33 @@ export class Finder<T> {
|
|
|
447
447
|
// Mode flags
|
|
448
448
|
private isPromptMode = false;
|
|
449
449
|
private isPanelMode = false;
|
|
450
|
+
/** True when the active prompt is a centred floating overlay. Search
|
|
451
|
+
* status then goes to the overlay's own footer (visible inside the frame)
|
|
452
|
+
* rather than the editor status bar (off at the bottom, easy to miss). */
|
|
453
|
+
private isOverlay = false;
|
|
454
|
+
|
|
455
|
+
/** Present a search-status message where the user is actually looking: on
|
|
456
|
+
* the overlay's input row (right-aligned by the match count) for a
|
|
457
|
+
* floating overlay, else the editor status bar. */
|
|
458
|
+
private setSearchStatus(message: string): void {
|
|
459
|
+
if (this.isOverlay) {
|
|
460
|
+
this.editor.setPromptStatus(message);
|
|
461
|
+
} else {
|
|
462
|
+
this.editor.setStatus(message);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/** Report a successful search with `count` matches. In overlay mode the
|
|
467
|
+
* "N / total" count on the input row already conveys this, so the status
|
|
468
|
+
* is cleared to avoid duplicating it; the status bar (non-overlay) still
|
|
469
|
+
* shows "Found N matches". */
|
|
470
|
+
private reportFound(count: number): void {
|
|
471
|
+
if (this.isOverlay) {
|
|
472
|
+
this.editor.setPromptStatus("");
|
|
473
|
+
} else {
|
|
474
|
+
this.editor.setStatus(`Found ${count} matches`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
450
477
|
|
|
451
478
|
// Handler names (for cleanup)
|
|
452
479
|
private handlerPrefix: string;
|
|
@@ -513,6 +540,7 @@ export class Finder<T> {
|
|
|
513
540
|
|
|
514
541
|
// Start the prompt
|
|
515
542
|
const overlay = options.floatingOverlay === true;
|
|
543
|
+
this.isOverlay = overlay;
|
|
516
544
|
if (options.initialQuery) {
|
|
517
545
|
this.editor.startPromptWithInitial(
|
|
518
546
|
options.title,
|
|
@@ -525,7 +553,7 @@ export class Finder<T> {
|
|
|
525
553
|
const result = this.editor.startPrompt(options.title, this.config.id, overlay);
|
|
526
554
|
this.editor.debug(`[Finder] startPrompt returned: ${result}`);
|
|
527
555
|
}
|
|
528
|
-
this.
|
|
556
|
+
this.setSearchStatus("Type to search…");
|
|
529
557
|
}
|
|
530
558
|
|
|
531
559
|
/**
|
|
@@ -545,6 +573,11 @@ export class Finder<T> {
|
|
|
545
573
|
// unchanged query.
|
|
546
574
|
this.promptState.lastQuery = "";
|
|
547
575
|
if (query.length === 0) return;
|
|
576
|
+
// The backend (or scope set) changed, so the on-screen results are now
|
|
577
|
+
// stale. Clear them and show progress immediately rather than leaving the
|
|
578
|
+
// previous output up while the (possibly slow) new search runs.
|
|
579
|
+
this.updatePromptResults([]);
|
|
580
|
+
this.setSearchStatus("Searching…");
|
|
548
581
|
await this.runSearch(query, this.currentSource);
|
|
549
582
|
}
|
|
550
583
|
|
|
@@ -713,9 +746,9 @@ export class Finder<T> {
|
|
|
713
746
|
this.updatePromptResults(filtered);
|
|
714
747
|
|
|
715
748
|
if (filtered.length > 0) {
|
|
716
|
-
this.
|
|
749
|
+
this.reportFound(filtered.length);
|
|
717
750
|
} else {
|
|
718
|
-
this.
|
|
751
|
+
this.setSearchStatus("No matches");
|
|
719
752
|
}
|
|
720
753
|
} else {
|
|
721
754
|
// Search mode: run external search
|
|
@@ -745,6 +778,7 @@ export class Finder<T> {
|
|
|
745
778
|
}
|
|
746
779
|
this.editor.setPromptSuggestions([]);
|
|
747
780
|
this.promptState.results = [];
|
|
781
|
+
this.setSearchStatus("");
|
|
748
782
|
return;
|
|
749
783
|
}
|
|
750
784
|
|
|
@@ -768,6 +802,11 @@ export class Finder<T> {
|
|
|
768
802
|
}
|
|
769
803
|
this.promptState.lastQuery = query;
|
|
770
804
|
|
|
805
|
+
// A search is now actually starting (every query change that gets here —
|
|
806
|
+
// typing, deleting, provider/scope refresh). Show pending status so the
|
|
807
|
+
// user sees the re-scan in progress rather than a stale result count.
|
|
808
|
+
this.setSearchStatus("Searching…");
|
|
809
|
+
|
|
771
810
|
try {
|
|
772
811
|
const searchResult = source.search(query);
|
|
773
812
|
|
|
@@ -793,21 +832,21 @@ export class Finder<T> {
|
|
|
793
832
|
this.updatePromptResults(parsed);
|
|
794
833
|
|
|
795
834
|
if (parsed.length > 0) {
|
|
796
|
-
this.
|
|
835
|
+
this.reportFound(parsed.length);
|
|
797
836
|
// Show preview of first result
|
|
798
837
|
if (this.shouldShowPreview()) {
|
|
799
838
|
await this.updatePreview(this.promptState.entries[0]);
|
|
800
839
|
}
|
|
801
840
|
} else {
|
|
802
|
-
this.
|
|
841
|
+
this.setSearchStatus("No matches");
|
|
803
842
|
}
|
|
804
843
|
} else if (result.exit_code === 1) {
|
|
805
844
|
// No matches
|
|
806
845
|
this.updatePromptResults([]);
|
|
807
|
-
this.
|
|
846
|
+
this.setSearchStatus("No matches");
|
|
808
847
|
} else if (result.exit_code !== -1) {
|
|
809
848
|
// Error (ignore -1 which means killed)
|
|
810
|
-
this.
|
|
849
|
+
this.setSearchStatus(`Search error: ${result.stderr}`);
|
|
811
850
|
}
|
|
812
851
|
} else {
|
|
813
852
|
// Promise<T[]>
|
|
@@ -821,12 +860,12 @@ export class Finder<T> {
|
|
|
821
860
|
this.updatePromptResults(results);
|
|
822
861
|
|
|
823
862
|
if (results.length > 0) {
|
|
824
|
-
this.
|
|
863
|
+
this.reportFound(results.length);
|
|
825
864
|
if (this.shouldShowPreview()) {
|
|
826
865
|
await this.updatePreview(this.promptState.entries[0]);
|
|
827
866
|
}
|
|
828
867
|
} else {
|
|
829
|
-
this.
|
|
868
|
+
this.setSearchStatus("No matches");
|
|
830
869
|
}
|
|
831
870
|
}
|
|
832
871
|
} catch (e) {
|
|
@@ -864,7 +903,15 @@ export class Finder<T> {
|
|
|
864
903
|
(entry, i) => ({
|
|
865
904
|
text: entry.label,
|
|
866
905
|
description: entry.description,
|
|
867
|
-
value
|
|
906
|
+
// The preview pane uses `value` as the authoritative
|
|
907
|
+
// `path:line:col` for the result. We must not rely on parsing the
|
|
908
|
+
// user-facing label, which may carry source badges (e.g. "[term]")
|
|
909
|
+
// that make the label unparseable as a path.
|
|
910
|
+
value: entry.location
|
|
911
|
+
? `${entry.location.file}:${entry.location.line}:${
|
|
912
|
+
entry.location.column ?? 1
|
|
913
|
+
}`
|
|
914
|
+
: `${i}`,
|
|
868
915
|
disabled: false,
|
|
869
916
|
})
|
|
870
917
|
);
|
package/plugins/lib/fresh.d.ts
CHANGED
|
@@ -621,6 +621,50 @@ type TerminalResult = {
|
|
|
621
621
|
*/
|
|
622
622
|
splitId: number | null;
|
|
623
623
|
};
|
|
624
|
+
type CreateWindowWithTerminalOptions = {
|
|
625
|
+
/**
|
|
626
|
+
* Absolute path to the new session's worktree / project
|
|
627
|
+
* root. Relative paths are rejected (logged, no window
|
|
628
|
+
* created).
|
|
629
|
+
*/
|
|
630
|
+
root: string;
|
|
631
|
+
/**
|
|
632
|
+
* Human-readable label for the new session. When empty,
|
|
633
|
+
* defaults to the basename of `root`.
|
|
634
|
+
*/
|
|
635
|
+
label: string;
|
|
636
|
+
/**
|
|
637
|
+
* Working directory for the spawned terminal. Defaults to
|
|
638
|
+
* `root` when omitted.
|
|
639
|
+
*/
|
|
640
|
+
cwd?: string;
|
|
641
|
+
/**
|
|
642
|
+
* Argv to spawn directly inside the PTY. `None` keeps the
|
|
643
|
+
* shell-and-type behaviour; `Some([cmd, ...args])` runs the
|
|
644
|
+
* command as the PTY child (used by Orchestrator so the
|
|
645
|
+
* agent process is the PTY's direct child).
|
|
646
|
+
*/
|
|
647
|
+
command?: Array<string>;
|
|
648
|
+
/**
|
|
649
|
+
* Tab title override. Defaults to `command[0]`'s basename
|
|
650
|
+
* when `command` is set, or "Terminal N" otherwise.
|
|
651
|
+
*/
|
|
652
|
+
title?: string;
|
|
653
|
+
};
|
|
654
|
+
type SessionWithTerminalResult = {
|
|
655
|
+
/**
|
|
656
|
+
* The new window's id.
|
|
657
|
+
*/
|
|
658
|
+
windowId: number;
|
|
659
|
+
/**
|
|
660
|
+
* The seeded terminal's id (for `sendTerminalInput`, etc.).
|
|
661
|
+
*/
|
|
662
|
+
terminalId: number;
|
|
663
|
+
/**
|
|
664
|
+
* The seeded terminal buffer's id.
|
|
665
|
+
*/
|
|
666
|
+
bufferId: number;
|
|
667
|
+
};
|
|
624
668
|
type CreateTerminalOptions = {
|
|
625
669
|
/**
|
|
626
670
|
* Working directory for the terminal (defaults to editor cwd)
|
|
@@ -691,6 +735,13 @@ type CursorInfo = {
|
|
|
691
735
|
start: number;
|
|
692
736
|
end: number;
|
|
693
737
|
} | null;
|
|
738
|
+
/**
|
|
739
|
+
* 0-indexed line number of the cursor. `None` when the line index is
|
|
740
|
+
* unavailable — e.g. a huge file whose line scan hasn't completed, where
|
|
741
|
+
* the editor positions purely by byte offset. Plugins must treat `null`
|
|
742
|
+
* as "unknown", never as line 0.
|
|
743
|
+
*/
|
|
744
|
+
line: number | null;
|
|
694
745
|
};
|
|
695
746
|
type OverlayOptions = {
|
|
696
747
|
/**
|
|
@@ -722,6 +773,13 @@ type OverlayOptions = {
|
|
|
722
773
|
*/
|
|
723
774
|
extendToLineEnd: boolean;
|
|
724
775
|
/**
|
|
776
|
+
* When `true`, `fg` is applied only on cells whose existing fg
|
|
777
|
+
* matches this overlay's resolved bg — i.e. a same-colour fg/bg
|
|
778
|
+
* collision. Lets a row-wide overlay stay legible on tokens that
|
|
779
|
+
* share the bg's colour without repainting unrelated tokens.
|
|
780
|
+
*/
|
|
781
|
+
fgOnCollisionOnly: boolean;
|
|
782
|
+
/**
|
|
725
783
|
* Optional URL for OSC 8 terminal hyperlinks.
|
|
726
784
|
* When set, the overlay text becomes a clickable hyperlink in terminals
|
|
727
785
|
* that support OSC 8 escape sequences.
|
|
@@ -1006,7 +1064,7 @@ type WidgetSpec = {
|
|
|
1006
1064
|
* `WidgetMutation::SetCompletions`. An empty `items`
|
|
1007
1065
|
* closes the popup.
|
|
1008
1066
|
*/
|
|
1009
|
-
completions?: Array<string>;
|
|
1067
|
+
completions?: Array<string | CompletionItem>;
|
|
1010
1068
|
/**
|
|
1011
1069
|
* How many candidate rows the popup paints at once
|
|
1012
1070
|
* when it opens. Excess candidates stay reachable
|
|
@@ -1091,7 +1149,7 @@ type WidgetMutation = {
|
|
|
1091
1149
|
} | {
|
|
1092
1150
|
"kind": "setCompletions";
|
|
1093
1151
|
widgetKey: string;
|
|
1094
|
-
items: Array<string>;
|
|
1152
|
+
items: Array<string | CompletionItem>;
|
|
1095
1153
|
} | {
|
|
1096
1154
|
"kind": "setChecked";
|
|
1097
1155
|
widgetKey: string;
|
|
@@ -1644,7 +1702,13 @@ interface EditorAPI {
|
|
|
1644
1702
|
*/
|
|
1645
1703
|
listSplits(): SplitSnapshot[];
|
|
1646
1704
|
/**
|
|
1647
|
-
* Get the line number (0-indexed) of the primary cursor
|
|
1705
|
+
* Get the line number (0-indexed) of the primary cursor.
|
|
1706
|
+
*
|
|
1707
|
+
* @deprecated Use `getPrimaryCursor()?.line` instead. This accessor cannot
|
|
1708
|
+
* represent "line index unavailable" (huge files before their line scan) —
|
|
1709
|
+
* it returns `0` in that case, indistinguishable from a real first line.
|
|
1710
|
+
* `getPrimaryCursor().line` is `number | null` and also covers every cursor
|
|
1711
|
+
* via `getAllCursors()`.
|
|
1648
1712
|
*/
|
|
1649
1713
|
getCursorLine(): number;
|
|
1650
1714
|
/**
|
|
@@ -1813,6 +1877,20 @@ interface EditorAPI {
|
|
|
1813
1877
|
*/
|
|
1814
1878
|
getAuthorityLabel(): string;
|
|
1815
1879
|
/**
|
|
1880
|
+
* Current Workspace Trust level for the active project: `"restricted"`,
|
|
1881
|
+
* `"trusted"`, or `"blocked"` (empty when unavailable). Exposed to JS as
|
|
1882
|
+
* `editor.workspaceTrustLevel()`. Plugins that run repo-controlled work
|
|
1883
|
+
* should treat anything other than `"trusted"` as "do not execute".
|
|
1884
|
+
*/
|
|
1885
|
+
workspaceTrustLevel(): string;
|
|
1886
|
+
/**
|
|
1887
|
+
* Whether an environment is currently active (set via `editor.setEnv`).
|
|
1888
|
+
* Exposed to JS as `editor.envActive()`. Lets the env-manager plugin
|
|
1889
|
+
* reflect activation and re-establish its file watch after the restart
|
|
1890
|
+
* that `setEnv` triggers.
|
|
1891
|
+
*/
|
|
1892
|
+
envActive(): boolean;
|
|
1893
|
+
/**
|
|
1816
1894
|
* Join path components (variadic - accepts multiple string arguments)
|
|
1817
1895
|
* Always uses forward slashes for cross-platform consistency (like Node.js path.posix.join)
|
|
1818
1896
|
*
|
|
@@ -2048,6 +2126,21 @@ interface EditorAPI {
|
|
|
2048
2126
|
*/
|
|
2049
2127
|
getDataDir(): string;
|
|
2050
2128
|
/**
|
|
2129
|
+
* Per-working-directory data root for plugin state that should be scoped
|
|
2130
|
+
* to the current project root / worktree rather than shared across all of
|
|
2131
|
+
* them (`<data_dir>/workdirs/<encoded-cwd>/`). Prefer this over
|
|
2132
|
+
* `getDataDir()` for per-project state; the directory is not created for
|
|
2133
|
+
* you. Note: terminal scrollback and orchestrator state use their own
|
|
2134
|
+
* dedicated layouts (see `getTerminalDir()`), not this root.
|
|
2135
|
+
*/
|
|
2136
|
+
getWorkingDataDir(): string;
|
|
2137
|
+
/**
|
|
2138
|
+
* Directory holding terminal scrollback backing files for the current
|
|
2139
|
+
* working directory. Each project root / worktree has its own subdir, so
|
|
2140
|
+
* a search can stay scoped to the active project's terminals.
|
|
2141
|
+
*/
|
|
2142
|
+
getTerminalDir(): string;
|
|
2143
|
+
/**
|
|
2051
2144
|
* Get themes directory path
|
|
2052
2145
|
*/
|
|
2053
2146
|
getThemesDir(): string;
|
|
@@ -2386,6 +2479,29 @@ interface EditorAPI {
|
|
|
2386
2479
|
*/
|
|
2387
2480
|
setPromptFooter(footer: StyledText[]): boolean;
|
|
2388
2481
|
/**
|
|
2482
|
+
* Set the floating-overlay prompt's header toolbar as a `WidgetSpec`
|
|
2483
|
+
* (real, clickable `Toggle`/`Button` widgets), rendered in place of the
|
|
2484
|
+
* styled-text title. Give each control a `key` equal to the action it
|
|
2485
|
+
* should fire on click (e.g. `"live_grep_toggle_files"`). Pass `null` to
|
|
2486
|
+
* clear it. No visible effect on non-overlay prompts.
|
|
2487
|
+
*/
|
|
2488
|
+
setPromptToolbar(spec: WidgetSpec | null): boolean;
|
|
2489
|
+
/**
|
|
2490
|
+
* Set the floating-overlay prompt's input-row status text, shown
|
|
2491
|
+
* right-aligned just left of the `selected / total` count (e.g.
|
|
2492
|
+
* "Searching…", "No matches"). Empty string clears it. No effect on
|
|
2493
|
+
* non-overlay prompts.
|
|
2494
|
+
*/
|
|
2495
|
+
setPromptStatus(status: string): boolean;
|
|
2496
|
+
/**
|
|
2497
|
+
* Toggle a floating-overlay toolbar control by its widget `key`. The host
|
|
2498
|
+
* owns the toggle's checked state, flips it in place, and emits a
|
|
2499
|
+
* `widget_event` (`event_type: "toggle"`, payload `{ checked }`). Use this
|
|
2500
|
+
* to route a plugin's own keyboard shortcut through the same host path as
|
|
2501
|
+
* a click or Space on the toggle, then react in your `widget_event` handler.
|
|
2502
|
+
*/
|
|
2503
|
+
toggleOverlayToolbarWidget(key: string): boolean;
|
|
2504
|
+
/**
|
|
2389
2505
|
* Override the currently-highlighted suggestion row in the
|
|
2390
2506
|
* open prompt. The editor clamps `index` to the suggestion
|
|
2391
2507
|
* list's bounds and the renderer scrolls it into view on
|
|
@@ -2774,6 +2890,16 @@ interface EditorAPI {
|
|
|
2774
2890
|
*/
|
|
2775
2891
|
clearAuthority(): void;
|
|
2776
2892
|
/**
|
|
2893
|
+
* Activate an environment: set the live env recipe (`snippet` run in
|
|
2894
|
+
* `dir`). Applied to every spawn, re-evaluated on demand — no restart.
|
|
2895
|
+
* Honored only when the workspace is Trusted.
|
|
2896
|
+
*/
|
|
2897
|
+
setEnv(snippet: string, dir: string | null): void;
|
|
2898
|
+
/**
|
|
2899
|
+
* Deactivate the environment — spawns return to the inherited env.
|
|
2900
|
+
*/
|
|
2901
|
+
clearEnv(): void;
|
|
2902
|
+
/**
|
|
2777
2903
|
* Override the Remote Indicator's displayed state. Plugins call
|
|
2778
2904
|
* this to surface lifecycle transitions that the authority layer
|
|
2779
2905
|
* doesn't know about yet — "Connecting" while `devcontainer up`
|
|
@@ -2799,6 +2925,19 @@ interface EditorAPI {
|
|
|
2799
2925
|
*/
|
|
2800
2926
|
clearRemoteIndicatorState(): void;
|
|
2801
2927
|
/**
|
|
2928
|
+
* Fetch a URL over HTTP(S) and stream the response body into `target_path`.
|
|
2929
|
+
*
|
|
2930
|
+
* Resolves with a `SpawnResult`-shaped value: `exit_code` is `0` on a
|
|
2931
|
+
* 2xx response (file written), the HTTP status code on non-2xx
|
|
2932
|
+
* (target file untouched), and `-1` on transport errors. `stderr`
|
|
2933
|
+
* carries an error message in the non-success cases; `stdout` is
|
|
2934
|
+
* always empty.
|
|
2935
|
+
*
|
|
2936
|
+
* This uses the editor's built-in HTTP client (`ureq`), so plugins
|
|
2937
|
+
* don't need `curl`/`wget` on PATH.
|
|
2938
|
+
*/
|
|
2939
|
+
httpFetch(url: string, targetPath: string): ProcessHandle<SpawnResult>;
|
|
2940
|
+
/**
|
|
2802
2941
|
* Wait for a process to complete and get its result (async)
|
|
2803
2942
|
*/
|
|
2804
2943
|
spawnProcessWait(processId: number): Promise<SpawnResult>;
|
|
@@ -2851,6 +2990,14 @@ interface EditorAPI {
|
|
|
2851
2990
|
*/
|
|
2852
2991
|
createTerminal(opts?: CreateTerminalOptions): Promise<TerminalResult>;
|
|
2853
2992
|
/**
|
|
2993
|
+
* Create a new editor window seeded with an agent terminal as
|
|
2994
|
+
* its only buffer. Atomic — replaces the legacy
|
|
2995
|
+
* `createWindow` + `setActiveWindow` + `createTerminal`
|
|
2996
|
+
* chain that left a transient `[No Name]` tab alongside the
|
|
2997
|
+
* agent terminal.
|
|
2998
|
+
*/
|
|
2999
|
+
createWindowWithTerminal(opts: CreateWindowWithTerminalOptions): Promise<SessionWithTerminalResult>;
|
|
3000
|
+
/**
|
|
2854
3001
|
* Send input data to a terminal
|
|
2855
3002
|
*/
|
|
2856
3003
|
sendTerminalInput(terminalId: number, data: string): boolean;
|
package/plugins/lib/widgets.ts
CHANGED
|
@@ -60,6 +60,14 @@ export function row(...children: WidgetSpec[]): WidgetSpec {
|
|
|
60
60
|
return { kind: "row", children };
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/** Horizontal layout that **wraps**: children that don't fit on one line
|
|
64
|
+
* reflow onto additional lines (growing the row's height) instead of being
|
|
65
|
+
* truncated. Children are never split, so wrap a logical group (e.g. a
|
|
66
|
+
* toggle + its accelerator) in a nested `row(...)` to keep it intact. */
|
|
67
|
+
export function wrappingRow(...children: WidgetSpec[]): WidgetSpec {
|
|
68
|
+
return { kind: "row", children, wrap: true };
|
|
69
|
+
}
|
|
70
|
+
|
|
63
71
|
/** Vertical layout. Children stacked top-to-bottom. */
|
|
64
72
|
export function col(...children: WidgetSpec[]): WidgetSpec {
|
|
65
73
|
return { kind: "col", children };
|
package/plugins/live_diff.ts
CHANGED
|
@@ -38,19 +38,18 @@ const PRIORITY = 9;
|
|
|
38
38
|
|
|
39
39
|
// Theme keys for backgrounds and "on top of bg" foregrounds. These
|
|
40
40
|
// are resolved at render time by the editor, so the diff colors track
|
|
41
|
-
// the active theme automatically. The `editor.diff_*
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
// override `editor.diff_*_fg` to a contrasting color.
|
|
41
|
+
// the active theme automatically. The `editor.diff_*_collision_fg`
|
|
42
|
+
// keys pair with `fgOnCollisionOnly: true` below: themes that define
|
|
43
|
+
// them get a contrasting fg painted only on cells whose syntax fg
|
|
44
|
+
// happens to match the diff bg (e.g. ANSI Green-on-Green); every
|
|
45
|
+
// other cell keeps its syntax colour.
|
|
47
46
|
const THEME = {
|
|
48
47
|
addedBg: "editor.diff_add_bg",
|
|
49
|
-
addedFg: "editor.
|
|
48
|
+
addedFg: "editor.diff_add_collision_fg",
|
|
50
49
|
modifiedBg: "editor.diff_modify_bg",
|
|
51
|
-
modifiedFg: "editor.
|
|
50
|
+
modifiedFg: "editor.diff_modify_collision_fg",
|
|
52
51
|
removedBg: "editor.diff_remove_bg",
|
|
53
|
-
removedFg: "editor.
|
|
52
|
+
removedFg: "editor.diff_remove_collision_fg",
|
|
54
53
|
};
|
|
55
54
|
|
|
56
55
|
// `setLineIndicator` only accepts RGB triples (not theme keys), so the
|
|
@@ -845,14 +844,9 @@ function renderHunks(state: BufferDiffState, newLines: string[]): void {
|
|
|
845
844
|
for (const h of state.hunks) {
|
|
846
845
|
if (h.kind === "added" || h.kind === "modified") {
|
|
847
846
|
const bg = h.kind === "added" ? THEME.addedBg : THEME.modifiedBg;
|
|
848
|
-
//
|
|
849
|
-
//
|
|
850
|
-
//
|
|
851
|
-
// otherwise collide with same-named syntax colors) get a
|
|
852
|
-
// contrasting fg painted on; themes that don't define the key
|
|
853
|
-
// resolve to `None` in `OverlayFace::ThemedStyle`, so the
|
|
854
|
-
// overlay leaves the cell's fg alone and syntax highlighting
|
|
855
|
-
// shows through unchanged.
|
|
847
|
+
// `fgOnCollisionOnly` keeps syntax highlighting intact on the
|
|
848
|
+
// rest of the line and only paints `fg` where a token's colour
|
|
849
|
+
// would otherwise be invisible against the diff bg.
|
|
856
850
|
const fg = h.kind === "added" ? THEME.addedFg : THEME.modifiedFg;
|
|
857
851
|
for (let i = 0; i < h.newCount; i++) {
|
|
858
852
|
const line = h.newStart + i;
|
|
@@ -871,6 +865,7 @@ function renderHunks(state: BufferDiffState, newLines: string[]): void {
|
|
|
871
865
|
editor.addOverlay(bid, NS_OVERLAY, start, end, {
|
|
872
866
|
bg,
|
|
873
867
|
fg,
|
|
868
|
+
fgOnCollisionOnly: true,
|
|
874
869
|
underline: false,
|
|
875
870
|
bold: false,
|
|
876
871
|
italic: false,
|