@fresh-editor/fresh-editor 0.1.59 → 0.1.63
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 +48 -0
- package/binary-install.js +26 -2
- package/package.json +2 -1
- package/plugins/config-schema.json +73 -4
- package/plugins/git_grep.ts +2 -1
- package/plugins/lib/fresh.d.ts +39 -13
- package/plugins/live_grep.ts +88 -10
- package/plugins/search_replace.ts +2 -1
- package/themes/dracula.json +62 -0
- package/themes/nord.json +62 -0
- package/themes/solarized-dark.json +62 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# Release Notes
|
|
2
2
|
|
|
3
|
+
## 0.1.63
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* **Shell Command Prompt**: Pipe buffer or selection through shell commands (Alt+|).
|
|
8
|
+
|
|
9
|
+
* **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).
|
|
10
|
+
|
|
11
|
+
* **Stdin Input**: Pipe content via stdin with background streaming (`echo "hello" | fresh -`).
|
|
12
|
+
|
|
13
|
+
* **Multi-File CLI**: Open multiple files from command line (#389).
|
|
14
|
+
|
|
15
|
+
* **Tab Indent Selection**: Tab indents selected lines, Shift+Tab dedents (#353).
|
|
16
|
+
|
|
17
|
+
* **Toggle Menu Bar**: Hide/show menu bar via command palette for extra screen space.
|
|
18
|
+
|
|
19
|
+
* **Global File Positions**: Cursor/scroll positions stored globally per file, not per project (#423).
|
|
20
|
+
|
|
21
|
+
* **Relative Line Numbers**: Show relative distances from cursor in gutter for easier vim-style navigation. Enable via `relative_line_numbers` config (#454).
|
|
22
|
+
|
|
23
|
+
### Bug Fixes
|
|
24
|
+
|
|
25
|
+
* **On-Save Missing Tools**: Graceful handling when formatter/linter command not found.
|
|
26
|
+
|
|
27
|
+
* **Settings UI Nested Dialogs**: Fixed nested ObjectArray navigation and save not persisting (e.g., editing on_save inside language config).
|
|
28
|
+
|
|
29
|
+
* **Live Grep Working Directory**: Fixed search plugins using process cwd instead of project working directory.
|
|
30
|
+
|
|
31
|
+
* **Open File Path Resolution**: Fixed relative paths resolving incorrectly when editor launched from different directory.
|
|
32
|
+
|
|
33
|
+
### Performance
|
|
34
|
+
|
|
35
|
+
* **Live Grep UI**: Fixed UI freezing for seconds during large codebase searches by making plugin event loop non-blocking.
|
|
36
|
+
|
|
37
|
+
### Internal
|
|
38
|
+
|
|
39
|
+
* Embedded plugins in binary as fallback for cargo-binstall (#416).
|
|
40
|
+
|
|
41
|
+
* Removed duplicate theme JSON files (#438).
|
|
42
|
+
|
|
43
|
+
* Extracted modules from mod.rs (file_operations, split_actions, clipboard, etc.).
|
|
44
|
+
|
|
45
|
+
* Pinned Rust 1.92 via rust-toolchain.toml (#338).
|
|
46
|
+
|
|
47
|
+
* Windows build switched from MSVC to GNU target.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
3
51
|
## 0.1.59
|
|
4
52
|
|
|
5
53
|
### 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
|
-
|
|
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',
|
|
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.
|
|
3
|
+
"version": "0.1.63",
|
|
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"
|
|
@@ -330,7 +330,8 @@
|
|
|
330
330
|
},
|
|
331
331
|
"required": [
|
|
332
332
|
"action"
|
|
333
|
-
]
|
|
333
|
+
],
|
|
334
|
+
"x-display-field": "/action"
|
|
334
335
|
},
|
|
335
336
|
"KeyPress": {
|
|
336
337
|
"description": "A single key in a sequence",
|
|
@@ -372,7 +373,8 @@
|
|
|
372
373
|
},
|
|
373
374
|
"default": []
|
|
374
375
|
}
|
|
375
|
-
}
|
|
376
|
+
},
|
|
377
|
+
"x-display-field": "/inherits"
|
|
376
378
|
},
|
|
377
379
|
"KeybindingMapOptions": {
|
|
378
380
|
"description": "Available keybinding maps",
|
|
@@ -453,8 +455,17 @@
|
|
|
453
455
|
"format": "uint",
|
|
454
456
|
"minimum": 0,
|
|
455
457
|
"default": null
|
|
458
|
+
},
|
|
459
|
+
"on_save": {
|
|
460
|
+
"description": "Actions to run when a file of this language is saved\nActions are run in order; if any fails (non-zero exit), subsequent actions don't run",
|
|
461
|
+
"type": "array",
|
|
462
|
+
"items": {
|
|
463
|
+
"$ref": "#/$defs/OnSaveAction"
|
|
464
|
+
},
|
|
465
|
+
"default": []
|
|
456
466
|
}
|
|
457
|
-
}
|
|
467
|
+
},
|
|
468
|
+
"x-display-field": "/grammar"
|
|
458
469
|
},
|
|
459
470
|
"HighlighterPreference": {
|
|
460
471
|
"description": "Preference for which syntax highlighting backend to use",
|
|
@@ -476,6 +487,63 @@
|
|
|
476
487
|
}
|
|
477
488
|
]
|
|
478
489
|
},
|
|
490
|
+
"OnSaveAction": {
|
|
491
|
+
"description": "Action to run when a file is saved",
|
|
492
|
+
"type": "object",
|
|
493
|
+
"properties": {
|
|
494
|
+
"command": {
|
|
495
|
+
"description": "The shell command to run\nThe file path is available as $FILE or as an argument",
|
|
496
|
+
"type": "string"
|
|
497
|
+
},
|
|
498
|
+
"args": {
|
|
499
|
+
"description": "Arguments to pass to the command\nUse \"$FILE\" to include the file path",
|
|
500
|
+
"type": "array",
|
|
501
|
+
"items": {
|
|
502
|
+
"type": "string"
|
|
503
|
+
},
|
|
504
|
+
"default": []
|
|
505
|
+
},
|
|
506
|
+
"working_dir": {
|
|
507
|
+
"description": "Working directory for the command (defaults to project root)",
|
|
508
|
+
"type": [
|
|
509
|
+
"string",
|
|
510
|
+
"null"
|
|
511
|
+
],
|
|
512
|
+
"default": null
|
|
513
|
+
},
|
|
514
|
+
"stdin": {
|
|
515
|
+
"description": "Whether to use the buffer content as stdin",
|
|
516
|
+
"type": "boolean",
|
|
517
|
+
"default": false
|
|
518
|
+
},
|
|
519
|
+
"replace_buffer": {
|
|
520
|
+
"description": "Whether to replace the buffer with the command's stdout\nUseful for formatters",
|
|
521
|
+
"type": "boolean",
|
|
522
|
+
"default": false
|
|
523
|
+
},
|
|
524
|
+
"timeout_ms": {
|
|
525
|
+
"description": "Timeout in milliseconds (default: 10000)",
|
|
526
|
+
"type": "integer",
|
|
527
|
+
"format": "uint64",
|
|
528
|
+
"minimum": 0,
|
|
529
|
+
"default": 10000
|
|
530
|
+
},
|
|
531
|
+
"optional": {
|
|
532
|
+
"description": "Whether this action is optional (won't error if command not found)\nUseful for default formatters that may not be installed\nWhen true, shows a status message instead of an error if command is missing",
|
|
533
|
+
"type": "boolean",
|
|
534
|
+
"default": false
|
|
535
|
+
},
|
|
536
|
+
"enabled": {
|
|
537
|
+
"description": "Whether this action is enabled (default: true)\nSet to false to disable an action without removing it from config",
|
|
538
|
+
"type": "boolean",
|
|
539
|
+
"default": true
|
|
540
|
+
}
|
|
541
|
+
},
|
|
542
|
+
"required": [
|
|
543
|
+
"command"
|
|
544
|
+
],
|
|
545
|
+
"x-display-field": "/command"
|
|
546
|
+
},
|
|
479
547
|
"LspServerConfig": {
|
|
480
548
|
"description": "LSP server configuration",
|
|
481
549
|
"type": "object",
|
|
@@ -518,7 +586,8 @@
|
|
|
518
586
|
},
|
|
519
587
|
"required": [
|
|
520
588
|
"command"
|
|
521
|
-
]
|
|
589
|
+
],
|
|
590
|
+
"x-display-field": "/command"
|
|
522
591
|
},
|
|
523
592
|
"ProcessLimits": {
|
|
524
593
|
"description": "Configuration for process resource limits",
|
package/plugins/git_grep.ts
CHANGED
|
@@ -89,7 +89,8 @@ globalThis.onGitGrepPromptChanged = function(args: {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
// Spawn git grep asynchronously
|
|
92
|
-
editor.
|
|
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
|
package/plugins/lib/fresh.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
702
|
+
* Spawn an external process and return a cancellable handle
|
|
678
703
|
*
|
|
679
|
-
*
|
|
680
|
-
*
|
|
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
|
-
*
|
|
687
|
-
*
|
|
688
|
-
*
|
|
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
|
|
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
|
package/plugins/live_grep.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
+
}
|
package/themes/nord.json
ADDED
|
@@ -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
|
+
}
|