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

@@ -13,7 +13,7 @@ import { ApiHandler, buildApiHandler } from "../api"
import { TerminalManager } from "../integrations/terminal/TerminalManager"
import { parseSourceCodeForDefinitionsTopLevel } from "../services/tree-sitter"
import { listFiles } from "../services/glob/list-files"
import { ClaudeDevProvider } from "./webviews/ClaudeDevProvider"
import { ClaudeDevProvider } from "./webview/ClaudeDevProvider"
import { ApiConfiguration } from "../shared/api"
import { ClaudeRequestResult } from "../shared/ClaudeRequestResult"
import { combineApiRequests } from "../shared/combineApiRequests"
@@ -25,11 +25,11 @@ import { Tool, ToolName } from "../shared/Tool"
import { ClaudeAskResponse } from "../shared/WebviewMessage"
import { findLast, findLastIndex, formatContentBlockToMarkdown } from "../utils"
import { truncateHalfConversation } from "../utils/context-management"
import { extractTextFromFile } from "../utils/extract-text"
import { extractTextFromFile } from "../integrations/misc/extract-text"
import { regexSearchFiles } from "../services/ripgrep"
import { parseMentions } from "../utils/context-mentions"
import { parseMentions } from "./mentions/context-mentions"
import { UrlContentFetcher } from "../services/browser/UrlContentFetcher"
import { diagnosticsToProblemsString, getNewDiagnostics } from "../utils/diagnostics"
import { diagnosticsToProblemsString, getNewDiagnostics } from "../integrations/diagnostics"
import { arePathsEqual } from "../utils/path-helpers"
const SYSTEM_PROMPT = async (

View File

@@ -0,0 +1,179 @@
import * as vscode from "vscode"
import * as path from "path"
import { openFile } from "../../integrations/misc/open-file"
import { UrlContentFetcher } from "../../services/browser/UrlContentFetcher"
import { mentionRegexGlobal } from "../../shared/context-mentions"
import fs from "fs/promises"
import { extractTextFromFile } from "../../integrations/misc/extract-text"
import { isBinaryFile } from "isbinaryfile"
import { diagnosticsToProblemsString } from "../../integrations/diagnostics"
export function openMention(mention?: string): void {
if (!mention) {
return
}
if (mention.startsWith("/")) {
const relPath = mention.slice(1)
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0)
if (!cwd) {
return
}
const absPath = path.resolve(cwd, relPath)
if (mention.endsWith("/")) {
vscode.commands.executeCommand("revealInExplorer", vscode.Uri.file(absPath))
// vscode.commands.executeCommand("vscode.openFolder", , { forceNewWindow: false }) opens in new window
} else {
openFile(absPath)
}
} else if (mention === "problems") {
vscode.commands.executeCommand("workbench.actions.view.problems")
} else if (mention.startsWith("http")) {
vscode.env.openExternal(vscode.Uri.parse(mention))
}
}
export async function parseMentions(text: string, cwd: string, urlContentFetcher: UrlContentFetcher): Promise<string> {
const mentions: Set<string> = new Set()
let parsedText = text.replace(mentionRegexGlobal, (match, mention) => {
mentions.add(mention)
if (mention.startsWith("http")) {
return `'${mention}' (see below for site content)`
} else if (mention.startsWith("/")) {
const mentionPath = mention.slice(1) // Remove the leading '/'
return mentionPath.endsWith("/")
? `'${mentionPath}' (see below for folder content)`
: `'${mentionPath}' (see below for file content)`
} else if (mention === "problems") {
return `Workspace Problems (see below for diagnostics)`
}
return match
})
const urlMention = Array.from(mentions).find((mention) => mention.startsWith("http"))
let launchBrowserError: Error | undefined
if (urlMention) {
try {
await urlContentFetcher.launchBrowser()
} catch (error) {
launchBrowserError = error
vscode.window.showErrorMessage(`Error fetching content for ${urlMention}: ${error.message}`)
}
}
for (const mention of mentions) {
if (mention.startsWith("http")) {
let result: string
if (launchBrowserError) {
result = `Error fetching content: ${launchBrowserError.message}`
} else {
try {
const markdown = await urlContentFetcher.urlToMarkdown(mention)
result = markdown
} catch (error) {
vscode.window.showErrorMessage(`Error fetching content for ${mention}: ${error.message}`)
result = `Error fetching content: ${error.message}`
}
}
parsedText += `\n\n<url_content url="${mention}">\n${result}\n</url_content>`
} else if (mention.startsWith("/")) {
const mentionPath = mention.slice(1)
try {
const content = await getFileOrFolderContent(mentionPath, cwd)
if (mention.endsWith("/")) {
parsedText += `\n\n<folder_content path="${mentionPath}">\n${content}\n</folder_content>`
} else {
parsedText += `\n\n<file_content path="${mentionPath}">\n${content}\n</file_content>`
}
} catch (error) {
if (mention.endsWith("/")) {
parsedText += `\n\n<folder_content path="${mentionPath}">\nError fetching content: ${error.message}\n</folder_content>`
} else {
parsedText += `\n\n<file_content path="${mentionPath}">\nError fetching content: ${error.message}\n</file_content>`
}
}
} else if (mention === "problems") {
try {
const problems = getWorkspaceProblems(cwd)
parsedText += `\n\n<workspace_diagnostics>\n${problems}\n</workspace_diagnostics>`
} catch (error) {
parsedText += `\n\n<workspace_diagnostics>\nError fetching diagnostics: ${error.message}\n</workspace_diagnostics>`
}
}
}
if (urlMention) {
try {
await urlContentFetcher.closeBrowser()
} catch (error) {
console.error(`Error closing browser: ${error.message}`)
}
}
return parsedText
}
async function getFileOrFolderContent(mentionPath: string, cwd: string): Promise<string> {
const absPath = path.resolve(cwd, mentionPath)
try {
const stats = await fs.stat(absPath)
if (stats.isFile()) {
const isBinary = await isBinaryFile(absPath).catch(() => false)
if (isBinary) {
return "(Binary file, unable to display content)"
}
const content = await extractTextFromFile(absPath)
return content
} else if (stats.isDirectory()) {
const entries = await fs.readdir(absPath, { withFileTypes: true })
let folderContent = ""
const fileContentPromises: Promise<string | undefined>[] = []
entries.forEach((entry, index) => {
const isLast = index === entries.length - 1
const linePrefix = isLast ? "└── " : "├── "
if (entry.isFile()) {
folderContent += `${linePrefix}${entry.name}\n`
const filePath = path.join(mentionPath, entry.name)
const absoluteFilePath = path.resolve(absPath, entry.name)
// const relativeFilePath = path.relative(cwd, absoluteFilePath);
fileContentPromises.push(
(async () => {
try {
const isBinary = await isBinaryFile(absoluteFilePath).catch(() => false)
if (isBinary) {
return undefined
}
const content = await extractTextFromFile(absoluteFilePath)
return `<file_content path="${filePath.toPosix()}">\n${content}\n</file_content>`
} catch (error) {
return undefined
}
})()
)
} else if (entry.isDirectory()) {
folderContent += `${linePrefix}${entry.name}/\n`
// not recursively getting folder contents
} else {
folderContent += `${linePrefix}${entry.name}\n`
}
})
const fileContents = (await Promise.all(fileContentPromises)).filter((content) => content)
return `${folderContent}\n${fileContents.join("\n\n")}`.trim()
} else {
return `(Failed to read contents of ${mentionPath})`
}
} catch (error) {
throw new Error(`Failed to access path "${mentionPath}": ${error.message}`)
}
}
function getWorkspaceProblems(cwd: string): string {
const diagnostics = vscode.languages.getDiagnostics()
const result = diagnosticsToProblemsString(diagnostics, cwd)
if (!result) {
return "No errors or warnings detected."
}
return result
}

