diffstalker 0.2.2 → 0.2.4

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.
Files changed (47) hide show
  1. package/.dependency-cruiser.cjs +2 -2
  2. package/dist/App.js +299 -664
  3. package/dist/KeyBindings.js +125 -39
  4. package/dist/ModalController.js +166 -0
  5. package/dist/MouseHandlers.js +43 -25
  6. package/dist/NavigationController.js +290 -0
  7. package/dist/StagingOperations.js +199 -0
  8. package/dist/config.js +39 -0
  9. package/dist/core/CompareManager.js +134 -0
  10. package/dist/core/ExplorerStateManager.js +27 -40
  11. package/dist/core/GitStateManager.js +28 -630
  12. package/dist/core/HistoryManager.js +72 -0
  13. package/dist/core/RemoteOperationManager.js +109 -0
  14. package/dist/core/WorkingTreeManager.js +412 -0
  15. package/dist/git/status.js +95 -0
  16. package/dist/index.js +59 -54
  17. package/dist/state/FocusRing.js +40 -0
  18. package/dist/state/UIState.js +82 -48
  19. package/dist/types/remote.js +5 -0
  20. package/dist/ui/PaneRenderers.js +11 -4
  21. package/dist/ui/modals/BaseBranchPicker.js +4 -7
  22. package/dist/ui/modals/CommitActionConfirm.js +66 -0
  23. package/dist/ui/modals/DiscardConfirm.js +4 -7
  24. package/dist/ui/modals/FileFinder.js +33 -27
  25. package/dist/ui/modals/HotkeysModal.js +32 -13
  26. package/dist/ui/modals/Modal.js +1 -0
  27. package/dist/ui/modals/RepoPicker.js +109 -0
  28. package/dist/ui/modals/ThemePicker.js +4 -7
  29. package/dist/ui/widgets/CommitPanel.js +52 -14
  30. package/dist/ui/widgets/CompareListView.js +1 -11
  31. package/dist/ui/widgets/DiffView.js +2 -27
  32. package/dist/ui/widgets/ExplorerContent.js +1 -4
  33. package/dist/ui/widgets/ExplorerView.js +1 -11
  34. package/dist/ui/widgets/FileList.js +2 -8
  35. package/dist/ui/widgets/Footer.js +1 -0
  36. package/dist/ui/widgets/Header.js +37 -3
  37. package/dist/utils/ansi.js +38 -0
  38. package/dist/utils/ansiTruncate.js +1 -5
  39. package/dist/utils/displayRows.js +72 -59
  40. package/dist/utils/fileCategories.js +7 -0
  41. package/dist/utils/fileResolution.js +23 -0
  42. package/dist/utils/languageDetection.js +3 -2
  43. package/dist/utils/logger.js +32 -0
  44. package/metrics/v0.2.3.json +243 -0
  45. package/metrics/v0.2.4.json +236 -0
  46. package/package.json +5 -2
  47. package/dist/utils/layoutCalculations.js +0 -100
@@ -1,12 +1,17 @@
1
1
  import { SPLIT_RATIO_STEP } from './ui/Layout.js';
2
- import { getFileAtIndex } from './ui/widgets/FileList.js';
3
- import { getFlatFileAtIndex } from './utils/flatFileList.js';
4
2
  /**
5
3
  * Register all keyboard bindings on the blessed screen.
6
4
  */
