@fresh-editor/fresh-editor 0.3.8 → 0.3.10
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 +77 -0
- package/README.md +0 -1
- package/package.json +1 -1
- package/plugins/audit_mode.ts +35 -1
- package/plugins/config-schema.json +9 -3
- package/plugins/dashboard.ts +3 -3
- package/plugins/diagnostics_panel.ts +10 -0
- package/plugins/env-manager.i18n.json +338 -0
- package/plugins/env-manager.ts +23 -33
- package/plugins/examples/bookmarks.ts +3 -2
- package/plugins/lib/finder.ts +101 -17
- package/plugins/lib/fresh.d.ts +100 -42
- package/plugins/lib/widgets.ts +8 -0
- package/plugins/live_diff.ts +12 -1
- package/plugins/live_grep.i18n.json +349 -27
- package/plugins/live_grep.ts +660 -141
- package/plugins/markdown_compose.ts +3 -1
- package/plugins/orchestrator.ts +1493 -504
- package/plugins/schemas/theme.schema.json +15 -2
- package/plugins/search_replace.ts +70 -28
- package/plugins/theme_editor.i18n.json +28 -0
- package/plugins/vi_mode.ts +3 -3
package/plugins/env-manager.ts
CHANGED
|
@@ -82,28 +82,26 @@ function isTrusted(): boolean {
|
|
|
82
82
|
/** Activate (or, when already active, reload) the detected environment. */
|
|
83
83
|
function activate(): void {
|
|
84
84
|
if (!isTrusted()) {
|
|
85
|
-
editor.setStatus(
|
|
86
|
-
"Workspace not trusted — run “Workspace Trust: Trust This Folder” to activate the environment",
|
|
87
|
-
);
|
|
85
|
+
editor.setStatus(editor.t("status.not_trusted"));
|
|
88
86
|
return;
|
|
89
87
|
}
|
|
90
88
|
const det = detect();
|
|
91
89
|
if (!det) {
|
|
92
|
-
editor.setStatus("
|
|
90
|
+
editor.setStatus(editor.t("status.no_env_detected"));
|
|
93
91
|
return;
|
|
94
92
|
}
|
|
95
93
|
// Core captures `snippet` on the active backend and applies it to every
|
|
96
94
|
// spawn; it restarts so language servers re-spawn under the fresh env.
|
|
97
95
|
editor.setEnv(det.snippet, editor.getCwd());
|
|
98
96
|
editor.setStatus(
|
|
99
|
-
|
|
97
|
+
editor.t(editor.envActive() ? "status.reloading" : "status.activating", { name: det.name }),
|
|
100
98
|
);
|
|
101
99
|
}
|
|
102
100
|
registerHandler("env_activate_handler", activate);
|
|
103
101
|
|
|
104
102
|
function useSystem(): void {
|
|
105
103
|
editor.clearEnv();
|
|
106
|
-
editor.setStatus("
|
|
104
|
+
editor.setStatus(editor.t("status.deactivated"));
|
|
107
105
|
}
|
|
108
106
|
registerHandler("env_use_system_handler", useSystem);
|
|
109
107
|
|
|
@@ -111,37 +109,23 @@ function showStatus(): void {
|
|
|
111
109
|
const det = detect();
|
|
112
110
|
const trust = editor.workspaceTrustLevel() || "unavailable";
|
|
113
111
|
if (editor.envActive()) {
|
|
114
|
-
editor.setStatus(`Environment active${det ? ` (${det.name})` : ""}`);
|
|
115
|
-
} else if (det) {
|
|
116
112
|
editor.setStatus(
|
|
117
|
-
|
|
113
|
+
det
|
|
114
|
+
? editor.t("status.env_active_named", { name: det.name })
|
|
115
|
+
: editor.t("status.env_active"),
|
|
118
116
|
);
|
|
117
|
+
} else if (det) {
|
|
118
|
+
editor.setStatus(editor.t("status.env_detected", { name: det.name, trust }));
|
|
119
119
|
} else {
|
|
120
|
-
editor.setStatus(
|
|
120
|
+
editor.setStatus(editor.t("status.no_env", { trust }));
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
registerHandler("env_status_handler", showStatus);
|
|
124
124
|
|
|
125
|
-
editor.registerCommand(
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
);
|
|
130
|
-
editor.registerCommand(
|
|
131
|
-
"env_reload",
|
|
132
|
-
"Env: Reload Environment (re-capture after .envrc/mise.toml change)",
|
|
133
|
-
"env_activate_handler",
|
|
134
|
-
);
|
|
135
|
-
editor.registerCommand(
|
|
136
|
-
"env_use_system",
|
|
137
|
-
"Env: Use System (Deactivate Environment)",
|
|
138
|
-
"env_use_system_handler",
|
|
139
|
-
);
|
|
140
|
-
editor.registerCommand(
|
|
141
|
-
"env_status",
|
|
142
|
-
"Env: Show Environment Status",
|
|
143
|
-
"env_status_handler",
|
|
144
|
-
);
|
|
125
|
+
editor.registerCommand("%cmd.activate", "%cmd.activate_desc", "env_activate_handler");
|
|
126
|
+
editor.registerCommand("%cmd.reload", "%cmd.reload_desc", "env_activate_handler");
|
|
127
|
+
editor.registerCommand("%cmd.use_system", "%cmd.use_system_desc", "env_use_system_handler");
|
|
128
|
+
editor.registerCommand("%cmd.status", "%cmd.status_desc", "env_status_handler");
|
|
145
129
|
|
|
146
130
|
// === Status pill (opt-in to a user's status-bar layout) ===
|
|
147
131
|
|
|
@@ -151,14 +135,20 @@ function refreshStatus(): void {
|
|
|
151
135
|
const det = detect();
|
|
152
136
|
let value: string;
|
|
153
137
|
if (editor.envActive()) {
|
|
154
|
-
value = det
|
|
138
|
+
value = det
|
|
139
|
+
? editor.t("statusbar.active", { name: det.name })
|
|
140
|
+
: editor.t("statusbar.active_unknown");
|
|
141
|
+
} else if (det) {
|
|
142
|
+
value = isTrusted()
|
|
143
|
+
? det.name
|
|
144
|
+
: editor.t("statusbar.locked", { name: det.name });
|
|
155
145
|
} else {
|
|
156
|
-
value =
|
|
146
|
+
value = editor.t("statusbar.system");
|
|
157
147
|
}
|
|
158
148
|
editor.setStatusBarValue(bufferId, STATUS_TOKEN, value);
|
|
159
149
|
}
|
|
160
150
|
|
|
161
|
-
editor.registerStatusBarElement(STATUS_TOKEN, "
|
|
151
|
+
editor.registerStatusBarElement(STATUS_TOKEN, editor.t("statusbar.label"));
|
|
162
152
|
|
|
163
153
|
registerHandler("env_refresh_status", refreshStatus);
|
|
164
154
|
for (const event of ["buffer_activated", "after_file_open", "focus_gained"]) {
|
|
@@ -46,8 +46,9 @@ function getCurrentLocation(): {
|
|
|
46
46
|
|
|
47
47
|
// Helper: Get actual line number using the API
|
|
48
48
|
function getCurrentLineCol(): { line: number; column: number } {
|
|
49
|
-
//
|
|
50
|
-
|
|
49
|
+
// Read the primary cursor's line from the snapshot. `line` is null when the
|
|
50
|
+
// buffer has no line index yet (huge files); fall back to 0 there.
|
|
51
|
+
const lineNumber = editor.getPrimaryCursor()?.line ?? 0;
|
|
51
52
|
|
|
52
53
|
// Get cursor position within the line by reading buffer content
|
|
53
54
|
const bufferId = editor.getActiveBufferId();
|
package/plugins/lib/finder.ts
CHANGED
|
@@ -116,9 +116,37 @@ export interface FinderConfig<T> {
|
|
|
116
116
|
/** Panel-specific: navigate source split when cursor moves (preview without focus change) */
|
|
117
117
|
navigateOnCursorMove?: boolean;
|
|
118
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Panel-specific: whether selecting an entry (Enter) closes the
|
|
121
|
+
* panel. Defaults to `true` — the panel dismisses as it jumps, which
|
|
122
|
+
* suits one-shot lists like Find References. Set `false` to keep the
|
|
123
|
+
* panel docked so the user can step through entries (Vim quickfix /
|
|
124
|
+
* VS Code results list). Only consulted by the default selection
|
|
125
|
+
* handler — a custom `onSelect` owns close behaviour itself.
|
|
126
|
+
*
|
|
127
|
+
* Note: leaving the panel open while `navigateOnCursorMove` is also
|
|
128
|
+
* on re-introduces the focus race the close was guarding against
|
|
129
|
+
* (a `focusSplit` queued after the jump can re-grab the panel), so
|
|
130
|
+
* the two shouldn't be combined without care.
|
|
131
|
+
*/
|
|
132
|
+
closeOnSelect?: boolean;
|
|
133
|
+
|
|
119
134
|
/** Called when the panel or prompt is closed (e.g. via Escape) */
|
|
120
135
|
onClose?: () => void;
|
|
121
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Panel-specific: extra key bindings active while the panel buffer
|
|
139
|
+
* is focused, as `[key, command]` pairs (e.g. `["q", "my_close"]`).
|
|
140
|
+
* `command` is dispatched as a plugin action, so it should name a
|
|
141
|
+
* handler registered via `registerHandler`. These augment the
|
|
142
|
+
* built-in `Return` (select) and `Escape` (close) bindings.
|
|
143
|
+
*
|
|
144
|
+
* Without this, keys a plugin advertises in its panel UI (e.g.
|
|
145
|
+
* "q: close") fall through to the read-only text layer and trip
|
|
146
|
+
* "Editing disabled in this buffer" (issue #2125).
|
|
147
|
+
*/
|
|
148
|
+
panelKeys?: Array<[string, string]>;
|
|
149
|
+
|
|
122
150
|
/**
|
|
123
151
|
* When true, panels created by this Finder are routed into the
|
|
124
152
|
* shared Utility Dock (issue #1796 / Section 2 of
|
|
@@ -447,6 +475,33 @@ export class Finder<T> {
|
|
|
447
475
|
// Mode flags
|
|
448
476
|
private isPromptMode = false;
|
|
449
477
|
private isPanelMode = false;
|
|
478
|
+
/** True when the active prompt is a centred floating overlay. Search
|
|
479
|
+
* status then goes to the overlay's own footer (visible inside the frame)
|
|
480
|
+
* rather than the editor status bar (off at the bottom, easy to miss). */
|
|
481
|
+
private isOverlay = false;
|
|
482
|
+
|
|
483
|
+
/** Present a search-status message where the user is actually looking: on
|
|
484
|
+
* the overlay's input row (right-aligned by the match count) for a
|
|
485
|
+
* floating overlay, else the editor status bar. */
|
|
486
|
+
private setSearchStatus(message: string): void {
|
|
487
|
+
if (this.isOverlay) {
|
|
488
|
+
this.editor.setPromptStatus(message);
|
|
489
|
+
} else {
|
|
490
|
+
this.editor.setStatus(message);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/** Report a successful search with `count` matches. In overlay mode the
|
|
495
|
+
* "N / total" count on the input row already conveys this, so the status
|
|
496
|
+
* is cleared to avoid duplicating it; the status bar (non-overlay) still
|
|
497
|
+
* shows "Found N matches". */
|
|
498
|
+
private reportFound(count: number): void {
|
|
499
|
+
if (this.isOverlay) {
|
|
500
|
+
this.editor.setPromptStatus("");
|
|
501
|
+
} else {
|
|
502
|
+
this.editor.setStatus(`Found ${count} matches`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
450
505
|
|
|
451
506
|
// Handler names (for cleanup)
|
|
452
507
|
private handlerPrefix: string;
|
|
@@ -513,6 +568,7 @@ export class Finder<T> {
|
|
|
513
568
|
|
|
514
569
|
// Start the prompt
|
|
515
570
|
const overlay = options.floatingOverlay === true;
|
|
571
|
+
this.isOverlay = overlay;
|
|
516
572
|
if (options.initialQuery) {
|
|
517
573
|
this.editor.startPromptWithInitial(
|
|
518
574
|
options.title,
|
|
@@ -525,7 +581,7 @@ export class Finder<T> {
|
|
|
525
581
|
const result = this.editor.startPrompt(options.title, this.config.id, overlay);
|
|
526
582
|
this.editor.debug(`[Finder] startPrompt returned: ${result}`);
|
|
527
583
|
}
|
|
528
|
-
this.
|
|
584
|
+
this.setSearchStatus("Type to search…");
|
|
529
585
|
}
|
|
530
586
|
|
|
531
587
|
/**
|
|
@@ -545,6 +601,11 @@ export class Finder<T> {
|
|
|
545
601
|
// unchanged query.
|
|
546
602
|
this.promptState.lastQuery = "";
|
|
547
603
|
if (query.length === 0) return;
|
|
604
|
+
// The backend (or scope set) changed, so the on-screen results are now
|
|
605
|
+
// stale. Clear them and show progress immediately rather than leaving the
|
|
606
|
+
// previous output up while the (possibly slow) new search runs.
|
|
607
|
+
this.updatePromptResults([]);
|
|
608
|
+
this.setSearchStatus("Searching…");
|
|
548
609
|
await this.runSearch(query, this.currentSource);
|
|
549
610
|
}
|
|
550
611
|
|
|
@@ -713,9 +774,9 @@ export class Finder<T> {
|
|
|
713
774
|
this.updatePromptResults(filtered);
|
|
714
775
|
|
|
715
776
|
if (filtered.length > 0) {
|
|
716
|
-
this.
|
|
777
|
+
this.reportFound(filtered.length);
|
|
717
778
|
} else {
|
|
718
|
-
this.
|
|
779
|
+
this.setSearchStatus("No matches");
|
|
719
780
|
}
|
|
720
781
|
} else {
|
|
721
782
|
// Search mode: run external search
|
|
@@ -745,6 +806,7 @@ export class Finder<T> {
|
|
|
745
806
|
}
|
|
746
807
|
this.editor.setPromptSuggestions([]);
|
|
747
808
|
this.promptState.results = [];
|
|
809
|
+
this.setSearchStatus("");
|
|
748
810
|
return;
|
|
749
811
|
}
|
|
750
812
|
|
|
@@ -768,6 +830,11 @@ export class Finder<T> {
|
|
|
768
830
|
}
|
|
769
831
|
this.promptState.lastQuery = query;
|
|
770
832
|
|
|
833
|
+
// A search is now actually starting (every query change that gets here —
|
|
834
|
+
// typing, deleting, provider/scope refresh). Show pending status so the
|
|
835
|
+
// user sees the re-scan in progress rather than a stale result count.
|
|
836
|
+
this.setSearchStatus("Searching…");
|
|
837
|
+
|
|
771
838
|
try {
|
|
772
839
|
const searchResult = source.search(query);
|
|
773
840
|
|
|
@@ -793,21 +860,21 @@ export class Finder<T> {
|
|
|
793
860
|
this.updatePromptResults(parsed);
|
|
794
861
|
|
|
795
862
|
if (parsed.length > 0) {
|
|
796
|
-
this.
|
|
863
|
+
this.reportFound(parsed.length);
|
|
797
864
|
// Show preview of first result
|
|
798
865
|
if (this.shouldShowPreview()) {
|
|
799
866
|
await this.updatePreview(this.promptState.entries[0]);
|
|
800
867
|
}
|
|
801
868
|
} else {
|
|
802
|
-
this.
|
|
869
|
+
this.setSearchStatus("No matches");
|
|
803
870
|
}
|
|
804
871
|
} else if (result.exit_code === 1) {
|
|
805
872
|
// No matches
|
|
806
873
|
this.updatePromptResults([]);
|
|
807
|
-
this.
|
|
874
|
+
this.setSearchStatus("No matches");
|
|
808
875
|
} else if (result.exit_code !== -1) {
|
|
809
876
|
// Error (ignore -1 which means killed)
|
|
810
|
-
this.
|
|
877
|
+
this.setSearchStatus(`Search error: ${result.stderr}`);
|
|
811
878
|
}
|
|
812
879
|
} else {
|
|
813
880
|
// Promise<T[]>
|
|
@@ -821,12 +888,12 @@ export class Finder<T> {
|
|
|
821
888
|
this.updatePromptResults(results);
|
|
822
889
|
|
|
823
890
|
if (results.length > 0) {
|
|
824
|
-
this.
|
|
891
|
+
this.reportFound(results.length);
|
|
825
892
|
if (this.shouldShowPreview()) {
|
|
826
893
|
await this.updatePreview(this.promptState.entries[0]);
|
|
827
894
|
}
|
|
828
895
|
} else {
|
|
829
|
-
this.
|
|
896
|
+
this.setSearchStatus("No matches");
|
|
830
897
|
}
|
|
831
898
|
}
|
|
832
899
|
} catch (e) {
|
|
@@ -864,7 +931,15 @@ export class Finder<T> {
|
|
|
864
931
|
(entry, i) => ({
|
|
865
932
|
text: entry.label,
|
|
866
933
|
description: entry.description,
|
|
867
|
-
value
|
|
934
|
+
// The preview pane uses `value` as the authoritative
|
|
935
|
+
// `path:line:col` for the result. We must not rely on parsing the
|
|
936
|
+
// user-facing label, which may carry source badges (e.g. "[term]")
|
|
937
|
+
// that make the label unparseable as a path.
|
|
938
|
+
value: entry.location
|
|
939
|
+
? `${entry.location.file}:${entry.location.line}:${
|
|
940
|
+
entry.location.column ?? 1
|
|
941
|
+
}`
|
|
942
|
+
: `${i}`,
|
|
868
943
|
disabled: false,
|
|
869
944
|
})
|
|
870
945
|
);
|
|
@@ -1064,12 +1139,17 @@ export class Finder<T> {
|
|
|
1064
1139
|
private registerPanelHandlers(): void {
|
|
1065
1140
|
const self = this;
|
|
1066
1141
|
|
|
1067
|
-
// Define panel mode
|
|
1142
|
+
// Define panel mode. The built-in Return/Escape bindings are
|
|
1143
|
+
// augmented with any caller-supplied `panelKeys` so plugin-specific
|
|
1144
|
+
// shortcuts (e.g. Diagnostics' "q: close | a: toggle filter") resolve
|
|
1145
|
+
// to their handlers instead of falling through to the read-only text
|
|
1146
|
+
// layer (issue #2125).
|
|
1068
1147
|
this.editor.defineMode(
|
|
1069
1148
|
this.modeName,
|
|
1070
1149
|
[
|
|
1071
1150
|
["Return", `${this.handlerPrefix}_panel_select`],
|
|
1072
1151
|
["Escape", `${this.handlerPrefix}_panel_close`],
|
|
1152
|
+
...(this.config.panelKeys ?? []),
|
|
1073
1153
|
],
|
|
1074
1154
|
true
|
|
1075
1155
|
);
|
|
@@ -1351,13 +1431,17 @@ export class Finder<T> {
|
|
|
1351
1431
|
} else if (entry.location) {
|
|
1352
1432
|
const loc = entry.location;
|
|
1353
1433
|
|
|
1354
|
-
// Close the panel first
|
|
1355
|
-
//
|
|
1356
|
-
//
|
|
1357
|
-
//
|
|
1358
|
-
|
|
1434
|
+
// Close the panel first (unless the consumer opted to keep it
|
|
1435
|
+
// docked via `closeOnSelect: false`). Closing is the default
|
|
1436
|
+
// because navigateOnCursorMove's focusSplit(panelSplitId) can
|
|
1437
|
+
// interfere with the jump — it queues a FocusSplit that runs after
|
|
1438
|
+
// OpenFileInSplit and restores the panel as the active split.
|
|
1439
|
+
if (this.config.closeOnSelect !== false) {
|
|
1440
|
+
this.closePanel();
|
|
1441
|
+
}
|
|
1359
1442
|
|
|
1360
|
-
//
|
|
1443
|
+
// openFile routes away from the dock leaf, so with the panel kept
|
|
1444
|
+
// open the file lands in the editor pane and the list stays put.
|
|
1361
1445
|
this.editor.openFile(loc.file, loc.line, loc.column);
|
|
1362
1446
|
this.editor.setStatus(`Jumped to ${loc.file}:${loc.line}`);
|
|
1363
1447
|
}
|
package/plugins/lib/fresh.d.ts
CHANGED
|
@@ -735,6 +735,13 @@ type CursorInfo = {
|
|
|
735
735
|
start: number;
|
|
736
736
|
end: number;
|
|
737
737
|
} | null;
|
|
738
|
+
/**
|
|
739
|
+
* 0-indexed line number of the cursor. `null` 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;
|
|
738
745
|
};
|
|
739
746
|
type OverlayOptions = {
|
|
740
747
|
/**
|
|
@@ -896,6 +903,15 @@ type WidgetSpec = {
|
|
|
896
903
|
"kind": "row";
|
|
897
904
|
children: Array<WidgetSpec>;
|
|
898
905
|
key?: string | null;
|
|
906
|
+
/**
|
|
907
|
+
* When true, children that don't fit on one line reflow onto
|
|
908
|
+
* additional lines (growing the row's height) instead of being
|
|
909
|
+
* truncated. Children are never split — wrap happens at child
|
|
910
|
+
* boundaries — so wrap a logical group (e.g. a toggle + its
|
|
911
|
+
* accelerator) in a nested non-wrapping `Row` to keep it intact.
|
|
912
|
+
* Ignored when the row contains multi-line (block) children.
|
|
913
|
+
*/
|
|
914
|
+
wrap: boolean;
|
|
899
915
|
} | {
|
|
900
916
|
"kind": "col";
|
|
901
917
|
children: Array<WidgetSpec>;
|
|
@@ -1206,6 +1222,16 @@ interface SearchHandle {
|
|
|
1206
1222
|
take(): SearchTakeResult;
|
|
1207
1223
|
cancel(): void;
|
|
1208
1224
|
}
|
|
1225
|
+
type ReplaceResult = {
|
|
1226
|
+
/**
|
|
1227
|
+
* Number of replacements made
|
|
1228
|
+
*/
|
|
1229
|
+
replacements: number;
|
|
1230
|
+
/**
|
|
1231
|
+
* Buffer ID of the edited buffer
|
|
1232
|
+
*/
|
|
1233
|
+
bufferId: number;
|
|
1234
|
+
};
|
|
1209
1235
|
type AuthorityFilesystem = {
|
|
1210
1236
|
kind: "local";
|
|
1211
1237
|
};
|
|
@@ -1489,16 +1515,6 @@ type RemoteIndicatorStatePayload = {
|
|
|
1489
1515
|
kind: "disconnected";
|
|
1490
1516
|
label?: string | null;
|
|
1491
1517
|
};
|
|
1492
|
-
type ReplaceResult = {
|
|
1493
|
-
/**
|
|
1494
|
-
* Number of replacements made
|
|
1495
|
-
*/
|
|
1496
|
-
replacements: number;
|
|
1497
|
-
/**
|
|
1498
|
-
* Buffer ID of the edited buffer
|
|
1499
|
-
*/
|
|
1500
|
-
bufferId: number;
|
|
1501
|
-
};
|
|
1502
1518
|
type SpawnResult = {
|
|
1503
1519
|
/**
|
|
1504
1520
|
* Complete stdout as string
|
|
@@ -1622,6 +1638,13 @@ interface EditorAPI {
|
|
|
1622
1638
|
*/
|
|
1623
1639
|
executeAction(actionName: string): boolean;
|
|
1624
1640
|
/**
|
|
1641
|
+
* Cancel the active prompt / overlay — the same teardown the
|
|
1642
|
+
* Escape key triggers. Lets a plugin dismiss a prompt it opened
|
|
1643
|
+
* (e.g. exporting Live Grep results to a dock panel) without
|
|
1644
|
+
* routing a synthetic keypress.
|
|
1645
|
+
*/
|
|
1646
|
+
cancelPrompt(): boolean;
|
|
1647
|
+
/**
|
|
1625
1648
|
* Register a custom statusbar token.
|
|
1626
1649
|
* Token will be named "plugin_name:token_name" where plugin_name is the current plugin.
|
|
1627
1650
|
* Returns true if registration succeeded, false if invalid or already registered.
|
|
@@ -1695,7 +1718,13 @@ interface EditorAPI {
|
|
|
1695
1718
|
*/
|
|
1696
1719
|
listSplits(): SplitSnapshot[];
|
|
1697
1720
|
/**
|
|
1698
|
-
* Get the line number (0-indexed) of the primary cursor
|
|
1721
|
+
* Get the line number (0-indexed) of the primary cursor.
|
|
1722
|
+
*
|
|
1723
|
+
* @deprecated Use `getPrimaryCursor()?.line` instead. This accessor cannot
|
|
1724
|
+
* represent "line index unavailable" (huge files before their line scan) —
|
|
1725
|
+
* it returns `0` in that case, indistinguishable from a real first line.
|
|
1726
|
+
* `getPrimaryCursor().line` is `number | null` and also covers every cursor
|
|
1727
|
+
* via `getAllCursors()`.
|
|
1699
1728
|
*/
|
|
1700
1729
|
getCursorLine(): number;
|
|
1701
1730
|
/**
|
|
@@ -1864,38 +1893,17 @@ interface EditorAPI {
|
|
|
1864
1893
|
*/
|
|
1865
1894
|
getAuthorityLabel(): string;
|
|
1866
1895
|
/**
|
|
1867
|
-
* Current Workspace Trust level for the active project:
|
|
1868
|
-
* `"
|
|
1869
|
-
*
|
|
1870
|
-
*
|
|
1871
|
-
* Trust is a per-project, user-granted decision. Plugins that run
|
|
1872
|
-
* repo-controlled work (env activation, project tooling, repo-local
|
|
1873
|
-
* binaries) MUST gate on this and treat anything other than
|
|
1874
|
-
* `"trusted"` as "do not execute".
|
|
1875
|
-
*/
|
|
1876
|
-
workspaceTrustLevel(): "restricted" | "trusted" | "blocked" | "";
|
|
1877
|
-
/**
|
|
1878
|
-
* Activate an environment by setting the live env recipe: an activation
|
|
1879
|
-
* shell `snippet` (e.g. `eval "$(direnv export bash)"`,
|
|
1880
|
-
* `source .venv/bin/activate`, or `""` for a pure login shell) run in
|
|
1881
|
-
* `dir` (defaults to the workspace). It is re-evaluated on demand on the
|
|
1882
|
-
* active backend and applied to every spawn — language servers,
|
|
1883
|
-
* formatters, `spawnProcess` — so they see the project environment. No
|
|
1884
|
-
* authority rebuild; the LSP is restarted to pick it up.
|
|
1885
|
-
*
|
|
1886
|
-
* Honored only when `workspaceTrustLevel() === "trusted"` (it runs
|
|
1887
|
-
* repo-controlled code). Call `clearEnv()` to deactivate.
|
|
1888
|
-
*/
|
|
1889
|
-
setEnv(snippet: string, dir?: string): void;
|
|
1890
|
-
/**
|
|
1891
|
-
* Deactivate the environment set by `setEnv` — spawns return to the
|
|
1892
|
-
* inherited environment.
|
|
1896
|
+
* Current Workspace Trust level for the active project: `"restricted"`,
|
|
1897
|
+
* `"trusted"`, or `"blocked"` (empty when unavailable). Exposed to JS as
|
|
1898
|
+
* `editor.workspaceTrustLevel()`. Plugins that run repo-controlled work
|
|
1899
|
+
* should treat anything other than `"trusted"` as "do not execute".
|
|
1893
1900
|
*/
|
|
1894
|
-
|
|
1901
|
+
workspaceTrustLevel(): string;
|
|
1895
1902
|
/**
|
|
1896
|
-
* Whether an environment is currently active (
|
|
1897
|
-
*
|
|
1898
|
-
* re-establish its file watch
|
|
1903
|
+
* Whether an environment is currently active (set via `editor.setEnv`).
|
|
1904
|
+
* Exposed to JS as `editor.envActive()`. Lets the env-manager plugin
|
|
1905
|
+
* reflect activation and re-establish its file watch after the restart
|
|
1906
|
+
* that `setEnv` triggers.
|
|
1899
1907
|
*/
|
|
1900
1908
|
envActive(): boolean;
|
|
1901
1909
|
/**
|
|
@@ -2134,6 +2142,21 @@ interface EditorAPI {
|
|
|
2134
2142
|
*/
|
|
2135
2143
|
getDataDir(): string;
|
|
2136
2144
|
/**
|
|
2145
|
+
* Directory holding terminal scrollback backing files for the current
|
|
2146
|
+
* working directory. Each project root / worktree has its own subdir, so
|
|
2147
|
+
* Universal Search's terminal scope can stay scoped to the active
|
|
2148
|
+
* project rather than spanning every project's terminals.
|
|
2149
|
+
*/
|
|
2150
|
+
getTerminalDir(): string;
|
|
2151
|
+
/**
|
|
2152
|
+
* Per-working-directory data root for plugin state scoped to the current
|
|
2153
|
+
* project root / worktree (`<data_dir>/workdirs/<encoded-cwd>/`). Use
|
|
2154
|
+
* instead of `getDataDir()` for state that should not be shared across
|
|
2155
|
+
* worktrees. The directory is not created here — callers create what
|
|
2156
|
+
* they need under it.
|
|
2157
|
+
*/
|
|
2158
|
+
getWorkingDataDir(): string;
|
|
2159
|
+
/**
|
|
2137
2160
|
* Get themes directory path
|
|
2138
2161
|
*/
|
|
2139
2162
|
getThemesDir(): string;
|
|
@@ -2267,6 +2290,12 @@ interface EditorAPI {
|
|
|
2267
2290
|
*/
|
|
2268
2291
|
clearOverlaysInRange(bufferId: number, start: number, end: number): boolean;
|
|
2269
2292
|
/**
|
|
2293
|
+
* Clear overlays in a single namespace that overlap with a byte range.
|
|
2294
|
+
* Unlike clearOverlaysInRange, overlays in other namespaces (e.g.
|
|
2295
|
+
* editor-owned LSP diagnostics) are left untouched.
|
|
2296
|
+
*/
|
|
2297
|
+
clearOverlaysInRangeForNamespace(bufferId: number, namespace: string, start: number, end: number): boolean;
|
|
2298
|
+
/**
|
|
2270
2299
|
* Remove an overlay by its handle
|
|
2271
2300
|
*/
|
|
2272
2301
|
removeOverlay(bufferId: number, handle: string): boolean;
|
|
@@ -2472,6 +2501,24 @@ interface EditorAPI {
|
|
|
2472
2501
|
*/
|
|
2473
2502
|
setPromptFooter(footer: StyledText[]): boolean;
|
|
2474
2503
|
/**
|
|
2504
|
+
* Set the floating-overlay prompt's input-row status text (right-aligned,
|
|
2505
|
+
* left of the match count). Empty string clears it.
|
|
2506
|
+
*/
|
|
2507
|
+
setPromptStatus(status: string): boolean;
|
|
2508
|
+
/**
|
|
2509
|
+
* Set the floating-overlay prompt's toolbar as a `WidgetSpec` (real,
|
|
2510
|
+
* clickable `Toggle`/`Button` widgets rendered in the header band, in
|
|
2511
|
+
* place of the styled-text title). Pass `null`/`undefined` to clear it.
|
|
2512
|
+
*/
|
|
2513
|
+
setPromptToolbar(specObj: unknown): boolean;
|
|
2514
|
+
/**
|
|
2515
|
+
* Toggle a floating-overlay toolbar control by its widget `key`. The host
|
|
2516
|
+
* owns the toggle's checked state, flips it, and emits a `widget_event`
|
|
2517
|
+
* the plugin can listen for. Lets a plugin route its own Alt+… shortcut
|
|
2518
|
+
* through the same host path as a click / Space on the toggle.
|
|
2519
|
+
*/
|
|
2520
|
+
toggleOverlayToolbarWidget(key: string): boolean;
|
|
2521
|
+
/**
|
|
2475
2522
|
* Override the currently-highlighted suggestion row in the
|
|
2476
2523
|
* open prompt. The editor clamps `index` to the suggestion
|
|
2477
2524
|
* list's bounds and the renderer scrolls it into view on
|
|
@@ -2860,6 +2907,16 @@ interface EditorAPI {
|
|
|
2860
2907
|
*/
|
|
2861
2908
|
clearAuthority(): void;
|
|
2862
2909
|
/**
|
|
2910
|
+
* Activate an environment: set the live env recipe (`snippet` run in
|
|
2911
|
+
* `dir`). Applied to every spawn, re-evaluated on demand — no restart.
|
|
2912
|
+
* Honored only when the workspace is Trusted.
|
|
2913
|
+
*/
|
|
2914
|
+
setEnv(snippet: string, dir: string | null): void;
|
|
2915
|
+
/**
|
|
2916
|
+
* Deactivate the environment — spawns return to the inherited env.
|
|
2917
|
+
*/
|
|
2918
|
+
clearEnv(): void;
|
|
2919
|
+
/**
|
|
2863
2920
|
* Override the Remote Indicator's displayed state. Plugins call
|
|
2864
2921
|
* this to surface lifecycle transitions that the authority layer
|
|
2865
2922
|
* doesn't know about yet — "Connecting" while `devcontainer up`
|
|
@@ -2926,13 +2983,14 @@ interface EditorAPI {
|
|
|
2926
2983
|
caseSensitive?: boolean;
|
|
2927
2984
|
maxResults?: number;
|
|
2928
2985
|
wholeWords?: boolean;
|
|
2986
|
+
sourceBufferId?: number;
|
|
2929
2987
|
}): SearchHandle;
|
|
2930
2988
|
/**
|
|
2931
2989
|
* Replace matches in a file's buffer (async)
|
|
2932
2990
|
* Opens the file if not already in a buffer, applies edits via the buffer model,
|
|
2933
2991
|
* and saves. All edits are grouped as a single undo action.
|
|
2934
2992
|
*/
|
|
2935
|
-
replaceInFile(filePath: string, matches: number[][], replacement: string): Promise<ReplaceResult>;
|
|
2993
|
+
replaceInFile(filePath: string, matches: number[][], replacement: string, bufferId?: number): Promise<ReplaceResult>;
|
|
2936
2994
|
/**
|
|
2937
2995
|
* Send LSP request (async, returns request_id)
|
|
2938
2996
|
*/
|
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
|
@@ -978,7 +978,18 @@ async function recompute(bufferId: number): Promise<void> {
|
|
|
978
978
|
}
|
|
979
979
|
|
|
980
980
|
const length = editor.getBufferLength(bufferId);
|
|
981
|
-
|
|
981
|
+
let newText: string;
|
|
982
|
+
try {
|
|
983
|
+
newText = await editor.getBufferText(bufferId, 0, length);
|
|
984
|
+
} catch (e) {
|
|
985
|
+
// The buffer can close between the awaits above (the git `show`
|
|
986
|
+
// for the reference is slow) and this fetch — e.g. the user
|
|
987
|
+
// closes the file, or an external process churns it while it's
|
|
988
|
+
// open. `buffer_closed` deletes our state, so if it's gone the
|
|
989
|
+
// recompute is moot: bail quietly instead of logging an error.
|
|
990
|
+
if (!states.has(bufferId)) return;
|
|
991
|
+
throw e;
|
|
992
|
+
}
|
|
982
993
|
|
|
983
994
|
// Skip 1: same buffer text as last recompute. `lines_changed` fires
|
|
984
995
|
// on viewport scrolls (cursor up/down past the visible area), and
|