reslop 0.1.8 → 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 (95) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/README.md +27 -38
  3. package/bin/reslop.js +2 -2
  4. package/lib/ansi.js +6 -2
  5. package/lib/api.js +2 -2
  6. package/lib/capabilities.js +10 -14
  7. package/lib/cli.js +28 -47
  8. package/lib/clipboard.js +1 -1
  9. package/lib/deps-diff.js +712 -0
  10. package/lib/deps.js +556 -32
  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 +34 -8
  20. package/lib/{git/branches.js → git-branches.js} +103 -10
  21. package/lib/{git/dependencies.js → git-deps.js} +111 -38
  22. package/lib/git.js +592 -65
  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 +22 -9
  30. package/lib/manifest.js +330 -0
  31. package/lib/remote.js +374 -0
  32. package/lib/render/branches.js +34 -66
  33. package/lib/render/chrome.js +57 -22
  34. package/lib/render/commits.js +85 -0
  35. package/lib/render/diff.js +16 -16
  36. package/lib/render/files.js +29 -37
  37. package/lib/render/frame.js +29 -13
  38. package/lib/render/notes.js +4 -3
  39. package/lib/render/primitives.js +46 -1
  40. package/lib/{render.js → render/render.js} +6 -7
  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 +93 -36
  46. package/lib/session/branches.js +130 -144
  47. package/lib/session/change-actions.js +206 -124
  48. package/lib/session/commit.js +217 -62
  49. package/lib/session/compose.js +158 -13
  50. package/lib/session/dispatch.js +109 -55
  51. package/lib/session/files-pane.js +145 -142
  52. package/lib/session/items.js +131 -34
  53. package/lib/session/lifecycle.js +82 -42
  54. package/lib/session/load.js +19 -33
  55. package/lib/session/navigation.js +15 -3
  56. package/lib/session/ops.js +28 -41
  57. package/lib/session/pointer.js +58 -54
  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 +82 -11
  64. package/lib/session/watch.js +11 -19
  65. package/lib/session.js +29 -226
  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 +2 -2
  71. package/lib/deps/changes.js +0 -331
  72. package/lib/deps/items.js +0 -131
  73. package/lib/deps/manifest.js +0 -145
  74. package/lib/deps/merge.js +0 -120
  75. package/lib/deps/proposals.js +0 -164
  76. package/lib/deps/reports.js +0 -180
  77. package/lib/deps/rows.js +0 -177
  78. package/lib/deps/scan.js +0 -138
  79. package/lib/deps/usage.js +0 -68
  80. package/lib/deps/versions.js +0 -119
  81. package/lib/diff.js +0 -33
  82. package/lib/git/changes.js +0 -164
  83. package/lib/git/read.js +0 -253
  84. package/lib/git/run.js +0 -100
  85. package/lib/npm/commands.js +0 -100
  86. package/lib/remote/changes.js +0 -37
  87. package/lib/remote/headers.js +0 -36
  88. package/lib/remote/http.js +0 -122
  89. package/lib/remote/notes.js +0 -94
  90. package/lib/review/format.js +0 -434
  91. package/lib/review/storage.js +0 -150
  92. package/lib/review/store.js +0 -178
  93. package/lib/review/templates.js +0 -49
  94. package/lib/session/origins.js +0 -7
  95. package/lib/sys.js +0 -84
package/lib/diff/align.js CHANGED
@@ -189,9 +189,6 @@ const mapHunkBlocks = (lines, onCtx, onChange) => {
189
189
  return out;
190
190
  };
191
191
 