View File

@@ -10,9 +10,9 @@ import fs from "fs/promises"
import { HistoryItem } from "../../shared/HistoryItem"
import axios from "axios"
import { getTheme } from "../../integrations/theme/getTheme"
import { openFile, openImage } from "../../utils/open-file"
import { openFile, openImage } from "../../integrations/misc/open-file"
import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker"
import { openMention } from "../../utils/context-mentions"
import { openMention } from "../mentions/context-mentions"
/*
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts

View File

@@ -0,0 +1,16 @@
/**
* A helper function that returns a unique alphanumeric identifier called a nonce.
*
* @remarks This function is primarily used to help enforce content security
* policies for resources/scripts being executed in a webview context.
*
* @returns A nonce
*/
export function getNonce() {
let text = ""
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length))
}
return text
}

View File

@@ -0,0 +1,15 @@
import { Uri, Webview } from "vscode"
/**
* A helper function which will get the webview URI of a given file or resource.
*
* @remarks This URI can be used within a webview's HTML as a link to the
* given file/resource.
*
* @param webview A reference to the extension webview
* @param extensionUri The URI of the directory containing the extension
* @param pathList An array of strings representing the path to a file/resource
* @returns A URI pointing to the file/resource
*/
export function getUri(webview: Webview, extensionUri: Uri, pathList: string[]) {
return webview.asWebviewUri(Uri.joinPath(extensionUri, ...pathList))
}