pi-hashline-edit-pro 0.9.8 → 0.11.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.
@@ -1,26 +1,26 @@
1
- import { throwIfAborted } from "../runtime";
2
- import { computeLineHashes } from "./hash";
1
+ import { abortIf } from "../runtime";
2
+ import { lineHashes } from "./hash";
3
3
  import {
4
- validateAnchorEdits,
5
- assertNoBareHashPrefixLines,
6
- maybeWarnSuspiciousUnicodeEscapePlaceholder,
7
- formatMismatchError,
8
- describeEdit,
9
- type ResolvedHashlineEdit,
10
- type NoopEdit,
11
- type HashlineEdit,
12
- type BoundaryDuplicationWarning,
4
+ valEdits,
5
+ assertNoBarePrefix,
6
+ warnUnicodeEsc,
7
+ fmtMismatch,
8
+ descEdit,
9
+ type RHEdit,
10
+ type NEdit,
11
+ type HEdit,
12
+ type BDupWarn,
13
13
  } from "./resolve";
14
- import { countVisibleLines } from "../utils";
15
- import { ANCHOR_CONTEXT_LINES, ANCHOR_MAX_OUTPUT_LINES } from "../constants";
14
+ import { cntLines } from "../utils";
15
+ import { CTX_LINES, MAX_OUT } from "../constants";
16
16
 
