reslop 0.1.9 → 0.2.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.
Files changed (94) hide show
  1. package/CHANGELOG.md +12 -1
  2. package/README.md +2 -1
  3. package/bin/reslop.js +2 -2
  4. package/lib/ansi.js +4 -2
  5. package/lib/api.js +2 -2
  6. package/lib/capabilities.js +7 -14
  7. package/lib/cli.js +28 -47
  8. package/lib/clipboard.js +1 -1
  9. package/lib/{deps/changes.js → deps-diff.js} +309 -19
  10. package/lib/deps.js +498 -29
  11. package/lib/detect.js +1 -7
  12. package/lib/diff/align.js +7 -10
  13. package/lib/diff/diff.js +53 -0
  14. package/lib/diff/display.js +5 -4
  15. package/lib/diff/inline.js +26 -21
  16. package/lib/diff/items.js +6 -4
  17. package/lib/diff/patch.js +1 -3
  18. package/lib/diff/unit.js +392 -0
  19. package/lib/files.js +5 -8
  20. package/lib/{git/branches.js → git-branches.js} +6 -5
  21. package/lib/{git/dependencies.js → git-deps.js} +111 -38
  22. package/lib/git.js +589 -68
  23. package/lib/github.js +28 -197
  24. package/lib/gitlab.js +24 -195
  25. package/lib/highlight/bash.js +1 -3
  26. package/lib/highlight/css.js +1 -3
  27. package/lib/highlight/js.js +11 -14
  28. package/lib/highlight.js +6 -9
  29. package/lib/keys.js +14 -8
  30. package/lib/manifest.js +330 -0
  31. package/lib/remote.js +374 -0
  32. package/lib/render/branches.js +14 -68
  33. package/lib/render/chrome.js +24 -9
  34. package/lib/render/commits.js +6 -59
  35. package/lib/render/diff.js +16 -16
  36. package/lib/render/files.js +6 -34
  37. package/lib/render/frame.js +23 -12
  38. package/lib/render/notes.js +4 -3
  39. package/lib/render/primitives.js +45 -0
  40. package/lib/{render.js → render/render.js} +5 -5
  41. package/lib/render/unit.js +41 -0
  42. package/lib/review.js +750 -14
  43. package/lib/select.js +5 -10
  44. package/lib/session/accessors.js +9 -1
  45. package/lib/session/actions.js +32 -16
  46. package/lib/session/branches.js +116 -128
  47. package/lib/session/change-actions.js +202 -123
  48. package/lib/session/commit.js +105 -112
  49. package/lib/session/compose.js +158 -13
  50. package/lib/session/dispatch.js +104 -60
  51. package/lib/session/files-pane.js +142 -148
  52. package/lib/session/items.js +22 -30
  53. package/lib/session/lifecycle.js +44 -44
  54. package/lib/session/load.js +19 -33
  55. package/lib/session/navigation.js +12 -1
  56. package/lib/session/ops.js +28 -41
  57. package/lib/session/pointer.js +54 -57
  58. package/lib/session/review.js +23 -37
  59. package/lib/session/terminal.js +6 -12
  60. package/lib/session/todos.js +8 -12
  61. package/lib/session/unit.js +208 -0
  62. package/lib/session/update.js +33 -46
  63. package/lib/session/view.js +78 -9
  64. package/lib/session/watch.js +11 -19
  65. package/lib/session.js +21 -246
  66. package/lib/source.js +3 -3
  67. package/lib/update.js +40 -75
  68. package/lib/{git/proc.js → utilities.js} +75 -11
  69. package/lib/wrap.js +1 -3
  70. package/package.json +1 -1
  71. package/lib/deps/items.js +0 -134
  72. package/lib/deps/manifest.js +0 -152
  73. package/lib/deps/merge.js +0 -120
  74. package/lib/deps/proposals.js +0 -164
  75. package/lib/deps/reports.js +0 -180
  76. package/lib/deps/rows.js +0 -176
  77. package/lib/deps/scan.js +0 -138
  78. package/lib/deps/usage.js +0 -68
  79. package/lib/deps/versions.js +0 -119
  80. package/lib/diff.js +0 -33
  81. package/lib/git/changes.js +0 -164
  82. package/lib/git/read.js +0 -253
  83. package/lib/git/run.js +0 -100
  84. package/lib/npm/commands.js +0 -100
  85. package/lib/remote/changes.js +0 -37
  86. package/lib/remote/headers.js +0 -36
  87. package/lib/remote/http.js +0 -122
  88. package/lib/remote/notes.js +0 -94
  89. package/lib/review/format.js +0 -434
  90. package/lib/review/storage.js +0 -150
  91. package/lib/review/store.js +0 -178
  92. package/lib/review/templates.js +0 -49
  93. package/lib/session/origins.js +0 -7
  94. package/lib/sys.js +0 -84
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const ansi = require('../ansi.js');
4
- const { displayLines } = require('../diff.js');
4
+ const { displayLines } = require('../diff/diff.js');
5
5
  const highlight = require('../highlight.js');
