diffstalker 0.2.2 → 0.2.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.
@@ -1,7 +1,8 @@
1
1
  /**
2
- * Format the commit panel as blessed-compatible tagged string.
2
+ * Build all lines for the commit panel (used for both rendering and totalRows).
3
3
  */
4
- export function formatCommitPanel(state, stagedCount, width) {
4
+ export function buildCommitPanelLines(opts) {
5
+ const { state, stagedCount, width, branch, remoteState, stashList, headCommit } = opts;
5
6
  const lines = [];
6
7
  // Title
7
8
  let title = '{bold}Commit Message{/bold}';
@@ -11,7 +12,6 @@ export function formatCommitPanel(state, stagedCount, width) {
11
12
  lines.push(title);
12
13
  lines.push('');
13
14
  // Message input area
14
- const borderChar = '\u2502';
15
15
  const borderColor = state.inputFocused ? 'cyan' : 'gray';
16
16
  // Top border
17
17
  const innerWidth = Math.max(20, width - 6);
@@ -24,7 +24,7 @@ export function formatCommitPanel(state, stagedCount, width) {
24
24
  const truncatedMessage = displayMessage.length > innerWidth
25
25
  ? displayMessage.slice(0, innerWidth - 1) + '\u2026'
26
26
  : displayMessage.padEnd(innerWidth);
27
- lines.push(`{${borderColor}-fg}${borderChar}{/${borderColor}-fg} ${messageColor}${truncatedMessage}${messageEnd} {${borderColor}-fg}${borderChar}{/${borderColor}-fg}`);
27
+ lines.push(`{${borderColor}-fg}\u2502{/${borderColor}-fg} ${messageColor}${truncatedMessage}${messageEnd} {${borderColor}-fg}\u2502{/${borderColor}-fg}`);
28
28
  // Bottom border
29
29
  lines.push(`{${borderColor}-fg}\u2514${'─'.repeat(innerWidth + 2)}\u2518{/${borderColor}-fg}`);
30
30
  lines.push('');
@@ -45,10 +45,116 @@ export function formatCommitPanel(state, stagedCount, width) {
45
45
  lines.push('');
46
46
  // Help text
47
47
  const helpText = state.inputFocused
48
- ? 'Enter: commit | Esc: unfocus'
49
- : 'i/Enter: edit | Esc: cancel | a: toggle amend';
48
+ ? 'Enter: commit | Ctrl+a: amend | Esc: unfocus'
49
+ : 'i/Enter: edit | a: amend | Esc: back';
50
50
  lines.push(`{gray-fg}Staged: ${stagedCount} file(s) | ${helpText}{/gray-fg}`);
51
- return lines.join('\n');
51
+ // Stash section
52
+ const stashEntries = stashList ?? [];
53
+ lines.push('');
54
+ lines.push(`{gray-fg}${'─'.repeat(3)} Stash (${stashEntries.length}) ${'─'.repeat(3)}{/gray-fg}`);
55
+ if (stashEntries.length > 0) {
56
+ const maxShow = 5;
57
+ for (let i = 0; i < Math.min(stashEntries.length, maxShow); i++) {
58
+ const entry = stashEntries[i];
59
+ const msg = entry.message.length > width - 10
60
+ ? entry.message.slice(0, width - 13) + '\u2026'
61
+ : entry.message;
62
+ lines.push(`{gray-fg}{${i}}{/gray-fg}: ${msg}`);
63
+ }
64
+ if (stashEntries.length > maxShow) {
65
+ lines.push(`{gray-fg}... ${stashEntries.length - maxShow} more{/gray-fg}`);
66
+ }
67
+ }
68
+ else {
69
+ lines.push('{gray-fg}(empty){/gray-fg}');
70
+ }
71
+ lines.push('{gray-fg}S: save | o: pop | l: list{/gray-fg}');
72
+ // Branch section
73
+ if (branch) {
74
+ lines.push('');
75
+ lines.push(`{gray-fg}${'─'.repeat(3)} Branch ${'─'.repeat(3)}{/gray-fg}`);
76
+ let branchLine = `{bold}* ${branch.current}{/bold}`;
77
+ if (branch.tracking) {
78
+ branchLine += ` {gray-fg}\u2192{/gray-fg} ${branch.tracking}`;
79
+ }
80
+ lines.push(branchLine);
81
+ lines.push('{gray-fg}b: switch/create{/gray-fg}');
82
+ }
83
+ // Undo section
84
+ lines.push('');
85
+ lines.push(`{gray-fg}${'─'.repeat(3)} Undo ${'─'.repeat(3)}{/gray-fg}`);
86
+ if (headCommit) {
87
+ lines.push(`{gray-fg}HEAD: {yellow-fg}${headCommit.shortHash}{/yellow-fg} ${headCommit.message}{/gray-fg}`);
88
+ }
89
+ lines.push('{gray-fg}X: soft reset HEAD~1{/gray-fg}');
90
+ // Remote section
91
+ if (branch) {
92
+ lines.push('');
93
+ lines.push(`{gray-fg}${'─'.repeat(3)} Remote ${'─'.repeat(3)}{/gray-fg}`);
94
+ // Tracking info
95
+ if (branch.tracking) {
96
+ let tracking = `${branch.current} {gray-fg}\u2192{/gray-fg} ${branch.tracking}`;
97
+ if (branch.ahead > 0)
98
+ tracking += ` {green-fg}\u2191${branch.ahead}{/green-fg}`;
99
+ if (branch.behind > 0)
100
+ tracking += ` {red-fg}\u2193${branch.behind}{/red-fg}`;
101
+ lines.push(tracking);
102
+ }
103
+ else {
104
+ lines.push(`{gray-fg}${branch.current} (no remote tracking){/gray-fg}`);
105
+ }
106
+ // Remote status
107
+ if (remoteState?.inProgress && remoteState.operation) {
108
+ const labels = {
109
+ push: 'Pushing...',
110
+ fetch: 'Fetching...',
111
+ pull: 'Rebasing...',
112
+ stash: 'Stashing...',
113
+ stashPop: 'Popping stash...',
114
+ branchSwitch: 'Switching branch...',
115
+ branchCreate: 'Creating branch...',
116
+ softReset: 'Resetting...',
117
+ cherryPick: 'Cherry-picking...',
118
+ revert: 'Reverting...',
119
+ };
120
+ lines.push(`{yellow-fg}${labels[remoteState.operation] ?? ''}{/yellow-fg}`);
121
+ }
122
+ else if (remoteState?.error) {
123
+ const brief = remoteState.error.length > 50
124
+ ? remoteState.error.slice(0, 50) + '\u2026'
125
+ : remoteState.error;
126
+ lines.push(`{red-fg}${brief}{/red-fg}`);
127
+ }
128
+ else if (remoteState?.lastResult) {
129
+ lines.push(`{green-fg}${remoteState.lastResult}{/green-fg}`);
130
+ }
131
+ lines.push('{gray-fg}P: push | F: fetch | R: pull --rebase{/gray-fg}');
132
+ }
133
+ return lines;
134
+ }
135
+ /**
136
+ * Get total row count for the commit panel (for scroll calculations).
137
+ */
138
+ export function getCommitPanelTotalRows(opts) {
139
+ return buildCommitPanelLines(opts).length;
140
+ }
141
+ /**
142
+ * Format the commit panel as blessed-compatible tagged string.
143
+ */
144
+ export function formatCommitPanel(state, stagedCount, width, branch, remoteState, stashList, headCommit, scrollOffset = 0, visibleHeight) {
145
+ const allLines = buildCommitPanelLines({
146
+ state,
147
+ stagedCount,
148
+ width,
149
+ branch,
150
+ remoteState,
151
+ stashList,
152
+ headCommit,
153
+ });
154
+ if (visibleHeight && allLines.length > visibleHeight) {
155
+ return allLines.slice(scrollOffset, scrollOffset + visibleHeight).join('\n');
156
+ }
157
+ return allLines.join('\n');
52
158
  }
53
159
  /**
54
160
  * Format inactive commit panel.
@@ -38,7 +38,7 @@ function computeBranchVisibleLength(branch) {
38
38
  /**
39
39
  * Format header content as blessed-compatible tagged string.
40
40
  */
41
- export function formatHeader(repoPath, branch, isLoading, error, width) {
41
+ export function formatHeader(repoPath, branch, isLoading, error, width, remoteState) {
42
42
  if (!repoPath) {
43
43
  return '{gray-fg}Waiting for target path...{/gray-fg}';
44
44
  }
@@ -55,6 +55,39 @@ export function formatHeader(repoPath, branch, isLoading, error, width) {
55
55
  else if (error) {
56
56
  leftContent += ` {red-fg}(${error}){/red-fg}`;
57
57
  }
58
+ // Remote operation status (shown after left content)
59
+ let remoteStatus = '';
60
+ let remoteStatusLen = 0;
61
+ if (remoteState) {
62
+ if (remoteState.inProgress && remoteState.operation) {
63
+ const labels = {
64
+ push: 'pushing...',
65
+ fetch: 'fetching...',
66
+ pull: 'rebasing...',
67
+ stash: 'stashing...',
68
+ stashPop: 'popping stash...',
69
+ branchSwitch: 'switching branch...',
70
+ branchCreate: 'creating branch...',
71
+ softReset: 'resetting...',
72
+ cherryPick: 'cherry-picking...',
73
+ revert: 'reverting...',
74
+ };
75
+ const label = labels[remoteState.operation] ?? '';
76
+ remoteStatus = ` {yellow-fg}${label}{/yellow-fg}`;
77
+ remoteStatusLen = 1 + label.length;
78
+ }
79
+ else if (remoteState.error) {
80
+ const brief = remoteState.error.length > 40
81
+ ? remoteState.error.slice(0, 40) + '\u2026'
82
+ : remoteState.error;
83
+ remoteStatus = ` {red-fg}${brief}{/red-fg}`;
84
+ remoteStatusLen = 1 + brief.length;
85
+ }
86
+ else if (remoteState.lastResult) {
87
+ remoteStatus = ` {green-fg}${remoteState.lastResult}{/green-fg}`;
88
+ remoteStatusLen = 1 + remoteState.lastResult.length;
89
+ }
90
+ }
58
91
  // Build right side content (branch info)
59
92
  const rightContent = branch ? formatBranch(branch) : '';
60
93
  if (rightContent) {
@@ -68,9 +101,10 @@ export function formatHeader(repoPath, branch, isLoading, error, width) {
68
101
  else if (error) {
69
102
  leftLen += error.length + 3; // " (error)"
70
103
  }
104
+ leftLen += remoteStatusLen;
71
105
  const rightLen = branch ? computeBranchVisibleLength(branch) : 0;
72
106
  const padding = Math.max(1, width - leftLen - rightLen - 2);
73
- return leftContent + ' '.repeat(padding) + rightContent;
107
+ return leftContent + remoteStatus + ' '.repeat(padding) + rightContent;
74
108
  }
75
- return leftContent;
109
+ return leftContent + remoteStatus;
76
110
  }
@@ -0,0 +1,243 @@
1
+ {
2
+ "timestamp": "2026-02-06T18:38:55.083Z",
3
+ "gitRef": "v0.2.3",
4
+ "gitSha": "11ba9fc",
5
+ "summary": {
6
+ "files": 82,
7
+ "lines": 16165,
8
+ "functions": 1146,
9
+ "avgCyclomaticComplexity": 5.1,
10
+ "maxCyclomaticComplexity": {
11
+ "value": 42,
12
+ "function": "buildDiffDisplayRows",
13
+ "file": "src/utils/displayRows.ts:112"
14
+ },
15
+ "avgCognitiveComplexity": 6.5,
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/ui/widgets/CommitPanel.ts",
33
+ "lines": 217,
34
+ "cyclomaticMax": 34,
35
+ "cognitiveMax": 42,
36
+ "smells": 0
37
+ },
38
+ {
39
+ "file": "src/App.ts",
40
+ "lines": 1548,
41
+ "cyclomaticMax": 31,
42
+ "cognitiveMax": 13,
43
+ "smells": 0
44
+ },
45
+ {
46
+ "file": "src/utils/flatFileList.ts",
47
+ "lines": 100,
48
+ "cyclomaticMax": 29,
49
+ "cognitiveMax": 14,
50
+ "smells": 0
51
+ },
52
+ {
53
+ "file": "src/git/diff.ts",
54
+ "lines": 656,
55
+ "cyclomaticMax": 26,
56
+ "cognitiveMax": 41,
57
+ "smells": 0
58
+ },
59
+ {
60
+ "file": "src/git/status.ts",
61
+ "lines": 392,
62
+ "cyclomaticMax": 22,
63
+ "cognitiveMax": 25,
64
+ "smells": 0
65
+ },
66
+ {
67
+ "file": "src/ui/PaneRenderers.ts",
68
+ "lines": 249,
69
+ "cyclomaticMax": 20,
70
+ "cognitiveMax": 8,
71
+ "smells": 0
72
+ },
73
+ {
74
+ "file": "src/ui/widgets/ExplorerContent.ts",
75
+ "lines": 130,
76
+ "cyclomaticMax": 20,
77
+ "cognitiveMax": 24,
78
+ "smells": 0
79
+ },
80
+ {
81
+ "file": "src/ui/widgets/Header.ts",
82
+ "lines": 134,
83
+ "cyclomaticMax": 18,
84
+ "cognitiveMax": 22,
85
+ "smells": 0
86
+ },
87
+ {
88
+ "file": "src/MouseHandlers.ts",
89
+ "lines": 268,
90
+ "cyclomaticMax": 17,
91
+ "cognitiveMax": 15,
92
+ "smells": 0
93
+ },
94
+ {
95
+ "file": "src/ipc/CommandServer.ts",
96
+ "lines": 266,
97
+ "cyclomaticMax": 17,
98
+ "cognitiveMax": 6,
99
+ "smells": 0
100
+ },
101
+ {
102
+ "file": "src/index.ts",
103
+ "lines": 178,
104
+ "cyclomaticMax": 16,
105
+ "cognitiveMax": 17,
106
+ "smells": 0
107
+ },
108
+ {
109
+ "file": "src/ui/widgets/ExplorerView.ts",
110
+ "lines": 245,
111
+ "cyclomaticMax": 16,
112
+ "cognitiveMax": 21,
113
+ "smells": 0
114
+ },
115
+ {
116
+ "file": "src/utils/ansiTruncate.ts",
117
+ "lines": 123,
118
+ "cyclomaticMax": 16,
119
+ "cognitiveMax": 24,
120
+ "smells": 0
121
+ },
122
+ {
123
+ "file": "src/ui/widgets/DiffView.ts",
124
+ "lines": 511,
125
+ "cyclomaticMax": 15,
126
+ "cognitiveMax": 12,
127
+ "smells": 0
128
+ },
129
+ {
130
+ "file": "src/ui/widgets/CompareListView.ts",
131
+ "lines": 383,
132
+ "cyclomaticMax": 14,
133
+ "cognitiveMax": 16,
134
+ "smells": 0
135
+ },
136
+ {
137
+ "file": "src/utils/diffRowCalculations.ts",
138
+ "lines": 136,
139
+ "cyclomaticMax": 13,
140
+ "cognitiveMax": 24,
141
+ "smells": 0
142
+ },
143
+ {
144
+ "file": "src/core/ExplorerStateManager.ts",
145
+ "lines": 727,
146
+ "cyclomaticMax": 12,
147
+ "cognitiveMax": 15,
148
+ "smells": 0
149
+ },
150
+ {
151
+ "file": "src/ui/widgets/FileList.ts",
152
+ "lines": 215,
153
+ "cyclomaticMax": 12,
154
+ "cognitiveMax": 12,
155
+ "smells": 0
156
+ },
157
+ {
158
+ "file": "src/utils/lineBreaking.ts",
159
+ "lines": 114,
160
+ "cyclomaticMax": 12,
161
+ "cognitiveMax": 17,
162
+ "smells": 0
163
+ },
164
+ {
165
+ "file": "src/ui/modals/BranchPicker.ts",
166
+ "lines": 185,
167
+ "cyclomaticMax": 11,
168
+ "cognitiveMax": 17,
169
+ "smells": 0
170
+ },
171
+ {
172
+ "file": "src/config.ts",
173
+ "lines": 107,
174
+ "cyclomaticMax": 10,
175
+ "cognitiveMax": 13,
176
+ "smells": 0
177
+ },
178
+ {
179
+ "file": "src/state/UIState.ts",
180
+ "lines": 307,
181
+ "cyclomaticMax": 10,
182
+ "cognitiveMax": 12,
183
+ "smells": 0
184
+ },
185
+ {
186
+ "file": "src/KeyBindings.ts",
187
+ "lines": 373,
188
+ "cyclomaticMax": 9,
189
+ "cognitiveMax": 15,
190
+ "smells": 0
191
+ },
192
+ {
193
+ "file": "src/core/GitStateManager.ts",
194
+ "lines": 989,
195
+ "cyclomaticMax": 9,
196
+ "cognitiveMax": 12,
197
+ "smells": 0
198
+ },
199
+ {
200
+ "file": "src/ui/widgets/HistoryView.ts",
201
+ "lines": 97,
202
+ "cyclomaticMax": 9,
203
+ "cognitiveMax": 12,
204
+ "smells": 0
205
+ },
206
+ {
207
+ "file": "src/utils/formatPath.ts",
208
+ "lines": 72,
209
+ "cyclomaticMax": 9,
210
+ "cognitiveMax": 11,
211
+ "smells": 0
212
+ },
213
+ {
214
+ "file": "src/utils/explorerDisplayRows.ts",
215
+ "lines": 206,
216
+ "cyclomaticMax": 8,
217
+ "cognitiveMax": 15,
218
+ "smells": 0
219
+ },
220
+ {
221
+ "file": "src/ui/modals/FileFinder.ts",
222
+ "lines": 238,
223
+ "cyclomaticMax": 7,
224
+ "cognitiveMax": 13,
225
+ "smells": 0
226
+ },
227
+ {
228
+ "file": "src/ui/modals/BaseBranchPicker.ts",
229
+ "lines": 134,
230
+ "cyclomaticMax": 6,
231
+ "cognitiveMax": 13,
232
+ "smells": 0
233
+ },
234
+ {
235
+ "file": "src/ui/modals/StashListModal.ts",
236
+ "lines": 122,
237
+ "cyclomaticMax": 5,
238
+ "cognitiveMax": 11,
239
+ "smells": 0
240
+ }
241
+ ],
242
+ "smellsByRule": {}
243
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diffstalker",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Terminal application that displays git diff/status for directories",
5
5
  "author": "yogh-io",
6
6
  "license": "MIT",
@@ -35,7 +35,10 @@
35
35
  "deps": "depcruise src/ --config .dependency-cruiser.cjs",
36
36
  "metrics": "bun scripts/collect-metrics.ts",
37
37
  "metrics:snapshot": "bun scripts/collect-metrics.ts --save",
38
- "prepublishOnly": "bun run build:prod"
38
+ "prepublishOnly": "bun run build:prod",
39
+ "release": "scripts/release.sh",
40
+ "release:minor": "scripts/release.sh minor",
41
+ "release:major": "scripts/release.sh major"
39
42
  },
40
43
  "keywords": [
41
44
  "git",