|
14 | 14 | * limitations under the License. |
15 | 15 | */ |
16 | 16 |
|
17 | | -import { applyHunks, computeHunks, getAssistantText, type DiffHunk, type LlmProvider } from "@codesetu/core"; |
| 17 | +import { |
| 18 | + applyHunks, |
| 19 | + computeHunks, |
| 20 | + getAssistantText, |
| 21 | + type DiffHunk, |
| 22 | + type LlmProvider, |
| 23 | +} from "@codesetu/core"; |
18 | 24 | import * as vscode from "vscode"; |
19 | 25 |
|
20 | 26 | import type { CodeSetuConfiguration } from "./configuration"; |
@@ -73,110 +79,115 @@ export function registerEditCommand(options: RegisterEditCommandOptions): vscode |
73 | 79 | const command = vscode.commands.registerCommand( |
74 | 80 | "codesetu.editSelection", |
75 | 81 | async (presetInstruction?: unknown) => { |
76 | | - const editor = getActiveOrLastEditor(vscode); |
77 | | - if (editor === undefined) { |
78 | | - void vscode.window.showWarningMessage( |
79 | | - "CodeSetu: open a file (and optionally select code) before running Edit with CodeSetu.", |
80 | | - ); |
81 | | - return; |
82 | | - } |
| 82 | + const editor = getActiveOrLastEditor(vscode); |
| 83 | + if (editor === undefined) { |
| 84 | + void vscode.window.showWarningMessage( |
| 85 | + "CodeSetu: open a file (and optionally select code) before running Edit with CodeSetu.", |
| 86 | + ); |
| 87 | + return; |
| 88 | + } |
83 | 89 |
|
84 | | - const document = editor.document; |
85 | | - const range = editor.selection.isEmpty |
86 | | - ? fullRange(document) |
87 | | - : new vscode.Range(editor.selection.start, editor.selection.end); |
88 | | - const target = document.getText(range); |
89 | | - if (target.trim().length === 0) { |
90 | | - void vscode.window.showWarningMessage("CodeSetu: nothing to edit — the file is empty."); |
91 | | - return; |
92 | | - } |
| 90 | + const document = editor.document; |
| 91 | + const range = editor.selection.isEmpty |
| 92 | + ? fullRange(document) |
| 93 | + : new vscode.Range(editor.selection.start, editor.selection.end); |
| 94 | + const target = document.getText(range); |
| 95 | + if (target.trim().length === 0) { |
| 96 | + void vscode.window.showWarningMessage("CodeSetu: nothing to edit — the file is empty."); |
| 97 | + return; |
| 98 | + } |
93 | 99 |
|
94 | | - // When invoked from the chat composer's `/edit <instruction>`, the |
95 | | - // instruction is passed in and we skip the prompt; otherwise ask for it. |
96 | | - const preset = typeof presetInstruction === "string" ? presetInstruction.trim() : ""; |
97 | | - const instruction = |
98 | | - preset.length > 0 |
99 | | - ? preset |
100 | | - : await vscode.window.showInputBox({ |
101 | | - title: "Edit with CodeSetu", |
102 | | - prompt: editor.selection.isEmpty |
103 | | - ? "Describe the change for the whole file" |
104 | | - : "Describe the change for the selection", |
105 | | - placeHolder: "e.g. add error handling and JSDoc", |
106 | | - ignoreFocusOut: true, |
107 | | - }); |
108 | | - if (instruction === undefined || instruction.trim().length === 0) { |
109 | | - return; |
110 | | - } |
| 100 | + // When invoked from the chat composer's `/edit <instruction>`, the |
| 101 | + // instruction is passed in and we skip the prompt; otherwise ask for it. |
| 102 | + const preset = typeof presetInstruction === "string" ? presetInstruction.trim() : ""; |
| 103 | + const instruction = |
| 104 | + preset.length > 0 |
| 105 | + ? preset |
| 106 | + : await vscode.window.showInputBox({ |
| 107 | + title: "Edit with CodeSetu", |
| 108 | + prompt: editor.selection.isEmpty |
| 109 | + ? "Describe the change for the whole file" |
| 110 | + : "Describe the change for the selection", |
| 111 | + placeHolder: "e.g. add error handling and JSDoc", |
| 112 | + ignoreFocusOut: true, |
| 113 | + }); |
| 114 | + if (instruction === undefined || instruction.trim().length === 0) { |
| 115 | + return; |
| 116 | + } |
111 | 117 |
|
112 | | - let newCode: string; |
113 | | - try { |
114 | | - newCode = await vscode.window.withProgress( |
115 | | - { location: vscode.ProgressLocation.Notification, title: "CodeSetu is editing…" }, |
116 | | - () => requestEdit(options, document.languageId, target, instruction), |
117 | | - ); |
118 | | - } catch (error: unknown) { |
119 | | - options.outputChannel.appendLine(`Edit failed: ${formatErrorMessage(error)}`); |
120 | | - void vscode.window.showErrorMessage(`CodeSetu edit failed: ${formatErrorMessage(error)}`); |
121 | | - return; |
122 | | - } |
| 118 | + let newCode: string; |
| 119 | + try { |
| 120 | + newCode = await vscode.window.withProgress( |
| 121 | + { location: vscode.ProgressLocation.Notification, title: "CodeSetu is editing…" }, |
| 122 | + () => requestEdit(options, document.languageId, target, instruction), |
| 123 | + ); |
| 124 | + } catch (error: unknown) { |
| 125 | + options.outputChannel.appendLine(`Edit failed: ${formatErrorMessage(error)}`); |
| 126 | + void vscode.window.showErrorMessage(`CodeSetu edit failed: ${formatErrorMessage(error)}`); |
| 127 | + return; |
| 128 | + } |
123 | 129 |
|
124 | | - if (newCode.trim().length === 0) { |
125 | | - void vscode.window.showWarningMessage("CodeSetu returned no edit."); |
126 | | - return; |
127 | | - } |
| 130 | + if (newCode.trim().length === 0) { |
| 131 | + void vscode.window.showWarningMessage("CodeSetu returned no edit."); |
| 132 | + return; |
| 133 | + } |
128 | 134 |
|
129 | | - const proposedFull = spliceText( |
130 | | - document.getText(), |
131 | | - document.offsetAt(range.start), |
132 | | - document.offsetAt(range.end), |
133 | | - newCode, |
134 | | - ); |
| 135 | + const proposedFull = spliceText( |
| 136 | + document.getText(), |
| 137 | + document.offsetAt(range.start), |
| 138 | + document.offsetAt(range.end), |
| 139 | + newCode, |
| 140 | + ); |
135 | 141 |
|
136 | | - counter += 1; |
137 | | - const proposedUri = vscode.Uri.from({ |
138 | | - scheme: EDIT_SCHEME, |
139 | | - path: `/${counter}/${baseName(document.uri)}`, |
140 | | - }); |
141 | | - proposed.set(proposedUri, proposedFull); |
| 142 | + counter += 1; |
| 143 | + const proposedUri = vscode.Uri.from({ |
| 144 | + scheme: EDIT_SCHEME, |
| 145 | + path: `/${counter}/${baseName(document.uri)}`, |
| 146 | + }); |
| 147 | + proposed.set(proposedUri, proposedFull); |
142 | 148 |
|
143 | | - try { |
144 | | - await vscode.commands.executeCommand( |
145 | | - "vscode.diff", |
146 | | - document.uri, |
147 | | - proposedUri, |
148 | | - `CodeSetu edit: ${baseName(document.uri)} (review)`, |
149 | | - { preview: true }, |
150 | | - ); |
| 149 | + try { |
| 150 | + await vscode.commands.executeCommand( |
| 151 | + "vscode.diff", |
| 152 | + document.uri, |
| 153 | + proposedUri, |
| 154 | + `CodeSetu edit: ${baseName(document.uri)} (review)`, |
| 155 | + { preview: true }, |
| 156 | + ); |
151 | 157 |
|
152 | | - const hunks = computeHunks(target, newCode); |
153 | | - // Only offer per-hunk selection when there's more than one independent |
154 | | - // change — a single hunk is just the all-or-nothing case. |
155 | | - const canChooseHunks = hunks.length > 1; |
156 | | - const actions = canChooseHunks ? ["Apply All", "Choose Hunks…", "Discard"] : ["Apply", "Discard"]; |
| 158 | + const hunks = computeHunks(target, newCode); |
| 159 | + // Only offer per-hunk selection when there's more than one independent |
| 160 | + // change — a single hunk is just the all-or-nothing case. |
| 161 | + const canChooseHunks = hunks.length > 1; |
| 162 | + const actions = canChooseHunks |
| 163 | + ? ["Apply All", "Choose Hunks…", "Discard"] |
| 164 | + : ["Apply", "Discard"]; |
157 | 165 |
|
158 | | - const choice = await vscode.window.showInformationMessage( |
159 | | - "Apply this CodeSetu edit?", |
160 | | - { modal: false }, |
161 | | - ...actions, |
162 | | - ); |
| 166 | + const choice = await vscode.window.showInformationMessage( |
| 167 | + "Apply this CodeSetu edit?", |
| 168 | + { modal: false }, |
| 169 | + ...actions, |
| 170 | + ); |
163 | 171 |
|
164 | | - if (choice === "Apply" || choice === "Apply All") { |
165 | | - await applyRangeEdit(document.uri, range, newCode); |
166 | | - } else if (choice === "Choose Hunks…") { |
167 | | - const accepted = await pickHunks(hunks); |
168 | | - if (accepted !== undefined) { |
169 | | - if (accepted.length === 0) { |
170 | | - void vscode.window.showInformationMessage("CodeSetu: no hunks selected — nothing applied."); |
171 | | - } else { |
172 | | - await applyRangeEdit(document.uri, range, applyHunks(target, hunks, accepted)); |
| 172 | + if (choice === "Apply" || choice === "Apply All") { |
| 173 | + await applyRangeEdit(document.uri, range, newCode); |
| 174 | + } else if (choice === "Choose Hunks…") { |
| 175 | + const accepted = await pickHunks(hunks); |
| 176 | + if (accepted !== undefined) { |
| 177 | + if (accepted.length === 0) { |
| 178 | + void vscode.window.showInformationMessage( |
| 179 | + "CodeSetu: no hunks selected — nothing applied.", |
| 180 | + ); |
| 181 | + } else { |
| 182 | + await applyRangeEdit(document.uri, range, applyHunks(target, hunks, accepted)); |
| 183 | + } |
173 | 184 | } |
174 | 185 | } |
| 186 | + } finally { |
| 187 | + proposed.delete(proposedUri); |
175 | 188 | } |
176 | | - } finally { |
177 | | - proposed.delete(proposedUri); |
178 | | - } |
179 | | - }); |
| 189 | + }, |
| 190 | + ); |
180 | 191 |
|
181 | 192 | return [command, providerRegistration]; |
182 | 193 | } |
@@ -223,7 +234,10 @@ async function pickHunks(hunks: readonly DiffHunk[]): Promise<number[] | undefin |
223 | 234 | function hunkPreview(hunk: DiffHunk): string { |
224 | 235 | const removed = hunk.oldLines.length > 0 ? `− ${hunk.oldLines[0]!.trim()}` : ""; |
225 | 236 | const added = hunk.newLines.length > 0 ? `+ ${hunk.newLines[0]!.trim()}` : ""; |
226 | | - return [removed, added].filter((part) => part.length > 0).join(" ").slice(0, 120); |
| 237 | + return [removed, added] |
| 238 | + .filter((part) => part.length > 0) |
| 239 | + .join(" ") |
| 240 | + .slice(0, 120); |
227 | 241 | } |
228 | 242 |
|
229 | 243 | async function requestEdit( |
|
0 commit comments