reslop 0.2.4 → 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 (109) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/LICENSE +1 -1
  3. package/README.md +12 -13
  4. package/bin/reslop.js +3 -8
  5. package/lib/ansi.js +32 -19
  6. package/lib/capabilities.js +8 -12
  7. package/lib/cli.js +40 -74
  8. package/lib/clipboard.js +48 -18
  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 +196 -404
  14. package/lib/deps.js +240 -399
  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 +138 -141
  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 +90 -206
  25. package/lib/editor.js +98 -55
  26. package/lib/files.js +86 -103
  27. package/lib/git-branches.js +254 -416
  28. package/lib/git-deps.js +182 -344
  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 +157 -132
  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 +16 -26
  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 +22 -35
  44. package/lib/highlight/txt.js +9 -22
  45. package/lib/highlight.js +17 -46
  46. package/lib/keys.js +62 -85
  47. package/lib/manifest.js +67 -130
  48. package/lib/npm-commands.js +119 -189
  49. package/lib/remote.js +141 -197
  50. package/lib/render/branches.js +66 -73
  51. package/lib/render/chrome.js +264 -323
  52. package/lib/render/commits.js +187 -500
  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 -228
  58. package/lib/render/files.js +45 -99
  59. package/lib/render/frame.js +129 -183
  60. package/lib/render/notes.js +144 -160
  61. package/lib/render/npm.js +234 -112
  62. package/lib/render/present.js +4 -11
  63. package/lib/render/primitives.js +131 -170
  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 -28
  68. package/lib/report-model.js +110 -33
  69. package/lib/report-parse.js +56 -38
  70. package/lib/report-render.js +102 -59
  71. package/lib/report-run.js +87 -78
  72. package/lib/report-test.js +256 -141
  73. package/lib/report-tools.js +96 -161
  74. package/lib/review-merge.js +114 -152
  75. package/lib/review.js +115 -189
  76. package/lib/runs.js +225 -0
  77. package/lib/select.js +89 -75
  78. package/lib/session/accessors.js +14 -21
  79. package/lib/session/actions.js +82 -63
  80. package/lib/session/branches.js +193 -247
  81. package/lib/session/change-actions.js +171 -214
  82. package/lib/session/commit.js +280 -342
  83. package/lib/session/compose.js +559 -674
  84. package/lib/session/dashboard.js +385 -0
  85. package/lib/session/dispatch.js +164 -157
  86. package/lib/session/edit-click.js +57 -0
  87. package/lib/session/files-pane.js +85 -53
  88. package/lib/session/items.js +73 -104
  89. package/lib/session/lifecycle.js +403 -541
  90. package/lib/session/load.js +48 -68
  91. package/lib/session/navigation.js +96 -87
  92. package/lib/session/npm.js +409 -345
  93. package/lib/session/ops.js +196 -63
  94. package/lib/session/pointer.js +23 -15
  95. package/lib/session/progress.js +33 -21
  96. package/lib/session/review.js +33 -54
  97. package/lib/session/tasks.js +333 -0
  98. package/lib/session/templates.js +51 -59
  99. package/lib/session/terminal.js +85 -136
  100. package/lib/session/unit.js +3 -4
  101. package/lib/session/update.js +76 -97
  102. package/lib/session/view.js +52 -83
  103. package/lib/session/watch.js +208 -204
  104. package/lib/session.js +112 -237
  105. package/lib/update.js +45 -116
  106. package/lib/utilities.js +67 -89
  107. package/lib/wrap.js +33 -44
  108. package/package.json +1 -1
  109. package/lib/session/todos.js +0 -293
