@fresh-editor/fresh-editor 0.1.75 → 0.1.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +8 -0
  3. package/package.json +1 -1
  4. package/plugins/audit_mode.ts +9 -4
  5. package/plugins/buffer_modified.ts +1 -1
  6. package/plugins/calculator.ts +1 -1
  7. package/plugins/check-types.sh +41 -0
  8. package/plugins/clangd_support.ts +1 -1
  9. package/plugins/color_highlighter.ts +4 -1
  10. package/plugins/config-schema.json +75 -3
  11. package/plugins/diagnostics_panel.i18n.json +52 -52
  12. package/plugins/diagnostics_panel.ts +168 -540
  13. package/plugins/find_references.ts +82 -324
  14. package/plugins/git_blame.i18n.json +260 -247
  15. package/plugins/git_blame.ts +4 -9
  16. package/plugins/git_explorer.ts +159 -0
  17. package/plugins/git_find_file.ts +42 -270
  18. package/plugins/git_grep.ts +50 -167
  19. package/plugins/git_gutter.ts +1 -1
  20. package/plugins/git_log.ts +4 -11
  21. package/plugins/lib/finder.ts +1499 -0
  22. package/plugins/lib/fresh.d.ts +118 -17
  23. package/plugins/lib/index.ts +23 -1
  24. package/plugins/lib/navigation-controller.ts +1 -1
  25. package/plugins/lib/panel-manager.ts +7 -13
  26. package/plugins/lib/results-panel.ts +914 -0
  27. package/plugins/lib/search-utils.ts +343 -0
  28. package/plugins/lib/types.ts +14 -0
  29. package/plugins/lib/virtual-buffer-factory.ts +3 -2
  30. package/plugins/live_grep.ts +56 -379
  31. package/plugins/markdown_compose.ts +1 -17
  32. package/plugins/merge_conflict.ts +16 -14
  33. package/plugins/odin-lsp.ts +135 -0
  34. package/plugins/path_complete.ts +1 -1
  35. package/plugins/search_replace.i18n.json +13 -13
  36. package/plugins/search_replace.ts +11 -9
  37. package/plugins/theme_editor.ts +15 -9
  38. package/plugins/todo_highlighter.ts +1 -0
  39. package/plugins/vi_mode.ts +9 -5
  40. package/plugins/welcome.ts +1 -1
