Refactor out of utils

This commit is contained in:
Saoud Rizwan
2024-09-24 11:36:37 -04:00
parent dedf8e9e48
commit 7c21a4c833
14 changed files with 18 additions and 18 deletions

View File

@@ -0,0 +1,90 @@
import * as vscode from "vscode"
import * as path from "path"
import deepEqual from "fast-deep-equal"
export function getNewDiagnostics(
oldDiagnostics: [vscode.Uri, vscode.Diagnostic[]][],
newDiagnostics: [vscode.Uri, vscode.Diagnostic[]][]
): [vscode.Uri, vscode.Diagnostic[]][] {
const newProblems: [vscode.Uri, vscode.Diagnostic[]][] = []
const oldMap = new Map(oldDiagnostics)
for (const [uri, newDiags] of newDiagnostics) {
const oldDiags = oldMap.get(uri) || []
const newProblemsForUri = newDiags.filter((newDiag) => !oldDiags.some((oldDiag) => deepEqual(oldDiag, newDiag)))
if (newProblemsForUri.length > 0) {
newProblems.push([uri, newProblemsForUri])
}
}
return newProblems
}
// Usage:
// const oldDiagnostics = // ... your old diagnostics array
// const newDiagnostics = // ... your new diagnostics array
// const newProblems = getNewDiagnostics(oldDiagnostics, newDiagnostics);
// Example usage with mocks:
//
// // Mock old diagnostics
// const oldDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = [
// [vscode.Uri.file("/path/to/file1.ts"), [
// new vscode.Diagnostic(new vscode.Range(0, 0, 0, 10), "Old error in file1", vscode.DiagnosticSeverity.Error)
// ]],
// [vscode.Uri.file("/path/to/file2.ts"), [
// new vscode.Diagnostic(new vscode.Range(5, 5, 5, 15), "Old warning in file2", vscode.DiagnosticSeverity.Warning)
// ]]
// ];
//
// // Mock new diagnostics
// const newDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = [
// [vscode.Uri.file("/path/to/file1.ts"), [
// new vscode.Diagnostic(new vscode.Range(0, 0, 0, 10), "Old error in file1", vscode.DiagnosticSeverity.Error),
// new vscode.Diagnostic(new vscode.Range(2, 2, 2, 12), "New error in file1", vscode.DiagnosticSeverity.Error)
// ]],
// [vscode.Uri.file("/path/to/file2.ts"), [
// new vscode.Diagnostic(new vscode.Range(5, 5, 5, 15), "Old warning in file2", vscode.DiagnosticSeverity.Warning)
// ]],
// [vscode.Uri.file("/path/to/file3.ts"), [
// new vscode.Diagnostic(new vscode.Range(1, 1, 1, 11), "New error in file3", vscode.DiagnosticSeverity.Error)
// ]]
// ];
//
// const newProblems = getNewProblems(oldDiagnostics, newDiagnostics);
//
// console.log("New problems:");
// for (const [uri, diagnostics] of newProblems) {
// console.log(`File: ${uri.fsPath}`);
// for (const diagnostic of diagnostics) {
// console.log(`- ${diagnostic.message} (${diagnostic.range.start.line}:${diagnostic.range.start.character})`);
// }
// }
//
// // Expected output:
// // New problems:
// // File: /path/to/file1.ts
// // - New error in file1 (2:2)
// // File: /path/to/file3.ts
// // - New error in file3 (1:1)
// will return empty string if no errors/warnings
export function diagnosticsToProblemsString(diagnostics: [vscode.Uri, vscode.Diagnostic[]][], cwd: string): string {
let result = ""
for (const [uri, fileDiagnostics] of diagnostics) {
const problems = fileDiagnostics.filter(
(d) => d.severity === vscode.DiagnosticSeverity.Error || d.severity === vscode.DiagnosticSeverity.Warning
)
if (problems.length > 0) {
result += `\n\n${path.relative(cwd, uri.fsPath).toPosix()}`
for (const diagnostic of problems) {
let severity = diagnostic.severity === vscode.DiagnosticSeverity.Error ? "Error" : "Warning"
const line = diagnostic.range.start.line + 1 // VSCode lines are 0-indexed
const source = diagnostic.source ? `${diagnostic.source} ` : ""
result += `\n- [${source}${severity}] Line ${line}: ${diagnostic.message}`
}
}
}
return result.trim()
}

View File

