reslop 0.2.4 → 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.
Files changed (67) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/README.md +1 -2
  3. package/lib/capabilities.js +0 -4
  4. package/lib/clipboard.js +39 -1
  5. package/lib/deps-diff.js +0 -3
  6. package/lib/deps.js +144 -176
  7. package/lib/diff/inline.js +66 -1
  8. package/lib/diff/unit.js +11 -28
  9. package/lib/editor.js +69 -9
  10. package/lib/files.js +17 -16
  11. package/lib/git-deps.js +58 -95
  12. package/lib/github.js +7 -7
  13. package/lib/gitlab.js +3 -3
  14. package/lib/highlight/core.js +11 -0
  15. package/lib/highlight/dot.js +2 -12
  16. package/lib/highlight/log.js +4 -11
  17. package/lib/highlight/txt.js +2 -12
  18. package/lib/keys.js +22 -12
  19. package/lib/manifest.js +0 -4
  20. package/lib/npm-commands.js +1 -22
  21. package/lib/remote.js +1 -7
  22. package/lib/render/branches.js +4 -13
  23. package/lib/render/chrome.js +85 -31
  24. package/lib/render/commits.js +95 -207
  25. package/lib/render/diff.js +31 -1
  26. package/lib/render/files.js +7 -16
  27. package/lib/render/frame.js +95 -110
  28. package/lib/render/notes.js +71 -16
  29. package/lib/render/npm.js +104 -18
  30. package/lib/render/primitives.js +108 -45
  31. package/lib/render/unit.js +4 -1
  32. package/lib/report-parse.js +0 -1
  33. package/lib/report-run.js +0 -1
  34. package/lib/report-test.js +40 -47
  35. package/lib/report-tools.js +0 -7
  36. package/lib/review-merge.js +101 -131
  37. package/lib/review.js +64 -77
  38. package/lib/select.js +52 -0
  39. package/lib/session/actions.js +23 -22
  40. package/lib/session/branches.js +5 -3
  41. package/lib/session/change-actions.js +173 -146
  42. package/lib/session/commit.js +32 -13
  43. package/lib/session/compose.js +120 -77
  44. package/lib/session/dispatch.js +36 -21
  45. package/lib/session/edit-click.js +57 -0
  46. package/lib/session/files-pane.js +24 -24
  47. package/lib/session/items.js +69 -89
  48. package/lib/session/lifecycle.js +31 -96
  49. package/lib/session/load.js +51 -58
  50. package/lib/session/navigation.js +105 -96
  51. package/lib/session/npm.js +158 -77
  52. package/lib/session/ops.js +192 -51
  53. package/lib/session/pointer.js +11 -5
  54. package/lib/session/progress.js +9 -6
  55. package/lib/session/review.js +35 -54
  56. package/lib/session/tasks.js +292 -0
  57. package/lib/session/terminal.js +13 -17
  58. package/lib/session/unit.js +3 -4
  59. package/lib/session/update.js +76 -97
  60. package/lib/session/view.js +17 -14
  61. package/lib/session/watch.js +47 -27
  62. package/lib/session.js +34 -79
  63. package/lib/update.js +1 -5
  64. package/lib/utilities.js +21 -1
  65. package/lib/wrap.js +15 -0
  66. package/package.json +1 -1
  67. package/lib/session/todos.js +0 -293
@@ -2,7 +2,7 @@
2
2
 
3
3
  const ansi = require('../ansi.js');
4
4
  const select = require('../select.js');
5
- const { overlayRows } = select;
5
+ const { overlayRows, markEditorSelection } = select;
6
6
  const primitives = require('./primitives.js');
7
7
  const chrome = require('./chrome.js');
8
8
  const files = require('./files.js');
@@ -16,8 +16,7 @@ const unit = require('./unit.js');
16
16
  const { RESET } = ansi;
17
17
  const { LIST_PANES, paintBodyFill, paneResult } = primitives;
18
18
  const { paintHeader, paintFooter, footerHits, paintStatusLine } = chrome;