192
- const alignPairs = (lines) =>
193
- mapHunkBlocks(lines, (line) => line, alignChangeBlock);
194
-
195
192
  const alignSplitBlock = (dels, adds, delToAdd) => {
196
193
  const out = [];
197
194
  walkAligned(dels, adds, delToAdd, {
@@ -208,14 +205,14 @@ const alignSplitBlock = (dels, adds, delToAdd) => {
208
205
  return out;
209
206
  };
210
207
 
211
- const splitCtxRow = (line) => ({ left: line, right: line });
212
-
213
- const alignSplit = (lines) =>
214
- mapHunkBlocks(lines, splitCtxRow, alignSplitBlock);
215
-
216
208
  const LAYOUT_ALIGN = {
217
- mixed: alignPairs,
218
- side: alignSplit,
209
+ mixed: (lines) => mapHunkBlocks(lines, (line) => line, alignChangeBlock),
210
+ side: (lines) =>
211
+ mapHunkBlocks(
212
+ lines,
213
+ (line) => ({ left: line, right: line }),
214
+ alignSplitBlock,
215
+ ),
219
216
  };
220
217
 
221
218
  module.exports = {
@@ -0,0 +1,53 @@
1
+ 'use strict';
2
+
3
+ const { diffChars } = require('./inline.js');
4
+ const { isCtxType, pairIndices, attachInline } = require('./align.js');
5
+ const { parseDiff } = require('./parse.js');
6
+ const patch = require('./patch.js');
7
+ const { splitHunk, flattenBlock, countSides, formatPatch } = patch;
8
+ const display = require('./display.js');
9
+ const { DISPLAY_CONTEXT, displayLines, blockAddText } = display;
10
+ const edit = require('./edit.js');
11
+ const { addBlockRange, replaceBlockAdds } = edit;
12
+ const items = require('./items.js');
13
+ const { synthesizeNewFile, itemsFromFiles, refreshIndexPatches } = items;
14
+ const unit = require('./unit.js');
15
+ const { unitLines, itemsForPath, blockLineRange, blockLineIndex } = unit;
16
+ const { hunkLineRange, hunkKey, sameHunk, splitEditor } = unit;
17
+ const { fileOffset, fileLines, sameBlock, splitFile } = unit;
18
+ const { fileLineIndex, attachEditSpans, mergeEditRows } = unit;
19
+
20
+ module.exports = {
21
+ DISPLAY_CONTEXT,
22
+ isCtxType,
23
+ diffChars,
24
+ pairIndices,
25
+ attachInline,
26
+ parseDiff,
27
+ splitHunk,
28
+ flattenBlock,
29
+ countSides,
30
+ formatPatch,
31
+ displayLines,
32
+ blockAddText,
33
+ addBlockRange,
34
+ replaceBlockAdds,
35
+ synthesizeNewFile,
36
+ itemsFromFiles,
37
+ refreshIndexPatches,
38
+ splitFile,
39
+ splitEditor,
40
+ itemsForPath,
41
+ unitLines,
42
+ sameBlock,
43
+ sameHunk,
44
+ hunkKey,
45
+ blockLineRange,
46
+ hunkLineRange,
47
+ blockLineIndex,
48
+ fileOffset,
49
+ fileLines,
50
+ fileLineIndex,
51
+ attachEditSpans,
52
+ mergeEditRows,
53
+ };
@@ -46,14 +46,15 @@ const replaceAdds = (lines, overlay, blockId) => {
46
46
  return lines.filter((line) => line.type !== 'add');
47
47
  }
48
48
  const texts = text.split('\n');
49
- const tagged = [];
49
+ const { length } = texts;
50
+ const tagged = new Array(length);
50
51
  let offset = 0;
51
- for (let i = 0; i < texts.length; i++) {
52
+ for (let i = 0; i < length; i++) {
52
53
  const row = texts[i];
53
54
  const start = offset;
54
55
  const end = start + row.length;
55
- const isLast = i === texts.length - 1;
56
- tagged.push(taggedAdd(row, blockId, start, end));
56
+ const isLast = i === length - 1;
57
+ tagged[i] = taggedAdd(row, blockId, start, end);
57
58
  offset = isLast ? end : end + 1;
58
59
  }
59
60
  const out = [];
@@ -72,13 +72,11 @@ const tokenSimilarity = (a, b) => {
72
72
  return (2 * lcs) / (a.length + b.length);
73
73
  };
74
74
 
75
- const similarity = (left, right) =>
76
- tokenSimilarity(graphemes(left), graphemes(right));
77
-
78
75
  const fillLcsTable = (a, b) => {
79
76
  const n = a.length;
80
77
  const m = b.length;
81
- const dp = Array.from({ length: n + 1 }, () => new Uint32Array(m + 1));
78
+ const dp = new Array(n + 1);
79
+ for (let i = 0; i <= n; i++) dp[i] = new Uint32Array(m + 1);
82
80
  for (let i = 1; i <= n; i++) {
83
81
  const ai = a[i - 1];
84
82
  const row = dp[i];
@@ -92,30 +90,31 @@ const fillLcsTable = (a, b) => {
92
90
  };
93
91
 
94
92
  const tracebackLcs = (a, b, dp) => {
95
- const rev = [];
93
+ const rev = new Array(a.length + b.length - dp[a.length][b.length]);
94
+ let n = 0;
96
95
  let i = a.length;
97
96
  let j = b.length;
98
97
  while (i > 0 && j > 0) {
99
98
  if (a[i - 1] === b[j - 1]) {
100
- rev.push({ type: 'eq', text: a[i - 1] });
99
+ rev[n++] = { type: 'eq', text: a[i - 1] };
101
100
  i -= 1;
102
101
  j -= 1;
103
102
  continue;
104
103
  }
105
104
  if (dp[i - 1][j] >= dp[i][j - 1]) {
106
- rev.push({ type: 'del', text: a[i - 1] });
105
+ rev[n++] = { type: 'del', text: a[i - 1] };
107
106
  i -= 1;
108
107
  continue;
109
108
  }
110
- rev.push({ type: 'add', text: b[j - 1] });
109
+ rev[n++] = { type: 'add', text: b[j - 1] };
111
110
  j -= 1;
112
111
  }
113
112
  while (i > 0) {
114
- rev.push({ type: 'del', text: a[i - 1] });
113
+ rev[n++] = { type: 'del', text: a[i - 1] };
115
114
  i -= 1;
116
115
  }
117
116
  while (j > 0) {
118
- rev.push({ type: 'add', text: b[j - 1] });
117
+ rev[n++] = { type: 'add', text: b[j - 1] };
119
118
  j -= 1;
120
119
  }
121
120
  rev.reverse();
@@ -174,13 +173,17 @@ const diffTokenMiddle = (midA, midB) => {
174
173
  };
175
174
 
176
175
  const joinAffix = (prefix, mid, suffix) => {
177
- const parts = [];
178
- if (prefix.length) {
179
- parts.push({ text: prefix.join(''), changed: false });
176
+ const hasPrefix = prefix.length > 0;
177
+ const hasSuffix = suffix.length > 0;
178
+ const extra = (hasPrefix ? 1 : 0) + (hasSuffix ? 1 : 0);
179
+ const parts = new Array(mid.length + extra);
180
+ const at = hasPrefix ? 1 : 0;
181
+ if (hasPrefix) {
182
+ parts[0] = { text: prefix.join(''), changed: false };
180
183
  }
181
- for (const span of mid) parts.push(span);
182
- if (suffix.length) {
183
- parts.push({ text: suffix.join(''), changed: false });
184
+ for (let i = 0; i < mid.length; i++) parts[at + i] = mid[i];
185
+ if (hasSuffix) {
186
+ parts[at + mid.length] = { text: suffix.join(''), changed: false };
184
187
  }
185
188
  return collapseSpans(parts);
186
189
  };
@@ -253,7 +256,10 @@ const spansFromWordOps = (ops) => {
253
256
  i += 1;
254
257
  }
255
258
  const one = dels.length === 1 && adds.length === 1;
256
- const close = one && similarity(dels[0], adds[0]) >= WORD_REFINE_MIN;
259
+ const close =
260
+ one &&
261
+ tokenSimilarity(graphemes(dels[0]), graphemes(adds[0])) >=
262
+ WORD_REFINE_MIN;
257
263
  if (close) {
258
264
  const inner = diffTokens(graphemes(dels[0]), graphemes(adds[0]));
259
265
  for (const span of inner.oldSpans) oldParts.push(span);
@@ -291,10 +297,9 @@ const diffChars = (oldText, newText) => {
291
297
  } else {
292
298
  mid = spansFromWordOps(lcsOps(midA, midB));
293
299
  }
294
- return {
295
- oldSpans: joinAffix(prefix, mid.oldSpans, suffix),
296
- newSpans: joinAffix(prefix, mid.newSpans, suffix),
297
- };
300
+ const oldSpans = joinAffix(prefix, mid.oldSpans, suffix);
301
+ const newSpans = joinAffix(prefix, mid.newSpans, suffix);
302
+ return { oldSpans, newSpans };
298
303
  };
299
304
 
300
305
  module.exports = { tokenSimilarity, diffChars };
package/lib/diff/items.js CHANGED
@@ -82,14 +82,16 @@ const refreshIndexPatches = (items, item) => {
82
82
  if (other.hunk !== item.hunk || other.file !== item.file) continue;
83
83
  sides[other.blockId] = INDEX_SIDE[other.origin] ?? 'old';
84
84
  }
85
- const next = [];
86
- for (const other of items) {
85
+ const { length } = items;
86
+ const next = new Array(length);
87
+ for (let i = 0; i < length; i++) {
88
+ const other = items[i];
87
89
  if (other.hunk !== item.hunk || other.file !== item.file) {
88
- next.push(other);
90
+ next[i] = other;
89
91
  continue;
90
92
  }
91
93
  const patchAdd = formatPatch(other.file, other.hunk, other.blockId, sides);
92
- next.push({ ...other, patchAdd });
94
+ next[i] = { ...other, patchAdd };
93
95
  }
94
96
  return next;
95
97
  };
package/lib/diff/patch.js CHANGED
@@ -70,10 +70,8 @@ const countSides = (lines) => {
70
70
  const formatHunkHeader = (oldStart, oldCount, newStart, newCount) =>
71
71
  `@@ -${oldStart},${oldCount} +${newStart},${newCount} @@`;
72
72
 
73
- const lineMark = (type) => LINE_MARK[type] ?? ' ';
74
-
75
73
  const formatBodyLine = (line) => {
76
- const rows = [`${lineMark(line.type)}${line.text}`];
74
+ const rows = [`${LINE_MARK[line.type] ?? ' '}${line.text}`];
77
75
  if (line.noNl) rows.push('\');
78
76
  return rows;
79
77
  };
@@ -0,0 +1,392 @@
1
+ 'use strict';
2
+
3
+ const { attachInline } = require('./align.js');
4
+ const { listPath } = require('../files.js');
5
+
6
+ const splitEditor = (text) => {
7
+ const lines = `${text}`.split('\n');
8
+ if (!lines.length) return [''];
9
+ return lines;
10
+ };
11
+
12
+ const splitFile = (text) => {
13
+ if (!text) return [];
14
+ const lines = `${text}`.split('\n');
15
+ if (lines.length && lines[lines.length - 1] === '') lines.pop();
16
+ return lines;
17
+ };
18
+
19
+ const hunkKey = (item) => {
20
+ if (!item) return '';
21
+ const rel = listPath(item);
22
+ const hunk = item.hunk;
23
+ if (!hunk) return `${rel}\0${item.blockId ?? ''}`;
24
+ return `${rel}\0${hunk.oldStart}\0${hunk.newStart}\0${hunk.header}`;
25
+ };
26
+
27
+ const sameHunk = (left, right) => {
28
+ if (!left || !right) return false;
29
+ if (left.hunk && right.hunk && left.hunk === right.hunk) {
30
+ return listPath(left) === listPath(right);
31
+ }
32
+ const key = hunkKey(left);
33
+ if (!key) return false;
34
+ return key === hunkKey(right);
35
+ };
36
+
37
+ const emptyMarks = (last) => {
38
+ const size = last + 1;
39
+ const dels = new Array(size);
40
+ const adds = new Array(size);
41
+ for (let i = 0; i < size; i++) {
42
+ dels[i] = [];
43
+ adds[i] = null;
44
+ }
45
+ return { dels, adds };
46
+ };
47
+
48
+ const clipAt = (n, last) => Math.min(Math.max(1, n), last);
49
+
50
+ const applyHunk = (marks, item) => {
51
+ const hunk = item.hunk;
52
+ if (!hunk || !hunk.lines) return;
53
+ const last = marks.dels.length - 1;
54
+ let newLine = hunk.newStart > 0 ? hunk.newStart : 1;
55
+ for (const line of hunk.lines) {
56
+ const extra = {
57
+ text: line.text ?? '',
58
+ item,
59
+ blockId: line.blockId,
60
+ origin: item.origin,
61
+ };
62
+ if (line.type === 'del') {
63
+ marks.dels[clipAt(newLine, last)].push(extra);
64
+ continue;
65
+ }
66
+ if (line.type === 'add') {
67
+ marks.adds[clipAt(newLine, last)] = extra;
68
+ newLine += 1;
69
+ continue;
70
+ }
71
+ newLine += 1;
72
+ }
73
+ };
74
+
75
+ const tagged = (type, text, extra = {}) => ({
76
+ type,
77
+ text,
78
+ noNl: false,
79
+ blockId: extra.blockId ?? null,
80
+ origin: extra.origin ?? '',
81
+ item: extra.item ?? null,
82
+ });
83
+
84
+ const itemsForPath = (items, rel) => {
85
+ const out = [];
86
+ for (const item of items) {
87
+ if (listPath(item) === rel) out.push(item);
88
+ }
89
+ return out;
90
+ };
91
+
92
+ const rowsFromAdds = (items) => {
93
+ const rows = [];
94
+ for (const item of items) {
95
+ const lines = item.hunk && item.hunk.lines ? item.hunk.lines : [];
96
+ for (const line of lines) {
97
+ if (line.type === 'add') rows.push(line.text ?? '');
98
+ }
99
+ }
100
+ return rows;
101
+ };
102
+
103
+ const unitLines = (text, items) => {
104
+ let rows = splitFile(text);
105
+ if (!rows.length) rows = rowsFromAdds(items);
106
+ const marks = emptyMarks(rows.length + 1);
107
+ for (const item of items) applyHunk(marks, item);
108
+ const out = [];
109
+ for (let i = 0; i < rows.length; i++) {
110
+ const n = i + 1;
111
+ for (const del of marks.dels[n]) out.push(tagged('del', del.text, del));
112
+ const add = marks.adds[n];
113
+ if (add) out.push(tagged('add', rows[i], add));
114
+ else out.push(tagged('ctx', rows[i], {}));
115
+ }
116
+ for (const del of marks.dels[rows.length + 1]) {
117
+ out.push(tagged('del', del.text, del));
118
+ }
119
+ if (!out.length) out.push(tagged('ctx', '', {}));
120
+ return attachInline(out);
121
+ };
122
+
123
+ const sameBlock = (line, item) => {
124
+ if (!line || !item) return false;
125
+ if (line.item !== item) return false;
126
+ return line.blockId === item.blockId;
127
+ };
128
+
129
+ const blockLineRange = (lines, item) => {
130
+ let start = 0;
131
+ let end = 0;
132
+ let found = false;
133
+ if (!item) return { start, end };
134
+ for (let i = 0; i < lines.length; i++) {
135
+ const line = lines[i];
136
+ if (!sameBlock(line, item)) continue;
137
+ if (line.type === 'ctx') continue;
138
+ if (!found) {
139
+ start = i;
140
+ found = true;
141
+ }
142
+ end = i;
143
+ }
144
+ return { start, end };
145
+ };
146
+
147
+ const blockLineIndex = (lines, item) => blockLineRange(lines, item).start;
148
+
149
+ const hunkLineRange = (lines, item) => {
150
+ let start = 0;
151
+ let end = 0;
152
+ let found = false;
153
+ if (!item) return { start, end };
154
+ for (let i = 0; i < lines.length; i++) {
155
+ const line = lines[i];
156
+ if (!sameHunk(line.item, item)) continue;
157
+ if (line.type === 'ctx') continue;
158
+ if (!found) {
159
+ start = i;
160
+ found = true;
161
+ }
162
+ end = i;
163
+ }
164
+ return { start, end };
165
+ };
166
+
167
+ const fileOffset = (lines, at) => {
168
+ let offset = 0;
169
+ const target = Math.max(0, at);
170
+ for (let i = 0; i < lines.length; i++) {
171
+ const line = lines[i];
172
+ if (line.type === 'del') continue;
173
+ if (i >= target) return offset;
174
+ offset += (line.text ?? '').length + 1;
175
+ }
176
+ return offset;
177
+ };
178
+
179
+ const fileLineIndex = (lines, fileLine) => {
180
+ let n = 0;
181
+ let last = 0;
182
+ for (let i = 0; i < lines.length; i++) {
183
+ if (lines[i].type === 'del') continue;
184
+ last = i;
185
+ if (n === fileLine) return i;
186
+ n += 1;
187
+ }
188
+ return last;
189
+ };
190
+
191
+ const lastEditableIndex = (lines) => {
192
+ for (let i = lines.length - 1; i >= 0; i--) {
193
+ if (lines[i].type !== 'del') return i;
194
+ }
195
+ return -1;
196
+ };
197
+
198
+ const attachEditSpans = (lines) => {
199
+ let offset = 0;
200
+ const lastAt = lastEditableIndex(lines);
201
+ for (let i = 0; i < lines.length; i++) {
202
+ const line = lines[i];
203
+ if (line.type === 'del') continue;
204
+ const text = line.text ?? '';
205
+ const last = i === lastAt;
206
+ line.wrap = true;
207
+ line.editStart = offset;
208
+ line.editEnd = offset + text.length;
209
+ line.editLast = last;
210
+ offset = last ? offset + text.length : offset + text.length + 1;
211
+ }
212
+ return lines;
213
+ };
214
+
215
+ const LCS_CELLS = 4000000;
216
+
217
+ const lcsTable = (prev, next) => {
218
+ const n = prev.length;
219
+ const m = next.length;
220
+ const dp = new Array(n + 1);
221
+ for (let i = 0; i <= n; i++) dp[i] = new Array(m + 1).fill(0);
222
+ for (let i = n - 1; i >= 0; i--) {
223
+ for (let j = m - 1; j >= 0; j--) {
224
+ if (prev[i] === next[j]) dp[i][j] = dp[i + 1][j + 1] + 1;
225
+ else dp[i][j] = Math.max(dp[i + 1][j], dp[i][j + 1]);
226
+ }
227
+ }
228
+ return dp;
229
+ };
230
+
231
+ const zipOps = (prev, next) => {
232
+ const n = Math.min(prev.length, next.length);
233
+ const ops = new Array(Math.max(prev.length, next.length));
234
+ let at = 0;
235
+ for (let i = 0; i < n; i++) ops[at++] = { kind: 'rep', text: next[i] };
236
+ for (let i = n; i < prev.length; i++) ops[at++] = { kind: 'del' };
237
+ for (let i = n; i < next.length; i++) {
238
+ ops[at++] = { kind: 'ins', text: next[i] };
239
+ }
240
+ return ops;
241
+ };
242
+
243
+ const walkLcs = (prev, next, dp) => {
244
+ const ops = new Array(prev.length + next.length - dp[0][0]);
245
+ let n = 0;
246
+ let i = 0;
247
+ let j = 0;
248
+ while (i < prev.length && j < next.length) {
249
+ if (prev[i] === next[j]) {
250
+ ops[n++] = { kind: 'eq', text: next[j] };
251
+ i += 1;
252
+ j += 1;
253
+ continue;
254
+ }
255
+ if (dp[i + 1][j] >= dp[i][j + 1]) {
256
+ ops[n++] = { kind: 'del' };
257
+ i += 1;
258
+ continue;
259
+ }
260
+ ops[n++] = { kind: 'ins', text: next[j] };
261
+ j += 1;
262
+ }
263
+ while (i < prev.length) {
264
+ ops[n++] = { kind: 'del' };
265
+ i += 1;
266
+ }
267
+ while (j < next.length) {
268
+ ops[n++] = { kind: 'ins', text: next[j] };
269
+ j += 1;
270
+ }
271
+ return ops;
272
+ };
273
+
274
+ const collapseRep = (ops) => {
275
+ const out = [];
276
+ for (let i = 0; i < ops.length; i++) {
277
+ const op = ops[i];
278
+ const next = ops[i + 1];
279
+ if (op.kind === 'del' && next && next.kind === 'ins') {
280
+ out.push({ kind: 'rep', text: next.text });
281
+ i += 1;
282
+ continue;
283
+ }
284
+ if (op.kind === 'ins' && next && next.kind === 'del') {
285
+ out.push({ kind: 'rep', text: op.text });
286
+ i += 1;
287
+ continue;
288
+ }
289
+ out.push(op);
290
+ }
291
+ return out;
292
+ };
293
+
294
+ const lineOps = (prev, next) => {
295
+ if (prev.length === next.length) return zipOps(prev, next);
296
+ if (prev.length * next.length > LCS_CELLS) return zipOps(prev, next);
297
+ return collapseRep(walkLcs(prev, next, lcsTable(prev, next)));
298
+ };
299
+
300
+ const copyRow = (row, text) => ({
301
+ type: row.type,
302
+ text,
303
+ noNl: row.noNl === true,
304
+ blockId: row.blockId ?? null,
305
+ origin: row.origin ?? '',
306
+ item: row.item ?? null,
307
+ });
308
+
309
+ const pushDels = (out, base, at) => {
310
+ let i = at;
311
+ while (i < base.length && base[i].type === 'del') {
312
+ out.push(copyRow(base[i], base[i].text ?? ''));
313
+ i += 1;
314
+ }
315
+ return i;
316
+ };
317
+
318
+ const mergeEditRows = (baseRows, nextTexts) => {
319
+ const base = baseRows ?? [];
320
+ const texts = nextTexts && nextTexts.length ? nextTexts : [''];
321
+ const prev = [];
322
+ for (const row of base) {
323
+ if (row.type === 'del') continue;
324
+ prev.push(row.text ?? '');
325
+ }
326
+ const ops = lineOps(prev, texts);
327
+ const out = [];
328
+ let at = 0;
329
+ for (const op of ops) {
330
+ at = pushDels(out, base, at);
331
+ if (op.kind === 'ins') {
332
+ out.push(tagged('ctx', op.text, {}));
333
+ continue;
334
+ }
335
+ if (at >= base.length) continue;
336
+ if (op.kind === 'del') {
337
+ at += 1;
338
+ continue;
339
+ }
340
+ out.push(copyRow(base[at], op.text));
341
+ at += 1;
342
+ }
343
+ pushDels(out, base, at);
344
+ if (!out.length) out.push(tagged('ctx', '', {}));
345
+ return attachEditSpans(attachInline(out));
346
+ };
347
+
348
+ const fileLines = (text) => {
349
+ const rows = splitFile(text);
350
+ if (!rows.length) rows.push('');
351
+ const { length } = rows;
352
+ const out = new Array(length);
353
+ let offset = 0;
354
+ for (let i = 0; i < length; i++) {
355
+ const row = rows[i];
356
+ const start = offset;
357
+ const end = start + row.length;
358
+ const last = i === length - 1;
359
+ out[i] = {
360
+ type: 'ctx',
361
+ text: row,
362
+ noNl: false,
363
+ wrap: true,
364
+ editStart: start,
365
+ editEnd: end,
366
+ editLast: last,
367
+ blockId: null,
368
+ origin: '',
369
+ item: null,
370
+ };
371
+ offset = last ? end : end + 1;
372
+ }
373
+ return out;
374
+ };
375
+
376
+ module.exports = {
377
+ splitFile,
378
+ splitEditor,
379
+ itemsForPath,
380
+ unitLines,
381
+ sameBlock,
382
+ sameHunk,
383
+ hunkKey,
384
+ blockLineRange,
385
+ hunkLineRange,
386
+ blockLineIndex,
387
+ fileOffset,
388
+ fileLineIndex,
389
+ attachEditSpans,
390
+ mergeEditRows,
391
+ fileLines,
392
+ };