reslop 0.2.3 → 0.2.5
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 +8 -4
- package/bin/reslop.js +4 -2
- package/lib/api.js +2 -1
- package/lib/capabilities.js +0 -4
- package/lib/cli.js +19 -10
- package/lib/clipboard.js +45 -4
- package/lib/deps-diff.js +61 -118
- package/lib/deps.js +166 -235
- 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 +68 -2
- package/lib/diff/patch.js +2 -1
- package/lib/diff/unit.js +15 -30
- package/lib/editor.js +91 -13
- package/lib/files.js +54 -27
- package/lib/git-branches.js +107 -154
- package/lib/git-deps.js +78 -155
- package/lib/git-worktree.js +508 -0
- package/lib/git.js +23 -580
- package/lib/github.js +9 -8
- package/lib/gitlab.js +5 -4
- package/lib/highlight/bash.js +2 -1
- package/lib/highlight/core.js +11 -0
- package/lib/highlight/css.js +2 -1
- package/lib/highlight/csv.js +2 -1
- package/lib/highlight/dot.js +3 -12
- package/lib/highlight/html.js +2 -1
- package/lib/highlight/json.js +2 -1
- package/lib/highlight/log.js +5 -11
- package/lib/highlight/txt.js +3 -12
- package/lib/highlight.js +2 -1
- package/lib/keys.js +22 -12
- package/lib/manifest.js +4 -6
- package/lib/npm-commands.js +82 -24
- package/lib/remote.js +21 -20
- package/lib/render/branches.js +9 -27
- package/lib/render/chrome.js +109 -35
- package/lib/render/commits.js +216 -216
- package/lib/render/diff.js +160 -47
- package/lib/render/files.js +9 -26
- package/lib/render/frame.js +97 -118
- package/lib/render/notes.js +74 -26
- package/lib/render/npm.js +104 -30
- package/lib/render/present.js +2 -1
- package/lib/render/primitives.js +128 -52
- package/lib/render/render.js +2 -1
- package/lib/render/unit.js +17 -11
- package/lib/report-model.js +6 -4
- package/lib/report-parse.js +8 -14
- package/lib/report-render.js +38 -21
- package/lib/report-run.js +12 -11
- package/lib/report-test.js +45 -51
- package/lib/report-tools.js +44 -25
- package/lib/review-merge.js +102 -131
- package/lib/review.js +69 -78
- package/lib/select.js +52 -0
- package/lib/session/actions.js +72 -87
- package/lib/session/branches.js +18 -52
- package/lib/session/change-actions.js +175 -147
- package/lib/session/commit.js +65 -61
- package/lib/session/compose.js +152 -83
- package/lib/session/dispatch.js +53 -46
- package/lib/session/edit-click.js +57 -0
- package/lib/session/files-pane.js +30 -51
- package/lib/session/items.js +73 -90
- package/lib/session/lifecycle.js +37 -98
- package/lib/session/load.js +52 -57
- package/lib/session/navigation.js +105 -95
- package/lib/session/npm.js +205 -71
- package/lib/session/ops.js +200 -45
- package/lib/session/pointer.js +33 -8
- package/lib/session/progress.js +9 -6
- package/lib/session/review.js +35 -53
- package/lib/session/tasks.js +292 -0
- package/lib/session/terminal.js +17 -19
- package/lib/session/unit.js +3 -4
- package/lib/session/update.js +76 -96
- package/lib/session/view.js +45 -16
- package/lib/session/watch.js +47 -27
- package/lib/session.js +66 -97
- package/lib/source.js +6 -3
- package/lib/update.js +5 -13
- package/lib/utilities.js +25 -3
- package/lib/wrap.js +18 -1
- package/package.json +1 -1
- package/lib/session/todos.js +0 -293
package/lib/render/notes.js
CHANGED
|
@@ -5,9 +5,9 @@ const wrap = require('../wrap.js');
|
|
|
5
5
|
const files = require('../files.js');
|
|
6
6
|
const primitives = require('./primitives.js');
|
|
7
7
|
|
|
8
|
-
const {
|
|
9
|
-
const { wrapMultiline, cursorInWrap } = wrap;
|
|
10
|
-
const { THEME,
|
|
8
|
+
const { REPO_TASKS_LABEL } = files;
|
|
9
|
+
const { wrapMultiline, wrapDoc, cursorInWrap } = wrap;
|
|
10
|
+
const { THEME, truncateVisible } = ansi;
|
|
11
11
|
const { paintBar, paintCheckBar, windowRows } = primitives;
|
|
12
12
|
const { LIST_PANES, CHECK_LEAD, paneResult } = primitives;
|
|
13
13
|
|
|
@@ -53,11 +53,35 @@ const todoCursor = (text, edit, inner, rowStart) => {
|
|
|
53
53
|
return { x: pos.col + lead + 2, row: rowStart + pos.row };
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
+
const taskEditHits = (text, inner, rowStart, width) => {
|
|
57
|
+
const lead = checkLeadLen(text);
|
|
58
|
+
const body = text.slice(lead);
|
|
59
|
+
const bodyWidth = Math.max(1, inner - lead);
|
|
60
|
+
const doc = wrapDoc(body, bodyWidth);
|
|
61
|
+
const textX = lead + 2;
|
|
62
|
+
const hits = [];
|
|
63
|
+
for (let line = 0; line < doc.length; line++) {
|
|
64
|
+
const piece = doc[line];
|
|
65
|
+
hits.push({
|
|
66
|
+
row: rowStart + line,
|
|
67
|
+
x0: lead + 1,
|
|
68
|
+
textX,
|
|
69
|
+
x1: width + 1,
|
|
70
|
+
start: piece.start,
|
|
71
|
+
text: piece.text,
|
|
72
|
+
limit: piece.start + piece.text.length,
|
|
73
|
+
live: true,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return hits;
|
|
77
|
+
};
|
|
78
|
+
|
|
56
79
|
const paintTodoList = (texts, focus, width, color, edit) => {
|
|
57
80
|
const inner = todoInnerWidth(width);
|
|
58
81
|
const rows = [];
|
|
59
82
|
const owners = [];
|
|
60
83
|
const checks = [];
|
|
84
|
+
const editHits = [];
|
|
61
85
|
let cursor = null;
|
|
62
86
|
for (let i = 0; i < texts.length; i++) {
|
|
63
87
|
const selected = i === focus;
|
|
@@ -73,6 +97,7 @@ const paintTodoList = (texts, focus, width, color, edit) => {
|
|
|
73
97
|
fgRgb,
|
|
74
98
|
bgRgb,
|
|
75
99
|
color,
|
|
100
|
+
true,
|
|
76
101
|
);
|
|
77
102
|
rows.push(painted);
|
|
78
103
|
owners.push(i);
|
|
@@ -80,8 +105,10 @@ const paintTodoList = (texts, focus, width, color, edit) => {
|
|
|
80
105
|
}
|
|
81
106
|
if (!edit || !selected) continue;
|
|
82
107
|
cursor = todoCursor(text, edit, inner, rowStart);
|
|
108
|
+
const hits = taskEditHits(text, inner, rowStart, width);
|
|
109
|
+
for (const hit of hits) editHits.push(hit);
|
|
83
110
|
}
|
|
84
|
-
return { rows, owners, checks, cursor };
|
|
111
|
+
return { rows, owners, checks, cursor, editHits };
|
|
85
112
|
};
|
|
86
113
|
|
|
87
114
|
const noteInnerWidth = (width) => Math.max(1, width - NOTE_PAD * 2);
|
|
@@ -100,11 +127,23 @@ const paintTemplateRow = (text, selected, width, color) => {
|
|
|
100
127
|
return paintNoteLine(text, width, fgRgb, bgRgb, color);
|
|
101
128
|
};
|
|
102
129
|
|
|
130
|
+
const noteEditHit = (piece, row, width) => ({
|
|
131
|
+
row,
|
|
132
|
+
x0: 1,
|
|
133
|
+
textX: NOTE_PAD + 1,
|
|
134
|
+
x1: width + 1,
|
|
135
|
+
start: piece.start,
|
|
136
|
+
text: piece.text,
|
|
137
|
+
limit: piece.start + piece.text.length,
|
|
138
|
+
live: true,
|
|
139
|
+
});
|
|
140
|
+
|
|
103
141
|
const paintComposeNote = (view, width, color, cap) => {
|
|
104
142
|
const inner = noteInnerWidth(width);
|
|
105
143
|
const bgRgb = THEME.noteBg;
|
|
106
144
|
const compose = view.compose;
|
|
107
|
-
const
|
|
145
|
+
const doc = wrapDoc(compose.text ?? '', inner);
|
|
146
|
+
const wrapped = doc.map((piece) => piece.text);
|
|
108
147
|
const { row, col } = cursorInWrap(compose.text, compose.cursor ?? 0, inner);
|
|
109
148
|
const shown = windowRows(wrapped, row, INPUT_MAX);
|
|
110
149
|
const rows = [];
|
|
@@ -123,8 +162,19 @@ const paintComposeNote = (view, width, color, cap) => {
|
|
|
123
162
|
}
|
|
124
163
|
}
|
|
125
164
|
const templateCount = rows.length;
|
|
126
|
-
|
|
127
|
-
|
|
165
|
+
const editHits = [];
|
|
166
|
+
for (let i = 0; i < shown.rows.length; i++) {
|
|
167
|
+
const piece = doc[shown.offset + i];
|
|
168
|
+
const painted = paintNoteLine(
|
|
169
|
+
shown.rows[i],
|
|
170
|
+
width,
|
|
171
|
+
THEME.chromeFg,
|
|
172
|
+
bgRgb,
|
|
173
|
+
color,
|
|
174
|
+
);
|
|
175
|
+
rows.push(painted);
|
|
176
|
+
if (!piece) continue;
|
|
177
|
+
editHits.push(noteEditHit(piece, templateCount + i, width));
|
|
128
178
|
}
|
|
129
179
|
return {
|
|
130
180
|
rows,
|
|
@@ -133,11 +183,12 @@ const paintComposeNote = (view, width, color, cap) => {
|
|
|
133
183
|
row: templateCount + row - shown.offset,
|
|
134
184
|
},
|
|
135
185
|
templateHits,
|
|
186
|
+
editHits,
|
|
136
187
|
};
|
|
137
188
|
};
|
|
138
189
|
|
|
139
190
|
const paintIdleNote = (view, width, color) => {
|
|
140
|
-
const empty = { rows: [], cursor: null, templateHits: [] };
|
|
191
|
+
const empty = { rows: [], cursor: null, templateHits: [], editHits: [] };
|
|
141
192
|
const text = view.noteText;
|
|
142
193
|
if (!text) return empty;
|
|
143
194
|
const inner = noteInnerWidth(width);
|
|
@@ -158,7 +209,7 @@ const paintIdleNote = (view, width, color) => {
|
|
|
158
209
|
};
|
|
159
210
|
|
|
160
211
|
const paintNotePanel = (view, width, color, cap) => {
|
|
161
|
-
const empty = { rows: [], cursor: null, templateHits: [] };
|
|
212
|
+
const empty = { rows: [], cursor: null, templateHits: [], editHits: [] };
|
|
162
213
|
const compose = view.compose;
|
|
163
214
|
if (compose && NOTE_COMPOSE.includes(compose.kind)) {
|
|
164
215
|
return paintComposeNote(view, width, color, cap);
|
|
@@ -168,43 +219,40 @@ const paintNotePanel = (view, width, color, cap) => {
|
|
|
168
219
|
return paintIdleNote(view, width, color);
|
|
169
220
|
};
|
|
170
221
|
|
|
171
|
-
const paintTodoCaption = (width, color) =>
|
|
172
|
-
|
|
173
|
-
const clipped = truncateVisible(text, width);
|
|
174
|
-
const pad = Math.max(0, width - visibleWidth(clipped));
|
|
175
|
-
const filled = clipped + ' '.repeat(pad);
|
|
176
|
-
if (!color) return filled;
|
|
177
|
-
const fgRgb = THEME.buttonHotFg;
|
|
178
|
-
const bgRgb = THEME.ctxBg;
|
|
179
|
-
return `${BOLD}${seq(fgRgb, bgRgb)}${EL}${filled}${RESET}`;
|
|
180
|
-
};
|
|
222
|
+
const paintTodoCaption = (width, color) =>
|
|
223
|
+
paintBar(` ${REPO_TASKS_LABEL}`, width, THEME.mutedFg, THEME.ctxBg, color);
|
|
181
224
|
|
|
182
225
|
const paintBodyTodo = (view, width, color) => {
|
|
183
|
-
const texts = view.
|
|
226
|
+
const texts = view.tasks ?? [];
|
|
184
227
|
let lines = texts;
|
|
185
228
|
if (!lines.length && view.noteText) lines = [view.noteText];
|
|
186
229
|
const painted = paintTodoList(
|
|
187
230
|
lines,
|
|
188
|
-
view.
|
|
231
|
+
view.tasksFocus ?? 0,
|
|
189
232
|
width,
|
|
190
233
|
color,
|
|
191
|
-
view.
|
|
234
|
+
view.taskEdit,
|
|
192
235
|
);
|
|
193
236
|
const caption = paintTodoCaption(width, color);
|
|
194
237
|
const body = [caption, ...painted.rows];
|
|
195
|
-
const
|
|
196
|
-
const
|
|
238
|
+
const taskOwners = [null, ...painted.owners];
|
|
239
|
+
const taskChecks = [false, ...painted.checks];
|
|
197
240
|
let cursor = null;
|
|
198
241
|
if (painted.cursor) {
|
|
199
242
|
cursor = { x: painted.cursor.x, row: painted.cursor.row + 1 };
|
|
200
243
|
}
|
|
244
|
+
const editHits = [];
|
|
245
|
+
for (const hit of painted.editHits) {
|
|
246
|
+
editHits.push({ ...hit, row: hit.row + 1 });
|
|
247
|
+
}
|
|
201
248
|
const fileHits = [];
|
|
202
249
|
const splitBody = false;
|
|
203
250
|
return paneResult({
|
|
204
251
|
body,
|
|
205
252
|
fileHits,
|
|
206
|
-
|
|
207
|
-
|
|
253
|
+
editHits,
|
|
254
|
+
taskOwners,
|
|
255
|
+
taskChecks,
|
|
208
256
|
cursor,
|
|
209
257
|
splitBody,
|
|
210
258
|
});
|
package/lib/render/npm.js
CHANGED
|
@@ -10,7 +10,7 @@ const { clipAnsi, stripAnsi, RESET, foregroundOn } = ansi;
|
|
|
10
10
|
const { formatBusyStatus } = chrome;
|
|
11
11
|
const { TABLE_KEY, TABLE_VAL } = npmCommands;
|
|
12
12
|
const { fill, FILE_MARK, paneResult, paintCursorList } = primitives;
|
|
13
|
-
const {
|
|
13
|
+
const { fieldWindow } = primitives;
|
|
14
14
|
|
|
15
15
|
const EDGE = 2;
|
|
16
16
|
const TAIL = 1;
|
|
@@ -64,39 +64,117 @@ const paintNpmRow = (entry, width, color, selected, nameCol = 0) => {
|
|
|
64
64
|
return out + edge;
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
+
const editNameSource = (view, compose, field) => {
|
|
68
|
+
if (field === 'name') return compose.text;
|
|
69
|
+
return view.npmDraftName ?? '';
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const editNameWidth = (commands, live) => {
|
|
73
|
+
const typed = visibleWidth(`${live ?? ''}`);
|
|
74
|
+
return Math.min(NPM_NAME_MAX, Math.max(npmNameWidth(commands), typed));
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const paintNpmEdit = (view, width, color, nameW) => {
|
|
78
|
+
const compose = view.compose;
|
|
79
|
+
const field = view.npmEditField === 'command' ? 'command' : 'name';
|
|
80
|
+
const nameSource = editNameSource(view, compose, field);
|
|
81
|
+
const draft = view.npmDraftCommand ?? '';
|
|
82
|
+
const commandSource = field === 'command' ? compose.text : draft;
|
|
83
|
+
const inner = Math.max(1, width - EDGE - TAIL);
|
|
84
|
+
const prefix = ` ${FILE_MARK} `;
|
|
85
|
+
const prefixW = visibleWidth(prefix);
|
|
86
|
+
const rest = Math.max(1, inner - prefixW);
|
|
87
|
+
const colW = Math.min(Math.max(0, nameW), rest);
|
|
88
|
+
const flatName = `${nameSource ?? ''}`.replaceAll('\n', ' ');
|
|
89
|
+
const nameAt = field === 'name' ? compose.cursor : 0;
|
|
90
|
+
const nameScroll = field === 'name' ? compose.scrollCol : 0;
|
|
91
|
+
const nameWin = fieldWindow(flatName, nameAt, nameScroll, colW);
|
|
92
|
+
const name = nameWin.text;
|
|
93
|
+
const gap = rest > colW + 2 ? ' ' : '';
|
|
94
|
+
const gapW = visibleWidth(gap);
|
|
95
|
+
const detailW = Math.max(0, rest - colW - gapW);
|
|
96
|
+
const flatCommand = `${commandSource ?? ''}`.replaceAll('\n', ' ');
|
|
97
|
+
const commandAt = field === 'command' ? compose.cursor : 0;
|
|
98
|
+
const commandScroll = field === 'command' ? compose.scrollCol : 0;
|
|
99
|
+
const commandWin = fieldWindow(
|
|
100
|
+
flatCommand,
|
|
101
|
+
commandAt,
|
|
102
|
+
commandScroll,
|
|
103
|
+
detailW,
|
|
104
|
+
);
|
|
105
|
+
const command = commandWin.text;
|
|
106
|
+
const namePad = ' '.repeat(Math.max(0, colW - visibleWidth(name)));
|
|
107
|
+
const mid = `${name}${namePad}`;
|
|
108
|
+
const after = `${gap}${command}`;
|
|
109
|
+
const pad = Math.max(0, rest - visibleWidth(mid + after));
|
|
110
|
+
const tail = ' '.repeat(TAIL);
|
|
111
|
+
const left = `${prefix}${mid}${after}${' '.repeat(pad)}`;
|
|
112
|
+
const fgRgb = THEME.chromeFg;
|
|
113
|
+
const bgRgb = THEME.buttonBg;
|
|
114
|
+
const edge = fill(EDGE, fgRgb, THEME.ctxBg, color);
|
|
115
|
+
let row = `${left}${tail}${edge}`;
|
|
116
|
+
if (color) {
|
|
117
|
+
row = `${seq(fgRgb, bgRgb)}${EL}`;
|
|
118
|
+
row += paint(prefix, fgRgb, bgRgb, color);
|
|
119
|
+
row += paint(mid, THEME.buttonHotFg, bgRgb, color, true);
|
|
120
|
+
row += paint(`${after}${' '.repeat(pad)}${tail}`, fgRgb, bgRgb, color);
|
|
121
|
+
row += edge;
|
|
122
|
+
}
|
|
123
|
+
const active = field === 'name' ? nameWin : commandWin;
|
|
124
|
+
const lead = field === 'name' ? 0 : colW + gapW;
|
|
125
|
+
const nameX = prefixW + 1;
|
|
126
|
+
const commandX = prefixW + colW + gapW + 1;
|
|
127
|
+
const edits = [
|
|
128
|
+
{
|
|
129
|
+
field: 'name',
|
|
130
|
+
x0: 1,
|
|
131
|
+
textX: nameX,
|
|
132
|
+
x1: commandX,
|
|
133
|
+
start: 0,
|
|
134
|
+
text: flatName,
|
|
135
|
+
limit: flatName.length,
|
|
136
|
+
scroll: nameWin.scroll,
|
|
137
|
+
live: field === 'name',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
field: 'command',
|
|
141
|
+
x0: commandX,
|
|
142
|
+
textX: commandX,
|
|
143
|
+
x1: width + 1,
|
|
144
|
+
start: 0,
|
|
145
|
+
text: flatCommand,
|
|
146
|
+
limit: flatCommand.length,
|
|
147
|
+
scroll: commandWin.scroll,
|
|
148
|
+
live: field === 'command',
|
|
149
|
+
},
|
|
150
|
+
];
|
|
151
|
+
const cursor = {
|
|
152
|
+
x: prefixW + lead + active.col + 1,
|
|
153
|
+
scroll: active.scroll,
|
|
154
|
+
};
|
|
155
|
+
return { row, cursor, edits };
|
|
156
|
+
};
|
|
157
|
+
|
|
67
158
|
const paintNpmList = (view, width, color, bodyH, headerLines) => {
|
|
68
159
|
const commands = view.npmCommands ?? [];
|
|
69
|
-
const nameW = npmNameWidth(commands);
|
|
70
|
-
const cursor = view.npmCursor ?? 0;
|
|
71
160
|
const compose = view.compose;
|
|
72
161
|
const editing = compose && compose.kind === 'npm';
|
|
73
|
-
const
|
|
74
|
-
|
|
162
|
+
const field = view.npmEditField === 'command' ? 'command' : 'name';
|
|
163
|
+
const live = editing ? editNameSource(view, compose, field) : '';
|
|
164
|
+
const nameW = editing
|
|
165
|
+
? editNameWidth(commands, live)
|
|
166
|
+
: npmNameWidth(commands);
|
|
167
|
+
const cursor = view.npmCursor ?? 0;
|
|
168
|
+
const pane = { width, color, bodyH, headerLines, offset: view.listScroll };
|
|
169
|
+
const paintRow = (entry, selected) =>
|
|
170
|
+
paintNpmRow(entry, width, color, selected, nameW);
|
|
75
171
|
let extra = null;
|
|
76
172
|
if (editing) {
|
|
77
|
-
const painted =
|
|
173
|
+
const painted = paintNpmEdit(view, width, color, nameW);
|
|
78
174
|
const at = view.npmEditName ? 'replace' : 'end';
|
|
79
|
-
extra = {
|
|
175
|
+
extra = { ...painted, at, index: cursor };
|
|
80
176
|
}
|
|
81
|
-
|
|
82
|
-
commands,
|
|
83
|
-
cursor,
|
|
84
|
-
width,
|
|
85
|
-
color,
|
|
86
|
-
bodyH,
|
|
87
|
-
headerLines,
|
|
88
|
-
paintRow,
|
|
89
|
-
extra,
|
|
90
|
-
view.listScroll,
|
|
91
|
-
);
|
|
92
|
-
return paneResult({
|
|
93
|
-
body: painted.body,
|
|
94
|
-
fileHits: painted.fileHits,
|
|
95
|
-
todoOwners: [],
|
|
96
|
-
cursor: painted.cursor,
|
|
97
|
-
splitBody: false,
|
|
98
|
-
listScroll: painted.offset,
|
|
99
|
-
});
|
|
177
|
+
return paintCursorList(commands, cursor, pane, paintRow, extra);
|
|
100
178
|
};
|
|
101
179
|
|
|
102
180
|
const logStart = (view, count, rows) => {
|
|
@@ -213,10 +291,6 @@ const paintNpmLog = (view, width, color, bodyH) => {
|
|
|
213
291
|
for (let i = 0; i < padY - top; i++) body.push(blank);
|
|
214
292
|
return paneResult({
|
|
215
293
|
body,
|
|
216
|
-
fileHits: [],
|
|
217
|
-
todoOwners: [],
|
|
218
|
-
cursor: null,
|
|
219
|
-
splitBody: false,
|
|
220
294
|
});
|
|
221
295
|
};
|
|
222
296
|
|
package/lib/render/present.js
CHANGED
package/lib/render/primitives.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const ansi = require('../ansi.js');
|
|
4
|
+
const actions = require('../session/actions.js');
|
|
5
|
+
const { LIST_PANES } = actions;
|
|
4
6
|
|
|
5
7
|
const { THEME, paint, visibleWidth, truncateVisible, seq, EL, RESET } = ansi;
|
|
8
|
+
const { graphemes, graphemeWidth } = ansi;
|
|
6
9
|
|
|
7
10
|
const GUTTER_W = 2;
|
|
8
11
|
const FILE_MARK = '▶';
|
|
9
|
-
const
|
|
12
|
+
const LIST_PAD = 1;
|
|
10
13
|
const CHECK_DONE = 'xX';
|
|
11
|
-
const LIST_PANES = ['files', 'branches', 'commits', 'npm'];
|
|
12
14
|
const CHECK_TOKEN = /\[([ xX])\]/;
|
|
13
15
|
const CHECK_LEAD = /^\[[ xX]\] /;
|
|
14
16
|
|
|
@@ -34,14 +36,21 @@ const splitCheck = (text) => {
|
|
|
34
36
|
return { before, mark, token, after };
|
|
35
37
|
};
|
|
36
38
|
|
|
37
|
-
const paintCheckBar = (text, width, fgRgb, bgRgb, color) => {
|
|
39
|
+
const paintCheckBar = (text, width, fgRgb, bgRgb, color, plain = false) => {
|
|
38
40
|
const clipped = truncateVisible(text, width);
|
|
39
41
|
const parts = color ? splitCheck(clipped) : null;
|
|
40
42
|
if (!parts) return paintBar(text, width, fgRgb, bgRgb, color);
|
|
41
43
|
const pad = Math.max(0, width - visibleWidth(clipped));
|
|
42
44
|
const done = CHECK_DONE.includes(parts.mark);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
let chipFg = THEME.checkFg;
|
|
46
|
+
let chipBg = THEME.checkBg;
|
|
47
|
+
if (plain) {
|
|
48
|
+
chipFg = fgRgb;
|
|
49
|
+
chipBg = bgRgb;
|
|
50
|
+
} else if (done) {
|
|
51
|
+
chipFg = THEME.checkDoneFg;
|
|
52
|
+
chipBg = THEME.checkDoneBg;
|
|
53
|
+
}
|
|
45
54
|
const after = `${parts.after}${' '.repeat(pad)}`;
|
|
46
55
|
let out = `${seq(fgRgb, bgRgb)}${EL}`;
|
|
47
56
|
if (parts.before) out += paint(parts.before, fgRgb, bgRgb, color);
|
|
@@ -111,49 +120,110 @@ const extraIdx = (extraRow, cursor, count) => {
|
|
|
111
120
|
return count;
|
|
112
121
|
};
|
|
113
122
|
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
const paneResult = (fields = {}) => ({
|
|
124
|
+
body: fields.body ?? [],
|
|
125
|
+
fileHits: fields.fileHits ?? [],
|
|
126
|
+
taskOwners: fields.taskOwners ?? [],
|
|
127
|
+
taskChecks: fields.taskChecks ?? [],
|
|
128
|
+
cursor: fields.cursor ?? null,
|
|
129
|
+
splitBody: fields.splitBody === true,
|
|
130
|
+
listScroll: fields.listScroll ?? 0,
|
|
131
|
+
editHits: fields.editHits ?? [],
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const NO_EDITS = [];
|
|
135
|
+
|
|
136
|
+
const listLine = (row, index, cursor = null, edits = NO_EDITS) => {
|
|
137
|
+
const hit = index >= 0;
|
|
138
|
+
return { row, index, hit, cursor, edits };
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const rowCursor = (cursor, row) => {
|
|
142
|
+
const out = { x: cursor.x, row };
|
|
143
|
+
if (typeof cursor.scroll === 'number') out.scroll = cursor.scroll;
|
|
144
|
+
return out;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const paintListWindow = (count, focus, pane, lineAt) => {
|
|
148
|
+
const { width, color, bodyH, headerLines, offset } = pane;
|
|
149
|
+
const pad = bodyH >= LIST_PAD * 2 + 1 ? LIST_PAD : 0;
|
|
126
150
|
const listH = bodyH - pad * 2;
|
|
127
|
-
const inserting = extraRow && extraRow.at === 'start';
|
|
128
|
-
const extra = extraRow && extraRow.at !== 'replace' ? 1 : 0;
|
|
129
|
-
const count = items.length + extra;
|
|
130
|
-
const extraAt = extraIdx(extraRow, cursor, items.length);
|
|
131
|
-
const focus = extraRow ? extraAt : cursor;
|
|
132
151
|
const start = listWindowStart(focus, offset, listH, count);
|
|
133
152
|
const blank = paintBodyFill(width, color, 'files');
|
|
134
153
|
const body = [];
|
|
135
154
|
const fileHits = [];
|
|
136
|
-
|
|
155
|
+
const editHits = [];
|
|
156
|
+
let cursor = null;
|
|
137
157
|
if (pad) body.push(blank);
|
|
138
158
|
for (let i = 0; i < listH; i++) {
|
|
139
|
-
const
|
|
140
|
-
if (
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
159
|
+
const line = lineAt(start + i);
|
|
160
|
+
if (!line) break;
|
|
161
|
+
const row = body.push(line.row) - 1;
|
|
162
|
+
if (line.cursor) cursor = rowCursor(line.cursor, row);
|
|
163
|
+
for (const edit of line.edits) editHits.push({ ...edit, row });
|
|
164
|
+
if (line.hit) {
|
|
165
|
+
fileHits.push({ y: headerLines + pad + i + 1, cursor: line.index });
|
|
146
166
|
}
|
|
147
|
-
const itemIdx = inserting ? idx - 1 : idx;
|
|
148
|
-
const entry = items[itemIdx];
|
|
149
|
-
if (!entry) break;
|
|
150
|
-
const selected = extraRow ? false : itemIdx === cursor;
|
|
151
|
-
body.push(paintRow(entry, width, color, selected));
|
|
152
|
-
const y = headerLines + pad + i + 1;
|
|
153
|
-
fileHits.push({ y, cursor: itemIdx });
|
|
154
167
|
}
|
|
155
168
|
if (pad) body.push(blank);
|
|
156
|
-
return { body, fileHits, cursor
|
|
169
|
+
return paneResult({ body, fileHits, editHits, cursor, listScroll: start });
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const editLine = (extraRow) => {
|
|
173
|
+
const { row, cursor, edit, edits = [] } = extraRow;
|
|
174
|
+
return listLine(row, -1, cursor, edit ? [...edits, edit] : edits);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const paintCursorList = (items, cursor, pane, paintRow, extraRow = null) => {
|
|
178
|
+
const edit = extraRow ? editLine(extraRow) : null;
|
|
179
|
+
const inserting = edit !== null && extraRow.at === 'start';
|
|
180
|
+
const extra = edit && extraRow.at !== 'replace' ? 1 : 0;
|
|
181
|
+
const extraAt = extraIdx(extraRow, cursor, items.length);
|
|
182
|
+
const lineAt = (idx) => {
|
|
183
|
+
if (edit && idx === extraAt) return edit;
|
|
184
|
+
const index = inserting ? idx - 1 : idx;
|
|
185
|
+
const entry = items[index];
|
|
186
|
+
if (!entry) return null;
|
|
187
|
+
return listLine(paintRow(entry, !edit && index === cursor), index);
|
|
188
|
+
};
|
|
189
|
+
const focus = edit ? extraAt : cursor;
|
|
190
|
+
return paintListWindow(items.length + extra, focus, pane, lineAt);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const sliceVisible = (text, skip, width) => {
|
|
194
|
+
if (width <= 0) return '';
|
|
195
|
+
const parts = graphemes(`${text ?? ''}`);
|
|
196
|
+
let used = 0;
|
|
197
|
+
let i = 0;
|
|
198
|
+
const drop = Math.max(0, skip);
|
|
199
|
+
while (i < parts.length) {
|
|
200
|
+
const w = graphemeWidth(parts[i], used);
|
|
201
|
+
if (used + w > drop) break;
|
|
202
|
+
used += w;
|
|
203
|
+
i += 1;
|
|
204
|
+
}
|
|
205
|
+
let out = '';
|
|
206
|
+
let taken = 0;
|
|
207
|
+
for (; i < parts.length; i++) {
|
|
208
|
+
const w = graphemeWidth(parts[i], drop + taken);
|
|
209
|
+
if (taken + w > width) break;
|
|
210
|
+
out += parts[i];
|
|
211
|
+
taken += w;
|
|
212
|
+
}
|
|
213
|
+
return out;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const fieldWindow = (text, cursor, scroll, width) => {
|
|
217
|
+
const raw = `${text ?? ''}`;
|
|
218
|
+
if (width <= 0) return { text: '', col: 0, scroll: 0 };
|
|
219
|
+
const at = Math.max(0, Math.min(cursor ?? 0, raw.length));
|
|
220
|
+
const col = visibleWidth(raw.slice(0, at));
|
|
221
|
+
let next = scroll ?? 0;
|
|
222
|
+
if (next < 0) next = 0;
|
|
223
|
+
if (col < next) next = col;
|
|
224
|
+
if (col >= next + width) next = col - width + 1;
|
|
225
|
+
const shown = sliceVisible(raw, next, width);
|
|
226
|
+
return { text: shown, col: col - next, scroll: next };
|
|
157
227
|
};
|
|
158
228
|
|
|
159
229
|
const branchLabel = (name) => `${name ?? ''}`.trim();
|
|
@@ -164,7 +234,8 @@ const paintListEdit = (compose, width, color, edgeW = 0) => {
|
|
|
164
234
|
const prefixW = visibleWidth(prefix);
|
|
165
235
|
const rest = Math.max(1, inner - prefixW);
|
|
166
236
|
const text = `${compose.text ?? ''}`.replaceAll('\n', ' ');
|
|
167
|
-
const
|
|
237
|
+
const win = fieldWindow(text, compose.cursor, compose.scrollCol, rest);
|
|
238
|
+
const clipped = win.text;
|
|
168
239
|
const pad = Math.max(0, rest - visibleWidth(clipped));
|
|
169
240
|
const left = `${prefix}${clipped}${' '.repeat(pad)}`;
|
|
170
241
|
const fgRgb = THEME.chromeFg;
|
|
@@ -179,20 +250,23 @@ const paintListEdit = (compose, width, color, edgeW = 0) => {
|
|
|
179
250
|
row += paint(gap, fgRgb, bgRgb, color);
|
|
180
251
|
row += edge;
|
|
181
252
|
}
|
|
182
|
-
const
|
|
183
|
-
return {
|
|
253
|
+
const flat = text;
|
|
254
|
+
return {
|
|
255
|
+
row,
|
|
256
|
+
cursor: { x: prefixW + win.col + 1, scroll: win.scroll },
|
|
257
|
+
edit: {
|
|
258
|
+
x0: 1,
|
|
259
|
+
textX: prefixW + 1,
|
|
260
|
+
x1: prefixW + 1 + rest,
|
|
261
|
+
start: 0,
|
|
262
|
+
text: flat,
|
|
263
|
+
limit: flat.length,
|
|
264
|
+
scroll: win.scroll,
|
|
265
|
+
live: true,
|
|
266
|
+
},
|
|
267
|
+
};
|
|
184
268
|
};
|
|
185
269
|
|
|
186
|
-
const paneResult = (fields) => ({
|
|
187
|
-
body: fields.body,
|
|
188
|
-
fileHits: fields.fileHits,
|
|
189
|
-
todoOwners: fields.todoOwners,
|
|
190
|
-
todoChecks: fields.todoChecks ?? [],
|
|
191
|
-
cursor: fields.cursor ?? null,
|
|
192
|
-
splitBody: fields.splitBody === true,
|
|
193
|
-
listScroll: fields.listScroll ?? 0,
|
|
194
|
-
});
|
|
195
|
-
|
|
196
270
|
const measureColumns = (entries, rowFields, align) => {
|
|
197
271
|
const keys = Object.keys(align);
|
|
198
272
|
const widths = Object.fromEntries(keys.map((key) => [key, 0]));
|
|
@@ -251,9 +325,11 @@ module.exports = {
|
|
|
251
325
|
paintSplitRule,
|
|
252
326
|
paintBodyFill,
|
|
253
327
|
windowRows,
|
|
254
|
-
|
|
328
|
+
listLine,
|
|
329
|
+
paintListWindow,
|
|
255
330
|
paintCursorList,
|
|
256
331
|
paintListEdit,
|
|
332
|
+
fieldWindow,
|
|
257
333
|
branchLabel,
|
|
258
334
|
paneResult,
|
|
259
335
|
};
|
package/lib/render/render.js
CHANGED
|
@@ -4,7 +4,8 @@ const chrome = require('./chrome.js');
|
|
|
4
4
|
const notes = require('./notes.js');
|
|
5
5
|
const diff = require('./diff.js');
|
|
6
6
|
const present = require('./present.js');
|
|
7
|
-
const
|
|
7
|
+
const frame = require('./frame.js');
|
|
8
|
+
const { renderFrame } = frame;
|
|
8
9
|
|
|
9
10
|
const { QUIT_PROMPT, PUSH_PROMPT } = chrome;
|
|
10
11
|
const { updatePrompt, dropPrompt } = chrome;
|
package/lib/render/unit.js
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const detect = require('../detect.js');
|
|
4
|
+
const { detectLang } = detect;
|
|
4
5
|
const primitives = require('./primitives.js');
|
|
5
6
|
const diff = require('./diff.js');
|
|
6
7
|
|
|
7
8
|
const { paneResult } = primitives;
|
|
8
|
-
const { paintDiffLine, codeCursorInRows } = diff;
|
|
9
|
-
const {
|
|
9
|
+
const { paintDiffLine, codeCursorInRows, codeEditHits } = diff;
|
|
10
|
+
const { expandSoftRows } = diff;
|
|
11
|
+
const diffLib = require('../diff/diff.js');
|
|
12
|
+
const { sameHunk } = diffLib;
|
|
10
13
|
|
|
11
14
|
const paintBodyUnit = (view, width, color) => {
|
|
12
15
|
const lines = view.unitLines ?? [];
|
|
13
16
|
const item = view.item;
|
|
14
17
|
const lang = detectLang(view.reviewPath ?? '');
|
|
15
18
|
const overlay = view.codeOverlay;
|
|
16
|
-
const editing =
|
|
17
|
-
|
|
19
|
+
const editing =
|
|
20
|
+
view.mode === 'compose' && overlay && typeof overlay.cursor === 'number';
|
|
21
|
+
const picked = editing ? overlay.scrollCol : 0;
|
|
22
|
+
const scrollCol = picked ?? 0;
|
|
23
|
+
const source = editing ? lines : expandSoftRows(lines, width, false, true);
|
|
24
|
+
const { length } = source;
|
|
18
25
|
const body = new Array(length);
|
|
19
26
|
for (let i = 0; i < length; i++) {
|
|
20
|
-
const line =
|
|
27
|
+
const line = source[i];
|
|
21
28
|
const isBlock = item && sameHunk(line.item, item);
|
|
22
29
|
const row = { ...line };
|
|
23
30
|
if (editing) {
|
|
@@ -25,16 +32,15 @@ const paintBodyUnit = (view, width, color) => {
|
|
|
25
32
|
} else if ((row.type === 'add' || row.type === 'del') && !isBlock) {
|
|
26
33
|
row.mark = ' ';
|
|
27
34
|
}
|
|
28
|
-
body[i] = paintDiffLine(row, width, color, row.origin, lang);
|
|
35
|
+
body[i] = paintDiffLine(row, width, color, row.origin, lang, scrollCol);
|
|
29
36
|
}
|
|
30
37
|
const edit = overlay ? overlay.cursor : null;
|
|
31
|
-
const cursor = codeCursorInRows(
|
|
38
|
+
const cursor = codeCursorInRows(source, false, width, edit, scrollCol);
|
|
39
|
+
const editHits = editing ? codeEditHits(source, false, width, scrollCol) : [];
|
|
32
40
|
return paneResult({
|
|
33
41
|
body,
|
|
34
|
-
fileHits: [],
|
|
35
|
-
todoOwners: [],
|
|
36
42
|
cursor,
|
|
37
|
-
|
|
43
|
+
editHits,
|
|
38
44
|
});
|
|
39
45
|
};
|
|
40
46
|
|