19
- const { layoutButtons, statusBranchHit, statusChoiceHits } = chrome;
20
- const { hiddenActions, dimFooterActions, extraFooterActions } = chrome;
19
+ const { footerLayout, statusBranchHit, statusChoiceHits } = chrome;
21
20
  const { paintNotePanel, paintBodyTodo } = notes;
22
21
  const { paintBodyFiles } = files;
23
22
  const { paintBodyBranches } = branches;
@@ -34,7 +33,7 @@ const bodyKind = (view) => {
34
33
  if (pane === 'npm') return 'npm';
35
34
  if (pane === 'unit') return 'unit';
36
35
  if (!view.item) return 'empty';
37
- if (view.item.origin === 'todo') return 'todo';
36
+ if (view.item.origin === 'task') return 'tasks';
38
37
  return 'diff';
39
38
  };
40
39
 
@@ -46,7 +45,7 @@ const BODY_PAINT = {
46
45
  commits: paintBodyCommits,
47
46
  npm: paintBodyNpm,
48
47
  empty: paintBodyEmpty,
49
- todo: paintBodyTodo,
48
+ tasks: paintBodyTodo,
50
49
  diff: paintBodyDiff,
51
50
  unit: paintBodyUnit,
52
51
  };
@@ -100,145 +99,131 @@ const revealScroll = (owners, focus, height, prefer) => {
100
99
  return start;
101
100
  };
102
101
 