@@ -1,185 +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
- next: ui.filesPane.onNext,
36
- prev: ui.filesPane.onPrev,
37
- layout: ui.filesPane.onLayout,
38
- theme: ui.filesPane.onTheme,
39
- files: ui.filesPane.onFiles,
40
- feedback: ui.composer.onFeedback,
41
- todo: ui.composer.onTodo,
42
- code: ui.composer.onCode,
43
- reload: ui.lifecycle.onReload,
44
- commit: ui.commits.onCommit,
45
- amend: ui.commits.onAmend,
46
- reword: ui.commits.onReword,
47
- apply: ui.commits.onApply,
48
- fixup: ui.commits.onFixup,
49
- view: () => ui.commits.onView(),
50
- branch: ui.gitBranches.onBranch,
51
- pull: ui.gitBranches.onPull,
52
- push: ui.gitBranches.onPush,
53
- newBranch: ui.gitBranches.onNewBranch,
54
- rebase: ui.gitBranches.onRebaseBranch,
55
- drop: () => {
56
- if (ui.pane === 'commits') {
57
- return void ui.commits.onDropCommit();
58
- }
59
- ui.gitBranches.onDropBranch();
60
- },
61
- open: ui.filesPane.onOpenFile,
62
- file: ui.filesPane.onFileScope,
63
- diff: ui.filesPane.onDiffScope,
64
- npm: ui.npm.open,
65
- npmEdit: ui.npm.edit,
66
- npmNew: ui.npm.create,
67
- npmDrop: () => ui.npm.askDrop(),
68
- npmLogs: () => ui.npm.askLogs(),
69
- npmStop: () => ui.npm.closeView(),
70
- npmRerun: () => ui.npm.rerun(),
71
- check: () => ui.composer.toggleFocusedTodo(),
72
- removeTodo: () => {
73
- if (ui.pane === 'npm') return void ui.npm.askDrop();
74
- ui.composer.removeFocusedTodo();
75
- },
76
- quit: () => ui.onQuit(),
77
- scrollUp: () => ui.filesPane.onScroll(-1),
78
- scrollDown: () => ui.filesPane.onScroll(1),
79
- pageUp: () => ui.filesPane.onScroll(ui.filesPane.scrollStep(-1)),
80
- pageDown: () => ui.filesPane.onScroll(ui.filesPane.scrollStep(1)),
81
- halfUp: () => ui.filesPane.onScroll(ui.filesPane.scrollStep(-0.5)),
82
- halfDown: () => ui.filesPane.onScroll(ui.filesPane.scrollStep(0.5)),
83
- home: ui.filesPane.onHome,
84
- end: ui.filesPane.onEnd,
85
- };
28
+ const scrollBy = (fraction) => (ui) =>
29
+ filesPane.onScroll(ui, filesPane.scrollStep(ui, fraction));
86
30
 
87
- const dispatch = (action) => {
88
- if (CONFIRM_MODE.includes(ui.mode)) return;
89
- if (ui.mode === 'compose') {
90
- if (!COMPOSE_LEAVE.includes(action)) return;
91
- const result = ui.composer.saveCompose();
92
- if (result && result.kind === 'error') return;
93
- }
94
- const pane = ui.pane;
95
- if (LIST_PANES.includes(pane) && (action === 'next' || action === 'prev')) {
96
- ui.filesPane.onScroll(action === 'next' ? 1 : -1);
97
- return;
98
- }
99
- const hidden = disabledActions(pane, currentTarget(ui));
100
- if (hidden.includes(action)) return;
101
- const handler = handlers[action];
102
- if (!handler) return;
103
- handler();
104
- };
31
+ const DASH_HANDLERS = Object.fromEntries(
32
+ Object.entries(DASH_BLOCKS).map(([id, block]) => [
33
+ id,
34
+ (ui) => ui.dashboard.open(block),
35
+ ]),
36
+ );
105
37
 
106
- 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,
107
95
  };
108
96
 
109
- const createInput = (ui) => {
110
- const confirmations = {
111
- confirmQuit: {
112
- ready: () => ui.finishQuit('ready'),
113
- editing: () => ui.finishQuit('editing'),
114
- cancel: () => ui.cancelQuit(),
115
- },
116
- confirmUpdate: {
117
- accept: ui.updater.accept,
118
- decline: ui.updater.decline,
119
- },
120
- confirmDrop: {
121
- confirm: () => {
122
- if (ui.npm.dropName) return void ui.npm.confirmDrop();
123
- if (ui.commits.dropName) {
124
- return void ui.commits.confirmDropCommit();
125
- }
126
- ui.gitBranches.confirmDropBranch();
127
- },
128
- cancel: () => {
129
- if (ui.npm.dropName) return void ui.npm.cancelDrop();
130
- if (ui.commits.dropName) {
131
- return void ui.commits.cancelDropCommit();
132
- }
133
- ui.gitBranches.cancelDropBranch();
134
- },
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();
135
129
  },
136
- confirmPush: {
137
- force: ui.gitBranches.onForcePush,
138
- 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();
139
134
  },
140
- };
135
+ },
136
+ confirmPush: {
137
+ force: (ui) => ui.gitBranches.onForcePush(),
138
+ cancel: (ui) => ui.gitBranches.cancelPush(),
139
+ },
140
+ };
141
+
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
+ };
147
+
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
+ };
141
155
 
