@involvex/fresh-editor 0.1.76 → 0.1.78

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 (90) hide show
  1. package/bin/CHANGELOG.md +1017 -0
  2. package/bin/LICENSE +117 -0
  3. package/bin/README.md +248 -0
  4. package/bin/fresh.exe +0 -0
  5. package/bin/plugins/README.md +71 -0
  6. package/bin/plugins/audit_mode.i18n.json +821 -0
  7. package/bin/plugins/audit_mode.ts +1810 -0
  8. package/bin/plugins/buffer_modified.i18n.json +67 -0
  9. package/bin/plugins/buffer_modified.ts +281 -0
  10. package/bin/plugins/calculator.i18n.json +93 -0
  11. package/bin/plugins/calculator.ts +770 -0
  12. package/bin/plugins/clangd-lsp.ts +168 -0
  13. package/bin/plugins/clangd_support.i18n.json +223 -0
  14. package/bin/plugins/clangd_support.md +20 -0
  15. package/bin/plugins/clangd_support.ts +325 -0
  16. package/bin/plugins/color_highlighter.i18n.json +145 -0
  17. package/bin/plugins/color_highlighter.ts +304 -0
  18. package/bin/plugins/config-schema.json +768 -0
  19. package/bin/plugins/csharp-lsp.ts +147 -0
  20. package/bin/plugins/csharp_support.i18n.json +80 -0
  21. package/bin/plugins/csharp_support.ts +170 -0
  22. package/bin/plugins/css-lsp.ts +143 -0
  23. package/bin/plugins/diagnostics_panel.i18n.json +236 -0
  24. package/bin/plugins/diagnostics_panel.ts +642 -0
  25. package/bin/plugins/examples/README.md +85 -0
  26. package/bin/plugins/examples/async_demo.ts +165 -0
  27. package/bin/plugins/examples/bookmarks.ts +329 -0
  28. package/bin/plugins/examples/buffer_query_demo.ts +110 -0
  29. package/bin/plugins/examples/git_grep.ts +262 -0
  30. package/bin/plugins/examples/hello_world.ts +93 -0
  31. package/bin/plugins/examples/virtual_buffer_demo.ts +116 -0
  32. package/bin/plugins/find_references.i18n.json +275 -0
  33. package/bin/plugins/find_references.ts +359 -0
  34. package/bin/plugins/git_blame.i18n.json +496 -0
  35. package/bin/plugins/git_blame.ts +707 -0
  36. package/bin/plugins/git_find_file.i18n.json +314 -0
  37. package/bin/plugins/git_find_file.ts +300 -0
  38. package/bin/plugins/git_grep.i18n.json +171 -0
  39. package/bin/plugins/git_grep.ts +191 -0
  40. package/bin/plugins/git_gutter.i18n.json +93 -0
  41. package/bin/plugins/git_gutter.ts +477 -0
  42. package/bin/plugins/git_log.i18n.json +481 -0
  43. package/bin/plugins/git_log.ts +1285 -0
  44. package/bin/plugins/go-lsp.ts +143 -0
  45. package/bin/plugins/html-lsp.ts +145 -0
  46. package/bin/plugins/json-lsp.ts +145 -0
  47. package/bin/plugins/lib/fresh.d.ts +1321 -0
  48. package/bin/plugins/lib/index.ts +24 -0
  49. package/bin/plugins/lib/navigation-controller.ts +214 -0
  50. package/bin/plugins/lib/panel-manager.ts +220 -0
  51. package/bin/plugins/lib/types.ts +72 -0
  52. package/bin/plugins/lib/virtual-buffer-factory.ts +130 -0
  53. package/bin/plugins/live_grep.i18n.json +171 -0
  54. package/bin/plugins/live_grep.ts +422 -0
  55. package/bin/plugins/markdown_compose.i18n.json +223 -0
  56. package/bin/plugins/markdown_compose.ts +630 -0
  57. package/bin/plugins/merge_conflict.i18n.json +821 -0
  58. package/bin/plugins/merge_conflict.ts +1810 -0
  59. package/bin/plugins/path_complete.i18n.json +80 -0
  60. package/bin/plugins/path_complete.ts +165 -0
  61. package/bin/plugins/python-lsp.ts +162 -0
  62. package/bin/plugins/rust-lsp.ts +166 -0
  63. package/bin/plugins/search_replace.i18n.json +405 -0
  64. package/bin/plugins/search_replace.ts +484 -0
  65. package/bin/plugins/test_i18n.i18n.json +67 -0
  66. package/bin/plugins/test_i18n.ts +18 -0
  67. package/bin/plugins/theme_editor.i18n.json +3746 -0
  68. package/bin/plugins/theme_editor.ts +2063 -0
  69. package/bin/plugins/todo_highlighter.i18n.json +184 -0
  70. package/bin/plugins/todo_highlighter.ts +206 -0
  71. package/bin/plugins/typescript-lsp.ts +167 -0
  72. package/bin/plugins/vi_mode.i18n.json +1549 -0
  73. package/bin/plugins/vi_mode.ts +2747 -0
  74. package/bin/plugins/welcome.i18n.json +236 -0
  75. package/bin/plugins/welcome.ts +76 -0
  76. package/bin/themes/dark.json +102 -0
  77. package/bin/themes/dracula.json +62 -0
  78. package/bin/themes/high-contrast.json +102 -0
  79. package/bin/themes/light.json +102 -0
  80. package/bin/themes/nord.json +62 -0
  81. package/bin/themes/nostalgia.json +102 -0
  82. package/bin/themes/solarized-dark.json +62 -0
  83. package/binary-install.js +1 -1
  84. package/dist/bin/fresh.js +9 -0
  85. package/dist/binary-install.js +149 -0
  86. package/dist/binary.js +30 -0
  87. package/dist/fresh-6yhknp07.exe +0 -0
  88. package/dist/install.js +158 -0
  89. package/dist/run-fresh.js +43 -0
  90. package/package.json +7 -2
