reslop 0.2.5 → 0.2.7

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 (114) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/LICENSE +1 -1
  3. package/README.md +11 -11
  4. package/bin/reslop.js +3 -8
  5. package/lib/ansi.js +72 -19
  6. package/lib/api.js +1 -1
  7. package/lib/capabilities.js +8 -8
  8. package/lib/cli.js +61 -75
  9. package/lib/clipboard.js +39 -47
  10. package/lib/dashboard/git.js +128 -0
  11. package/lib/dashboard/model.js +357 -0
  12. package/lib/dashboard/npm.js +290 -0
  13. package/lib/dashboard/tree.js +278 -0
  14. package/lib/deps-diff.js +197 -402
  15. package/lib/deps.js +113 -240
  16. package/lib/detect.js +8 -18
  17. package/lib/diff/align.js +41 -113
  18. package/lib/diff/diff.js +12 -43
  19. package/lib/diff/display.js +25 -36
  20. package/lib/diff/edit.js +6 -20
  21. package/lib/diff/inline.js +76 -144
  22. package/lib/diff/items.js +33 -47
  23. package/lib/diff/parse.js +28 -56
  24. package/lib/diff/patch.js +12 -33
  25. package/lib/diff/unit.js +79 -178
  26. package/lib/editor.js +52 -69
  27. package/lib/files.js +79 -91
  28. package/lib/find.js +61 -0
  29. package/lib/git-branches.js +254 -416
  30. package/lib/git-deps.js +148 -272
  31. package/lib/git-reword-todo.js +5 -13
  32. package/lib/git-worktree.js +161 -214
  33. package/lib/git.js +21 -49
  34. package/lib/github.js +67 -181
  35. package/lib/gitlab.js +40 -90
  36. package/lib/highlight/bash.js +55 -177
  37. package/lib/highlight/core.js +155 -141
  38. package/lib/highlight/css.js +25 -58
  39. package/lib/highlight/csv.js +19 -37
  40. package/lib/highlight/dart.js +72 -213
  41. package/lib/highlight/dot.js +14 -14
  42. package/lib/highlight/html.js +42 -63
  43. package/lib/highlight/js.js +177 -506
  44. package/lib/highlight/json.js +17 -37
  45. package/lib/highlight/log.js +19 -25
  46. package/lib/highlight/txt.js +7 -10
  47. package/lib/highlight.js +17 -46
  48. package/lib/keys.js +75 -83
  49. package/lib/manifest.js +67 -126
  50. package/lib/npm-commands.js +120 -169
  51. package/lib/remote.js +140 -190
  52. package/lib/render/branches.js +63 -61
  53. package/lib/render/chrome.js +412 -347
  54. package/lib/render/commits.js +160 -361
  55. package/lib/render/dash-activity.js +212 -0
  56. package/lib/render/dash-blocks.js +496 -0
  57. package/lib/render/dash-table.js +295 -0
  58. package/lib/render/dashboard.js +62 -0
  59. package/lib/render/diff.js +123 -258
  60. package/lib/render/files.js +60 -86
  61. package/lib/render/frame.js +82 -117
  62. package/lib/render/notes.js +150 -210
  63. package/lib/render/npm.js +213 -177
  64. package/lib/render/packages.js +93 -0
  65. package/lib/render/present.js +4 -11
  66. package/lib/render/primitives.js +77 -172
  67. package/lib/render/render.js +5 -14
  68. package/lib/render/report.js +43 -0
  69. package/lib/render/repos.js +226 -0
  70. package/lib/render/search.js +25 -0
  71. package/lib/render/tiles.js +355 -0
  72. package/lib/render/unit.js +15 -31
  73. package/lib/report-model.js +110 -33
  74. package/lib/report-parse.js +56 -37
  75. package/lib/report-render.js +102 -59
  76. package/lib/report-run.js +87 -77
  77. package/lib/report-test.js +221 -99
  78. package/lib/report-tools.js +96 -154
  79. package/lib/review-merge.js +33 -41
  80. package/lib/review.js +69 -129
  81. package/lib/runs.js +225 -0
  82. package/lib/select.js +53 -91
  83. package/lib/session/accessors.js +14 -21
  84. package/lib/session/actions.js +189 -65
  85. package/lib/session/branches.js +164 -222
  86. package/lib/session/change-actions.js +49 -119
  87. package/lib/session/commit.js +298 -352
  88. package/lib/session/compose.js +572 -715
  89. package/lib/session/dashboard.js +530 -0
  90. package/lib/session/dispatch.js +197 -171
  91. package/lib/session/files-pane.js +92 -45
  92. package/lib/session/find.js +94 -0
  93. package/lib/session/items.js +6 -17
  94. package/lib/session/lifecycle.js +420 -466
  95. package/lib/session/load.js +1 -14
  96. package/lib/session/navigation.js +4 -2
  97. package/lib/session/npm.js +403 -420
  98. package/lib/session/ops.js +5 -13
  99. package/lib/session/packages.js +143 -0
  100. package/lib/session/pointer.js +30 -14
  101. package/lib/session/progress.js +39 -30
  102. package/lib/session/review.js +1 -3
  103. package/lib/session/tasks.js +277 -236
  104. package/lib/session/templates.js +51 -59
  105. package/lib/session/terminal.js +85 -132
  106. package/lib/session/view.js +59 -80
  107. package/lib/session/watch.js +201 -212
  108. package/lib/session/workspace.js +455 -0
  109. package/lib/session.js +95 -161
  110. package/lib/update.js +44 -111
  111. package/lib/utilities.js +46 -88
  112. package/lib/workspace.js +166 -0
  113. package/lib/wrap.js +29 -55
  114. package/package.json +1 -1
