reslop 0.2.5 → 0.2.6

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 (105) hide show
  1. package/CHANGELOG.md +12 -1
  2. package/LICENSE +1 -1
  3. package/README.md +11 -11
  4. package/bin/reslop.js +3 -8
  5. package/lib/ansi.js +32 -19
  6. package/lib/capabilities.js +8 -8
  7. package/lib/cli.js +40 -74
  8. package/lib/clipboard.js +39 -47
  9. package/lib/dashboard/git.js +128 -0
  10. package/lib/dashboard/model.js +332 -0
  11. package/lib/dashboard/npm.js +193 -0
  12. package/lib/dashboard/tree.js +278 -0
  13. package/lib/deps-diff.js +197 -402
  14. package/lib/deps.js +113 -240
  15. package/lib/detect.js +8 -18
  16. package/lib/diff/align.js +41 -113
  17. package/lib/diff/diff.js +12 -43
  18. package/lib/diff/display.js +25 -36
  19. package/lib/diff/edit.js +6 -20
  20. package/lib/diff/inline.js +76 -144
  21. package/lib/diff/items.js +33 -47
  22. package/lib/diff/parse.js +28 -56
  23. package/lib/diff/patch.js +12 -33
  24. package/lib/diff/unit.js +79 -178
  25. package/lib/editor.js +52 -69
  26. package/lib/files.js +71 -89
  27. package/lib/git-branches.js +254 -416
  28. package/lib/git-deps.js +147 -272
  29. package/lib/git-reword-todo.js +5 -13
  30. package/lib/git-worktree.js +141 -212
  31. package/lib/git.js +21 -49
  32. package/lib/github.js +67 -181
  33. package/lib/gitlab.js +40 -90
  34. package/lib/highlight/bash.js +55 -177
  35. package/lib/highlight/core.js +155 -141
  36. package/lib/highlight/css.js +25 -58
  37. package/lib/highlight/csv.js +19 -37
  38. package/lib/highlight/dart.js +72 -213
  39. package/lib/highlight/dot.js +14 -14
  40. package/lib/highlight/html.js +42 -63
  41. package/lib/highlight/js.js +177 -506
  42. package/lib/highlight/json.js +17 -37
  43. package/lib/highlight/log.js +19 -25
  44. package/lib/highlight/txt.js +7 -10
  45. package/lib/highlight.js +17 -46
  46. package/lib/keys.js +49 -82
  47. package/lib/manifest.js +67 -126
  48. package/lib/npm-commands.js +120 -169
  49. package/lib/remote.js +140 -190
  50. package/lib/render/branches.js +63 -61
  51. package/lib/render/chrome.js +220 -333
  52. package/lib/render/commits.js +160 -361
  53. package/lib/render/dash-activity.js +212 -0
  54. package/lib/render/dash-blocks.js +245 -0
  55. package/lib/render/dash-table.js +290 -0
  56. package/lib/render/dashboard.js +62 -0
  57. package/lib/render/diff.js +123 -258
  58. package/lib/render/files.js +40 -85
  59. package/lib/render/frame.js +78 -117
  60. package/lib/render/notes.js +139 -210
  61. package/lib/render/npm.js +213 -177
  62. package/lib/render/present.js +4 -11
  63. package/lib/render/primitives.js +72 -174
  64. package/lib/render/render.js +5 -14
  65. package/lib/render/report.js +43 -0
  66. package/lib/render/tiles.js +260 -0
  67. package/lib/render/unit.js +15 -31
  68. package/lib/report-model.js +110 -33
  69. package/lib/report-parse.js +56 -37
  70. package/lib/report-render.js +102 -59
  71. package/lib/report-run.js +87 -77
  72. package/lib/report-test.js +221 -99
  73. package/lib/report-tools.js +96 -154
  74. package/lib/review-merge.js +33 -41
  75. package/lib/review.js +68 -129
  76. package/lib/runs.js +225 -0
  77. package/lib/select.js +53 -91
  78. package/lib/session/accessors.js +14 -21
  79. package/lib/session/actions.js +61 -43
  80. package/lib/session/branches.js +166 -222
  81. package/lib/session/change-actions.js +49 -119
  82. package/lib/session/commit.js +274 -355
  83. package/lib/session/compose.js +556 -714
  84. package/lib/session/dashboard.js +385 -0
  85. package/lib/session/dispatch.js +162 -170
  86. package/lib/session/files-pane.js +76 -44
  87. package/lib/session/items.js +6 -17
  88. package/lib/session/lifecycle.js +393 -466
  89. package/lib/session/load.js +1 -14
  90. package/lib/session/navigation.js +2 -2
  91. package/lib/session/npm.js +403 -420
  92. package/lib/session/ops.js +5 -13
  93. package/lib/session/pointer.js +16 -14
  94. package/lib/session/progress.js +39 -30
  95. package/lib/session/review.js +1 -3
  96. package/lib/session/tasks.js +276 -235
  97. package/lib/session/templates.js +51 -59
  98. package/lib/session/terminal.js +85 -132
  99. package/lib/session/view.js +47 -81
  100. package/lib/session/watch.js +196 -212
  101. package/lib/session.js +81 -161
  102. package/lib/update.js +44 -111
  103. package/lib/utilities.js +46 -88
  104. package/lib/wrap.js +29 -55
  105. package/package.json +1 -1