103
- const mapBodyCursor = (cursor, shift, scroll, headerLines, shown) => {
102
+ const windowY = (win, row) => {
103
+ const at = row + win.shift - win.scroll;
104
+ if (at < 0 || at >= win.shown) return -1;
105
+ return win.headerLines + at + 1;
106
+ };
107
+
108
+ const mapEditHits = (hits, win) => {
109
+ const out = [];
110
+ for (const hit of hits) {
111
+ const y = windowY(win, hit.row);
112
+ if (y >= 0) out.push({ ...hit, y });
113
+ }
114
+ return out;
115
+ };
116
+
117
+ const mapBodyCursor = (cursor, win) => {
104
118
  if (!cursor) return null;
105
- const row = cursor.row + shift;
106
- if (row < scroll || row >= scroll + shown) return null;
107
- return {
108
- x: cursor.x,
109
- y: headerLines + (row - scroll) + 1,
110
- };
119
+ const y = windowY(win, cursor.row);
120
+ if (y < 0) return null;
121
+ const point = { x: cursor.x, y };
122
+ if (typeof cursor.scroll === 'number') point.scroll = cursor.scroll;
123
+ return point;
111
124
  };
112
125
 
113
- const windowBody = (view, width, color, bodyH, headerLines, paintedBody) => {
114
- const sourceBody = paintedBody.body;
115
- const sourceOwners = paintedBody.todoOwners;
116
- const sourceChecks = paintedBody.todoChecks ?? [];
117
- const splitBody = paintedBody.splitBody === true;
118
- const pane = view.pane ?? 'diff';
119
- const listPane = LIST_PANES.includes(pane);
120
- const fillKind = bodyFillKind(listPane, splitBody);
126
+ const windowBody = (view, paintedBody, frame) => {
127
+ const { width, color, bodyH, headerLines } = frame;
128
+ const listPane = LIST_PANES.includes(view.pane ?? 'diff');
129
+ const fillKind = bodyFillKind(listPane, paintedBody.splitBody);
121
130
  const filler = paintBodyFill(width, color, fillKind);
122
- const lead = !listPane && sourceBody.length > 0;
123
- const body = new Array(sourceBody.length + (lead ? 1 : 0));
124
- let bodyAt = 0;
125
- let shift = 0;
126
- if (lead) {
127
- body[bodyAt++] = filler;
128
- shift = 1;
129
- }
130
- for (let i = 0; i < sourceBody.length; i++) body[bodyAt++] = sourceBody[i];
131
- const ownerLead = lead && sourceOwners.length;
132
- const todoOwners = new Array(sourceOwners.length + (ownerLead ? 1 : 0));
133
- let ownerAt = 0;
134
- if (ownerLead) todoOwners[ownerAt++] = null;
135
- for (let i = 0; i < sourceOwners.length; i++) {
136
- todoOwners[ownerAt++] = sourceOwners[i];
137
- }
138
- const checkLead = lead && sourceChecks.length;
139
- const todoChecks = new Array(sourceChecks.length + (checkLead ? 1 : 0));
140
- let checkAt = 0;
141
- if (checkLead) todoChecks[checkAt++] = false;
142
- for (let i = 0; i < sourceChecks.length; i++) {
143
- todoChecks[checkAt++] = sourceChecks[i];
144
- }
145
- const scrollRaw = view.scroll ?? 0;
131
+ const shift = !listPane && paintedBody.body.length > 0 ? 1 : 0;
132
+ const lead = (items, first) =>
133
+ shift && items.length ? [first, ...items] : items;
134
+ const body = lead(paintedBody.body, filler);
135
+ const taskOwners = lead(paintedBody.taskOwners, null);
136
+ const taskChecks = lead(paintedBody.taskChecks, false);
146
137
  const scrollMax = Math.max(0, body.length - bodyH);
147
- let scroll = listPane ? 0 : Math.min(scrollRaw, scrollMax);
148
- if (view.item && view.item.origin === 'todo') {
149
- scroll = revealScroll(todoOwners, view.todoFocus ?? 0, bodyH, scroll);
138
+ let scroll = listPane ? 0 : Math.min(view.scroll ?? 0, scrollMax);
139
+ if (view.item && view.item.origin === 'task') {
140
+ scroll = revealScroll(taskOwners, view.tasksFocus ?? 0, bodyH, scroll);
150
141
  }
151
- const end = Math.min(body.length, scroll + bodyH);
152
- const copied = Math.max(0, end - scroll);
153
- const slice = new Array(bodyH);
154
- for (let i = 0; i < copied; i++) slice[i] = body[scroll + i];
155
- const todoHits = [];
156
- for (let i = 0; i < copied; i++) {
157
- const owner = todoOwners[scroll + i];
142
+ const slice = body.slice(scroll, scroll + bodyH);
143
+ const win = { shift, scroll, shown: slice.length, headerLines };
144
+ const taskHits = [];
145
+ for (let i = 0; i < slice.length; i++) {
146
+ const owner = taskOwners[scroll + i];
158
147
  if (owner === undefined || owner === null) continue;
159
148
  const y = headerLines + i + 1;
160
- if (todoChecks[scroll + i]) {
161
- todoHits.push({ y, cursor: owner, x0: 1, x1: 4, check: true });
149
+ if (taskChecks[scroll + i]) {
150
+ taskHits.push({ y, cursor: owner, x0: 1, x1: 4, check: true });
162
151
  }
163
- todoHits.push({ y, cursor: owner });
152
+ taskHits.push({ y, cursor: owner });
164
153
  }
165
- const cursor = mapBodyCursor(
166
- paintedBody.cursor,
167
- shift,
154
+ const cursor = mapBodyCursor(paintedBody.cursor, win);
155
+ const editHits = mapEditHits(paintedBody.editHits, win);
156
+ while (slice.length < bodyH) slice.push(filler);
157
+ const { listScroll, fileHits } = paintedBody;
158
+ return {
159
+ slice,
168
160
  scroll,
169
- headerLines,
170
- copied,
171
- );
172
- for (let i = copied; i < bodyH; i++) slice[i] = filler;
173
- const listScroll = paintedBody.listScroll ?? 0;
174
- const fileHits = paintedBody.fileHits;
175
- return { slice, scroll, scrollMax, listScroll, todoHits, fileHits, cursor };
161
+ scrollMax,
162
+ listScroll,
163
+ taskHits,
164
+ fileHits,
165
+ editHits,
166
+ cursor,
167
+ };
176
168
  };
177
169
 
178
- const notePointerHits = (notesWanted, noteRows, noteCursor, bodyH) => {
179
- let cursor = null;
180
- if (noteCursor) {
181
- cursor = { x: noteCursor.x, y: 2 + bodyH + noteCursor.row };
182
- }
170
+ const notePointerHits = (notes, bodyH) => {
171
+ const { notesWanted, noteRows, noteCursor } = notes;
172
+ const top = 2 + bodyH;
173
+ const x = noteCursor ? noteCursor.x : 0;
174
+ const y = noteCursor ? top + noteCursor.row : 0;
175
+ const cursor = noteCursor ? { x, y } : null;
183
176
  const templateHits = [];
184
177
  for (const hit of notesWanted.templateHits ?? []) {
185
178
  if (hit.row >= noteRows.length) continue;
186
- templateHits.push({ y: 2 + bodyH + hit.row, cursor: hit.cursor });
179
+ templateHits.push({ y: top + hit.row, cursor: hit.cursor });
187
180
  }
188
- return { cursor, templateHits };
189
- };
190
-
191
- const stackFrame = (view, width, color, rows, layout, windowed, notes) => {
192
- for (const row of windowed.slice) rows.push(row);
193
- for (const row of notes.noteRows) rows.push(row);
194
- const status = view.status ?? '';
195
- rows.push(paintStatusLine(view, status, width, color));
196
- rows.push(paintFooter(view, layout, width, color));
197
- return overlayRows(rows, view.selection);
181
+ const editHits = [];
182
+ for (const hit of notesWanted.editHits ?? []) {
183
+ if (hit.row >= noteRows.length) continue;
184
+ editHits.push({ ...hit, y: top + hit.row });
185
+ }
186
+ return { cursor, templateHits, editHits };
198
187
  };
199
188
 
200
189
  const renderFrame = (view, options = {}) => {
201
190
  const width = Math.max(20, options.width ?? 80);
202
191
  const height = Math.max(6, options.height ?? 24);
203
192
  const color = options.color ?? true;
204
- const rows = [paintHeader(view, width, color)];
205
- const extra = extraFooterActions(view);
206
- const hidden = hiddenActions(view);
207
- const dimmed = dimFooterActions(view);
208
- const layout = layoutButtons(width, false, hidden, extra, dimmed);
193
+ const header = paintHeader(view, width, color);
194
+ const layout = footerLayout(view, width);
209
195
  const notes = frameNotes(view, width, height, color);
210
- const headerLines = rows.length;
211
- const bodyH = notes.bodyH;
196
+ const headerLines = 1;
197
+ const { bodyH } = notes;
212
198
  const paintedBody = paintFrameBody(view, width, color, bodyH, headerLines);
213
- const windowed = windowBody(
214
- view,
215
- width,
216
- color,
217
- bodyH,
218
- headerLines,
219
- paintedBody,
220
- );
221
- const painted = stackFrame(view, width, color, rows, layout, windowed, notes);
222
- const text = painted.map((row) => (color ? row + RESET : row)).join('\n');
223
- const hits = notePointerHits(
224
- notes.notesWanted,
225
- notes.noteRows,
226
- notes.noteCursor,
227
- bodyH,
228
- );
199
+ const frame = { width, color, bodyH, headerLines };
200
+ const windowed = windowBody(view, paintedBody, frame);
201
+ const rows = [
202
+ header,
203
+ ...windowed.slice,
204
+ ...notes.noteRows,
205
+ paintStatusLine(view, view.status ?? '', width, color),
206
+ paintFooter(view, layout, width, color),
207
+ ];
208
+ const painted = overlayRows(rows, view.selection);
209
+ const hits = notePointerHits(notes, bodyH);
229
210
  const statusY = painted.length - 1;
230
211
  const branchHit = statusBranchHit(view, width, statusY);
231
212
  const statusHits = branchHit ? [branchHit] : statusChoiceHits(view, statusY);
232
- const { fileHits, todoHits, scroll, scrollMax, listScroll } = windowed;
213
+ const { fileHits, taskHits, scroll, scrollMax, listScroll } = windowed;
233
214
  const { templateHits } = hits;
215
+ const editHits = [...windowed.editHits, ...hits.editHits];
216
+ const shown = markEditorSelection(painted, view, editHits, color);
217
+ const text = shown.map((row) => (color ? row + RESET : row)).join('\n');
234
218
  const footerY = painted.length;
235
219
  return {
236
220
  text,
237
- rows: painted,
221
+ rows: shown,
238
222
  buttons: footerHits(view, layout),
239
223
  fileHits,
240
- todoHits,
224
+ taskHits,
241
225
  templateHits,
226
+ editHits,
242
227
  statusHits,
243
228
  footerY,
244
229
  height: footerY,
@@ -250,4 +235,4 @@ const renderFrame = (view, options = {}) => {
250
235
  };
251
236
  };
252
237
 
253
- module.exports = { BODY_PAINT, renderFrame };
238
+ module.exports = { renderFrame };
@@ -5,8 +5,8 @@ const wrap = require('../wrap.js');
5
5
  const files = require('../files.js');
6
6
  const primitives = require('./primitives.js');
7
7
 
8
- const { REPO_TODOS_LABEL } = files;
9
- const { wrapMultiline, cursorInWrap } = wrap;
8
+ const { REPO_TASKS_LABEL } = files;
9
+ const { wrapMultiline, wrapDoc, cursorInWrap } = wrap;
10
10
  const { THEME, truncateVisible } = ansi;
11
11
  const { paintBar, paintCheckBar, windowRows } = primitives;
12
12
  const { LIST_PANES, CHECK_LEAD, paneResult } = primitives;
@@ -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;
@@ -81,8 +105,10 @@ const paintTodoList = (texts, focus, width, color, edit) => {
81
105
  }
82
106
  if (!edit || !selected) continue;
83
107
  cursor = todoCursor(text, edit, inner, rowStart);
108
+ const hits = taskEditHits(text, inner, rowStart, width);
109
+ for (const hit of hits) editHits.push(hit);
84
110
  }
85
- return { rows, owners, checks, cursor };
111
+ return { rows, owners, checks, cursor, editHits };
86
112
  };
87
113
 
88
114
  const noteInnerWidth = (width) => Math.max(1, width - NOTE_PAD * 2);
@@ -101,11 +127,23 @@ const paintTemplateRow = (text, selected, width, color) => {
101
127
  return paintNoteLine(text, width, fgRgb, bgRgb, color);
102
128
  };
103
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
+
104
141
  const paintComposeNote = (view, width, color, cap) => {
105
142
  const inner = noteInnerWidth(width);
106
143
  const bgRgb = THEME.noteBg;
107
144
  const compose = view.compose;
108
- const wrapped = wrapMultiline(compose.text, inner);
145
+ const doc = wrapDoc(compose.text ?? '', inner);
146
+ const wrapped = doc.map((piece) => piece.text);
109
147
  const { row, col } = cursorInWrap(compose.text, compose.cursor ?? 0, inner);
110
148
  const shown = windowRows(wrapped, row, INPUT_MAX);
111
149
  const rows = [];
@@ -124,8 +162,19 @@ const paintComposeNote = (view, width, color, cap) => {
124
162
  }
125
163
  }
126
164
  const templateCount = rows.length;
127
- for (const line of shown.rows) {
128
- rows.push(paintNoteLine(line, width, THEME.chromeFg, bgRgb, color));
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));
129
178
  }
130
179
  return {
131
180
  rows,
@@ -134,11 +183,12 @@ const paintComposeNote = (view, width, color, cap) => {
134
183
  row: templateCount + row - shown.offset,
135
184
  },
136
185
  templateHits,
186
+ editHits,
137
187
  };
138
188
  };
139
189
 
140
190
  const paintIdleNote = (view, width, color) => {
141
- const empty = { rows: [], cursor: null, templateHits: [] };
191
+ const empty = { rows: [], cursor: null, templateHits: [], editHits: [] };
142
192
  const text = view.noteText;
143
193
  if (!text) return empty;
144
194
  const inner = noteInnerWidth(width);
@@ -159,7 +209,7 @@ const paintIdleNote = (view, width, color) => {
159
209
  };
160
210
 
161
211
  const paintNotePanel = (view, width, color, cap) => {
162
- const empty = { rows: [], cursor: null, templateHits: [] };
212
+ const empty = { rows: [], cursor: null, templateHits: [], editHits: [] };
163
213
  const compose = view.compose;
164
214
  if (compose && NOTE_COMPOSE.includes(compose.kind)) {
165
215
  return paintComposeNote(view, width, color, cap);
@@ -170,34 +220,39 @@ const paintNotePanel = (view, width, color, cap) => {
170
220
  };
171
221
 
172
222
  const paintTodoCaption = (width, color) =>
173
- paintBar(` ${REPO_TODOS_LABEL}`, width, THEME.mutedFg, THEME.ctxBg, color);
223
+ paintBar(` ${REPO_TASKS_LABEL}`, width, THEME.mutedFg, THEME.ctxBg, color);
174
224
 
175
225
  const paintBodyTodo = (view, width, color) => {
176
- const texts = view.todos ?? [];
226
+ const texts = view.tasks ?? [];
177
227
  let lines = texts;
178
228
  if (!lines.length && view.noteText) lines = [view.noteText];
179
229
  const painted = paintTodoList(
180
230
  lines,
181
- view.todoFocus ?? 0,
231
+ view.tasksFocus ?? 0,
182
232
  width,
183
233
  color,
184
- view.todoEdit,
234
+ view.taskEdit,
185
235
  );
186
236
  const caption = paintTodoCaption(width, color);
187
237
  const body = [caption, ...painted.rows];
188
- const todoOwners = [null, ...painted.owners];
189
- const todoChecks = [false, ...painted.checks];
238
+ const taskOwners = [null, ...painted.owners];
239
+ const taskChecks = [false, ...painted.checks];
190
240
  let cursor = null;
191
241
  if (painted.cursor) {
192
242
  cursor = { x: painted.cursor.x, row: painted.cursor.row + 1 };
193
243
  }
244
+ const editHits = [];
245
+ for (const hit of painted.editHits) {
246
+ editHits.push({ ...hit, row: hit.row + 1 });
247
+ }
194
248
  const fileHits = [];
195
249
  const splitBody = false;
196
250
  return paneResult({
197
251
  body,
198
252
  fileHits,
199
- todoOwners,
200
- todoChecks,
253
+ editHits,
254
+ taskOwners,
255
+ taskChecks,
201
256
  cursor,
202
257
  splitBody,
203
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 { paintListEdit } = primitives;
13
+ const { fieldWindow } = primitives;
14
14
 
15
15
  const EDGE = 2;
16
16
  const TAIL = 1;
@@ -64,31 +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 paintRow = (entry, rowWidth, rowColor, selected) =>
74
- paintNpmRow(entry, rowWidth, rowColor, selected, nameW);
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 = paintListEdit(compose, width, color, EDGE);
173
+ const painted = paintNpmEdit(view, width, color, nameW);
78
174
  const at = view.npmEditName ? 'replace' : 'end';
79
- extra = { row: painted.row, cursor: painted.cursor, at, index: cursor };
175
+ extra = { ...painted, at, index: cursor };
80
176
  }
81
- return paintCursorList(
82
- commands,
83
- cursor,
84
- width,
85
- color,
86
- bodyH,
87
- headerLines,
88
- paintRow,
89
- extra,
90
- view.listScroll,
91
- );
177
+ return paintCursorList(commands, cursor, pane, paintRow, extra);
92
178
  };
93
179
 
94
180
  const logStart = (view, count, rows) => {