@@ -0,0 +1,94 @@
import { Anthropic } from "@anthropic-ai/sdk"
import os from "os"
import * as path from "path"
import * as vscode from "vscode"
export async function downloadTask(dateTs: number, conversationHistory: Anthropic.MessageParam[]) {
// File name
const date = new Date(dateTs)
const month = date.toLocaleString("en-US", { month: "short" }).toLowerCase()
const day = date.getDate()
const year = date.getFullYear()
let hours = date.getHours()
const minutes = date.getMinutes().toString().padStart(2, "0")
const seconds = date.getSeconds().toString().padStart(2, "0")
const ampm = hours >= 12 ? "pm" : "am"
hours = hours % 12
hours = hours ? hours : 12 // the hour '0' should be '12'
const fileName = `claude_dev_task_${month}-${day}-${year}_${hours}-${minutes}-${seconds}-${ampm}.md`
// Generate markdown
const markdownContent = conversationHistory
.map((message) => {
const role = message.role === "user" ? "**User:**" : "**Assistant:**"
const content = Array.isArray(message.content)
? message.content.map((block) => formatContentBlockToMarkdown(block, conversationHistory)).join("\n")
: message.content
return `${role}\n\n${content}\n\n`
})
.join("---\n\n")
// Prompt user for save location
const saveUri = await vscode.window.showSaveDialog({
filters: { Markdown: ["md"] },
defaultUri: vscode.Uri.file(path.join(os.homedir(), "Downloads", fileName)),
})
if (saveUri) {
// Write content to the selected location
await vscode.workspace.fs.writeFile(saveUri, Buffer.from(markdownContent))
vscode.window.showTextDocument(saveUri, { preview: true })
}
}
export function formatContentBlockToMarkdown(
block:
| Anthropic.TextBlockParam
| Anthropic.ImageBlockParam
| Anthropic.ToolUseBlockParam
| Anthropic.ToolResultBlockParam,
messages: Anthropic.MessageParam[]
): string {
switch (block.type) {
case "text":
return block.text
case "image":
return `[Image]`
case "tool_use":
let input: string
if (typeof block.input === "object" && block.input !== null) {
input = Object.entries(block.input)
.map(([key, value]) => `${key.charAt(0).toUpperCase() + key.slice(1)}: ${value}`)
.join("\n")
} else {
input = String(block.input)
}
return `[Tool Use: ${block.name}]\n${input}`
case "tool_result":
const toolName = findToolName(block.tool_use_id, messages)
if (typeof block.content === "string") {
return `[${toolName}${block.is_error ? " (Error)" : ""}]\n${block.content}`
} else if (Array.isArray(block.content)) {
return `[${toolName}${block.is_error ? " (Error)" : ""}]\n${block.content
.map((contentBlock) => formatContentBlockToMarkdown(contentBlock, messages))
.join("\n")}`
} else {
return `[${toolName}${block.is_error ? " (Error)" : ""}]`
}
default:
return "[Unexpected content type]"
}
}
function findToolName(toolCallId: string, messages: Anthropic.MessageParam[]): string {
for (const message of messages) {
if (Array.isArray(message.content)) {
for (const block of message.content) {
if (block.type === "tool_use" && block.id === toolCallId) {
return block.name
}
}
}
}
return "Unknown Tool"
}

View File

@@ -0,0 +1,55 @@
import * as path from "path"
// @ts-ignore-next-line
import pdf from "pdf-parse/lib/pdf-parse"
import mammoth from "mammoth"
import fs from "fs/promises"
import { isBinaryFile } from "isbinaryfile"
export async function extractTextFromFile(filePath: string): Promise<string> {
try {
await fs.access(filePath)
} catch (error) {
throw new Error(`File not found: ${filePath}`)
}
const fileExtension = path.extname(filePath).toLowerCase()
switch (fileExtension) {
case ".pdf":
return extractTextFromPDF(filePath)
case ".docx":
return extractTextFromDOCX(filePath)
case ".ipynb":
return extractTextFromIPYNB(filePath)
default:
const isBinary = await isBinaryFile(filePath).catch(() => false)
if (!isBinary) {
return await fs.readFile(filePath, "utf8")
} else {
throw new Error(`Cannot read text for file type: ${fileExtension}`)
}
}
}
async function extractTextFromPDF(filePath: string): Promise<string> {
const dataBuffer = await fs.readFile(filePath)
const data = await pdf(dataBuffer)
return data.text
}
async function extractTextFromDOCX(filePath: string): Promise<string> {
const result = await mammoth.extractRawText({ path: filePath })
return result.value
}
async function extractTextFromIPYNB(filePath: string): Promise<string> {
const data = await fs.readFile(filePath, "utf8")
const notebook = JSON.parse(data)
let extractedText = ""
for (const cell of notebook.cells) {
if ((cell.cell_type === "markdown" || cell.cell_type === "code") && cell.source) {
extractedText += cell.source.join("\n") + "\n"
}
}
return extractedText
}