@@ -1,200 +1,192 @@
1
1
  'use strict';
2
2
 
3
3
  const compose = require('./compose.js');
4
+ const { COMPOSE_LEAVE } = compose;
4
5
  const commit = require('./commit.js');
5
6
  const { hasStagedChanges, listedCommits } = commit;
6
- const { COMPOSE_LEAVE } = compose;
7
7
  const actions = require('./actions.js');
8
- const {
9
- LIST_PANES,
10
- CONFIRM_MODE,
11
- disabledActions,
12
- actionFromKey,
13
- applyConfirm,
14
- } = actions;
8
+ const { LIST_PANES, DASH_BLOCKS, DASH_ALLOWED, CONFIRM_MODE } = actions;
9
+ const { disabledActions, actionFromKey, confirmKey } = actions;
10
+ const changeActions = require('./change-actions.js');
11
+ const { onCommand } = changeActions;
12
+ const filesPane = require('./files-pane.js');
13
+ const pointer = require('./pointer.js');
14
+ const { handlePointer } = pointer;
15
15
 
16
16
  const currentTarget = (ui) => {
17
17
  if (ui.pane === 'files') return ui.fileCursorEntry();
18
- if (ui.pane === 'branches') {
19
- return ui.branches[ui.branchCursor];
20
- }
18
+ if (ui.pane === 'branches') return ui.branches[ui.branchCursor];
21
19
  if (ui.pane === 'commits') {
22
20
  const entry = listedCommits(ui.commits.commits)[ui.commitCursor];
23
- const staged = hasStagedChanges(ui);
21
+ const canCommit = hasStagedChanges(ui);
24
22
  const unstagedCurrent = !ui.rev && !ui.revShort;
25
- return { ...entry, canCommit: staged, unstagedCurrent };
23
+ return { ...entry, canCommit, unstagedCurrent };
26
24
  }
27
25
  return ui.current();
28
26
  };
29
27
 