142
- const confirmChoice = (key) => {
143
- const mode = ui.mode;
144
- if (!CONFIRM_MODE.includes(mode)) return;
145
- applyConfirm(mode, key, confirmations[mode]);
146
- };
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
+ };
147
162
 
148
- const handleKey = (key) => {
149
- const mode = ui.mode;
150
- if (CONFIRM_MODE.includes(mode)) return void confirmChoice(key);
151
- if (mode === 'compose') {
152
- if (key === 'ctrl-c') return void ui.onQuit();
153
- const command = ui.composer.handleKey(key);
154
- return void ui.runComposeCommand(command);
155
- }
156
- if (ui.pane === 'npm' && ui.npm.viewing) {
157
- if (key === 'escape') return void ui.npm.closeView();
158
- if (key === 'r') return void ui.npm.rerun();
159
- if (key === 'e' || key === 'n') return;
160
- }
161
- if (ui.pane === 'npm' && key === 'ctrl-up') return void ui.npm.reorder(-1);
162
- if (ui.pane === 'npm' && key === 'ctrl-down') {
163
- return void ui.npm.reorder(1);
164
- }
165
- if (ui.pane === 'npm' && (key === 'up' || key === 'down')) {
166
- return void ui.npm.move(key === 'up' ? -1 : 1);
167
- }
168
- if (key === 'escape') return void ui.filesPane.onEscape();
169
- const toggle = key === 'x' || key === ' ';
170
- if (toggle && ui.composer.toggleFocusedTodo()) return;
171
- if (ui.composer.typeIntoTodo(key)) return;
172
- const action = actionFromKey(key, ui.pane, ui.fileScope);
173
- if (action) ui.dispatch(action);
174
- };
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
+ };
175
170
 
176
- const handleEvent = (event) => {
177
- if (event.type === 'key') return void handleKey(event.key);
178
- if (event.type !== 'mouse') return;
179
- ui.pointer.handle(event);
180
- };
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
+ };
181
186
 
182
- 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);
183
190
  };
184
191
 
185
- module.exports = { createDispatcher, createInput };
192
+ module.exports = { dispatchAction, confirmChoice, handleEvent };
@@ -0,0 +1,57 @@
1
+ 'use strict';
2
+
3
+ const wrap = require('../wrap.js');
4
+ const diff = require('../render/diff.js');
5
+
6
+ const { indexAtRow } = wrap;
7
+ const { codeInnerWidth } = diff;
8
+
9
+ const hitAt = (hits, cell) => {
10
+ for (const hit of hits) {
11
+ if (hit.y !== cell.y) continue;
12
+ const x0 = hit.x0 ?? hit.textX;
13
+ if (cell.x < x0) continue;
14
+ if (hit.x1 !== undefined && cell.x >= hit.x1) continue;
15
+ return hit;
16
+ }
17
+ return null;
18
+ };
19
+
20
+ const offsetOf = (hit, cellX) => {
21
+ const shown = cellX - hit.textX;
22
+ const col = Math.max(0, shown) + (hit.scroll || 0);
23
+ const at = indexAtRow(hit.start || 0, hit.text, col);
24
+ if (hit.limit !== undefined && at > hit.limit) return hit.limit;
25
+ return at;
26
+ };
27
+
28
+ const revealCode = (ui) => {
29
+ const kind = ui.composeKind;
30
+ if (kind !== 'code' && kind !== 'file') return;
31
+ const size = ui.lastSize ?? ui.getSize();
32
+ ui.editor.reveal(codeInnerWidth(size.width, ui.layout));
33
+ };
34
+
35
+ const focusField = (ui, hit) => {
36
+ if (!hit.field || !ui.npm) return true;
37
+ if (hit.field === ui.npm.editField) return true;
38
+ return ui.npm.focusField(hit.field);
39
+ };
40
+
41
+ const placeClick = (ui, cell) => {
42
+ if (ui.mode !== 'compose' || !ui.editor) return false;
43
+ const hits = ui.lastFrame && ui.lastFrame.editHits;
44
+ if (!hits || !hits.length) return false;
45
+ const hit = hitAt(hits, cell);
46
+ if (!hit) return false;
47
+ if (!focusField(ui, hit)) {
48
+ ui.paint();
49
+ return true;
50
+ }
51
+ ui.editor.place(offsetOf(hit, cell.x));
52
+ revealCode(ui);
53
+ ui.paint();
54
+ return true;
55
+ };
56
+
57
+ module.exports = { placeClick };
@@ -3,7 +3,7 @@
3
3
  const ansi = require('../ansi.js');