@@ -3,11 +3,15 @@
3
3
  const ansi = require('../ansi.js');
4
4
  const files = require('../files.js');
5
5
  const primitives = require('./primitives.js');
6
- const { measureColumns, padColumns } = primitives;
7
6
 
8
7
  const { isTasksEntry, shortAge } = files;
9
- const { THEME, paint, visibleWidth, truncateVisible, seq, EL } = ansi;
10
- const { fill, FILE_MARK, paintCursorList } = primitives;
8
+ const { THEME, paint, visibleWidth, trimVisible, seq, EL } = ansi;
9
+ const { fill, markPrefix, paintCursorList } = primitives;
10
+ const { measureColumns, padColumns } = primitives;
11
+ const searchPaint = require('./search.js');
12
+ const { paintPathText } = searchPaint;
13
+ const locate = require('../find.js');
14
+ const { activeHit, spansFor } = locate;
11
15
 
12
16
  const FILE_STATUS_FG = {
13
17
  unstaged: THEME.delLineFg,
@@ -27,115 +31,85 @@ const FILE_COL_ALIGN = {
27
31
 
28
32
  const FILE_DATE_GAP = ' ';
29
33
 
30
- const signedCount = (sign, n) => `${sign}${n ?? 0}`;
31
-
32
34
  const fileRowFields = (entry) => {
35
+ const n = `${entry.staged ?? 0}`;
36
+ const m = `${entry.remaining ?? 0}`;
33
37
  if (isTasksEntry(entry)) {
34
- const n = `${entry.staged ?? 0}`;
35
- const m = `${entry.remaining ?? 0}`;
36
38
  return { add: '', del: '', status: '', n, m, date: '' };
37
39
  }
38
- const { added, removed, staged, remaining } = entry;
39
- const add = signedCount('+', added);
40
- const del = signedCount('-', removed);
40
+ const add = `+${entry.added ?? 0}`;
41
+ const del = `-${entry.removed ?? 0}`;
41
42
  const status = entry.status ?? '';
42
- const n = `${staged ?? 0}`;
43
- const m = `${remaining ?? 0}`;
44
- const date = shortAge(entry.date ?? '');
45
- return { add, del, status, n, m, date };
43
+ return { add, del, status, n, m, date: shortAge(entry.date) };
46
44
  };
47
45
 
48
- const fileRowFg = (status, selected) => {
49
- const statusFg = FILE_STATUS_FG[status];
46
+ const fileRowFg = (entry, selected) => {
47
+ if (isTasksEntry(entry)) return THEME.buttonHotFg;
48
+ const statusFg = FILE_STATUS_FG[entry.status];
50
49
  if (statusFg) return statusFg;
51
- if (selected) return THEME.chromeFg;
52
- return THEME.mutedFg;
53
- };
54
-
55
- const joinFileDate = (parts, cols) => {
56
- if (!cols?.date) return '';
57
- return `${FILE_DATE_GAP}${parts.date}`;
50
+ return selected ? THEME.chromeFg : THEME.mutedFg;
58
51
  };
59
52
 
60
- const joinFileMeta = (parts) => {
61
- const { add, del, status, n, m } = parts;
62
- return ` ${add} ${del} ${status} ${n}/${m}`;
63
- };
64
-
65
- const paintFileSuffix = (parts, cols, fgRgb, bgRgb, color) => {
66
- const { add, del, status, n, m } = parts;
53
+ const paintFileMeta = (parts, fgRgb, bgRgb, color) => {
67
54
  const gap = paint(' ', fgRgb, bgRgb, color);
68
- const addText = paint(add, THEME.addLineFg, bgRgb, color);
69
- const delText = paint(del, THEME.delLineFg, bgRgb, color);
70
- const statusText = paint(status, fgRgb, bgRgb, color);
71
- const nText = paint(n, fgRgb, bgRgb, color);
72
- const slash = paint('/', fgRgb, bgRgb, color);
73
- const mText = paint(m, fgRgb, bgRgb, color);
74
- const counts = addText + gap + delText;
75
- const meta = statusText + gap + nText + slash + mText;
76
- if (!cols?.date) return gap + counts + gap + meta;
77
- const dateGap = paint(FILE_DATE_GAP, fgRgb, bgRgb, color);
78
- const age = paint(parts.date, THEME.mutedFg, bgRgb, color);
79
- return gap + counts + gap + meta + dateGap + age;
55
+ let out = gap + paint(parts.add, THEME.addLineFg, bgRgb, color);
56
+ out += gap + paint(parts.del, THEME.delLineFg, bgRgb, color);
57
+ out += gap + paint(parts.status, fgRgb, bgRgb, color);
58
+ out += gap + paint(parts.n, fgRgb, bgRgb, color);
59
+ out += paint('/', fgRgb, bgRgb, color);
60
+ return out + paint(parts.m, fgRgb, bgRgb, color);
80
61
  };
81
62
 
82
- const paintTodosRow = (entry, width, color, selected, cols) => {
83
- const inner = Math.max(1, width - 1);
84
- const mark = selected ? FILE_MARK : ' ';
85
- const prefix = ` ${mark} `;
86
- const parts = padColumns(fileRowFields(entry), cols, FILE_COL_ALIGN);
87
- const meta = joinFileMeta(parts);
88
- const dated = joinFileDate(parts, cols);
89
- const suffix = `${meta}${dated}`;
90
- const pathW = Math.max(1, inner - visibleWidth(prefix + suffix));
91
- const clipped = truncateVisible(entry.path, pathW);
92
- const pad = Math.max(0, pathW - visibleWidth(clipped));
93
- const left = `${prefix}${clipped}${' '.repeat(pad)}`;
94
- const fgRgb = THEME.buttonHotFg;
95
- const bgRgb = selected ? THEME.buttonBg : THEME.ctxBg;
96
- const edge = fill(1, fgRgb, bgRgb, color);
97
- if (!color) return `${left}${suffix}${edge}`;
98
- let out = `${seq(fgRgb, bgRgb)}${EL}`;
99
- out += paint(left, fgRgb, bgRgb, color, true);
100
- out += paint(meta, fgRgb, bgRgb, color, true);
101
- if (!dated) return out + edge;
102
- out += paint(FILE_DATE_GAP, fgRgb, bgRgb, color);
103
- out += paint(parts.date, THEME.mutedFg, bgRgb, color);
104
- return out + edge;
63
+ const rowSpans = (entry, path, marks) => {
64
+ if (!marks || !marks.query) return [];
65
+ const hit = marks.hit && marks.hit.index === marks.index ? marks.hit : null;
66
+ return spansFor(path, entry.path ?? '', marks.query, hit);
105
67
  };
106
68
 
107
- const paintFileRow = (entry, width, color, selected, cols) => {
108
- if (isTasksEntry(entry)) {
109
- return paintTodosRow(entry, width, color, selected, cols);
110
- }
69
+ const paintFileRow = (entry, width, color, selected, cols, marks) => {
70
+ const tasks = isTasksEntry(entry);
111
71
  const inner = Math.max(1, width - 1);
112
- const mark = selected ? FILE_MARK : ' ';
113
- const prefix = ` ${mark} `;
72
+ const prefix = markPrefix(selected);
114
73
  const parts = padColumns(fileRowFields(entry), cols, FILE_COL_ALIGN);
115
- const suffix = `${joinFileMeta(parts)}${joinFileDate(parts, cols)}`;
116
- const pathW = Math.max(1, inner - visibleWidth(prefix + suffix));
117
- const { path, status } = entry;
118
- const clipped = truncateVisible(path, pathW);
119
- const pad = Math.max(0, pathW - visibleWidth(clipped));
120
- const left = `${prefix}${clipped}${' '.repeat(pad)}`;
121
- const fgRgb = fileRowFg(status, selected);
74
+ const { add, del, status, n, m } = parts;
75
+ const meta = ` ${add} ${del} ${status} ${n}/${m}`;
76
+ const date = cols.date ? `${FILE_DATE_GAP}${parts.date}` : '';
77
+ const pathW = Math.max(1, inner - visibleWidth(prefix + meta + date));
78
+ const path = trimVisible(entry.path, pathW);
79
+ const pad = ' '.repeat(Math.max(0, pathW - visibleWidth(path)));
80
+ const left = `${prefix}${path}${pad}`;
81
+ const fgRgb = fileRowFg(entry, selected);
122
82
  const bgRgb = selected ? THEME.buttonBg : THEME.ctxBg;
123
83
  const edge = fill(1, fgRgb, bgRgb, color);
124
- if (!color) return `${left}${suffix}${edge}`;
84
+ if (!color) return `${left}${meta}${date}${edge}`;
85
+ const spans = rowSpans(entry, path, marks);
125
86
  let out = `${seq(fgRgb, bgRgb)}${EL}`;
126
- out += paint(left, fgRgb, bgRgb, color);
127
- out += paintFileSuffix(parts, cols, fgRgb, bgRgb, color);
87
+ out += paint(prefix, fgRgb, bgRgb, color, tasks);
88
+ out += paintPathText(path, fgRgb, bgRgb, color, tasks, spans);
89
+ out += paint(pad, fgRgb, bgRgb, color, tasks);
90
+ if (tasks) out += paint(meta, fgRgb, bgRgb, color, true);
91
+ else out += paintFileMeta(parts, fgRgb, bgRgb, color);
92
+ if (date) {
93
+ out += paint(FILE_DATE_GAP, fgRgb, bgRgb, color);
94
+ out += paint(parts.date, THEME.mutedFg, bgRgb, color);
95
+ }
128
96
  return out + edge;
129
97
  };
130
98
 
131
99
  const paintBodyFiles = (view, width, color, bodyH, headerLines) => {
132
- const files = view.files ?? [];
133
- const cols = measureColumns(files, fileRowFields, FILE_COL_ALIGN);
100
+ const entries = view.files ?? [];
101
+ const cols = measureColumns(entries, fileRowFields, FILE_COL_ALIGN);
134
102
  const cursor = view.fileCursor ?? 0;
135
103
  const pane = { width, color, bodyH, headerLines, offset: view.listScroll };
136
- const paintRow = (entry, selected) =>
137
- paintFileRow(entry, width, color, selected, cols);
138
- return paintCursorList(files, cursor, pane, paintRow);
104
+ const hit = activeHit(entries, view.find);
105
+ const query = view.find && view.find.query ? view.find.query : '';
106
+ const paintRow = (entry, selected, index) =>
107
+ paintFileRow(entry, width, color, selected, cols, {
108
+ query,
109
+ hit,
110
+ index,
111
+ });
112
+ return paintCursorList(entries, cursor, pane, paintRow);
139
113
  };
140
114
 
141
115
  module.exports = { paintBodyFiles };
@@ -2,7 +2,6 @@
2
2
 
3
3
  const ansi = require('../ansi.js');
4
4
  const select = require('../select.js');
5
- const { overlayRows, markEditorSelection } = select;
6
5
  const primitives = require('./primitives.js');
7
6
  const chrome = require('./chrome.js');
8
7
  const files = require('./files.js');
@@ -12,91 +11,58 @@ const notes = require('./notes.js');
12
11
  const npm = require('./npm.js');
13
12
  const diff = require('./diff.js');
14
13
  const unit = require('./unit.js');
14
+ const dashboard = require('./dashboard.js');
15
+ const packages = require('./packages.js');
16
+ const repos = require('./repos.js');
15
17
 
16
18
  const { RESET } = ansi;
17
- const { LIST_PANES, paintBodyFill, paneResult } = primitives;
19
+ const { overlayRows, markEditorSelection } = select;
20
+ const { isListPane, isTaskView, paintBodyFill, paneResult } = primitives;
18
21
  const { paintHeader, paintFooter, footerHits, paintStatusLine } = chrome;
19
22
  const { footerLayout, statusBranchHit, statusChoiceHits } = chrome;
20
23
  const { paintNotePanel, paintBodyTodo } = notes;
21
- const { paintBodyFiles } = files;
22
- const { paintBodyBranches } = branches;
23
- const { paintBodyCommits } = commits;
24
- const { paintBodyNpm } = npm;
25
24
  const { paintBodyDiff } = diff;
26
- const { paintBodyUnit } = unit;
27
25
 
28
- const bodyKind = (view) => {
29
- const pane = view.pane ?? 'diff';
30
- if (pane === 'files' || pane === 'branches' || pane === 'commits') {
31
- return pane;
32
- }
33
- if (pane === 'npm') return 'npm';
34
- if (pane === 'unit') return 'unit';
35
- if (!view.item) return 'empty';
36
- if (view.item.origin === 'task') return 'tasks';
37
- return 'diff';
26
+ const PANE_BODY = {
27
+ dashboard: dashboard.paintBodyDashboard,
28
+ repos: repos.paintBodyRepos,
29
+ files: files.paintBodyFiles,
30
+ branches: branches.paintBodyBranches,
31
+ commits: commits.paintBodyCommits,
32
+ npm: npm.paintBodyNpm,
33
+ packages: packages.paintBodyPackages,
34
+ unit: unit.paintBodyUnit,
35
+ tasks: paintBodyTodo,
38
36
  };
39
37
 
40
38
  const paintBodyEmpty = () => paneResult();
41
39
 
42
- const BODY_PAINT = {
43
- files: paintBodyFiles,
44
- branches: paintBodyBranches,
45
- commits: paintBodyCommits,
46
- npm: paintBodyNpm,
47
- empty: paintBodyEmpty,
48
- tasks: paintBodyTodo,
49
- diff: paintBodyDiff,
50
- unit: paintBodyUnit,
51
- };
52
-
53
- const paintFrameBody = (view, width, color, bodyH, headerLines) => {
54
- const paint = BODY_PAINT[bodyKind(view)];
55
- return paint(view, width, color, bodyH, headerLines);
40
+ const bodyPainter = (view) => {
41
+ if (Object.hasOwn(PANE_BODY, view.pane)) return PANE_BODY[view.pane];
42
+ if (!view.item) return paintBodyEmpty;
43
+ return paintBodyDiff;
56
44
  };
57
45
 
58
46
  const frameNotes = (view, width, height, color) => {
59
47
  const notesCap = Math.max(0, height - 4);
60
- const notesWanted = paintNotePanel(view, width, color, notesCap);
61
- const noteRows = notesWanted.rows.slice(0, notesCap);
62
- let noteCursor = notesWanted.cursor;
63
- if (noteCursor && noteCursor.row >= noteRows.length) noteCursor = null;
64
- const chrome = 3 + noteRows.length;
65
- const bodyH = Math.max(1, height - chrome);
66
- return { notesWanted, noteRows, noteCursor, bodyH };
67
- };
68
-
69
- const bodyFillKind = (listPane, splitBody) => {
70
- if (listPane) return 'files';
71
- if (splitBody) return 'split';
72
- return 'plain';
73
- };
74
-
75
- const ownerSpan = (owners, focus) => {
76
- let first = -1;
77
- let last = -1;
78
- for (let i = 0; i < owners.length; i++) {
79
- if (owners[i] !== focus) continue;
80
- if (first < 0) first = i;
81
- last = i;
82
- }
83
- return { first, last };
48
+ const panel = paintNotePanel(view, width, color, notesCap);
49
+ const rows = panel.rows.slice(0, notesCap);
50
+ const inside = panel.cursor && panel.cursor.row < rows.length;
51
+ const cursor = inside ? panel.cursor : null;
52
+ const bodyH = Math.max(1, height - 3 - rows.length);
53
+ return { panel, rows, cursor, bodyH };
84
54
  };
85
55
 
86
56
  const revealScroll = (owners, focus, height, prefer) => {
87
- const span = ownerSpan(owners, focus);
88
- if (span.first < 0) return prefer;
57
+ const first = owners.indexOf(focus);
58
+ if (first < 0) return prefer;
59
+ const last = owners.lastIndexOf(focus);
89
60
  const room = Math.max(1, height);
90
- let start = prefer;
91
- if (span.first < start) start = span.first;
92
- if (span.last >= start + room) {
93
- const fits = span.last - span.first + 1 <= room;
94
- start = fits ? span.last - room + 1 : span.first;
61
+ let start = Math.min(prefer, first);
62
+ if (last >= start + room) {
63
+ start = last - first + 1 <= room ? last - room + 1 : first;
95
64
  }
96
- const maxStart = Math.max(0, owners.length - room);
97
- if (start > maxStart) start = maxStart;
98
- if (start < 0) start = 0;
99
- return start;
65
+ return Math.max(0, Math.min(start, owners.length - room));
100
66
  };
101
67
 
102
68
  const windowY = (win, row) => {
@@ -123,20 +89,25 @@ const mapBodyCursor = (cursor, win) => {
123
89
  return point;
124
90
  };
125
91
 
126
- const windowBody = (view, paintedBody, frame) => {
92
+ const bodyFillKind = (listPane, splitBody) => {
93
+ if (listPane) return 'files';
94
+ return splitBody ? 'split' : 'plain';
95
+ };
96
+
97
+ const windowBody = (view, painted, frame) => {
127
98
  const { width, color, bodyH, headerLines } = frame;
128
- const listPane = LIST_PANES.includes(view.pane ?? 'diff');
129
- const fillKind = bodyFillKind(listPane, paintedBody.splitBody);
99
+ const listPane = isListPane(view.pane);
100
+ const fillKind = bodyFillKind(listPane, painted.splitBody);
130
101
  const filler = paintBodyFill(width, color, fillKind);
131
- const shift = !listPane && paintedBody.body.length > 0 ? 1 : 0;
102
+ const shift = !listPane && painted.body.length > 0 ? 1 : 0;
132
103
  const lead = (items, first) =>
133
104
  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);
105
+ const body = lead(painted.body, filler);
106
+ const taskOwners = lead(painted.taskOwners, null);
107
+ const taskChecks = lead(painted.taskChecks, false);
137
108
  const scrollMax = Math.max(0, body.length - bodyH);
138
109
  let scroll = listPane ? 0 : Math.min(view.scroll ?? 0, scrollMax);
139
- if (view.item && view.item.origin === 'task') {
110
+ if (isTaskView(view)) {
140
111
  scroll = revealScroll(taskOwners, view.tasksFocus ?? 0, bodyH, scroll);
141
112
  }
142
113
  const slice = body.slice(scroll, scroll + bodyH);
@@ -151,85 +122,79 @@ const windowBody = (view, paintedBody, frame) => {
151
122
  }
152
123
  taskHits.push({ y, cursor: owner });
153
124
  }
154
- const cursor = mapBodyCursor(paintedBody.cursor, win);
155
- const editHits = mapEditHits(paintedBody.editHits, win);
156
125
  while (slice.length < bodyH) slice.push(filler);
157
- const { listScroll, fileHits } = paintedBody;
158
126
  return {
159
127
  slice,
160
128
  scroll,
161
129
  scrollMax,
162
- listScroll,
130
+ listScroll: painted.listScroll,
163
131
  taskHits,
164
- fileHits,
165
- editHits,
166
- cursor,
132
+ fileHits: painted.fileHits,
133
+ editHits: mapEditHits(painted.editHits, win),
134
+ cursor: mapBodyCursor(painted.cursor, win),
167
135
  };
168
136
  };
169
137
 
170
138
  const notePointerHits = (notes, bodyH) => {
171
- const { notesWanted, noteRows, noteCursor } = notes;
139
+ const { panel, rows, cursor } = notes;
172
140
  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;
176
141
  const templateHits = [];
177
- for (const hit of notesWanted.templateHits ?? []) {
178
- if (hit.row >= noteRows.length) continue;
179
- templateHits.push({ y: top + hit.row, cursor: hit.cursor });
142
+ for (const hit of panel.templateHits) {
143
+ if (hit.row < rows.length) {
144
+ templateHits.push({ y: top + hit.row, cursor: hit.cursor });
145
+ }
180
146
  }
181
147
  const editHits = [];
182
- for (const hit of notesWanted.editHits ?? []) {
183
- if (hit.row >= noteRows.length) continue;
184
- editHits.push({ ...hit, y: top + hit.row });
148
+ for (const hit of panel.editHits) {
149
+ if (hit.row < rows.length) editHits.push({ ...hit, y: top + hit.row });
185
150
  }
186
- return { cursor, templateHits, editHits };
151
+ const point = cursor ? { x: cursor.x, y: top + cursor.row } : null;
152
+ return { cursor: point, templateHits, editHits };
187
153
  };
188
154
 
189
155
  const renderFrame = (view, options = {}) => {
190
156
  const width = Math.max(20, options.width ?? 80);
191
157
  const height = Math.max(6, options.height ?? 24);
192
158
  const color = options.color ?? true;
193
- const header = paintHeader(view, width, color);
159
+ const headerLines = 1;
194
160
  const layout = footerLayout(view, width);
195
161
  const notes = frameNotes(view, width, height, color);
196
- const headerLines = 1;
197
162
  const { bodyH } = notes;
198
- const paintedBody = paintFrameBody(view, width, color, bodyH, headerLines);
163
+ const paint = bodyPainter(view);
164
+ const painted = paint(view, width, color, bodyH, headerLines);
199
165
  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);
166
+ const windowed = windowBody(view, painted, frame);
167
+ const rows = overlayRows(
168
+ [
169
+ paintHeader(view, width, color),
170
+ ...windowed.slice,
171
+ ...notes.rows,
172
+ paintStatusLine(view, view.status ?? '', width, color),
173
+ paintFooter(view, layout, width, color),
174
+ ],
175
+ view.selection,
176
+ );
209
177
  const hits = notePointerHits(notes, bodyH);
210
- const statusY = painted.length - 1;
178
+ const statusY = rows.length - 1;
211
179
  const branchHit = statusBranchHit(view, width, statusY);
212
180
  const statusHits = branchHit ? [branchHit] : statusChoiceHits(view, statusY);
213
- const { fileHits, taskHits, scroll, scrollMax, listScroll } = windowed;
214
- const { templateHits } = hits;
215
181
  const editHits = [...windowed.editHits, ...hits.editHits];
216
- const shown = markEditorSelection(painted, view, editHits, color);
182
+ const shown = markEditorSelection(rows, view, editHits, color);
217
183
  const text = shown.map((row) => (color ? row + RESET : row)).join('\n');
218
- const footerY = painted.length;
219
184
  return {
220
185
  text,
221
186
  rows: shown,
222
187
  buttons: footerHits(view, layout),
223
- fileHits,
224
- taskHits,
225
- templateHits,
188
+ fileHits: windowed.fileHits,
189
+ taskHits: windowed.taskHits,
190
+ templateHits: hits.templateHits,
226
191
  editHits,
227
192
  statusHits,
228
- footerY,
229
- height: footerY,
230
- scroll,
231
- scrollMax,
232
- listScroll,
193
+ footerY: rows.length,
194
+ height: rows.length,
195
+ scroll: windowed.scroll,
196
+ scrollMax: windowed.scrollMax,
197
+ listScroll: windowed.listScroll,
233
198
  cursor: hits.cursor ?? windowed.cursor,
234
199
  bodyH,
235
200
  };