30
- const createDispatcher = (ui) => {
31
- const handlers = {
32
- add: ui.changeActions.onAdd,
33
- unstage: ui.changeActions.onUnstage,
34
- revert: ui.changeActions.onRevert,
35
- ignore: ui.changeActions.onIgnore,
36
- next: ui.filesPane.onNext,
37
- prev: ui.filesPane.onPrev,
38
- layout: ui.filesPane.onLayout,
39
- theme: ui.filesPane.onTheme,
40
- files: ui.filesPane.onFiles,
41
- feedback: ui.composer.onFeedback,
42
- tasks: ui.composer.onTasks,
43
- code: ui.composer.onCode,
44
- reload: ui.lifecycle.onReload,
45
- commit: ui.commits.onCommit,
46
- amend: ui.commits.onAmend,
47
- reword: ui.commits.onReword,
48
- apply: ui.commits.onApply,
49
- fixup: ui.commits.onFixup,
50
- view: () => ui.commits.onView(),
51
- branch: ui.gitBranches.onBranch,
52
- pull: ui.gitBranches.onPull,
53
- push: ui.gitBranches.onPush,
54
- newBranch: ui.gitBranches.onNewBranch,
55
- rebase: ui.gitBranches.onRebaseBranch,
56
- drop: () => {
57
- if (ui.pane === 'commits') {
58
- return void ui.commits.onDropCommit();
59
- }
60
- ui.gitBranches.onDropBranch();
61
- },
62
- open: ui.filesPane.onOpenFile,
63
- file: ui.filesPane.onFileScope,
64
- diff: ui.filesPane.onDiffScope,
65
- npm: ui.npm.open,
66
- npmEdit: ui.npm.edit,
67
- npmNew: ui.npm.create,
68
- npmDrop: () => ui.npm.askDrop(),
69
- npmLogs: () => ui.npm.askLogs(),
70
- npmStop: () => ui.npm.closeView(),
71
- back: () => ui.filesPane.onEscape(),
72
- npmVerbose: () => ui.npm.toggleVerbose(),
73
- npmRerun: () => ui.npm.rerun(),
74
- check: () => ui.composer.toggleFocusedTask(),
75
- removeTask: () => {
76
- if (ui.pane === 'npm') return void ui.npm.askDrop();
77
- ui.composer.removeFocusedTask();
78
- },
79
- quit: () => ui.onQuit(),
80
- scrollUp: () => ui.filesPane.onScroll(-1),
81
- scrollDown: () => ui.filesPane.onScroll(1),
82
- pageUp: () => ui.filesPane.onScroll(ui.filesPane.scrollStep(-1)),
83
- pageDown: () => ui.filesPane.onScroll(ui.filesPane.scrollStep(1)),
84
- halfUp: () => ui.filesPane.onScroll(ui.filesPane.scrollStep(-0.5)),
85
- halfDown: () => ui.filesPane.onScroll(ui.filesPane.scrollStep(0.5)),
86
- home: ui.filesPane.onHome,
87
- end: ui.filesPane.onEnd,
88
- };
28
+ const scrollBy = (fraction) => (ui) =>
29
+ filesPane.onScroll(ui, filesPane.scrollStep(ui, fraction));
89
30
 
90
- const dispatch = (action) => {
91
- if (CONFIRM_MODE.includes(ui.mode)) return;
92
- if (ui.mode === 'compose') {
93
- if (!COMPOSE_LEAVE.includes(action)) return;
94
- const result = ui.composer.saveCompose();
95
- if (result && result.kind === 'error') return;
96
- }
97
- const pane = ui.pane;
98
- if (LIST_PANES.includes(pane) && (action === 'next' || action === 'prev')) {
99
- ui.filesPane.onScroll(action === 'next' ? 1 : -1);
100
- return;
101
- }
102
- const hidden = disabledActions(pane, currentTarget(ui));
103
- if (hidden.includes(action)) return;
104
- const handler = handlers[action];
105
- if (!handler) return;
106
- handler();
107
- };
31
+ const DASH_HANDLERS = Object.fromEntries(
32
+ Object.entries(DASH_BLOCKS).map(([id, block]) => [
33
+ id,
34
+ (ui) => ui.dashboard.open(block),
35
+ ]),
36
+ );
108
37
 
109
- return { dispatch, handlers };
38
+ const HANDLERS = {
39
+ add: (ui) => onCommand(ui, 'add'),
40
+ unstage: (ui) => onCommand(ui, 'unstage'),
41
+ revert: (ui) => onCommand(ui, 'revert'),
42
+ ignore: (ui) => onCommand(ui, 'ignore'),
43
+ next: filesPane.onNext,
44
+ prev: filesPane.onPrev,
45
+ layout: filesPane.onLayout,
46
+ theme: filesPane.onTheme,
47
+ files: filesPane.onFiles,
48
+ open: filesPane.onOpenFile,
49
+ file: filesPane.onFileScope,
50
+ diff: filesPane.onDiffScope,
51
+ back: filesPane.onEscape,
52
+ home: filesPane.onHome,
53
+ end: filesPane.onEnd,
54
+ scrollUp: (ui) => filesPane.onScroll(ui, -1),
55
+ scrollDown: (ui) => filesPane.onScroll(ui, 1),
56
+ pageUp: scrollBy(-1),
57
+ pageDown: scrollBy(1),
58
+ halfUp: scrollBy(-0.5),
59
+ halfDown: scrollBy(0.5),
60
+ feedback: (ui) => ui.composer.onFeedback(),
61
+ code: (ui) => ui.composer.onCode(),
62
+ tasks: (ui) => ui.composer.tasks.onTasks(),
63
+ check: (ui) => ui.composer.tasks.toggleFocusedTask(),
64
+ removeTask: (ui) => {
65
+ if (ui.pane === 'npm') return void ui.npm.askDrop();
66
+ ui.composer.tasks.removeFocusedTask();
67
+ },
68
+ reload: (ui) => ui.lifecycle.onReload(),
69
+ commit: (ui) => ui.commits.onCommit(),
70
+ amend: (ui) => ui.commits.onAmend(),
71
+ reword: (ui) => ui.commits.onReword(),
72
+ apply: (ui) => ui.commits.onApply(),
73
+ fixup: (ui) => ui.commits.onFixup(),
74
+ view: (ui) => ui.commits.onView(),
75
+ branch: (ui) => ui.gitBranches.onBranch(),
76
+ pull: (ui) => ui.gitBranches.onPull(),
77
+ push: (ui) => ui.gitBranches.onPush(),
78
+ newBranch: (ui) => ui.gitBranches.onNewBranch(),
79
+ rebase: (ui) => ui.gitBranches.onRebaseBranch(),
80
+ drop: (ui) => {
81
+ if (ui.pane === 'commits') return void ui.commits.onDropCommit();
82
+ if (ui.pane === 'tasks') return void ui.composer.tasks.removeFocusedTask();
83
+ ui.gitBranches.onDropBranch();
84
+ },
85
+ npm: (ui) => ui.npm.open(),
86
+ npmEdit: (ui) => ui.npm.edit(),
87
+ npmNew: (ui) => ui.npm.create(),
88
+ npmDrop: (ui) => ui.npm.askDrop(),
89
+ npmLogs: (ui) => ui.npm.askLogs(),
90
+ npmStop: (ui) => ui.npm.closeView(),
91
+ npmVerbose: (ui) => ui.npm.toggleVerbose(),
92
+ npmRerun: (ui) => ui.npm.rerun(),
93
+ quit: (ui) => ui.onQuit(),
94
+ ...DASH_HANDLERS,
110
95
  };
111
96
 
112
- const createInput = (ui) => {
113
- const confirmations = {
114
- confirmQuit: {
115
- ready: () => ui.finishQuit('ready'),
116
- editing: () => ui.finishQuit('editing'),
117
- cancel: () => ui.cancelQuit(),
118
- },
119
- confirmUpdate: {
120
- accept: () => ui.updater.accept(),
121
- decline: () => ui.updater.decline(),
122
- },
123
- confirmDrop: {
124
- confirm: () => {
125
- if (ui.npm.dropName) return void ui.npm.confirmDrop();
126
- if (ui.commits.dropName) {
127
- return void ui.commits.confirmDropCommit();
128
- }
129
- ui.gitBranches.confirmDropBranch();
130
- },
131
- cancel: () => {
132
- if (ui.npm.dropName) return void ui.npm.cancelDrop();
133
- if (ui.commits.dropName) {
134
- return void ui.commits.cancelDropCommit();
135
- }
136
- ui.gitBranches.cancelDropBranch();
137
- },
97
+ const dispatchAction = (ui, action) => {
98
+ if (CONFIRM_MODE.includes(ui.mode)) return;
99
+ if (ui.mode === 'compose') {
100
+ if (!COMPOSE_LEAVE.includes(action)) return;
101
+ const result = ui.composer.saveCompose();
102
+ if (result && result.kind === 'error') return;
103
+ }
104
+ const pane = ui.pane;
105
+ if (pane === 'dashboard' && !DASH_ALLOWED.includes(action)) return;
106
+ if (LIST_PANES.includes(pane) && (action === 'next' || action === 'prev')) {
107
+ return void filesPane.onScroll(ui, action === 'next' ? 1 : -1);
108
+ }
109
+ if (disabledActions(pane, currentTarget(ui)).includes(action)) return;
110
+ const handler = HANDLERS[action];
111
+ if (handler) handler(ui);
112
+ };
113
+
114
+ const CONFIRMATIONS = {
115
+ confirmQuit: {
116
+ ready: (ui) => ui.finishQuit('ready'),
117
+ editing: (ui) => ui.finishQuit('editing'),
118
+ cancel: (ui) => ui.cancelQuit(),
119
+ },
120
+ confirmUpdate: {
121
+ accept: (ui) => ui.updater.accept(),
122
+ decline: (ui) => ui.updater.decline(),
123
+ },
124
+ confirmDrop: {
125
+ confirm: (ui) => {
126
+ if (ui.npm.dropName) return void ui.npm.confirmDrop();
127
+ if (ui.commits.dropName) return void ui.commits.confirmDropCommit();
128
+ ui.gitBranches.confirmDropBranch();
138
129
  },
139
- confirmPush: {
140
- force: ui.gitBranches.onForcePush,
141
- cancel: ui.gitBranches.cancelPush,
130
+ cancel: (ui) => {
131
+ if (ui.npm.dropName) return void ui.npm.cancelDrop();
132
+ if (ui.commits.dropName) return void ui.commits.cancelDropCommit();
133
+ ui.gitBranches.cancelDropBranch();
142
134
  },
143
- };
144
-
145
- const confirmChoice = (key) => {
146
- const mode = ui.mode;
147
- if (!CONFIRM_MODE.includes(mode)) return;
148
- applyConfirm(mode, key, confirmations[mode]);
149
- };
135
+ },
136
+ confirmPush: {
137
+ force: (ui) => ui.gitBranches.onForcePush(),
138
+ cancel: (ui) => ui.gitBranches.cancelPush(),
139
+ },
140
+ };
150
141
 
151
- const npmViewKeys = {
152
- escape: () => ui.npm.closeView(),
153
- r: () => ui.npm.rerun(),
154
- v: () => ui.npm.toggleVerbose(),
155
- e: () => {},
156
- n: () => {},
157
- };
142
+ const confirmChoice = (ui, key) => {
143
+ const handlers = CONFIRMATIONS[ui.mode];
144
+ const action = handlers ? confirmKey(ui.mode, key) : null;
145
+ if (action && handlers[action]) handlers[action](ui);
146
+ };
158
147
 
159
- const npmListKeys = {
160
- 'ctrl-up': () => ui.npm.reorder(-1),
161
- 'ctrl-down': () => ui.npm.reorder(1),
162
- up: () => ui.npm.move(-1),
163
- down: () => ui.npm.move(1),
164
- };
148
+ const NPM_VIEW_KEYS = {
149
+ escape: (ui) => ui.npm.closeView(),
150
+ r: (ui) => ui.npm.rerun(),
151
+ v: (ui) => ui.npm.toggleVerbose(),
152
+ e: () => {},
153
+ i: () => {},
154
+ };
165
155
 
166
- const npmKey = (key) => {
167
- if (ui.pane !== 'npm') return null;
168
- if (ui.npm.viewing && Object.hasOwn(npmViewKeys, key)) {
169
- return npmViewKeys[key];
170
- }
171
- return Object.hasOwn(npmListKeys, key) ? npmListKeys[key] : null;
172
- };
156
+ const NPM_LIST_KEYS = {
157
+ 'ctrl-up': (ui) => ui.npm.reorder(-1),
158
+ 'ctrl-down': (ui) => ui.npm.reorder(1),
159
+ up: (ui) => ui.npm.move(-1),
160
+ down: (ui) => ui.npm.move(1),
161
+ };
173
162
 
174
- const handleKey = (key) => {
175
- const mode = ui.mode;
176
- if (CONFIRM_MODE.includes(mode)) return void confirmChoice(key);
177
- if (mode === 'compose') {
178
- const command = ui.composer.handleKey(key);
179
- return void ui.runComposeCommand(command);
180
- }
181
- const onNpmKey = npmKey(key);
182
- if (onNpmKey) return void onNpmKey();
183
- if (key === 'escape') return void ui.filesPane.onEscape();
184
- const toggle = key === 'x' || key === ' ';
185
- if (toggle && ui.composer.toggleFocusedTask()) return;
186
- if (ui.composer.typeIntoTask(key)) return;
187
- const action = actionFromKey(key, ui.pane, ui.fileScope);
188
- if (action) ui.dispatch(action);
189
- };
163
+ const npmKey = (ui, key) => {
164
+ if (ui.pane !== 'npm') return null;
165
+ if (ui.npm.viewing && Object.hasOwn(NPM_VIEW_KEYS, key)) {
166
+ return NPM_VIEW_KEYS[key];
167
+ }
168
+ return Object.hasOwn(NPM_LIST_KEYS, key) ? NPM_LIST_KEYS[key] : null;
169
+ };
190
170
 
191
- const handleEvent = (event) => {
192
- if (event.type === 'key') return void handleKey(event.key);
193
- if (event.type !== 'mouse') return;
194
- ui.pointer.handle(event);
195
- };
171
+ const handleKey = (ui, key) => {
172
+ if (CONFIRM_MODE.includes(ui.mode)) return void confirmChoice(ui, key);
173
+ if (ui.mode === 'compose') {
174
+ return void ui.runComposeCommand(ui.composer.handleKey(key));
175
+ }
176
+ const onNpmKey = npmKey(ui, key);
177
+ if (onNpmKey) return void onNpmKey(ui);
178
+ if (key === 'escape') return void filesPane.onEscape(ui);
179
+ const tasks = ui.composer.tasks;
180
+ const toggle = key === 'x' || key === ' ';
181
+ if (toggle && tasks.toggleFocusedTask()) return;
182
+ if (tasks.typeIntoTask(key)) return;
183
+ const action = actionFromKey(key, ui.pane, ui.fileScope);
184
+ if (action) ui.dispatch(action);
185
+ };
196
186
 
197
- return { handleEvent, confirmChoice };
187
+ const handleEvent = (ui, event) => {
188
+ if (event.type === 'key') return void handleKey(ui, event.key);
189
+ if (event.type === 'mouse') handlePointer(ui, event);
198
190
  };
199
191
 
200
- module.exports = { createDispatcher, createInput };
192
+ module.exports = { dispatchAction, confirmChoice, handleEvent };
@@ -13,16 +13,16 @@ const { setFileScope, openUnitFile, moveUnitBlock } = unit;
13
13
 
14
14
  const LAYOUTS = ['unified', 'mixed', 'side'];
15
15
 
16
- const current = (ui) => ui.nav.current(ui.items, ui.tasksItem);
16
+ const fileCursorEntry = (ui) => ui.fileList()[ui.nav.fileCursor] ?? null;
17
17
 
18
18
  const clampIndex = (ui) => ui.nav.clampIndex(ui.items.length);
19
19
 
20
- const syncReviewPath = (ui) => ui.nav.syncReviewPath(current(ui));
20
+ const syncReviewPath = (ui) => ui.nav.syncReviewPath(ui.current());
21
21
 
22
22
  const clampFileCursor = (ui) => ui.nav.clampFileCursor(ui.fileList().length);
23
23
 
24
24
  const followReviewPath = (ui) =>
25
- ui.nav.followReviewPath(ui.fileList(), current(ui));
25
+ ui.nav.followReviewPath(ui.fileList(), ui.current());
26
26
 
27
27
  const syncFileCursor = (ui) => {
28
28
  if (ui.nav.pane === 'files') return void clampFileCursor(ui);
@@ -44,7 +44,42 @@ const showFiles = (ui) => {
44
44
  if (held) ui.lifecycle.catchUpWatch();
45
45
  };
46
46
 
47
+ const viewingCommit = (ui) => !ui.change && Boolean(ui.rev || ui.revShort);
48
+
49
+ const releaseCommit = (ui) => {
50
+ if (!viewingCommit(ui)) return;
51
+ ui.rev = '';
52
+ ui.revShort = '';
53
+ ui.refreshFromRepo({ keepEmpty: true });
54
+ };
55
+
56
+ const showDashboard = (ui) => {
57
+ const nav = ui.nav;
58
+ const held = nav.pane !== 'dashboard';
59
+ nav.pane = 'dashboard';
60
+ nav.tasksOpen = false;
61
+ nav.scroll = 0;
62
+ nav.clearSelection();
63
+ ui.status = '';
64
+ releaseCommit(ui);
65
+ if (held) ui.lifecycle.catchUpWatch();
66
+ ui.dashboard.enter();
67
+ };
68
+
69
+ const HOME_CHILDREN = ['files', 'branches', 'commits', 'npm'];
70
+
71
+ const returnsHome = (ui) => {
72
+ if (!ui.dashboardHome) return false;
73
+ const { pane, tasksOpen } = ui.nav;
74
+ return HOME_CHILDREN.includes(pane) || tasksOpen === true;
75
+ };
76
+
47
77
  const onEscape = (ui) => {
78
+ if (ui.nav.pane === 'dashboard') return void ui.onQuit();
79
+ const commitDiff = ui.nav.pane === 'diff' && viewingCommit(ui);
80
+ if (returnsHome(ui) || (ui.dashboardHome && commitDiff)) {
81
+ return void showDashboard(ui);
82
+ }
48
83
  if (ui.nav.pane === 'files') return void ui.onQuit();
49
84
  showFiles(ui);
50
85
  };
@@ -60,7 +95,6 @@ const onFiles = (ui) => {
60
95
  }
61
96
  ui.nav.pane = 'diff';
62
97
  ui.nav.clearSelection();
63
- if (ui.nav.reviewPath === TASKS_FILE) ui.composer.openTasksPage();
64
98
  return;
65
99
  }
66
100
  showFiles(ui);
@@ -73,14 +107,14 @@ const onOpenFile = (ui) => {
73
107
  if (ui.nav.pane === 'commits') return void ui.commits.onShowDiff();
74
108
  if (ui.nav.pane === 'npm') return void ui.npm.run();
75
109
  if (ui.nav.pane !== 'files') {
76
- if (ui.nav.tasksOpen) ui.composer.editFocusedTask();
110
+ if (ui.nav.tasksOpen) ui.composer.tasks.editFocusedTask();
77
111
  return;
78
112
  }
79
113
  const list = ui.fileList();
80
114
  const entry = list[ui.nav.fileCursor];
81
115
  if (!entry) return;
82
116
  if (isTasksEntry(entry)) {
83
- ui.composer.openTasksPage();
117
+ ui.composer.tasks.openTasksPage();
84
118
  ui.status = '';
85
119
  return;
86
120
  }
@@ -134,21 +168,13 @@ const moveRemaining = (ui, step) => {
134
168
 
135
169
  const onNext = (ui) => {
136
170
  if (ui.nav.pane === 'unit') return void moveUnitBlock(ui, 1);
137
- if (ui.nav.tasksOpen) {
138
- ui.composer.closeTasksPage();
139
- if (ui.items.length) ui.nav.index = 0;
140
- ui.nav.scroll = 0;
141
- syncReviewPath(ui);
142
- syncFileCursor(ui);
143
- return;
144
- }
171
+ if (ui.nav.pane === 'tasks') return void ui.composer.tasks.moveTaskFocus(1);
145
172
  moveRemaining(ui, 1);
146
173
  };
147
174
 
148
175
  const onPrev = (ui) => {
149
176
  if (ui.nav.pane === 'unit') return void moveUnitBlock(ui, -1);
150
- if (ui.nav.tasksOpen) return;
151
- if (ui.nav.index <= 0) return void ui.composer.openTasksPage();
177
+ if (ui.nav.pane === 'tasks') return void ui.composer.tasks.moveTaskFocus(-1);
152
178
  moveRemaining(ui, -1);
153
179
  };
154
180
 
@@ -158,7 +184,7 @@ const scrollStep = (ui, fraction) => {
158
184
  const rows = size.height ?? 24;
159
185
  const fallback = Math.max(1, rows - 4);
160
186
  const bodyH = frame?.bodyH ?? fallback;
161
- const viewing = ui.nav.pane === 'npm' && ui.npm && ui.npm.viewing;
187
+ const viewing = ui.nav.pane === 'npm' && ui.npm.viewing;
162
188
  const viewRows = viewing ? logViewRows(bodyH) : bodyH;
163
189
  const height = Math.max(1, viewRows);
164
190
  const span = Math.floor(height * Math.abs(fraction));
@@ -168,6 +194,7 @@ const scrollStep = (ui, fraction) => {
168
194
  };
169
195
 
170
196
  const onScroll = (ui, delta) => {
197
+ if (ui.nav.pane === 'dashboard') return;
171
198
  if (ui.nav.pane === 'files') return void onFileMove(ui, delta);
172
199
  if (ui.nav.pane === 'branches') {
173
200
  return void ui.gitBranches.onBranchMove(delta);
@@ -176,8 +203,8 @@ const onScroll = (ui, delta) => {
176
203
  return void ui.commits.onCommitMove(delta);
177
204
  }
178
205
  if (ui.nav.pane === 'npm') return void ui.npm.move(delta);
179
- if (ui.nav.tasksOpen && ui.composer.taskRowCount() > 1) {
180
- return void ui.composer.moveTaskFocus(delta);
206
+ if (ui.nav.tasksOpen && ui.composer.tasks.taskRowCount() > 1) {
207
+ return void ui.composer.tasks.moveTaskFocus(delta);
181
208
  }
182
209
  const frame = ui.lastFrame;
183
210
  const max = frame ? frame.scrollMax : null;
@@ -190,15 +217,16 @@ const onScroll = (ui, delta) => {
190
217
  };
191
218
 
192
219
  const onJump = (ui, toEnd) => {
220
+ if (ui.nav.pane === 'dashboard') return;
193
221
  if (LIST_PANES.includes(ui.nav.pane)) {
194
222
  const delta = toEnd ? Number.MAX_SAFE_INTEGER : -Number.MAX_SAFE_INTEGER;
195
223
  return void onScroll(ui, delta);
196
224
  }
197
- if (ui.nav.tasksOpen && ui.composer.taskRowCount() > 0) {
198
- const last = ui.composer.taskRowCount() - 1;
225
+ if (ui.nav.tasksOpen && ui.composer.tasks.taskRowCount() > 0) {
226
+ const last = ui.composer.tasks.taskRowCount() - 1;
199
227
  const target = toEnd ? last : 0;
200
- const at = ui.composer.clampedTaskFocus();
201
- return void ui.composer.moveTaskFocus(target - at);
228
+ const at = ui.composer.tasks.clampedTaskFocus();
229
+ return void ui.composer.tasks.moveTaskFocus(target - at);
202
230
  }
203
231
  const frame = ui.lastFrame;
204
232
  const max = frame ? frame.scrollMax : 0;
@@ -223,24 +251,28 @@ const onTheme = (ui) => {
223
251
  ui.status = next;
224
252
  };
225
253
 
226
- const createFilesPane = (ui) => ({
227
- clampIndex: () => clampIndex(ui),
228
- syncReviewPath: () => syncReviewPath(ui),
229
- syncFileCursor: () => syncFileCursor(ui),
230
- showFiles: () => showFiles(ui),
231
- onEscape: () => onEscape(ui),
232
- onFiles: () => onFiles(ui),
233
- onOpenFile: () => onOpenFile(ui),
234
- onNext: () => onNext(ui),
235
- onPrev: () => onPrev(ui),
236
- scrollStep: (fraction) => scrollStep(ui, fraction),
237
- onScroll: (delta) => onScroll(ui, delta),
238
- onHome: () => onJump(ui, false),
239
- onEnd: () => onJump(ui, true),
240
- onLayout: () => onLayout(ui),
241
- onTheme: () => onTheme(ui),
242
- onFileScope: () => setFileScope(ui, 'file'),
243
- onDiffScope: () => setFileScope(ui, 'diff'),
244
- });
245
-
246
- module.exports = { createFilesPane };
254
+ const onHome = (ui) => onJump(ui, false);
255
+ const onEnd = (ui) => onJump(ui, true);
256
+ const onFileScope = (ui) => setFileScope(ui, 'file');
257
+ const onDiffScope = (ui) => setFileScope(ui, 'diff');
258
+
259
+ module.exports = {
260
+ fileCursorEntry,
261
+ clampIndex,
262
+ syncReviewPath,
263
+ syncFileCursor,
264
+ showFiles,
265
+ onEscape,
266
+ onFiles,
267
+ onOpenFile,
268
+ onNext,
269
+ onPrev,
270
+ scrollStep,
271
+ onScroll,
272
+ onHome,
273
+ onEnd,
274
+ onLayout,
275
+ onTheme,
276
+ onFileScope,
277
+ onDiffScope,
278
+ };
@@ -29,13 +29,9 @@ const blockKey = (item) => {
29
29
  if (isTaskItem(item)) return `todo:${TASKS_FILE}`;
30
30
  const depId = depChangeKey(item);
31
31
  if (depId) return `${rel}:dep:${depId}`;
32
- if (!item.hunk) {
33
- const block = item.blockId ?? '-';
34
- return `${rel}:file:${block}`;
35
- }
36
- const oldStart = item.hunk.oldStart;
37
- const newStart = item.hunk.newStart;
38
32
  const block = item.blockId ?? '-';
33
+ if (!item.hunk) return `${rel}:file:${block}`;
34
+ const { oldStart, newStart } = item.hunk;
39
35
  return `${rel}:${oldStart}:${newStart}:${block}`;
40
36
  };
41
37
 
@@ -119,12 +115,10 @@ const alignLoadedItems = (prev, next) => {
119
115
  const ordered = [];
120
116
  for (const [rel, list] of groupByPath(prev)) {
121
117
  seen.add(rel);
122
- const aligned = alignGroup(list, nextGroups.get(rel) ?? []);
123
- for (const item of aligned) ordered.push(item);
118
+ ordered.push(...alignGroup(list, nextGroups.get(rel) ?? []));
124
119
  }
125
120
  for (const [rel, list] of nextGroups) {
126
- if (seen.has(rel)) continue;
127
- for (const item of list) ordered.push(item);
121
+ if (!seen.has(rel)) ordered.push(...list);
128
122
  }
129
123
  return ordered;
130
124
  };
@@ -195,8 +189,7 @@ const sameBlock = (left, right) => {
195
189
  const canEditCode = (item) => {
196
190
  if (!item || isTaskItem(item)) return false;
197
191
  if (!item.hunk || !item.file) return false;
198
- if (item.file.isBinary) return false;
199
- return true;
192
+ return !item.file.isBinary;
200
193
  };
201
194
 
202
195
  class ItemCollection {
@@ -237,11 +230,7 @@ class ItemCollection {
237
230
  const replaced = this.items.map((entry) =>
238
231
  sameBlock(entry, item) ? next : entry,
239
232
  );
240
- if (next.dep) {
241
- this.items = replaced;
242
- return;
243
- }
244
- this.items = refreshIndexPatches(replaced, next);
233
+ this.items = next.dep ? replaced : refreshIndexPatches(replaced, next);
245
234
  }
246
235
  }
247
236