@@ -0,0 +1,135 @@
1
+ /// <reference path="./lib/fresh.d.ts" />
2
+ const editor = getEditor();
3
+
4
+
5
+ /**
6
+ * Odin LSP Helper Plugin
7
+ *
8
+ * Provides user-friendly error handling for Odin LSP server issues.
9
+ * When ols (Odin Language Server) fails to start, this plugin shows an actionable
10
+ * popup with installation instructions.
11
+ *
12
+ * Features:
13
+ * - Detects Odin LSP server errors (ols)
14
+ * - Shows popup with build instructions
15
+ * - Allows copying build commands to clipboard
16
+ * - Provides option to disable Odin LSP
17
+ *
18
+ * OLS: https://github.com/DanielGavin/ols
19
+ */
20
+
21
+ interface LspServerErrorData {
22
+ language: string;
23
+ server_command: string;
24
+ error_type: string;
25
+ message: string;
26
+ }
27
+
28
+ interface LspStatusClickedData {
29
+ language: string;
30
+ has_error: boolean;
31
+ }
32
+
33
+ interface ActionPopupResultData {
34
+ popup_id: string;
35
+ action_id: string;
36
+ }
37
+
38
+ // OLS GitHub repository
39
+ const OLS_URL = "https://github.com/DanielGavin/ols";
40
+
41
+ // Track error state for Odin LSP
42
+ let odinLspError: { serverCommand: string; message: string } | null = null;
43
+
44
+ /**
45
+ * Handle LSP server errors for Odin
46
+ */
47
+ globalThis.on_odin_lsp_server_error = function (data: LspServerErrorData): void {
48
+ // Only handle Odin language errors
49
+ if (data.language !== "odin") {
50
+ return;
51
+ }
52
+
53
+ editor.debug(`odin-lsp: Server error - ${data.error_type}: ${data.message}`);
54
+
55
+ // Store error state for later reference
56
+ odinLspError = {
57
+ serverCommand: data.server_command,
58
+ message: data.message,
59
+ };
60
+
61
+ // Show a status message for immediate feedback
62
+ if (data.error_type === "not_found") {
63
+ editor.setStatus(
64
+ `Odin LSP server '${data.server_command}' not found. Click status bar for help.`
65
+ );
66
+ } else {
67
+ editor.setStatus(`Odin LSP error: ${data.message}`);
68
+ }
69
+ };
70
+
71
+ // Register hook for LSP server errors
72
+ editor.on("lsp_server_error", "on_odin_lsp_server_error");
73
+
74
+ /**
75
+ * Handle status bar click when there's an Odin LSP error
76
+ */
77
+ globalThis.on_odin_lsp_status_clicked = function (
78
+ data: LspStatusClickedData
79
+ ): void {
80
+ // Only handle Odin language clicks when there's an error
81
+ if (data.language !== "odin" || !odinLspError) {
82
+ return;
83
+ }
84
+
85
+ editor.debug("odin-lsp: Status clicked, showing help popup");
86
+
87
+ // Show action popup with install options
88
+ editor.showActionPopup({
89
+ id: "odin-lsp-help",
90
+ title: "Odin Language Server Not Found",
91
+ message: `"${odinLspError.serverCommand}" (OLS) provides code completion, diagnostics, and navigation for Odin files.\n\nInstallation: ${OLS_URL}`,
92
+ actions: [
93
+ { id: "disable", label: "Disable Odin LSP" },
94
+ { id: "dismiss", label: "Dismiss (ESC)" },
95
+ ],
96
+ });
97
+ };
98
+
99
+ // Register hook for status bar clicks
100
+ editor.on("lsp_status_clicked", "on_odin_lsp_status_clicked");
101
+
102
+ /**
103
+ * Handle action popup results for Odin LSP help
104
+ */
105
+ globalThis.on_odin_lsp_action_result = function (
106
+ data: ActionPopupResultData
107
+ ): void {
108
+ // Only handle our popup
109
+ if (data.popup_id !== "odin-lsp-help") {
110
+ return;
111
+ }
112
+
113
+ editor.debug(`odin-lsp: Action selected - ${data.action_id}`);
114
+
115
+ switch (data.action_id) {
116
+ case "disable":
117
+ editor.disableLspForLanguage("odin");
118
+ editor.setStatus("Odin LSP disabled");
119
+ odinLspError = null;
120
+ break;
121
+
122
+ case "dismiss":
123
+ case "dismissed":
124
+ // Just close the popup without action
125
+ break;
126
+
127
+ default:
128
+ editor.debug(`odin-lsp: Unknown action: ${data.action_id}`);
129
+ }
130
+ };
131
+
132
+ // Register hook for action popup results
133
+ editor.on("action_popup_result", "on_odin_lsp_action_result");
134
+
135
+ editor.debug("odin-lsp: Plugin loaded");
@@ -1,4 +1,4 @@
1
- /// <reference path="../types/fresh.d.ts" />
1
+ /// <reference path="./lib/fresh.d.ts" />
2
2
  const editor = getEditor();
3
3
 
4
4
 
@@ -28,7 +28,7 @@
28
28
  "panel.results": "Results: {count}",
29
29
  "panel.limited": "(limited to {max})",
30
30
  "panel.selected": "({selected} selected)",
31
- "panel.help": "[SPC] toggle [a] all [n] none [r] REPLACE [RET] preview [q] close"
31
+ "panel.help": "[Up/Down] navigate [Space] toggle [Enter] replace [Esc] close"
32
32
  },
33
33
  "cs": {
34
34
  "cmd.search_replace": "Hledat a nahradit v projektu",
@@ -59,7 +59,7 @@
59
59
  "panel.results": "Vysledky: {count}",
60
60
  "panel.limited": "(omezeno na {max})",
61
61
  "panel.selected": "({selected} vybrano)",
62
- "panel.help": "[MEZERNIK] prepnout [a] vse [n] nic [r] NAHRADIT [ENTER] nahled [q] zavrit"
62
+ "panel.help": "[Nahoru/Dolu] navigace [Mezernik] prepnout [Enter] nahradit [Esc] zavrit"
63
63
  },
