reslop 0.2.1 → 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.
- package/CHANGELOG.md +27 -1
- package/README.md +19 -4
- package/bin/reslop.js +4 -2
- package/lib/ansi.js +120 -4
- package/lib/api.js +2 -1
- package/lib/cli.js +21 -9
- package/lib/clipboard.js +6 -3
- package/lib/deps-diff.js +61 -115
- package/lib/deps.js +57 -54
- package/lib/detect.js +4 -2
- package/lib/diff/align.js +2 -1
- package/lib/diff/diff.js +6 -3
- package/lib/diff/display.js +2 -1
- package/lib/diff/inline.js +2 -1
- package/lib/diff/patch.js +2 -1
- package/lib/diff/unit.js +4 -2
- package/lib/editor.js +22 -4
- package/lib/files.js +38 -12
- package/lib/git-branches.js +150 -165
- package/lib/git-deps.js +20 -60
- package/lib/git-worktree.js +508 -0
- package/lib/git.js +23 -580
- package/lib/github.js +2 -1
- package/lib/gitlab.js +2 -1
- package/lib/highlight/bash.js +2 -1
- package/lib/highlight/css.js +2 -1
- package/lib/highlight/csv.js +2 -1
- package/lib/highlight/dot.js +2 -1
- package/lib/highlight/html.js +2 -1
- package/lib/highlight/json.js +2 -1
- package/lib/highlight/log.js +2 -1
- package/lib/highlight/txt.js +2 -1
- package/lib/highlight.js +2 -1
- package/lib/keys.js +12 -6
- package/lib/manifest.js +4 -2
- package/lib/npm-commands.js +592 -0
- package/lib/remote.js +20 -13
- package/lib/render/branches.js +25 -41
- package/lib/render/chrome.js +163 -128
- package/lib/render/commits.js +727 -65
- package/lib/render/diff.js +130 -47
- package/lib/render/files.js +4 -12
- package/lib/render/frame.js +41 -13
- package/lib/render/notes.js +4 -11
- package/lib/render/npm.js +216 -0
- package/lib/render/present.js +2 -1
- package/lib/render/primitives.js +34 -50
- package/lib/render/render.js +2 -1
- package/lib/render/unit.js +14 -11
- package/lib/report-model.js +129 -0
- package/lib/report-parse.js +89 -0
- package/lib/report-render.js +104 -0
- package/lib/report-run.js +166 -0
- package/lib/report-test.js +507 -0
- package/lib/report-tools.js +337 -0
- package/lib/review-merge.js +2 -1
- package/lib/review.js +6 -2
- package/lib/session/actions.js +77 -44
- package/lib/session/branches.js +13 -49
- package/lib/session/change-actions.js +2 -1
- package/lib/session/commit.js +113 -53
- package/lib/session/compose.js +83 -14
- package/lib/session/dispatch.js +42 -22
- package/lib/session/files-pane.js +16 -21
- package/lib/session/items.js +6 -3
- package/lib/session/lifecycle.js +12 -5
- package/lib/session/load.js +4 -2
- package/lib/session/navigation.js +2 -0
- package/lib/session/npm.js +388 -0
- package/lib/session/ops.js +15 -1
- package/lib/session/pointer.js +30 -3
- package/lib/session/review.js +2 -1
- package/lib/session/terminal.js +4 -2
- package/lib/session/update.js +2 -1
- package/lib/session/view.js +41 -4
- package/lib/session.js +44 -22
- package/lib/source.js +6 -3
- package/lib/update.js +5 -9
- package/lib/utilities.js +4 -2
- package/lib/wrap.js +3 -1
- package/package.json +6 -6
|
@@ -4,7 +4,11 @@ const ansi = require('../ansi.js');
|
|
|
4
4
|
const files = require('../files.js');
|
|
5
5
|
const { THEME_NAMES, setTheme, themeName } = ansi;
|
|
6
6
|
const { isTodosEntry, TODO_FILE } = files;
|
|
7
|
+
const npm = require('../render/npm.js');
|
|
8
|
+
const { logViewRows } = npm;
|
|
7
9
|
const unit = require('./unit.js');
|
|
10
|
+
const actions = require('./actions.js');
|
|
11
|
+
const { LIST_PANES } = actions;
|
|
8
12
|
const { setFileScope, openUnitFile, moveUnitBlock } = unit;
|
|
9
13
|
|
|
10
14
|
const LAYOUTS = ['unified', 'mixed', 'side'];
|
|
@@ -28,7 +32,10 @@ const syncFileCursor = (ui) => {
|
|
|
28
32
|
const showFiles = (ui) => {
|
|
29
33
|
const nav = ui.nav;
|
|
30
34
|
const held =
|
|
31
|
-
nav.todoOpen === true ||
|
|
35
|
+
nav.todoOpen === true ||
|
|
36
|
+
nav.pane === 'branches' ||
|
|
37
|
+
nav.pane === 'commits' ||
|
|
38
|
+
nav.pane === 'npm';
|
|
32
39
|
nav.pane = 'files';
|
|
33
40
|
nav.clearSelection();
|
|
34
41
|
followReviewPath(ui);
|
|
@@ -37,9 +44,6 @@ const showFiles = (ui) => {
|
|
|
37
44
|
};
|
|
38
45
|
|
|
39
46
|
const onEscape = (ui) => {
|
|
40
|
-
if (ui.nav.pane === 'branches' || ui.nav.pane === 'commits') {
|
|
41
|
-
return void showFiles(ui);
|
|
42
|
-
}
|
|
43
47
|
if (ui.nav.pane === 'files') return void ui.onQuit();
|
|
44
48
|
showFiles(ui);
|
|
45
49
|
};
|
|
@@ -65,7 +69,8 @@ const onOpenFile = (ui) => {
|
|
|
65
69
|
if (ui.nav.pane === 'branches') {
|
|
66
70
|
return void ui.gitBranches.onCheckoutBranch();
|
|
67
71
|
}
|
|
68
|
-
if (ui.nav.pane === 'commits') return;
|
|
72
|
+
if (ui.nav.pane === 'commits') return void ui.commits.onShowDiff();
|
|
73
|
+
if (ui.nav.pane === 'npm') return void ui.npm.run();
|
|
69
74
|
if (ui.nav.pane !== 'files') {
|
|
70
75
|
if (ui.nav.todoOpen) ui.composer.editFocusedTodo();
|
|
71
76
|
return;
|
|
@@ -152,7 +157,9 @@ const scrollStep = (ui, fraction) => {
|
|
|
152
157
|
const rows = size.height ?? 24;
|
|
153
158
|
const fallback = Math.max(1, rows - 4);
|
|
154
159
|
const bodyH = frame?.bodyH ?? fallback;
|
|
155
|
-
const
|
|
160
|
+
const viewing = ui.nav.pane === 'npm' && ui.npm && ui.npm.viewing;
|
|
161
|
+
const viewRows = viewing ? logViewRows(bodyH) : bodyH;
|
|
162
|
+
const height = Math.max(1, viewRows);
|
|
156
163
|
const span = Math.floor(height * Math.abs(fraction));
|
|
157
164
|
const n = Math.max(1, span);
|
|
158
165
|
if (fraction < 0) return -n;
|
|
@@ -167,6 +174,7 @@ const onScroll = (ui, delta) => {
|
|
|
167
174
|
if (ui.nav.pane === 'commits') {
|
|
168
175
|
return void ui.commits.onCommitMove(delta);
|
|
169
176
|
}
|
|
177
|
+
if (ui.nav.pane === 'npm') return void ui.npm.move(delta);
|
|
170
178
|
if (ui.nav.todoOpen && ui.composer.todoRowCount() > 1) {
|
|
171
179
|
return void ui.composer.moveTodoFocus(delta);
|
|
172
180
|
}
|
|
@@ -181,22 +189,9 @@ const onScroll = (ui, delta) => {
|
|
|
181
189
|
};
|
|
182
190
|
|
|
183
191
|
const onJump = (ui, toEnd) => {
|
|
184
|
-
if (ui.nav.pane
|
|
185
|
-
const list = ui.fileList();
|
|
186
|
-
if (!list.length) {
|
|
187
|
-
ui.status = 'nothing to review';
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
const target = toEnd ? list.length - 1 : 0;
|
|
191
|
-
return void onFileMove(ui, target - ui.nav.fileCursor);
|
|
192
|
-
}
|
|
193
|
-
if (ui.nav.pane === 'branches') {
|
|
192
|
+
if (LIST_PANES.includes(ui.nav.pane)) {
|
|
194
193
|
const delta = toEnd ? Number.MAX_SAFE_INTEGER : -Number.MAX_SAFE_INTEGER;
|
|
195
|
-
return void ui
|
|
196
|
-
}
|
|
197
|
-
if (ui.nav.pane === 'commits') {
|
|
198
|
-
const delta = toEnd ? Number.MAX_SAFE_INTEGER : -Number.MAX_SAFE_INTEGER;
|
|
199
|
-
return void ui.commits.onCommitMove(delta);
|
|
194
|
+
return void onScroll(ui, delta);
|
|
200
195
|
}
|
|
201
196
|
if (ui.nav.todoOpen && ui.composer.todoRowCount() > 0) {
|
|
202
197
|
const last = ui.composer.todoRowCount() - 1;
|
package/lib/session/items.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const accessors = require('./accessors.js');
|
|
4
|
+
const { stateView } = accessors;
|
|
4
5
|
|
|
5
6
|
const files = require('../files.js');
|
|
6
7
|
const { itemPath, TODO_FILE, isTodoItem } = files;
|
|
7
|
-
const
|
|
8
|
-
const {
|
|
8
|
+
const review = require('../review.js');
|
|
9
|
+
const { feedbackKey } = review;
|
|
10
|
+
const diff = require('../diff/diff.js');
|
|
11
|
+
const { refreshIndexPatches } = diff;
|
|
9
12
|
|
|
10
13
|
const depChangeKey = (item) => {
|
|
11
14
|
const change = item.dep && item.dep.change;
|
package/lib/session/lifecycle.js
CHANGED
|
@@ -3,14 +3,18 @@
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
const {
|
|
6
|
+
const sessionLoad = require('./load.js');
|
|
7
|
+
const { isAbortError } = sessionLoad;
|
|
8
|
+
const watch = require('./watch.js');
|
|
9
|
+
const { createDiskWatcher, POLL_MS, DEBOUNCE_MS } = watch;
|
|
8
10
|
const files = require('../files.js');
|
|
9
11
|
const { itemPath, relativeAge, TODO_FILE } = files;
|
|
10
|
-
const
|
|
12
|
+
const deps = require('../deps.js');
|
|
13
|
+
const { MANIFEST, LOCKFILE, depFileMeta } = deps;
|
|
11
14
|
const review = require('../review.js');
|
|
12
15
|
const { hasNotes, loadReview } = review;
|
|
13
|
-
const
|
|
16
|
+
const reviewMerge = require('../review-merge.js');
|
|
17
|
+
const { mergeReview } = reviewMerge;
|
|
14
18
|
|
|
15
19
|
const loadOptions = (api, extra = {}) => {
|
|
16
20
|
const { loadConfig, ui } = api;
|
|
@@ -485,7 +489,10 @@ const worktreeStamp = (api) => {
|
|
|
485
489
|
};
|
|
486
490
|
|
|
487
491
|
const watchesWorktree = (nav) =>
|
|
488
|
-
nav.todoOpen === true ||
|
|
492
|
+
nav.todoOpen === true ||
|
|
493
|
+
nav.pane === 'branches' ||
|
|
494
|
+
nav.pane === 'commits' ||
|
|
495
|
+
nav.pane === 'npm';
|
|
489
496
|
|
|
490
497
|
const currentFileStamp = (api) => {
|
|
491
498
|
const { nav } = api;
|
package/lib/session/load.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const accessors = require('./accessors.js');
|
|
4
|
+
const { stateView } = accessors;
|
|
4
5
|
|
|
5
|
-
const
|
|
6
|
+
const utilities = require('../utilities.js');
|
|
7
|
+
const { isAbort: isAbortError } = utilities;
|
|
6
8
|
|
|
7
9
|
const createLoadCoordinator = () => {
|
|
8
10
|
const state = {
|
|
@@ -9,6 +9,7 @@ const initialState = (options = {}) => ({
|
|
|
9
9
|
fileCursor: 0,
|
|
10
10
|
branchCursor: 0,
|
|
11
11
|
commitCursor: 0,
|
|
12
|
+
npmCursor: 0,
|
|
12
13
|
scroll: 0,
|
|
13
14
|
listScroll: 0,
|
|
14
15
|
reviewPath: null,
|
|
@@ -17,6 +18,7 @@ const initialState = (options = {}) => ({
|
|
|
17
18
|
selection: null,
|
|
18
19
|
mouseAnchor: null,
|
|
19
20
|
pendingClick: null,
|
|
21
|
+
lastClick: null,
|
|
20
22
|
todoOpen: false,
|
|
21
23
|
todoFocus: 0,
|
|
22
24
|
});
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const commands = require('../npm-commands.js');
|
|
4
|
+
const npm = require('../render/npm.js');
|
|
5
|
+
const { logViewRows } = npm;
|
|
6
|
+
|
|
7
|
+
const { listCommands, reduceOutput, writeLog, parseScriptLine } = commands;
|
|
8
|
+
const { staleLogFiles, removeStaleLogs, formatSize } = commands;
|
|
9
|
+
const { scriptLine, saveScript, removeScript, reorderScript } = commands;
|
|
10
|
+
const { startNpm } = commands;
|
|
11
|
+
|
|
12
|
+
const selected = (api) => {
|
|
13
|
+
const cursor = api.ui.nav.npmCursor;
|
|
14
|
+
return api.state.commands[cursor];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const clampCursor = (api) => {
|
|
18
|
+
const last = api.state.commands.length - 1;
|
|
19
|
+
const nav = api.ui.nav;
|
|
20
|
+
if (nav.npmCursor > last) nav.npmCursor = Math.max(0, last);
|
|
21
|
+
if (nav.npmCursor < 0) nav.npmCursor = 0;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const refresh = (api) => {
|
|
25
|
+
api.state.commands = listCommands(api.ui.top);
|
|
26
|
+
clampCursor(api);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const canEdit = (ui) => {
|
|
30
|
+
if (ui.capabilities && ui.capabilities.changes === false) return false;
|
|
31
|
+
if (ui.readOnly) return false;
|
|
32
|
+
return true;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const refreshLogs = (api) => {
|
|
36
|
+
try {
|
|
37
|
+
const found = staleLogFiles(api.ui.top);
|
|
38
|
+
api.state.logBytes = found.bytes;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
api.state.logBytes = 0;
|
|
41
|
+
api.ui.status = error.message;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const open = (api) => {
|
|
46
|
+
if (api.ui.mode === 'compose') return;
|
|
47
|
+
refresh(api);
|
|
48
|
+
refreshLogs(api);
|
|
49
|
+
api.state.viewing = false;
|
|
50
|
+
api.state.output = '';
|
|
51
|
+
api.ui.nav.pane = 'npm';
|
|
52
|
+
api.ui.status = '';
|
|
53
|
+
api.ui.nav.clearSelection();
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const scrollLog = (api, delta) => {
|
|
57
|
+
const lines = api.state.output.split('\n');
|
|
58
|
+
let count = lines.at(-1) === '' ? lines.length - 1 : lines.length;
|
|
59
|
+
if (api.state.running) count += 1;
|
|
60
|
+
const frame = api.ui.lastFrame;
|
|
61
|
+
const bodyH = frame && frame.bodyH ? frame.bodyH : 1;
|
|
62
|
+
const max = Math.max(0, count - logViewRows(bodyH));
|
|
63
|
+
let start = api.state.followEnd ? max : api.state.logScroll;
|
|
64
|
+
start = Math.min(max, Math.max(0, start + delta));
|
|
65
|
+
api.state.followEnd = start === max;
|
|
66
|
+
api.state.logScroll = start;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const move = (api, delta) => {
|
|
70
|
+
if (api.state.viewing) return void scrollLog(api, delta);
|
|
71
|
+
if (!api.state.commands.length) return;
|
|
72
|
+
const nav = api.ui.nav;
|
|
73
|
+
const last = api.state.commands.length - 1;
|
|
74
|
+
const next = Math.min(last, Math.max(0, nav.npmCursor + delta));
|
|
75
|
+
if (next === nav.npmCursor) return;
|
|
76
|
+
nav.npmCursor = next;
|
|
77
|
+
api.ui.status = '';
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const stopRun = (api) => {
|
|
81
|
+
api.state.running = false;
|
|
82
|
+
api.state.child = null;
|
|
83
|
+
if (api.ui.progress) api.ui.progress.stop('npm');
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const showOutput = (api, text) => {
|
|
87
|
+
if (!api.state.viewing || !api.state.running) return;
|
|
88
|
+
if (api.state.stopping) return;
|
|
89
|
+
api.state.output = text;
|
|
90
|
+
api.ui.paint();
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const withNotice = (text, notice) => {
|
|
94
|
+
const body = `${text ?? ''}`;
|
|
95
|
+
if (body.endsWith(`${notice}\n`)) return body;
|
|
96
|
+
if (!body) return `${notice}\n`;
|
|
97
|
+
if (body.endsWith('\n')) return `${body}${notice}\n`;
|
|
98
|
+
return `${body}\n${notice}\n`;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const finishRun = (api, entry, result) => {
|
|
102
|
+
const stopped = api.state.stopping === true;
|
|
103
|
+
const reduced = stopped
|
|
104
|
+
? withNotice(api.state.output, 'terminated')
|
|
105
|
+
: reduceOutput(result.text, api.ui.top, result.status);
|
|
106
|
+
api.state.output = reduced;
|
|
107
|
+
api.state.stopping = false;
|
|
108
|
+
stopRun(api);
|
|
109
|
+
try {
|
|
110
|
+
writeLog(api.ui.top, entry.name, reduced);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
api.ui.status = error.message;
|
|
113
|
+
api.ui.paint();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (stopped && api.state.viewing) api.ui.status = 'terminated';
|
|
117
|
+
api.ui.paint();
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const startRun = (api, entry) => {
|
|
121
|
+
api.state.output = '';
|
|
122
|
+
api.state.viewing = true;
|
|
123
|
+
api.state.followEnd = true;
|
|
124
|
+
api.state.logScroll = 0;
|
|
125
|
+
api.state.running = true;
|
|
126
|
+
api.state.stopping = false;
|
|
127
|
+
api.ui.status = '';
|
|
128
|
+
if (api.ui.progress) api.ui.progress.start('npm');
|
|
129
|
+
const runner = api.ui.repo && api.ui.repo.runNpmCommand;
|
|
130
|
+
const start = typeof runner === 'function' ? runner : startNpm;
|
|
131
|
+
api.state.child = start(
|
|
132
|
+
api.ui.top,
|
|
133
|
+
entry,
|
|
134
|
+
(text) => showOutput(api, reduceOutput(text, api.ui.top, '')),
|
|
135
|
+
(result) => finishRun(api, entry, result),
|
|
136
|
+
);
|
|
137
|
+
api.ui.paint();
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const runSelected = (api) => {
|
|
141
|
+
if (api.state.viewing || api.state.child) return;
|
|
142
|
+
const entry = selected(api);
|
|
143
|
+
if (!entry) return;
|
|
144
|
+
startRun(api, entry);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const rerun = (api) => {
|
|
148
|
+
if (!api.state.viewing || api.state.running) return;
|
|
149
|
+
const entry = selected(api);
|
|
150
|
+
if (!entry) return;
|
|
151
|
+
startRun(api, entry);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const closeView = (api) => {
|
|
155
|
+
if (api.state.running) {
|
|
156
|
+
api.state.stopping = true;
|
|
157
|
+
if (api.state.child) api.state.child.kill();
|
|
158
|
+
if (!api.state.running) return;
|
|
159
|
+
api.state.output = withNotice(api.state.output, 'terminated');
|
|
160
|
+
stopRun(api);
|
|
161
|
+
api.ui.status = 'terminated';
|
|
162
|
+
api.ui.paint();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
api.state.viewing = false;
|
|
166
|
+
api.ui.status = '';
|
|
167
|
+
api.ui.paint();
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const editSelected = (api) => {
|
|
171
|
+
if (api.state.viewing) return;
|
|
172
|
+
if (!canEdit(api.ui)) {
|
|
173
|
+
api.ui.status = 'read only';
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const entry = selected(api);
|
|
177
|
+
if (!entry || entry.kind !== 'script') {
|
|
178
|
+
api.ui.status = 'not a script';
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
api.state.editName = entry.name;
|
|
182
|
+
api.ui.composer.openCompose('npm', scriptLine(entry));
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const newCommand = (api) => {
|
|
186
|
+
if (api.state.viewing) return;
|
|
187
|
+
if (!canEdit(api.ui)) {
|
|
188
|
+
api.ui.status = 'read only';
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
api.state.editName = '';
|
|
192
|
+
api.ui.composer.openCompose('npm', '');
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const finishEdit = (api) => {
|
|
196
|
+
const editor = api.ui.composer.state.editor;
|
|
197
|
+
const text = editor ? editor.text : '';
|
|
198
|
+
const parsed = parseScriptLine(text);
|
|
199
|
+
if (!parsed) {
|
|
200
|
+
api.ui.status = 'name: command';
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
api.ui.ignoreWatch();
|
|
205
|
+
saveScript(api.ui.top, api.state.editName, parsed);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
api.ui.status = error.message;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
api.ui.composer.closeCompose();
|
|
211
|
+
refresh(api);
|
|
212
|
+
const at = api.state.commands.findIndex(
|
|
213
|
+
(entry) => entry.name === parsed.name,
|
|
214
|
+
);
|
|
215
|
+
if (at >= 0) api.ui.nav.npmCursor = at;
|
|
216
|
+
api.ui.status = 'saved';
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const askDrop = (api) => {
|
|
220
|
+
if (api.state.viewing) return;
|
|
221
|
+
if (!canEdit(api.ui)) {
|
|
222
|
+
api.ui.status = 'read only';
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const entry = selected(api);
|
|
226
|
+
if (!entry || entry.kind !== 'script') {
|
|
227
|
+
api.ui.status = 'not a script';
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
api.state.dropKind = 'script';
|
|
231
|
+
api.state.dropName = entry.name;
|
|
232
|
+
api.ui.mode = 'confirmDrop';
|
|
233
|
+
api.ui.status = '';
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const askLogs = (api) => {
|
|
237
|
+
if (api.state.viewing) return;
|
|
238
|
+
if (!canEdit(api.ui)) {
|
|
239
|
+
api.ui.status = 'read only';
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
refreshLogs(api);
|
|
243
|
+
if (!api.state.logBytes) return;
|
|
244
|
+
api.state.dropKind = 'logs';
|
|
245
|
+
const size = formatSize(api.state.logBytes);
|
|
246
|
+
api.state.dropName = `logs older than 5 days (${size})`;
|
|
247
|
+
api.ui.mode = 'confirmDrop';
|
|
248
|
+
api.ui.status = '';
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const confirmDrop = (api) => {
|
|
252
|
+
const kind = api.state.dropKind;
|
|
253
|
+
const name = api.state.dropName;
|
|
254
|
+
api.state.dropName = '';
|
|
255
|
+
api.state.dropKind = '';
|
|
256
|
+
api.ui.mode = 'review';
|
|
257
|
+
if (kind === 'logs') {
|
|
258
|
+
try {
|
|
259
|
+
removeStaleLogs(api.ui.top);
|
|
260
|
+
} catch (error) {
|
|
261
|
+
api.ui.status = error.message;
|
|
262
|
+
refreshLogs(api);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
refreshLogs(api);
|
|
266
|
+
api.ui.status = 'dropped logs';
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (!name) return;
|
|
270
|
+
try {
|
|
271
|
+
api.ui.ignoreWatch();
|
|
272
|
+
removeScript(api.ui.top, name);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
api.ui.status = error.message;
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
refresh(api);
|
|
278
|
+
api.ui.status = `dropped ${name}`;
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const cancelDrop = (api) => {
|
|
282
|
+
api.state.dropName = '';
|
|
283
|
+
api.state.dropKind = '';
|
|
284
|
+
api.ui.mode = 'review';
|
|
285
|
+
api.ui.status = '';
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const reorder = (api, delta) => {
|
|
289
|
+
if (api.state.viewing) return;
|
|
290
|
+
if (!canEdit(api.ui)) {
|
|
291
|
+
api.ui.status = 'read only';
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const entry = selected(api);
|
|
295
|
+
if (!entry || entry.kind !== 'script') {
|
|
296
|
+
api.ui.status = 'not a script';
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
api.ui.ignoreWatch();
|
|
301
|
+
if (!reorderScript(api.ui.top, entry.name, delta)) return;
|
|
302
|
+
} catch (error) {
|
|
303
|
+
api.ui.status = error.message;
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
refresh(api);
|
|
307
|
+
const at = api.state.commands.findIndex((item) => item.name === entry.name);
|
|
308
|
+
if (at >= 0) api.ui.nav.npmCursor = at;
|
|
309
|
+
api.ui.status = '';
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const createNpmController = (ui) => {
|
|
313
|
+
const state = {
|
|
314
|
+
commands: [],
|
|
315
|
+
viewing: false,
|
|
316
|
+
output: '',
|
|
317
|
+
followEnd: true,
|
|
318
|
+
logScroll: 0,
|
|
319
|
+
editName: '',
|
|
320
|
+
dropName: '',
|
|
321
|
+
dropKind: '',
|
|
322
|
+
logBytes: 0,
|
|
323
|
+
running: false,
|
|
324
|
+
stopping: false,
|
|
325
|
+
child: null,
|
|
326
|
+
};
|
|
327
|
+
const api = { ui, state };
|
|
328
|
+
return {
|
|
329
|
+
state,
|
|
330
|
+
get commands() {
|
|
331
|
+
return state.commands;
|
|
332
|
+
},
|
|
333
|
+
get viewing() {
|
|
334
|
+
return state.viewing;
|
|
335
|
+
},
|
|
336
|
+
get output() {
|
|
337
|
+
return state.output;
|
|
338
|
+
},
|
|
339
|
+
get dropName() {
|
|
340
|
+
return state.dropName;
|
|
341
|
+
},
|
|
342
|
+
get logLabel() {
|
|
343
|
+
if (!state.logBytes) return '';
|
|
344
|
+
return `old logs ${formatSize(state.logBytes)}`;
|
|
345
|
+
},
|
|
346
|
+
get followEnd() {
|
|
347
|
+
return state.followEnd;
|
|
348
|
+
},
|
|
349
|
+
get logScroll() {
|
|
350
|
+
return state.logScroll;
|
|
351
|
+
},
|
|
352
|
+
get editName() {
|
|
353
|
+
return state.editName;
|
|
354
|
+
},
|
|
355
|
+
get running() {
|
|
356
|
+
return state.running;
|
|
357
|
+
},
|
|
358
|
+
reset() {
|
|
359
|
+
if (state.child) state.child.kill();
|
|
360
|
+
stopRun(api);
|
|
361
|
+
state.commands = [];
|
|
362
|
+
state.viewing = false;
|
|
363
|
+
state.output = '';
|
|
364
|
+
state.followEnd = true;
|
|
365
|
+
state.logScroll = 0;
|
|
366
|
+
state.editName = '';
|
|
367
|
+
state.dropName = '';
|
|
368
|
+
state.dropKind = '';
|
|
369
|
+
state.logBytes = 0;
|
|
370
|
+
state.stopping = false;
|
|
371
|
+
},
|
|
372
|
+
open: () => open(api),
|
|
373
|
+
move: (delta) => move(api, delta),
|
|
374
|
+
run: () => runSelected(api),
|
|
375
|
+
rerun: () => rerun(api),
|
|
376
|
+
closeView: () => closeView(api),
|
|
377
|
+
edit: () => editSelected(api),
|
|
378
|
+
create: () => newCommand(api),
|
|
379
|
+
finishEdit: () => finishEdit(api),
|
|
380
|
+
askDrop: () => askDrop(api),
|
|
381
|
+
askLogs: () => askLogs(api),
|
|
382
|
+
confirmDrop: () => confirmDrop(api),
|
|
383
|
+
cancelDrop: () => cancelDrop(api),
|
|
384
|
+
reorder: (delta) => reorder(api, delta),
|
|
385
|
+
};
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
module.exports = { createNpmController };
|
package/lib/session/ops.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const accessors = require('./accessors.js');
|
|
4
|
+
const { stateView } = accessors;
|
|
4
5
|
|
|
5
6
|
const ASYNC_WITHOUT_UI = ['pull', 'push'];
|
|
6
7
|
|
|
@@ -81,6 +82,18 @@ const createOpsRunner = (ui) => {
|
|
|
81
82
|
finishBusy(done, after);
|
|
82
83
|
};
|
|
83
84
|
|
|
85
|
+
const runWrite = (name, args, kind, done, after) => {
|
|
86
|
+
const picked = pickRepoWrite(ui.repo, name, ui.uiOpen);
|
|
87
|
+
const write = (top) => picked.write(top, ...args);
|
|
88
|
+
if (picked.async) {
|
|
89
|
+
const refresh = () => after({ doneStatus: done });
|
|
90
|
+
return void runBusy(kind, done, write, refresh);
|
|
91
|
+
}
|
|
92
|
+
if (!runRepo(done, write)) return;
|
|
93
|
+
after({});
|
|
94
|
+
if (!ui.status) ui.status = done;
|
|
95
|
+
};
|
|
96
|
+
|
|
84
97
|
return stateView(
|
|
85
98
|
state,
|
|
86
99
|
{
|
|
@@ -88,6 +101,7 @@ const createOpsRunner = (ui) => {
|
|
|
88
101
|
setBusy,
|
|
89
102
|
clearBusy,
|
|
90
103
|
runRepo,
|
|
104
|
+
runWrite,
|
|
91
105
|
runBusy,
|
|
92
106
|
},
|
|
93
107
|
['gitBusy', 'busy'],
|
package/lib/session/pointer.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const {
|
|
5
|
-
const
|
|
3
|
+
const select = require('../select.js');
|
|
4
|
+
const { extractText, isRange } = select;
|
|
5
|
+
const keys = require('../keys.js');
|
|
6
|
+
const { hitAction } = keys;
|
|
7
|
+
const clipboard = require('../clipboard.js');
|
|
8
|
+
const { copyText } = clipboard;
|
|
6
9
|
|
|
7
10
|
const mouseCell = (ui, event) => {
|
|
8
11
|
const size = ui.lastSize ?? ui.getSize();
|
|
@@ -45,6 +48,20 @@ const clickButton = (ui, cell, clickId) => {
|
|
|
45
48
|
return true;
|
|
46
49
|
};
|
|
47
50
|
|
|
51
|
+
const DOUBLE_CLICK_MS = 500;
|
|
52
|
+
|
|
53
|
+
const armDoubleClick = (nav, pane, cursor) => {
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
const prev = nav.lastClick;
|
|
56
|
+
const again =
|
|
57
|
+
prev !== null &&
|
|
58
|
+
prev.pane === pane &&
|
|
59
|
+
prev.cursor === cursor &&
|
|
60
|
+
now - prev.at <= DOUBLE_CLICK_MS;
|
|
61
|
+
nav.lastClick = { pane, cursor, at: now };
|
|
62
|
+
return again;
|
|
63
|
+
};
|
|
64
|
+
|
|
48
65
|
const clickHit = (ui, hits, cell, onHit) => {
|
|
49
66
|
const nav = ui.nav;
|
|
50
67
|
if (!hits || isRange(nav.selection)) return false;
|
|
@@ -99,9 +116,19 @@ const RELEASE_TARGETS = [
|
|
|
99
116
|
match: (ui) => ui.pane === 'commits',
|
|
100
117
|
hits: 'fileHits',
|
|
101
118
|
onHit: (ui, hit) => {
|
|
119
|
+
if (ui.mode === 'compose') return;
|
|
102
120
|
ui.nav.commitCursor = hit.cursor;
|
|
103
121
|
},
|
|
104
122
|
},
|
|
123
|
+
{
|
|
124
|
+
match: (ui) => ui.pane === 'npm',
|
|
125
|
+
hits: 'fileHits',
|
|
126
|
+
onHit: (ui, hit) => {
|
|
127
|
+
const again = armDoubleClick(ui.nav, 'npm', hit.cursor);
|
|
128
|
+
ui.nav.npmCursor = hit.cursor;
|
|
129
|
+
if (again && ui.mode === 'review') ui.npm.run();
|
|
130
|
+
},
|
|
131
|
+
},
|
|
105
132
|
{
|
|
106
133
|
match: (ui) => ui.pane === 'diff',
|
|
107
134
|
hits: 'todoHits',
|
package/lib/session/review.js
CHANGED
package/lib/session/terminal.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const {
|
|
3
|
+
const render = require('../render/render.js');
|
|
4
|
+
const { presentRows, presentCursor } = render;
|
|
5
|
+
const utilities = require('../utilities.js');
|
|
6
|
+
const { listen, watchResize } = utilities;
|
|
5
7
|
|
|
6
8
|
const ENTER_TERM = '\x1b[?1049h\x1b[?25l\x1b[?7l\x1b[?1002h\x1b[?1006h';
|
|
7
9
|
const LEAVE_TERM =
|
package/lib/session/update.js
CHANGED