Last active
March 3, 2025 06:21
-
-
Save laiso/df56c55f2b7f0581fcfebed4751499a0 to your computer and use it in GitHub Desktop.
mini-cline.ts: https://zenn.dev/laiso/articles/ae259902fe7c5c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as vscode from 'vscode'; | |
import * as fs from 'fs'; | |
export function activate(context: vscode.ExtensionContext) { | |
const outputChannel = vscode.window.createOutputChannel('Print Debug Info'); | |
const disposable = vscode.commands.registerCommand('min-cline.RunMain', async () => { | |
outputChannel.clear(); | |
const diagnostics = vscode.languages.getDiagnostics(); | |
const formattedDiagnostics = await formatDiagnosticsWithContent(diagnostics); | |
const [model] = await vscode.lm.selectChatModels({ | |
vendor: 'copilot', | |
family: 'gpt-4o' | |
}); | |
const prompt = [ | |
vscode.LanguageModelChatMessage.User( | |
'Please generate SEARCH/REPLACE blocks to fix the following problems in the codebase:' | |
), | |
vscode.LanguageModelChatMessage.User( | |
formattedDiagnostics | |
) | |
]; | |
const response = await model.sendRequest(prompt, { | |
tools: [{ | |
name: "replace_in_file", | |
description: "Request to replace sections of content in an existing file using SEARCH/REPLACE blocks that define exact changes to specific parts of the file.", | |
inputSchema: { | |
type: "object", | |
properties: { | |
path: { | |
type: "string", | |
description: "The path of the file to modify (relative to the current working directory ${cwd.toPosix()})" | |
}, | |
diff: { | |
type: "string", | |
description: "One or more SEARCH/REPLACE blocks following this exact format: <<<<<<< SEARCH [exact content to find] ======= [new content to replace with] >>>>>>> REPLACE" | |
} | |
}, | |
required: ["path", "diff"] | |
} | |
} | |
] | |
}); | |
const toolCallResults: { path: string, diff: string }[] = []; | |
for await (const chunk of response.stream) { | |
outputChannel.append('\n\n--- Tool Call Result ---\n'); | |
outputChannel.append(`chunk.args.path: ${chunk.input.path}\n`); | |
outputChannel.append(`chunk.args.diff: ${chunk.input.diff}\n`); | |
outputChannel.append('------------------------\n\n'); | |
toolCallResults.push({ | |
path: chunk.input.path, | |
diff: chunk.input.diff | |
}); | |
} | |
outputChannel.show(); | |
if (toolCallResults.length > 0) { | |
const applyAction = 'Apply Changes'; | |
const userChoice = await vscode.window.showInformationMessage( | |
'Do you want to apply the suggested changes?', | |
applyAction | |
); | |
if (userChoice === applyAction) { | |
let appliedCount = 0; | |
for (const result of toolCallResults) { | |
const success = await applyChanges(result.path, result.diff); | |
if (success) { | |
appliedCount++; | |
} | |
} | |
vscode.window.showInformationMessage( | |
`Applied changes to ${appliedCount} of ${toolCallResults.length} files.` | |
); | |
} | |
} | |
vscode.window.showInformationMessage('Task completed!'); | |
}); | |
context.subscriptions.push(disposable); | |
// --- Utility Functions --- | |
async function getFileContent(uri: vscode.Uri): Promise<string> { | |
try { | |
const document = await vscode.workspace.openTextDocument(uri); | |
return document.getText(); | |
} catch (error) { | |
return `Unable to read file: ${error}`; | |
} | |
} | |
async function formatDiagnosticsWithContent(diagnostics: [vscode.Uri, vscode.Diagnostic[]][]): Promise<string> { | |
if (diagnostics.length === 0) { | |
return 'No diagnostics found.'; | |
} | |
let result = ''; | |
for (const [uri, fileDiagnostics] of diagnostics) { | |
if (fileDiagnostics.length === 0) { | |
continue; | |
} | |
result += `\nFile: ${uri.fsPath}\n`; | |
result += `${'-'.repeat(80)}\n`; | |
const content = await getFileContent(uri); | |
result += `Content:\n${content}\n\n`; | |
result += `Diagnostics:\n`; | |
result += `${'-'.repeat(80)}\n`; | |
for (const diagnostic of fileDiagnostics) { | |
const line = diagnostic.range.start.line + 1; | |
const column = diagnostic.range.start.character + 1; | |
const severity = getSeverityString(diagnostic.severity); | |
const message = diagnostic.message.replace(/\n/g, '\n '); | |
result += `[${severity}] Line ${line}, Column ${column}: ${message}\n`; | |
} | |
result += '\n'; | |
} | |
return result; | |
} | |
function getSeverityString(severity: vscode.DiagnosticSeverity | undefined): string { | |
switch (severity) { | |
case vscode.DiagnosticSeverity.Error: | |
return 'Error'; | |
case vscode.DiagnosticSeverity.Warning: | |
return 'Warning'; | |
case vscode.DiagnosticSeverity.Information: | |
return 'Info'; | |
case vscode.DiagnosticSeverity.Hint: | |
return 'Hint'; | |
default: | |
return 'Unknown'; | |
} | |
} | |
async function applyChanges(filePath: string, diff: string): Promise<boolean> { | |
try { | |
if (!fs.existsSync(filePath)) { | |
vscode.window.showErrorMessage(`File not found: ${filePath}`); | |
return false; | |
} | |
const fileContent = fs.readFileSync(filePath, 'utf8'); | |
const searchReplacePattern = /<<<<<<< SEARCH\n([\s\S]*?)=======\n([\s\S]*?)>>>>>>> REPLACE/g; | |
let matches; | |
let modifiedContent = fileContent; | |
while ((matches = searchReplacePattern.exec(diff)) !== null) { | |
const searchText = matches[1]; | |
const replaceText = matches[2]; | |
if (!modifiedContent.includes(searchText)) { | |
vscode.window.showWarningMessage(`Search text not found in ${filePath}`); | |
return false; | |
} | |
modifiedContent = modifiedContent.replace(searchText, replaceText); | |
} | |
fs.writeFileSync(filePath, modifiedContent, 'utf8'); | |
const fileUri = vscode.Uri.file(filePath); | |
vscode.window.showTextDocument(fileUri); | |
return true; | |
} catch (error) { | |
vscode.window.showErrorMessage(`Error applying changes: ${error}`); | |
return false; | |
} | |
} | |
} | |
export function deactivate() { } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment