diffstalker 0.2.1 → 0.2.2
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/.dependency-cruiser.cjs +67 -0
- package/.githooks/pre-commit +2 -0
- package/.githooks/pre-push +15 -0
- package/README.md +43 -35
- package/bun.lock +60 -4
- package/dist/App.js +378 -129
- package/dist/KeyBindings.js +59 -9
- package/dist/MouseHandlers.js +56 -20
- package/dist/core/ExplorerStateManager.js +17 -38
- package/dist/core/GitStateManager.js +111 -46
- package/dist/git/diff.js +99 -18
- package/dist/git/status.js +16 -54
- package/dist/git/test-helpers.js +67 -0
- package/dist/index.js +53 -47
- package/dist/ipc/CommandClient.js +6 -7
- package/dist/state/UIState.js +22 -0
- package/dist/ui/PaneRenderers.js +33 -13
- package/dist/ui/modals/FileFinder.js +26 -65
- package/dist/ui/modals/HotkeysModal.js +12 -3
- package/dist/ui/modals/ThemePicker.js +1 -2
- package/dist/ui/widgets/CommitPanel.js +1 -1
- package/dist/ui/widgets/CompareListView.js +44 -23
- package/dist/ui/widgets/DiffView.js +216 -170
- package/dist/ui/widgets/ExplorerView.js +50 -54
- package/dist/ui/widgets/FileList.js +62 -95
- package/dist/ui/widgets/FlatFileList.js +65 -0
- package/dist/ui/widgets/Footer.js +25 -15
- package/dist/ui/widgets/Header.js +14 -6
- package/dist/ui/widgets/fileRowFormatters.js +73 -0
- package/dist/utils/ansiTruncate.js +0 -1
- package/dist/utils/displayRows.js +101 -21
- package/dist/utils/flatFileList.js +67 -0
- package/dist/utils/layoutCalculations.js +5 -3
- package/eslint.metrics.js +0 -1
- package/metrics/v0.2.2.json +229 -0
- package/package.json +6 -2
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a deduplicated, alphabetically sorted flat file list.
|
|
3
|
+
* Files that appear in both staged and unstaged are merged into one entry
|
|
4
|
+
* with stagingState 'partial'.
|
|
5
|
+
*/
|
|
6
|
+
export function buildFlatFileList(files, hunkCounts) {
|
|
7
|
+
// Group by path
|
|
8
|
+
const byPath = new Map();
|
|
9
|
+
for (const file of files) {
|
|
10
|
+
const existing = byPath.get(file.path) ?? { staged: null, unstaged: null };
|
|
11
|
+
if (file.staged) {
|
|
12
|
+
existing.staged = file;
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
existing.unstaged = file;
|
|
16
|
+
}
|
|
17
|
+
byPath.set(file.path, existing);
|
|
18
|
+
}
|
|
19
|
+
const result = [];
|
|
20
|
+
for (const [filePath, { staged, unstaged }] of byPath) {
|
|
21
|
+
const stagedHunks = hunkCounts?.staged.get(filePath) ?? 0;
|
|
22
|
+
const unstagedHunks = hunkCounts?.unstaged.get(filePath) ?? 0;
|
|
23
|
+
const totalHunks = stagedHunks + unstagedHunks;
|
|
24
|
+
let stagingState;
|
|
25
|
+
if (staged && unstaged) {
|
|
26
|
+
stagingState = 'partial';
|
|
27
|
+
}
|
|
28
|
+
else if (staged) {
|
|
29
|
+
stagingState = 'staged';
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
stagingState = 'unstaged';
|
|
33
|
+
}
|
|
34
|
+
// Use the unstaged entry as primary (for status), fall back to staged
|
|
35
|
+
const primary = unstaged ?? staged;
|
|
36
|
+
// Combine insertions/deletions from both entries
|
|
37
|
+
let insertions;
|
|
38
|
+
let deletions;
|
|
39
|
+
if (staged?.insertions !== undefined || unstaged?.insertions !== undefined) {
|
|
40
|
+
insertions = (staged?.insertions ?? 0) + (unstaged?.insertions ?? 0);
|
|
41
|
+
}
|
|
42
|
+
if (staged?.deletions !== undefined || unstaged?.deletions !== undefined) {
|
|
43
|
+
deletions = (staged?.deletions ?? 0) + (unstaged?.deletions ?? 0);
|
|
44
|
+
}
|
|
45
|
+
result.push({
|
|
46
|
+
path: filePath,
|
|
47
|
+
status: primary.status,
|
|
48
|
+
stagingState,
|
|
49
|
+
stagedHunks,
|
|
50
|
+
totalHunks,
|
|
51
|
+
insertions,
|
|
52
|
+
deletions,
|
|
53
|
+
originalPath: primary.originalPath,
|
|
54
|
+
stagedEntry: staged,
|
|
55
|
+
unstagedEntry: unstaged,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
// Sort alphabetically by path
|
|
59
|
+
result.sort((a, b) => a.path.localeCompare(b.path));
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
export function getFlatFileAtIndex(flatFiles, index) {
|
|
63
|
+
return flatFiles[index] ?? null;
|
|
64
|
+
}
|
|
65
|
+
export function getFlatFileIndexByPath(flatFiles, path) {
|
|
66
|
+
return flatFiles.findIndex((f) => f.path === path);
|
|
67
|
+
}
|
|
@@ -32,11 +32,13 @@ export function getFileListTotalRows(files) {
|
|
|
32
32
|
*
|
|
33
33
|
* The top pane grows to fit files up to 40% of content height.
|
|
34
34
|
* The bottom pane gets the remaining space.
|
|
35
|
+
*
|
|
36
|
+
* When flatRowCount is provided (flat view mode), uses that directly instead
|
|
37
|
+
* of computing row count from categorized file list.
|
|
35
38
|
*/
|
|
36
|
-
export function calculatePaneHeights(files, contentHeight, maxTopRatio = 0.4) {
|
|
39
|
+
export function calculatePaneHeights(files, contentHeight, maxTopRatio = 0.4, flatRowCount) {
|
|
37
40
|
// Calculate content rows needed for staging area
|
|
38
|
-
|
|
39
|
-
const neededRows = getFileListTotalRows(files);
|
|
41
|
+
const neededRows = flatRowCount !== undefined ? flatRowCount : getFileListTotalRows(files);
|
|
40
42
|
// Minimum height of 3 (header + 2 lines for empty state)
|
|
41
43
|
const minHeight = 3;
|
|
42
44
|
// Maximum is maxTopRatio of content area
|
package/eslint.metrics.js
CHANGED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
{
|
|
2
|
+
"timestamp": "2026-02-01T21:09:51.898Z",
|
|
3
|
+
"gitRef": "v0.2.2",
|
|
4
|
+
"gitSha": "b349fdc",
|
|
5
|
+
"summary": {
|
|
6
|
+
"files": 77,
|
|
7
|
+
"lines": 14894,
|
|
8
|
+
"functions": 1046,
|
|
9
|
+
"avgCyclomaticComplexity": 5.3,
|
|
10
|
+
"maxCyclomaticComplexity": {
|
|
11
|
+
"value": 42,
|
|
12
|
+
"function": "buildDiffDisplayRows",
|
|
13
|
+
"file": "src/utils/displayRows.ts:112"
|
|
14
|
+
},
|
|
15
|
+
"avgCognitiveComplexity": 6.4,
|
|
16
|
+
"maxCognitiveComplexity": {
|
|
17
|
+
"value": 68,
|
|
18
|
+
"function": "buildDiffDisplayRows",
|
|
19
|
+
"file": "src/utils/displayRows.ts:112"
|
|
20
|
+
},
|
|
21
|
+
"smells": 0
|
|
22
|
+
},
|
|
23
|
+
"hotspots": [
|
|
24
|
+
{
|
|
25
|
+
"file": "src/utils/displayRows.ts",
|
|
26
|
+
"lines": 567,
|
|
27
|
+
"cyclomaticMax": 42,
|
|
28
|
+
"cognitiveMax": 68,
|
|
29
|
+
"smells": 0
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"file": "src/utils/flatFileList.ts",
|
|
33
|
+
"lines": 100,
|
|
34
|
+
"cyclomaticMax": 29,
|
|
35
|
+
"cognitiveMax": 14,
|
|
36
|
+
"smells": 0
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"file": "src/git/diff.ts",
|
|
40
|
+
"lines": 656,
|
|
41
|
+
"cyclomaticMax": 26,
|
|
42
|
+
"cognitiveMax": 41,
|
|
43
|
+
"smells": 0
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"file": "src/App.ts",
|
|
47
|
+
"lines": 1391,
|
|
48
|
+
"cyclomaticMax": 23,
|
|
49
|
+
"cognitiveMax": 13,
|
|
50
|
+
"smells": 0
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"file": "src/git/status.ts",
|
|
54
|
+
"lines": 268,
|
|
55
|
+
"cyclomaticMax": 22,
|
|
56
|
+
"cognitiveMax": 25,
|
|
57
|
+
"smells": 0
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"file": "src/ui/PaneRenderers.ts",
|
|
61
|
+
"lines": 224,
|
|
62
|
+
"cyclomaticMax": 20,
|
|
63
|
+
"cognitiveMax": 8,
|
|
64
|
+
"smells": 0
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"file": "src/ui/widgets/ExplorerContent.ts",
|
|
68
|
+
"lines": 130,
|
|
69
|
+
"cyclomaticMax": 20,
|
|
70
|
+
"cognitiveMax": 24,
|
|
71
|
+
"smells": 0
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"file": "src/MouseHandlers.ts",
|
|
75
|
+
"lines": 257,
|
|
76
|
+
"cyclomaticMax": 17,
|
|
77
|
+
"cognitiveMax": 15,
|
|
78
|
+
"smells": 0
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"file": "src/ipc/CommandServer.ts",
|
|
82
|
+
"lines": 266,
|
|
83
|
+
"cyclomaticMax": 17,
|
|
84
|
+
"cognitiveMax": 6,
|
|
85
|
+
"smells": 0
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"file": "src/index.ts",
|
|
89
|
+
"lines": 178,
|
|
90
|
+
"cyclomaticMax": 16,
|
|
91
|
+
"cognitiveMax": 17,
|
|
92
|
+
"smells": 0
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"file": "src/ui/widgets/ExplorerView.ts",
|
|
96
|
+
"lines": 245,
|
|
97
|
+
"cyclomaticMax": 16,
|
|
98
|
+
"cognitiveMax": 21,
|
|
99
|
+
"smells": 0
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"file": "src/utils/ansiTruncate.ts",
|
|
103
|
+
"lines": 123,
|
|
104
|
+
"cyclomaticMax": 16,
|
|
105
|
+
"cognitiveMax": 24,
|
|
106
|
+
"smells": 0
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"file": "src/ui/widgets/DiffView.ts",
|
|
110
|
+
"lines": 511,
|
|
111
|
+
"cyclomaticMax": 15,
|
|
112
|
+
"cognitiveMax": 12,
|
|
113
|
+
"smells": 0
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"file": "src/ui/widgets/CompareListView.ts",
|
|
117
|
+
"lines": 383,
|
|
118
|
+
"cyclomaticMax": 14,
|
|
119
|
+
"cognitiveMax": 16,
|
|
120
|
+
"smells": 0
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"file": "src/ui/widgets/CommitPanel.ts",
|
|
124
|
+
"lines": 83,
|
|
125
|
+
"cyclomaticMax": 13,
|
|
126
|
+
"cognitiveMax": 11,
|
|
127
|
+
"smells": 0
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"file": "src/utils/diffRowCalculations.ts",
|
|
131
|
+
"lines": 136,
|
|
132
|
+
"cyclomaticMax": 13,
|
|
133
|
+
"cognitiveMax": 24,
|
|
134
|
+
"smells": 0
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"file": "src/core/ExplorerStateManager.ts",
|
|
138
|
+
"lines": 750,
|
|
139
|
+
"cyclomaticMax": 12,
|
|
140
|
+
"cognitiveMax": 15,
|
|
141
|
+
"smells": 0
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"file": "src/ui/widgets/FileList.ts",
|
|
145
|
+
"lines": 215,
|
|
146
|
+
"cyclomaticMax": 12,
|
|
147
|
+
"cognitiveMax": 12,
|
|
148
|
+
"smells": 0
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"file": "src/utils/lineBreaking.ts",
|
|
152
|
+
"lines": 114,
|
|
153
|
+
"cyclomaticMax": 12,
|
|
154
|
+
"cognitiveMax": 17,
|
|
155
|
+
"smells": 0
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"file": "src/ui/widgets/Header.ts",
|
|
159
|
+
"lines": 98,
|
|
160
|
+
"cyclomaticMax": 11,
|
|
161
|
+
"cognitiveMax": 13,
|
|
162
|
+
"smells": 0
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"file": "src/config.ts",
|
|
166
|
+
"lines": 107,
|
|
167
|
+
"cyclomaticMax": 10,
|
|
168
|
+
"cognitiveMax": 13,
|
|
169
|
+
"smells": 0
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"file": "src/state/UIState.ts",
|
|
173
|
+
"lines": 307,
|
|
174
|
+
"cyclomaticMax": 10,
|
|
175
|
+
"cognitiveMax": 12,
|
|
176
|
+
"smells": 0
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"file": "src/KeyBindings.ts",
|
|
180
|
+
"lines": 289,
|
|
181
|
+
"cyclomaticMax": 9,
|
|
182
|
+
"cognitiveMax": 15,
|
|
183
|
+
"smells": 0
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"file": "src/core/GitStateManager.ts",
|
|
187
|
+
"lines": 815,
|
|
188
|
+
"cyclomaticMax": 9,
|
|
189
|
+
"cognitiveMax": 12,
|
|
190
|
+
"smells": 0
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"file": "src/ui/widgets/HistoryView.ts",
|
|
194
|
+
"lines": 97,
|
|
195
|
+
"cyclomaticMax": 9,
|
|
196
|
+
"cognitiveMax": 12,
|
|
197
|
+
"smells": 0
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"file": "src/utils/formatPath.ts",
|
|
201
|
+
"lines": 72,
|
|
202
|
+
"cyclomaticMax": 9,
|
|
203
|
+
"cognitiveMax": 11,
|
|
204
|
+
"smells": 0
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"file": "src/utils/explorerDisplayRows.ts",
|
|
208
|
+
"lines": 206,
|
|
209
|
+
"cyclomaticMax": 8,
|
|
210
|
+
"cognitiveMax": 15,
|
|
211
|
+
"smells": 0
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"file": "src/ui/modals/FileFinder.ts",
|
|
215
|
+
"lines": 226,
|
|
216
|
+
"cyclomaticMax": 7,
|
|
217
|
+
"cognitiveMax": 13,
|
|
218
|
+
"smells": 0
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"file": "src/ui/modals/BaseBranchPicker.ts",
|
|
222
|
+
"lines": 134,
|
|
223
|
+
"cyclomaticMax": 6,
|
|
224
|
+
"cognitiveMax": 13,
|
|
225
|
+
"smells": 0
|
|
226
|
+
}
|
|
227
|
+
],
|
|
228
|
+
"smellsByRule": {}
|
|
229
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "diffstalker",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Terminal application that displays git diff/status for directories",
|
|
5
5
|
"author": "yogh-io",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
21
|
"postinstall": "node scripts/patch-neo-blessed.cjs",
|
|
22
|
+
"prepare": "git config core.hooksPath .githooks",
|
|
22
23
|
"dev": "bun --watch src/index.ts",
|
|
23
24
|
"build": "tsc",
|
|
24
25
|
"build:prod": "tsc && bun build dist/index.js --outfile dist/index.js --minify --target node --packages external",
|
|
@@ -27,10 +28,11 @@
|
|
|
27
28
|
"start:bundle": "bun dist/bundle/index.js",
|
|
28
29
|
"test": "bun test src/*.test.ts src/**/*.test.ts",
|
|
29
30
|
"test:watch": "bun test --watch src/*.test.ts src/**/*.test.ts",
|
|
30
|
-
"lint": "eslint src/",
|
|
31
|
+
"lint": "eslint src/ && depcruise src/ --config .dependency-cruiser.cjs",
|
|
31
32
|
"lint:fix": "eslint src/ --fix",
|
|
32
33
|
"format": "prettier --write src/",
|
|
33
34
|
"format:check": "prettier --check src/",
|
|
35
|
+
"deps": "depcruise src/ --config .dependency-cruiser.cjs",
|
|
34
36
|
"metrics": "bun scripts/collect-metrics.ts",
|
|
35
37
|
"metrics:snapshot": "bun scripts/collect-metrics.ts --save",
|
|
36
38
|
"prepublishOnly": "bun run build:prod"
|
|
@@ -49,6 +51,7 @@
|
|
|
49
51
|
"chokidar": "^4.0.3",
|
|
50
52
|
"emphasize": "^7.0.0",
|
|
51
53
|
"fast-diff": "^1.3.0",
|
|
54
|
+
"fzf": "^0.5.2",
|
|
52
55
|
"ignore": "^7.0.5",
|
|
53
56
|
"neo-blessed": "^0.2.0",
|
|
54
57
|
"simple-git": "^3.27.0",
|
|
@@ -58,6 +61,7 @@
|
|
|
58
61
|
"@eslint/js": "^9.39.2",
|
|
59
62
|
"@types/blessed": "^0.1.27",
|
|
60
63
|
"@types/node": "^22.10.7",
|
|
64
|
+
"dependency-cruiser": "^17.3.7",
|
|
61
65
|
"eslint": "^9.39.2",
|
|
62
66
|
"eslint-config-prettier": "^10.1.8",
|
|
63
67
|
"eslint-plugin-sonarjs": "^3.0.6",
|