@oh-my-pi/hashline 15.10.11 → 15.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.
- package/dist/types/messages.d.ts +11 -3
- package/package.json +1 -1
- package/src/block.ts +3 -1
- package/src/messages.ts +42 -7
- package/src/mismatch.ts +5 -25
package/dist/types/messages.d.ts
CHANGED
|
@@ -6,6 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
/** Lines of context shown either side of a hash mismatch. */
|
|
8
8
|
export declare const MISMATCH_CONTEXT = 2;
|
|
9
|
+
/**
|
|
10
|
+
* Render numbered `LINE:TEXT` context rows around `anchorLines`
|
|
11
|
+
* (±{@link MISMATCH_CONTEXT} lines each), `*`-marking the anchored lines and
|
|
12
|
+
* separating non-adjacent runs with `...`. Out-of-range anchors contribute no
|
|
13
|
+
* rows; returns an empty array when every anchor is out of range.
|
|
14
|
+
*/
|
|
15
|
+
export declare function formatAnchoredContext(anchorLines: readonly number[], fileLines: readonly string[]): string[];
|
|
9
16
|
/** Optional patch envelope start marker; silently consumed when present. */
|
|
10
17
|
export declare const BEGIN_PATCH_MARKER = "*** Begin Patch";
|
|
11
18
|
/** Optional patch envelope end marker; terminates parsing when encountered. */
|
|
@@ -32,10 +39,11 @@ export declare const EMPTY_BLOCK = "`replace block N:` needs at least one `+TEXT
|
|
|
32
39
|
* Error text emitted when a block-anchored op cannot be resolved to a
|
|
33
40
|
* syntactic block (unrecognized language, blank/out-of-range line, no node
|
|
34
41
|
* begins on line N such as a lone closing delimiter, or the resolved block has
|
|
35
|
-
* a syntax error). Names the offending line
|
|
36
|
-
* concrete-line form
|
|
42
|
+
* a syntax error). Names the offending line, steers back to an explicit
|
|
43
|
+
* concrete-line form, and — when `fileLines` is provided — appends a
|
|
44
|
+
* {@link formatAnchoredContext} preview of the file around the anchor line.
|
|
37
45
|
*/
|
|
38
|
-
export declare function blockUnresolvedMessage(line: number, op?: "replace" | "delete" | "insert_after"): string;
|
|
46
|
+
export declare function blockUnresolvedMessage(line: number, op?: "replace" | "delete" | "insert_after", fileLines?: readonly string[]): string;
|
|
39
47
|
/**
|
|
40
48
|
* Error text emitted when a block-anchored edit reaches a code path that
|
|
41
49
|
* has no {@link BlockResolver} wired in. Indicates a host-configuration bug
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/hashline",
|
|
4
|
-
"version": "15.
|
|
4
|
+
"version": "15.11.0",
|
|
5
5
|
"description": "Hashline: a compact, line-anchored patch language and applier. Pluggable FS/IO so it works over disk, in-memory, or any custom backend.",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
package/src/block.ts
CHANGED
|
@@ -69,7 +69,9 @@ export function resolveBlockEdits(
|
|
|
69
69
|
if (span === null) {
|
|
70
70
|
if (onUnresolved === "drop") continue;
|
|
71
71
|
throw new Error(
|
|
72
|
-
`line ${edit.lineNum}: ${
|
|
72
|
+
`line ${edit.lineNum}: ${
|
|
73
|
+
resolver ? blockUnresolvedMessage(edit.anchor.line, op, text.split("\n")) : BLOCK_RESOLVER_UNAVAILABLE
|
|
74
|
+
}`,
|
|
73
75
|
);
|
|
74
76
|
}
|
|
75
77
|
options.onResolved?.({
|
package/src/messages.ts
CHANGED
|
@@ -5,11 +5,37 @@
|
|
|
5
5
|
* them.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { HL_FILE_HASH_SEP, HL_FILE_PREFIX, HL_FILE_SUFFIX } from "./format";
|
|
8
|
+
import { formatNumberedLine, HL_FILE_HASH_SEP, HL_FILE_PREFIX, HL_FILE_SUFFIX } from "./format";
|
|
9
9
|
|
|
10
10
|
/** Lines of context shown either side of a hash mismatch. */
|
|
11
11
|
export const MISMATCH_CONTEXT = 2;
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Render numbered `LINE:TEXT` context rows around `anchorLines`
|
|
15
|
+
* (±{@link MISMATCH_CONTEXT} lines each), `*`-marking the anchored lines and
|
|
16
|
+
* separating non-adjacent runs with `...`. Out-of-range anchors contribute no
|
|
17
|
+
* rows; returns an empty array when every anchor is out of range.
|
|
18
|
+
*/
|
|
19
|
+
export function formatAnchoredContext(anchorLines: readonly number[], fileLines: readonly string[]): string[] {
|
|
20
|
+
const displayLines = new Set<number>();
|
|
21
|
+
for (const line of anchorLines) {
|
|
22
|
+
if (line < 1 || line > fileLines.length) continue;
|
|
23
|
+
const lo = Math.max(1, line - MISMATCH_CONTEXT);
|
|
24
|
+
const hi = Math.min(fileLines.length, line + MISMATCH_CONTEXT);
|
|
25
|
+
for (let lineNum = lo; lineNum <= hi; lineNum++) displayLines.add(lineNum);
|
|
26
|
+
}
|
|
27
|
+
const anchorSet = new Set(anchorLines);
|
|
28
|
+
const rows: string[] = [];
|
|
29
|
+
let previous = -1;
|
|
30
|
+
for (const lineNum of [...displayLines].sort((a, b) => a - b)) {
|
|
31
|
+
if (previous !== -1 && lineNum > previous + 1) rows.push("...");
|
|
32
|
+
previous = lineNum;
|
|
33
|
+
const marker = anchorSet.has(lineNum) ? "*" : " ";
|
|
34
|
+
rows.push(`${marker}${formatNumberedLine(lineNum, fileLines[lineNum - 1] ?? "")}`);
|
|
35
|
+
}
|
|
36
|
+
return rows;
|
|
37
|
+
}
|
|
38
|
+
|
|
13
39
|
/** Optional patch envelope start marker; silently consumed when present. */
|
|
14
40
|
export const BEGIN_PATCH_MARKER = "*** Begin Patch";
|
|
15
41
|
|
|
@@ -50,10 +76,15 @@ export const EMPTY_BLOCK =
|
|
|
50
76
|
* Error text emitted when a block-anchored op cannot be resolved to a
|
|
51
77
|
* syntactic block (unrecognized language, blank/out-of-range line, no node
|
|
52
78
|
* begins on line N such as a lone closing delimiter, or the resolved block has
|
|
53
|
-
* a syntax error). Names the offending line
|
|
54
|
-
* concrete-line form
|
|
79
|
+
* a syntax error). Names the offending line, steers back to an explicit
|
|
80
|
+
* concrete-line form, and — when `fileLines` is provided — appends a
|
|
81
|
+
* {@link formatAnchoredContext} preview of the file around the anchor line.
|
|
55
82
|
*/
|
|
56
|
-
export function blockUnresolvedMessage(
|
|
83
|
+
export function blockUnresolvedMessage(
|
|
84
|
+
line: number,
|
|
85
|
+
op: "replace" | "delete" | "insert_after" = "replace",
|
|
86
|
+
fileLines?: readonly string[],
|
|
87
|
+
): string {
|
|
57
88
|
const phrase =
|
|
58
89
|
op === "delete"
|
|
59
90
|
? `delete block ${line}`
|
|
@@ -66,11 +97,15 @@ export function blockUnresolvedMessage(line: number, op: "replace" | "delete" |
|
|
|
66
97
|
: op === "insert_after"
|
|
67
98
|
? `\`insert after M:\` with the block's explicit last line`
|
|
68
99
|
: `\`replace ${line}..M:\` with the block's explicit end line`;
|
|
69
|
-
|
|
100
|
+
let message =
|
|
70
101
|
`\`${phrase}\` could not resolve a syntactic block beginning on line ${line}. ` +
|
|
71
102
|
`The language may be unsupported, the line may be blank or a closing delimiter, or the block may not parse. ` +
|
|
72
|
-
`Use ${fallback} instead
|
|
73
|
-
)
|
|
103
|
+
`Use ${fallback} instead.`;
|
|
104
|
+
if (fileLines) {
|
|
105
|
+
const context = formatAnchoredContext([line], fileLines);
|
|
106
|
+
if (context.length > 0) message += `\n\n${context.join("\n")}`;
|
|
107
|
+
}
|
|
108
|
+
return message;
|
|
74
109
|
}
|
|
75
110
|
|
|
76
111
|
/**
|
package/src/mismatch.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* plus a couple of lines of surrounding context. The {@link MismatchError}
|
|
7
7
|
* formats this into a message at construction time.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { HL_FILE_HASH_EXAMPLES, HL_FILE_HASH_SEP, HL_FILE_PREFIX, HL_FILE_SUFFIX } from "./format";
|
|
10
|
+
import { formatAnchoredContext } from "./messages";
|
|
11
11
|
|
|
12
12
|
const LINE_REF_RE = /^\s*[>+\-*]*\s*(\d+)(?::.*)?\s*$/;
|
|
13
13
|
/** Format the required-shape diagnostic shown when a line reference is malformed. */
|
|
@@ -46,17 +46,6 @@ export interface MismatchDetails {
|
|
|
46
46
|
hashRecognized?: boolean;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
function getMismatchDisplayLines(anchorLines: readonly number[], fileLines: string[]): number[] {
|
|
50
|
-
const displayLines = new Set<number>();
|
|
51
|
-
for (const line of anchorLines) {
|
|
52
|
-
if (line < 1 || line > fileLines.length) continue;
|
|
53
|
-
const lo = Math.max(1, line - MISMATCH_CONTEXT);
|
|
54
|
-
const hi = Math.min(fileLines.length, line + MISMATCH_CONTEXT);
|
|
55
|
-
for (let lineNum = lo; lineNum <= hi; lineNum++) displayLines.add(lineNum);
|
|
56
|
-
}
|
|
57
|
-
return [...displayLines].sort((a, b) => a - b);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
49
|
/**
|
|
61
50
|
* Raised when a hashline section's snapshot tag doesn't match the live file's
|
|
62
51
|
* content (and recovery, if configured, declined the merge). Carries the
|
|
@@ -113,19 +102,10 @@ export class MismatchError extends Error {
|
|
|
113
102
|
}
|
|
114
103
|
|
|
115
104
|
static formatMessage(details: MismatchDetails): string {
|
|
116
|
-
const anchorSet = new Set(details.anchorLines ?? []);
|
|
117
105
|
const lines = MismatchError.rejectionHeader(details);
|
|
118
|
-
const
|
|
119
|
-
if (
|
|
120
|
-
lines.push("");
|
|
121
|
-
let previous = -1;
|
|
122
|
-
for (const lineNum of displayLines) {
|
|
123
|
-
if (previous !== -1 && lineNum > previous + 1) lines.push("...");
|
|
124
|
-
previous = lineNum;
|
|
125
|
-
const text = details.fileLines[lineNum - 1] ?? "";
|
|
126
|
-
const marker = anchorSet.has(lineNum) ? "*" : " ";
|
|
127
|
-
lines.push(`${marker}${formatNumberedLine(lineNum, text)}`);
|
|
128
|
-
}
|
|
106
|
+
const context = formatAnchoredContext(details.anchorLines ?? [], details.fileLines);
|
|
107
|
+
if (context.length === 0) return lines.join("\n");
|
|
108
|
+
lines.push("", ...context);
|
|
129
109
|
return lines.join("\n");
|
|
130
110
|
}
|
|
131
111
|
}
|