64
64
  "de": {
65
65
  "cmd.search_replace": "Suchen und Ersetzen im Projekt",
@@ -90,7 +90,7 @@
90
90
  "panel.results": "Ergebnisse: {count}",
91
91
  "panel.limited": "(begrenzt auf {max})",
92
92
  "panel.selected": "({selected} ausgewaehlt)",
93
- "panel.help": "[LEER] umschalten [a] alle [n] keine [r] ERSETZEN [RET] Vorschau [q] schliessen"
93
+ "panel.help": "[Auf/Ab] navigieren [Leer] umschalten [Enter] ersetzen [Esc] schliessen"
94
94
  },
95
95
  "es": {
96
96
  "cmd.search_replace": "Buscar y Reemplazar en Proyecto",
@@ -121,7 +121,7 @@
121
121
  "panel.results": "Resultados: {count}",
122
122
  "panel.limited": "(limitado a {max})",
123
123
  "panel.selected": "({selected} seleccionados)",
124
- "panel.help": "[ESP] alternar [a] todos [n] ninguno [r] REEMPLAZAR [RET] vista previa [q] cerrar"
124
+ "panel.help": "[Arriba/Abajo] navegar [Espacio] alternar [Enter] reemplazar [Esc] cerrar"
125
125
  },
126
126
  "fr": {
127
127
  "cmd.search_replace": "Rechercher et Remplacer dans le Projet",
@@ -152,7 +152,7 @@
152
152
  "panel.results": "Resultats : {count}",
153
153
  "panel.limited": "(limite a {max})",
154
154
  "panel.selected": "({selected} selectionnes)",
155
- "panel.help": "[ESP] basculer [a] tous [n] aucun [r] REMPLACER [RET] apercu [q] fermer"
155
+ "panel.help": "[Haut/Bas] naviguer [Espace] basculer [Entree] remplacer [Esc] fermer"
156
156
  },
157
157
  "it": {
158
158
  "cmd.search_replace": "Cerca e sostituisci nel progetto",
@@ -183,7 +183,7 @@
183
183
  "panel.results": "Risultati: {count}",
184
184
  "panel.limited": "(limitati a {max})",
185
185
  "panel.selected": "({selected} selezionati)",
186
- "panel.help": "[SPZ] alterna [a] tutti [n] nessuno [r] SOSTITUISCI [RET] anteprima [q] chiudi"
186
+ "panel.help": "[Su/Giu] naviga [Spazio] alterna [Invio] sostituisci [Esc] chiudi"
187
187
  },
188
188
  "ja": {
189
189
  "cmd.search_replace": "プロジェクト内で検索と置換",
@@ -214,7 +214,7 @@
214
214
  "panel.results": "結果: {count}",
215
215
  "panel.limited": "(最大 {max} 件)",
216
216
  "panel.selected": "({selected} 件選択)",
217
- "panel.help": "[スペース] 切替 [a] 全選択 [n] 全解除 [r] 置換 [RET] プレビュー [q] 閉じる"
217
+ "panel.help": "[上/下] 移動 [スペース] 切替 [Enter] 置換 [Esc] 閉じる"
218
218
  },
219
219
  "ko": {
220
220
  "cmd.search_replace": "프로젝트에서 검색 및 바꾸기",
@@ -245,7 +245,7 @@
245
245
  "panel.results": "결과: {count}",
246
246
  "panel.limited": "(최대 {max}개)",
247
247
  "panel.selected": "({selected}개 선택)",
248
- "panel.help": "[스페이스] 전환 [a] 전체 [n] 없음 [r] 바꾸기 [엔터] 미리보기 [q] 닫기"
248
+ "panel.help": "[위/아래] 탐색 [스페이스] 전환 [엔터] 바꾸기 [Esc] 닫기"
249
249
  },