7
5
  export function setupKeyBindings(screen, actions, ctx) {
8
- // Quit
9
- screen.key(['q', 'C-c'], () => {
6
+ // Quit: q closes modal if open, Ctrl+C always exits
7
+ screen.key(['q'], () => {
8
+ if (ctx.hasActiveModal()) {
9
+ actions.closeActiveModal();
10
+ return;
11
+ }
12
+ actions.exit();
13
+ });
14
+ screen.key(['C-c'], () => {
10
15
  actions.exit();
11
16
  });
12
17
  // Navigation (skip if modal is open)
@@ -35,11 +40,16 @@ export function setupKeyBindings(screen, actions, ctx) {
35
40
  ctx.uiState.setTab(tab);
36
41
  });
37
42
  }
38
- // Pane toggle (skip if modal is open)
43
+ // Focus zone cycling (skip if modal or commit input is active)
39
44
  screen.key(['tab'], () => {
40
- if (ctx.hasActiveModal())
45
+ if (ctx.hasActiveModal() || ctx.isCommitInputFocused())
46
+ return;
47
+ ctx.uiState.advanceFocus();
48
+ });
49
+ screen.key(['S-tab'], () => {
50
+ if (ctx.hasActiveModal() || ctx.isCommitInputFocused())
41
51
  return;
42
- ctx.uiState.togglePane();
52
+ ctx.uiState.retreatFocus();
43
53
  });
44
54
  // Staging operations (skip if modal is open)
45
55
  // Context-aware: hunk staging when diff pane is focused on diff tab
@@ -72,6 +82,17 @@ export function setupKeyBindings(screen, actions, ctx) {
72
82
  screen.key(['enter', 'space'], () => {
73
83
  if (ctx.hasActiveModal())
74
84
  return;
85
+ const zone = ctx.getFocusedZone();
86
+ // Zone-aware dispatch for commit panel elements
87
+ if (zone === 'commitMessage' && !ctx.isCommitInputFocused()) {
88
+ actions.focusCommitInput();
89
+ return;
90
+ }
91
+ if (zone === 'commitAmend') {
92
+ ctx.commitFlowState.toggleAmend();
93
+ actions.render();
94
+ return;
95
+ }
75
96
  if (ctx.getBottomTab() === 'explorer' && ctx.getCurrentPane() === 'explorer') {
76
97
  actions.enterExplorerDirectory();
77
98
  }
@@ -122,6 +143,8 @@ export function setupKeyBindings(screen, actions, ctx) {
122
143
  }
123
144
  });
124
145
  screen.key(['a'], () => {
146
+ if (ctx.hasActiveModal())
147
+ return;
125
148
  if (ctx.getBottomTab() === 'commit' && !ctx.isCommitInputFocused()) {
126
149
  ctx.commitFlowState.toggleAmend();
127
150
  actions.render();
@@ -130,7 +153,19 @@ export function setupKeyBindings(screen, actions, ctx) {
130
153
  ctx.uiState.toggleAutoTab();
131
154
  }
132
155
  });
156
+ // Ctrl+a: toggle amend on commit tab (works even when input is focused)
157
+ screen.key(['C-a'], () => {
158
+ if (ctx.getBottomTab() === 'commit') {
159
+ ctx.commitFlowState.toggleAmend();
160
+ actions.render();
161
+ }
162
+ });
163
+ // Escape: close modal first, then commit-tab escape logic
133
164
  screen.key(['escape'], () => {
165
+ if (ctx.hasActiveModal()) {
166
+ actions.closeActiveModal();
167
+ return;
168
+ }
134
169
  if (ctx.getBottomTab() === 'commit') {
135
170
  if (ctx.isCommitInputFocused()) {
136
171
  actions.unfocusCommitInput();
@@ -140,12 +175,32 @@ export function setupKeyBindings(screen, actions, ctx) {
140
175
  }
141
176
  }
142
177
  });
143
- // Refresh
144
- screen.key(['r'], () => actions.refresh());
145
- // Display toggles
146
- screen.key(['w'], () => ctx.uiState.toggleWrapMode());
147
- screen.key(['m'], () => actions.toggleMouseMode());
148
- screen.key(['S-t'], () => ctx.uiState.toggleAutoTab());
178
+ // Repo picker (toggle)
179
+ screen.key(['r'], () => {
180
+ if (ctx.getActiveModalType() === 'repoPicker') {
181
+ actions.closeActiveModal();
182
+ return;
183
+ }
184
+ if (ctx.hasActiveModal())
185
+ return;
186
+ actions.openRepoPicker();
187
+ });
188
+ // Display toggles (guarded)
189
+ screen.key(['w'], () => {
190
+ if (ctx.hasActiveModal())
191
+ return;
192
+ ctx.uiState.toggleWrapMode();
193
+ });
194
+ screen.key(['m'], () => {
195
+ if (ctx.hasActiveModal())
196
+ return;
197
+ actions.toggleMouseMode();
198
+ });
199
+ screen.key(['S-t'], () => {
200
+ if (ctx.hasActiveModal())
201
+ return;
202
+ ctx.uiState.toggleAutoTab();
203
+ });
149
204
  // Split ratio adjustments
150
205
  screen.key(['-', '_', '['], () => {
151
206
  ctx.uiState.adjustSplitRatio(-SPLIT_RATIO_STEP);
@@ -157,16 +212,42 @@ export function setupKeyBindings(screen, actions, ctx) {
157
212
  ctx.layout.setSplitRatio(ctx.uiState.state.splitRatio);
158
213
  actions.render();
159
214
  });
160
- // Theme picker
161
- screen.key(['t'], () => ctx.uiState.openModal('theme'));
162
- // Hotkeys modal
163
- screen.key(['?'], () => ctx.uiState.toggleModal('hotkeys'));
164
- // Follow toggle
165
- screen.key(['f'], () => actions.toggleFollow());
166
- // Compare view: base branch picker
215
+ // Theme picker (toggle)
216
+ screen.key(['t'], () => {
217
+ if (ctx.getActiveModalType() === 'theme') {
218
+ actions.closeActiveModal();
219
+ return;
220
+ }
221
+ if (ctx.hasActiveModal())
222
+ return;
223
+ actions.openThemePicker();
224
+ });
225
+ // Hotkeys modal (toggle)
226
+ screen.key(['?'], () => {
227
+ if (ctx.getActiveModalType() === 'hotkeys') {
228
+ actions.closeActiveModal();
229
+ return;
230
+ }
231
+ if (ctx.hasActiveModal())
232
+ return;
233
+ actions.openHotkeysModal();
234
+ });
235
+ // Follow toggle (guarded)
236
+ screen.key(['f'], () => {
237
+ if (ctx.hasActiveModal())
238
+ return;
239
+ actions.toggleFollow();
240
+ });
241
+ // Compare view: base branch picker (toggle)
167
242
  screen.key(['b'], () => {
243
+ if (ctx.getActiveModalType() === 'baseBranch') {
244
+ actions.closeActiveModal();
245
+ return;
246
+ }
247
+ if (ctx.hasActiveModal())
248
+ return;
168
249
  if (ctx.getBottomTab() === 'compare') {
169
- ctx.uiState.openModal('baseBranch');
250
+ actions.openBaseBranchPicker();
170
251
  }
171
252
  });
172
253
  // u: toggle uncommitted in compare view
@@ -176,7 +257,7 @@ export function setupKeyBindings(screen, actions, ctx) {
176
257
  if (ctx.getBottomTab() === 'compare') {
177
258
  ctx.uiState.toggleIncludeUncommitted();
178
259
  const includeUncommitted = ctx.uiState.state.includeUncommitted;
179
- ctx.getGitManager()?.refreshCompareDiff(includeUncommitted);
260
+ ctx.getGitManager()?.compare.refreshCompareDiff(includeUncommitted);
180
261
  }
181
262
  });
182
263
  // Toggle flat file view (diff/commit tab only)
@@ -188,25 +269,14 @@ export function setupKeyBindings(screen, actions, ctx) {
188
269
  ctx.uiState.toggleFlatViewMode();
189
270
  }
190
271
  });
191
- // Discard changes (with confirmation)
272
+ // Discard changes (with confirmation, guarded)
192
273
  screen.key(['d'], () => {
274
+ if (ctx.hasActiveModal())
275
+ return;
193
276
  if (ctx.getBottomTab() === 'diff') {
194
- if (ctx.uiState.state.flatViewMode) {
195
- const flatEntry = getFlatFileAtIndex(ctx.getCachedFlatFiles(), ctx.getSelectedIndex());
196
- if (flatEntry?.unstagedEntry) {
197
- const file = flatEntry.unstagedEntry;
198
- if (file.status !== 'untracked') {
199
- actions.showDiscardConfirm(file);
200
- }
201
- }
202
- }
203
- else {
204
- const files = ctx.getStatusFiles();
205
- const selectedFile = getFileAtIndex(files, ctx.getSelectedIndex());
206
- // Only allow discard for unstaged modified files
207
- if (selectedFile && !selectedFile.staged && selectedFile.status !== 'untracked') {
208
- actions.showDiscardConfirm(selectedFile);
209
- }
277
+ const file = ctx.resolveFileAtIndex(ctx.getSelectedIndex());
278
+ if (file && !file.staged && file.status !== 'untracked') {
279
+ actions.openDiscardConfirm(file);
210
280
  }
211
281
  }
212
282
  });
@@ -225,4 +295,20 @@ export function setupKeyBindings(screen, actions, ctx) {
225
295
  actions.navigatePrevHunk();
226
296
  }
227
297
  });
298
+ // Cherry-pick selected commit (history tab only)
299
+ screen.key(['p'], () => {
300
+ if (ctx.hasActiveModal() || ctx.isCommitInputFocused())
301
+ return;
302
+ if (ctx.getBottomTab() === 'history') {
303
+ actions.openCherryPickConfirm();
304
+ }
305
+ });
306
+ // Revert selected commit (history tab only)
307
+ screen.key(['v'], () => {
308
+ if (ctx.hasActiveModal() || ctx.isCommitInputFocused())
309
+ return;
310
+ if (ctx.getBottomTab() === 'history') {
311
+ actions.openRevertConfirm();
312
+ }
313
+ });
228
314
  }
