@nextclaw/ui 0.11.21 → 0.11.22

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 (120) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/assets/{ChannelsList-ByHWHkQS.js → ChannelsList-Zeys_w43.js} +6 -6
  3. package/dist/assets/ChatPage-DWOU_8P6.js +43 -0
  4. package/dist/assets/DocBrowser-B9OaZjmg.js +1 -0
  5. package/dist/assets/{DocBrowser-3y_NHZ71.js → DocBrowser-BmtBLFU0.js} +1 -1
  6. package/dist/assets/{DocBrowserContext-CVJuwCcw.js → DocBrowserContext-YIKkPb76.js} +1 -1
  7. package/dist/assets/{LogoBadge-D8fyilO-.js → LogoBadge-F7ZWdxLT.js} +1 -1
  8. package/dist/assets/MarketplacePage-BfaTTqN6.js +1 -0
  9. package/dist/assets/{MarketplacePage-CmhsZXr1.js → MarketplacePage-Cd4faegU.js} +2 -2
  10. package/dist/assets/{McpMarketplacePage-C7PkCYbp.js → McpMarketplacePage-C09Ngs7O.js} +2 -2
  11. package/dist/assets/ModelConfig-DJgdcgvQ.js +1 -0
  12. package/dist/assets/ProvidersList-w0rVFIBf.js +1 -0
  13. package/dist/assets/RemoteAccessPage-BJ_ckkOV.js +1 -0
  14. package/dist/assets/RuntimeConfig-Cmn2xPQO.js +1 -0
  15. package/dist/assets/{SearchConfig-Dm7r2yfp.js → SearchConfig-BT13qpR_.js} +1 -1
  16. package/dist/assets/{SecretsConfig-BBP_mbQh.js → SecretsConfig-CvqEVn0B.js} +2 -2
  17. package/dist/assets/{SessionsConfig-6wNJloZN.js → SessionsConfig-DHHcYznk.js} +2 -2
  18. package/dist/assets/{book-open-B26jGBjY.js → book-open-CXoF5nQC.js} +1 -1
  19. package/dist/assets/chat-session-display-VW6ZMvZP.js +1 -0
  20. package/dist/assets/{chunk-JZWAC4HX-B-4B29RN.js → chunk-JZWAC4HX-CvRWvTy5.js} +1 -1
  21. package/dist/assets/{config-BaC29Qf-.js → config-DJswxxE8.js} +1 -1
  22. package/dist/assets/{createLucideIcon-DiFAvXmK.js → createLucideIcon-CjGHOWb6.js} +1 -1
  23. package/dist/assets/{dist-pCfWPG1A.js → dist-Cl2QB-2y.js} +1 -1
  24. package/dist/assets/{dist-kW_O3kyZ.js → dist-nqTTbVdA.js} +1 -1
  25. package/dist/assets/{external-link-D5-p-Gmm.js → external-link-tIO7zING.js} +1 -1
  26. package/dist/assets/{hash-BlwrSV0q.js → hash-JWUyl1pT.js} +1 -1
  27. package/dist/assets/i18n-CDHMXlRZ.js +1 -0
  28. package/dist/assets/index-BlH4-cBw.css +1 -0
  29. package/dist/assets/{index-DvKS3L9j.js → index-C6d0xmtm.js} +3 -3
  30. package/dist/assets/{label-RyXfZqkP.js → label-BIpeNu4r.js} +1 -1
  31. package/dist/assets/loader-circle-Cs8XVFTw.js +1 -0
  32. package/dist/assets/{logos-Bpl8QTgI.js → logos-DThdM9lk.js} +1 -1
  33. package/dist/assets/{page-layout--S0YBU0W.js → page-layout-D3Xo605Z.js} +1 -1
  34. package/dist/assets/plus-PHf8q-Ct.js +1 -0
  35. package/dist/assets/{popover-BEjfbEwy.js → popover-BJRUGA_H.js} +1 -1
  36. package/dist/assets/provider-models-bz5y28rq.js +1 -0
  37. package/dist/assets/{react-BuSP2-8B.js → react-7ZHqQtEV.js} +1 -1
  38. package/dist/assets/refresh-ccw-CC6-_QuL.js +1 -0
  39. package/dist/assets/{save-DPPPpD_c.js → save-DJM5RRWW.js} +1 -1
  40. package/dist/assets/search-C91yH_6y.js +1 -0
  41. package/dist/assets/{security-config-6t78Ph-I.js → security-config-T5zpg16O.js} +1 -1
  42. package/dist/assets/{select-CT50pzod.js → select-DSkTc61S.js} +1 -1
  43. package/dist/assets/skeleton-Dzg-HOiN.js +1 -0
  44. package/dist/assets/{status-dot-BbBqRHfh.js → status-dot-LNBlDu3q.js} +1 -1
  45. package/dist/assets/{switch-D3l6AcCk.js → switch-Bo-Y46HZ.js} +1 -1
  46. package/dist/assets/tabs-custom-DXv507_2.js +1 -0
  47. package/dist/assets/{trash-2-B2_AGVE3.js → trash-2-DFZmW6Gg.js} +1 -1
  48. package/dist/assets/useConfirmDialog-Bs5Ll17m.js +1 -0
  49. package/dist/assets/{useMutation-BzCrO8j-.js → useMutation-DrZrOgVL.js} +1 -1
  50. package/dist/assets/x-D7Q1yqSF.js +1 -0
  51. package/dist/index.html +18 -18
  52. package/package.json +3 -3
  53. package/src/api/ncp-session.test.ts +37 -0
  54. package/src/api/ncp-session.ts +29 -1
  55. package/src/api/server-path.ts +23 -0
  56. package/src/api/types.ts +41 -0
  57. package/src/components/chat/ChatConversationPanel.test.tsx +43 -7
  58. package/src/components/chat/ChatConversationPanel.tsx +23 -17
  59. package/src/components/chat/ChatSidebar.test.tsx +2 -2
  60. package/src/components/chat/ChatSidebar.tsx +2 -2
  61. package/src/components/chat/adapters/chat-input-bar.adapter.test.ts +1 -0
  62. package/src/components/chat/adapters/chat-input-bar.adapter.ts +7 -2
  63. package/src/components/chat/adapters/chat-message-part.adapter.ts +13 -9
  64. package/src/components/chat/adapters/chat-message.adapter.test.ts +76 -4
  65. package/src/components/chat/adapters/{chat-message.file-operation-card.ts → file-operation/card.ts} +74 -181
  66. package/src/components/chat/adapters/{chat-message.file-operation-diff.ts → file-operation/diff.ts} +178 -188
  67. package/src/components/chat/adapters/file-operation/line-builder.ts +249 -0
  68. package/src/components/chat/adapters/file-operation/record-readers.ts +233 -0
  69. package/src/components/chat/chat-composer-state.ts +3 -3
  70. package/src/components/chat/chat-session-display.test.ts +21 -0
  71. package/src/components/chat/chat-session-display.ts +6 -1
  72. package/src/components/chat/containers/chat-input-bar.container.tsx +21 -24
  73. package/src/components/chat/hooks/use-chat-session-label.ts +19 -0
  74. package/src/components/chat/hooks/use-chat-session-project.test.tsx +117 -0
  75. package/src/components/chat/hooks/use-chat-session-project.ts +40 -0
  76. package/src/components/chat/{chat-session-label.service.ts → hooks/use-chat-session-update.ts} +11 -7
  77. package/src/components/chat/managers/chat-session-list.manager.ts +5 -1
  78. package/src/components/chat/ncp/NcpChatPage.tsx +55 -17
  79. package/src/components/chat/ncp/ncp-chat-page-data.test.ts +33 -0
  80. package/src/components/chat/ncp/ncp-chat-page-data.ts +21 -15
  81. package/src/components/chat/ncp/ncp-session-adapter.test.ts +3 -0
  82. package/src/components/chat/ncp/ncp-session-adapter.ts +16 -0
  83. package/src/components/chat/session-header/chat-session-header-actions.test.tsx +63 -0
  84. package/src/components/chat/session-header/chat-session-header-actions.tsx +95 -0
  85. package/src/components/chat/session-header/chat-session-header-menu-item.tsx +35 -0
  86. package/src/components/chat/session-header/chat-session-project-badge.test.tsx +66 -0
  87. package/src/components/chat/session-header/chat-session-project-badge.tsx +102 -0
  88. package/src/components/chat/session-header/chat-session-project-dialog.tsx +34 -0
  89. package/src/components/chat/stores/chat-input.store.ts +6 -3
  90. package/src/components/chat/stores/chat-thread.store.ts +6 -2
  91. package/src/components/path-picker/server-path-picker-dialog.test.tsx +92 -0
  92. package/src/components/path-picker/server-path-picker-dialog.tsx +282 -0
  93. package/src/hooks/server-path/use-server-path-browse.ts +19 -0
  94. package/src/hooks/useConfig.ts +26 -1
  95. package/src/lib/i18n/i18n-language-owner.ts +94 -0
  96. package/src/lib/i18n/i18n.path-picker.ts +12 -0
  97. package/src/lib/i18n.chat.ts +23 -0
  98. package/src/lib/i18n.ts +21 -84
  99. package/src/lib/session-project/session-project.utils.ts +30 -0
  100. package/dist/assets/ChatPage-FdT3pDnw.js +0 -42
  101. package/dist/assets/DocBrowser-CMdPdbZj.js +0 -1
  102. package/dist/assets/MarketplacePage-9oKmxN2n.js +0 -1
  103. package/dist/assets/ModelConfig-DmCY6jWM.js +0 -1
  104. package/dist/assets/ProvidersList-ClT-34aX.js +0 -1
  105. package/dist/assets/RemoteAccessPage-B6hUZl1O.js +0 -1
  106. package/dist/assets/RuntimeConfig-C5aqliGk.js +0 -1
  107. package/dist/assets/chat-session-display-Bjmn4aIZ.js +0 -1
  108. package/dist/assets/i18n-CSytxMFI.js +0 -1
  109. package/dist/assets/index-CUy6doWo.css +0 -1
  110. package/dist/assets/loader-circle-B2J777gj.js +0 -1
  111. package/dist/assets/plus-CM9XJ0Tf.js +0 -1
  112. package/dist/assets/provider-models-C8JQUd1E.js +0 -1
  113. package/dist/assets/search-Ctaw34Kp.js +0 -1
  114. package/dist/assets/skeleton-Bycyb0zU.js +0 -1
  115. package/dist/assets/tabs-custom-TZQ5WPWP.js +0 -1
  116. package/dist/assets/useConfirmDialog-BDpdjfIO.js +0 -1
  117. package/dist/assets/x-CHOBE-63.js +0 -1
  118. /package/dist/assets/{config-hints-fGnUjDe9.js → config-hints-WtpHP_DW.js} +0 -0
  119. /package/dist/assets/{config-layout-B-7erZRN.js → config-layout-LQ10ozRC.js} +0 -0
  120. /package/dist/assets/{marketplace-localization-CXeGRf6E.js → marketplace-localization-CxSTG9wr.js} +0 -0