@@ -0,0 +1,171 @@
1
+ {
2
+ "en": {
3
+ "cmd.live_grep": "Live Grep (Find in Files)",
4
+ "cmd.live_grep_desc": "Search for text across project with live preview",
5
+ "status.ready": "Live Grep ready - use command palette or bind 'start_live_grep'",
6
+ "status.type_to_search": "Type to search (min 2 chars)...",
7
+ "status.found_matches": "Found {count} matches",
8
+ "status.no_matches": "No matches found",
9
+ "status.search_error": "Search error: {error}",
10
+ "status.opened_file": "Opened {file}:{line}",
11
+ "status.no_file_selected": "No file selected",
12
+ "status.cancelled": "Live grep cancelled",
13
+ "prompt.live_grep": "Live grep: "
14
+ },
15
+ "cs": {
16
+ "cmd.live_grep": "Live Grep (Hledat v souborech)",
17
+ "cmd.live_grep_desc": "Vyhledavani textu v projektu s zivy nahledem",
18
+ "status.ready": "Live Grep pripraven - pouzijte paletu prikazu nebo nastavte 'start_live_grep'",
19
+ "status.type_to_search": "Piste pro vyhledavani (min 2 znaky)...",
20
+ "status.found_matches": "Nalezeno {count} shod",
21
+ "status.no_matches": "Zadne shody nenalezeny",
22
+ "status.search_error": "Chyba vyhledavani: {error}",
23
+ "status.opened_file": "Otevreno {file}:{line}",
24
+ "status.no_file_selected": "Zaden soubor nevybran",
25
+ "status.cancelled": "Live grep zrusen",
26
+ "prompt.live_grep": "Live grep: "
27
+ },
28
+ "de": {
29
+ "cmd.live_grep": "Live Grep (Suche in Dateien)",
30
+ "cmd.live_grep_desc": "Text im gesamten Projekt mit Live-Vorschau suchen",
31
+ "status.ready": "Live Grep bereit - Befehlspalette verwenden oder 'start_live_grep' binden",
32
+ "status.type_to_search": "Tippen zum Suchen (min. 2 Zeichen)...",
33
+ "status.found_matches": "{count} Treffer gefunden",
34
+ "status.no_matches": "Keine Treffer gefunden",
35
+ "status.search_error": "Suchfehler: {error}",
36
+ "status.opened_file": "{file}:{line} geoffnet",
37
+ "status.no_file_selected": "Keine Datei ausgewahlt",
38
+ "status.cancelled": "Live Grep abgebrochen",
39
+ "prompt.live_grep": "Live Grep: "
40
+ },
41
+ "es": {
42
+ "cmd.live_grep": "Grep en Vivo (Buscar en Archivos)",
43
+ "cmd.live_grep_desc": "Buscar texto en todo el proyecto con vista previa en vivo",
44
+ "status.ready": "Grep en Vivo listo - usa la paleta de comandos o asigna 'start_live_grep'",
45
+ "status.type_to_search": "Escribe para buscar (min 2 caracteres)...",
46
+ "status.found_matches": "Se encontraron {count} coincidencias",
47
+ "status.no_matches": "No se encontraron coincidencias",
48
+ "status.search_error": "Error de busqueda: {error}",
49
+ "status.opened_file": "Abierto {file}:{line}",
50
+ "status.no_file_selected": "Ningun archivo seleccionado",
51
+ "status.cancelled": "Grep en vivo cancelado",
52
+ "prompt.live_grep": "Grep en vivo: "
53
+ },
54
+ "fr": {
55
+ "cmd.live_grep": "Grep en Direct (Rechercher dans les Fichiers)",
56
+ "cmd.live_grep_desc": "Rechercher du texte dans tout le projet avec apercu en direct",
57
+ "status.ready": "Grep en Direct pret - utilisez la palette de commandes ou liez 'start_live_grep'",
58
+ "status.type_to_search": "Tapez pour rechercher (min 2 caracteres)...",
59
+ "status.found_matches": "{count} correspondances trouvees",
60
+ "status.no_matches": "Aucune correspondance trouvee",
61
+ "status.search_error": "Erreur de recherche : {error}",
62
+ "status.opened_file": "Ouvert {file}:{line}",
63
+ "status.no_file_selected": "Aucun fichier selectionne",
64
+ "status.cancelled": "Grep en direct annule",
65
+ "prompt.live_grep": "Grep en direct : "
66
+ },
67
+ "it": {
68
+ "cmd.live_grep": "Live Grep (Cerca nei file)",
69
+ "cmd.live_grep_desc": "Cerca testo nel progetto con anteprima in tempo reale",
70
+ "status.ready": "Live Grep pronto - usa la tavolozza comandi o associa 'start_live_grep'",
71
+ "status.type_to_search": "Scrivi per cercare (min 2 caratteri)...",
72
+ "status.found_matches": "Trovate {count} corrispondenze",
73
+ "status.no_matches": "Nessuna corrispondenza trovata",
74
+ "status.search_error": "Errore di ricerca: {error}",
75
+ "status.opened_file": "Aperto {file}:{line}",
76
+ "status.no_file_selected": "Nessun file selezionato",
77
+ "status.cancelled": "Live grep annullato",
78
+ "prompt.live_grep": "Live grep: "
79
+ },
80
+ "ja": {
81
+ "cmd.live_grep": "Live Grep (ファイル内検索)",
82
+ "cmd.live_grep_desc": "プロジェクト全体をライブプレビュー付きで検索",
83
+ "status.ready": "Live Grep 準備完了 - コマンドパレットを使用するか 'start_live_grep' をバインド",
84
+ "status.type_to_search": "検索するには入力してください (最小2文字)...",
85
+ "status.found_matches": "{count} 件の一致が見つかりました",
86
+ "status.no_matches": "一致するものが見つかりません",
87
+ "status.search_error": "検索エラー: {error}",
88
+ "status.opened_file": "{file}:{line} を開きました",
89
+ "status.no_file_selected": "ファイルが選択されていません",
90
+ "status.cancelled": "Live grep がキャンセルされました",
91
+ "prompt.live_grep": "Live grep: "
92
+ },
93
+ "ko": {
94
+ "cmd.live_grep": "라이브 Grep (파일에서 찾기)",
95
+ "cmd.live_grep_desc": "실시간 미리보기로 프로젝트 전체에서 텍스트 검색",
96
+ "status.ready": "라이브 Grep 준비됨 - 명령 팔레트를 사용하거나 'start_live_grep' 바인딩",
97
+ "status.type_to_search": "검색하려면 입력하세요 (최소 2자)...",
98
+ "status.found_matches": "{count}개의 일치 항목 발견",
99
+ "status.no_matches": "일치 항목 없음",
100
+ "status.search_error": "검색 오류: {error}",
101
+ "status.opened_file": "{file}:{line} 열림",
102
+ "status.no_file_selected": "선택된 파일 없음",
103
+ "status.cancelled": "라이브 grep 취소됨",
104
+ "prompt.live_grep": "라이브 grep: "
105
+ },
106
+ "pt-BR": {
107
+ "cmd.live_grep": "Grep ao Vivo (Buscar em Arquivos)",
108
+ "cmd.live_grep_desc": "Pesquisar texto no projeto com visualizacao ao vivo",
109
+ "status.ready": "Grep ao Vivo pronto - use a paleta de comandos ou vincule 'start_live_grep'",
110
+ "status.type_to_search": "Digite para pesquisar (min 2 caracteres)...",
111
+ "status.found_matches": "{count} correspondencias encontradas",
112
+ "status.no_matches": "Nenhuma correspondencia encontrada",
113
+ "status.search_error": "Erro de pesquisa: {error}",
114
+ "status.opened_file": "Aberto {file}:{line}",
115
+ "status.no_file_selected": "Nenhum arquivo selecionado",
116
+ "status.cancelled": "Grep ao vivo cancelado",
117
+ "prompt.live_grep": "Grep ao vivo: "
118
+ },
119
+ "ru": {
120
+ "cmd.live_grep": "Live Grep (Поиск в файлах)",
121
+ "cmd.live_grep_desc": "Поиск текста по всему проекту с предпросмотром в реальном времени",
122
+ "status.ready": "Live Grep готов - используйте палитру команд или назначьте 'start_live_grep'",
123
+ "status.type_to_search": "Введите для поиска (мин. 2 символа)...",
124
+ "status.found_matches": "Найдено {count} совпадений",
125
+ "status.no_matches": "Совпадения не найдены",
126
+ "status.search_error": "Ошибка поиска: {error}",
127
+ "status.opened_file": "Открыт {file}:{line}",
128
+ "status.no_file_selected": "Файл не выбран",
129
+ "status.cancelled": "Live grep отменен",
130
+ "prompt.live_grep": "Live grep: "
131
+ },
132
+ "th": {
133
+ "cmd.live_grep": "Live Grep (ค้นหาในไฟล์)",
134
+ "cmd.live_grep_desc": "ค้นหาข้อความในโปรเจกต์พร้อมดูตัวอย่างสด",
135
+ "status.ready": "Live Grep พร้อมแล้ว - ใช้พาเลทคำสั่งหรือผูก 'start_live_grep'",
136
+ "status.type_to_search": "พิมพ์เพื่อค้นหา (อย่างน้อย 2 ตัวอักษร)...",
137
+ "status.found_matches": "พบ {count} รายการที่ตรงกัน",
138
+ "status.no_matches": "ไม่พบรายการที่ตรงกัน",
139
+ "status.search_error": "ข้อผิดพลาดการค้นหา: {error}",
140
+ "status.opened_file": "เปิดแล้ว {file}:{line}",
141
+ "status.no_file_selected": "ไม่ได้เลือกไฟล์",
142
+ "status.cancelled": "ยกเลิก Live grep แล้ว",
143
+ "prompt.live_grep": "Live grep: "
144
+ },
145
+ "uk": {
146
+ "cmd.live_grep": "Live Grep (Пошук у файлах)",
147
+ "cmd.live_grep_desc": "Пошук тексту по всьому проекту з попереднім переглядом у реальному часі",
148
+ "status.ready": "Live Grep готовий - використовуйте палітру команд або прив'яжіть 'start_live_grep'",
149
+ "status.type_to_search": "Введіть для пошуку (мін. 2 символи)...",
150
+ "status.found_matches": "Знайдено {count} збігів",
151
+ "status.no_matches": "Збігів не знайдено",
152
+ "status.search_error": "Помилка пошуку: {error}",
153
+ "status.opened_file": "Відкрито {file}:{line}",
154
+ "status.no_file_selected": "Файл не вибрано",
155
+ "status.cancelled": "Live grep скасовано",
156
+ "prompt.live_grep": "Live grep: "
157
+ },
158
+ "zh-CN": {
159
+ "cmd.live_grep": "实时 Grep (文件内搜索)",
160
+ "cmd.live_grep_desc": "在整个项目中搜索文本并实时预览",
161
+ "status.ready": "实时 Grep 已就绪 - 使用命令面板或绑定 'start_live_grep'",
162
+ "status.type_to_search": "输入以搜索 (最少2个字符)...",
163
+ "status.found_matches": "找到 {count} 个匹配项",
164
+ "status.no_matches": "未找到匹配项",
165
+ "status.search_error": "搜索错误: {error}",
166
+ "status.opened_file": "已打开 {file}:{line}",
167
+ "status.no_file_selected": "未选择文件",
168
+ "status.cancelled": "实时 grep 已取消",
169
+ "prompt.live_grep": "实时 grep: "
170
+ }
171
+ }
@@ -0,0 +1,422 @@
1
+ /// <reference path="./lib/fresh.d.ts" />
2
+ const editor = getEditor();
3
+
4
+
5
+ /**
6
+ * Live Grep Plugin
7
+ *
8
+ * Project-wide search with ripgrep and live preview.
9
+ * - Type to search across all files
10
+ * - Navigate results with Up/Down to see preview
11
+ * - Press Enter to open file at location
12
+ */
13
+
14
+ interface GrepMatch {
15
+ file: string;
16
+ line: number;
17
+ column: number;
18
+ content: string;
19
+ }
20
+
21
+ // State management
22
+ let grepResults: GrepMatch[] = [];
23
+ let previewBufferId: number | null = null;
24
+ let previewSplitId: number | null = null;
25
+ let originalSplitId: number | null = null;
26
+ let lastQuery: string = "";
27
+ let previewCreated: boolean = false;
28
+ let currentSearch: ProcessHandle | null = null;
29
+ let pendingKill: Promise<boolean> | null = null; // Track pending kill globally
30
+ let searchVersion = 0; // Incremented on each input change for debouncing
31
+
32
+ const DEBOUNCE_MS = 150; // Wait 150ms after last keystroke before searching
33
+
34
+ // Parse ripgrep output line
35
+ // Format: file:line:column:content
36
+ function parseRipgrepLine(line: string): GrepMatch | null {
37
+ const match = line.match(/^([^:]+):(\d+):(\d+):(.*)$/);
38
+ if (match) {
39
+ return {
40
+ file: match[1],
41
+ line: parseInt(match[2], 10),
42
+ column: parseInt(match[3], 10),
43
+ content: match[4],
44
+ };
45
+ }
46
+ return null;
47
+ }
48
+
49
+ // Parse ripgrep output into suggestions
50
+ function parseRipgrepOutput(stdout: string): {
51
+ results: GrepMatch[];
52
+ suggestions: PromptSuggestion[];
53
+ } {
54
+ const results: GrepMatch[] = [];
55
+ const suggestions: PromptSuggestion[] = [];
56
+
57
+ for (const line of stdout.split("\n")) {
58
+ if (!line.trim()) continue;
59
+ const match = parseRipgrepLine(line);
60
+ if (match) {
61
+ results.push(match);
62
+
63
+ // Truncate long content for display
64
+ const displayContent =
65
+ match.content.length > 60
66
+ ? match.content.substring(0, 57) + "..."
67
+ : match.content;
68
+
69
+ suggestions.push({
70
+ text: `${match.file}:${match.line}`,
71
+ description: displayContent.trim(),
72
+ value: `${results.length - 1}`, // Store index as value
73
+ disabled: false,
74
+ });
75
+
76
+ // Limit to 100 results for performance
77
+ if (results.length >= 100) {
78
+ break;
79
+ }
80
+ }
81
+ }
82
+
83
+ return { results, suggestions };
84
+ }
85
+
86
+ // Create or update preview buffer with file content
87
+ async function updatePreview(match: GrepMatch): Promise<void> {
88
+ try {
89
+ // Read the file content
90
+ const content = await editor.readFile(match.file);
91
+ const lines = content.split("\n");
92
+
93
+ // Calculate context window (5 lines before and after)
94
+ const contextBefore = 5;
95
+ const contextAfter = 5;
96
+ const startLine = Math.max(0, match.line - 1 - contextBefore);
97
+ const endLine = Math.min(lines.length, match.line + contextAfter);
98
+
99
+ // Build preview entries with highlighting
100
+ const entries: TextPropertyEntry[] = [];
101
+
102
+ // Header
103
+ entries.push({
104
+ text: ` ${match.file}:${match.line}:${match.column}\n`,
105
+ properties: { type: "header" },
106
+ });
107
+ entries.push({
108
+ text: "─".repeat(60) + "\n",
109
+ properties: { type: "separator" },
110
+ });
111
+
112
+ // Content lines with line numbers
113
+ for (let i = startLine; i < endLine; i++) {
114
+ const lineNum = i + 1;
115
+ const lineContent = lines[i] || "";
116
+ const isMatchLine = lineNum === match.line;
117
+ const prefix = isMatchLine ? "> " : " ";
118
+ const lineNumStr = String(lineNum).padStart(4, " ");
119
+
120
+ entries.push({
121
+ text: `${prefix}${lineNumStr} │ ${lineContent}\n`,
122
+ properties: {
123
+ type: isMatchLine ? "match" : "context",
124
+ line: lineNum,
125
+ },
126
+ });
127
+ }
128
+
129
+ // Create or update the preview buffer
130
+ if (previewBufferId === null) {
131
+ // Define mode for preview buffer
132
+ editor.defineMode("live-grep-preview", "special", [["q", "close_buffer"]], true);
133
+
134
+ // Create preview in a split on the right
135
+ const result = await editor.createVirtualBufferInSplit({
136
+ name: "*Preview*",
137
+ mode: "live-grep-preview",
138
+ read_only: true,
139
+ entries,
140
+ ratio: 0.5,
141
+ direction: "vertical",
142
+ panel_id: "live-grep-preview",
143
+ show_line_numbers: false,
144
+ editing_disabled: true,
145
+ });
146
+
147
+ // Extract buffer and split IDs from result
148
+ previewBufferId = result.buffer_id;
149
+ previewSplitId = result.split_id ?? null;
150
+
151
+ // Return focus to original split so prompt stays active
152
+ if (originalSplitId !== null) {
153
+ editor.focusSplit(originalSplitId);
154
+ }
155
+ } else {
156
+ // Update existing buffer content
157
+ editor.setVirtualBufferContent(previewBufferId, entries);
158
+ }
159
+ } catch (e) {
160
+ editor.debug(`Failed to update preview: ${e}`);
161
+ }
162
+ }
163
+
164
+ // Close preview buffer and its split
165
+ function closePreview(): void {
166
+ // Close the buffer first
167
+ if (previewBufferId !== null) {
168
+ editor.closeBuffer(previewBufferId);
169
+ previewBufferId = null;
170
+ }
171
+ // Then close the split
172
+ if (previewSplitId !== null) {
173
+ editor.closeSplit(previewSplitId);
174
+ previewSplitId = null;
175
+ }
176
+ }
177
+
178
+ // Run ripgrep search with debouncing
179
+ async function runSearch(query: string): Promise<void> {
180
+ // Increment version to invalidate any pending debounced search
181
+ const thisVersion = ++searchVersion;
182
+ editor.debug(`[live_grep] runSearch called: query="${query}", version=${thisVersion}`);
183
+
184
+ // Kill any existing search immediately (don't wait) to stop wasting CPU
185
+ // Store the kill promise globally so ALL pending searches wait for it
186
+ if (currentSearch) {
187
+ editor.debug(`[live_grep] killing existing search immediately`);
188
+ pendingKill = currentSearch.kill();
189
+ currentSearch = null;
190
+ }
191
+
192
+ if (!query || query.trim().length < 2) {
193
+ // Wait for any pending kill to complete before returning
194
+ if (pendingKill) {
195
+ await pendingKill;
196
+ pendingKill = null;
197
+ }
198
+ editor.debug(`[live_grep] query too short, clearing`);
199
+ editor.setPromptSuggestions([]);
200
+ grepResults = [];
201
+ return;
202
+ }
203
+
204
+ // Debounce: wait a bit to see if user is still typing
205
+ editor.debug(`[live_grep] debouncing for ${DEBOUNCE_MS}ms...`);
206
+ await editor.delay(DEBOUNCE_MS);
207
+
208
+ // Always await any pending kill before continuing - ensures old process is dead
209
+ if (pendingKill) {
210
+ editor.debug(`[live_grep] waiting for previous search to terminate`);
211
+ await pendingKill;
212
+ pendingKill = null;
213
+ editor.debug(`[live_grep] previous search terminated`);
214
+ }
215
+
216
+ // If version changed during delay, a newer search was triggered - abort this one
217
+ if (searchVersion !== thisVersion) {
218
+ editor.debug(`[live_grep] version mismatch after debounce (${thisVersion} vs ${searchVersion}), aborting`);
219
+ return;
220
+ }
221
+
222
+ // Avoid duplicate searches
223
+ if (query === lastQuery) {
224
+ editor.debug(`[live_grep] duplicate query, skipping`);
225
+ return;
226
+ }
227
+ lastQuery = query;
228
+
229
+ try {
230
+ const cwd = editor.getCwd();
231
+ editor.debug(`[live_grep] spawning rg for query="${query}" in cwd="${cwd}"`);
232
+ const searchStartTime = Date.now();
233
+ const search = editor.spawnProcess("rg", [
234
+ "--line-number",
235
+ "--column",
236
+ "--no-heading",
237
+ "--color=never",
238
+ "--smart-case",
239
+ "--max-count=100",
240
+ "-g", "!.git",
241
+ "-g", "!node_modules",
242
+ "-g", "!target",
243
+ "-g", "!*.lock",
244
+ "--",
245
+ query,
246
+ ], cwd);
247
+
248
+ currentSearch = search;
249
+ editor.debug(`[live_grep] awaiting search result...`);
250
+ const result = await search;
251
+ const searchDuration = Date.now() - searchStartTime;
252
+ editor.debug(`[live_grep] rg completed in ${searchDuration}ms, exit_code=${result.exit_code}, stdout_len=${result.stdout.length}`);
253
+
254
+ // Check if this search was cancelled (a new search started)
255
+ if (currentSearch !== search) {
256
+ editor.debug(`[live_grep] search was superseded, discarding results`);
257
+ return; // Discard stale results
258
+ }
259
+ currentSearch = null;
260
+
261
+ if (result.exit_code === 0) {
262
+ const parseStart = Date.now();
263
+ const { results, suggestions } = parseRipgrepOutput(result.stdout);
264
+ editor.debug(`[live_grep] parsed ${results.length} results in ${Date.now() - parseStart}ms`);
265
+ grepResults = results;
266
+ editor.setPromptSuggestions(suggestions);
267
+
268
+ if (results.length > 0) {
269
+ editor.setStatus(editor.t("status.found_matches", { count: String(results.length) }));
270
+ // Show preview of first result
271
+ await updatePreview(results[0]);
272
+ } else {
273
+ editor.setStatus(editor.t("status.no_matches"));
274
+ }
275
+ } else if (result.exit_code === 1) {
276
+ // No matches
277
+ editor.debug(`[live_grep] no matches (exit_code=1)`);
278
+ grepResults = [];
279
+ editor.setPromptSuggestions([]);
280
+ editor.setStatus(editor.t("status.no_matches"));
281
+ } else if (result.exit_code === -1) {
282
+ // Process was killed, ignore
283
+ editor.debug(`[live_grep] process was killed`);
284
+ } else {
285
+ editor.debug(`[live_grep] search error: ${result.stderr}`);
286
+ editor.setStatus(editor.t("status.search_error", { error: result.stderr }));
287
+ }
288
+ } catch (e) {
289
+ // Ignore errors from killed processes
290
+ const errorMsg = String(e);
291
+ editor.debug(`[live_grep] caught error: ${errorMsg}`);
292
+ if (!errorMsg.includes("killed") && !errorMsg.includes("not found")) {
293
+ editor.setStatus(editor.t("status.search_error", { error: String(e) }));
294
+ }
295
+ }
296
+ }
297
+
298
+ // Start live grep
299
+ globalThis.start_live_grep = function (): void {
300
+ // Clear previous state
301
+ grepResults = [];
302
+ lastQuery = "";
303
+ previewBufferId = null;
304
+
305
+ // Remember original split to keep focus
306
+ originalSplitId = editor.getActiveSplitId();
307
+
308
+ // Start the prompt
309
+ editor.startPrompt(editor.t("prompt.live_grep"), "live-grep");
310
+ editor.setStatus(editor.t("status.type_to_search"));
311
+ };
312
+
313
+ // Handle prompt input changes
314
+ globalThis.onLiveGrepPromptChanged = function (args: {
315
+ prompt_type: string;
316
+ input: string;
317
+ }): boolean {
318
+ if (args.prompt_type !== "live-grep") {
319
+ return true;
320
+ }
321
+
322
+ editor.debug(`[live_grep] onPromptChanged: input="${args.input}"`);
323
+
324
+ // runSearch handles debouncing internally
325
+ runSearch(args.input);
326
+
327
+ return true;
328
+ };
329
+
330
+ // Handle selection changes - update preview
331
+ globalThis.onLiveGrepSelectionChanged = function (args: {
332
+ prompt_type: string;
333
+ selected_index: number;
334
+ }): boolean {
335
+ if (args.prompt_type !== "live-grep") {
336
+ return true;
337
+ }
338
+
339
+ const match = grepResults[args.selected_index];
340
+ if (match) {
341
+ updatePreview(match);
342
+ }
343
+
344
+ return true;
345
+ };
346
+
347
+ // Handle prompt confirmation - open file
348
+ globalThis.onLiveGrepPromptConfirmed = function (args: {
349
+ prompt_type: string;
350
+ selected_index: number | null;
351
+ input: string;
352
+ }): boolean {
353
+ if (args.prompt_type !== "live-grep") {
354
+ return true;
355
+ }
356
+
357
+ // Kill any running search
358
+ if (currentSearch) {
359
+ currentSearch.kill();
360
+ currentSearch = null;
361
+ }
362
+
363
+ // Close preview first
364
+ closePreview();
365
+
366
+ // Open selected file
367
+ if (args.selected_index !== null && grepResults[args.selected_index]) {
368
+ const selected = grepResults[args.selected_index];
369
+ editor.openFile(selected.file, selected.line, selected.column);
370
+ editor.setStatus(editor.t("status.opened_file", { file: selected.file, line: String(selected.line) }));
371
+ } else {
372
+ editor.setStatus(editor.t("status.no_file_selected"));
373
+ }
374
+
375
+ // Clear state
376
+ grepResults = [];
377
+ originalSplitId = null;
378
+ previewSplitId = null;
379
+
380
+ return true;
381
+ };
382
+
383
+ // Handle prompt cancellation
384
+ globalThis.onLiveGrepPromptCancelled = function (args: {
385
+ prompt_type: string;
386
+ }): boolean {
387
+ if (args.prompt_type !== "live-grep") {
388
+ return true;
389
+ }
390
+
391
+ // Kill any running search
392
+ if (currentSearch) {
393
+ currentSearch.kill();
394
+ currentSearch = null;
395
+ }
396
+
397
+ // Close preview and cleanup
398
+ closePreview();
399
+ grepResults = [];
400
+ originalSplitId = null;
401
+ previewSplitId = null;
402
+ editor.setStatus(editor.t("status.cancelled"));
403
+
404
+ return true;
405
+ };
406
+
407
+ // Register event handlers
408
+ editor.on("prompt_changed", "onLiveGrepPromptChanged");
409
+ editor.on("prompt_selection_changed", "onLiveGrepSelectionChanged");
410
+ editor.on("prompt_confirmed", "onLiveGrepPromptConfirmed");
411
+ editor.on("prompt_cancelled", "onLiveGrepPromptCancelled");
412
+
413
+ // Register command
414
+ editor.registerCommand(
415
+ "%cmd.live_grep",
416
+ "%cmd.live_grep_desc",
417
+ "start_live_grep",
418
+ "normal"
419
+ );
420
+
421
+ editor.debug("Live Grep plugin loaded");
422
+ editor.setStatus(editor.t("status.ready"));