250
250
  "pt-BR": {
251
251
  "cmd.search_replace": "Pesquisar e Substituir no Projeto",
@@ -276,7 +276,7 @@
276
276
  "panel.results": "Resultados: {count}",
277
277
  "panel.limited": "(limitado a {max})",
278
278
  "panel.selected": "({selected} selecionados)",
279
- "panel.help": "[ESP] alternar [a] todos [n] nenhum [r] SUBSTITUIR [ENTER] visualizar [q] fechar"
279
+ "panel.help": "[Cima/Baixo] navegar [Espaco] alternar [Enter] substituir [Esc] fechar"
280
280
  },
281
281
  "ru": {
282
282
  "cmd.search_replace": "Поиск и замена в проекте",
@@ -307,7 +307,7 @@
307
307
  "panel.results": "Результаты: {count}",
308
308
  "panel.limited": "(ограничено до {max})",
309
309
  "panel.selected": "({selected} выбрано)",
310
- "panel.help": "[ПРОБЕЛ] переключить [a] все [n] ничего [r] ЗАМЕНИТЬ [ВВОД] просмотр [q] закрыть"
310
+ "panel.help": "[Вверх/Вниз] навигация [Пробел] переключить [Enter] заменить [Esc] закрыть"
311
311
  },
312
312
  "th": {
313
313
  "cmd.search_replace": "ค้นหาและแทนที่ในโปรเจกต์",
@@ -338,7 +338,7 @@
338
338
  "panel.results": "ผลลัพธ์: {count}",
339
339
  "panel.limited": "(จำกัด {max})",
340
340
  "panel.selected": "(เลือก {selected})",
341
- "panel.help": "[เว้นวรรค] สลับ [a] ทั้งหมด [n] ไม่มี [r] แทนที่ [ENTER] ดูตัวอย่าง [q] ปิด"
341
+ "panel.help": "[ขึ้น/ลง] นำทาง [เว้นวรรค] สลับ [Enter] แทนที่ [Esc] ปิด"
342
342
  },
343
343
  "uk": {
344
344
  "cmd.search_replace": "Пошук та заміна в проекті",
@@ -369,7 +369,7 @@
369
369
  "panel.results": "Результати: {count}",
370
370
  "panel.limited": "(обмежено до {max})",
371
371
  "panel.selected": "({selected} вибрано)",
372
- "panel.help": "[ПРОБІЛ] перемкнути [a] всі [n] нічого [r] ЗАМІНИТИ [ВВІД] перегляд [q] закрити"
372
+ "panel.help": "[Вгору/Вниз] навігація [Пробіл] перемкнути [Enter] замінити [Esc] закрити"
373
373
  },
374
374
  "zh-CN": {
375
375
  "cmd.search_replace": "在项目中搜索和替换",
@@ -400,6 +400,6 @@
400
400
  "panel.results": "结果: {count}",
401
401
  "panel.limited": "(限制为 {max})",
402
402
  "panel.selected": "(已选择 {selected})",
403
- "panel.help": "[空格] 切换 [a] 全选 [n] 全不选 [r] 替换 [回车] 预览 [q] 关闭"
403
+ "panel.help": "[上/下] 导航 [空格] 切换 [回车] 替换 [Esc] 关闭"
404
404
  }
405
405
  }
@@ -1,4 +1,4 @@
1
- /// <reference path="../types/fresh.d.ts" />
1
+ /// <reference path="./lib/fresh.d.ts" />
2
2
  const editor = getEditor();
3
3
 
4
4
 
@@ -32,16 +32,17 @@ let searchRegex: boolean = false;
32
32
  const MAX_RESULTS = 200;
33
33
 
34
34
  // Define the search-replace mode with keybindings
