pi-hashline-edit-pro 4.4.3 → 4.5.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/README.md +312 -63
- package/index.ts +22 -6
- package/package.json +4 -3
- package/prompts/copy-guidelines.md +1 -0
- package/prompts/copy-snippet.md +1 -0
- package/prompts/copy.md +3 -0
- package/prompts/grep.md +1 -1
- package/prompts/insert.md +1 -1
- package/prompts/move-guidelines.md +1 -0
- package/prompts/move-snippet.md +1 -0
- package/prompts/move.md +3 -0
- package/prompts/read-guidelines.md +2 -1
- package/prompts/replace-guidelines.md +0 -2
- package/prompts/replace.md +1 -1
- package/prompts/undo-last-change-guidelines.md +2 -1
- package/prompts/undo-last-change-snippet.md +1 -1
- package/prompts/undo-last-change.md +1 -1
- package/src/anchor-registry.ts +128 -39
- package/src/auto-read-all.ts +3 -1
- package/src/batch.ts +35 -7
- package/src/commit.ts +22 -4
- package/src/config-ui.ts +2 -1
- package/src/config.ts +6 -1
- package/src/constants.ts +4 -1
- package/src/copy-move.ts +793 -0
- package/src/edit-common.ts +21 -1
- package/src/file-reader.ts +4 -0
- package/src/grep.ts +3 -1
- package/src/hash-store/validation.ts +22 -0
- package/src/hash-store.ts +33 -5
- package/src/hashline/apply.ts +8 -3
- package/src/hashline/index.ts +3 -0
- package/src/hashline/parse.ts +22 -10
- package/src/hashline/resolve.ts +23 -8
- package/src/insert.ts +28 -10
- package/src/line-endings.ts +151 -0
- package/src/paths.ts +23 -3
- package/src/payload-contract.ts +29 -1
- package/src/read.ts +7 -5
- package/src/replace-render.ts +4 -4
- package/src/replace-undo.ts +13 -4
- package/src/replace.ts +17 -5
- package/src/utils.ts +40 -5
package/src/batch.ts
CHANGED
|
@@ -15,9 +15,11 @@ import {
|
|
|
15
15
|
MAX_HASH_LINES,
|
|
16
16
|
type HEdit,
|
|
17
17
|
type PlannedEdit,
|
|
18
|
+
type StripWarningLocation,
|
|
18
19
|
} from "./hashline";
|
|
19
|
-
import { adoptAnchors, servedForPath } from "./anchor-registry";
|
|
20
|
-
import {
|
|
20
|
+
import { adoptAnchors, servedForPath, formatAnchorReclaimNotice, takeReclaimedPaths } from "./anchor-registry";
|
|
21
|
+
import { stripBOM, toLF, type LineEnding } from "./normalize";
|
|
22
|
+
import { applySpanEndings, joinSeparators, separatorsForSpans } from "./line-endings";
|
|
21
23
|
import { assertInsertReq, assertReq, normReq } from "./payload-contract";
|
|
22
24
|
import { saveUndo } from "./replace-undo";
|
|
23
25
|
import { buildChanged, buildNoop, type RMetrics, type TResult } from "./replace-response";
|
|
@@ -44,6 +46,7 @@ interface BatchBase {
|
|
|
44
46
|
hashes: string[];
|
|
45
47
|
bom: string;
|
|
46
48
|
ending: LineEnding;
|
|
49
|
+
separators: LineEnding[];
|
|
47
50
|
identity: FileIdentity;
|
|
48
51
|
hadUtf8DecodeErrors: boolean;
|
|
49
52
|
absolutePath: string;
|
|
@@ -64,6 +67,7 @@ export interface BatchPiece {
|
|
|
64
67
|
noop: boolean;
|
|
65
68
|
foldedLines: number;
|
|
66
69
|
carryIndex?: number;
|
|
70
|
+
separators?: (LineEnding | undefined)[];
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
export interface BatchMemberInput {
|
|
@@ -77,6 +81,8 @@ export interface BatchMemberInput {
|
|
|
77
81
|
hedit: HEdit;
|
|
78
82
|
extraWarnings: string[];
|
|
79
83
|
foldedLines?: number;
|
|
84
|
+
stripWarning?: StripWarningLocation;
|
|
85
|
+
contentSeparators?: (LineEnding | undefined)[];
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
interface BatchFailure {
|
|
@@ -434,6 +440,7 @@ export async function ensureBatchBase(input: {
|
|
|
434
440
|
hashes: file.fileHashes.slice(),
|
|
435
441
|
bom: file.bom,
|
|
436
442
|
ending: file.originalEnding,
|
|
443
|
+
separators: file.endingSeparators,
|
|
437
444
|
identity: file.identity,
|
|
438
445
|
hadUtf8DecodeErrors: file.hadUtf8DecodeErrors,
|
|
439
446
|
absolutePath: file.absolutePath,
|
|
@@ -482,6 +489,7 @@ export async function executeBatchMember(input: BatchMemberInput): Promise<TResu
|
|
|
482
489
|
servedHashes: runtime.served,
|
|
483
490
|
signal: input.signal,
|
|
484
491
|
baseFileLines: base.baseLines,
|
|
492
|
+
stripWarning: input.stripWarning,
|
|
485
493
|
});
|
|
486
494
|
} catch (error) {
|
|
487
495
|
if (error instanceof RangeStaleError) adoptAnchors(base.absolutePath, error.rangeServedMap);
|
|
@@ -497,6 +505,7 @@ export async function executeBatchMember(input: BatchMemberInput): Promise<TResu
|
|
|
497
505
|
const originalSlice = baseLines.slice(start - 1, end);
|
|
498
506
|
const noop = originalSlice.length === newLines.length && originalSlice.every((line, index) => line === newLines[index]);
|
|
499
507
|
const foldedLines = input.foldedLines ?? 0;
|
|
508
|
+
const separators = input.contentSeparators ?? input.hedit.content_separators;
|
|
500
509
|
const carryIndex =
|
|
501
510
|
input.kind === "insert" && foldedLines > 0
|
|
502
511
|
? input.direction === "after"
|
|
@@ -513,6 +522,7 @@ export async function executeBatchMember(input: BatchMemberInput): Promise<TResu
|
|
|
513
522
|
fromHash: input.hedit.hash_bounds[0].hash,
|
|
514
523
|
toHash: input.hedit.hash_bounds[1].hash,
|
|
515
524
|
newLines: [...newLines],
|
|
525
|
+
...(separators !== undefined ? { separators } : {}),
|
|
516
526
|
warnings: [...input.extraWarnings, ...planned.warnings],
|
|
517
527
|
noop,
|
|
518
528
|
foldedLines,
|
|
@@ -569,12 +579,16 @@ function mergeInsertPairs(pieces: BatchPiece[]): BatchPiece[] {
|
|
|
569
579
|
consumed.add(partner);
|
|
570
580
|
const before = piece.direction === "before" ? piece : partner;
|
|
571
581
|
const after = piece.direction === "before" ? partner : piece;
|
|
582
|
+
const beforeSeparators = before.separators ?? before.newLines.map(() => undefined);
|
|
583
|
+
const afterSeparators = after.separators ?? after.newLines.map(() => undefined);
|
|
584
|
+
const mergedSeparators = [...beforeSeparators.slice(0, before.newLines.length), ...afterSeparators.slice(1)];
|
|
572
585
|
merged.push({
|
|
573
586
|
...before,
|
|
574
587
|
order: Math.min(before.order, after.order),
|
|
575
588
|
newLines: [...before.newLines, ...after.newLines.slice(1)],
|
|
576
589
|
warnings: [...before.warnings, ...after.warnings],
|
|
577
590
|
foldedLines: before.foldedLines + after.foldedLines - 1,
|
|
591
|
+
...(before.separators !== undefined || after.separators !== undefined ? { separators: mergedSeparators } : {}),
|
|
578
592
|
});
|
|
579
593
|
}
|
|
580
594
|
return merged;
|
|
@@ -625,19 +639,29 @@ async function finishBatch(member: PlannedMember, signal?: AbortSignal): Promise
|
|
|
625
639
|
}
|
|
626
640
|
}
|
|
627
641
|
const composed = composeBatchLines(base.content, effectivePieces);
|
|
642
|
+
const spans = pieceMappingSpans(effectivePieces);
|
|
643
|
+
const resultSeparators = separatorsForSpans(base.separators, base.hashes.length, spans, composed, base.ending);
|
|
644
|
+
applySpanEndings(resultSeparators, effectivePieces.map((piece) => ({
|
|
645
|
+
start: piece.start - 1,
|
|
646
|
+
end: piece.end - 1,
|
|
647
|
+
replacementCount: piece.newLines.length,
|
|
648
|
+
...(piece.separators !== undefined ? { endings: piece.separators } : {}),
|
|
649
|
+
})));
|
|
628
650
|
const warnings = [...runtime.warnings];
|
|
629
651
|
if (base.hadUtf8DecodeErrors) warnings.push("Non-UTF-8 bytes were shown as U+FFFD; this edit rewrote the file as UTF-8.");
|
|
630
652
|
try {
|
|
631
653
|
await throwIfStrictInput(dedupeWarnings(warnings));
|
|
632
654
|
assertNotEmpty(base.content, composed);
|
|
633
655
|
assertLineLimit(composed, paths.displayPath, MAX_HASH_LINES);
|
|
634
|
-
const finalBytes = base.bom +
|
|
656
|
+
const finalBytes = base.bom + joinSeparators(composed, resultSeparators);
|
|
635
657
|
assertByteLimit(finalBytes, paths.displayPath);
|
|
636
658
|
} catch (error) {
|
|
637
659
|
discardBatchState(runtime);
|
|
638
660
|
if (error instanceof Error) error.message = withAbortSuffix(error.message, runtime.display);
|
|
639
661
|
throw error;
|
|
640
662
|
}
|
|
663
|
+
const reclaimNotice = formatAnchorReclaimNotice(takeReclaimedPaths());
|
|
664
|
+
if (reclaimNotice !== undefined) warnings.push(reclaimNotice);
|
|
641
665
|
if (composed === base.content) {
|
|
642
666
|
const snapshotId = await safeSnapId(paths.absolutePath, "noop edit");
|
|
643
667
|
return combinedNoop(paths.displayPath, member, runtime, snapshotId);
|
|
@@ -655,12 +679,11 @@ async function finishBatch(member: PlannedMember, signal?: AbortSignal): Promise
|
|
|
655
679
|
discardBatchState(runtime);
|
|
656
680
|
throw new Error(`[E_OP_ABORTED] Batch ${runtime.display} aborted: the file changed after the batch started. Call read for fresh anchors and retry.`);
|
|
657
681
|
}
|
|
658
|
-
const preflightSpans = pieceMappingSpans(effectivePieces);
|
|
659
682
|
try {
|
|
660
683
|
await lineHashes(composed, runtime.target, {
|
|
661
684
|
content: base.content,
|
|
662
685
|
hashes: base.hashes,
|
|
663
|
-
spans
|
|
686
|
+
spans,
|
|
664
687
|
}, undefined, false, true);
|
|
665
688
|
} catch (error) {
|
|
666
689
|
discardBatchState(runtime);
|
|
@@ -671,8 +694,10 @@ async function finishBatch(member: PlannedMember, signal?: AbortSignal): Promise
|
|
|
671
694
|
content: base.content,
|
|
672
695
|
bom: base.bom,
|
|
673
696
|
originalEnding: base.ending,
|
|
697
|
+
separators: base.separators,
|
|
674
698
|
hashes: base.hashes,
|
|
675
699
|
resultContent: composed,
|
|
700
|
+
resultSeparators,
|
|
676
701
|
});
|
|
677
702
|
if (!undo.persisted) {
|
|
678
703
|
discardBatchState(runtime);
|
|
@@ -680,7 +705,7 @@ async function finishBatch(member: PlannedMember, signal?: AbortSignal): Promise
|
|
|
680
705
|
}
|
|
681
706
|
try {
|
|
682
707
|
abortIf(signal);
|
|
683
|
-
await writeAtomic(paths.absolutePath, base.bom +
|
|
708
|
+
await writeAtomic(paths.absolutePath, base.bom + joinSeparators(composed, resultSeparators), base.identity);
|
|
684
709
|
} catch (error) {
|
|
685
710
|
await undo.restore();
|
|
686
711
|
discardBatchState(runtime);
|
|
@@ -688,7 +713,6 @@ async function finishBatch(member: PlannedMember, signal?: AbortSignal): Promise
|
|
|
688
713
|
throw error;
|
|
689
714
|
}
|
|
690
715
|
const updatedSnapshotId = await safeSnapId(paths.absolutePath, "post-edit");
|
|
691
|
-
const spans = pieceMappingSpans(effectivePieces);
|
|
692
716
|
let resultHashes: string[];
|
|
693
717
|
try {
|
|
694
718
|
resultHashes = await lineHashes(composed, runtime.target, {
|
|
@@ -700,6 +724,8 @@ async function finishBatch(member: PlannedMember, signal?: AbortSignal): Promise
|
|
|
700
724
|
const detail = error instanceof Error ? error.message : String(error);
|
|
701
725
|
throw new Error(`${detail} File was written; anchor finalization failed. One undo reverts. Call read for fresh anchors.`);
|
|
702
726
|
}
|
|
727
|
+
const writeReclaim = formatAnchorReclaimNotice(takeReclaimedPaths());
|
|
728
|
+
if (writeReclaim !== undefined) warnings.push(writeReclaim);
|
|
703
729
|
const range = changedRange(base.content, composed);
|
|
704
730
|
let added = 0;
|
|
705
731
|
let removed = 0;
|
|
@@ -746,6 +772,8 @@ async function finishBatch(member: PlannedMember, signal?: AbortSignal): Promise
|
|
|
746
772
|
async function combinedNoop(path: string, member: PlannedMember, runtime: BatchState, snapshotId: string | undefined): Promise<TResult> {
|
|
747
773
|
const executed = runtime.applied + runtime.noops;
|
|
748
774
|
const warnings = [...runtime.warnings];
|
|
775
|
+
const reclaimNotice = formatAnchorReclaimNotice(takeReclaimedPaths());
|
|
776
|
+
if (reclaimNotice !== undefined) warnings.push(reclaimNotice);
|
|
749
777
|
const noop = buildNoop(
|
|
750
778
|
{
|
|
751
779
|
path,
|
package/src/commit.ts
CHANGED
|
@@ -6,10 +6,12 @@ import { saveUndo } from "./replace-undo";
|
|
|
6
6
|
import { getDiffContextLines } from "./config";
|
|
7
7
|
import { safeSnapId } from "./file-reader";
|
|
8
8
|
import { writeAtomic } from "./fs-write";
|
|
9
|
+
import { formatAnchorReclaimNotice, takeReclaimedPaths } from "./anchor-registry";
|
|
9
10
|
import { servedHashesFromDiff, serveRows } from "./served";
|
|
10
11
|
import { lineHashes } from "./hashline";
|
|
11
12
|
import { spanForEdit } from "./replace";
|
|
12
|
-
import { restoreEndings, stripBOM, toLF } from "./normalize";
|
|
13
|
+
import { restoreEndings, stripBOM, toLF, type LineEnding } from "./normalize";
|
|
14
|
+
import { applyEndingOverrides, joinSeparators, separatorsForSpans } from "./line-endings";
|
|
13
15
|
export interface CommitMeta {
|
|
14
16
|
editAnchors?: [string, string];
|
|
15
17
|
anchorCarry?: number;
|
|
@@ -21,12 +23,15 @@ export interface CommitMeta {
|
|
|
21
23
|
noopNoun?: string;
|
|
22
24
|
prefixWarnings?: string[];
|
|
23
25
|
foldedAnchorLines?: number;
|
|
26
|
+
endingOverrides?: (LineEnding | undefined)[];
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
export async function commitEdit(pipe: PipelineResult, meta: CommitMeta): Promise<TResult> {
|
|
27
30
|
const { path, absolutePath, mutationTargetPath, signal } = meta;
|
|
28
31
|
const warnings = [...(meta.prefixWarnings ?? []), ...pipe.warnings];
|
|
29
32
|
const editsAttempted = 1;
|
|
33
|
+
const readReclaim = formatAnchorReclaimNotice(takeReclaimedPaths());
|
|
34
|
+
if (readReclaim !== undefined) warnings.push(readReclaim);
|
|
30
35
|
|
|
31
36
|
if (pipe.result === pipe.originalNormalized) {
|
|
32
37
|
const noopSnapshotId = await safeSnapId(absolutePath, "noop edit");
|
|
@@ -47,7 +52,18 @@ export async function commitEdit(pipe: PipelineResult, meta: CommitMeta): Promis
|
|
|
47
52
|
);
|
|
48
53
|
}
|
|
49
54
|
|
|
50
|
-
const
|
|
55
|
+
const span = pipe.spans?.[0] ?? (meta.editAnchors ? spanForEdit(pipe.originalHashes, meta.editAnchors[0], meta.editAnchors[1], pipe.result) : undefined);
|
|
56
|
+
if (span && meta.anchorCarry !== undefined) span.carry = meta.anchorCarry;
|
|
57
|
+
const resultSeparators = span
|
|
58
|
+
? separatorsForSpans(pipe.originalSeparators, pipe.originalHashes.length, [span], pipe.result, pipe.originalEnding)
|
|
59
|
+
: undefined;
|
|
60
|
+
if (resultSeparators !== undefined && span !== undefined) {
|
|
61
|
+
applyEndingOverrides(resultSeparators, span.start, pipe.contentSeparators);
|
|
62
|
+
applyEndingOverrides(resultSeparators, span.start, meta.endingOverrides);
|
|
63
|
+
}
|
|
64
|
+
const finalFileBytes = pipe.bom + (resultSeparators !== undefined
|
|
65
|
+
? joinSeparators(pipe.result, resultSeparators)
|
|
66
|
+
: restoreEndings(pipe.result, pipe.originalEnding));
|
|
51
67
|
assertByteLimit(finalFileBytes, path);
|
|
52
68
|
|
|
53
69
|
if (pipe.hadUtf8DecodeErrors) {
|
|
@@ -77,8 +93,10 @@ export async function commitEdit(pipe: PipelineResult, meta: CommitMeta): Promis
|
|
|
77
93
|
content: pipe.originalNormalized,
|
|
78
94
|
bom: pipe.bom,
|
|
79
95
|
originalEnding: pipe.originalEnding,
|
|
96
|
+
separators: pipe.originalSeparators,
|
|
80
97
|
hashes: pipe.originalHashes,
|
|
81
98
|
resultContent: pipe.result,
|
|
99
|
+
...(resultSeparators !== undefined ? { resultSeparators } : {}),
|
|
82
100
|
});
|
|
83
101
|
if (!undo.persisted) {
|
|
84
102
|
throw new Error(
|
|
@@ -107,8 +125,6 @@ export async function commitEdit(pipe: PipelineResult, meta: CommitMeta): Promis
|
|
|
107
125
|
removedLines: pipe.totalRemovedLines,
|
|
108
126
|
};
|
|
109
127
|
|
|
110
|
-
const span = meta.editAnchors ? spanForEdit(pipe.originalHashes, meta.editAnchors[0], meta.editAnchors[1], pipe.result) : undefined;
|
|
111
|
-
if (span && meta.anchorCarry !== undefined) span.carry = meta.anchorCarry;
|
|
112
128
|
let resultHashes: string[];
|
|
113
129
|
try {
|
|
114
130
|
resultHashes = await lineHashes(pipe.result, mutationTargetPath, {
|
|
@@ -120,6 +136,8 @@ export async function commitEdit(pipe: PipelineResult, meta: CommitMeta): Promis
|
|
|
120
136
|
const detail = error instanceof Error ? error.message : String(error);
|
|
121
137
|
throw new Error(`${detail} File was written; anchor finalization failed. One undo reverts. Call read for fresh anchors.`);
|
|
122
138
|
}
|
|
139
|
+
const writeReclaim = formatAnchorReclaimNotice(takeReclaimedPaths());
|
|
140
|
+
if (writeReclaim !== undefined) warnings.push(writeReclaim);
|
|
123
141
|
const successInput = {
|
|
124
142
|
path,
|
|
125
143
|
originalNormalized: pipe.originalNormalized,
|
package/src/config-ui.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Key, matchesKey, visibleWidth } from "@earendil-works/pi-tui";
|
|
|
2
2
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { readConfig, type Config } from "./config";
|
|
4
4
|
|
|
5
|
-
export type ConfigToggleKey = "autoRead" | "autoReadAll" | "autoReadAllIgnore" | "anchorGrepEnabled" | "requirePath" | "strictInput" | "diffContextLines";
|
|
5
|
+
export type ConfigToggleKey = "autoRead" | "autoReadAll" | "autoReadAllIgnore" | "anchorGrepEnabled" | "copyMoveEnabled" | "requirePath" | "strictInput" | "diffContextLines";
|
|
6
6
|
|
|
7
7
|
export interface ConfigRow {
|
|
8
8
|
key: ConfigToggleKey;
|
|
@@ -23,6 +23,7 @@ export function configRows(config: Config): ConfigRow[] {
|
|
|
23
23
|
{ key: "autoReadAllIgnore", label: "Ignore folders/files", hint: "Extra folders, files, or globs skipped by auto-read all (comma-separated)", enabled: (config.autoReadAllIgnore ?? []).length > 0, folders: config.autoReadAllIgnore ?? [] },
|
|
24
24
|
{ key: "diffContextLines", label: "Diff context", hint: "Surrounding lines in post-edit diffs (needs Auto-read)", enabled: config.autoRead !== false, value: config.diffContextLines ?? 1, disabled: config.autoRead === false },
|
|
25
25
|
{ key: "anchorGrepEnabled", label: "Anchor grep", hint: "anchor_grep tool (builtin grep off while on)", enabled: config.anchorGrepEnabled === true },
|
|
26
|
+
{ key: "copyMoveEnabled", label: "Copy/move", hint: "copy and move tools (both off while disabled)", enabled: config.copyMoveEnabled !== false },
|
|
26
27
|
{ key: "requirePath", label: "Require path", hint: "replace + insert need path (RPC visibility)", enabled: config.requirePath === true },
|
|
27
28
|
{ key: "strictInput", label: "Strict input", hint: "Reject auto-fixable slips instead of warnings", enabled: config.strictInput === true },
|
|
28
29
|
];
|
package/src/config.ts
CHANGED
|
@@ -13,6 +13,7 @@ export const MAX_DIFF_CONTEXT_LINES = 10;
|
|
|
13
13
|
export interface Config {
|
|
14
14
|
autoRead: boolean;
|
|
15
15
|
anchorGrepEnabled: boolean;
|
|
16
|
+
copyMoveEnabled?: boolean;
|
|
16
17
|
autoReadAll?: AutoReadAllMode;
|
|
17
18
|
autoReadAllIgnore?: string[];
|
|
18
19
|
requirePath?: boolean;
|
|
@@ -23,6 +24,7 @@ export interface Config {
|
|
|
23
24
|
const DEFAULT_CONFIG: Config = {
|
|
24
25
|
autoRead: true,
|
|
25
26
|
anchorGrepEnabled: true,
|
|
27
|
+
copyMoveEnabled: true,
|
|
26
28
|
autoReadAll: "off",
|
|
27
29
|
autoReadAllIgnore: [],
|
|
28
30
|
requirePath: false,
|
|
@@ -72,6 +74,7 @@ function parseConfig(content: string): Config {
|
|
|
72
74
|
}
|
|
73
75
|
const autoRead = parsed.autoRead;
|
|
74
76
|
const anchorGrepEnabled = parsed.anchorGrepEnabled;
|
|
77
|
+
const copyMoveEnabled = parsed.copyMoveEnabled;
|
|
75
78
|
const autoReadAll = parsed.autoReadAll;
|
|
76
79
|
const requirePath = parsed.requirePath;
|
|
77
80
|
const strictInput = parsed.strictInput;
|
|
@@ -80,6 +83,7 @@ function parseConfig(content: string): Config {
|
|
|
80
83
|
return {
|
|
81
84
|
autoRead: typeof autoRead === "boolean" ? autoRead : DEFAULT_CONFIG.autoRead,
|
|
82
85
|
anchorGrepEnabled: typeof anchorGrepEnabled === "boolean" ? anchorGrepEnabled : DEFAULT_CONFIG.anchorGrepEnabled,
|
|
86
|
+
copyMoveEnabled: typeof copyMoveEnabled === "boolean" ? copyMoveEnabled : DEFAULT_CONFIG.copyMoveEnabled,
|
|
83
87
|
autoReadAll: parseAutoReadAllMode(autoReadAll),
|
|
84
88
|
requirePath: typeof requirePath === "boolean" ? requirePath : DEFAULT_CONFIG.requirePath,
|
|
85
89
|
strictInput: typeof strictInput === "boolean" ? strictInput : DEFAULT_CONFIG.strictInput,
|
|
@@ -192,7 +196,7 @@ export async function writeConfig(config: Config): Promise<void> {
|
|
|
192
196
|
}
|
|
193
197
|
|
|
194
198
|
|
|
195
|
-
type ToggleKey = "autoRead" | "anchorGrepEnabled" | "requirePath" | "strictInput";
|
|
199
|
+
type ToggleKey = "autoRead" | "anchorGrepEnabled" | "copyMoveEnabled" | "requirePath" | "strictInput";
|
|
196
200
|
|
|
197
201
|
async function toggleFlag(key: ToggleKey): Promise<boolean> {
|
|
198
202
|
const config = await updateConfig((c) => { c[key] = !(c[key] === true); });
|
|
@@ -200,6 +204,7 @@ async function toggleFlag(key: ToggleKey): Promise<boolean> {
|
|
|
200
204
|
}
|
|
201
205
|
export const toggleAutoRead = (): Promise<boolean> => toggleFlag("autoRead");
|
|
202
206
|
export const toggleAnchorGrep = (): Promise<boolean> => toggleFlag("anchorGrepEnabled");
|
|
207
|
+
export const toggleCopyMove = (): Promise<boolean> => toggleFlag("copyMoveEnabled");
|
|
203
208
|
export async function cycleAutoReadAllMode(): Promise<AutoReadAllMode> {
|
|
204
209
|
let next: AutoReadAllMode = "off";
|
|
205
210
|
await updateConfig((c) => {
|
package/src/constants.ts
CHANGED
|
@@ -9,7 +9,7 @@ export const MAX_GREP_LINE_BYTES = 500;
|
|
|
9
9
|
export const MAX_DIFF_INPUT_BYTES = 1024 * 1024;
|
|
10
10
|
|
|
11
11
|
export const HASH_STORE_BUSY_TIMEOUT = 1000;
|
|
12
|
-
export const HASH_STORE_VERSION =
|
|
12
|
+
export const HASH_STORE_VERSION = 9;
|
|
13
13
|
export const NEW_CONTENT_NOT_ARRAY_MSG =
|
|
14
14
|
`[E_BAD_SHAPE] "replacement_lines" must be an array of strings, one per line (use [] to delete).`;
|
|
15
15
|
|
|
@@ -19,6 +19,9 @@ export const NUL_CONTENT_MSG =
|
|
|
19
19
|
export const ANCHOR_POOL_EXHAUSTED_PREFIX =
|
|
20
20
|
"[E_FILE_TOO_LARGE] The session's anchor pool is exhausted";
|
|
21
21
|
|
|
22
|
+
export const ANCHOR_RECLAIM_WARNING_CODE = "[W_ANCHOR_RECLAIMED]";
|
|
23
|
+
export const MAX_RECLAIMED_PATHS_REPORTED = 10;
|
|
24
|
+
|
|
22
25
|
export const AUTO_READ_ALL_CUSTOM_TYPE = "hashline-auto-read-all";
|
|
23
26
|
export const AUTO_READ_ALL_MAX_FILES = 500;
|
|
24
27
|
export const AUTO_READ_ALL_MAX_FILE_BYTES = 200_000;
|