6
6
  const { detectLang } = require('../detect.js');
7
7
  const files = require('../files.js');
@@ -81,8 +81,6 @@ const SOFT_WRAP = ['warn', 'note'];
81
81
  const DEL_CHAR_BG = ansi.bg(THEME.delCharBg);
82
82
  const ADD_CHAR_BG = ansi.bg(THEME.addCharBg);
83
83
 
84
- const lineStyles = (origin) => (origin === 'staged' ? STAGED_STYLES : STYLES);
85
-
86
84
  const paintSpans = (pieces, lineStyle, budget, color) => {
87
85
  let out = '';
88
86
  let used = 0;
@@ -129,13 +127,13 @@ const paintAuditRule = (width, color) => {
129
127
  const paintDiffLine = (line, width, color, origin, lang) => {
130
128
  if (line.type === 'noteSep') return paintAuditRule(width, color);
131
129
  if (line.type === 'note') return paintAuditNote(line, width, color);
132
- const styles = lineStyles(origin);
130
+ const styles = origin === 'staged' ? STAGED_STYLES : STYLES;
133
131
  const style = styles[line.type] ?? styles.ctx;
134
132
  const diffSpans = line.spans ?? [{ text: line.text, changed: false }];
135
133
  const tokens = tokenize(lang, line.text ?? '');
136
134
  const pieces = overlayTokens(tokens, diffSpans);
137
135
  const gutterW = Math.min(GUTTER_W, Math.max(0, width));
138
- const label = `${style.mark} `.slice(0, gutterW);
136
+ const label = `${line.mark ?? style.mark} `.slice(0, gutterW);
139
137
  const gutter = paint(label, CODE_FG.punct, style.baseBg, color);
140
138
  const budget = Math.max(0, width - gutterW);
141
139
  const rest = paintSpans(pieces, style, budget, color);
@@ -190,11 +188,12 @@ const wrapSoftLine = (line, width) => {
190
188
  const budget = Math.max(1, width - gutterW);
191
189
  const rows = wrapPlain(line.text ?? '', budget);
192
190
  const changed = line.wrap === true;
193
- const pieces = [];
191
+ const { length } = rows;
192
+ const pieces = new Array(length);
194
193
  let used = 0;
195
- for (let i = 0; i < rows.length; i++) {
194
+ for (let i = 0; i < length; i++) {
196
195
  const text = rows[i];
197
- const last = i === rows.length - 1;
196
+ const last = i === length - 1;
198
197
  const spans = [{ text, changed }];
199
198
  const next = { ...line, text, spans, editLast: last };
200
199
  if (line.editStart !== undefined) {
@@ -203,7 +202,7 @@ const wrapSoftLine = (line, width) => {
203
202
  else next.editEnd = next.editStart + text.length;
204
203
  }
205
204
  used += text.length;
206
- pieces.push(next);
205
+ pieces[i] = next;
207
206
  }
208
207
  return pieces;
209
208
  };
@@ -249,8 +248,7 @@ const expandSoftRows = (lines, width, split) => {
249
248
  const onCodePiece = (line, cursor) => {
250
249
  if (!line || line.editStart === undefined) return false;
251
250
  if (cursor < line.editStart) return false;
252
- if (line.editLast) return cursor <= line.editEnd;
253
- return cursor < line.editEnd;
251
+ return cursor <= line.editEnd;
254
252
  };
255
253
 
256
254
  const codeCursorInRows = (rows, split, width, cursor) => {
@@ -283,14 +281,15 @@ const paintBodyDiff = (view, width, color) => {
283
281
  const splitBody = layout === 'side' && !item.file.isBinary;
284
282
  const raw = dropLeadingEmpty(bodyLines(item, layout, overlay), splitBody);
285
283
  const lines = expandSoftRows(raw, width, splitBody);
286
- const body = [];
284
+ const { length } = lines;
285
+ const body = new Array(length);
287
286
  if (splitBody) {
288
- for (const row of lines) {
289
- body.push(paintSplitRow(row, width, color, item.origin, lang));
287
+ for (let i = 0; i < length; i++) {
288
+ body[i] = paintSplitRow(lines[i], width, color, item.origin, lang);
290
289
  }
291
290
  } else {
292
- for (const line of lines) {
293
- body.push(paintDiffLine(line, width, color, item.origin, lang));
291
+ for (let i = 0; i < length; i++) {
292
+ body[i] = paintDiffLine(lines[i], width, color, item.origin, lang);
294
293
  }
295
294
  }
296
295
  const edit = overlay ? overlay.cursor : null;
@@ -304,4 +303,5 @@ module.exports = {
304
303
  paintDiffLine,
305
304
  codeInnerWidth,
306
305
  paintBodyDiff,
306
+ codeCursorInRows,
307
307
  };
@@ -3,10 +3,11 @@
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;
6
7
 
7
8
  const { isTodosEntry } = files;
8
9
  const { THEME, paint, visibleWidth, truncateVisible, seq, EL } = ansi;
9
- const { fill, padVisible, FILE_MARK, paneResult, paintCursorList } = primitives;
10
+ const { fill, FILE_MARK, paneResult, paintCursorList } = primitives;
10
11
 
11
12
  const FILE_STATUS_FG = {
12
13
  unstaged: THEME.delLineFg,
@@ -15,8 +16,6 @@ const FILE_STATUS_FG = {
15
16
  partial: THEME.warnFg,
16
17
  };
17
18
 
18
- const FILE_COL_KEYS = ['add', 'del', 'status', 'n', 'm', 'date'];
19
-
20
19
  const FILE_COL_ALIGN = {
21
20
  add: 'start',
22
21
  del: 'start',
@@ -46,19 +45,6 @@ const fileRowFields = (entry) => {
46
45
  return { add, del, status, n, m, date };
47
46
  };
48
47
 
49
- const fileColumns = (files) => {
50
- const widths = {};
51
- for (const key of FILE_COL_KEYS) widths[key] = 0;
52
- for (const entry of files) {
53
- const fields = fileRowFields(entry);
54
- for (const key of FILE_COL_KEYS) {
55
- const w = visibleWidth(fields[key]);
56
- if (w > widths[key]) widths[key] = w;
57
- }
58
- }
59
- return widths;
60
- };
61
-
62
48
  const fileRowFg = (status, selected) => {
63
49
  const statusFg = FILE_STATUS_FG[status];
64
50
  if (statusFg) return statusFg;
@@ -66,17 +52,6 @@ const fileRowFg = (status, selected) => {
66
52
  return THEME.mutedFg;
67
53
  };
68
54
 
69
- const fileRowParts = (entry, cols) => {
70
- const fields = fileRowFields(entry);
71
- const parts = {};
72
- for (const key of FILE_COL_KEYS) {
73
- const width = cols?.[key] ?? visibleWidth(fields[key]);
74
- const align = FILE_COL_ALIGN[key];
75
- parts[key] = padVisible(fields[key], width, align);
76
- }
77
- return parts;
78
- };
79
-
80
55
  const joinFileDate = (parts, cols) => {
81
56
  if (!cols?.date) return '';
82
57
  return `${FILE_DATE_GAP}${parts.date}`;
@@ -87,9 +62,6 @@ const joinFileMeta = (parts) => {
87
62
  return ` ${add} ${del} ${status} ${n}/${m}`;
88
63
  };
89
64
 
90
- const joinFileSuffix = (parts, cols) =>
91
- `${joinFileMeta(parts)}${joinFileDate(parts, cols)}`;
92
-
93
65
  const paintFileSuffix = (parts, cols, fgRgb, bgRgb, color) => {
94
66
  const { add, del, status, n, m } = parts;
95
67
  const gap = paint(' ', fgRgb, bgRgb, color);
@@ -111,7 +83,7 @@ const paintTodosRow = (entry, width, color, selected, cols) => {
111
83
  const inner = Math.max(1, width - 1);
112
84
  const mark = selected ? FILE_MARK : ' ';
113
85
  const prefix = ` ${mark} `;
114
- const parts = fileRowParts(entry, cols);
86
+ const parts = padColumns(fileRowFields(entry), cols, FILE_COL_ALIGN);
115
87
  const meta = joinFileMeta(parts);
116
88
  const dated = joinFileDate(parts, cols);
117
89
  const suffix = `${meta}${dated}`;
@@ -139,8 +111,8 @@ const paintFileRow = (entry, width, color, selected, cols) => {
139
111
  const inner = Math.max(1, width - 1);
140
112
  const mark = selected ? FILE_MARK : ' ';
141
113
  const prefix = ` ${mark} `;
142
- const parts = fileRowParts(entry, cols);
143
- const suffix = joinFileSuffix(parts, cols);
114
+ const parts = padColumns(fileRowFields(entry), cols, FILE_COL_ALIGN);
115
+ const suffix = `${joinFileMeta(parts)}${joinFileDate(parts, cols)}`;
144
116
  const pathW = Math.max(1, inner - visibleWidth(prefix + suffix));
145
117
  const { path, status } = entry;
146
118
  const clipped = truncateVisible(path, pathW);
@@ -158,7 +130,7 @@ const paintFileRow = (entry, width, color, selected, cols) => {
158
130
 
159
131
  const paintBodyFiles = (view, width, color, bodyH, headerLines) => {
160
132
  const files = view.files ?? [];
161
- const cols = fileColumns(files);
133
+ const cols = measureColumns(files, fileRowFields, FILE_COL_ALIGN);
162
134
  const cursor = view.fileCursor ?? 0;
163
135
  const paintRow = (entry, rowWidth, rowColor, selected) =>
164
136
  paintFileRow(entry, rowWidth, rowColor, selected, cols);
@@ -9,6 +9,7 @@ const branches = require('./branches.js');
9
9
  const commits = require('./commits.js');
10
10
  const notes = require('./notes.js');
11
11
  const diff = require('./diff.js');
12
+ const unit = require('./unit.js');
12
13
 
13
14
  const { RESET } = ansi;
14
15
  const { LIST_PANES, paintBodyFill, paneResult } = primitives;
@@ -20,12 +21,14 @@ const { paintBodyFiles } = files;
20
21
  const { paintBodyBranches } = branches;
21
22
  const { paintBodyCommits } = commits;
22
23
  const { paintBodyDiff } = diff;
24
+ const { paintBodyUnit } = unit;
23
25
 
24
26
  const bodyKind = (view) => {
25
27
  const pane = view.pane ?? 'diff';
26
28
  if (pane === 'files' || pane === 'branches' || pane === 'commits') {
27
29
  return pane;
28
30
  }
31
+ if (pane === 'unit') return 'unit';
29
32
  if (!view.item) return 'empty';
30
33
  if (view.item.origin === 'todo') return 'todo';
31
34
  return 'diff';
@@ -47,6 +50,7 @@ const BODY_PAINT = {
47
50
  empty: paintBodyEmpty,
48
51
  todo: paintBodyTodo,
49
52
  diff: paintBodyDiff,
53
+ unit: paintBodyUnit,
50
54
  };
51
55
 
52
56
  const paintFrameBody = (view, width, color, bodyH, headerLines) => {
@@ -89,24 +93,31 @@ const windowBody = (view, width, color, bodyH, headerLines, paintedBody) => {
89
93
  const listPane = LIST_PANES.includes(pane);
90
94
  const fillKind = bodyFillKind(listPane, splitBody);
91
95
  const filler = paintBodyFill(width, color, fillKind);
92
- const body = [];
93
- const todoOwners = [];
96
+ const lead = !listPane && sourceBody.length > 0;
97
+ const body = new Array(sourceBody.length + (lead ? 1 : 0));
98
+ let bodyAt = 0;
94
99
  let shift = 0;
95
- if (!listPane && sourceBody.length > 0) {
96
- body.push(filler);
97
- if (sourceOwners.length) todoOwners.push(null);
100
+ if (lead) {
101
+ body[bodyAt++] = filler;
98
102
  shift = 1;
99
103
  }
100
- for (const row of sourceBody) body.push(row);
101
- for (const owner of sourceOwners) todoOwners.push(owner);
104
+ for (let i = 0; i < sourceBody.length; i++) body[bodyAt++] = sourceBody[i];
105
+ const ownerLead = lead && sourceOwners.length;
106
+ const todoOwners = new Array(sourceOwners.length + (ownerLead ? 1 : 0));
107
+ let ownerAt = 0;
108
+ if (ownerLead) todoOwners[ownerAt++] = null;
109
+ for (let i = 0; i < sourceOwners.length; i++) {
110
+ todoOwners[ownerAt++] = sourceOwners[i];
111
+ }
102
112
  const scrollRaw = view.scroll ?? 0;
103
113
  const scrollMax = Math.max(0, body.length - bodyH);
104
114
  const scroll = listPane ? 0 : Math.min(scrollRaw, scrollMax);
105
- const slice = [];
106
115
  const end = Math.min(body.length, scroll + bodyH);
107
- for (let i = scroll; i < end; i++) slice.push(body[i]);
116
+ const copied = Math.max(0, end - scroll);
117
+ const slice = new Array(bodyH);
118
+ for (let i = 0; i < copied; i++) slice[i] = body[scroll + i];
108
119
  const todoHits = [];
109
- for (let i = 0; i < slice.length; i++) {
120
+ for (let i = 0; i < copied; i++) {
110
121
  const owner = todoOwners[scroll + i];
111
122
  if (owner === undefined || owner === null) continue;
112
123
  todoHits.push({ y: headerLines + i + 1, cursor: owner });
@@ -116,9 +127,9 @@ const windowBody = (view, width, color, bodyH, headerLines, paintedBody) => {
116
127
  shift,
117
128
  scroll,
118
129
  headerLines,
119
- slice.length,
130
+ copied,
120
131
  );
121
- while (slice.length < bodyH) slice.push(filler);
132
+ for (let i = copied; i < bodyH; i++) slice[i] = filler;
122
133
  const listScroll = paintedBody.listScroll ?? 0;
123
134
  const fileHits = paintedBody.fileHits;
124
135
  return { slice, scroll, scrollMax, listScroll, todoHits, fileHits, cursor };
@@ -36,10 +36,11 @@ const wrapTodoItem = (text, inner) => {
36
36
  const bodyWidth = Math.max(1, inner - lead);
37
37
  const hang = ' '.repeat(lead);
38
38
  const wrapped = wrapMultiline(body, bodyWidth);
39
- const lines = [];
40
- for (let i = 0; i < wrapped.length; i++) {
39
+ const { length } = wrapped;
40
+ const lines = new Array(length);
41
+ for (let i = 0; i < length; i++) {
41
42
  const prefix = i === 0 ? mark : hang;
42
- lines.push(`${prefix}${wrapped[i]}`);
43
+ lines[i] = `${prefix}${wrapped[i]}`;
43
44
  }
44
45
  return lines;
45
46
  };
@@ -180,7 +180,52 @@ const paneResult = (fields) => ({
180
180
  listScroll: fields.listScroll ?? 0,
181
181
  });
182
182
 
183
+ const measureColumns = (entries, rowFields, align) => {
184
+ const keys = Object.keys(align);
185
+ const widths = Object.fromEntries(keys.map((key) => [key, 0]));
186
+ for (const entry of entries) {
187
+ const fields = rowFields(entry);
188
+ for (const key of keys) {
189
+ widths[key] = Math.max(widths[key], visibleWidth(fields[key]));
190
+ }
191
+ }
192
+ return widths;
193
+ };
194
+
195
+ const padColumns = (fields, widths, align) => {
196
+ const parts = {};
197
+ for (const key of Object.keys(align)) {
198
+ const width = widths?.[key] ?? visibleWidth(fields[key]);
199
+ parts[key] = padVisible(fields[key], width, align[key]);
200
+ }
201
+ return parts;
202
+ };
203
+
204
+ const columnGap = (key) => (key === 'date' ? ' ' : ' ');
205
+
206
+ const joinColumns = (parts, widths) => {
207
+ let text = '';
208
+ for (const key of Object.keys(parts)) {
209
+ if (widths[key]) text += columnGap(key) + parts[key];
210
+ }
211
+ return text;
212
+ };
213
+
214
+ const paintColumns = (parts, widths, fgRgb, bgRgb, color, tones) => {
215
+ let text = '';
216
+ for (const key of Object.keys(parts)) {
217
+ if (!widths[key]) continue;
218
+ text += paint(columnGap(key), fgRgb, bgRgb, color);
219
+ text += paint(parts[key], tones[key] ?? fgRgb, bgRgb, color);
220
+ }
221
+ return text;
222
+ };
223
+
183
224
  module.exports = {
225
+ measureColumns,
226
+ padColumns,
227
+ joinColumns,
228
+ paintColumns,
184
229
  GUTTER_W,
185
230
  FILE_MARK,
186
231
  LIST_PANES,
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const chrome = require('./render/chrome.js');
4
- const notes = require('./render/notes.js');
5
- const diff = require('./render/diff.js');
6
- const present = require('./render/present.js');
7
- const { renderFrame } = require('./render/frame.js');
3
+ const chrome = require('./chrome.js');
4
+ const notes = require('./notes.js');
5
+ const diff = require('./diff.js');
6
+ const present = require('./present.js');
7
+ const { renderFrame } = require('./frame.js');
8
8
 
9
9
  const { QUIT_PROMPT, PUSH_PROMPT } = chrome;
10
10
  const { updatePrompt, dropPrompt } = chrome;
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ const { detectLang } = require('../detect.js');
4
+ const primitives = require('./primitives.js');
5
+ const diff = require('./diff.js');
6
+
7
+ const { paneResult } = primitives;
8
+ const { paintDiffLine, codeCursorInRows } = diff;
9
+ const { sameHunk } = require('../diff/diff.js');
10
+
11
+ const paintBodyUnit = (view, width, color) => {
12
+ const lines = view.unitLines ?? [];
13
+ const item = view.item;
14
+ const lang = detectLang(view.reviewPath ?? '');
15
+ const overlay = view.codeOverlay;
16
+ const editing = view.mode === 'compose' && overlay;
17
+ const { length } = lines;
18
+ const body = new Array(length);
19
+ for (let i = 0; i < length; i++) {
20
+ const line = lines[i];
21
+ const isBlock = item && sameHunk(line.item, item);
22
+ const row = { ...line };
23
+ if (editing) {
24
+ row.mark = ' ';
25
+ } else if ((row.type === 'add' || row.type === 'del') && !isBlock) {
26
+ row.mark = ' ';
27
+ }
28
+ body[i] = paintDiffLine(row, width, color, row.origin, lang);
29
+ }
30
+ const edit = overlay ? overlay.cursor : null;
31
+ const cursor = codeCursorInRows(lines, false, width, edit);
32
+ return paneResult({
33
+ body,
34
+ fileHits: [],
35
+ todoOwners: [],
36
+ cursor,
37
+ splitBody: false,
38
+ });
39
+ };
40
+
41
+ module.exports = { paintBodyUnit };