@@ -1,4 +1,12 @@
1
1
  import type { ChatFileOperationLineViewModel } from "@nextclaw/agent-chat-ui";
2
+ import {
3
+ buildLineDiff,
4
+ buildPreviewLines,
5
+ createLine,
6
+ incrementLineNumber,
7
+ readUnifiedDiffHunkStart,
8
+ splitLines,
9
+ } from "@/components/chat/adapters/file-operation/line-builder";
2
10
 
3
11
  export type ParsedBlock = {
4
12
  path: string;
@@ -10,19 +18,15 @@ export type ParsedBlock = {
10
18
  };
11
19
 
12
20
  const MAX_VISIBLE_DIFF_LINES = 120;
13
- const MAX_DIFF_MATRIX_CELLS = 12_000;
14
-
15
- function splitLines(value: string): string[] {
16
- const normalized = value.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
17
- return normalized === "" ? [] : normalized.split("\n");
18
- }
19
21
 
20
22
  function buildCaption(params: {
21
23
  operation?: string | null;
22
24
  lines: ChatFileOperationLineViewModel[];
23
25
  }): string | undefined {
24
26
  const additions = params.lines.filter((line) => line.kind === "add").length;
25
- const deletions = params.lines.filter((line) => line.kind === "remove").length;
27
+ const deletions = params.lines.filter(
28
+ (line) => line.kind === "remove",
29
+ ).length;
26
30
  const parts: string[] = [];
27
31
  const normalizedOperation = params.operation?.trim().toLowerCase() ?? "";
28
32
  if (normalizedOperation && normalizedOperation !== "update") {
@@ -37,9 +41,39 @@ function buildCaption(params: {
37
41
  return parts.length > 0 ? parts.join(" · ") : undefined;
38
42
  }
39
43
 
40
- function limitLines(
41
- lines: ChatFileOperationLineViewModel[],
42
- ): { lines: ChatFileOperationLineViewModel[]; truncated: boolean } {
44
+ function readDefaultDiffStartLines(params: {
45
+ operation?: string | null;
46
+ beforeText?: string | null;
47
+ afterText?: string | null;
48
+ oldStartLine?: number | null;
49
+ newStartLine?: number | null;
50
+ }): {
51
+ oldStartLine?: number;
52
+ newStartLine?: number;
53
+ } {
54
+ const normalizedOperation = params.operation?.trim().toLowerCase() ?? "";
55
+ const oldStartLine =
56
+ typeof params.oldStartLine === "number"
57
+ ? params.oldStartLine
58
+ : (normalizedOperation === "delete" ||
59
+ normalizedOperation === "remove") &&
60
+ params.beforeText != null
61
+ ? 1
62
+ : undefined;
63
+ const newStartLine =
64
+ typeof params.newStartLine === "number"
65
+ ? params.newStartLine
66
+ : (normalizedOperation === "write" || normalizedOperation === "add") &&
67
+ params.afterText != null
68
+ ? 1
69
+ : undefined;
70
+ return { oldStartLine, newStartLine };
71
+ }
72
+
73
+ function limitLines(lines: ChatFileOperationLineViewModel[]): {
74
+ lines: ChatFileOperationLineViewModel[];
75
+ truncated: boolean;
76
+ } {
43
77
  if (lines.length <= MAX_VISIBLE_DIFF_LINES) {
44
78
  return { lines, truncated: false };
45
79
  }
@@ -53,26 +87,25 @@ export function buildRawPreviewBlock(params: {
53
87
  path: string;
54
88
  text: string;
55
89
  operation?: string | null;
90
+ oldStartLine?: number | null;
91
+ newStartLine?: number | null;
56
92
  }): ParsedBlock | null {
57
93
  const previewText = params.text.trim();
58
94
  if (!previewText) {
59
95
  return null;
60
96
  }
61
- const previewKind = params.operation?.trim().toLowerCase() === "write" ? "add" : "context";
62
- const lines = splitLines(previewText).map((line, index) =>
63
- previewKind === "add"
64
- ? {
65
- kind: "add" as const,
66
- text: line,
67
- newLineNumber: index + 1,
68
- }
69
- : {
70
- kind: "context" as const,
71
- text: line,
72
- oldLineNumber: index + 1,
73
- newLineNumber: index + 1,
74
- },
75
- );
97
+ const previewKind =
98
+ params.operation?.trim().toLowerCase() === "write" ? "add" : "context";
99
+ const oldStartLine =
100
+ typeof params.oldStartLine === "number" ? params.oldStartLine : 1;
101
+ const newStartLine =
102
+ typeof params.newStartLine === "number" ? params.newStartLine : 1;
103
+ const lines = buildPreviewLines({
104
+ text: previewText,
105
+ kind: previewKind,
106
+ oldStartLine,
107
+ newStartLine,
108
+ });
76
109
  return {
77
110
  path: params.path,
78
111
  display: "preview",
@@ -84,149 +117,21 @@ export function buildRawPreviewBlock(params: {
84
117
  };
85
118
  }
86
119
 
87
- function buildFallbackDiffLines(params: {
88
- beforeLines: string[];
89
- afterLines: string[];
90
- }): ChatFileOperationLineViewModel[] {
91
- return [
92
- ...params.beforeLines.map((line, index) => ({
93
- kind: "remove" as const,
94
- text: line,
95
- oldLineNumber: index + 1,
96
- })),
97
- ...params.afterLines.map((line, index) => ({
98
- kind: "add" as const,
99
- text: line,
100
- newLineNumber: index + 1,
101
- })),
102
- ];
103
- }
104
-
105
- function buildLcsMatrix(params: {
106
- beforeLines: string[];
107
- afterLines: string[];
108
- }): number[][] {
109
- const matrix: number[][] = Array.from(
110
- { length: params.beforeLines.length + 1 },
111
- () => Array.from({ length: params.afterLines.length + 1 }, () => 0),
112
- );
113
- for (let beforeIndex = params.beforeLines.length - 1; beforeIndex >= 0; beforeIndex -= 1) {
114
- for (let afterIndex = params.afterLines.length - 1; afterIndex >= 0; afterIndex -= 1) {
115
- matrix[beforeIndex]![afterIndex] =
116
- params.beforeLines[beforeIndex] === params.afterLines[afterIndex]
117
- ? (matrix[beforeIndex + 1]![afterIndex + 1] ?? 0) + 1
118
- : Math.max(
119
- matrix[beforeIndex + 1]![afterIndex] ?? 0,
120
- matrix[beforeIndex]![afterIndex + 1] ?? 0,
121
- );
122
- }
123
- }
124
- return matrix;
125
- }
126
-
127
- function appendRemainingDiffLines(params: {
128
- lines: ChatFileOperationLineViewModel[];
129
- beforeLines: string[];
130
- afterLines: string[];
131
- beforeIndex: number;
132
- afterIndex: number;
133
- oldLineNumber: number;
134
- newLineNumber: number;
135
- }): void {
136
- for (let index = params.beforeIndex; index < params.beforeLines.length; index += 1) {
137
- params.lines.push({
138
- kind: "remove",
139
- text: params.beforeLines[index] ?? "",
140
- oldLineNumber: params.oldLineNumber,
141
- });
142
- params.oldLineNumber += 1;
143
- }
144
- for (let index = params.afterIndex; index < params.afterLines.length; index += 1) {
145
- params.lines.push({
146
- kind: "add",
147
- text: params.afterLines[index] ?? "",
148
- newLineNumber: params.newLineNumber,
149
- });
150
- params.newLineNumber += 1;
151
- }
152
- }
153
-
154
- function buildLineDiff(
155
- beforeText: string,
156
- afterText: string,
157
- ): ChatFileOperationLineViewModel[] {
158
- const beforeLines = splitLines(beforeText);
159
- const afterLines = splitLines(afterText);
160
- if (beforeLines.length * afterLines.length > MAX_DIFF_MATRIX_CELLS) {
161
- return buildFallbackDiffLines({
162
- beforeLines,
163
- afterLines,
164
- });
165
- }
166
-
167
- const lcs = buildLcsMatrix({
168
- beforeLines,
169
- afterLines,
170
- });
171
- const lines: ChatFileOperationLineViewModel[] = [];
172
- let beforeIndex = 0;
173
- let afterIndex = 0;
174
- let oldLineNumber = 1;
175
- let newLineNumber = 1;
176
- while (beforeIndex < beforeLines.length && afterIndex < afterLines.length) {
177
- if (beforeLines[beforeIndex] === afterLines[afterIndex]) {
178
- lines.push({
179
- kind: "context",
180
- text: beforeLines[beforeIndex] ?? "",
181
- oldLineNumber,
182
- newLineNumber,
183
- });
184
- beforeIndex += 1;
185
- afterIndex += 1;
186
- oldLineNumber += 1;
187
- newLineNumber += 1;
188
- continue;
189
- }
190
-
191
- if ((lcs[beforeIndex + 1]![afterIndex] ?? 0) >= (lcs[beforeIndex]![afterIndex + 1] ?? 0)) {
192
- lines.push({
193
- kind: "remove",
194
- text: beforeLines[beforeIndex] ?? "",
195
- oldLineNumber,
196
- });
197
- beforeIndex += 1;
198
- oldLineNumber += 1;
199
- continue;
200
- }
201
-
202
- lines.push({
203
- kind: "add",
204
- text: afterLines[afterIndex] ?? "",
205
- newLineNumber,
206
- });
207
- afterIndex += 1;
208
- newLineNumber += 1;
209
- }
210
-
211
- appendRemainingDiffLines({
212
- lines,
213
- beforeLines,
214
- afterLines,
215
- beforeIndex,
216
- afterIndex,
217
- oldLineNumber,
218
- newLineNumber,
219
- });
220
- return lines;
221
- }
222
-
223
120
  export function buildFullReplaceBlock(params: {
224
121
  path: string;
225
122
  beforeText?: string | null;
226
123
  afterText?: string | null;
227
124
  operation?: string | null;
125
+ oldStartLine?: number | null;
126
+ newStartLine?: number | null;
228
127
  }): ParsedBlock | null {
229
- const lines = buildLineDiff(params.beforeText ?? "", params.afterText ?? "");
128
+ const { oldStartLine, newStartLine } = readDefaultDiffStartLines(params);
129
+ const lines = buildLineDiff({
130
+ beforeText: params.beforeText ?? "",
131
+ afterText: params.afterText ?? "",
132
+ oldStartLine,
133
+ newStartLine,
134
+ });
230
135
  const limited = limitLines(lines);
231
136
  if (limited.lines.length === 0) {
232
137
  return null;
@@ -268,7 +173,10 @@ function updateApplyPatchCursor(params: {
268
173
  }): boolean {
269
174
  if (params.line.startsWith("*** Update File: ")) {
270
175
  params.flushCurrent();
271
- params.setCurrent(params.line.slice("*** Update File: ".length).trim(), "update");
176
+ params.setCurrent(
177
+ params.line.slice("*** Update File: ".length).trim(),
178
+ "update",
179
+ );
272
180
  return true;
273
181
  }
274
182
  if (params.line.startsWith("*** Add File: ")) {
@@ -278,36 +186,69 @@ function updateApplyPatchCursor(params: {
278
186
  }
279
187
  if (params.line.startsWith("*** Delete File: ")) {
280
188
  params.flushCurrent();
281
- params.setCurrent(params.line.slice("*** Delete File: ".length).trim(), "delete");
189
+ params.setCurrent(
190
+ params.line.slice("*** Delete File: ".length).trim(),
191
+ "delete",
192
+ );
282
193
  return true;
283
194
  }
284
195
  return false;
285
196
  }
286
197
 
287
- function appendPatchLine(
288
- currentLines: ChatFileOperationLineViewModel[],
289
- line: string,
290
- ): void {
198
+ function appendPatchLine(params: {
199
+ currentLines: ChatFileOperationLineViewModel[];
200
+ line: string;
201
+ oldLineNumber?: number;
202
+ newLineNumber?: number;
203
+ }): {
204
+ oldLineNumber?: number;
205
+ newLineNumber?: number;
206
+ } {
207
+ const { currentLines, line } = params;
291
208
  if (line.startsWith("+")) {
292
- currentLines.push({
293
- kind: "add",
294
- text: line.slice(1),
295
- });
296
- return;
209
+ currentLines.push(
210
+ createLine({
211
+ kind: "add",
212
+ text: line.slice(1),
213
+ newLineNumber: params.newLineNumber,
214
+ }),
215
+ );
216
+ return {
217
+ oldLineNumber: params.oldLineNumber,
218
+ newLineNumber: incrementLineNumber(params.newLineNumber),
219
+ };
297
220
  }
298
221
  if (line.startsWith("-")) {
299
- currentLines.push({
300
- kind: "remove",
301
- text: line.slice(1),
302
- });
303
- return;
222
+ currentLines.push(
223
+ createLine({
224
+ kind: "remove",
225
+ text: line.slice(1),
226
+ oldLineNumber: params.oldLineNumber,
227
+ }),
228
+ );
229
+ return {
230
+ oldLineNumber: incrementLineNumber(params.oldLineNumber),
231
+ newLineNumber: params.newLineNumber,
232
+ };
304
233
  }
305
234
  if (line.startsWith(" ")) {
306
- currentLines.push({
307
- kind: "context",
308
- text: line.slice(1),
309
- });
235
+ currentLines.push(
236
+ createLine({
237
+ kind: "context",
238
+ text: line.slice(1),
239
+ oldLineNumber: params.oldLineNumber,
240
+ newLineNumber: params.newLineNumber,
241
+ }),
242
+ );
243
+ return {
244
+ oldLineNumber: incrementLineNumber(params.oldLineNumber),
245
+ newLineNumber: incrementLineNumber(params.newLineNumber),
246
+ };
310
247
  }
248
+ return {
249
+ oldLineNumber: params.oldLineNumber,
250
+ newLineNumber: params.newLineNumber,
251
+ };
311
252
  }
312
253
 
313
254
  function parseApplyPatchText(patchText: string): ParsedBlock[] {
@@ -315,11 +256,15 @@ function parseApplyPatchText(patchText: string): ParsedBlock[] {
315
256
  let currentPath: string | null = null;
316
257
  let currentOperation: string | null = null;
317
258
  let currentLines: ChatFileOperationLineViewModel[] = [];
259
+ let currentOldLineNumber: number | undefined;
260
+ let currentNewLineNumber: number | undefined;
318
261
 
319
262
  const flushCurrent = () => {
320
263
  if (!currentPath) {
321
264
  currentLines = [];
322
265
  currentOperation = null;
266
+ currentOldLineNumber = undefined;
267
+ currentNewLineNumber = undefined;
323
268
  return;
324
269
  }
325
270
  blocks.push(
@@ -332,6 +277,8 @@ function parseApplyPatchText(patchText: string): ParsedBlock[] {
332
277
  currentPath = null;
333
278
  currentOperation = null;
334
279
  currentLines = [];
280
+ currentOldLineNumber = undefined;
281
+ currentNewLineNumber = undefined;
335
282
  };
336
283
 
337
284
  for (const line of splitLines(patchText)) {
@@ -347,13 +294,30 @@ function parseApplyPatchText(patchText: string): ParsedBlock[] {
347
294
  ) {
348
295
  continue;
349
296
  }
350
- if (line.startsWith("*** Move to: ") || line.startsWith("*** Begin Patch") || line.startsWith("*** End Patch")) {
297
+ if (
298
+ line.startsWith("*** Move to: ") ||
299
+ line.startsWith("*** Begin Patch") ||
300
+ line.startsWith("*** End Patch")
301
+ ) {
351
302
  continue;
352
303
  }
353
- if (line.startsWith("@@") || !currentPath) {
304
+ if (line.startsWith("@@")) {
305
+ const hunkStart = readUnifiedDiffHunkStart(line);
306
+ currentOldLineNumber = hunkStart?.oldLineNumber;
307
+ currentNewLineNumber = hunkStart?.newLineNumber;
354
308
  continue;
355
309
  }
356
- appendPatchLine(currentLines, line);
310
+ if (!currentPath) {
311
+ continue;
312
+ }
313
+ const nextCursor = appendPatchLine({
314
+ currentLines,
315
+ line,
316
+ oldLineNumber: currentOldLineNumber,
317
+ newLineNumber: currentNewLineNumber,
318
+ });
319
+ currentOldLineNumber = nextCursor.oldLineNumber;
320
+ currentNewLineNumber = nextCursor.newLineNumber;
357
321
  }
358
322
 
359
323
  flushCurrent();
@@ -364,10 +328,14 @@ function parseUnifiedDiffText(patchText: string): ParsedBlock[] {
364
328
  const blocks: ParsedBlock[] = [];
365
329
  let currentPath: string | null = null;
366
330
  let currentLines: ChatFileOperationLineViewModel[] = [];
331
+ let currentOldLineNumber: number | undefined;
332
+ let currentNewLineNumber: number | undefined;
367
333
 
368
334
  const flushCurrent = () => {
369
335
  if (!currentPath) {
370
336
  currentLines = [];
337
+ currentOldLineNumber = undefined;
338
+ currentNewLineNumber = undefined;
371
339
  return;
372
340
  }
373
341
  blocks.push(
@@ -379,18 +347,40 @@ function parseUnifiedDiffText(patchText: string): ParsedBlock[] {
379
347
  );
380
348
  currentPath = null;
381
349
  currentLines = [];
350
+ currentOldLineNumber = undefined;
351
+ currentNewLineNumber = undefined;
382
352
  };
383
353
 
384
354
  for (const line of splitLines(patchText)) {
385
355
  if (line.startsWith("+++ ")) {
386
356
  flushCurrent();
387
- currentPath = line.slice(4).trim().replace(/^b\//, "").replace(/^a\//, "");
357
+ currentPath = line
358
+ .slice(4)
359
+ .trim()
360
+ .replace(/^b\//, "")
361
+ .replace(/^a\//, "");
362
+ continue;
363
+ }
364
+ if (line.startsWith("--- ")) {
388
365
  continue;
389
366
  }
390
- if (line.startsWith("--- ") || line.startsWith("@@") || !currentPath) {
367
+ if (line.startsWith("@@")) {
368
+ const hunkStart = readUnifiedDiffHunkStart(line);
369
+ currentOldLineNumber = hunkStart?.oldLineNumber;
370
+ currentNewLineNumber = hunkStart?.newLineNumber;
391
371
  continue;
392
372
  }
393
- appendPatchLine(currentLines, line);
373
+ if (!currentPath) {
374
+ continue;
375
+ }
376
+ const nextCursor = appendPatchLine({
377
+ currentLines,
378
+ line,
379
+ oldLineNumber: currentOldLineNumber,
380
+ newLineNumber: currentNewLineNumber,
381
+ });
382
+ currentOldLineNumber = nextCursor.oldLineNumber;
383
+ currentNewLineNumber = nextCursor.newLineNumber;
394
384
  }
395
385
 
396
386
  flushCurrent();