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 { const mentions: Set = 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\n${result}\n` } else if (mention.startsWith("/")) { const mentionPath = mention.slice(1) try { const content = await getFileOrFolderContent(mentionPath, cwd) if (mention.endsWith("/")) { parsedText += `\n\n\n${content}\n` } else { parsedText += `\n\n\n${content}\n` } } catch (error) { if (mention.endsWith("/")) { parsedText += `\n\n\nError fetching content: ${error.message}\n` } else { parsedText += `\n\n\nError fetching content: ${error.message}\n` } } } else if (mention === "problems") { try { const problems = getWorkspaceProblems(cwd) parsedText += `\n\n\n${problems}\n` } catch (error) { parsedText += `\n\n\nError fetching diagnostics: ${error.message}\n` } } } 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 { 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[] = [] 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 `\n${content}\n` } 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, [vscode.DiagnosticSeverity.Error, vscode.DiagnosticSeverity.Warning], cwd, ) if (!result) { return "No errors or warnings detected." } return result }