@fresh-editor/fresh-editor 0.1.59 → 0.1.65

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 CHANGED
@@ -1,5 +1,79 @@
1
1
  # Release Notes
2
2
 
3
+ ## 0.1.65
4
+
5
+ ### Features
6
+
7
+ * **Warning Indicators**: Non-intrusive warning notifications in the status bar. Click or use commands to view warnings, with domains for LSP and general warnings.
8
+
9
+ * **Format Buffer Command**: Explicit command to format the current buffer on demand.
10
+
11
+ * **Config Applied on Open**: `line_numbers` and `line_wrap` settings now properly apply when opening new buffers.
12
+
13
+ ### Bug Fixes
14
+
15
+ * **Settings Persistence**: Fixed settings not persisting after save and reopen (#474, #457).
16
+
17
+ * **SaveAs Overwrite Confirmation**: Added confirmation dialog when SaveAs would overwrite an existing file (#476).
18
+
19
+ * **Multi-Byte Character Input**: Fixed panic when editing multi-byte characters in text inputs and prompts (#466).
20
+
21
+ * **TextList Dialog**: Fixed add-new input not rendering in entry dialogs.
22
+
23
+ ---
24
+
25
+ ## 0.1.64
26
+
27
+ * To prevent accidental deletion of files, removed 'd' / delete key bindings from File Explorer, changed the underlying delete to show a prompt and to move files to trash instead of really deleting.
28
+
29
+ ## 0.1.63
30
+
31
+ ### Features
32
+
33
+ * **Shell Command Prompt**: Pipe buffer or selection through shell commands (Alt+|).
34
+
35
+ * **On-Save Actions**: Run formatters/linters on save. Default formatters included for Rust (rustfmt), JavaScript/TypeScript (prettier), Python (ruff), C/C++ (clang-format), Go (gofmt).
36
+
37
+ * **Stdin Input**: Pipe content via stdin with background streaming (`echo "hello" | fresh -`).
38
+
39
+ * **Multi-File CLI**: Open multiple files from command line (#389).
40
+
41
+ * **Tab Indent Selection**: Tab indents selected lines, Shift+Tab dedents (#353).
42
+
43
+ * **Toggle Menu Bar**: Hide/show menu bar via command palette for extra screen space.
44
+
45
+ * **Global File Positions**: Cursor/scroll positions stored globally per file, not per project (#423).
46
+
47
+ * **Relative Line Numbers**: Show relative distances from cursor in gutter for easier vim-style navigation. Enable via `relative_line_numbers` config (#454).
48
+
49
+ ### Bug Fixes
50
+
51
+ * **On-Save Missing Tools**: Graceful handling when formatter/linter command not found.
52
+
53
+ * **Settings UI Nested Dialogs**: Fixed nested ObjectArray navigation and save not persisting (e.g., editing on_save inside language config).
54
+
55
+ * **Live Grep Working Directory**: Fixed search plugins using process cwd instead of project working directory.
56
+
57
+ * **Open File Path Resolution**: Fixed relative paths resolving incorrectly when editor launched from different directory.
58
+
59
+ ### Performance
60
+
61
+ * **Live Grep UI**: Fixed UI freezing for seconds during large codebase searches by making plugin event loop non-blocking.
62
+
63
+ ### Internal
64
+
65
+ * Embedded plugins in binary as fallback for cargo-binstall (#416).
66
+
67
+ * Removed duplicate theme JSON files (#438).
68
+
69
+ * Extracted modules from mod.rs (file_operations, split_actions, clipboard, etc.).
70
+
71
+ * Pinned Rust 1.92 via rust-toolchain.toml (#338).
72
+
73
+ * Windows build switched from MSVC to GNU target.
74
+
75
+ ---
76
+
3
77
  ## 0.1.59
4
78
 
5
79
  ### Features
package/binary-install.js CHANGED
@@ -10,18 +10,28 @@ const REPO = 'sinelaw/fresh';
10
10
  function download(url, dest) {
11
11
  return new Promise((resolve, reject) => {
12
12
  const file = fs.createWriteStream(dest);
13
+ file.on('error', (err) => {
14
+ file.close();
15
+ reject(err);
16
+ });
13
17
  https.get(url, (response) => {
14
18
  if (response.statusCode === 302 || response.statusCode === 301) {
15
- download(response.headers.location, dest).then(resolve).catch(reject);
19
+ file.close(() => {
20
+ download(response.headers.location, dest).then(resolve).catch(reject);
21
+ });
16
22
  return;
17
23
  }
18
24
  if (response.statusCode !== 200) {
25
+ file.close();
19
26
  reject(new Error(`Failed to download: ${response.statusCode}`));
20
27
  return;
21
28
  }
22
29
  response.pipe(file);
23
30
  file.on('finish', () => { file.close(); resolve(); });
24
- }).on('error', reject);
31
+ }).on('error', (err) => {
32
+ file.close();
33
+ reject(err);
34
+ });
25
35
  });
26
36
  }
27
37
 
@@ -31,10 +41,18 @@ async function install() {
31
41
  const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${archiveName}`;
32
42
  const archivePath = path.join(__dirname, archiveName);
33
43
  const binDir = path.join(__dirname, 'bin');
44
+ const binaryPath = path.join(binDir, info.binaryName);
34
45
 
35
46
  console.log(`Downloading ${url}...`);
36
47
  await download(url, archivePath);
37
48
 
49
+ // Verify download succeeded
50
+ const stats = fs.statSync(archivePath);
51
+ if (stats.size === 0) {
52
+ throw new Error(`Downloaded file is empty: ${archivePath}`);
53
+ }
54
+ console.log(`Downloaded ${stats.size} bytes`);
55
+
38
56
  fs.mkdirSync(binDir, { recursive: true });
39
57
 
40
58
  if (info.ext === 'tar.xz') {
@@ -56,6 +74,12 @@ async function install() {
56
74
  }
57
75
 
58
76
  fs.unlinkSync(archivePath);
77
+
78
+ // Verify binary exists
79
+ if (!fs.existsSync(binaryPath)) {
80
+ throw new Error(`Installation failed: binary not found at ${binaryPath}`);
81
+ }
82
+
59
83
  console.log('fresh-editor installed successfully!');
60
84
  }
61
85
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fresh-editor/fresh-editor",
3
- "version": "0.1.59",
3
+ "version": "0.1.65",
4
4
  "description": "A modern terminal-based text editor with plugin support",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,6 +19,7 @@
19
19
  "install.js",
20
20
  "run-fresh.js",
21
21
  "plugins/**/*",
22
+ "themes/**/*",
22
23
  "README.md",
23
24
  "LICENSE",
24
25
  "CHANGELOG.md"
@@ -4,6 +4,13 @@
4
4
  "description": "Main configuration structure",
5
5
  "type": "object",
6
6
  "properties": {
7
+ "version": {
8
+ "description": "Configuration version (for migration support)\nConfigs without this field are treated as version 0",
9
+ "type": "integer",
10
+ "format": "uint32",
11
+ "minimum": 0,
12
+ "default": 0
13
+ },
7
14
  "theme": {
8
15
  "description": "Color theme name",
9
16
  "$ref": "#/$defs/ThemeOptions",
@@ -98,6 +105,13 @@
98
105
  "menu": {
99
106
  "description": "Menu bar configuration",
100
107
  "$ref": "#/$defs/MenuConfig"
108
+ },
109
+ "warnings": {
110
+ "description": "Warning notification settings",
111
+ "$ref": "#/$defs/WarningsConfig",
112
+ "default": {
113
+ "show_status_indicator": true
114
+ }
101
115
  }
102
116
  },
103
117
  "$defs": {
@@ -128,7 +142,7 @@
128
142
  "default": true
129
143
  },
130
144
  "line_numbers": {
131
- "description": "Show line numbers in the gutter",
145
+ "description": "Show line numbers in the gutter (default for new buffers)",
132
146
  "type": "boolean",
133
147
  "default": true
134
148
  },
@@ -150,7 +164,7 @@
150
164
  "default": true
151
165
  },
152
166
  "line_wrap": {
153
- "description": "Wrap long lines to fit the window width",
167
+ "description": "Wrap long lines to fit the window width (default for new views)",
154
168
  "type": "boolean",
155
169
  "default": true
156
170
  },
@@ -330,7 +344,8 @@
330
344
  },
331
345
  "required": [
332
346
  "action"
333
- ]
347
+ ],
348
+ "x-display-field": "/action"
334
349
  },
335
350
  "KeyPress": {
336
351
  "description": "A single key in a sequence",
@@ -372,7 +387,8 @@
372
387
  },
373
388
  "default": []
374
389
  }
375
- }
390
+ },
391
+ "x-display-field": "/inherits"
376
392
  },
377
393
  "KeybindingMapOptions": {
378
394
  "description": "Available keybinding maps",
@@ -453,8 +469,34 @@
453
469
  "format": "uint",
454
470
  "minimum": 0,
455
471
  "default": null
472
+ },
473
+ "formatter": {
474
+ "description": "The formatter for this language (used by format_buffer command)",
475
+ "anyOf": [
476
+ {
477
+ "$ref": "#/$defs/FormatterConfig"
478
+ },
479
+ {
480
+ "type": "null"
481
+ }
482
+ ],
483
+ "default": null
484
+ },
485
+ "format_on_save": {
486
+ "description": "Whether to automatically format on save (uses the formatter above)",
487
+ "type": "boolean",
488
+ "default": false
489
+ },
490
+ "on_save": {
491
+ "description": "Actions to run when a file of this language is saved (linters, etc.)\nActions are run in order; if any fails (non-zero exit), subsequent actions don't run\nNote: Use `formatter` + `format_on_save` for formatting, not on_save",
492
+ "type": "array",
493
+ "items": {
494
+ "$ref": "#/$defs/OnSaveAction"
495
+ },
496
+ "default": []
456
497
  }
457
- }
498
+ },
499
+ "x-display-field": "/grammar"
458
500
  },
459
501
  "HighlighterPreference": {
460
502
  "description": "Preference for which syntax highlighting backend to use",
@@ -476,6 +518,87 @@
476
518
  }
477
519
  ]
478
520
  },
521
+ "FormatterConfig": {
522
+ "description": "Formatter configuration for a language",
523
+ "type": "object",
524
+ "properties": {
525
+ "command": {
526
+ "description": "The formatter command to run (e.g., \"rustfmt\", \"prettier\")",
527
+ "type": "string"
528
+ },
529
+ "args": {
530
+ "description": "Arguments to pass to the formatter\nUse \"$FILE\" to include the file path",
531
+ "type": "array",
532
+ "items": {
533
+ "type": "string"
534
+ },
535
+ "default": []
536
+ },
537
+ "stdin": {
538
+ "description": "Whether to pass buffer content via stdin (default: true)\nMost formatters read from stdin and write to stdout",
539
+ "type": "boolean",
540
+ "default": true
541
+ },
542
+ "timeout_ms": {
543
+ "description": "Timeout in milliseconds (default: 10000)",
544
+ "type": "integer",
545
+ "format": "uint64",
546
+ "minimum": 0,
547
+ "default": 10000
548
+ }
549
+ },
550
+ "required": [
551
+ "command"
552
+ ],
553
+ "x-display-field": "/command"
554
+ },
555
+ "OnSaveAction": {
556
+ "description": "Action to run when a file is saved (for linters, etc.)",
557
+ "type": "object",
558
+ "properties": {
559
+ "command": {
560
+ "description": "The shell command to run\nThe file path is available as $FILE or as an argument",
561
+ "type": "string"
562
+ },
563
+ "args": {
564
+ "description": "Arguments to pass to the command\nUse \"$FILE\" to include the file path",
565
+ "type": "array",
566
+ "items": {
567
+ "type": "string"
568
+ },
569
+ "default": []
570
+ },
571
+ "working_dir": {
572
+ "description": "Working directory for the command (defaults to project root)",
573
+ "type": [
574
+ "string",
575
+ "null"
576
+ ],
577
+ "default": null
578
+ },
579
+ "stdin": {
580
+ "description": "Whether to use the buffer content as stdin",
581
+ "type": "boolean",
582
+ "default": false
583
+ },
584
+ "timeout_ms": {
585
+ "description": "Timeout in milliseconds (default: 10000)",
586
+ "type": "integer",
587
+ "format": "uint64",
588
+ "minimum": 0,
589
+ "default": 10000
590
+ },
591
+ "enabled": {
592
+ "description": "Whether this action is enabled (default: true)\nSet to false to disable an action without removing it from config",
593
+ "type": "boolean",
594
+ "default": true
595
+ }
596
+ },
597
+ "required": [
598
+ "command"
599
+ ],
600
+ "x-display-field": "/command"
601
+ },
479
602
  "LspServerConfig": {
480
603
  "description": "LSP server configuration",
481
604
  "type": "object",
@@ -518,7 +641,8 @@
518
641
  },
519
642
  "required": [
520
643
  "command"
521
- ]
644
+ ],
645
+ "x-display-field": "/command"
522
646
  },
523
647
  "ProcessLimits": {
524
648
  "description": "Configuration for process resource limits",
@@ -685,6 +809,17 @@
685
809
  ]
686
810
  }
687
811
  ]
812
+ },
813
+ "WarningsConfig": {
814
+ "description": "Warning notification configuration",
815
+ "type": "object",
816
+ "properties": {
817
+ "show_status_indicator": {
818
+ "description": "Show warning/error indicators in the status bar (default: true)\nWhen enabled, displays a colored indicator for LSP errors and other warnings",
819
+ "type": "boolean",
820
+ "default": true
821
+ }
822
+ }
688
823
  }
689
824
  }
690
825
  }
@@ -89,7 +89,8 @@ globalThis.onGitGrepPromptChanged = function(args: {
89
89
  }
90
90
 
91
91
  // Spawn git grep asynchronously
92
- editor.spawnProcess("git", ["grep", "-n", "--column", "-I", "--", query])
92
+ const cwd = editor.getCwd();
93
+ editor.spawnProcess("git", ["grep", "-n", "--column", "-I", "--", query], cwd)
93
94
  .then((result) => {
94
95
  if (result.exit_code === 0) {
95
96
  // Parse results and update suggestions
@@ -81,6 +81,16 @@ interface LayoutHints {
81
81
  column_guides?: number[] | null;
82
82
  }
83
83
 
84
+ /** Handle for a cancellable process spawned with spawnProcess */
85
+ interface ProcessHandle extends PromiseLike<SpawnResult> {
86
+ /** Promise that resolves to the process ID */
87
+ readonly processId: Promise<number>;
88
+ /** Promise that resolves to the result when the process completes */
89
+ readonly result: Promise<SpawnResult>;
90
+ /** Kill the process. Returns true if killed, false if already completed */
91
+ kill(): Promise<boolean>;
92
+ }
93
+
84
94
  /** Result from spawnProcess */
85
95
  interface SpawnResult {
86
96
  /** Complete stdout as string. Newlines preserved; trailing newline included. */
@@ -626,15 +636,31 @@ interface EditorAPI {
626
636
  */
627
637
  spawnBackgroundProcess(command: string, args: string[], cwd?: string | null): Promise<BackgroundProcessResult>;
628
638
  /**
629
- * Kill a background process by ID
639
+ * Kill a background or cancellable process by ID
630
640
  *
631
641
  * Sends SIGTERM to gracefully terminate the process.
632
642
  * Returns true if the process was found and killed, false if not found.
633
643
  *
634
- * @param process_id - ID returned from spawnBackgroundProcess
644
+ * @param process_id - ID returned from spawnBackgroundProcess or spawnProcessStart
635
645
  * @returns true if process was killed, false if not found
636
646
  */
637
647
  killProcess(#[bigint] process_id: number): Promise<boolean>;
648
+ /**
649
+ * Wait for a cancellable process to complete and get its result
650
+ *
651
+ * @param process_id - ID returned from spawnProcessStart
652
+ * @returns SpawnResult with stdout, stderr, and exit_code
653
+ */
654
+ spawnProcessWait(#[bigint] process_id: number): Promise<SpawnResult>;
655
+ /**
656
+ * Delay execution for a specified number of milliseconds
657
+ *
658
+ * Useful for debouncing user input or adding delays between operations.
659
+ * @param ms - Number of milliseconds to delay
660
+ * @example
661
+ * await editor.delay(100); // Wait 100ms
662
+ */
663
+ delay(#[bigint] ms: number): Promise<[]>;
638
664
  /**
639
665
  * Start a prompt with pre-filled initial value
640
666
  * @param label - Label to display (e.g., "Git grep: ")
@@ -672,24 +698,24 @@ interface EditorAPI {
672
698
  */
673
699
  setBufferCursor(buffer_id: number, position: number): boolean;
674
700
 
675
- // === Async Operations ===
676
701
  /**
677
- * Run an external command and capture its output
702
+ * Spawn an external process and return a cancellable handle
678
703
  *
679
- * Waits for process to complete before returning. For long-running processes,
680
- * consider if this will block your plugin. Output is captured completely;
681
- * very large outputs may use significant memory.
704
+ * Returns a ProcessHandle that can be awaited for the result or killed early.
705
+ * The handle is also a PromiseLike, so `await spawnProcess(...)` works directly.
682
706
  * @param command - Program name (searched in PATH) or absolute path
683
707
  * @param args - Command arguments (each array element is one argument)
684
708
  * @param cwd - Working directory; null uses editor's cwd
685
709
  * @example
686
- * const result = await editor.spawnProcess("git", ["log", "--oneline", "-5"]);
687
- * if (result.exit_code !== 0) {
688
- * editor.setStatus(`git failed: ${result.stderr}`);
689
- * }
710
+ * // Simple usage (backward compatible)
711
+ * const result = await editor.spawnProcess("git", ["status"]);
712
+ *
713
+ * // Cancellable usage
714
+ * const search = editor.spawnProcess("rg", ["pattern"]);
715
+ * // ... later, if user types new query:
716
+ * search.kill(); // Cancel the search
690
717
  */
691
- spawnProcess(command: string, args: string[], cwd?: string | null): Promise<SpawnResult>;
692
-
718
+ spawnProcess(command: string, args?: string[], cwd?: string | null): ProcessHandle;
693
719
  // === Overlay Operations ===
694
720
  /**
695
721
  * Add a colored highlight overlay to text without modifying content
@@ -22,8 +22,12 @@ let previewBufferId: number | null = null;
22
22
  let previewSplitId: number | null = null;
23
23
  let originalSplitId: number | null = null;
24
24
  let lastQuery: string = "";
25
- let searchDebounceTimer: number | null = null;
26
25
  let previewCreated: boolean = false;
26
+ let currentSearch: ProcessHandle | null = null;
27
+ let pendingKill: Promise<boolean> | null = null; // Track pending kill globally
28
+ let searchVersion = 0; // Incremented on each input change for debouncing
29
+
30
+ const DEBOUNCE_MS = 150; // Wait 150ms after last keystroke before searching
27
31
 
28
32
  // Parse ripgrep output line
29
33
  // Format: file:line:column:content
@@ -169,22 +173,62 @@ function closePreview(): void {
169
173
  }
170
174
  }
171
175
 
172
- // Run ripgrep search
176
+ // Run ripgrep search with debouncing
173
177
  async function runSearch(query: string): Promise<void> {
178
+ // Increment version to invalidate any pending debounced search
179
+ const thisVersion = ++searchVersion;
180
+ editor.debug(`[live_grep] runSearch called: query="${query}", version=${thisVersion}`);
181
+
182
+ // Kill any existing search immediately (don't wait) to stop wasting CPU
183
+ // Store the kill promise globally so ALL pending searches wait for it
184
+ if (currentSearch) {
185
+ editor.debug(`[live_grep] killing existing search immediately`);
186
+ pendingKill = currentSearch.kill();
187
+ currentSearch = null;
188
+ }
189
+
174
190
  if (!query || query.trim().length < 2) {
191
+ // Wait for any pending kill to complete before returning
192
+ if (pendingKill) {
193
+ await pendingKill;
194
+ pendingKill = null;
195
+ }
196
+ editor.debug(`[live_grep] query too short, clearing`);
175
197
  editor.setPromptSuggestions([]);
176
198
  grepResults = [];
177
199
  return;
178
200
  }
179
201
 
202
+ // Debounce: wait a bit to see if user is still typing
203
+ editor.debug(`[live_grep] debouncing for ${DEBOUNCE_MS}ms...`);
204
+ await editor.delay(DEBOUNCE_MS);
205
+
206
+ // Always await any pending kill before continuing - ensures old process is dead
207
+ if (pendingKill) {
208
+ editor.debug(`[live_grep] waiting for previous search to terminate`);
209
+ await pendingKill;
210
+ pendingKill = null;
211
+ editor.debug(`[live_grep] previous search terminated`);
212
+ }
213
+
214
+ // If version changed during delay, a newer search was triggered - abort this one
215
+ if (searchVersion !== thisVersion) {
216
+ editor.debug(`[live_grep] version mismatch after debounce (${thisVersion} vs ${searchVersion}), aborting`);
217
+ return;
218
+ }
219
+
180
220
  // Avoid duplicate searches
181
221
  if (query === lastQuery) {
222
+ editor.debug(`[live_grep] duplicate query, skipping`);
182
223
  return;
183
224
  }
184
225
  lastQuery = query;
185
226
 
186
227
  try {
187
- const result = await editor.spawnProcess("rg", [
228
+ const cwd = editor.getCwd();
229
+ editor.debug(`[live_grep] spawning rg for query="${query}" in cwd="${cwd}"`);
230
+ const searchStartTime = Date.now();
231
+ const search = editor.spawnProcess("rg", [
188
232
  "--line-number",
189
233
  "--column",
190
234
  "--no-heading",
@@ -197,10 +241,25 @@ async function runSearch(query: string): Promise<void> {
197
241
  "-g", "!*.lock",
198
242
  "--",
199
243
  query,
200
- ]);
244
+ ], cwd);
245
+
246
+ currentSearch = search;
247
+ editor.debug(`[live_grep] awaiting search result...`);
248
+ const result = await search;
249
+ const searchDuration = Date.now() - searchStartTime;
250
+ editor.debug(`[live_grep] rg completed in ${searchDuration}ms, exit_code=${result.exit_code}, stdout_len=${result.stdout.length}`);
251
+
252
+ // Check if this search was cancelled (a new search started)
253
+ if (currentSearch !== search) {
254
+ editor.debug(`[live_grep] search was superseded, discarding results`);
255
+ return; // Discard stale results
256
+ }
257
+ currentSearch = null;
201
258
 
202
259
  if (result.exit_code === 0) {
260
+ const parseStart = Date.now();
203
261
  const { results, suggestions } = parseRipgrepOutput(result.stdout);
262
+ editor.debug(`[live_grep] parsed ${results.length} results in ${Date.now() - parseStart}ms`);
204
263
  grepResults = results;
205
264
  editor.setPromptSuggestions(suggestions);
206
265
 
@@ -213,14 +272,24 @@ async function runSearch(query: string): Promise<void> {
213
272
  }
214
273
  } else if (result.exit_code === 1) {
215
274
  // No matches
275
+ editor.debug(`[live_grep] no matches (exit_code=1)`);
216
276
  grepResults = [];
217
277
  editor.setPromptSuggestions([]);
218
278
  editor.setStatus("No matches found");
279
+ } else if (result.exit_code === -1) {
280
+ // Process was killed, ignore
281
+ editor.debug(`[live_grep] process was killed`);
219
282
  } else {
283
+ editor.debug(`[live_grep] search error: ${result.stderr}`);
220
284
  editor.setStatus(`Search error: ${result.stderr}`);
221
285
  }
222
286
  } catch (e) {
223
- editor.setStatus(`Search error: ${e}`);
287
+ // Ignore errors from killed processes
288
+ const errorMsg = String(e);
289
+ editor.debug(`[live_grep] caught error: ${errorMsg}`);
290
+ if (!errorMsg.includes("killed") && !errorMsg.includes("not found")) {
291
+ editor.setStatus(`Search error: ${e}`);
292
+ }
224
293
  }
225
294
  }
226
295
 
@@ -248,12 +317,9 @@ globalThis.onLiveGrepPromptChanged = function (args: {
248
317
  return true;
249
318
  }
250
319
 
251
- // Debounce search to avoid too many requests while typing
252
- if (searchDebounceTimer !== null) {
253
- // Can't actually cancel in this runtime, but we track it
254
- }
320
+ editor.debug(`[live_grep] onPromptChanged: input="${args.input}"`);
255
321
 
256
- // Run search (with small delay effect via async)
322
+ // runSearch handles debouncing internally
257
323
  runSearch(args.input);
258
324
 
259
325
  return true;
@@ -286,6 +352,12 @@ globalThis.onLiveGrepPromptConfirmed = function (args: {
286
352
  return true;
287
353
  }
288
354
 
355
+ // Kill any running search
356
+ if (currentSearch) {
357
+ currentSearch.kill();
358
+ currentSearch = null;
359
+ }
360
+
289
361
  // Close preview first
290
362
  closePreview();
291
363
 
@@ -314,6 +386,12 @@ globalThis.onLiveGrepPromptCancelled = function (args: {
314
386
  return true;
315
387
  }
316
388
 
389
+ // Kill any running search
390
+ if (currentSearch) {
391
+ currentSearch.kill();
392
+ currentSearch = null;
393
+ }
394
+
317
395
  // Close preview and cleanup
318
396
  closePreview();
319
397
  grepResults = [];
@@ -185,7 +185,8 @@ async function performSearch(pattern: string, replace: string, isRegex: boolean)
185
185
  args.push("--", pattern);
186
186
 
187
187
  try {
188
- const result = await editor.spawnProcess("git", args);
188
+ const cwd = editor.getCwd();
189
+ const result = await editor.spawnProcess("git", args, cwd);
189
190
 
190
191
  searchResults = [];
191
192
 
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "dracula",
3
+ "editor": {
4
+ "bg": [40, 42, 54],
5
+ "fg": [248, 248, 242],
6
+ "cursor": [255, 121, 198],
7
+ "selection_bg": [68, 71, 90],
8
+ "current_line_bg": [68, 71, 90],
9
+ "line_number_fg": [98, 114, 164],
10
+ "line_number_bg": [40, 42, 54]
11
+ },
12
+ "ui": {
13
+ "tab_active_fg": [248, 248, 242],
14
+ "tab_active_bg": [189, 147, 249],
15
+ "tab_inactive_fg": [248, 248, 242],
16
+ "tab_inactive_bg": [68, 71, 90],
17
+ "tab_separator_bg": [40, 42, 54],
18
+ "status_bar_fg": [40, 42, 54],
19
+ "status_bar_bg": [189, 147, 249],
20
+ "prompt_fg": [40, 42, 54],
21
+ "prompt_bg": [80, 250, 123],
22
+ "prompt_selection_fg": [248, 248, 242],
23
+ "prompt_selection_bg": [189, 147, 249],
24
+ "popup_border_fg": [98, 114, 164],
25
+ "popup_bg": [68, 71, 90],
26
+ "popup_selection_bg": [189, 147, 249],
27
+ "popup_text_fg": [248, 248, 242],
28
+ "suggestion_bg": [68, 71, 90],
29
+ "suggestion_selected_bg": [189, 147, 249],
30
+ "help_bg": [40, 42, 54],
31
+ "help_fg": [248, 248, 242],
32
+ "help_key_fg": [139, 233, 253],
33
+ "help_separator_fg": [98, 114, 164],
34
+ "help_indicator_fg": [255, 85, 85],
35
+ "help_indicator_bg": [40, 42, 54],
36
+ "split_separator_fg": [98, 114, 164]
37
+ },
38
+ "search": {
39
+ "match_bg": [241, 250, 140],
40
+ "match_fg": [40, 42, 54]
41
+ },
42
+ "diagnostic": {
43
+ "error_fg": [255, 85, 85],
44
+ "error_bg": [64, 42, 54],
45
+ "warning_fg": [241, 250, 140],
46
+ "warning_bg": [64, 60, 42],
47
+ "info_fg": [139, 233, 253],
48
+ "info_bg": [40, 56, 70],
49
+ "hint_fg": [98, 114, 164],
50
+ "hint_bg": [40, 42, 54]
51
+ },
52
+ "syntax": {
53
+ "keyword": [255, 121, 198],
54
+ "string": [241, 250, 140],
55
+ "comment": [98, 114, 164],
56
+ "function": [80, 250, 123],
57
+ "type": [139, 233, 253],
58
+ "variable": [248, 248, 242],
59
+ "constant": [189, 147, 249],
60
+ "operator": [255, 121, 198]
61
+ }
62
+ }
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "nord",
3
+ "editor": {
4
+ "bg": [46, 52, 64],
5
+ "fg": [216, 222, 233],
6
+ "cursor": [136, 192, 208],
7
+ "selection_bg": [67, 76, 94],
8
+ "current_line_bg": [59, 66, 82],
9
+ "line_number_fg": [76, 86, 106],
10
+ "line_number_bg": [46, 52, 64]
11
+ },
12
+ "ui": {
13
+ "tab_active_fg": [236, 239, 244],
14
+ "tab_active_bg": [67, 76, 94],
15
+ "tab_inactive_fg": [216, 222, 233],
16
+ "tab_inactive_bg": [59, 66, 82],
17
+ "tab_separator_bg": [46, 52, 64],
18
+ "status_bar_fg": [46, 52, 64],
19
+ "status_bar_bg": [136, 192, 208],
20
+ "prompt_fg": [46, 52, 64],
21
+ "prompt_bg": [163, 190, 140],
22
+ "prompt_selection_fg": [236, 239, 244],
23
+ "prompt_selection_bg": [94, 129, 172],
24
+ "popup_border_fg": [76, 86, 106],
25
+ "popup_bg": [59, 66, 82],
26
+ "popup_selection_bg": [94, 129, 172],
27
+ "popup_text_fg": [216, 222, 233],
28
+ "suggestion_bg": [59, 66, 82],
29
+ "suggestion_selected_bg": [94, 129, 172],
30
+ "help_bg": [46, 52, 64],
31
+ "help_fg": [216, 222, 233],
32
+ "help_key_fg": [136, 192, 208],
33
+ "help_separator_fg": [76, 86, 106],
34
+ "help_indicator_fg": [191, 97, 106],
35
+ "help_indicator_bg": [46, 52, 64],
36
+ "split_separator_fg": [76, 86, 106]
37
+ },
38
+ "search": {
39
+ "match_bg": [235, 203, 139],
40
+ "match_fg": [46, 52, 64]
41
+ },
42
+ "diagnostic": {
43
+ "error_fg": [191, 97, 106],
44
+ "error_bg": [59, 46, 50],
45
+ "warning_fg": [235, 203, 139],
46
+ "warning_bg": [59, 56, 46],
47
+ "info_fg": [129, 161, 193],
48
+ "info_bg": [46, 52, 64],
49
+ "hint_fg": [76, 86, 106],
50
+ "hint_bg": [46, 52, 64]
51
+ },
52
+ "syntax": {
53
+ "keyword": [129, 161, 193],
54
+ "string": [163, 190, 140],
55
+ "comment": [76, 86, 106],
56
+ "function": [136, 192, 208],
57
+ "type": [143, 188, 187],
58
+ "variable": [216, 222, 233],
59
+ "constant": [180, 142, 173],
60
+ "operator": [129, 161, 193]
61
+ }
62
+ }
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "solarized-dark",
3
+ "editor": {
4
+ "bg": [0, 43, 54],
5
+ "fg": [131, 148, 150],
6
+ "cursor": [38, 139, 210],
7
+ "selection_bg": [7, 54, 66],
8
+ "current_line_bg": [7, 54, 66],
9
+ "line_number_fg": [88, 110, 117],
10
+ "line_number_bg": [0, 43, 54]
11
+ },
12
+ "ui": {
13
+ "tab_active_fg": [253, 246, 227],
14
+ "tab_active_bg": [38, 139, 210],
15
+ "tab_inactive_fg": [131, 148, 150],
16
+ "tab_inactive_bg": [7, 54, 66],
17
+ "tab_separator_bg": [0, 43, 54],
18
+ "status_bar_fg": [0, 43, 54],
19
+ "status_bar_bg": [147, 161, 161],
20
+ "prompt_fg": [0, 43, 54],
21
+ "prompt_bg": [181, 137, 0],
22
+ "prompt_selection_fg": [253, 246, 227],
23
+ "prompt_selection_bg": [38, 139, 210],
24
+ "popup_border_fg": [88, 110, 117],
25
+ "popup_bg": [7, 54, 66],
26
+ "popup_selection_bg": [38, 139, 210],
27
+ "popup_text_fg": [131, 148, 150],
28
+ "suggestion_bg": [7, 54, 66],
29
+ "suggestion_selected_bg": [38, 139, 210],
30
+ "help_bg": [0, 43, 54],
31
+ "help_fg": [131, 148, 150],
32
+ "help_key_fg": [42, 161, 152],
33
+ "help_separator_fg": [88, 110, 117],
34
+ "help_indicator_fg": [220, 50, 47],
35
+ "help_indicator_bg": [0, 43, 54],
36
+ "split_separator_fg": [88, 110, 117]
37
+ },
38
+ "search": {
39
+ "match_bg": [181, 137, 0],
40
+ "match_fg": [253, 246, 227]
41
+ },
42
+ "diagnostic": {
43
+ "error_fg": [220, 50, 47],
44
+ "error_bg": [42, 43, 54],
45
+ "warning_fg": [181, 137, 0],
46
+ "warning_bg": [30, 54, 54],
47
+ "info_fg": [38, 139, 210],
48
+ "info_bg": [0, 50, 66],
49
+ "hint_fg": [88, 110, 117],
50
+ "hint_bg": [0, 43, 54]
51
+ },
52
+ "syntax": {
53
+ "keyword": [133, 153, 0],
54
+ "string": [42, 161, 152],
55
+ "comment": [88, 110, 117],
56
+ "function": [38, 139, 210],
57
+ "type": [181, 137, 0],
58
+ "variable": [131, 148, 150],
59
+ "constant": [203, 75, 22],
60
+ "operator": [131, 148, 150]
61
+ }
62
+ }