@ladbabynpm/picc-edit 0.1.0 → 0.1.1

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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # picc-edit
2
2
 
3
+ [![npm downloads](https://img.shields.io/npm/dt/@ladbabynpm/picc-edit.svg)](https://www.npmjs.com/package/@ladbabynpm/picc-edit)
4
+
3
5
  Claude Code style **Edit** tool for [pi](https://pi.dev) — a faithful port of Claude Code's `Edit` tool, overriding pi's built-in `edit`.
4
6
 
5
7
  Part of [picc](https://github.com/Ladbaby/picc), a pi agent setup mirroring Claude Code's harness.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ladbabynpm/picc-edit",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Claude Code style Edit tool for pi — a faithful port of Claude Code's `Edit` (exact string replacement, quote normalization, read-first guard, replace_all, structured diff).",
5
5
  "type": "module",
6
6
  "main": "index.ts",
package/src/diff.ts CHANGED
@@ -62,13 +62,18 @@ export function countLinesChanged(
62
62
  }
63
63
 
64
64
  /**
65
- * Generate a display-oriented diff string using a **unified** line-number
66
- * counter (the new file's line number for all lines).
65
+ * Generate a display-oriented diff string matching Claude Code's `Edit`
66
+ * output format:
67
+ * - The line number is right-aligned, then the `+`/`-` marker sits to its
68
+ * right (context lines leave the marker column blank).
69
+ * - Line numbering uses **two independent counters**:
70
+ * - Added: new-file line number (counter advances)
71
+ * - Removed: old-file line number (old counter advances; the new counter
72
+ * does NOT advance for removed lines)
73
+ * - Context: new-file line number (both counters advance)
67
74
  *
68
- * Numbering rules:
69
- * - Context: ` NNN content`, counter advances
70
- * - Added: `+NNN content`, counter advances
71
- * - Removed: `-NNN content`, counter does NOT advance
75
+ * Example (old = `a,b,c,d,e`; new = `a,c,e` — removes `b` and `d`):
76
+ * ` 1 a`, ` 2 - b`, ` 2 c`, ` 4 - d`, ` 3 e`
72
77
  *
73
78
  * Context collapsing mirrors pi's `generateDiffString`: only up to
74
79
  * `contextLines` lines are shown before/after a change; larger gaps are
@@ -87,9 +92,19 @@ export function generateDisplayDiff(
87
92
  const maxLineNum = Math.max(oldLines.length, newLines.length);
88
93
  const lineNumWidth = String(maxLineNum).length;
89
94
 
95
+ let oldLineNum = 1;
90
96
  let newLineNum = 1;
91
97
  let lastWasChange = false;
92
98
 
99
+ // Emit one diff line. `marker` is "+", "-" or " " (blank column for
100
+ // context). Layout: `<gutter-space><right-aligned num><space><marker><space><content>`,
101
+ // so changed/context columns align and the marker sits to the right of the
102
+ // line number (matching Claude Code's `7 +` / `10 -` rendering).
103
+ const emitLine = (num: number, marker: string, line: string) => {
104
+ const lineNum = String(num).padStart(lineNumWidth, " ");
105
+ output.push(` ${lineNum} ${marker} ${line}`);
106
+ };
107
+
93
108
  for (let i = 0; i < parts.length; i++) {
94
109
  const part = parts[i];
95
110
  const raw = part.value.split("\n");
@@ -99,12 +114,12 @@ export function generateDisplayDiff(
99
114
 
100
115
  if (part.added || part.removed) {
101
116
  for (const line of raw) {
102
- const lineNum = String(newLineNum).padStart(lineNumWidth, " ");
103
117
  if (part.added) {
104
- output.push(`+${lineNum} ${line}`);
118
+ emitLine(newLineNum, "+", line);
105
119
  newLineNum++;
106
120
  } else {
107
- output.push(`-${lineNum} ${line}`);
121
+ emitLine(oldLineNum, "-", line);
122
+ oldLineNum++;
108
123
  }
109
124
  }
110
125
  lastWasChange = true;
@@ -115,54 +130,41 @@ export function generateDisplayDiff(
115
130
  const hasLeadingChange = lastWasChange;
116
131
  const hasTrailingChange = nextPartIsChange;
117
132
 
133
+ const emitContext = (lines: string[]) => {
134
+ for (const line of lines) {
135
+ emitLine(newLineNum, " ", line);
136
+ oldLineNum++;
137
+ newLineNum++;
138
+ }
139
+ };
140
+ const emitEllipsis = (skipped: number) => {
141
+ output.push(` ${"".padStart(lineNumWidth, " ")} ...`);
142
+ oldLineNum += skipped;
143
+ newLineNum += skipped;
144
+ };
145
+
118
146
  if (hasLeadingChange && hasTrailingChange) {
119
147
  if (raw.length <= contextLines * 2) {
120
- for (const line of raw) {
121
- const lineNum = String(newLineNum).padStart(lineNumWidth, " ");
122
- output.push(` ${lineNum} ${line}`);
123
- newLineNum++;
124
- }
148
+ emitContext(raw);
125
149
  } else {
126
150
  const leading = raw.slice(0, contextLines);
127
151
  const trailing = raw.slice(raw.length - contextLines);
128
152
  const skipped = raw.length - leading.length - trailing.length;
129
- for (const line of leading) {
130
- const lineNum = String(newLineNum).padStart(lineNumWidth, " ");
131
- output.push(` ${lineNum} ${line}`);
132
- newLineNum++;
133
- }
134
- output.push(` ${"".padStart(lineNumWidth, " ")} ...`);
135
- newLineNum += skipped;
136
- for (const line of trailing) {
137
- const lineNum = String(newLineNum).padStart(lineNumWidth, " ");
138
- output.push(` ${lineNum} ${line}`);
139
- newLineNum++;
140
- }
153
+ emitContext(leading);
154
+ emitEllipsis(skipped);
155
+ emitContext(trailing);
141
156
  }
142
157
  } else if (hasLeadingChange) {
143
158
  const shown = raw.slice(0, contextLines);
144
159
  const skipped = raw.length - shown.length;
145
- for (const line of shown) {
146
- const lineNum = String(newLineNum).padStart(lineNumWidth, " ");
147
- output.push(` ${lineNum} ${line}`);
148
- newLineNum++;
149
- }
150
- if (skipped > 0) {
151
- output.push(` ${"".padStart(lineNumWidth, " ")} ...`);
152
- newLineNum += skipped;
153
- }
160
+ emitContext(shown);
161
+ if (skipped > 0) emitEllipsis(skipped);
154
162
  } else if (hasTrailingChange) {
155
163
  const skipped = Math.max(0, raw.length - contextLines);
156
- if (skipped > 0) {
157
- output.push(` ${"".padStart(lineNumWidth, " ")} ...`);
158
- newLineNum += skipped;
159
- }
160
- for (const line of raw.slice(skipped)) {
161
- const lineNum = String(newLineNum).padStart(lineNumWidth, " ");
162
- output.push(` ${lineNum} ${line}`);
163
- newLineNum++;
164
- }
164
+ if (skipped > 0) emitEllipsis(skipped);
165
+ emitContext(raw.slice(skipped));
165
166
  } else {
167
+ oldLineNum += raw.length;
166
168
  newLineNum += raw.length;
167
169
  }
168
170
  lastWasChange = false;
package/src/renderDiff.ts CHANGED
@@ -26,16 +26,21 @@ function renderAddedLine(line: string): string {
26
26
  return `${ADDED_LINE_FG}${line}${FG_RESET}`;
27
27
  }
28
28
 
29
- /** Parse diff line to extract prefix, line number, and content.
30
- * Format: "+123 content" or "-123 content" or " 123 content" or " ..." */
29
+ /** Parse diff line to extract line number, marker, and content.
30
+ * Claude Code layout: `<gutter-space><right-aligned num><space><marker><space><content>`
31
+ * where the marker is "+" (added), "-" (removed) or " " (context), i.e.
32
+ * " 7 + content" / " 10 - content" / " 7 content". The number is
33
+ * right-padded, so `\s*\d+` captures the padding (kept for alignment). The
34
+ * collapsed ` ...` marker has no digits and falls through (returns null).
35
+ */
31
36
  function parseDiffLine(line: string): {
32
37
  prefix: string;
33
38
  lineNum: string;
34
39
  content: string;
35
40
  } | null {
36
- const match = line.match(/^([+-\s])(\s*\d*)\s(.*)$/);
41
+ const match = line.match(/^ (\s*\d+) ([-+ ]) (.*)$/);
37
42
  if (!match) return null;
38
- return { prefix: match[1], lineNum: match[2], content: match[3] };
43
+ return { prefix: match[2], lineNum: match[1], content: match[3] };
39
44
  }
40
45
 
41
46
  /** Replace tabs with spaces for consistent rendering. */
@@ -144,30 +149,30 @@ export function renderDiff(
144
149
  result.push(
145
150
  theme.fg(
146
151
  "toolDiffRemoved",
147
- `-${removed.lineNum} ${removedLine}`,
152
+ ` ${removed.lineNum} - ${removedLine}`,
148
153
  ),
149
154
  );
150
- result.push(renderAddedLine(`+${added.lineNum} ${addedLine}`));
155
+ result.push(renderAddedLine(` ${added.lineNum} + ${addedLine}`));
151
156
  } else {
152
157
  // Show all removed lines first, then all added lines
153
158
  for (const removed of removedLines) {
154
159
  result.push(
155
160
  theme.fg(
156
161
  "toolDiffRemoved",
157
- `-${removed.lineNum} ${replaceTabs(removed.content)}`,
162
+ ` ${removed.lineNum} - ${replaceTabs(removed.content)}`,
158
163
  ),
159
164
  );
160
165
  }
161
166
  for (const added of addedLines) {
162
167
  result.push(
163
- renderAddedLine(`+${added.lineNum} ${replaceTabs(added.content)}`),
168
+ renderAddedLine(` ${added.lineNum} + ${replaceTabs(added.content)}`),
164
169
  );
165
170
  }
166
171
  }
167
172
  } else if (parsed.prefix === "+") {
168
173
  // Standalone added line
169
174
  result.push(
170
- renderAddedLine(`+${parsed.lineNum} ${replaceTabs(parsed.content)}`),
175
+ renderAddedLine(` ${parsed.lineNum} + ${replaceTabs(parsed.content)}`),
171
176
  );
172
177
  i++;
173
178
  } else {
@@ -175,7 +180,7 @@ export function renderDiff(
175
180
  result.push(
176
181
  theme.fg(
177
182
  "toolDiffContext",
178
- ` ${parsed.lineNum} ${replaceTabs(parsed.content)}`,
183
+ ` ${parsed.lineNum} ${replaceTabs(parsed.content)}`,
179
184
  ),
180
185
  );
181
186
  i++;