@fresh-editor/fresh-editor 0.2.25 → 0.3.0
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 +152 -0
- package/README.md +6 -0
- package/package.json +1 -1
- package/plugins/audit_mode.i18n.json +14 -14
- package/plugins/audit_mode.ts +76 -33
- package/plugins/config-schema.json +69 -6
- package/plugins/dashboard.ts +1785 -0
- package/plugins/devcontainer.i18n.json +1416 -0
- package/plugins/devcontainer.ts +2066 -0
- package/plugins/git_log.i18n.json +14 -42
- package/plugins/git_log.ts +11 -1
- package/plugins/lib/fresh.d.ts +225 -0
- package/plugins/markdown_compose.ts +54 -6
- package/plugins/schemas/theme.schema.json +108 -0
- package/plugins/theme_editor.i18n.json +28 -0
- package/plugins/theme_editor.ts +4 -1
- package/themes/high-contrast.json +2 -2
- package/themes/nord.json +4 -0
- package/themes/solarized-dark.json +4 -0
|
@@ -32,9 +32,7 @@
|
|
|
32
32
|
"status.move_to_diff": "Move cursor to a diff line",
|
|
33
33
|
|
|
34
34
|
"panel.commits_header": "Commits:",
|
|
35
|
-
"panel.no_commits": " No commits found"
|
|
36
|
-
"panel.log_footer": "%{count} commits | Up/Down/j/k: navigate | RET: show | y: yank hash | r: refresh | q: quit",
|
|
37
|
-
"panel.detail_footer": "Up/Down/j/k: navigate | RET: open file at line | q: back to log"
|
|
35
|
+
"panel.no_commits": " No commits found"
|
|
38
36
|
},
|
|
39
37
|
"cs": {
|
|
40
38
|
"cmd.git_log": "Git Log",
|
|
@@ -69,9 +67,7 @@
|
|
|
69
67
|
"status.move_to_diff": "Presunte kurzor na radek diffu",
|
|
70
68
|
|
|
71
69
|
"panel.commits_header": "Commity:",
|
|
72
|
-
"panel.no_commits": " Zadne commity nenalezeny"
|
|
73
|
-
"panel.log_footer": "%{count} commitu | Nahoru/Dolu/j/k: navigace | RET: zobrazit | y: kopirovat hash | r: obnovit | q: ukoncit",
|
|
74
|
-
"panel.detail_footer": "Nahoru/Dolu/j/k: navigace | RET: otevrit soubor na radku | q: zpet do logu"
|
|
70
|
+
"panel.no_commits": " Zadne commity nenalezeny"
|
|
75
71
|
},
|
|
76
72
|
"de": {
|
|
77
73
|
"cmd.git_log": "Git-Protokoll",
|
|
@@ -106,9 +102,7 @@
|
|
|
106
102
|
"status.move_to_diff": "Cursor auf eine Diff-Zeile bewegen",
|
|
107
103
|
|
|
108
104
|
"panel.commits_header": "Commits:",
|
|
109
|
-
"panel.no_commits": " Keine Commits gefunden"
|
|
110
|
-
"panel.log_footer": "%{count} Commits | Auf/Ab/j/k: navigieren | RET: anzeigen | y: Hash kopieren | r: aktualisieren | q: beenden",
|
|
111
|
-
"panel.detail_footer": "Auf/Ab/j/k: navigieren | RET: Datei bei Zeile oeffnen | q: zurueck zum Protokoll"
|
|
105
|
+
"panel.no_commits": " Keine Commits gefunden"
|
|
112
106
|
},
|
|
113
107
|
"es": {
|
|
114
108
|
"cmd.git_log": "Registro Git",
|
|
@@ -143,9 +137,7 @@
|
|
|
143
137
|
"status.move_to_diff": "Mueve el cursor a una linea de diff",
|
|
144
138
|
|
|
145
139
|
"panel.commits_header": "Commits:",
|
|
146
|
-
"panel.no_commits": " No se encontraron commits"
|
|
147
|
-
"panel.log_footer": "%{count} commits | Arriba/Abajo/j/k: navegar | RET: mostrar | y: copiar hash | r: actualizar | q: salir",
|
|
148
|
-
"panel.detail_footer": "Arriba/Abajo/j/k: navegar | RET: abrir archivo en linea | q: volver al registro"
|
|
140
|
+
"panel.no_commits": " No se encontraron commits"
|
|
149
141
|
},
|
|
150
142
|
"fr": {
|
|
151
143
|
"cmd.git_log": "Journal Git",
|
|
@@ -180,9 +172,7 @@
|
|
|
180
172
|
"status.move_to_diff": "Deplacez le curseur sur une ligne de diff",
|
|
181
173
|
|
|
182
174
|
"panel.commits_header": "Commits:",
|
|
183
|
-
"panel.no_commits": " Aucun commit trouve"
|
|
184
|
-
"panel.log_footer": "%{count} commits | Haut/Bas/j/k: naviguer | RET: afficher | y: copier hash | r: actualiser | q: quitter",
|
|
185
|
-
"panel.detail_footer": "Haut/Bas/j/k: naviguer | RET: ouvrir fichier a la ligne | q: retour au journal"
|
|
175
|
+
"panel.no_commits": " Aucun commit trouve"
|
|
186
176
|
},
|
|
187
177
|
"it": {
|
|
188
178
|
"cmd.git_log": "Git Log",
|
|
@@ -215,9 +205,7 @@
|
|
|
215
205
|
"status.move_to_diff_with_context": "Sposta il cursore su una riga di diff con contesto file",
|
|
216
206
|
"status.move_to_diff": "Sposta il cursore su una riga di diff",
|
|
217
207
|
"panel.commits_header": "Commit:",
|
|
218
|
-
"panel.no_commits": " Nessun commit trovato"
|
|
219
|
-
"panel.log_footer": "%{count} commit | Su/Giù/j/k: naviga | RET: mostra | y: copia hash | r: aggiorna | q: esci",
|
|
220
|
-
"panel.detail_footer": "Su/Giù/j/k: naviga | RET: apri file alla riga | q: torna al log"
|
|
208
|
+
"panel.no_commits": " Nessun commit trovato"
|
|
221
209
|
},
|
|
222
210
|
"ja": {
|
|
223
211
|
"cmd.git_log": "Gitログ",
|
|
@@ -252,9 +240,7 @@
|
|
|
252
240
|
"status.move_to_diff": "カーソルを差分行に移動してください",
|
|
253
241
|
|
|
254
242
|
"panel.commits_header": "コミット:",
|
|
255
|
-
"panel.no_commits": " コミットが見つかりません"
|
|
256
|
-
"panel.log_footer": "%{count}件のコミット | 上/下/j/k: 移動 | RET: 表示 | y: ハッシュをコピー | r: 更新 | q: 終了",
|
|
257
|
-
"panel.detail_footer": "上/下/j/k: 移動 | RET: ファイルを行で開く | q: ログに戻る"
|
|
243
|
+
"panel.no_commits": " コミットが見つかりません"
|
|
258
244
|
},
|
|
259
245
|
"ko": {
|
|
260
246
|
"cmd.git_log": "Git 로그",
|
|
@@ -289,9 +275,7 @@
|
|
|
289
275
|
"status.move_to_diff": "커서를 diff 줄로 이동하세요",
|
|
290
276
|
|
|
291
277
|
"panel.commits_header": "커밋:",
|
|
292
|
-
"panel.no_commits": " 커밋을 찾을 수 없습니다"
|
|
293
|
-
"panel.log_footer": "%{count}개 커밋 | 위/아래/j/k: 탐색 | RET: 표시 | y: 해시 복사 | r: 새로고침 | q: 종료",
|
|
294
|
-
"panel.detail_footer": "위/아래/j/k: 탐색 | RET: 해당 줄에서 파일 열기 | q: 로그로 돌아가기"
|
|
278
|
+
"panel.no_commits": " 커밋을 찾을 수 없습니다"
|
|
295
279
|
},
|
|
296
280
|
"pt-BR": {
|
|
297
281
|
"cmd.git_log": "Git Log",
|
|
@@ -326,9 +310,7 @@
|
|
|
326
310
|
"status.move_to_diff": "Mova o cursor para uma linha de diff",
|
|
327
311
|
|
|
328
312
|
"panel.commits_header": "Commits:",
|
|
329
|
-
"panel.no_commits": " Nenhum commit encontrado"
|
|
330
|
-
"panel.log_footer": "%{count} commits | Cima/Baixo/j/k: navegar | RET: mostrar | y: copiar hash | r: atualizar | q: sair",
|
|
331
|
-
"panel.detail_footer": "Cima/Baixo/j/k: navegar | RET: abrir arquivo na linha | q: voltar ao log"
|
|
313
|
+
"panel.no_commits": " Nenhum commit encontrado"
|
|
332
314
|
},
|
|
333
315
|
"ru": {
|
|
334
316
|
"cmd.git_log": "Git Log",
|
|
@@ -363,9 +345,7 @@
|
|
|
363
345
|
"status.move_to_diff": "Peremesstite kursor na stroku diff",
|
|
364
346
|
|
|
365
347
|
"panel.commits_header": "Kommity:",
|
|
366
|
-
"panel.no_commits": " Kommity ne naydeny"
|
|
367
|
-
"panel.log_footer": "%{count} kommitov | Vverkh/Vniz/j/k: navigatsiya | RET: pokazat' | y: kopirovat' khesh | r: obnovit' | q: vyyti",
|
|
368
|
-
"panel.detail_footer": "Vverkh/Vniz/j/k: navigatsiya | RET: otkryt' fayl na stroke | q: nazad k logu"
|
|
348
|
+
"panel.no_commits": " Kommity ne naydeny"
|
|
369
349
|
},
|
|
370
350
|
"th": {
|
|
371
351
|
"cmd.git_log": "Git Log",
|
|
@@ -400,9 +380,7 @@
|
|
|
400
380
|
"status.move_to_diff": "เลื่อนเคอร์เซอร์ไปที่บรรทัด diff",
|
|
401
381
|
|
|
402
382
|
"panel.commits_header": "คอมมิต:",
|
|
403
|
-
"panel.no_commits": " ไม่พบคอมมิต"
|
|
404
|
-
"panel.log_footer": "%{count} คอมมิต | ขึ้น/ลง/j/k: นำทาง | RET: แสดง | y: คัดลอกแฮช | r: รีเฟรช | q: ออก",
|
|
405
|
-
"panel.detail_footer": "ขึ้น/ลง/j/k: นำทาง | RET: เปิดไฟล์ที่บรรทัด | q: กลับไปที่ log"
|
|
383
|
+
"panel.no_commits": " ไม่พบคอมมิต"
|
|
406
384
|
},
|
|
407
385
|
"uk": {
|
|
408
386
|
"cmd.git_log": "Git Log",
|
|
@@ -437,9 +415,7 @@
|
|
|
437
415
|
"status.move_to_diff": "Peremistit' kursor na ryadok diff",
|
|
438
416
|
|
|
439
417
|
"panel.commits_header": "Komity:",
|
|
440
|
-
"panel.no_commits": " Komity ne znaydeno"
|
|
441
|
-
"panel.log_footer": "%{count} komitiv | Vhoru/Vnyz/j/k: navihatsiya | RET: pokazaty | y: kopiyuvaty khesh | r: onovyty | q: vyyty",
|
|
442
|
-
"panel.detail_footer": "Vhoru/Vnyz/j/k: navihatsiya | RET: vidkryty fayl na ryadku | q: nazad do lohu"
|
|
418
|
+
"panel.no_commits": " Komity ne znaydeno"
|
|
443
419
|
},
|
|
444
420
|
"vi": {
|
|
445
421
|
"cmd.git_log": "Git Log",
|
|
@@ -474,9 +450,7 @@
|
|
|
474
450
|
"status.move_to_diff": "Di chuyển con trỏ đến dòng diff",
|
|
475
451
|
|
|
476
452
|
"panel.commits_header": "Commit:",
|
|
477
|
-
"panel.no_commits": " Không tìm thấy commit"
|
|
478
|
-
"panel.log_footer": "%{count} commit | Lên/Xuống/j/k: điều hướng | RET: hiển thị | y: sao chép hash | r: làm mới | q: thoát",
|
|
479
|
-
"panel.detail_footer": "Lên/Xuống/j/k: điều hướng | RET: mở tệp tại dòng | q: quay lại log"
|
|
453
|
+
"panel.no_commits": " Không tìm thấy commit"
|
|
480
454
|
},
|
|
481
455
|
"zh-CN": {
|
|
482
456
|
"cmd.git_log": "Git日志",
|
|
@@ -511,8 +485,6 @@
|
|
|
511
485
|
"status.move_to_diff": "请将光标移动到差异行",
|
|
512
486
|
|
|
513
487
|
"panel.commits_header": "提交:",
|
|
514
|
-
"panel.no_commits": " 未找到提交"
|
|
515
|
-
"panel.log_footer": "%{count}个提交 | 上/下/j/k: 导航 | RET: 显示 | y: 复制哈希 | r: 刷新 | q: 退出",
|
|
516
|
-
"panel.detail_footer": "上/下/j/k: 导航 | RET: 在行处打开文件 | q: 返回日志"
|
|
488
|
+
"panel.no_commits": " 未找到提交"
|
|
517
489
|
}
|
|
518
490
|
}
|
package/plugins/git_log.ts
CHANGED
|
@@ -686,13 +686,23 @@ async function git_log_detail_open_file(): Promise<void> {
|
|
|
686
686
|
registerHandler("git_log_detail_open_file", git_log_detail_open_file);
|
|
687
687
|
|
|
688
688
|
// File-view mode so `q` closes the tab and returns to the group.
|
|
689
|
+
//
|
|
690
|
+
// j/k alias Up/Down as in the main git-log mode, and we inherit Normal
|
|
691
|
+
// bindings so arrows, PageUp/Down, Home/End, Ctrl+C copy, etc. still work
|
|
692
|
+
// in this read-only buffer — without `inheritNormalBindings`, unbound keys
|
|
693
|
+
// in a read-only mode fall through to the edit actions and trip the
|
|
694
|
+
// `editing_disabled` status message (see #566).
|
|
689
695
|
editor.defineMode(
|
|
690
696
|
"git-log-file-view",
|
|
691
697
|
[
|
|
698
|
+
["k", "move_up"],
|
|
699
|
+
["j", "move_down"],
|
|
692
700
|
["q", "git_log_file_view_close"],
|
|
693
701
|
["Escape", "git_log_file_view_close"],
|
|
694
702
|
],
|
|
695
|
-
true
|
|
703
|
+
true, // read-only
|
|
704
|
+
false, // allow_text_input
|
|
705
|
+
true, // inherit Normal-context bindings for unbound keys
|
|
696
706
|
);
|
|
697
707
|
|
|
698
708
|
function git_log_file_view_close(): void {
|
package/plugins/lib/fresh.d.ts
CHANGED
|
@@ -70,6 +70,35 @@ interface MouseClickHookArgs {
|
|
|
70
70
|
/** 0-indexed byte column inside the buffer row. */
|
|
71
71
|
buffer_col: number | null;
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Registry of typed plugin APIs surfaced through
|
|
75
|
+
* `editor.exportPluginApi` / `editor.getPluginApi`.
|
|
76
|
+
*
|
|
77
|
+
* Plugins that want their surface to be typed for downstream
|
|
78
|
+
* consumers augment this interface in their own source:
|
|
79
|
+
*
|
|
80
|
+
* ```ts
|
|
81
|
+
* // in my_plugin.ts
|
|
82
|
+
* export type MyPluginApi = { doThing(): void };
|
|
83
|
+
* declare global {
|
|
84
|
+
* interface FreshPluginRegistry {
|
|
85
|
+
* "my-plugin": MyPluginApi;
|
|
86
|
+
* }
|
|
87
|
+
* }
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* `editor.getPluginApi("my-plugin")` then returns
|
|
91
|
+
* `MyPluginApi | null` without any `as`-cast on the consumer side.
|
|
92
|
+
* Plugins that skip the augmentation still work — the untyped
|
|
93
|
+
* `getPluginApi<T = unknown>(name: string): T | null` overload
|
|
94
|
+
* takes over.
|
|
95
|
+
*
|
|
96
|
+
* Each plugin's augmentation is emitted to
|
|
97
|
+
* `<config_dir>/types/plugins.d.ts` at load time (via oxc's
|
|
98
|
+
* isolated-declarations), so init.ts sees every loaded plugin's
|
|
99
|
+
* registry entry automatically.
|
|
100
|
+
*/
|
|
101
|
+
interface FreshPluginRegistry {}
|
|
73
102
|
type TextPropertyEntry = {
|
|
74
103
|
/**
|
|
75
104
|
* Text content for this entry
|
|
@@ -343,6 +372,15 @@ type BufferInfo = {
|
|
|
343
372
|
* refreshing itself for a preview tab.
|
|
344
373
|
*/
|
|
345
374
|
is_preview: boolean;
|
|
375
|
+
/**
|
|
376
|
+
* Split ids that currently hold this buffer (empty when the buffer is
|
|
377
|
+
* open but not visible in any split — e.g. background-opened tabs
|
|
378
|
+
* that haven't been focused). Lets plugins implement "focus existing
|
|
379
|
+
* buffer if visible, else open new" without having to track split
|
|
380
|
+
* ids across editor restarts (which reassign them). The list is a
|
|
381
|
+
* snapshot at the last `update_plugin_state_snapshot` tick.
|
|
382
|
+
*/
|
|
383
|
+
splits: number[];
|
|
346
384
|
};
|
|
347
385
|
type JsDiagnostic = {
|
|
348
386
|
/**
|
|
@@ -497,6 +535,15 @@ type CreateTerminalOptions = {
|
|
|
497
535
|
* Whether to focus the new terminal split (default: true)
|
|
498
536
|
*/
|
|
499
537
|
focus?: boolean;
|
|
538
|
+
/**
|
|
539
|
+
* Whether this terminal is part of the user's persisted workspace.
|
|
540
|
+
* Defaults to `false` for plugin-created terminals — they are typically
|
|
541
|
+
* one-off tool UIs (rebuilds, exec shells, build output) and should
|
|
542
|
+
* start with empty scrollback on each invocation. Set to `true` only
|
|
543
|
+
* when the plugin owns a terminal that the user should see restored
|
|
544
|
+
* across editor restarts.
|
|
545
|
+
*/
|
|
546
|
+
persistent?: boolean;
|
|
500
547
|
};
|
|
501
548
|
type CursorInfo = {
|
|
502
549
|
/**
|
|
@@ -584,6 +631,31 @@ type GrammarInfoSnapshot = {
|
|
|
584
631
|
*/
|
|
585
632
|
short_name: string | null;
|
|
586
633
|
};
|
|
634
|
+
type AuthorityFilesystem = {
|
|
635
|
+
kind: "local";
|
|
636
|
+
};
|
|
637
|
+
type AuthoritySpawner = {
|
|
638
|
+
kind: "local";
|
|
639
|
+
} | {
|
|
640
|
+
kind: "docker-exec";
|
|
641
|
+
container_id: string;
|
|
642
|
+
user?: string | null;
|
|
643
|
+
workspace?: string | null;
|
|
644
|
+
};
|
|
645
|
+
type AuthorityTerminalWrapper = {
|
|
646
|
+
kind: "host-shell";
|
|
647
|
+
} | {
|
|
648
|
+
kind: "explicit";
|
|
649
|
+
command: string;
|
|
650
|
+
args: string[];
|
|
651
|
+
manages_cwd?: boolean;
|
|
652
|
+
};
|
|
653
|
+
type AuthorityPayload = {
|
|
654
|
+
filesystem: AuthorityFilesystem;
|
|
655
|
+
spawner: AuthoritySpawner;
|
|
656
|
+
terminal_wrapper: AuthorityTerminalWrapper;
|
|
657
|
+
display_label?: string;
|
|
658
|
+
};
|
|
587
659
|
type BackgroundProcessResult = {
|
|
588
660
|
/**
|
|
589
661
|
* Unique process ID for later reference
|
|
@@ -808,6 +880,21 @@ type LspServerPackConfig = {
|
|
|
808
880
|
*/
|
|
809
881
|
processLimits: ProcessLimitsPackConfig | null;
|
|
810
882
|
};
|
|
883
|
+
type RemoteIndicatorStatePayload = {
|
|
884
|
+
kind: "local";
|
|
885
|
+
} | {
|
|
886
|
+
kind: "connecting";
|
|
887
|
+
label?: string | null;
|
|
888
|
+
} | {
|
|
889
|
+
kind: "connected";
|
|
890
|
+
label?: string | null;
|
|
891
|
+
} | {
|
|
892
|
+
kind: "failed_attach";
|
|
893
|
+
error?: string | null;
|
|
894
|
+
} | {
|
|
895
|
+
kind: "disconnected";
|
|
896
|
+
label?: string | null;
|
|
897
|
+
};
|
|
811
898
|
type ReplaceResult = {
|
|
812
899
|
/**
|
|
813
900
|
* Number of replacements made
|
|
@@ -860,6 +947,30 @@ interface EditorAPI {
|
|
|
860
947
|
*/
|
|
861
948
|
apiVersion(): number;
|
|
862
949
|
/**
|
|
950
|
+
* The name of the plugin this `editor` handle belongs to. Used by the
|
|
951
|
+
* M3 plugin-API plane (`exportPluginApi` tags the exporter). Plugin
|
|
952
|
+
* authors generally don't call this directly.
|
|
953
|
+
*/
|
|
954
|
+
pluginName(): string;
|
|
955
|
+
/**
|
|
956
|
+
* Publish a typed API surface under `name`. Another plugin (typically
|
|
957
|
+
* `init.ts`) can reach it later via `getPluginApi(name)`. Calling
|
|
958
|
+
* again with the same `name` replaces the previous registration
|
|
959
|
+
* (idempotent — reload works). Exports are auto-dropped when the
|
|
960
|
+
* calling plugin is unloaded.
|
|
961
|
+
*
|
|
962
|
+
* Returns `true` on success. Rejects with a TypeError if `name` is
|
|
963
|
+
* empty or `api` is not an object (functions and primitives are not
|
|
964
|
+
* valid API surfaces — only objects).
|
|
965
|
+
*/
|
|
966
|
+
exportPluginApi(name: string, api: unknown): boolean;
|
|
967
|
+
/**
|
|
968
|
+
* Look up a plugin API previously published via `exportPluginApi`.
|
|
969
|
+
* Returns the api object (restored into the caller's context) or
|
|
970
|
+
* `null` if no plugin exports under that name.
|
|
971
|
+
*/
|
|
972
|
+
getPluginApi(name: string): unknown | null;
|
|
973
|
+
/**
|
|
863
974
|
* Get the active buffer ID (0 if none)
|
|
864
975
|
*/
|
|
865
976
|
getActiveBufferId(): number;
|
|
@@ -1045,8 +1156,26 @@ interface EditorAPI {
|
|
|
1045
1156
|
*/
|
|
1046
1157
|
getCwd(): string;
|
|
1047
1158
|
/**
|
|
1159
|
+
* Get the active authority's display label.
|
|
1160
|
+
*
|
|
1161
|
+
* Empty means the local (default) authority. A non-empty value
|
|
1162
|
+
* means a plugin-installed or SSH authority is in effect (e.g.
|
|
1163
|
+
* `"Container:abc123def456"` for a devcontainer). Intended as a
|
|
1164
|
+
* simple "am I already attached?" check that survives editor
|
|
1165
|
+
* restarts — the label lives on the `Editor` state snapshot so it
|
|
1166
|
+
* is fresh after the authority-transition restart flow.
|
|
1167
|
+
*/
|
|
1168
|
+
getAuthorityLabel(): string;
|
|
1169
|
+
/**
|
|
1048
1170
|
* Join path components (variadic - accepts multiple string arguments)
|
|
1049
1171
|
* Always uses forward slashes for cross-platform consistency (like Node.js path.posix.join)
|
|
1172
|
+
*
|
|
1173
|
+
* Preserves up to 2 leading slashes, which matters on Windows: Rust's
|
|
1174
|
+
* `Path::canonicalize` returns `\\?\`-prefixed paths, and `editor.getCwd()`
|
|
1175
|
+
* surfaces that to plugin code verbatim. After the backslash→slash
|
|
1176
|
+
* normalization the prefix becomes `//?/C:/...`; collapsing the leading
|
|
1177
|
+
* `//` to a single `/` yields `/?/C:/...`, which every filesystem API on
|
|
1178
|
+
* Windows rejects, breaking `findConfig()`-style plugin logic.
|
|
1050
1179
|
*/
|
|
1051
1180
|
pathJoin(...parts: string[]): string;
|
|
1052
1181
|
/**
|
|
@@ -1129,6 +1258,17 @@ interface EditorAPI {
|
|
|
1129
1258
|
*/
|
|
1130
1259
|
getTempDir(): string;
|
|
1131
1260
|
/**
|
|
1261
|
+
* Parse a JSONC (JSON with comments) string into a JS value.
|
|
1262
|
+
*
|
|
1263
|
+
* Accepts the JSONC superset: line and block comments, trailing
|
|
1264
|
+
* commas, single-quoted strings, and unquoted object keys — matching
|
|
1265
|
+
* devcontainer.json / tsconfig.json / VS Code settings.json.
|
|
1266
|
+
*
|
|
1267
|
+
* Throws a JS error (catchable with try/catch) when the input is not
|
|
1268
|
+
* valid JSONC, like `JSON.parse` does for invalid JSON.
|
|
1269
|
+
*/
|
|
1270
|
+
parseJsonc(text: string): unknown;
|
|
1271
|
+
/**
|
|
1132
1272
|
* Get current config as JS object.
|
|
1133
1273
|
*
|
|
1134
1274
|
* The snapshot holds an `Arc<serde_json::Value>` that was serialized
|
|
@@ -1146,6 +1286,21 @@ interface EditorAPI {
|
|
|
1146
1286
|
*/
|
|
1147
1287
|
reloadConfig(): void;
|
|
1148
1288
|
/**
|
|
1289
|
+
* Set a single config setting in the runtime layer for this session.
|
|
1290
|
+
*
|
|
1291
|
+
* `path` is dot-separated (e.g. `"editor.tab_size"`). `value` is any JSON
|
|
1292
|
+
* value in the shape the setting expects. The write lives in an
|
|
1293
|
+
* in-memory layer scoped to the calling plugin — it does not modify
|
|
1294
|
+
* `config.json`, and unloading the plugin (or reloading init.ts) drops
|
|
1295
|
+
* it. Intended use is `init.ts` running a conditional:
|
|
1296
|
+
* `if (editor.getEnv("SSH_TTY")) editor.setSetting("terminal.mouse", false);`
|
|
1297
|
+
*
|
|
1298
|
+
* Returns `true` if the write was queued. The actual update is
|
|
1299
|
+
* asynchronous; a subsequent `getConfig()` will reflect it after the
|
|
1300
|
+
* editor processes the command.
|
|
1301
|
+
*/
|
|
1302
|
+
setSetting(path: string, value: unknown): boolean;
|
|
1303
|
+
/**
|
|
1149
1304
|
* Reload theme registry from disk
|
|
1150
1305
|
* Call this after installing theme packages or saving new themes
|
|
1151
1306
|
*/
|
|
@@ -1197,6 +1352,17 @@ interface EditorAPI {
|
|
|
1197
1352
|
*/
|
|
1198
1353
|
applyTheme(themeName: string): boolean;
|
|
1199
1354
|
/**
|
|
1355
|
+
* Override theme colors in-memory for the running session. `overrides`
|
|
1356
|
+
* is a JS object mapping `"section.field"` keys (same namespace as
|
|
1357
|
+
* `getThemeSchema`) to `[r, g, b]` triplets (0–255 each).
|
|
1358
|
+
*
|
|
1359
|
+
* Unknown keys are dropped silently; out-of-range values are clamped
|
|
1360
|
+
* to `0..=255`. Overrides survive until the next `applyTheme` call
|
|
1361
|
+
* (which replaces the whole `Theme`). Intended for fast animation
|
|
1362
|
+
* loops from `init.ts` — no disk I/O, no theme-registry rescan.
|
|
1363
|
+
*/
|
|
1364
|
+
overrideThemeColors(overrides: unknown): boolean;
|
|
1365
|
+
/**
|
|
1200
1366
|
* Get theme schema as JS object
|
|
1201
1367
|
*/
|
|
1202
1368
|
getThemeSchema(): unknown;
|
|
@@ -1610,6 +1776,56 @@ interface EditorAPI {
|
|
|
1610
1776
|
*/
|
|
1611
1777
|
spawnProcess(command: string, args: string[], cwd?: string): ProcessHandle<SpawnResult>;
|
|
1612
1778
|
/**
|
|
1779
|
+
* Spawn a process on the host regardless of the active authority.
|
|
1780
|
+
*
|
|
1781
|
+
* Intended for plugin internals that must run host-side work
|
|
1782
|
+
* (e.g. `devcontainer up`) before installing an authority that
|
|
1783
|
+
* would otherwise route the spawn elsewhere. Same calling shape
|
|
1784
|
+
* as `spawnProcess`.
|
|
1785
|
+
*/
|
|
1786
|
+
spawnHostProcess(command: string, args: string[], cwd?: string): ProcessHandle<SpawnResult>;
|
|
1787
|
+
/**
|
|
1788
|
+
* Install a new authority via an opaque payload.
|
|
1789
|
+
*
|
|
1790
|
+
* The payload is a JS object describing filesystem + spawner +
|
|
1791
|
+
* terminal wrapper + display label. The canonical schema lives in
|
|
1792
|
+
* the `AuthorityPayload` type in `fresh-editor`; plugins should
|
|
1793
|
+
* hand-build objects that match it. Fire-and-forget: the editor
|
|
1794
|
+
* restarts as part of the transition, so the plugin is reloaded
|
|
1795
|
+
* before any follow-up work can run on this call's return value.
|
|
1796
|
+
*/
|
|
1797
|
+
setAuthority(payload: AuthorityPayload): boolean;
|
|
1798
|
+
/**
|
|
1799
|
+
* Restore the default local authority. Same restart semantics as
|
|
1800
|
+
* `setAuthority`.
|
|
1801
|
+
*/
|
|
1802
|
+
clearAuthority(): void;
|
|
1803
|
+
/**
|
|
1804
|
+
* Override the Remote Indicator's displayed state. Plugins call
|
|
1805
|
+
* this to surface lifecycle transitions that the authority layer
|
|
1806
|
+
* doesn't know about yet — "Connecting" while `devcontainer up`
|
|
1807
|
+
* runs, "FailedAttach" after a non-zero exit, etc.
|
|
1808
|
+
*
|
|
1809
|
+
* Accepts a tagged JS object:
|
|
1810
|
+
* ```ts
|
|
1811
|
+
* editor.setRemoteIndicatorState({ kind: "connecting", label: "Building" });
|
|
1812
|
+
* editor.setRemoteIndicatorState({ kind: "failed_attach", error: "exit 1" });
|
|
1813
|
+
* editor.setRemoteIndicatorState({ kind: "connected", label: "Container:abc" });
|
|
1814
|
+
* editor.setRemoteIndicatorState({ kind: "local" });
|
|
1815
|
+
* ```
|
|
1816
|
+
*
|
|
1817
|
+
* The override sticks until replaced or cleared via
|
|
1818
|
+
* `clearRemoteIndicatorState`. Editor restart (e.g. on
|
|
1819
|
+
* `setAuthority`) resets it — plugins must reassert after a
|
|
1820
|
+
* post-restart init if they want the override to persist.
|
|
1821
|
+
*/
|
|
1822
|
+
setRemoteIndicatorState(state: RemoteIndicatorStatePayload): boolean;
|
|
1823
|
+
/**
|
|
1824
|
+
* Drop any active Remote Indicator override. Safe to call even
|
|
1825
|
+
* without a prior `setRemoteIndicatorState`.
|
|
1826
|
+
*/
|
|
1827
|
+
clearRemoteIndicatorState(): void;
|
|
1828
|
+
/**
|
|
1613
1829
|
* Wait for a process to complete and get its result (async)
|
|
1614
1830
|
*/
|
|
1615
1831
|
spawnProcessWait(processId: number): Promise<SpawnResult>;
|
|
@@ -1700,3 +1916,12 @@ interface EditorAPI {
|
|
|
1700
1916
|
enabled: boolean;
|
|
1701
1917
|
}>>;
|
|
1702
1918
|
}
|
|
1919
|
+
/**
|
|
1920
|
+
* Typed overload of `editor.getPluginApi`. When the caller passes a
|
|
1921
|
+
* key that some loaded plugin declared in `FreshPluginRegistry`, the
|
|
1922
|
+
* return type is narrowed to that plugin's API. Unknown names fall
|
|
1923
|
+
* through to the untyped `unknown | null` signature.
|
|
1924
|
+
*/
|
|
1925
|
+
interface EditorAPI {
|
|
1926
|
+
getPluginApi<K extends keyof FreshPluginRegistry>(name: K): FreshPluginRegistry[K] | null;
|
|
1927
|
+
}
|
|
@@ -966,6 +966,23 @@ function concealedText(text: string): string {
|
|
|
966
966
|
|
|
967
967
|
const MIN_COL_W = 3;
|
|
968
968
|
|
|
969
|
+
/**
|
|
970
|
+
* Return the effective compose width for layout: the configured compose
|
|
971
|
+
* width clamped to the available viewport width.
|
|
972
|
+
*
|
|
973
|
+
* When `config.composeWidth` is explicitly set (e.g. 80) but the editor
|
|
974
|
+
* content area is smaller (e.g. after the File Explorer sidebar opens),
|
|
975
|
+
* using the configured value verbatim overflows the viewport. The Rust
|
|
976
|
+
* render layer already clamps the compose area the same way in
|
|
977
|
+
* `calculate_compose_layout`; plugin-side computations (table column
|
|
978
|
+
* allocation, soft-wrap width) need to match.
|
|
979
|
+
*/
|
|
980
|
+
function effectiveComposeWidth(viewportWidth: number): number {
|
|
981
|
+
const cw = config.composeWidth;
|
|
982
|
+
if (cw == null) return viewportWidth;
|
|
983
|
+
return Math.min(cw, viewportWidth);
|
|
984
|
+
}
|
|
985
|
+
|
|
969
986
|
/**
|
|
970
987
|
* W3C-inspired column width distribution.
|
|
971
988
|
* Constrains columns to fit within `available` width, distributing space
|
|
@@ -1142,6 +1159,26 @@ function processLineConceals(
|
|
|
1142
1159
|
if (lineContent[i] === '|') pipePositions.push(i);
|
|
1143
1160
|
}
|
|
1144
1161
|
|
|
1162
|
+
// Precompute which cells will be truncated. Per-character conceals
|
|
1163
|
+
// that land inside a truncated cell must be suppressed — the cell-
|
|
1164
|
+
// wide truncate conceal already renders the replacement. When both
|
|
1165
|
+
// fire, the per-char conceal at the cell's first byte emits its
|
|
1166
|
+
// replacement, and the cell-wide conceal emits its replacement one
|
|
1167
|
+
// byte later, producing a cell one character wider than allocated.
|
|
1168
|
+
const truncatedCellCharRanges: Array<{start: number; end: number}> = [];
|
|
1169
|
+
if (!cursorStrictlyOnLine && colWidths) {
|
|
1170
|
+
for (let ci = 0; ci < Math.min(cells.length, colWidths.length); ci++) {
|
|
1171
|
+
const cellText = concealedText(cells[ci]);
|
|
1172
|
+
if (cellText.length > colWidths[ci]) {
|
|
1173
|
+
const prevPipe = pipePositions[ci];
|
|
1174
|
+
const nextPipe = pipePositions[ci + 1];
|
|
1175
|
+
if (prevPipe !== undefined && nextPipe !== undefined) {
|
|
1176
|
+
truncatedCellCharRanges.push({ start: prevPipe + 1, end: nextPipe });
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1145
1182
|
// Track which pipe index we're on (0 = leading pipe)
|
|
1146
1183
|
let pipeIdx = 0;
|
|
1147
1184
|
for (let i = 0; i < lineContent.length; i++) {
|
|
@@ -1161,11 +1198,15 @@ function processLineConceals(
|
|
|
1161
1198
|
const allocatedWidth = colWidths[cellIdx];
|
|
1162
1199
|
|
|
1163
1200
|
if (cellWidth > allocatedWidth) {
|
|
1164
|
-
// Truncate: conceal entire cell content and replace with truncated text
|
|
1201
|
+
// Truncate: conceal entire cell content and replace with truncated text.
|
|
1202
|
+
// Separator rows use box-drawing ─ to match the non-truncated path
|
|
1203
|
+
// (per-char conceals replace source `-` with ─ and pad via pipe replacement).
|
|
1165
1204
|
const prevPipeCharPos = pipePositions[pipeIdx - 1];
|
|
1166
1205
|
const cellByteStart = charToByte(lineContent, prevPipeCharPos + 1, byteStart);
|
|
1167
1206
|
const cellByteEnd = pipeByte;
|
|
1168
|
-
const truncated =
|
|
1207
|
+
const truncated = isSeparator
|
|
1208
|
+
? '─'.repeat(allocatedWidth)
|
|
1209
|
+
: cellText.slice(0, allocatedWidth - 1) + '-';
|
|
1169
1210
|
editor.addConceal(bufferId, "md-syntax", cellByteStart, cellByteEnd, truncated);
|
|
1170
1211
|
truncatedByteRanges.push({start: cellByteStart, end: cellByteEnd});
|
|
1171
1212
|
} else {
|
|
@@ -1188,6 +1229,10 @@ function processLineConceals(
|
|
|
1188
1229
|
}
|
|
1189
1230
|
pipeIdx++;
|
|
1190
1231
|
} else if (isSeparator && lineContent[i] === '-') {
|
|
1232
|
+
// Skip per-character conceals that land inside a truncated cell;
|
|
1233
|
+
// the cell-wide truncate conceal already handles the rendering.
|
|
1234
|
+
const inTruncated = truncatedCellCharRanges.some(r => i >= r.start && i < r.end);
|
|
1235
|
+
if (inTruncated) continue;
|
|
1191
1236
|
const db = charToByte(lineContent, i, byteStart);
|
|
1192
1237
|
editor.addConceal(bufferId, "md-syntax", db, charToByte(lineContent, i + 1, byteStart), "─");
|
|
1193
1238
|
}
|
|
@@ -1292,7 +1337,7 @@ function processLineSoftBreaks(
|
|
|
1292
1337
|
|
|
1293
1338
|
const viewport = editor.getViewport();
|
|
1294
1339
|
if (!viewport) return;
|
|
1295
|
-
const width =
|
|
1340
|
+
const width = effectiveComposeWidth(viewport.width);
|
|
1296
1341
|
|
|
1297
1342
|
// Parse this single line to get block structure
|
|
1298
1343
|
const blocks = parseMarkdownBlocks(lineContent);
|
|
@@ -1516,9 +1561,12 @@ function processTableAlignment(
|
|
|
1516
1561
|
mergeWith(widthMap.get(ln)!.maxW);
|
|
1517
1562
|
}
|
|
1518
1563
|
|
|
1519
|
-
// Compute allocated widths constrained to viewport
|
|
1564
|
+
// Compute allocated widths constrained to viewport. Clamp the
|
|
1565
|
+
// configured compose width to the actual viewport — otherwise a
|
|
1566
|
+
// large configured width overflows when the editor area shrinks
|
|
1567
|
+
// (e.g. when the File Explorer sidebar opens).
|
|
1520
1568
|
const viewport = editor.getViewport();
|
|
1521
|
-
const composeW =
|
|
1569
|
+
const composeW = effectiveComposeWidth(viewport ? viewport.width : 80);
|
|
1522
1570
|
const numCols = merged.length;
|
|
1523
1571
|
const available = composeW - (numCols + 1); // subtract pipe/box-drawing characters
|
|
1524
1572
|
const allocated = distributeColumnWidths(merged, available);
|
|
@@ -1688,7 +1736,7 @@ function onMarkdownViewportChanged(data: {
|
|
|
1688
1736
|
// Recompute allocated table column widths for new viewport width
|
|
1689
1737
|
const bufWidths = getTableWidths(data.buffer_id);
|
|
1690
1738
|
if (bufWidths) {
|
|
1691
|
-
const composeW =
|
|
1739
|
+
const composeW = effectiveComposeWidth(data.width);
|
|
1692
1740
|
const seen = new Set<string>(); // Track by JSON key to deduplicate shared TableWidthInfo
|
|
1693
1741
|
for (const [lineNum, info] of bufWidths) {
|
|
1694
1742
|
const key = info.maxW.join(",");
|