4
4
  const files = require('../files.js');
5
5
  const { THEME_NAMES, setTheme, themeName } = ansi;
6
- const { isTodosEntry, TODO_FILE } = files;
6
+ const { isTasksEntry, TASKS_FILE } = files;
7
7
  const npm = require('../render/npm.js');
8
8
  const { logViewRows } = npm;
9
9
  const unit = require('./unit.js');
@@ -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.todoItem);
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);
@@ -32,18 +32,54 @@ const syncFileCursor = (ui) => {
32
32
  const showFiles = (ui) => {
33
33
  const nav = ui.nav;
34
34
  const held =
35
- nav.todoOpen === true ||
35
+ nav.tasksOpen === true ||
36
36
  nav.pane === 'branches' ||
37
37
  nav.pane === 'commits' ||
38
38
  nav.pane === 'npm';
39
39
  nav.pane = 'files';
40
+ nav.resetListScroll('files');
40
41
  nav.clearSelection();
41
42
  followReviewPath(ui);
42
- nav.todoOpen = false;
43
+ nav.tasksOpen = false;
43
44
  if (held) ui.lifecycle.catchUpWatch();
44
45
  };
45
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
+
46
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
+ }
47
83
  if (ui.nav.pane === 'files') return void ui.onQuit();
48
84
  showFiles(ui);
49
85
  };
@@ -54,12 +90,11 @@ const onFiles = (ui) => {
54
90
  if (ui.nav.fileScope === 'file') {
55
91
  const list = ui.fileList();
56
92
  const entry = list[ui.nav.fileCursor];
57
- if (entry && !isTodosEntry(entry)) openUnitFile(ui, entry);
93
+ if (entry && !isTasksEntry(entry)) openUnitFile(ui, entry);
58
94
  return;
59
95
  }
60
96
  ui.nav.pane = 'diff';
61
97
  ui.nav.clearSelection();
62
- if (ui.nav.reviewPath === TODO_FILE) ui.composer.openTodoPage();
63
98
  return;
64
99
  }
65
100
  showFiles(ui);