@@ -0,0 +1,166 @@
1
+ import { ThemePicker } from './ui/modals/ThemePicker.js';
2
+ import { HotkeysModal } from './ui/modals/HotkeysModal.js';
3
+ import { BaseBranchPicker } from './ui/modals/BaseBranchPicker.js';
4
+ import { DiscardConfirm } from './ui/modals/DiscardConfirm.js';
5
+ import { FileFinder } from './ui/modals/FileFinder.js';
6
+ import { CommitActionConfirm } from './ui/modals/CommitActionConfirm.js';
7
+ import { RepoPicker } from './ui/modals/RepoPicker.js';
8
+ import { saveConfig } from './config.js';
9
+ import * as logger from './utils/logger.js';
10
+ /**
11
+ * Manages all modal dialogs: creation, focus, and dismissal.
12
+ * Single source of truth for modal state.
13
+ */
14
+ export class ModalController {
15
+ ctx;
16
+ activeModal = null;
17
+ activeModalType = null;
18
+ constructor(ctx) {
19
+ this.ctx = ctx;
20
+ }
21
+ hasActiveModal() {
22
+ return this.activeModal !== null;
23
+ }
24
+ getActiveModalType() {
25
+ return this.activeModalType;
26
+ }
27
+ closeActiveModal() {
28
+ if (this.activeModal) {
29
+ this.activeModal.destroy();
30
+ this.activeModal = null;
31
+ this.activeModalType = null;
32
+ this.ctx.render();
33
+ }
34
+ }
35
+ clearModal() {
36
+ this.activeModal = null;
37
+ this.activeModalType = null;
38
+ }
39
+ openThemePicker() {
40
+ this.activeModalType = 'theme';
41
+ this.activeModal = new ThemePicker(this.ctx.screen, this.ctx.getCurrentTheme(), (theme) => {
42
+ this.ctx.setCurrentTheme(theme);
43
+ saveConfig({ theme });
44
+ this.clearModal();
45
+ this.ctx.render();
46
+ }, () => {
47
+ this.clearModal();
48
+ });
49
+ this.activeModal.focus();
50
+ }
51
+ openHotkeysModal() {
52
+ this.activeModalType = 'hotkeys';
53
+ this.activeModal = new HotkeysModal(this.ctx.screen, () => {
54
+ this.clearModal();
55
+ });
56
+ this.activeModal.focus();
57
+ }
58
+ openBaseBranchPicker() {
59
+ const gm = this.ctx.getGitManager();
60
+ if (!gm)
61
+ return;
62
+ this.activeModalType = 'baseBranch';
63
+ gm.compare
64
+ .getCandidateBaseBranches()
65
+ .then((branches) => {
66
+ const currentBranch = gm.compare.compareState.compareBaseBranch ?? null;
67
+ const modal = new BaseBranchPicker(this.ctx.screen, branches, currentBranch, (branch) => {
68
+ this.clearModal();
69
+ const includeUncommitted = this.ctx.uiState.state.includeUncommitted;
70
+ gm.compare.setCompareBaseBranch(branch, includeUncommitted);
71
+ }, () => {
72
+ this.clearModal();
73
+ });
74
+ this.activeModal = modal;
75
+ modal.focus();
76
+ })
77
+ .catch((err) => {
78
+ this.clearModal();
79
+ logger.error('Failed to load base branches', err);
80
+ });
81
+ }
82
+ openDiscardConfirm(file) {
83
+ this.activeModalType = 'discard';
84
+ this.activeModal = new DiscardConfirm(this.ctx.screen, file.path, async () => {
85
+ this.clearModal();
86
+ await this.ctx.getGitManager()?.workingTree.discard(file);
87
+ }, () => {
88
+ this.clearModal();
89
+ });
90
+ this.activeModal.focus();
91
+ }
92
+ async openFileFinder() {
93
+ const explorer = this.ctx.getExplorerManager();
94
+ let allPaths = explorer?.getCachedFilePaths() ?? [];
95
+ if (allPaths.length === 0) {
96
+ await explorer?.loadFilePaths();
97
+ allPaths = explorer?.getCachedFilePaths() ?? [];
98
+ }
99
+ if (allPaths.length === 0)
100
+ return;
101
+ this.activeModalType = 'fileFinder';
102
+ this.activeModal = new FileFinder(this.ctx.screen, allPaths, async (selectedPath) => {
103
+ this.clearModal();
104
+ if (this.ctx.uiState.state.bottomTab !== 'explorer') {
105
+ this.ctx.uiState.setTab('explorer');
106
+ }
107
+ const success = await explorer?.navigateToPath(selectedPath);
108
+ if (success) {
109
+ const selectedIndex = explorer?.state.selectedIndex ?? 0;
110
+ this.ctx.uiState.setExplorerSelectedIndex(selectedIndex);
111
+ this.ctx.uiState.setExplorerFileScrollOffset(0);
112
+ const visibleHeight = this.ctx.getTopPaneHeight();
113
+ if (selectedIndex >= visibleHeight) {
114
+ this.ctx.uiState.setExplorerScrollOffset(selectedIndex - Math.floor(visibleHeight / 2));
115
+ }
116
+ else {
117
+ this.ctx.uiState.setExplorerScrollOffset(0);
118
+ }
119
+ }
120
+ this.ctx.render();
121
+ }, () => {
122
+ this.clearModal();
123
+ this.ctx.render();
124
+ });
125
+ this.activeModal.focus();
126
+ }
127
+ openCherryPickConfirm() {
128
+ const commit = this.ctx.getGitManager()?.history.historyState.selectedCommit;
129
+ if (!commit)
130
+ return;
131
+ this.activeModalType = 'commitAction';
132
+ this.activeModal = new CommitActionConfirm(this.ctx.screen, 'Cherry-pick', commit, () => {
133
+ this.clearModal();
134
+ this.ctx.getGitManager()?.remote.cherryPick(commit.hash);
135
+ }, () => {
136
+ this.clearModal();
137
+ });
138
+ this.activeModal.focus();
139
+ }
140
+ openRevertConfirm() {
141
+ const commit = this.ctx.getGitManager()?.history.historyState.selectedCommit;
142
+ if (!commit)
143
+ return;
144
+ this.activeModalType = 'commitAction';
145
+ this.activeModal = new CommitActionConfirm(this.ctx.screen, 'Revert', commit, () => {
146
+ this.clearModal();
147
+ this.ctx.getGitManager()?.remote.revertCommit(commit.hash);
148
+ }, () => {
149
+ this.clearModal();
150
+ });
151
+ this.activeModal.focus();
152
+ }
153
+ openRepoPicker() {
154
+ const repos = this.ctx.getRecentRepos();
155
+ const currentRepo = this.ctx.getRepoPath();
156
+ this.activeModalType = 'repoPicker';
157
+ this.activeModal = new RepoPicker(this.ctx.screen, repos, currentRepo, (selected) => {
158
+ this.clearModal();
159
+ this.ctx.onRepoSwitch(selected);
160
+ }, () => {
161
+ this.clearModal();
162
+ this.ctx.render();
163
+ });
164
+ this.activeModal.focus();
165
+ }
166
+ }
@@ -1,3 +1,4 @@
1
+ import { TAB_ZONES } from './state/UIState.js';
1
2
  import { getFileListTotalRows, getFileIndexFromRow } from './ui/widgets/FileList.js';