View File

@@ -0,0 +1,51 @@
import * as path from "path"
import * as os from "os"
import * as vscode from "vscode"
import { arePathsEqual } from "../../utils/path-helpers"
export async function openImage(dataUri: string) {
const matches = dataUri.match(/^data:image\/([a-zA-Z]+);base64,(.+)$/)
if (!matches) {
vscode.window.showErrorMessage("Invalid data URI format")
return
}
const [, format, base64Data] = matches
const imageBuffer = Buffer.from(base64Data, "base64")
const tempFilePath = path.join(os.tmpdir(), `temp_image_${Date.now()}.${format}`)
try {
await vscode.workspace.fs.writeFile(vscode.Uri.file(tempFilePath), imageBuffer)
await vscode.commands.executeCommand("vscode.open", vscode.Uri.file(tempFilePath))
} catch (error) {
vscode.window.showErrorMessage(`Error opening image: ${error}`)
}
}
export async function openFile(absolutePath: string) {
try {
const uri = vscode.Uri.file(absolutePath)
// Check if the document is already open in a tab group that's not in the active editor's column. If it is, then close it (if not dirty) so that we don't duplicate tabs
try {
for (const group of vscode.window.tabGroups.all) {
const existingTab = group.tabs.find(
(tab) => tab.input instanceof vscode.TabInputText && arePathsEqual(tab.input.uri.fsPath, uri.fsPath)
)
if (existingTab) {
const activeColumn = vscode.window.activeTextEditor?.viewColumn
const tabColumn = vscode.window.tabGroups.all.find((group) =>
group.tabs.includes(existingTab)
)?.viewColumn
if (activeColumn && activeColumn !== tabColumn && !existingTab.isDirty) {
await vscode.window.tabGroups.close(existingTab)
}
break
}
}
} catch {} // not essential, sometimes tab operations fail
const document = await vscode.workspace.openTextDocument(uri)
await vscode.window.showTextDocument(document, { preview: false })
} catch (error) {
vscode.window.showErrorMessage(`Could not open file!`)
}
}

View File

@@ -0,0 +1,45 @@
import * as vscode from "vscode"
import fs from "fs/promises"
import * as path from "path"
export async function selectImages(): Promise<string[]> {
const options: vscode.OpenDialogOptions = {
canSelectMany: true,
openLabel: "Select",
filters: {
Images: ["png", "jpg", "jpeg", "webp"], // supported by anthropic and openrouter
},
}
const fileUris = await vscode.window.showOpenDialog(options)
if (!fileUris || fileUris.length === 0) {
return []
}
return await Promise.all(
fileUris.map(async (uri) => {
const imagePath = uri.fsPath
const buffer = await fs.readFile(imagePath)
const base64 = buffer.toString("base64")
const mimeType = getMimeType(imagePath)
const dataUrl = `data:${mimeType};base64,${base64}`
return dataUrl
})
)
}
function getMimeType(filePath: string): string {
const ext = path.extname(filePath).toLowerCase()
switch (ext) {
case ".png":
return "image/png"
case ".jpeg":
case ".jpg":
return "image/jpeg"
case ".webp":
return "image/webp"
default:
throw new Error(`Unsupported file type: ${ext}`)
}
}

View File

@@ -0,0 +1,42 @@
import * as vscode from "vscode"
/*
Used to get user's current python environment (unnecessary now that we use the IDE's terminal)
${await (async () => {
try {
const pythonEnvPath = await getPythonEnvPath()
if (pythonEnvPath) {
return `\nPython Environment: ${pythonEnvPath}`
}
} catch {}
return ""
})()}
*/
export async function getPythonEnvPath(): Promise<string | undefined> {
const pythonExtension = vscode.extensions.getExtension("ms-python.python")
if (!pythonExtension) {
return undefined
}
// Ensure the Python extension is activated
if (!pythonExtension.isActive) {
// if the python extension is not active, we can assume the project is not a python project
return undefined
}
// Access the Python extension API
const pythonApi = pythonExtension.exports
// Get the active environment path for the current workspace
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]
if (!workspaceFolder) {
return undefined
}
// Get the active python environment path for the current workspace
const pythonEnv = await pythonApi?.environments?.getActiveEnvironmentPath(workspaceFolder.uri)
if (pythonEnv && pythonEnv.path) {
return pythonEnv.path
} else {
return undefined
}
}