@@ -72,18 +107,18 @@ const onOpenFile = (ui) => {
72
107
  if (ui.nav.pane === 'commits') return void ui.commits.onShowDiff();
73
108
  if (ui.nav.pane === 'npm') return void ui.npm.run();
74
109
  if (ui.nav.pane !== 'files') {
75
- if (ui.nav.todoOpen) ui.composer.editFocusedTodo();
110
+ if (ui.nav.tasksOpen) ui.composer.tasks.editFocusedTask();
76
111
  return;
77
112
  }
78
113
  const list = ui.fileList();
79
114
  const entry = list[ui.nav.fileCursor];
80
115
  if (!entry) return;
81
- if (isTodosEntry(entry)) {
82
- ui.composer.openTodoPage();
116
+ if (isTasksEntry(entry)) {
117
+ ui.composer.tasks.openTasksPage();
83
118
  ui.status = '';
84
119
  return;
85
120
  }
86
- ui.nav.todoOpen = false;
121
+ ui.nav.tasksOpen = false;
87
122
  ui.nav.index = entry.openIndex ?? entry.firstIndex;
88
123
  if (ui.nav.fileScope === 'file') {
89
124
  return void openUnitFile(ui, entry);
@@ -107,8 +142,8 @@ const onFileMove = (ui, delta) => {
107
142
  if (next === ui.nav.fileCursor) return;
108
143
  ui.nav.fileCursor = next;
109
144
  const entry = list[next];
110
- if (isTodosEntry(entry)) {
111
- ui.nav.reviewPath = TODO_FILE;
145
+ if (isTasksEntry(entry)) {
146
+ ui.nav.reviewPath = TASKS_FILE;
112
147
  } else {
113
148
  ui.nav.reviewPath = entry.path;
114
149
  ui.nav.index = entry.firstIndex;
@@ -133,21 +168,13 @@ const moveRemaining = (ui, step) => {
133
168
 
134
169
  const onNext = (ui) => {
135
170
  if (ui.nav.pane === 'unit') return void moveUnitBlock(ui, 1);
136
- if (ui.nav.todoOpen) {
137
- ui.composer.closeTodoPage();
138
- if (ui.items.length) ui.nav.index = 0;
139
- ui.nav.scroll = 0;
140
- syncReviewPath(ui);
141
- syncFileCursor(ui);
142
- return;
143
- }
171
+ if (ui.nav.pane === 'tasks') return void ui.composer.tasks.moveTaskFocus(1);
144
172
  moveRemaining(ui, 1);
145
173
  };
146
174
 
147
175
  const onPrev = (ui) => {
148
176
  if (ui.nav.pane === 'unit') return void moveUnitBlock(ui, -1);
149
- if (ui.nav.todoOpen) return;
150
- if (ui.nav.index <= 0) return void ui.composer.openTodoPage();
177
+ if (ui.nav.pane === 'tasks') return void ui.composer.tasks.moveTaskFocus(-1);
151
178
  moveRemaining(ui, -1);
152
179
  };
153
180
 
@@ -157,7 +184,7 @@ const scrollStep = (ui, fraction) => {
157
184
  const rows = size.height ?? 24;
158
185
  const fallback = Math.max(1, rows - 4);
159
186
  const bodyH = frame?.bodyH ?? fallback;
160
- const viewing = ui.nav.pane === 'npm' && ui.npm && ui.npm.viewing;
187
+ const viewing = ui.nav.pane === 'npm' && ui.npm.viewing;
161
188
  const viewRows = viewing ? logViewRows(bodyH) : bodyH;
162
189
  const height = Math.max(1, viewRows);
163
190
  const span = Math.floor(height * Math.abs(fraction));
@@ -167,6 +194,7 @@ const scrollStep = (ui, fraction) => {
167
194
  };
168
195
 
169
196
  const onScroll = (ui, delta) => {
197
+ if (ui.nav.pane === 'dashboard') return;
170
198
  if (ui.nav.pane === 'files') return void onFileMove(ui, delta);
171
199
  if (ui.nav.pane === 'branches') {
172
200
  return void ui.gitBranches.onBranchMove(delta);
@@ -175,8 +203,8 @@ const onScroll = (ui, delta) => {
175
203
  return void ui.commits.onCommitMove(delta);
176
204
  }
177
205
  if (ui.nav.pane === 'npm') return void ui.npm.move(delta);
178
- if (ui.nav.todoOpen && ui.composer.todoRowCount() > 1) {
179
- return void ui.composer.moveTodoFocus(delta);
206
+ if (ui.nav.tasksOpen && ui.composer.tasks.taskRowCount() > 1) {
207
+ return void ui.composer.tasks.moveTaskFocus(delta);
180
208
  }
181
209
  const frame = ui.lastFrame;
182
210
  const max = frame ? frame.scrollMax : null;
@@ -189,15 +217,16 @@ const onScroll = (ui, delta) => {
189
217
  };
190
218
 
191
219
  const onJump = (ui, toEnd) => {
220
+ if (ui.nav.pane === 'dashboard') return;
192
221
  if (LIST_PANES.includes(ui.nav.pane)) {
193
222
  const delta = toEnd ? Number.MAX_SAFE_INTEGER : -Number.MAX_SAFE_INTEGER;
194
223
  return void onScroll(ui, delta);
195
224
  }
196
- if (ui.nav.todoOpen && ui.composer.todoRowCount() > 0) {
197
- const last = ui.composer.todoRowCount() - 1;
225
+ if (ui.nav.tasksOpen && ui.composer.tasks.taskRowCount() > 0) {
226
+ const last = ui.composer.tasks.taskRowCount() - 1;
198
227
  const target = toEnd ? last : 0;
199
- const at = ui.composer.clampedTodoFocus();
200
- return void ui.composer.moveTodoFocus(target - at);
228
+ const at = ui.composer.tasks.clampedTaskFocus();
229
+ return void ui.composer.tasks.moveTaskFocus(target - at);
201
230
  }
202
231
  const frame = ui.lastFrame;
203
232
  const max = frame ? frame.scrollMax : 0;
@@ -222,25 +251,28 @@ const onTheme = (ui) => {
222
251
  ui.status = next;
223
252
  };
224
253
 
225
- const createFilesPane = (ui) => ({
226
- clampIndex: () => clampIndex(ui),
227
- syncReviewPath: () => syncReviewPath(ui),
228
- syncFileCursor: () => syncFileCursor(ui),
229
- showFiles: () => showFiles(ui),
230
- onEscape: () => onEscape(ui),
231
- onFiles: () => onFiles(ui),
232
- onOpenFile: () => onOpenFile(ui),
233
- onFileMove: (delta) => onFileMove(ui, delta),
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 = { LAYOUTS, 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
+ };