2
3
  import { getFlatFileListTotalRows } from './ui/widgets/FlatFileList.js';
3
4
  import { getCompareListTotalRows, getCompareSelectionFromRow, } from './ui/widgets/CompareListView.js';
@@ -22,18 +23,28 @@ export function setupMouseHandlers(layout, actions, ctx) {
22
23
  layout.bottomPane.on('wheelup', () => {
23
24
  handleBottomPaneScroll(-SCROLL_AMOUNT, layout, ctx);
24
25
  });
25
- // Click on top pane to select item
26
+ // Click on top pane to select item (does NOT change focus zone —
27
+ // preserves diff pane focus so hunk staging with 's' keeps working)
26
28
  layout.topPane.on('click', (mouse) => {
27
29
  const clickedRow = layout.screenYToTopPaneRow(mouse.y);
28
30
  if (clickedRow >= 0) {
29
31
  handleTopPaneClick(clickedRow, mouse.x, actions, ctx);
30
32
  }
31
33
  });
32
- // Click on bottom pane to select hunk (diff tab)
34
+ // Click on bottom pane
33
35
  layout.bottomPane.on('click', (mouse) => {
34
36
  const clickedRow = layout.screenYToBottomPaneRow(mouse.y);
35
37
  if (clickedRow >= 0) {
36
- actions.selectHunkAtRow(clickedRow);
38
+ if (ctx.uiState.state.bottomTab === 'commit') {
39
+ handleCommitPaneClick(clickedRow, actions, ctx);
40
+ }
41
+ else {
42
+ // Set focus to the bottom-pane zone for this tab
43
+ const zones = TAB_ZONES[ctx.uiState.state.bottomTab];
44
+ const bottomZone = zones[zones.length - 1];
45
+ ctx.uiState.setFocusedZone(bottomZone);
46
+ actions.selectHunkAtRow(clickedRow);
47
+ }
37
48
  }
38
49
  });
39
50
  // Click on footer for tabs and toggles
@@ -43,33 +54,40 @@ export function setupMouseHandlers(layout, actions, ctx) {
43
54
  }
44
55
  function handleFileListClick(row, x, actions, ctx) {
45
56
  const state = ctx.uiState.state;
57
+ // Row-to-index mapping differs between flat and categorized mode
58
+ let fileIndex;
46
59
  if (state.flatViewMode) {
47
60
  // Flat mode: row 0 is header, files start at row 1
48
61
  const absoluteRow = row + state.fileListScrollOffset;
49
- const fileIndex = absoluteRow - 1; // subtract header row
62
+ const idx = absoluteRow - 1; // subtract header row
50
63
  const flatFiles = ctx.getCachedFlatFiles();
51
- if (fileIndex < 0 || fileIndex >= flatFiles.length)
52
- return;
53
- if (x !== undefined && x >= 2 && x <= 4) {
54
- actions.toggleFileByIndex(fileIndex);
55
- }
56
- else {
57
- ctx.uiState.setSelectedIndex(fileIndex);
58
- actions.selectFileByIndex(fileIndex);
59
- }
64
+ fileIndex = idx >= 0 && idx < flatFiles.length ? idx : null;
60
65
  }
61
66
  else {
62
- const files = ctx.getStatusFiles();
63
- const fileIndex = getFileIndexFromRow(row + state.fileListScrollOffset, files);
64
- if (fileIndex === null || fileIndex < 0)
65
- return;
66
- if (x !== undefined && x >= 2 && x <= 4) {
67
- actions.toggleFileByIndex(fileIndex);
68
- }
69
- else {
70
- ctx.uiState.setSelectedIndex(fileIndex);
71
- actions.selectFileByIndex(fileIndex);
72
- }
67
+ fileIndex = getFileIndexFromRow(row + state.fileListScrollOffset, ctx.getStatusFiles());
68
+ }
69
+ if (fileIndex === null || fileIndex < 0)
70
+ return;
71
+ if (x !== undefined && x >= 2 && x <= 4) {
72
+ actions.toggleFileByIndex(fileIndex);
73
+ }
74
+ else {
75
+ ctx.uiState.setSelectedIndex(fileIndex);
76
+ actions.selectFileByIndex(fileIndex);
77
+ }
78
+ }
79
+ function handleCommitPaneClick(row, actions, ctx) {
80
+ // Commit panel layout: rows 0-4 = title + message box, row 5 = blank, row 6 = amend
81
+ const absoluteRow = row + ctx.uiState.state.diffScrollOffset;
82
+ if (absoluteRow === 6) {
83
+ ctx.uiState.setFocusedZone('commitAmend');
84
+ }
85
+ else if (absoluteRow >= 2 && absoluteRow <= 4) {
86
+ ctx.uiState.setFocusedZone('commitMessage');
87
+ actions.focusCommitInput();
88
+ }
89
+ else {
90
+ ctx.uiState.setFocusedZone('commitMessage');
73
91
  }
74
92
  }
75
93
  function handleTopPaneClick(row, x, actions, ctx) {
@@ -140,7 +158,7 @@ function handleFooterClick(x, actions, ctx) {
140
158
  ctx.getExplorerManager()?.toggleShowOnlyChanges();
141
159
  }
142
160
  else if (x === 0) {
143
- ctx.uiState.openModal('hotkeys');
161
+ actions.openHotkeysModal();
144
162
  }
145
163
  }
146
164
  function handleTopPaneScroll(delta, layout, ctx) {