diffwatch 1.0.1 → 1.0.3
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/dist/index.js +92 -14
- package/dist/index.js.map +1 -1
- package/dist/utils/diff-formatter.d.ts +1 -1
- package/dist/utils/diff-formatter.js +12 -4
- package/dist/utils/diff-formatter.js.map +1 -1
- package/dist/utils/git.d.ts +3 -1
- package/dist/utils/git.js +68 -1
- package/dist/utils/git.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +223 -137
- package/src/utils/diff-formatter.ts +13 -4
- package/src/utils/git.ts +67 -2
- package/tests/git.test.ts +48 -1
package/dist/index.js
CHANGED
|
@@ -70,8 +70,29 @@ function main() {
|
|
|
70
70
|
border: { type: 'line' },
|
|
71
71
|
tags: false,
|
|
72
72
|
});
|
|
73
|
+
const searchBox = blessed.box({
|
|
74
|
+
top: 'center',
|
|
75
|
+
left: 'center',
|
|
76
|
+
width: '50%',
|
|
77
|
+
height: 3,
|
|
78
|
+
label: ' Search ',
|
|
79
|
+
border: { type: 'line' },
|
|
80
|
+
style: { border: { fg: 'yellow' } },
|
|
81
|
+
hidden: true,
|
|
82
|
+
});
|
|
83
|
+
const searchInput = blessed.textbox({
|
|
84
|
+
parent: searchBox,
|
|
85
|
+
top: 0,
|
|
86
|
+
left: 0,
|
|
87
|
+
width: '100%-2',
|
|
88
|
+
height: 1,
|
|
89
|
+
keys: true,
|
|
90
|
+
inputOnFocus: true,
|
|
91
|
+
style: { fg: 'white', bg: 'black' },
|
|
92
|
+
});
|
|
73
93
|
screen.append(fileList);
|
|
74
94
|
screen.append(diffView);
|
|
95
|
+
screen.append(searchBox);
|
|
75
96
|
const updateBorders = () => {
|
|
76
97
|
fileList.style.border.fg = screen.focused === fileList ? 'yellow' : 'white';
|
|
77
98
|
diffView.style.border.fg = screen.focused === diffView ? 'yellow' : 'white';
|
|
@@ -80,6 +101,7 @@ function main() {
|
|
|
80
101
|
let currentFiles = [];
|
|
81
102
|
let lastSelectedPath = null;
|
|
82
103
|
let diffUpdateTimeout = null;
|
|
104
|
+
let currentSearchTerm = '';
|
|
83
105
|
const scheduleDiffUpdate = () => {
|
|
84
106
|
if (diffUpdateTimeout)
|
|
85
107
|
clearTimeout(diffUpdateTimeout);
|
|
@@ -107,17 +129,33 @@ function main() {
|
|
|
107
129
|
const selectedIndex = fileList.selected;
|
|
108
130
|
const selectedFile = currentFiles[selectedIndex];
|
|
109
131
|
if (selectedFile) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
132
|
+
let content = '';
|
|
133
|
+
let label = ` Diff (${selectedFile.path}) `;
|
|
134
|
+
if (selectedFile.status !== 'unchanged' && selectedFile.status !== 'deleted') {
|
|
135
|
+
const diff = yield gitHandler.getDiff(selectedFile.path);
|
|
136
|
+
content = (0, diff_formatter_1.formatDiffWithDiff2Html)(diff, currentSearchTerm);
|
|
137
|
+
}
|
|
138
|
+
if (!content && selectedFile.status !== 'deleted') {
|
|
139
|
+
content = yield gitHandler.getFileContent(selectedFile.path);
|
|
140
|
+
// Highlight search term in full content
|
|
141
|
+
if (currentSearchTerm) {
|
|
142
|
+
const regex = new RegExp(`(${currentSearchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
|
|
143
|
+
content = content.replace(regex, `\x1b[43m\x1b[30m$1\x1b[0m`);
|
|
144
|
+
}
|
|
145
|
+
label = ` File (${selectedFile.path}) `;
|
|
146
|
+
}
|
|
147
|
+
else if (!content && selectedFile.status === 'deleted') {
|
|
148
|
+
content = chalk_1.default.red('File was deleted.');
|
|
149
|
+
label = ` Diff (${selectedFile.path}) `;
|
|
150
|
+
}
|
|
113
151
|
const currentContent = diffView.content;
|
|
114
152
|
const currentLabel = diffView.label;
|
|
115
153
|
// Only update if content or label changed to reduce flickering
|
|
116
|
-
if (
|
|
154
|
+
if (content !== currentContent || label !== currentLabel) {
|
|
117
155
|
const savedScroll = diffView.scrollTop;
|
|
118
156
|
const isNewFile = selectedFile.path !== lastSelectedPath;
|
|
119
|
-
diffView.setContent(
|
|
120
|
-
diffView.setLabel(
|
|
157
|
+
diffView.setContent(content);
|
|
158
|
+
diffView.setLabel(label);
|
|
121
159
|
if (isNewFile) {
|
|
122
160
|
diffView.scrollTo(0);
|
|
123
161
|
}
|
|
@@ -143,9 +181,14 @@ function main() {
|
|
|
143
181
|
var _a;
|
|
144
182
|
// Preserve selected file path and scroll positions
|
|
145
183
|
const selectedPath = (_a = currentFiles[fileList.selected]) === null || _a === void 0 ? void 0 : _a.path;
|
|
146
|
-
const fileListScroll = fileList.scroll;
|
|
147
184
|
const diffScroll = diffView.scrollTop;
|
|
148
|
-
|
|
185
|
+
let files;
|
|
186
|
+
if (currentSearchTerm) {
|
|
187
|
+
files = yield gitHandler.searchFiles(currentSearchTerm);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
files = yield gitHandler.getStatus();
|
|
191
|
+
}
|
|
149
192
|
currentFiles = files;
|
|
150
193
|
const items = files.map(f => {
|
|
151
194
|
let color = '{white-fg}';
|
|
@@ -157,10 +200,13 @@ function main() {
|
|
|
157
200
|
color = '{blue-fg}';
|
|
158
201
|
else if (f.status === 'unstaged')
|
|
159
202
|
color = '{white-fg}';
|
|
203
|
+
else if (f.status === 'unchanged')
|
|
204
|
+
color = '{grey-fg}';
|
|
160
205
|
return `${color}${f.path}{/}`;
|
|
161
206
|
});
|
|
162
207
|
fileList.setItems(items);
|
|
163
|
-
|
|
208
|
+
const labelTitle = currentSearchTerm ? ` Files (${files.length}) - Searching: "${currentSearchTerm}" ` : ` Files (${files.length}) `;
|
|
209
|
+
fileList.setLabel(labelTitle);
|
|
164
210
|
if (items.length > 0) {
|
|
165
211
|
// Restore selection by path if possible
|
|
166
212
|
const newSelectedIndex = selectedPath ? currentFiles.findIndex(f => f.path === selectedPath) : -1;
|
|
@@ -173,11 +219,17 @@ function main() {
|
|
|
173
219
|
yield updateDiff();
|
|
174
220
|
}
|
|
175
221
|
else {
|
|
176
|
-
diffView.setContent('No changes detected.');
|
|
222
|
+
diffView.setContent(currentSearchTerm ? `No files match "${currentSearchTerm}".` : 'No changes detected.');
|
|
177
223
|
diffView.setLabel(' Diff () ');
|
|
178
224
|
}
|
|
179
|
-
// Restore scroll positions
|
|
180
|
-
|
|
225
|
+
// Restore scroll positions if reasonably possible (reset if list changed drastically)
|
|
226
|
+
// Actually, if we filter, the scroll position might be invalid.
|
|
227
|
+
// Ideally we keep it 0 if it was 0 or just let the select() call handle scrolling to the item.
|
|
228
|
+
// The previous implementation blindly restored scrollTop.
|
|
229
|
+
// If the list shrunk, select() should have brought it into view.
|
|
230
|
+
// We only explicitly restore if items.length > 0
|
|
231
|
+
// But setting scroll to previous value might be wrong if the list is now shorter.
|
|
232
|
+
// Safe to only restore diffView scroll as it depends on content, fileList is handled by select.
|
|
181
233
|
diffView.scrollTop = diffScroll;
|
|
182
234
|
screen.render();
|
|
183
235
|
});
|
|
@@ -188,8 +240,34 @@ function main() {
|
|
|
188
240
|
scheduleDiffUpdate();
|
|
189
241
|
});
|
|
190
242
|
screen.key(['escape', 'q', 'C-c'], () => {
|
|
191
|
-
|
|
192
|
-
|
|
243
|
+
if (!searchBox.hidden) {
|
|
244
|
+
searchBox.hide();
|
|
245
|
+
screen.render();
|
|
246
|
+
fileList.focus();
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
screen.destroy();
|
|
250
|
+
process.exit(0);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
screen.key(['s'], () => {
|
|
254
|
+
searchBox.show();
|
|
255
|
+
searchBox.setFront();
|
|
256
|
+
searchInput.setValue(currentSearchTerm);
|
|
257
|
+
searchInput.focus();
|
|
258
|
+
screen.render();
|
|
259
|
+
});
|
|
260
|
+
searchInput.on('submit', (value) => __awaiter(this, void 0, void 0, function* () {
|
|
261
|
+
currentSearchTerm = (value || '').trim();
|
|
262
|
+
searchBox.hide();
|
|
263
|
+
fileList.focus();
|
|
264
|
+
// Force immediate update
|
|
265
|
+
yield updateFileList();
|
|
266
|
+
}));
|
|
267
|
+
searchInput.on('cancel', () => {
|
|
268
|
+
searchBox.hide();
|
|
269
|
+
fileList.focus();
|
|
270
|
+
screen.render();
|
|
193
271
|
});
|
|
194
272
|
screen.key(['tab'], () => {
|
|
195
273
|
if (screen.focused === fileList) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AACA,MAAM,OAAO,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAC3C,kDAA0B;AAC1B,iDAAsC;AACtC,qCAAqD;AACrD,2DAAiE;AAEjE,SAAe,IAAI;;QACjB,MAAM,UAAU,GAAG,IAAI,gBAAU,EAAE,CAAC;QAEpC,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,GAAG,CAAC,mDAAmD,CAAC,CAAC,CAAC;YAC5E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YAC5B,QAAQ,EAAE,IAAI;YACd,KAAK,EAAE,WAAW;SACnB,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;YAC5B,GAAG,EAAE,CAAC;YACN,IAAI,EAAE,CAAC;YACP,KAAK,EAAE,KAAK;YACZ,MAAM,EAAE,MAAM;YACd,KAAK,EAAE,aAAa;YACpB,IAAI,EAAE,IAAI;YACV,EAAE,EAAE,IAAI;YACR,KAAK,EAAE,IAAI;YACX,IAAI,EAAE,IAAI;YACV,SAAS,EAAE;gBACT,EAAE,EAAE,GAAG;gBACP,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE;gBACtB,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE;aACtB;YACD,KAAK,EAAE;gBACL,QAAQ,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE;gBACtC,MAAM,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE;aACxB;YACD,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;SACzB,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC;YACtC,GAAG,EAAE,CAAC;YACN,IAAI,EAAE,KAAK;YACX,KAAK,EAAE,KAAK;YACZ,MAAM,EAAE,MAAM;YACd,KAAK,EAAE,WAAW;YAClB,IAAI,EAAE,IAAI;YACV,EAAE,EAAE,IAAI;YACR,KAAK,EAAE,IAAI;YACX,SAAS,EAAE;gBACT,EAAE,EAAE,GAAG;gBACP,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE;gBACtB,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE;aACtB;YACD,KAAK,EAAE;gBACL,MAAM,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE;aACxB;YACD,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;YACxB,IAAI,EAAE,KAAK;SACZ,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACxB,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function formatDiffWithDiff2Html(diffString: string): string;
|
|
1
|
+
export declare function formatDiffWithDiff2Html(diffString: string, searchTerm?: string): string;
|
|
@@ -36,9 +36,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.formatDiffWithDiff2Html = formatDiffWithDiff2Html;
|
|
37
37
|
const cheerio = __importStar(require("cheerio"));
|
|
38
38
|
const Diff2Html = require('diff2html');
|
|
39
|
-
function formatDiffWithDiff2Html(diffString) {
|
|
39
|
+
function formatDiffWithDiff2Html(diffString, searchTerm) {
|
|
40
40
|
if (!diffString || diffString.trim() === '') {
|
|
41
|
-
return '
|
|
41
|
+
return '';
|
|
42
42
|
}
|
|
43
43
|
try {
|
|
44
44
|
const html = Diff2Html.html(diffString, {
|
|
@@ -69,7 +69,15 @@ function formatDiffWithDiff2Html(diffString) {
|
|
|
69
69
|
else {
|
|
70
70
|
content = $lineWrapper.text().trim();
|
|
71
71
|
}
|
|
72
|
-
|
|
72
|
+
let fullLine = prefix + content;
|
|
73
|
+
// Highlight search term if present
|
|
74
|
+
if (searchTerm && fullLine.toLowerCase().includes(searchTerm.toLowerCase())) {
|
|
75
|
+
const regex = new RegExp(`(${searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
|
|
76
|
+
// We need to know the base color to restore it.
|
|
77
|
+
// Added: 32, Deleted: 31, Normal: 37
|
|
78
|
+
const baseColor = isAdded ? '32' : (isDeleted ? '31' : '37');
|
|
79
|
+
fullLine = fullLine.replace(regex, `\x1b[43m\x1b[30m$1\x1b[0m\x1b[${baseColor}m`);
|
|
80
|
+
}
|
|
73
81
|
if (isAdded) {
|
|
74
82
|
blessedText += `\x1b[32m${fullLine}\x1b[0m\n`;
|
|
75
83
|
}
|
|
@@ -85,7 +93,7 @@ function formatDiffWithDiff2Html(diffString) {
|
|
|
85
93
|
blessedText += `\x1b[36m${hunkText}\x1b[0m\n`;
|
|
86
94
|
}
|
|
87
95
|
});
|
|
88
|
-
return blessedText.trim()
|
|
96
|
+
return blessedText.trim();
|
|
89
97
|
}
|
|
90
98
|
catch (error) {
|
|
91
99
|
return `Error formatting diff: ${error}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff-formatter.js","sourceRoot":"","sources":["../../src/utils/diff-formatter.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGA,
|
|
1
|
+
{"version":3,"file":"diff-formatter.js","sourceRoot":"","sources":["../../src/utils/diff-formatter.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/git.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export interface FileStatus {
|
|
2
2
|
path: string;
|
|
3
|
-
status: 'modified' | 'added' | 'deleted' | 'unstaged' | 'unknown';
|
|
3
|
+
status: 'modified' | 'added' | 'deleted' | 'unstaged' | 'unknown' | 'unchanged';
|
|
4
4
|
mtime?: Date;
|
|
5
5
|
}
|
|
6
6
|
export declare class GitHandler {
|
|
@@ -8,5 +8,7 @@ export declare class GitHandler {
|
|
|
8
8
|
constructor(workingDir?: string);
|
|
9
9
|
isRepo(): Promise<boolean>;
|
|
10
10
|
getStatus(): Promise<FileStatus[]>;
|
|
11
|
+
searchFiles(term: string): Promise<FileStatus[]>;
|
|
11
12
|
getDiff(filePath: string): Promise<string>;
|
|
13
|
+
getFileContent(filePath: string): Promise<string>;
|
|
12
14
|
}
|
package/dist/utils/git.js
CHANGED
|
@@ -74,6 +74,63 @@ class GitHandler {
|
|
|
74
74
|
});
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
|
+
searchFiles(term) {
|
|
78
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
79
|
+
if (!term || !term.trim())
|
|
80
|
+
return [];
|
|
81
|
+
try {
|
|
82
|
+
// Use git grep to find files containing the term
|
|
83
|
+
// -i: ignore case
|
|
84
|
+
// -l: list filenames only
|
|
85
|
+
// -F: interpret pattern as a fixed string
|
|
86
|
+
// --untracked: include untracked files
|
|
87
|
+
const result = yield this.git.raw(['grep', '-i', '-l', '-F', '--untracked', term.trim()]);
|
|
88
|
+
const matchedPaths = [...new Set(result.split('\n').map(p => p.trim()).filter(p => p !== ''))];
|
|
89
|
+
if (matchedPaths.length === 0)
|
|
90
|
+
return [];
|
|
91
|
+
// Get current status to identify which matched files are changed
|
|
92
|
+
const changedFiles = yield this.getStatus();
|
|
93
|
+
const changedMap = new Map();
|
|
94
|
+
changedFiles.forEach(f => changedMap.set(f.path, f));
|
|
95
|
+
const finalResults = [];
|
|
96
|
+
for (const path of matchedPaths) {
|
|
97
|
+
if (changedMap.has(path)) {
|
|
98
|
+
finalResults.push(changedMap.get(path));
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
// It's an unchanged file
|
|
102
|
+
let mtime = new Date(0);
|
|
103
|
+
try {
|
|
104
|
+
const stat = yield promises_1.default.stat(path);
|
|
105
|
+
mtime = stat.mtime;
|
|
106
|
+
}
|
|
107
|
+
catch (_a) { }
|
|
108
|
+
finalResults.push({
|
|
109
|
+
path,
|
|
110
|
+
status: 'unchanged',
|
|
111
|
+
mtime
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Sort results by mtime descending
|
|
116
|
+
return finalResults.sort((a, b) => {
|
|
117
|
+
const mtimeA = a.mtime || new Date(0);
|
|
118
|
+
const mtimeB = b.mtime || new Date(0);
|
|
119
|
+
const timeDiff = mtimeB.getTime() - mtimeA.getTime();
|
|
120
|
+
if (timeDiff !== 0)
|
|
121
|
+
return timeDiff;
|
|
122
|
+
return a.path.localeCompare(b.path);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
// git grep returns exit code 1 if no matches are found, which simple-git might throw as an error
|
|
127
|
+
if (error.message && (error.message.includes('exit code 1') || error.exitCode === 1)) {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
77
134
|
getDiff(filePath) {
|
|
78
135
|
return __awaiter(this, void 0, void 0, function* () {
|
|
79
136
|
try {
|
|
@@ -84,13 +141,23 @@ class GitHandler {
|
|
|
84
141
|
});
|
|
85
142
|
}
|
|
86
143
|
const diff = yield this.git.diff(['HEAD', '--', filePath]);
|
|
87
|
-
return diff || '
|
|
144
|
+
return diff || '';
|
|
88
145
|
}
|
|
89
146
|
catch (error) {
|
|
90
147
|
return `Error getting diff: ${error}`;
|
|
91
148
|
}
|
|
92
149
|
});
|
|
93
150
|
}
|
|
151
|
+
getFileContent(filePath) {
|
|
152
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
153
|
+
try {
|
|
154
|
+
return yield promises_1.default.readFile(filePath, 'utf8');
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
return `Error reading file: ${error}`;
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
94
161
|
}
|
|
95
162
|
exports.GitHandler = GitHandler;
|
|
96
163
|
//# sourceMappingURL=git.js.map
|
package/dist/utils/git.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const blessed = require('neo-neo-blessed');
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
import { spawn } from 'child_process';
|
|
5
|
-
import { GitHandler, FileStatus } from './utils/git';
|
|
6
|
-
import { formatDiffWithDiff2Html } from './utils/diff-formatter';
|
|
2
|
+
const blessed = require('neo-neo-blessed');
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
import { GitHandler, FileStatus } from './utils/git';
|
|
6
|
+
import { formatDiffWithDiff2Html } from './utils/diff-formatter';
|
|
7
7
|
|
|
8
8
|
async function main() {
|
|
9
9
|
const gitHandler = new GitHandler();
|
|
@@ -61,8 +61,31 @@ async function main() {
|
|
|
61
61
|
tags: false,
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
+
const searchBox = blessed.box({
|
|
65
|
+
top: 'center',
|
|
66
|
+
left: 'center',
|
|
67
|
+
width: '50%',
|
|
68
|
+
height: 3,
|
|
69
|
+
label: ' Search ',
|
|
70
|
+
border: { type: 'line' },
|
|
71
|
+
style: { border: { fg: 'yellow' } },
|
|
72
|
+
hidden: true,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const searchInput = blessed.textbox({
|
|
76
|
+
parent: searchBox,
|
|
77
|
+
top: 0,
|
|
78
|
+
left: 0,
|
|
79
|
+
width: '100%-2',
|
|
80
|
+
height: 1,
|
|
81
|
+
keys: true,
|
|
82
|
+
inputOnFocus: true,
|
|
83
|
+
style: { fg: 'white', bg: 'black' },
|
|
84
|
+
});
|
|
85
|
+
|
|
64
86
|
screen.append(fileList);
|
|
65
87
|
screen.append(diffView);
|
|
88
|
+
screen.append(searchBox);
|
|
66
89
|
|
|
67
90
|
const updateBorders = () => {
|
|
68
91
|
fileList.style.border.fg = screen.focused === fileList ? 'yellow' : 'white';
|
|
@@ -70,125 +93,188 @@ async function main() {
|
|
|
70
93
|
screen.render();
|
|
71
94
|
};
|
|
72
95
|
|
|
73
|
-
let currentFiles: FileStatus[] = [];
|
|
74
|
-
let lastSelectedPath: string | null = null;
|
|
75
|
-
let diffUpdateTimeout: NodeJS.Timeout | null = null;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
let currentFiles: FileStatus[] = [];
|
|
97
|
+
let lastSelectedPath: string | null = null;
|
|
98
|
+
let diffUpdateTimeout: NodeJS.Timeout | null = null;
|
|
99
|
+
let currentSearchTerm: string = '';
|
|
100
|
+
|
|
101
|
+
const scheduleDiffUpdate = () => {
|
|
102
|
+
if (diffUpdateTimeout) clearTimeout(diffUpdateTimeout);
|
|
103
|
+
diffUpdateTimeout = setTimeout(async () => {
|
|
104
|
+
await updateDiff();
|
|
105
|
+
}, 150); // 150ms debounce
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const openInEditor = (filePath: string) => {
|
|
109
|
+
try {
|
|
110
|
+
if (process.platform === 'win32') {
|
|
111
|
+
// On Windows, use 'start' to open with default program
|
|
112
|
+
spawn('cmd', ['/c', 'start', '', filePath], { stdio: 'ignore', detached: true }).unref();
|
|
113
|
+
} else {
|
|
114
|
+
// On Unix-like systems, try EDITOR, fallback to xdg-open
|
|
115
|
+
const editor = process.env.EDITOR || process.env.VISUAL || 'xdg-open';
|
|
116
|
+
spawn(editor, [filePath], { stdio: 'ignore', detached: true }).unref();
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error(`Failed to open ${filePath}: ${error}`);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const updateDiff = async () => {
|
|
124
|
+
const selectedIndex = fileList.selected;
|
|
125
|
+
const selectedFile = currentFiles[selectedIndex];
|
|
126
|
+
if (selectedFile) {
|
|
127
|
+
let content = '';
|
|
128
|
+
let label = ` Diff (${selectedFile.path}) `;
|
|
129
|
+
|
|
130
|
+
if (selectedFile.status !== 'unchanged' && selectedFile.status !== 'deleted') {
|
|
131
|
+
const diff = await gitHandler.getDiff(selectedFile.path);
|
|
132
|
+
content = formatDiffWithDiff2Html(diff, currentSearchTerm);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!content && selectedFile.status !== 'deleted') {
|
|
136
|
+
content = await gitHandler.getFileContent(selectedFile.path);
|
|
137
|
+
// Highlight search term in full content
|
|
138
|
+
if (currentSearchTerm) {
|
|
139
|
+
const regex = new RegExp(`(${currentSearchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
|
|
140
|
+
content = content.replace(regex, `\x1b[43m\x1b[30m$1\x1b[0m`);
|
|
141
|
+
}
|
|
142
|
+
label = ` File (${selectedFile.path}) `;
|
|
143
|
+
} else if (!content && selectedFile.status === 'deleted') {
|
|
144
|
+
content = chalk.red('File was deleted.');
|
|
145
|
+
label = ` Diff (${selectedFile.path}) `;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const currentContent = diffView.content;
|
|
149
|
+
const currentLabel = diffView.label;
|
|
150
|
+
|
|
151
|
+
// Only update if content or label changed to reduce flickering
|
|
152
|
+
if (content !== currentContent || label !== currentLabel) {
|
|
153
|
+
const savedScroll = diffView.scrollTop;
|
|
154
|
+
const isNewFile = selectedFile.path !== lastSelectedPath;
|
|
155
|
+
|
|
156
|
+
diffView.setContent(content);
|
|
157
|
+
diffView.setLabel(label);
|
|
158
|
+
|
|
159
|
+
if (isNewFile) {
|
|
160
|
+
diffView.scrollTo(0);
|
|
161
|
+
} else {
|
|
162
|
+
diffView.scrollTop = savedScroll;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
lastSelectedPath = selectedFile.path;
|
|
166
|
+
} else {
|
|
167
|
+
const newContent = 'Select a file to view diff.';
|
|
168
|
+
const newLabel = ' Diff () ';
|
|
169
|
+
if (diffView.content !== newContent || diffView.label !== newLabel) {
|
|
170
|
+
diffView.setContent(newContent);
|
|
171
|
+
diffView.setLabel(newLabel);
|
|
172
|
+
diffView.scrollTo(0);
|
|
173
|
+
}
|
|
174
|
+
lastSelectedPath = null;
|
|
175
|
+
}
|
|
176
|
+
screen.render();
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const updateFileList = async () => {
|
|
180
|
+
// Preserve selected file path and scroll positions
|
|
181
|
+
const selectedPath = currentFiles[fileList.selected]?.path;
|
|
182
|
+
const diffScroll = diffView.scrollTop;
|
|
183
|
+
|
|
184
|
+
let files: FileStatus[];
|
|
185
|
+
|
|
186
|
+
if (currentSearchTerm) {
|
|
187
|
+
files = await gitHandler.searchFiles(currentSearchTerm);
|
|
188
|
+
} else {
|
|
189
|
+
files = await gitHandler.getStatus();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
currentFiles = files;
|
|
193
|
+
|
|
194
|
+
const items = files.map(f => {
|
|
195
|
+
let color = '{white-fg}';
|
|
196
|
+
if (f.status === 'added') color = '{green-fg}';
|
|
197
|
+
else if (f.status === 'deleted') color = '{red-fg}';
|
|
198
|
+
else if (f.status === 'modified') color = '{blue-fg}';
|
|
199
|
+
else if (f.status === 'unstaged') color = '{white-fg}';
|
|
200
|
+
else if (f.status === 'unchanged') color = '{grey-fg}';
|
|
201
|
+
|
|
202
|
+
return `${color}${f.path}{/}`;
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
fileList.setItems(items);
|
|
206
|
+
|
|
207
|
+
const labelTitle = currentSearchTerm ? ` Files (${files.length}) - Searching: "${currentSearchTerm}" ` : ` Files (${files.length}) `;
|
|
208
|
+
fileList.setLabel(labelTitle);
|
|
209
|
+
|
|
210
|
+
if (items.length > 0) {
|
|
211
|
+
// Restore selection by path if possible
|
|
212
|
+
const newSelectedIndex = selectedPath ? currentFiles.findIndex(f => f.path === selectedPath) : -1;
|
|
213
|
+
fileList.select(newSelectedIndex >= 0 ? newSelectedIndex : 0);
|
|
214
|
+
// Cancel any pending diff update and update immediately
|
|
215
|
+
if (diffUpdateTimeout) {
|
|
216
|
+
clearTimeout(diffUpdateTimeout);
|
|
217
|
+
diffUpdateTimeout = null;
|
|
218
|
+
}
|
|
219
|
+
await updateDiff();
|
|
220
|
+
} else {
|
|
221
|
+
diffView.setContent(currentSearchTerm ? `No files match "${currentSearchTerm}".` : 'No changes detected.');
|
|
222
|
+
diffView.setLabel(' Diff () ');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Restore scroll positions if reasonably possible (reset if list changed drastically)
|
|
226
|
+
// Actually, if we filter, the scroll position might be invalid.
|
|
227
|
+
// Ideally we keep it 0 if it was 0 or just let the select() call handle scrolling to the item.
|
|
228
|
+
// The previous implementation blindly restored scrollTop.
|
|
229
|
+
// If the list shrunk, select() should have brought it into view.
|
|
230
|
+
// We only explicitly restore if items.length > 0
|
|
231
|
+
// But setting scroll to previous value might be wrong if the list is now shorter.
|
|
232
|
+
// Safe to only restore diffView scroll as it depends on content, fileList is handled by select.
|
|
233
|
+
|
|
234
|
+
diffView.scrollTop = diffScroll;
|
|
235
|
+
|
|
236
|
+
screen.render();
|
|
97
237
|
};
|
|
98
238
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const currentContent = diffView.content;
|
|
107
|
-
const currentLabel = diffView.label;
|
|
108
|
-
|
|
109
|
-
// Only update if content or label changed to reduce flickering
|
|
110
|
-
if (formattedDiff !== currentContent || newLabel !== currentLabel) {
|
|
111
|
-
const savedScroll = diffView.scrollTop;
|
|
112
|
-
const isNewFile = selectedFile.path !== lastSelectedPath;
|
|
113
|
-
|
|
114
|
-
diffView.setContent(formattedDiff);
|
|
115
|
-
diffView.setLabel(newLabel);
|
|
116
|
-
|
|
117
|
-
if (isNewFile) {
|
|
118
|
-
diffView.scrollTo(0);
|
|
119
|
-
} else {
|
|
120
|
-
diffView.scrollTop = savedScroll;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
lastSelectedPath = selectedFile.path;
|
|
124
|
-
} else {
|
|
125
|
-
const newContent = 'Select a file to view diff.';
|
|
126
|
-
const newLabel = ' Diff () ';
|
|
127
|
-
if (diffView.content !== newContent || diffView.label !== newLabel) {
|
|
128
|
-
diffView.setContent(newContent);
|
|
129
|
-
diffView.setLabel(newLabel);
|
|
130
|
-
diffView.scrollTo(0);
|
|
131
|
-
}
|
|
132
|
-
lastSelectedPath = null;
|
|
133
|
-
}
|
|
134
|
-
screen.render();
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
const updateFileList = async () => {
|
|
138
|
-
// Preserve selected file path and scroll positions
|
|
139
|
-
const selectedPath = currentFiles[fileList.selected]?.path;
|
|
140
|
-
const fileListScroll = fileList.scroll;
|
|
141
|
-
const diffScroll = diffView.scrollTop;
|
|
142
|
-
|
|
143
|
-
const files = await gitHandler.getStatus();
|
|
144
|
-
currentFiles = files;
|
|
145
|
-
|
|
146
|
-
const items = files.map(f => {
|
|
147
|
-
let color = '{white-fg}';
|
|
148
|
-
if (f.status === 'added') color = '{green-fg}';
|
|
149
|
-
else if (f.status === 'deleted') color = '{red-fg}';
|
|
150
|
-
else if (f.status === 'modified') color = '{blue-fg}';
|
|
151
|
-
else if (f.status === 'unstaged') color = '{white-fg}';
|
|
152
|
-
|
|
153
|
-
return `${color}${f.path}{/}`;
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
fileList.setItems(items);
|
|
157
|
-
fileList.setLabel(` Files (${files.length}) `);
|
|
158
|
-
|
|
159
|
-
if (items.length > 0) {
|
|
160
|
-
// Restore selection by path if possible
|
|
161
|
-
const newSelectedIndex = selectedPath ? currentFiles.findIndex(f => f.path === selectedPath) : -1;
|
|
162
|
-
fileList.select(newSelectedIndex >= 0 ? newSelectedIndex : 0);
|
|
163
|
-
// Cancel any pending diff update and update immediately
|
|
164
|
-
if (diffUpdateTimeout) {
|
|
165
|
-
clearTimeout(diffUpdateTimeout);
|
|
166
|
-
diffUpdateTimeout = null;
|
|
167
|
-
}
|
|
168
|
-
await updateDiff();
|
|
169
|
-
} else {
|
|
170
|
-
diffView.setContent('No changes detected.');
|
|
171
|
-
diffView.setLabel(' Diff () ');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Restore scroll positions
|
|
175
|
-
fileList.scroll = fileListScroll;
|
|
176
|
-
diffView.scrollTop = diffScroll;
|
|
177
|
-
|
|
178
|
-
screen.render();
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
fileList.on('select item', () => {
|
|
182
|
-
scheduleDiffUpdate();
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
fileList.key(['up', 'down'], () => {
|
|
186
|
-
scheduleDiffUpdate();
|
|
187
|
-
});
|
|
239
|
+
fileList.on('select item', () => {
|
|
240
|
+
scheduleDiffUpdate();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
fileList.key(['up', 'down'], () => {
|
|
244
|
+
scheduleDiffUpdate();
|
|
245
|
+
});
|
|
188
246
|
|
|
189
247
|
screen.key(['escape', 'q', 'C-c'], () => {
|
|
190
|
-
|
|
191
|
-
|
|
248
|
+
if (!searchBox.hidden) {
|
|
249
|
+
searchBox.hide();
|
|
250
|
+
screen.render();
|
|
251
|
+
fileList.focus();
|
|
252
|
+
} else {
|
|
253
|
+
screen.destroy();
|
|
254
|
+
process.exit(0);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
screen.key(['s'], () => {
|
|
259
|
+
searchBox.show();
|
|
260
|
+
searchBox.setFront();
|
|
261
|
+
searchInput.setValue(currentSearchTerm);
|
|
262
|
+
searchInput.focus();
|
|
263
|
+
screen.render();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
searchInput.on('submit', async (value: string) => {
|
|
267
|
+
currentSearchTerm = (value || '').trim();
|
|
268
|
+
searchBox.hide();
|
|
269
|
+
fileList.focus();
|
|
270
|
+
// Force immediate update
|
|
271
|
+
await updateFileList();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
searchInput.on('cancel', () => {
|
|
275
|
+
searchBox.hide();
|
|
276
|
+
fileList.focus();
|
|
277
|
+
screen.render();
|
|
192
278
|
});
|
|
193
279
|
|
|
194
280
|
screen.key(['tab'], () => {
|
|
@@ -200,23 +286,23 @@ async function main() {
|
|
|
200
286
|
updateBorders();
|
|
201
287
|
});
|
|
202
288
|
|
|
203
|
-
screen.key(['left'], () => {
|
|
204
|
-
fileList.focus();
|
|
205
|
-
updateBorders();
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
screen.key(['right'], () => {
|
|
209
|
-
diffView.focus();
|
|
210
|
-
updateBorders();
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
screen.key(['enter'], () => {
|
|
214
|
-
const selectedIndex = fileList.selected;
|
|
215
|
-
const selectedFile = currentFiles[selectedIndex];
|
|
216
|
-
if (selectedFile) {
|
|
217
|
-
openInEditor(selectedFile.path);
|
|
218
|
-
}
|
|
219
|
-
});
|
|
289
|
+
screen.key(['left'], () => {
|
|
290
|
+
fileList.focus();
|
|
291
|
+
updateBorders();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
screen.key(['right'], () => {
|
|
295
|
+
diffView.focus();
|
|
296
|
+
updateBorders();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
screen.key(['enter'], () => {
|
|
300
|
+
const selectedIndex = fileList.selected;
|
|
301
|
+
const selectedFile = currentFiles[selectedIndex];
|
|
302
|
+
if (selectedFile) {
|
|
303
|
+
openInEditor(selectedFile.path);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
220
306
|
|
|
221
307
|
setInterval(async () => {
|
|
222
308
|
await updateFileList();
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as cheerio from 'cheerio';
|
|
2
2
|
const Diff2Html = require('diff2html');
|
|
3
3
|
|
|
4
|
-
export function formatDiffWithDiff2Html(diffString: string): string {
|
|
4
|
+
export function formatDiffWithDiff2Html(diffString: string, searchTerm?: string): string {
|
|
5
5
|
if (!diffString || diffString.trim() === '') {
|
|
6
|
-
return '
|
|
6
|
+
return '';
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
try {
|
|
@@ -41,7 +41,16 @@ export function formatDiffWithDiff2Html(diffString: string): string {
|
|
|
41
41
|
content = $lineWrapper.text().trim();
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
let fullLine = prefix + content;
|
|
45
|
+
|
|
46
|
+
// Highlight search term if present
|
|
47
|
+
if (searchTerm && fullLine.toLowerCase().includes(searchTerm.toLowerCase())) {
|
|
48
|
+
const regex = new RegExp(`(${searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
|
|
49
|
+
// We need to know the base color to restore it.
|
|
50
|
+
// Added: 32, Deleted: 31, Normal: 37
|
|
51
|
+
const baseColor = isAdded ? '32' : (isDeleted ? '31' : '37');
|
|
52
|
+
fullLine = fullLine.replace(regex, `\x1b[43m\x1b[30m$1\x1b[0m\x1b[${baseColor}m`);
|
|
53
|
+
}
|
|
45
54
|
|
|
46
55
|
if (isAdded) {
|
|
47
56
|
blessedText += `\x1b[32m${fullLine}\x1b[0m\n`;
|
|
@@ -58,7 +67,7 @@ export function formatDiffWithDiff2Html(diffString: string): string {
|
|
|
58
67
|
}
|
|
59
68
|
});
|
|
60
69
|
|
|
61
|
-
return blessedText.trim()
|
|
70
|
+
return blessedText.trim();
|
|
62
71
|
} catch (error) {
|
|
63
72
|
return `Error formatting diff: ${error}`;
|
|
64
73
|
}
|
package/src/utils/git.ts
CHANGED
|
@@ -3,7 +3,7 @@ import fs from 'fs/promises';
|
|
|
3
3
|
|
|
4
4
|
export interface FileStatus {
|
|
5
5
|
path: string;
|
|
6
|
-
status: 'modified' | 'added' | 'deleted' | 'unstaged' | 'unknown';
|
|
6
|
+
status: 'modified' | 'added' | 'deleted' | 'unstaged' | 'unknown' | 'unchanged';
|
|
7
7
|
mtime?: Date;
|
|
8
8
|
}
|
|
9
9
|
|
|
@@ -73,6 +73,63 @@ export class GitHandler {
|
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
async searchFiles(term: string): Promise<FileStatus[]> {
|
|
77
|
+
if (!term || !term.trim()) return [];
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
// Use git grep to find files containing the term
|
|
81
|
+
// -i: ignore case
|
|
82
|
+
// -l: list filenames only
|
|
83
|
+
// -F: interpret pattern as a fixed string
|
|
84
|
+
// --untracked: include untracked files
|
|
85
|
+
const result = await this.git.raw(['grep', '-i', '-l', '-F', '--untracked', term.trim()]);
|
|
86
|
+
const matchedPaths = [...new Set(result.split('\n').map(p => p.trim()).filter(p => p !== ''))];
|
|
87
|
+
|
|
88
|
+
if (matchedPaths.length === 0) return [];
|
|
89
|
+
|
|
90
|
+
// Get current status to identify which matched files are changed
|
|
91
|
+
const changedFiles = await this.getStatus();
|
|
92
|
+
const changedMap = new Map<string, FileStatus>();
|
|
93
|
+
changedFiles.forEach(f => changedMap.set(f.path, f));
|
|
94
|
+
|
|
95
|
+
const finalResults: FileStatus[] = [];
|
|
96
|
+
|
|
97
|
+
for (const path of matchedPaths) {
|
|
98
|
+
if (changedMap.has(path)) {
|
|
99
|
+
finalResults.push(changedMap.get(path)!);
|
|
100
|
+
} else {
|
|
101
|
+
// It's an unchanged file
|
|
102
|
+
let mtime = new Date(0);
|
|
103
|
+
try {
|
|
104
|
+
const stat = await fs.stat(path);
|
|
105
|
+
mtime = stat.mtime;
|
|
106
|
+
} catch {}
|
|
107
|
+
|
|
108
|
+
finalResults.push({
|
|
109
|
+
path,
|
|
110
|
+
status: 'unchanged',
|
|
111
|
+
mtime
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Sort results by mtime descending
|
|
117
|
+
return finalResults.sort((a, b) => {
|
|
118
|
+
const mtimeA = a.mtime || new Date(0);
|
|
119
|
+
const mtimeB = b.mtime || new Date(0);
|
|
120
|
+
const timeDiff = mtimeB.getTime() - mtimeA.getTime();
|
|
121
|
+
if (timeDiff !== 0) return timeDiff;
|
|
122
|
+
return a.path.localeCompare(b.path);
|
|
123
|
+
});
|
|
124
|
+
} catch (error: any) {
|
|
125
|
+
// git grep returns exit code 1 if no matches are found, which simple-git might throw as an error
|
|
126
|
+
if (error.message && (error.message.includes('exit code 1') || error.exitCode === 1)) {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
76
133
|
async getDiff(filePath: string): Promise<string> {
|
|
77
134
|
try {
|
|
78
135
|
const isUntracked = (await this.git.status()).not_added.includes(filePath);
|
|
@@ -84,9 +141,17 @@ export class GitHandler {
|
|
|
84
141
|
}
|
|
85
142
|
|
|
86
143
|
const diff = await this.git.diff(['HEAD', '--', filePath]);
|
|
87
|
-
return diff || '
|
|
144
|
+
return diff || '';
|
|
88
145
|
} catch (error) {
|
|
89
146
|
return `Error getting diff: ${error}`;
|
|
90
147
|
}
|
|
91
148
|
}
|
|
149
|
+
|
|
150
|
+
async getFileContent(filePath: string): Promise<string> {
|
|
151
|
+
try {
|
|
152
|
+
return await fs.readFile(filePath, 'utf8');
|
|
153
|
+
} catch (error) {
|
|
154
|
+
return `Error reading file: ${error}`;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
92
157
|
}
|
package/tests/git.test.ts
CHANGED
|
@@ -3,11 +3,12 @@ jest.mock('simple-git', () => ({
|
|
|
3
3
|
}));
|
|
4
4
|
jest.mock('fs/promises', () => ({
|
|
5
5
|
stat: jest.fn(),
|
|
6
|
+
readFile: jest.fn(),
|
|
6
7
|
}));
|
|
7
8
|
|
|
8
9
|
import { simpleGit } from 'simple-git';
|
|
9
10
|
import fs from 'fs/promises';
|
|
10
|
-
import { GitHandler } from '../src/utils/git';
|
|
11
|
+
import { GitHandler, FileStatus } from '../src/utils/git';
|
|
11
12
|
|
|
12
13
|
describe('GitHandler', () => {
|
|
13
14
|
let gitHandler: GitHandler;
|
|
@@ -22,6 +23,7 @@ describe('GitHandler', () => {
|
|
|
22
23
|
};
|
|
23
24
|
(simpleGit as jest.Mock).mockReturnValue(mockGit);
|
|
24
25
|
(fs.stat as jest.Mock).mockResolvedValue({ mtime: new Date() });
|
|
26
|
+
(fs.readFile as jest.Mock).mockResolvedValue('');
|
|
25
27
|
gitHandler = new GitHandler();
|
|
26
28
|
});
|
|
27
29
|
|
|
@@ -62,6 +64,13 @@ describe('GitHandler', () => {
|
|
|
62
64
|
expect(diff).toBe('+ added line\n- removed line');
|
|
63
65
|
});
|
|
64
66
|
|
|
67
|
+
it('should return empty string if no diff', async () => {
|
|
68
|
+
mockGit.status.mockResolvedValue({ not_added: [] });
|
|
69
|
+
mockGit.diff.mockResolvedValue('');
|
|
70
|
+
const diff = await gitHandler.getDiff('file1.ts');
|
|
71
|
+
expect(diff).toBe('');
|
|
72
|
+
});
|
|
73
|
+
|
|
65
74
|
it('should sort files by last modified descending then filename', async () => {
|
|
66
75
|
// Mock fs.stat to return different mtimes
|
|
67
76
|
const mockStat = jest.fn();
|
|
@@ -98,4 +107,42 @@ describe('GitHandler', () => {
|
|
|
98
107
|
'a-file.ts', // oldest
|
|
99
108
|
]);
|
|
100
109
|
});
|
|
110
|
+
|
|
111
|
+
it('should search files correctly', async () => {
|
|
112
|
+
mockGit.raw = jest.fn().mockResolvedValue('match.ts\nunchanged.ts');
|
|
113
|
+
mockGit.status.mockResolvedValue({
|
|
114
|
+
modified: ['match.ts'],
|
|
115
|
+
deleted: [],
|
|
116
|
+
created: [],
|
|
117
|
+
not_added: [],
|
|
118
|
+
renamed: [],
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const results = await gitHandler.searchFiles('match');
|
|
122
|
+
|
|
123
|
+
expect(results).toHaveLength(2);
|
|
124
|
+
expect(results).toEqual(expect.arrayContaining([
|
|
125
|
+
expect.objectContaining({ path: 'match.ts', status: 'modified' }),
|
|
126
|
+
expect.objectContaining({ path: 'unchanged.ts', status: 'unchanged' }),
|
|
127
|
+
]));
|
|
128
|
+
expect(mockGit.raw).toHaveBeenCalledWith(['grep', '-i', '-l', '-F', '--untracked', 'match']);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should deduplicate search results', async () => {
|
|
132
|
+
// Return duplicate paths
|
|
133
|
+
mockGit.raw = jest.fn().mockResolvedValue('match.ts\nmatch.ts\nother.ts\nother.ts');
|
|
134
|
+
mockGit.status.mockResolvedValue({
|
|
135
|
+
modified: [],
|
|
136
|
+
deleted: [],
|
|
137
|
+
created: [],
|
|
138
|
+
not_added: [],
|
|
139
|
+
renamed: [],
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const results = await gitHandler.searchFiles('term');
|
|
143
|
+
|
|
144
|
+
expect(results).toHaveLength(2);
|
|
145
|
+
const paths = results.map(r => r.path).sort();
|
|
146
|
+
expect(paths).toEqual(['match.ts', 'other.ts']);
|
|
147
|
+
});
|
|
101
148
|
});
|