35
+ // Inherits from "normal" for cursor navigation (Up/Down)
36
+ // Simplified keybindings following UX best practices:
37
+ // - Enter: Execute replace (primary action)
38
+ // - Space: Toggle selection
39
+ // - Escape: Close panel
35
40
  editor.defineMode(
36
41
  "search-replace-list",
37
- null,
42
+ "normal", // Inherit from normal for cursor movement
38
43
  [
39
- ["Return", "search_replace_preview"],
44
+ ["Return", "search_replace_execute"],
40
45
  ["space", "search_replace_toggle_item"],
41
- ["a", "search_replace_select_all"],
42
- ["n", "search_replace_select_none"],
43
- ["r", "search_replace_execute"],
44
- ["q", "search_replace_close"],
45
46
  ["Escape", "search_replace_close"],
46
47
  ],
47
48
  true // read-only
@@ -225,7 +226,7 @@ async function showResultsPanel(): Promise<void> {
225
226
  const entries = buildPanelEntries();
226
227
 
227
228
  try {
228
- resultsBufferId = await editor.createVirtualBufferInSplit({
229
+ const result = await editor.createVirtualBufferInSplit({
229
230
  name: "*Search/Replace*",
230
231
  mode: "search-replace-list",
231
232
  read_only: true,
@@ -235,9 +236,10 @@ async function showResultsPanel(): Promise<void> {
235
236
  show_line_numbers: false,
236
237
  show_cursors: true,
237
238
  });
239
+ resultsBufferId = result.buffer_id;
240
+ resultsSplitId = result.split_id ?? editor.getActiveSplitId();
238
241
 
239
242
  panelOpen = true;
240
- resultsSplitId = editor.getActiveSplitId();
241
243
  editor.debug(`Search/Replace panel opened with buffer ID ${resultsBufferId}`);
242
244
  } catch (error) {
243
245
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -1,4 +1,4 @@
1
- /// <reference path="../types/fresh.d.ts" />
1
+ /// <reference path="./lib/fresh.d.ts" />
2
2
  const editor = getEditor();
3
3
 
4
4
 
@@ -12,6 +12,7 @@ const editor = getEditor();
12
12
  * - Copy from built-in themes to use as starting point
13
13
  * - Save as new theme name
14
14
  * - Easy option to set as default theme
15
+ *
15
16
  */
16
17
 
17
18
  // =============================================================================
@@ -108,7 +109,10 @@ function loadThemeSections(): ThemeSection[] {
108
109
  return cachedThemeSections;
109
110
  }
110
111
 
111
- const schema = editor.getThemeSchema();
112
+ const schema = editor.getThemeSchema() as {
113
+ $defs?: Record<string, Record<string, unknown>>;
114
+ properties?: Record<string, unknown>;
115
+ };
112
116
  const defs = schema.$defs || {};
113
117
 
114
118
  // Helper to resolve $ref and get the referenced schema
@@ -480,7 +484,7 @@ function findThemesDir(): string {
480
484
  */
481
485
  async function loadBuiltinThemes(): Promise<string[]> {
482
486
  try {
483
- const builtinThemes = editor.getBuiltinThemes();
487
+ const builtinThemes = editor.getBuiltinThemes() as Record<string, string>;
484
488
  return Object.keys(builtinThemes);
485
489
  } catch (e) {
486
490
  editor.debug(`Failed to load built-in themes list: ${e}`);
@@ -493,7 +497,7 @@ async function loadBuiltinThemes(): Promise<string[]> {
493
497
  */
494
498
  async function loadThemeFile(name: string): Promise<Record<string, unknown> | null> {
495
499
  try {
496
- const builtinThemes = editor.getBuiltinThemes();
500
+ const builtinThemes = editor.getBuiltinThemes() as Record<string, string>;
497
501
  if (name in builtinThemes) {
498
502
  return JSON.parse(builtinThemes[name]);
499
503
  }
@@ -1639,8 +1643,10 @@ globalThis.theme_editor_nav_prev_section = function(): void {
1639
1643
  */
1640
1644
  globalThis.open_theme_editor = async function(): Promise<void> {
1641
1645
  if (isThemeEditorOpen()) {
1642
- // Focus the existing theme editor buffer
1643
- editor.focusBuffer(state.bufferId!);
1646
+ // Focus the existing theme editor split
1647
+ if (state.splitId !== null) {
1648
+ editor.focusSplit(state.splitId);
1649
+ }
1644
1650
  editor.setStatus(editor.t("status.already_open"));
1645
1651
  return;
1646
1652
  }
@@ -1794,7 +1800,7 @@ globalThis.theme_editor_edit_color = function(): void {
1794
1800
  }
1795
1801
 
1796
1802
  if (field.isSection) {
1797
- theme_editor_toggle_section();
1803
+ globalThis.theme_editor_toggle_section();
1798
1804
  return;
1799
1805
  }
1800
1806
 
@@ -1860,13 +1866,13 @@ globalThis.theme_editor_save = async function(): Promise<void> {
1860
1866
  // Built-in themes require Save As
1861
1867
  if (state.isBuiltin) {
1862
1868
  editor.setStatus(editor.t("status.builtin_requires_save_as"));
1863
- theme_editor_save_as();
1869
+ globalThis.theme_editor_save_as();
1864
1870
  return;
1865
1871
  }
1866
1872
 
1867
1873
  // If theme has never been saved (no path), trigger "Save As" instead
1868
1874
  if (!state.themePath) {
1869
- theme_editor_save_as();
1875
+ globalThis.theme_editor_save_as();
1870
1876
  return;
1871
1877
  }
1872
1878
 
@@ -1,3 +1,4 @@
1
+ /// <reference path="./lib/fresh.d.ts" />
1
2
  // TypeScript TODO Highlighter Plugin
2
3
  // Highlights TODO, FIXME, XXX keywords in source code
3
4
  // Uses targeted overlay invalidation for efficient updates on edits
@@ -12,6 +12,10 @@ const editor = getEditor();
12
12
  *
13
13
  * Uses the plugin API's executeAction() for true operator+motion composability:
14
14
  * any operator works with any motion via O(operators + motions) code.
15
+ *
16
+ * TODO: This plugin uses APIs that don't exist yet:
17
+ * - getLineStartPosition() - for visual block mode column calculation
18
+ * - defineMode with null parent - needs string parent mode
15
19
  */
16
20
 
17
21
  // Vi mode state
@@ -601,7 +605,7 @@ globalThis.vi_repeat = async function (): Promise<void> {
601
605
  editor.executeAction("delete_forward");
602
606
  }
603
607
  if (change.insertedText) {
604
- editor.insertText(change.insertedText);
608
+ editor.insertAtCursor(change.insertedText);
605
609
  }
606
610
  } else if (change.action) {
607
611
  // Simple action like delete_forward, delete_backward
@@ -632,7 +636,7 @@ globalThis.vi_repeat = async function (): Promise<void> {
632
636
  editor.deleteRange(editor.getActiveBufferId(), start, end);
633
637
  }
634
638
  if (change.insertedText) {
635
- editor.insertText(change.insertedText);
639
+ editor.insertAtCursor(change.insertedText);
636
640
  }
637
641
  }
638
642
  break;
@@ -645,7 +649,7 @@ globalThis.vi_repeat = async function (): Promise<void> {
645
649
  // For change: do the delete part, then insert the text
646
650
  applyOperatorWithMotion("d", change.motion, count);
647
651
  if (change.insertedText) {
648
- editor.insertText(change.insertedText);
652
+ editor.insertAtCursor(change.insertedText);
649
653
  }
650
654
  } else {
651
655
  applyOperatorWithMotion(change.operator, change.motion, count);
@@ -662,7 +666,7 @@ globalThis.vi_repeat = async function (): Promise<void> {
662
666
  state.pendingTextObject = change.textObject.modifier;
663
667
  await applyTextObject(change.textObject.object);
664
668
  if (change.operator === "c" && change.insertedText) {
665
- editor.insertText(change.insertedText);
669
+ editor.insertAtCursor(change.insertedText);
666
670
  }
667
671
  }
668
672
  break;
@@ -671,7 +675,7 @@ globalThis.vi_repeat = async function (): Promise<void> {
671
675
  case "insert": {
672
676
  // Pure insert (i, a, o, O)
673
677
  if (change.insertedText) {
674
- editor.insertText(change.insertedText);
678
+ editor.insertAtCursor(change.insertedText);
675
679
  }
676
680
  break;
677
681
  }
@@ -1,4 +1,4 @@
1
- /// <reference path="../types/fresh.d.ts" />
1
+ /// <reference path="./lib/fresh.d.ts" />
2
2
  const editor = getEditor();
3
3
 
4
4