17
- type LineIndex = {
17
+ type LIdx = {
18
18
  fileLines: string[];
19
19
  lineStarts: number[];
20
20
  hasTerminalNewline: boolean;
21
21
  };
22
22
 
23
- export function buildLineIndex(content: string): LineIndex {
23
+ export function buildIdx(content: string): LIdx {
24
24
  const fileLines = content.split("\n");
25
25
  const lineStarts: number[] = [];
26
26
  let offset = 0;
@@ -40,8 +40,7 @@ export function buildLineIndex(content: string): LineIndex {
40
40
  };
41
41
  }
42
42
 
43
-
44
- type ResolvedEditSpan = {
43
+ type RESpan = {
45
44
  kind: "replace";
46
45
  index: number;
47
46
  label: string;
@@ -50,7 +49,7 @@ type ResolvedEditSpan = {
50
49
  replacement: string;
51
50
  };
52
51
 
53
- function assertDoesNotEmptyFile(originalContent: string, result: string): void {
52
+ function assertNotEmpty(originalContent: string, result: string): void {
54
53
  if (originalContent.length > 0 && result.length === 0) {
55
54
  throw new Error(
56
55
  "[E_WOULD_EMPTY] Cannot empty a non-empty file via edit."
@@ -58,8 +57,7 @@ function assertDoesNotEmptyFile(originalContent: string, result: string): void {
58
57
  }
59
58
  }
60
59
 
61
-
62
- function throwEditConflict(
60
+ function throwConflict(
63
61
  left: { index: number; label: string },
64
62
  right: { index: number; label: string },
65
63
  reason: string,
@@ -69,14 +67,13 @@ function throwEditConflict(
69
67
  );
70
68
  }
71
69
 
72
-
73
- function resolveEditToSpan(
74
- edit: ResolvedHashlineEdit,
70
+ function resToSpan(
71
+ edit: RHEdit,
75
72
  index: number,
76
73
  content: string,
77
- lineIndex: LineIndex,
78
- noopEdits: NoopEdit[],
79
- ): ResolvedEditSpan | null {
74
+ lineIndex: LIdx,
75
+ noopEdits: NEdit[],
76
+ ): RESpan | null {
80
77
  const { fileLines, lineStarts, hasTerminalNewline } = lineIndex;
81
78
 
82
79
  const startLine = edit.old_range[0].line;
@@ -100,7 +97,7 @@ function resolveEditToSpan(
100
97
  return {
101
98
  kind: "replace",
102
99
  index,
103
- label: describeEdit(edit),
100
+ label: descEdit(edit),
104
101
  start: lineStarts[startLine - 1]!,
105
102
  end: lineStarts[endLine - 1]! + fileLines[endLine - 1]!.length,
106
103
  replacement: edit.new_lines.join("\n"),
@@ -111,7 +108,7 @@ function resolveEditToSpan(
111
108
  return {
112
109
  kind: "replace",
113
110
  index,
114
- label: describeEdit(edit),
111
+ label: descEdit(edit),
115
112
  start: 0,
116
113
  end: content.length,
117
114
  replacement: "",
@@ -122,7 +119,7 @@ function resolveEditToSpan(
122
119
  return {
123
120
  kind: "replace",
124
121
  index,
125
- label: describeEdit(edit),
122
+ label: descEdit(edit),
126
123
  start: lineStarts[startLine - 1]!,
127
124
  end: lineStarts[endLine]!,
128
125
  replacement: "",
@@ -132,14 +129,14 @@ function resolveEditToSpan(
132
129
  return {
133
130
  kind: "replace",
134
131
  index,
135
- label: describeEdit(edit),
132
+ label: descEdit(edit),
136
133
  start: Math.max(0, lineStarts[startLine - 1]! - 1),
137
134
  end: lineStarts[endLine - 1]! + fileLines[endLine - 1]!.length,
138
135
  replacement: "",
139
136
  };
140
137
  }
141
138
 
142
- function assertNoConflictingSpans(spans: ResolvedEditSpan[]): void {
139
+ function assertNoConflict(spans: RESpan[]): void {
143
140
  for (let leftIndex = 0; leftIndex < spans.length; leftIndex++) {
144
141
  const left = spans[leftIndex]!;
145
142
  for (
@@ -150,7 +147,7 @@ function assertNoConflictingSpans(spans: ResolvedEditSpan[]): void {
150
147
  const right = spans[rightIndex]!;
151
148
 
152
149
  if (left.start < right.end && right.start < left.end) {
153
- throwEditConflict(
150
+ throwConflict(
154
151
  left,
155
152
  right,
156
153
  "overlap on the same original line range",
@@ -160,18 +157,18 @@ function assertNoConflictingSpans(spans: ResolvedEditSpan[]): void {
160
157
  }
161
158
  }
162
159
 
163
- function resolveEditSpans(
164
- edits: ResolvedHashlineEdit[],
160
+ function resSpans(
161
+ edits: RHEdit[],
165
162
  content: string,
166
- lineIndex: LineIndex,
167
- noopEdits: NoopEdit[],
163
+ lineIndex: LIdx,
164
+ noopEdits: NEdit[],
168
165
  signal: AbortSignal | undefined,
169
- ): ResolvedEditSpan[] {
166
+ ): RESpan[] {
170
167
  const seenSpanKeys = new Set<string>();
171
- const resolvedSpans: ResolvedEditSpan[] = [];
168
+ const resolvedSpans: RESpan[] = [];
172
169
  for (const [index, edit] of edits.entries()) {
173
- throwIfAborted(signal);
174
- const span = resolveEditToSpan(
170
+ abortIf(signal);
171
+ const span = resToSpan(
175
172
  edit,
176
173
  index,
177
174
  content,
@@ -191,7 +188,7 @@ function resolveEditSpans(
191
188
  resolvedSpans.push(span);
192
189
  }
193
190
 
194
- assertNoConflictingSpans(resolvedSpans);
191
+ assertNoConflict(resolvedSpans);
195
192
  return [...resolvedSpans].sort((left, right) => {
196
193
  if (right.end !== left.end) {
197
194
  return right.end - left.end;
@@ -200,24 +197,23 @@ function resolveEditSpans(
200
197
  });
201
198
  }
202
199
 
203
- function assembleEditResult(
200
+ function assemble(
204
201
  content: string,
205
- spans: ResolvedEditSpan[],
202
+ spans: RESpan[],
206
203
  signal: AbortSignal | undefined,
207
204
  ): string {
208
205
  let result = content;
209
206
  for (const span of spans) {
210
- throwIfAborted(signal);
207
+ abortIf(signal);
211
208
  result =
212
209
  result.slice(0, span.start) + span.replacement + result.slice(span.end);
213
210
  }
214
211
  return result;
215
212
  }
216
213
 
217
-
218
- export function applyHashlineEdits(
214
+ export function applyEdits(
219
215
  content: string,
220
- edits: import("./resolve").HashlineEdit[],
216
+ edits: import("./resolve").HEdit[],
221
217
  signal?: AbortSignal,
222
218
  precomputedHashes?: string[],
223
219
  ): {
@@ -225,9 +221,9 @@ export function applyHashlineEdits(
225
221
  firstChangedLine: number | undefined;
226
222
  lastChangedLine: number | undefined;
227
223
  warnings?: string[];
228
- noopEdits?: NoopEdit[];
224
+ noopEdits?: NEdit[];
229
225
  } {
230
- throwIfAborted(signal);
226
+ abortIf(signal);
231
227
  if (!edits.length)
232
228
  return {
233
229
  content,
@@ -242,12 +238,12 @@ export function applyHashlineEdits(
242
238
  : edit,
243
239
  );
244
240
 
245
- const lineIndex = buildLineIndex(content);
246
- const fileHashes = precomputedHashes ?? computeLineHashes(content);
247
- const noopEdits: NoopEdit[] = [];
241
+ const lineIndex = buildIdx(content);
242
+ const fileHashes = precomputedHashes ?? lineHashes(content);
243
+ const noopEdits: NEdit[] = [];
248
244
  const warnings: string[] = [];
249
245
 
250
- const { resolved, mismatches, boundaryWarnings } = validateAnchorEdits(
246
+ const { resolved, mismatches, boundaryWarnings } = valEdits(
251
247
  edits,
252
248
  lineIndex.fileLines,
253
249
  fileHashes,
@@ -256,15 +252,15 @@ export function applyHashlineEdits(
256
252
  );
257
253
  if (mismatches.length) {
258
254
  throw new Error(
259
- formatMismatchError(mismatches, lineIndex.fileLines, fileHashes),
255
+ fmtMismatch(mismatches, lineIndex.fileLines, fileHashes),
260
256
  );
261
257
  }
262
258
 
263
- const barePrefixWarnings = assertNoBareHashPrefixLines(edits, lineIndex.fileLines, fileHashes);
259
+ const barePrefixWarnings = assertNoBarePrefix(edits, lineIndex.fileLines, fileHashes);
264
260
  warnings.push(...barePrefixWarnings);
265
- maybeWarnSuspiciousUnicodeEscapePlaceholder(edits, warnings);
261
+ warnUnicodeEsc(edits, warnings);
266
262
 
267
- const orderedSpans = resolveEditSpans(
263
+ const orderedSpans = resSpans(
268
264
  resolved,
269
265
  content,
270
266
  lineIndex,
@@ -272,12 +268,12 @@ export function applyHashlineEdits(
272
268
  signal,
273
269
  );
274
270
 
275
- const result = assembleEditResult(content, orderedSpans, signal);
276
- assertDoesNotEmptyFile(content, result);
277
- const changedRange = computeChangedLineRange(content, result);
271
+ const result = assemble(content, orderedSpans, signal);
272
+ assertNotEmpty(content, result);
273
+ const range = changedRange(content, result);
278
274
 
279
275
  const resultLines = result.split("\n");
280
- const resultHashes = computeLineHashes(result);
276
+ const resultHashes = lineHashes(result);
281
277
  for (const bw of boundaryWarnings) {
282
278
  let seen = 0;
283
279
  let matchIndex = -1;
@@ -303,15 +299,14 @@ export function applyHashlineEdits(
303
299
 
304
300
  return {
305
301
  content: result,
306
- firstChangedLine: changedRange?.firstChangedLine,
307
- lastChangedLine: changedRange?.lastChangedLine,
302
+ firstChangedLine: range?.firstChangedLine,
303
+ lastChangedLine: range?.lastChangedLine,
308
304
  ...(warnings.length ? { warnings } : {}),
309
305
  ...(noopEdits.length ? { noopEdits } : {}),
310
306
  };
311
307
  }
312
308
 
313
-
314
- export function computeAffectedLineRange(params: {
309
+ export function affRange(params: {
315
310
  firstChangedLine: number | undefined;
316
311
  lastChangedLine: number | undefined;
317
312
  resultLineCount: number;
@@ -322,8 +317,8 @@ export function computeAffectedLineRange(params: {
322
317
  firstChangedLine,
323
318
  lastChangedLine,
324
319
  resultLineCount,
325
- contextLines = ANCHOR_CONTEXT_LINES,
326
- maxOutputLines = ANCHOR_MAX_OUTPUT_LINES,
320
+ contextLines = CTX_LINES,
321
+ maxOutputLines = MAX_OUT,
327
322
  } = params;
328
323
 
329
324
  if (firstChangedLine === undefined || lastChangedLine === undefined) {
@@ -352,13 +347,13 @@ export function computeAffectedLineRange(params: {
352
347
  return { start, end };
353
348
  }
354
349
 
355
- export function formatHashlineRegion(
350
+ export function fmtRegion(
356
351
  hashes: string[],
357
352
  lines: string[],
358
353
  ): string {
359
354
  if (hashes.length !== lines.length) {
360
355
  throw new Error(
361
- `formatHashlineRegion: hashes.length (${hashes.length}) must match lines.length (${lines.length}).`,
356
+ `fmtRegion: hashes.length (${hashes.length}) must match lines.length (${lines.length}).`,
362
357
  );
363
358
  }
364
359
  return lines
@@ -366,25 +361,23 @@ export function formatHashlineRegion(
366
361
  .join("\n");
367
362
  }
368
363
 
369
-
370
- export function computeChangedLineRange(
364
+ export function changedRange(
371
365
  original: string,
372
366
  result: string,
373
367
  ): { firstChangedLine: number; lastChangedLine: number } | null {
374
368
  if (original === result) return null;
375
369
 
376
-
377
370
  if (original.length === 0) {
378
371
  return {
379
372
  firstChangedLine: 1,
380
- lastChangedLine: countVisibleLines(result),
373
+ lastChangedLine: cntLines(result),
381
374
  };
382
375
  }
383
376
 
384
377
  if (result.startsWith(original) && original.endsWith("\n")) {
385
378
  return {
386
- firstChangedLine: countVisibleLines(original) + 1,
387
- lastChangedLine: countVisibleLines(result),
379
+ firstChangedLine: cntLines(original) + 1,
380
+ lastChangedLine: cntLines(result),
388
381
  };
389
382
  }
390
383
 
@@ -395,7 +388,6 @@ export function computeChangedLineRange(
395
388
  }
396
389
  if (firstDiff === minLen && original.length === result.length) return null;
397
390
 
398
-
399
391
  let lastOrig = original.length - 1;
400
392
  let lastRes = result.length - 1;
401
393
  while (
@@ -407,7 +399,7 @@ export function computeChangedLineRange(
407
399
  lastRes--;
408
400
  }
409
401
 
410
- function indexToLine(charIdx: number, text: string): number {
402
+ function idxToLine(charIdx: number, text: string): number {
411
403
  let line = 1;
412
404
  for (let i = 0; i < charIdx && i < text.length; i++) {
413
405
  if (text[i] === "\n") line++;
@@ -415,10 +407,10 @@ export function computeChangedLineRange(
415
407
  return line;
416
408
  }
417
409
 
418
- const firstChangedLine = indexToLine(firstDiff + 1, result);
410
+ const firstChangedLine = idxToLine(firstDiff + 1, result);
419
411
  let lastChangedLine: number;
420
412
  if (lastRes < firstDiff) {
421
- lastChangedLine = result.length === 0 ? 1 : countVisibleLines(result);
413
+ lastChangedLine = result.length === 0 ? 1 : cntLines(result);
422
414
  } else if (
423
415
  firstDiff === 0 &&
424
416
  original.length > 0 &&
@@ -426,8 +418,8 @@ export function computeChangedLineRange(
426
418
  ) {
427
419
  lastChangedLine = firstChangedLine;
428
420
  } else {
429
- lastChangedLine = indexToLine(lastRes + 1, result);
421
+ lastChangedLine = idxToLine(lastRes + 1, result);
430
422
  }
431
423
 
432
424
  return { firstChangedLine, lastChangedLine };
433
- }
425
+ }
@@ -1,82 +1,78 @@
1
1
  import xxhash from "xxhash-wasm";
2
2
 
3
+ export const HASH_LEN = 3;
4
+ export const ANCHOR_LEN = HASH_LEN;
3
5
 
4
- export const HASH_LENGTH = 3;
5
- export const HASH_PREFIX = "";
6
- export const ANCHOR_LENGTH = HASH_LENGTH;
7
-
8
- const HASH_ALPHABET =
6
+ const ALPH =
9
7
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
10
- const HASH_ALPHABET_BITS = 6;
11
- const HASH_ALPHABET_MASK = (1 << HASH_ALPHABET_BITS) - 1;
12
- const HASH_ALPHABET_REGEX_SAFE = HASH_ALPHABET.replace(/-/g, "\\-");
13
- const HASH_ALPHABET_RE = new RegExp(`^[${HASH_ALPHABET_REGEX_SAFE}]+$`);
14
- export const HASH_CHARS_CLASS = `${HASH_PREFIX}[${HASH_ALPHABET_REGEX_SAFE}]{${HASH_LENGTH}}`;
15
- function hashToString(h: number): string {
16
- const totalBits = HASH_LENGTH * HASH_ALPHABET_BITS;
8
+ const ALPH_BITS = 6;
9
+ const ALPH_MASK = (1 << ALPH_BITS) - 1;
10
+ const ALPH_SAFE = ALPH.replace(/-/g, "\\-");
11
+ const ALPH_RE = new RegExp(`^[${ALPH_SAFE}]+$`);
12
+ export const HASH_CLASS = `[${ALPH_SAFE}]{${HASH_LEN}}`;
13
+
14
+ function h2s(h: number): string {
15
+ const totalBits = HASH_LEN * ALPH_BITS;
17
16
  const shift = 32 - totalBits;
18
17
  let n = h >>> shift;
19
18
  let out = "";
20
- for (let j = 0; j < HASH_LENGTH; j++) {
19
+ for (let j = 0; j < HASH_LEN; j++) {
21
20
  out +=
22
- HASH_ALPHABET[
23
- (n >>> ((HASH_LENGTH - 1 - j) * HASH_ALPHABET_BITS)) &
24
- HASH_ALPHABET_MASK
21
+ ALPH[
22
+ (n >>> ((HASH_LEN - 1 - j) * ALPH_BITS)) &
23
+ ALPH_MASK
25
24
  ]!;
26
25
  }
27
26
  return out;
28
27
  }
29
28
 
30
- export const HASHLINE_PREFIX_RE = new RegExp(
31
- `^\\s*(?:>>>|>>)?\\s*${HASH_CHARS_CLASS}│`,
29
+ export const HL_PREFIX_RE = new RegExp(
30
+ `^\\s*(?:>>>|>>)?\\s*${HASH_CLASS}│`,
32
31
  );
33
- export const HASHLINE_PREFIX_PLUS_RE = new RegExp(
34
- `^\\+\\s*${HASH_CHARS_CLASS}│`,
32
+ export const HL_PREFIX_PLUS_RE = new RegExp(
33
+ `^\\+\\s*${HASH_CLASS}│`,
35
34
  );
36
35
  export const DIFF_MINUS_RE = /^-\s*\d+\s{4}/;
37
36
 
38
- export const HASHLINE_BARE_PREFIX_RE = new RegExp(`^\\s*(${HASH_CHARS_CLASS})│`);
39
-
40
-
37
+ export const HL_BARE_PREFIX_RE = new RegExp(`^\\s*(${HASH_CLASS})│`);
41
38
 
42
39
  type Hasher = { h32(input: string, seed?: number): number };
43
- let hasherPromise: Promise<Hasher> | null = null;
44
- let hasherSync: Hasher | null = null;
40
+ let hasherP: Promise<Hasher> | null = null;
41
+ let hasher: Hasher | null = null;
45
42
 
46
- function getHasher(): Hasher {
47
- if (hasherSync) return hasherSync;
43
+ function getH(): Hasher {
44
+ if (hasher) return hasher;
48
45
  throw new Error("xxhash-wasm not initialized yet. This should not happen.");
49
46
  }
50
47
 
51
- hasherPromise = xxhash().then((h) => {
52
- hasherSync = h;
48
+ hasherP = xxhash().then((h) => {
49
+ hasher = h;
53
50
  return h;
54
51
  });
55
52
 
56
- export function ensureHasherReady(): Promise<Hasher> {
57
- return hasherPromise!
53
+ export function initHasher(): Promise<Hasher> {
54
+ return hasherP!
58
55
  }
59
56
 
60
57
  function xxh32(input: string, seed = 0): number {
61
- return getHasher().h32(input, seed) >>> 0;
58
+ return getH().h32(input, seed) >>> 0;
62
59
  }
63
60
 
64
-
65
- function canonicalizeLine(line: string): string {
61
+ function canon(line: string): string {
66
62
  return line.replace(/\r/g, "").trimEnd();
67
63
  }
68
64
 
69
- export function computeLineHashes(content: string): string[] {
65
+ export function lineHashes(content: string): string[] {
70
66
  const lines = content.split("\n");
71
67
  const hashes = new Array<string>(lines.length);
72
68
  const assigned = new Set<string>();
73
69
  for (let i = 0; i < lines.length; i++) {
74
- const canonical = canonicalizeLine(lines[i]!);
75
- let hash = hashToString(xxh32(canonical));
70
+ const c = canon(lines[i]!);
71
+ let hash = h2s(xxh32(c));
76
72
  let retry = 0;
77
73
  while (assigned.has(hash)) {
78
74
  retry++;
79
- hash = hashToString(xxh32(`${canonical}:R${retry}`));
75
+ hash = h2s(xxh32(`${c}:R${retry}`));
80
76
  }
81
77
  assigned.add(hash);
82
78
  hashes[i] = hash;
@@ -84,18 +80,8 @@ export function computeLineHashes(content: string): string[] {
84
80
  return hashes;
85
81
  }
86
82
 
87
- export function computeLineHash(idx: number, line: string): string {
88
- const canonical = canonicalizeLine(line);
89
- return hashToString(xxh32(canonical));
83
+ export function lineHash(idx: number, line: string): string {
84
+ return h2s(xxh32(canon(line)));
90
85
  }
91
86
 
92
- export const HASH_FORMAT = {
93
- prefix: HASH_PREFIX,
94
- length: HASH_LENGTH,
95
- anchorLength: ANCHOR_LENGTH,
96
- bitsPerChar: HASH_ALPHABET_BITS,
97
- alphabet: HASH_ALPHABET,
98
- };
99
-
100
-
101
- export { HASH_ALPHABET_RE };
87
+ export { ALPH_RE };
@@ -1,44 +1,40 @@
1
-
2
-
3
1
  export {
4
- HASH_LENGTH,
5
- HASH_PREFIX,
6
- ANCHOR_LENGTH,
7
- HASH_FORMAT,
8
- HASH_CHARS_CLASS,
9
- HASHLINE_PREFIX_RE,
10
- HASHLINE_PREFIX_PLUS_RE,
2
+ HASH_LEN,
3
+ ANCHOR_LEN,
4
+ HASH_CLASS,
5
+ HL_PREFIX_RE,
6
+ HL_PREFIX_PLUS_RE,
11
7
  DIFF_MINUS_RE,
12
- HASHLINE_BARE_PREFIX_RE,
13
- computeLineHashes,
14
- computeLineHash,
15
- ensureHasherReady,
8
+ HL_BARE_PREFIX_RE,
9
+ lineHashes,
10
+ lineHash,
11
+ initHasher,
16
12
  } from "./hash";
17
13
 
18
14
  export {
19
15
  parseHashRef,
20
- hashlineParseText,
16
+ parseText,
21
17
  type Anchor,
22
18
  } from "./parse";
23
19
 
24
20
  export {
25
- type ResolvedAnchor,
26
- type HashlineEdit,
27
- type ResolvedHashlineEdit,
28
- type HashlineToolEdit,
29
- type NoopEdit,
30
- type BoundaryDuplicationWarning,
31
- describeEdit,
32
- resolveEditAnchors,
33
- validateAnchorEdits,
34
- assertNoBareHashPrefixLines,
35
- formatMismatchError,
21
+ type RAnchor,
22
+ type HEdit,
23
+ type RHEdit,
24
+ type HTEdit,
25
+ type NEdit,
26
+ type BDupWarn,
27
+ descEdit,
28
+ resEdits,
29
+ valEdits,
30
+ assertNoBarePrefix,
31
+ fmtMismatch,
36
32
  } from "./resolve";
37
33
 
38
34
  export {
39
- buildLineIndex,
40
- applyHashlineEdits,
41
- computeAffectedLineRange,
42
- formatHashlineRegion,
43
- computeChangedLineRange,
35
+ buildIdx,
36
+ applyEdits,
37
+ affRange,
38
+ fmtRegion,
39
+ changedRange,
44
40
  } from "./apply";
@@ -1,61 +1,61 @@
1
-
2
1
  import {
3
- ANCHOR_LENGTH,
4
- HASH_ALPHABET_RE,
5
- HASH_CHARS_CLASS,
6
- HASHLINE_PREFIX_PLUS_RE,
2
+ ANCHOR_LEN,
3
+ ALPH_RE,
4
+ HASH_CLASS,
5
+ HL_PREFIX_PLUS_RE,
7
6
  DIFF_MINUS_RE,
8
7
  } from "./hash";
9
8
 
10
-
11
9
  export type Anchor = { hash: string };
12
10
 
13
-
14
- function diagnoseHashRef(ref: string): string {
11
+ function diagRef(ref: string): string {
15
12
  const trimmed = ref.trim();
16
13
 
17
14
  if (!trimmed.length) {
18
- return `[E_BAD_REF] Invalid anchor. Expected a 3-character base64 anchor (e.g. \"aB3\").`;
15
+ return `[E_BAD_REF] Invalid anchor. Expected a 3-char base64 anchor (e.g. "aB3").`;
19
16
  }
20
17
 
21
18
  if (/^\d+/.test(trimmed)) {
22
- return `[E_BAD_REF] Invalid anchor. Use the hash alone (e.g. \"aB3\") — no line numbers or trailing content.`;
19
+ return `[E_BAD_REF] Invalid anchor. Use the hash alone (e.g. "aB3") — no line numbers or trailing content.`;
23
20
  }
24
21
 
25
- return `[E_BAD_REF] Invalid anchor \"${trimmed}\". Expected a 3-character base64 anchor (e.g. \"aB3\").`;
22
+ if (trimmed.includes("")) {
23
+ return `[E_BAD_REF] Invalid anchor "${trimmed}". old_range must contain the 3-char hash only — remove everything from "│" onward.`;
24
+ }
25
+
26
+ return `[E_BAD_REF] Invalid anchor "${trimmed}". Expected a 3-char base64 anchor (e.g. "aB3").`;
26
27
  }
27
28
 
28
- function parseAnchorRef(ref: string): Anchor {
29
+ function parseRef(ref: string): Anchor {
29
30
  const trimmed = ref.trim();
30
31
 
31
32
  if (
32
- trimmed.length === ANCHOR_LENGTH &&
33
- HASH_ALPHABET_RE.test(trimmed)
33
+ trimmed.length === ANCHOR_LEN &&
34
+ ALPH_RE.test(trimmed)
34
35
  ) {
35
36
  return { hash: trimmed };
36
37
  }
37
38
 
38
- throw new Error(diagnoseHashRef(ref));
39
+ throw new Error(diagRef(ref));
39
40
  }
40
41
 
41
- export const parseHashRef = parseAnchorRef;
42
-
42
+ export const parseHashRef = parseRef;
43
43
 
44
- function assertNoDisplayPrefixes(lines: string[]): void {
44
+ function assertNoPrefixes(lines: string[]): void {
45
45
  for (const line of lines) {
46
46
  if (!line.length) continue;
47
47
  if (
48
- HASHLINE_PREFIX_PLUS_RE.test(line) ||
48
+ HL_PREFIX_PLUS_RE.test(line) ||
49
49
  DIFF_MINUS_RE.test(line)
50
50
  ) {
51
51
  throw new Error(
52
- `[E_INVALID_PATCH] \"lines\" must contain literal file content, not HASH| or diff prefixes. Offending line: ${JSON.stringify(line)}`
52
+ `[E_INVALID_PATCH] "lines" must contain literal file content, not HASH| or diff prefixes. Offending line: ${JSON.stringify(line)}`
53
53
  );
54
54
  }
55
55
  }
56
56
  }
57
57
 
58
- export function hashlineParseText(edit: string[] | string | null): string[] {
58
+ export function parseText(edit: string[] | string | null): string[] {
59
59
  if (edit === null) return [];
60
60
  const lines =
61
61
  typeof edit === "string"
@@ -63,6 +63,6 @@ export function hashlineParseText(edit: string[] | string | null): string[] {
63
63
  .replaceAll("\r", "")
64
64
  .split("\n")
65
65
  : edit;
66
- assertNoDisplayPrefixes(lines);
66
+ assertNoPrefixes(lines);
67
67
  return lines;
68
68
  }