reslop 0.2.6 → 0.3.0
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 +16 -14
- package/bin/reslop.js +8 -5
- package/lib/agents/agents.js +504 -0
- package/lib/agents/allow.js +71 -0
- package/lib/agents/allow.json +16 -0
- package/lib/agents/models.js +460 -0
- package/lib/api.js +15 -12
- package/lib/cli.js +61 -66
- package/lib/{files.js → common/files.js} +51 -52
- package/lib/common/find.js +61 -0
- package/lib/common/format.js +101 -0
- package/lib/{utilities.js → common/process.js} +7 -95
- package/lib/common/tasks.json +27 -0
- package/lib/common/utilities.js +112 -0
- package/lib/dashboard/git.js +9 -16
- package/lib/dashboard/model.js +117 -51
- package/lib/dashboard/npm.js +133 -32
- package/lib/dashboard/tree.js +27 -30
- package/lib/{session → dashboard}/watch.js +41 -31
- package/lib/dashboard/workspace.js +151 -0
- package/lib/{deps.js → deps/deps.js} +31 -45
- package/lib/{deps-diff.js → deps/diff.js} +35 -25
- package/lib/{manifest.js → deps/manifest.js} +47 -30
- package/lib/diff/align.js +1 -2
- package/lib/diff/diff.js +2 -2
- package/lib/diff/display.js +29 -5
- package/lib/diff/inline.js +10 -14
- package/lib/diff/items.js +6 -4
- package/lib/diff/patch.js +39 -3
- package/lib/diff/unit.js +45 -16
- package/lib/{git-branches.js → git/branches.js} +53 -48
- package/lib/{git-deps.js → git/deps.js} +123 -95
- package/lib/git/git.js +71 -0
- package/lib/git/worktree.js +475 -0
- package/lib/highlight/bash.js +1 -2
- package/lib/highlight/csv.js +1 -2
- package/lib/highlight/dot.js +1 -2
- package/lib/{highlight.js → highlight/highlight.js} +42 -11
- package/lib/highlight/html.js +20 -18
- package/lib/highlight/json.js +1 -2
- package/lib/highlight/log.js +1 -2
- package/lib/highlight/txt.js +1 -2
- package/lib/{session → input}/actions.js +240 -71
- package/lib/{editor.js → input/editor.js} +7 -8
- package/lib/{keys.js → input/keys.js} +36 -39
- package/lib/input/line-edit.js +77 -0
- package/lib/input/menu-pick.js +70 -0
- package/lib/render/agents.js +640 -0
- package/lib/render/branches.js +4 -5
- package/lib/render/commits.js +54 -64
- package/lib/render/{dash-activity.js → dashboard/activity.js} +25 -40
- package/lib/render/dashboard/blocks.js +490 -0
- package/lib/render/{dashboard.js → dashboard/dashboard.js} +25 -22
- package/lib/render/diff.js +166 -45
- package/lib/render/field.js +131 -0
- package/lib/render/files.js +22 -8
- package/lib/render/footer.js +383 -0
- package/lib/render/frame.js +127 -25
- package/lib/render/header.js +176 -0
- package/lib/render/log.js +319 -0
- package/lib/render/menu.js +60 -0
- package/lib/render/notes.js +29 -13
- package/lib/render/npm.js +322 -196
- package/lib/render/packages.js +89 -0
- package/lib/render/primitives.js +109 -16
- package/lib/render/render.js +7 -11
- package/lib/render/repos.js +214 -0
- package/lib/render/status.js +370 -0
- package/lib/render/status.json +85 -0
- package/lib/render/{dash-table.js → table.js} +19 -61
- package/lib/render/tiles.js +203 -47
- package/lib/render/unit.js +32 -13
- package/lib/{report-model.js → report/model.js} +4 -6
- package/lib/{report-parse.js → report/parse.js} +9 -14
- package/lib/{report-render.js → report/render.js} +46 -14
- package/lib/{report-run.js → report/run.js} +42 -30
- package/lib/{report-test.js → report/test.js} +56 -55
- package/lib/{report-tools.js → report/tools.js} +24 -33
- package/lib/review/agent-instructions.md +9 -0
- package/lib/review/format.js +426 -0
- package/lib/{review-merge.js → review/merge.js} +9 -8
- package/lib/review/review.js +190 -0
- package/lib/review/store.js +281 -0
- package/lib/review/templates.js +83 -0
- package/lib/runs/commands.js +14 -0
- package/lib/runs/logs.js +299 -0
- package/lib/runs/output.js +174 -0
- package/lib/runs/process.js +113 -0
- package/lib/{runs.js → runs/runs.js} +84 -36
- package/lib/runs/scripts.js +150 -0
- package/lib/session/agents/agents.js +958 -0
- package/lib/session/agents/job.js +320 -0
- package/lib/session/agents/jobs.js +355 -0
- package/lib/session/agents/options.js +153 -0
- package/lib/session/agents/plans.js +83 -0
- package/lib/session/branches.js +36 -44
- package/lib/session/change-actions.js +34 -100
- package/lib/session/commit.js +122 -56
- package/lib/session/compose.js +197 -146
- package/lib/session/dashboard.js +183 -37
- package/lib/session/dispatch.js +120 -22
- package/lib/session/files-pane.js +60 -24
- package/lib/session/find.js +81 -0
- package/lib/session/import.js +82 -0
- package/lib/session/items.js +8 -17
- package/lib/session/lifecycle.js +96 -66
- package/lib/session/load.js +2 -4
- package/lib/session/navigation.js +8 -3
- package/lib/session/npm/history.js +249 -0
- package/lib/session/npm/npm.js +815 -0
- package/lib/session/npm/packages.js +150 -0
- package/lib/session/ops.js +125 -34
- package/lib/session/pointer.js +216 -10
- package/lib/session/prefs.js +112 -0
- package/lib/session/reads.js +109 -0
- package/lib/session/review.js +28 -10
- package/lib/session/run-panel.js +40 -0
- package/lib/{session.js → session/session.js} +166 -59
- package/lib/session/tasks.js +321 -66
- package/lib/session/templates.js +1 -1
- package/lib/session/terminal.js +94 -12
- package/lib/session/unit.js +35 -30
- package/lib/session/update.js +194 -6
- package/lib/session/view.js +72 -59
- package/lib/session/workspace.js +416 -0
- package/lib/{capabilities.js → source/capabilities.js} +3 -12
- package/lib/{github.js → source/github.js} +102 -32
- package/lib/{gitlab.js → source/gitlab.js} +87 -10
- package/lib/{remote.js → source/remote.js} +144 -40
- package/lib/source/review-threads.graphql +20 -0
- package/lib/source/source.js +74 -0
- package/lib/term/ansi.js +396 -0
- package/lib/{clipboard.js → term/clipboard.js} +3 -6
- package/lib/{select.js → term/select.js} +2 -2
- package/lib/term/themes/dark.json +122 -0
- package/lib/term/themes/light.json +122 -0
- package/lib/term/wide.txt +21 -0
- package/lib/{wrap.js → term/wrap.js} +4 -4
- package/package.json +2 -2
- package/lib/ansi.js +0 -505
- package/lib/detect.js +0 -34
- package/lib/diff/edit.js +0 -33
- package/lib/git-worktree.js +0 -437
- package/lib/git.js +0 -79
- package/lib/npm-commands.js +0 -522
- package/lib/render/chrome.js +0 -628
- package/lib/render/dash-blocks.js +0 -245
- package/lib/render/present.js +0 -19
- package/lib/render/report.js +0 -43
- package/lib/review.js +0 -744
- package/lib/session/accessors.js +0 -26
- package/lib/session/edit-click.js +0 -57
- package/lib/session/npm.js +0 -452
- package/lib/session/progress.js +0 -41
- package/lib/source.js +0 -36
- package/lib/update.js +0 -192
- /package/lib/{git-reword-todo.js → git/reword-todo.js} +0 -0
package/lib/session/tasks.js
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
|
|
5
|
+
const { clamp, trimText } = require('../common/utilities.js');
|
|
6
|
+
const files = require('../common/files.js');
|
|
7
|
+
const { isTasksEntry, TASKS_FILE, TASK_KINDS, taskKind, taskTitle } = files;
|
|
8
|
+
const { REVIEW_DIR } = files;
|
|
9
|
+
const review = require('../review/review.js');
|
|
10
|
+
const { checkLabel } = review;
|
|
11
|
+
const { orderedPlanNames, allocateReviewPath } = review;
|
|
12
|
+
const plans = require('./agents/plans.js');
|
|
13
|
+
const { planKey, planPart, alignPlanLabels } = plans;
|
|
14
|
+
const { MenuPick, menuRows, pickDelta } = require('../input/menu-pick.js');
|
|
15
|
+
const { isTextKey } = require('../input/keys.js');
|
|
16
|
+
const { lineKey } = require('../input/line-edit.js');
|
|
7
17
|
|
|
8
18
|
const TASK_TYPE_SKIP = [
|
|
9
19
|
'enter',
|
|
@@ -27,23 +37,45 @@ const TASK_TYPE_SKIP = [
|
|
|
27
37
|
'f',
|
|
28
38
|
'd',
|
|
29
39
|
't',
|
|
40
|
+
'i',
|
|
30
41
|
'b',
|
|
31
42
|
'r',
|
|
32
43
|
'c',
|
|
33
44
|
];
|
|
34
45
|
|
|
35
46
|
const isTaskTypeKey = (key) => {
|
|
36
|
-
if (!key) return false;
|
|
37
|
-
|
|
38
|
-
if (key.startsWith('ctrl-') || key.startsWith('alt-')) return false;
|
|
39
|
-
return key.charCodeAt(0) >= 32;
|
|
47
|
+
if (!isTextKey(key) || key === ' ') return false;
|
|
48
|
+
return !TASK_TYPE_SKIP.includes(key);
|
|
40
49
|
};
|
|
41
50
|
|
|
42
|
-
const
|
|
51
|
+
const sectionTitle = (row) => {
|
|
52
|
+
if (row.quote) return row.file;
|
|
53
|
+
return taskTitle(row.kind);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const idlePick = () => false;
|
|
57
|
+
const NEW_PLAN = '<new plan>';
|
|
58
|
+
const PLAN_ROWS = 8;
|
|
59
|
+
|
|
60
|
+
const planListSize = (ui) => {
|
|
61
|
+
const frame = ui.lastFrame;
|
|
62
|
+
const height = frame && frame.height ? frame.height : ui.getSize().height;
|
|
63
|
+
const room = Math.max(1, height - 3);
|
|
64
|
+
return Math.min(room, PLAN_ROWS);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const planOpenScroll = (cursor, extra, count, size) => {
|
|
68
|
+
const span = Math.abs(extra - cursor);
|
|
69
|
+
const end = span < size ? Math.max(cursor, extra) : cursor;
|
|
70
|
+
const start = Math.max(0, end - size + 1);
|
|
71
|
+
const maxStart = Math.max(0, count - size);
|
|
72
|
+
return Math.min(start, maxStart);
|
|
73
|
+
};
|
|
43
74
|
|
|
44
75
|
class TaskPage {
|
|
45
76
|
constructor(composer) {
|
|
46
77
|
this.composer = composer;
|
|
78
|
+
this.planPick = null;
|
|
47
79
|
}
|
|
48
80
|
|
|
49
81
|
get ui() {
|
|
@@ -68,14 +100,46 @@ class TaskPage {
|
|
|
68
100
|
return rows;
|
|
69
101
|
}
|
|
70
102
|
|
|
103
|
+
quoteSections() {
|
|
104
|
+
const notes = this.ui.review.store;
|
|
105
|
+
if (!notes) return [];
|
|
106
|
+
const groups = new Map();
|
|
107
|
+
for (const [key, note] of notes.feedback) {
|
|
108
|
+
if (!trimText(note.text)) continue;
|
|
109
|
+
const file = note.file || key;
|
|
110
|
+
let items = groups.get(file);
|
|
111
|
+
if (!items) {
|
|
112
|
+
items = [];
|
|
113
|
+
groups.set(file, items);
|
|
114
|
+
}
|
|
115
|
+
items.push({ key, note });
|
|
116
|
+
}
|
|
117
|
+
const sections = [];
|
|
118
|
+
for (const [file, items] of groups) sections.push({ file, items });
|
|
119
|
+
return sections;
|
|
120
|
+
}
|
|
121
|
+
|
|
71
122
|
rows() {
|
|
72
123
|
const rows = [];
|
|
73
124
|
for (const kind of TASK_KINDS) {
|
|
74
125
|
const tasks = this.list(kind.id);
|
|
75
126
|
for (const task of tasks) {
|
|
76
|
-
rows.push({ kind: kind.id, task, draft: false });
|
|
127
|
+
rows.push({ quote: false, kind: kind.id, task, draft: false });
|
|
128
|
+
}
|
|
129
|
+
rows.push({ quote: false, kind: kind.id, task: null, draft: true });
|
|
130
|
+
}
|
|
131
|
+
for (const section of this.quoteSections()) {
|
|
132
|
+
for (const item of section.items) {
|
|
133
|
+
rows.push({
|
|
134
|
+
quote: true,
|
|
135
|
+
kind: section.file,
|
|
136
|
+
file: section.file,
|
|
137
|
+
key: item.key,
|
|
138
|
+
note: item.note,
|
|
139
|
+
task: null,
|
|
140
|
+
draft: false,
|
|
141
|
+
});
|
|
77
142
|
}
|
|
78
|
-
rows.push({ kind: kind.id, task: null, draft: true });
|
|
79
143
|
}
|
|
80
144
|
return rows;
|
|
81
145
|
}
|
|
@@ -89,13 +153,17 @@ class TaskPage {
|
|
|
89
153
|
if (!dest) return 0;
|
|
90
154
|
for (let i = 0; i < rows.length; i++) {
|
|
91
155
|
const row = rows[i];
|
|
156
|
+
if (dest.quote) {
|
|
157
|
+
if (row.quote && row.key === dest.key) return i;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
92
160
|
if (dest.draft) {
|
|
93
161
|
if (row.draft && row.kind === dest.kind) return i;
|
|
94
162
|
continue;
|
|
95
163
|
}
|
|
96
164
|
if (row.task && dest.task && row.task.id === dest.task.id) return i;
|
|
97
165
|
}
|
|
98
|
-
return
|
|
166
|
+
return 0;
|
|
99
167
|
}
|
|
100
168
|
|
|
101
169
|
editingId() {
|
|
@@ -105,7 +173,11 @@ class TaskPage {
|
|
|
105
173
|
|
|
106
174
|
isDraftCompose() {
|
|
107
175
|
const composer = this.composer;
|
|
108
|
-
return
|
|
176
|
+
return (
|
|
177
|
+
composer.composeKind === 'tasks' &&
|
|
178
|
+
composer.composeTaskId === null &&
|
|
179
|
+
!composer.composeQuoteKey
|
|
180
|
+
);
|
|
109
181
|
}
|
|
110
182
|
|
|
111
183
|
canToggle() {
|
|
@@ -113,14 +185,10 @@ class TaskPage {
|
|
|
113
185
|
return this.ui.mode === 'review';
|
|
114
186
|
}
|
|
115
187
|
|
|
116
|
-
focused() {
|
|
117
|
-
const row = this.rowAt(this.clampedTaskFocus());
|
|
118
|
-
return row && row.task ? row.task : null;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
188
|
focusedKind() {
|
|
122
189
|
const row = this.rowAt(this.clampedTaskFocus());
|
|
123
|
-
|
|
190
|
+
if (!row || row.quote) return TASK_KINDS[0].id;
|
|
191
|
+
return row.kind;
|
|
124
192
|
}
|
|
125
193
|
|
|
126
194
|
closeEditor() {
|
|
@@ -128,26 +196,36 @@ class TaskPage {
|
|
|
128
196
|
this.composer.closeCompose();
|
|
129
197
|
}
|
|
130
198
|
|
|
131
|
-
|
|
132
|
-
if (!this.isOpen()) return [];
|
|
199
|
+
rowText(row, at) {
|
|
133
200
|
const editor = this.composer.editor;
|
|
134
|
-
const editingId = this.editingId();
|
|
135
|
-
const draft = this.isDraftCompose();
|
|
136
201
|
const focus = this.clampedTaskFocus();
|
|
137
|
-
|
|
202
|
+
if (row.draft) {
|
|
203
|
+
const live = this.isDraftCompose() && at === focus && editor;
|
|
204
|
+
return checkLabel(live ? editor.text : '', false);
|
|
205
|
+
}
|
|
206
|
+
if (row.quote) {
|
|
207
|
+
const live = this.composer.composeQuoteKey === row.key && editor;
|
|
208
|
+
const text = live ? editor.text : row.note.text;
|
|
209
|
+
return checkLabel(text, row.note.done);
|
|
210
|
+
}
|
|
211
|
+
const live = this.editingId() === row.task.id && editor;
|
|
212
|
+
const text = live ? editor.text : row.task.text;
|
|
213
|
+
return checkLabel(text, row.task.done);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
taskSections() {
|
|
217
|
+
if (!this.isOpen()) return [];
|
|
138
218
|
const sections = [];
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
219
|
+
let current = null;
|
|
220
|
+
let at = 0;
|
|
221
|
+
for (const row of this.rows()) {
|
|
222
|
+
const key = row.quote ? `quote:${row.file}` : row.kind;
|
|
223
|
+
if (!current || current.key !== key) {
|
|
224
|
+
current = { key, title: sectionTitle(row), texts: [] };
|
|
225
|
+
sections.push(current);
|
|
146
226
|
}
|
|
147
|
-
|
|
148
|
-
texts.push(checkLabel(drafting ? editor.text : '', false));
|
|
227
|
+
current.texts.push(this.rowText(row, at));
|
|
149
228
|
at += 1;
|
|
150
|
-
sections.push({ title: kind.title, texts });
|
|
151
229
|
}
|
|
152
230
|
return sections;
|
|
153
231
|
}
|
|
@@ -164,13 +242,13 @@ class TaskPage {
|
|
|
164
242
|
}
|
|
165
243
|
|
|
166
244
|
clampedTaskFocus() {
|
|
167
|
-
return clamp(this.nav.tasksFocus, this.taskRowCount() - 1);
|
|
245
|
+
return clamp(this.nav.tasksFocus, 0, this.taskRowCount() - 1);
|
|
168
246
|
}
|
|
169
247
|
|
|
170
248
|
moveTaskFocus(delta) {
|
|
171
249
|
const count = this.taskRowCount();
|
|
172
250
|
if (!count) return;
|
|
173
|
-
this.nav.tasksFocus = clamp(this.clampedTaskFocus() + delta, count - 1);
|
|
251
|
+
this.nav.tasksFocus = clamp(this.clampedTaskFocus() + delta, 0, count - 1);
|
|
174
252
|
}
|
|
175
253
|
|
|
176
254
|
openTasksPage() {
|
|
@@ -182,16 +260,9 @@ class TaskPage {
|
|
|
182
260
|
this.ui.syncReviewPath();
|
|
183
261
|
}
|
|
184
262
|
|
|
185
|
-
closeTasksPage() {
|
|
186
|
-
this.nav.tasksOpen = false;
|
|
187
|
-
this.ui.syncReviewPath();
|
|
188
|
-
this.ui.syncFileCursor();
|
|
189
|
-
this.ui.lifecycle.catchUpWatch();
|
|
190
|
-
}
|
|
191
|
-
|
|
192
263
|
startDraftCompose() {
|
|
193
264
|
this.openTasksPage();
|
|
194
|
-
this.nav.tasksFocus = this.list(
|
|
265
|
+
this.nav.tasksFocus = this.list(TASK_KINDS[0].id).length;
|
|
195
266
|
this.nav.scroll = 0;
|
|
196
267
|
this.composer.openCompose('tasks', '', null);
|
|
197
268
|
}
|
|
@@ -203,6 +274,10 @@ class TaskPage {
|
|
|
203
274
|
this.composer.openCompose('tasks', '', null);
|
|
204
275
|
return;
|
|
205
276
|
}
|
|
277
|
+
if (row.quote) {
|
|
278
|
+
this.composer.openQuote(row.key, row.note.text);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
206
281
|
this.composer.openCompose('tasks', row.task.text, row.task.id);
|
|
207
282
|
}
|
|
208
283
|
|
|
@@ -223,7 +298,7 @@ class TaskPage {
|
|
|
223
298
|
}
|
|
224
299
|
const rows = this.rows();
|
|
225
300
|
const at = this.clampedTaskFocus();
|
|
226
|
-
const next = clamp(at + delta, rows.length - 1);
|
|
301
|
+
const next = clamp(at + delta, 0, rows.length - 1);
|
|
227
302
|
if (next === at) return;
|
|
228
303
|
const dest = rows[next];
|
|
229
304
|
this.composer.commitCompose();
|
|
@@ -244,9 +319,18 @@ class TaskPage {
|
|
|
244
319
|
const nav = this.nav;
|
|
245
320
|
if (!this.isOpen()) return;
|
|
246
321
|
const row = this.rowAt(this.clampedTaskFocus());
|
|
247
|
-
if (!row ||
|
|
322
|
+
if (!row || row.draft) return;
|
|
323
|
+
const store = this.ui.review.store;
|
|
324
|
+
if (row.quote) {
|
|
325
|
+
store.setFeedback(row.key, { ...row.note, text: '' });
|
|
326
|
+
nav.tasksFocus = Math.min(nav.tasksFocus, this.rows().length - 1);
|
|
327
|
+
nav.tasksFocus = Math.max(0, nav.tasksFocus);
|
|
328
|
+
this.ui.flushReview(true);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (!row.task) return;
|
|
248
332
|
const kind = row.kind;
|
|
249
|
-
removeTask(
|
|
333
|
+
store.removeTask(row.task.id);
|
|
250
334
|
const rows = this.rows();
|
|
251
335
|
let focus = Math.min(nav.tasksFocus, rows.length - 1);
|
|
252
336
|
const at = rows[focus];
|
|
@@ -260,24 +344,51 @@ class TaskPage {
|
|
|
260
344
|
|
|
261
345
|
toggleFocusedTask() {
|
|
262
346
|
if (!this.canToggle()) return false;
|
|
263
|
-
const
|
|
264
|
-
if (!
|
|
265
|
-
|
|
347
|
+
const row = this.rowAt(this.clampedTaskFocus());
|
|
348
|
+
if (!row || row.draft) return false;
|
|
349
|
+
const store = this.ui.review.store;
|
|
350
|
+
if (row.quote) store.setFeedbackDone(row.key, !row.note.done);
|
|
351
|
+
else if (row.task) store.setTaskDone(row.task.id, !row.task.done);
|
|
352
|
+
else return false;
|
|
266
353
|
this.ui.flushReview(true);
|
|
267
354
|
return true;
|
|
268
355
|
}
|
|
269
356
|
|
|
357
|
+
followRow(dest) {
|
|
358
|
+
this.nav.tasksFocus = this.rowIndex(dest);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
moveIssue(delta) {
|
|
362
|
+
if (!this.isOpen() || !delta) return;
|
|
363
|
+
const row = this.rowAt(this.clampedTaskFocus());
|
|
364
|
+
if (!row || row.draft) return;
|
|
365
|
+
this.composer.commitCompose();
|
|
366
|
+
const store = this.ui.review.store;
|
|
367
|
+
const moved = row.quote
|
|
368
|
+
? store.moveFeedback(row.key, delta)
|
|
369
|
+
: store.moveTask(row.task.id, delta);
|
|
370
|
+
if (!moved) return;
|
|
371
|
+
this.composer.closeCompose();
|
|
372
|
+
this.followRow(row.quote ? { quote: true, key: row.key } : row);
|
|
373
|
+
this.ui.flushReview(true);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
sameCompose(row, cursor) {
|
|
377
|
+
const composer = this.composer;
|
|
378
|
+
if (cursor !== this.clampedTaskFocus()) return false;
|
|
379
|
+
if (row.draft) return this.isDraftCompose();
|
|
380
|
+
if (row.quote) return composer.composeQuoteKey === row.key;
|
|
381
|
+
return row.task && row.task.id === composer.composeTaskId;
|
|
382
|
+
}
|
|
383
|
+
|
|
270
384
|
onTaskHit(cursor) {
|
|
271
385
|
if (!this.isOpen()) return;
|
|
386
|
+
this.closePlanMenu();
|
|
272
387
|
const row = this.rowAt(cursor);
|
|
273
388
|
if (!row) return;
|
|
274
389
|
const composer = this.composer;
|
|
275
390
|
if (composer.composeKind === 'tasks') {
|
|
276
|
-
|
|
277
|
-
cursor === this.clampedTaskFocus() &&
|
|
278
|
-
((row.draft && composer.composeTaskId === null) ||
|
|
279
|
-
(row.task && row.task.id === composer.composeTaskId));
|
|
280
|
-
if (same) return;
|
|
391
|
+
if (this.sameCompose(row, cursor)) return;
|
|
281
392
|
this.closeEditor();
|
|
282
393
|
}
|
|
283
394
|
this.nav.tasksFocus = cursor;
|
|
@@ -287,15 +398,20 @@ class TaskPage {
|
|
|
287
398
|
onTaskCheck(cursor) {
|
|
288
399
|
if (!this.isOpen()) return;
|
|
289
400
|
const row = this.rowAt(cursor);
|
|
290
|
-
if (!row ||
|
|
401
|
+
if (!row || row.draft) return void this.onTaskHit(cursor);
|
|
291
402
|
const composer = this.composer;
|
|
292
|
-
|
|
403
|
+
const otherTask =
|
|
404
|
+
row.task &&
|
|
293
405
|
composer.composeKind === 'tasks' &&
|
|
294
|
-
composer.composeTaskId !== row.task.id
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
406
|
+
composer.composeTaskId !== row.task.id;
|
|
407
|
+
const otherQuote =
|
|
408
|
+
row.quote &&
|
|
409
|
+
composer.composeKind === 'tasks' &&
|
|
410
|
+
composer.composeQuoteKey !== row.key;
|
|
411
|
+
if (otherTask || otherQuote) this.closeEditor();
|
|
412
|
+
const store = this.ui.review.store;
|
|
413
|
+
if (row.quote) store.setFeedbackDone(row.key, !row.note.done);
|
|
414
|
+
else store.setTaskDone(row.task.id, !row.task.done);
|
|
299
415
|
this.nav.tasksFocus = cursor;
|
|
300
416
|
this.ui.flushReview(true);
|
|
301
417
|
}
|
|
@@ -319,15 +435,154 @@ class TaskPage {
|
|
|
319
435
|
|
|
320
436
|
commitTask(text) {
|
|
321
437
|
const notes = this.ui.review.store;
|
|
438
|
+
const key = this.composer.composeQuoteKey;
|
|
439
|
+
if (key) {
|
|
440
|
+
const note = notes.feedback.get(key);
|
|
441
|
+
if (!note) return;
|
|
442
|
+
notes.setFeedback(key, { ...note, text });
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
322
445
|
const id = this.composer.composeTaskId;
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
if (prev) return void setTaskText(
|
|
446
|
+
const found = notes.tasks.find((task) => task.id === id);
|
|
447
|
+
const prev = id === null ? null : found;
|
|
448
|
+
if (prev) return void notes.setTaskText(id, text);
|
|
326
449
|
if (!text.trim()) return;
|
|
327
450
|
const kind = this.focusedKind();
|
|
328
|
-
const todo = addTask(
|
|
451
|
+
const todo = notes.addTask(TASKS_FILE, text, false, kind);
|
|
329
452
|
this.composer.composeTaskId = todo.id;
|
|
330
453
|
}
|
|
454
|
+
|
|
455
|
+
planName() {
|
|
456
|
+
const store = this.ui.review.store;
|
|
457
|
+
if (!store || !store.reviewPath) return '';
|
|
458
|
+
return path.basename(store.reviewPath);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
planNames() {
|
|
462
|
+
const names = orderedPlanNames(this.ui.top || this.ui.cwd);
|
|
463
|
+
const current = this.planName();
|
|
464
|
+
if (current && !names.includes(current)) names.push(current);
|
|
465
|
+
return names;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
planLabels() {
|
|
469
|
+
const root = this.ui.top || this.ui.cwd;
|
|
470
|
+
const dir = path.join(root, REVIEW_DIR);
|
|
471
|
+
const store = this.ui.review.store;
|
|
472
|
+
const parts = this.planNames().map((name) => planPart(dir, name, store));
|
|
473
|
+
const labels = alignPlanLabels(parts.filter(Boolean));
|
|
474
|
+
labels.push(NEW_PLAN);
|
|
475
|
+
return labels;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
planMenu() {
|
|
479
|
+
const labels = this.planLabels();
|
|
480
|
+
if (!this.planPick) return labels;
|
|
481
|
+
return this.planPick.filter(labels);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
planQuery() {
|
|
485
|
+
return this.planPick ? this.planPick.query : '';
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
planScroll() {
|
|
489
|
+
return this.planPick ? this.planPick.scroll : 0;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
view(active) {
|
|
493
|
+
const pick = this.planPick;
|
|
494
|
+
return {
|
|
495
|
+
tasks: this.taskTexts(),
|
|
496
|
+
taskSections: this.taskSections(),
|
|
497
|
+
tasksFocus: this.clampedTaskFocus(),
|
|
498
|
+
taskEdit: this.taskEditView(),
|
|
499
|
+
planName: this.planName(),
|
|
500
|
+
planNames: active && pick ? this.planMenu() : [],
|
|
501
|
+
planOpen: this.planOpen,
|
|
502
|
+
planCursor: this.planCursor,
|
|
503
|
+
planScroll: this.planScroll(),
|
|
504
|
+
planQuery: this.planQuery(),
|
|
505
|
+
planEditor: pick ? pick.editor : null,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
get planOpen() {
|
|
510
|
+
return this.planPick !== null;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
get planCursor() {
|
|
514
|
+
return this.planPick ? this.planPick.cursor : 0;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
closePlanMenu() {
|
|
518
|
+
this.planPick = null;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
togglePlanMenu() {
|
|
522
|
+
if (this.planPick) return void this.closePlanMenu();
|
|
523
|
+
const labels = this.planLabels();
|
|
524
|
+
if (!labels.length) return;
|
|
525
|
+
const current = this.planName();
|
|
526
|
+
const at = labels.findIndex((item) => planKey(item) === current);
|
|
527
|
+
const cursor = at < 0 ? labels.length - 1 : at;
|
|
528
|
+
const extra = labels.length - 1;
|
|
529
|
+
const size = planListSize(this.ui);
|
|
530
|
+
this.planPick = new MenuPick('plan', cursor);
|
|
531
|
+
this.planPick.scroll = planOpenScroll(cursor, extra, labels.length, size);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
movePlan(delta) {
|
|
535
|
+
const pick = this.planPick;
|
|
536
|
+
const items = this.planMenu();
|
|
537
|
+
if (!pick || !items.length) return;
|
|
538
|
+
pick.move(delta, items, idlePick);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
planStep(key) {
|
|
542
|
+
const body = this.ui.lastFrame && this.ui.lastFrame.bodyH;
|
|
543
|
+
return pickDelta(key, menuRows(body), menuRows(body, 0.5));
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
notePlanEdit(changed) {
|
|
547
|
+
if (changed) this.planPick.touch();
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
onPlanKey(key) {
|
|
551
|
+
const pick = this.planPick;
|
|
552
|
+
if (!pick) return;
|
|
553
|
+
if (key === 'escape') return void this.closePlanMenu();
|
|
554
|
+
if (key === 'enter') return void this.choosePlan(pick.cursor);
|
|
555
|
+
if (key === ' ') return;
|
|
556
|
+
const handled = lineKey(pick.editor, key, this.ui, false);
|
|
557
|
+
if (handled) return void this.notePlanEdit(handled === 'text');
|
|
558
|
+
const step = this.planStep(key);
|
|
559
|
+
if (step) this.movePlan(step);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
openPlan(name) {
|
|
563
|
+
if (!name || name === this.planName()) return;
|
|
564
|
+
this.composer.commitCompose();
|
|
565
|
+
this.composer.closeCompose();
|
|
566
|
+
const dir = this.ui.top || this.ui.cwd;
|
|
567
|
+
if (!this.ui.review.usePlan(dir, name)) return;
|
|
568
|
+
this.nav.tasksFocus = 0;
|
|
569
|
+
this.nav.scroll = 0;
|
|
570
|
+
this.ui.lifecycle.noteReviewStamp();
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
createPlan() {
|
|
574
|
+
const dir = this.ui.top || this.ui.cwd;
|
|
575
|
+
const reviewPath = allocateReviewPath(dir, new Date(), this.planNames());
|
|
576
|
+
this.openPlan(path.basename(reviewPath));
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
choosePlan(index) {
|
|
580
|
+
const label = this.planMenu()[index];
|
|
581
|
+
const name = label ? planKey(label) : '';
|
|
582
|
+
this.closePlanMenu();
|
|
583
|
+
if (name === NEW_PLAN) return void this.createPlan();
|
|
584
|
+
this.openPlan(name);
|
|
585
|
+
}
|
|
331
586
|
}
|
|
332
587
|
|
|
333
588
|
module.exports = { TaskPage };
|
package/lib/session/templates.js
CHANGED
package/lib/session/terminal.js
CHANGED
|
@@ -1,9 +1,36 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
3
|
+
const { presentRows, presentCursor } = require('../render/render.js');
|
|
4
|
+
|
|
5
|
+
const listen = (emitter, event, handler) => {
|
|
6
|
+
if (!emitter || typeof emitter.on !== 'function') return () => {};
|
|
7
|
+
emitter.on(event, handler);
|
|
8
|
+
return () => {
|
|
9
|
+
if (typeof emitter.removeListener === 'function') {
|
|
10
|
+
return void emitter.removeListener(event, handler);
|
|
11
|
+
}
|
|
12
|
+
if (typeof emitter.off === 'function') emitter.off(event, handler);
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const watchResize = (stdout, proc, onResize, signal) => {
|
|
17
|
+
const stopStdout = listen(stdout, 'resize', onResize);
|
|
18
|
+
let stopProc = () => {};
|
|
19
|
+
try {
|
|
20
|
+
stopProc = listen(proc, 'SIGWINCH', onResize);
|
|
21
|
+
} catch {
|
|
22
|
+
// SIGWINCH is not available on Windows
|
|
23
|
+
}
|
|
24
|
+
const stop = () => {
|
|
25
|
+
stopStdout();
|
|
26
|
+
stopProc();
|
|
27
|
+
};
|
|
28
|
+
if (signal) {
|
|
29
|
+
if (signal.aborted) stop();
|
|
30
|
+
else signal.addEventListener('abort', stop, { once: true });
|
|
31
|
+
}
|
|
32
|
+
return stop;
|
|
33
|
+
};
|
|
7
34
|
|
|
8
35
|
const ENTER_TERM = '\x1b[?1049h\x1b[?25l\x1b[?7l\x1b[?1002h\x1b[?1006h';
|
|
9
36
|
const LEAVE_TERM =
|
|
@@ -14,8 +41,12 @@ const sameCursor = (a, b) => {
|
|
|
14
41
|
return a.x === b.x && a.y === b.y;
|
|
15
42
|
};
|
|
16
43
|
|
|
17
|
-
const sameSize = (a, b) =>
|
|
18
|
-
|
|
44
|
+
const sameSize = (a, b) => {
|
|
45
|
+
if (a === null) return false;
|
|
46
|
+
const sameWidth = a.width === b.width;
|
|
47
|
+
const sameHeight = a.height === b.height;
|
|
48
|
+
return sameWidth && sameHeight;
|
|
49
|
+
};
|
|
19
50
|
|
|
20
51
|
class Terminal {
|
|
21
52
|
constructor({ stdin = null, stdout, proc = process }) {
|
|
@@ -36,10 +67,6 @@ class Terminal {
|
|
|
36
67
|
this.lastSize = null;
|
|
37
68
|
}
|
|
38
69
|
|
|
39
|
-
hasTimer(name) {
|
|
40
|
-
return this.timers.has(name);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
70
|
cancel(name) {
|
|
44
71
|
const id = this.timers.get(name);
|
|
45
72
|
if (id === undefined) return;
|
|
@@ -87,6 +114,7 @@ class Terminal {
|
|
|
87
114
|
|
|
88
115
|
startListening({ onData, onResize }) {
|
|
89
116
|
if (this.disposed) return;
|
|
117
|
+
this.listenOpts = { onData, onResize };
|
|
90
118
|
this.disposers.push(listen(this.stdin, 'data', onData));
|
|
91
119
|
this.disposers.push(watchResize(this.stdout, this.proc, onResize));
|
|
92
120
|
}
|
|
@@ -95,6 +123,19 @@ class Terminal {
|
|
|
95
123
|
while (this.disposers.length) this.disposers.pop()();
|
|
96
124
|
}
|
|
97
125
|
|
|
126
|
+
pause() {
|
|
127
|
+
this.stopListening();
|
|
128
|
+
this.leave();
|
|
129
|
+
if (this.stdin && this.stdin.pause) this.stdin.pause();
|
|
130
|
+
this.resetCache();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
resume() {
|
|
134
|
+
if (this.disposed || this.open) return;
|
|
135
|
+
this.enter();
|
|
136
|
+
if (this.listenOpts) this.startListening(this.listenOpts);
|
|
137
|
+
}
|
|
138
|
+
|
|
98
139
|
paint(frame, size, cursor) {
|
|
99
140
|
if (this.disposed) return;
|
|
100
141
|
const resized = !sameSize(this.lastSize, size);
|
|
@@ -107,8 +148,49 @@ class Terminal {
|
|
|
107
148
|
}
|
|
108
149
|
this.lastSize = size;
|
|
109
150
|
this.lastCursor = cursor;
|
|
110
|
-
|
|
151
|
+
const prev = resized || !last ? null : last.rows;
|
|
152
|
+
this.stdout.write(
|
|
153
|
+
presentRows(frame.rows, { clear: resized, cursor, prev }),
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
class Progress {
|
|
159
|
+
constructor(term, onTick, intervalMs) {
|
|
160
|
+
this.term = term;
|
|
161
|
+
this.onTick = onTick;
|
|
162
|
+
this.intervalMs = intervalMs;
|
|
163
|
+
this.active = new Set();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
tick() {
|
|
167
|
+
if (!this.active.size) return;
|
|
168
|
+
this.onTick();
|
|
169
|
+
this.schedule();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
schedule() {
|
|
173
|
+
this.term.later('progress', () => this.tick(), this.intervalMs);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
start(id) {
|
|
177
|
+
this.active.add(id);
|
|
178
|
+
this.schedule();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
stop(id) {
|
|
182
|
+
this.active.delete(id);
|
|
183
|
+
if (!this.active.size) this.term.cancel('progress');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
clear() {
|
|
187
|
+
this.active.clear();
|
|
188
|
+
this.term.cancel('progress');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
size() {
|
|
192
|
+
return this.active.size;
|
|
111
193
|
}
|
|
112
194
|
}
|
|
113
195
|
|
|
114
|
-
module.exports = { ENTER_TERM, LEAVE_TERM, Terminal };
|
|
196
|
+
module.exports = { watchResize, ENTER_TERM, LEAVE_TERM, Terminal, Progress };
|