|
@@ -1,17 +1,18 @@
|
|
|
import * as vscode from "vscode"
|
|
import * as vscode from "vscode"
|
|
|
import * as path from "path"
|
|
import * as path from "path"
|
|
|
|
|
+import fs from "fs/promises"
|
|
|
import { openFile } from "../../integrations/misc/open-file"
|
|
import { openFile } from "../../integrations/misc/open-file"
|
|
|
import { UrlContentFetcher } from "../../services/browser/UrlContentFetcher"
|
|
import { UrlContentFetcher } from "../../services/browser/UrlContentFetcher"
|
|
|
-import { mentionRegexGlobal, formatGitSuggestion, type MentionSuggestion } from "../../shared/context-mentions"
|
|
|
|
|
-import fs from "fs/promises"
|
|
|
|
|
|
|
+import { mentionRegexGlobal } from "../../shared/context-mentions"
|
|
|
|
|
+import { getWorkspacePath } from "../../utils/path"
|
|
|
|
|
+import { HandlerConfig, MentionContext, XmlTag } from "./types"
|
|
|
import { extractTextFromFile } from "../../integrations/misc/extract-text"
|
|
import { extractTextFromFile } from "../../integrations/misc/extract-text"
|
|
|
import { isBinaryFile } from "isbinaryfile"
|
|
import { isBinaryFile } from "isbinaryfile"
|
|
|
|
|
+import { getWorkingState, getCommitInfo } from "../../utils/git"
|
|
|
import { diagnosticsToProblemsString } from "../../integrations/diagnostics"
|
|
import { diagnosticsToProblemsString } from "../../integrations/diagnostics"
|
|
|
-import { getCommitInfo, getWorkingState } from "../../utils/git"
|
|
|
|
|
import { getLatestTerminalOutput } from "../../integrations/terminal/get-latest-output"
|
|
import { getLatestTerminalOutput } from "../../integrations/terminal/get-latest-output"
|
|
|
-import { getWorkspacePath } from "../../utils/path"
|
|
|
|
|
|
|
|
|
|
-export async function openMention(mention?: string): Promise<void> {
|
|
|
|
|
|
|
+export async function openMention(mention?: string, osInfo?: string): Promise<void> {
|
|
|
if (!mention) {
|
|
if (!mention) {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
@@ -21,10 +22,20 @@ export async function openMention(mention?: string): Promise<void> {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (mention.startsWith("/")) {
|
|
|
|
|
|
|
+ if (
|
|
|
|
|
+ (osInfo !== "win32" && mention.startsWith("/")) ||
|
|
|
|
|
+ (osInfo === "win32" && mention.startsWith("\\"))
|
|
|
|
|
+ ) {
|
|
|
const relPath = mention.slice(1)
|
|
const relPath = mention.slice(1)
|
|
|
- const absPath = path.resolve(cwd, relPath)
|
|
|
|
|
- if (mention.endsWith("/")) {
|
|
|
|
|
|
|
+ let absPath = path.resolve(cwd, relPath)
|
|
|
|
|
+ if (absPath.includes(" ")) {
|
|
|
|
|
+ let escapedSpace = osInfo === "win32" ? "/ " : "\\ "
|
|
|
|
|
+ absPath = absPath.replaceAll(escapedSpace, " ")
|
|
|
|
|
+ }
|
|
|
|
|
+ if (
|
|
|
|
|
+ ((osInfo === "unix" || osInfo === undefined) && mention.endsWith("/")) ||
|
|
|
|
|
+ (osInfo === "win32" && mention.endsWith("\\"))
|
|
|
|
|
+ ) {
|
|
|
vscode.commands.executeCommand("revealInExplorer", vscode.Uri.file(absPath))
|
|
vscode.commands.executeCommand("revealInExplorer", vscode.Uri.file(absPath))
|
|
|
} else {
|
|
} else {
|
|
|
openFile(absPath)
|
|
openFile(absPath)
|
|
@@ -37,115 +48,29 @@ export async function openMention(mention?: string): Promise<void> {
|
|
|
vscode.env.openExternal(vscode.Uri.parse(mention))
|
|
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)
|
|
|
|
|
- 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)`
|
|
|
|
|
- } else if (mention === "git-changes") {
|
|
|
|
|
- return `Working directory changes (see below for details)`
|
|
|
|
|
- } else if (/^[a-f0-9]{7,40}$/.test(mention)) {
|
|
|
|
|
- return `Git commit '${mention}' (see below for commit info)`
|
|
|
|
|
- } else if (mention === "terminal") {
|
|
|
|
|
- return `Terminal Output (see below for output)`
|
|
|
|
|
- }
|
|
|
|
|
- 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}`)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+// Utility functions
|
|
|
|
|
+export const createXmlTag = (name: string, attrs: Record<string, string> = {}): XmlTag => {
|
|
|
|
|
+ const attrString = Object.entries(attrs)
|
|
|
|
|
+ .map(([key, value]) => `${key}="${value}"`)
|
|
|
|
|
+ .join(" ")
|
|
|
|
|
+ return {
|
|
|
|
|
+ start: `\n\n<${name}${attrString ? " " + attrString : ""}>`,
|
|
|
|
|
+ end: `</${name}>`,
|
|
|
}
|
|
}
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- 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 = await 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>`
|
|
|
|
|
- }
|
|
|
|
|
- } else if (mention === "git-changes") {
|
|
|
|
|
- try {
|
|
|
|
|
- const workingState = await getWorkingState(cwd)
|
|
|
|
|
- parsedText += `\n\n<git_working_state>\n${workingState}\n</git_working_state>`
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- parsedText += `\n\n<git_working_state>\nError fetching working state: ${error.message}\n</git_working_state>`
|
|
|
|
|
- }
|
|
|
|
|
- } else if (/^[a-f0-9]{7,40}$/.test(mention)) {
|
|
|
|
|
- try {
|
|
|
|
|
- const commitInfo = await getCommitInfo(mention, cwd)
|
|
|
|
|
- parsedText += `\n\n<git_commit hash="${mention}">\n${commitInfo}\n</git_commit>`
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- parsedText += `\n\n<git_commit hash="${mention}">\nError fetching commit info: ${error.message}\n</git_commit>`
|
|
|
|
|
- }
|
|
|
|
|
- } else if (mention === "terminal") {
|
|
|
|
|
- try {
|
|
|
|
|
- const terminalOutput = await getLatestTerminalOutput()
|
|
|
|
|
- parsedText += `\n\n<terminal_output>\n${terminalOutput}\n</terminal_output>`
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- parsedText += `\n\n<terminal_output>\nError fetching terminal output: ${error.message}\n</terminal_output>`
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+export const wrapContent = (content: string, tag: XmlTag): string => `${tag.start}\n${content}\n${tag.end}`
|
|
|
|
|
|
|
|
- if (urlMention) {
|
|
|
|
|
- try {
|
|
|
|
|
- await urlContentFetcher.closeBrowser()
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error(`Error closing browser: ${error.message}`)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+export const handleError = (error: Error, message: string): string => {
|
|
|
|
|
+ const errorMsg = `Error ${message}: ${error.message}`
|
|
|
|
|
+ if (error instanceof Error) {
|
|
|
|
|
+ vscode.window.showErrorMessage(errorMsg)
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- return parsedText
|
|
|
|
|
|
|
+ return errorMsg
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-async function getFileOrFolderContent(mentionPath: string, cwd: string): Promise<string> {
|
|
|
|
|
|
|
+// File utilities
|
|
|
|
|
+export async function getFileOrFolderContent(mentionPath: string, cwd: string, osInfo: string): Promise<string> {
|
|
|
const absPath = path.resolve(cwd, mentionPath)
|
|
const absPath = path.resolve(cwd, mentionPath)
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
@@ -177,7 +102,7 @@ async function getFileOrFolderContent(mentionPath: string, cwd: string): Promise
|
|
|
return undefined
|
|
return undefined
|
|
|
}
|
|
}
|
|
|
const content = await extractTextFromFile(absoluteFilePath)
|
|
const content = await extractTextFromFile(absoluteFilePath)
|
|
|
- return `<file_content path="${filePath.toPosix()}">\n${content}\n</file_content>`
|
|
|
|
|
|
|
+ return `<file_content path="${filePath}">\n${content}\n</file_content>`
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
return undefined
|
|
return undefined
|
|
|
}
|
|
}
|
|
@@ -199,7 +124,8 @@ async function getFileOrFolderContent(mentionPath: string, cwd: string): Promise
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-async function getWorkspaceProblems(cwd: string): Promise<string> {
|
|
|
|
|
|
|
+// Workspace utilities
|
|
|
|
|
+export async function getWorkspaceProblems(cwd: string): Promise<string> {
|
|
|
const diagnostics = vscode.languages.getDiagnostics()
|
|
const diagnostics = vscode.languages.getDiagnostics()
|
|
|
const result = await diagnosticsToProblemsString(
|
|
const result = await diagnosticsToProblemsString(
|
|
|
diagnostics,
|
|
diagnostics,
|
|
@@ -211,3 +137,193 @@ async function getWorkspaceProblems(cwd: string): Promise<string> {
|
|
|
}
|
|
}
|
|
|
return result
|
|
return result
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+// Handler implementations
|
|
|
|
|
+const urlHandler: HandlerConfig = {
|
|
|
|
|
+ name: "url",
|
|
|
|
|
+ test: (mention: string) => mention.startsWith("http"),
|
|
|
|
|
+ handler: async (mention, { urlContentFetcher, launchBrowserError }) => {
|
|
|
|
|
+ const tag = createXmlTag("url_content", { url: mention })
|
|
|
|
|
+ let content: string
|
|
|
|
|
+
|
|
|
|
|
+ if (launchBrowserError) {
|
|
|
|
|
+ content = handleError(launchBrowserError, "fetching content")
|
|
|
|
|
+ } else {
|
|
|
|
|
+ try {
|
|
|
|
|
+ content = await urlContentFetcher.urlToMarkdown(mention)
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ content = handleError(error, `fetching content for ${mention}`)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return wrapContent(content, tag)
|
|
|
|
|
+ },
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const fileHandler: HandlerConfig = {
|
|
|
|
|
+ name: "file",
|
|
|
|
|
+ test: (mention: string, { osInfo }) => (osInfo !== "win32" ? mention.startsWith("/") : mention.startsWith("\\")),
|
|
|
|
|
+ handler: async (mention, { cwd, osInfo }) => {
|
|
|
|
|
+ let mentionPath = mention.slice(1)
|
|
|
|
|
+ const isFolder = osInfo === "win32" ? mention.endsWith("\\") : mention.endsWith("/")
|
|
|
|
|
+ const tag = createXmlTag(isFolder ? "folder_content" : "file_content", { path: mentionPath })
|
|
|
|
|
+
|
|
|
|
|
+ if (mentionPath.includes(" ")) {
|
|
|
|
|
+ let escapedSpace = osInfo === "win32" ? "/ " : "\\ "
|
|
|
|
|
+ mentionPath = mentionPath.replaceAll(escapedSpace, " ")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const content = await getFileOrFolderContent(mentionPath, cwd, osInfo)
|
|
|
|
|
+ return wrapContent(content, tag)
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ return wrapContent(handleError(error, "fetching content"), tag)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const problemsHandler: HandlerConfig = {
|
|
|
|
|
+ name: "problems",
|
|
|
|
|
+ test: (mention: string) => mention === "problems",
|
|
|
|
|
+ handler: async (mention, { cwd }) => {
|
|
|
|
|
+ const tag = createXmlTag("workspace_diagnostics")
|
|
|
|
|
+ try {
|
|
|
|
|
+ const problems = await getWorkspaceProblems(cwd)
|
|
|
|
|
+ return wrapContent(problems, tag)
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ return wrapContent(handleError(error, "fetching diagnostics"), tag)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const gitChangesHandler: HandlerConfig = {
|
|
|
|
|
+ name: "git-changes",
|
|
|
|
|
+ test: (mention: string) => mention === "git-changes",
|
|
|
|
|
+ handler: async (mention, { cwd }) => {
|
|
|
|
|
+ const tag = createXmlTag("git_working_state")
|
|
|
|
|
+ try {
|
|
|
|
|
+ const workingState = await getWorkingState(cwd)
|
|
|
|
|
+ return wrapContent(workingState, tag)
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ return wrapContent(handleError(error, "fetching working state"), tag)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const commitHandler: HandlerConfig = {
|
|
|
|
|
+ name: "commit",
|
|
|
|
|
+ test: (mention: string) => /^[a-f0-9]{7,40}$/.test(mention),
|
|
|
|
|
+ handler: async (mention, { cwd }) => {
|
|
|
|
|
+ const tag = createXmlTag("git_commit", { hash: mention })
|
|
|
|
|
+ try {
|
|
|
|
|
+ const commitInfo = await getCommitInfo(mention, cwd)
|
|
|
|
|
+ return wrapContent(commitInfo, tag)
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ return wrapContent(handleError(error, "fetching commit info"), tag)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const terminalHandler: HandlerConfig = {
|
|
|
|
|
+ name: "terminal",
|
|
|
|
|
+ test: (mention: string) => mention === "terminal",
|
|
|
|
|
+ handler: async (mention) => {
|
|
|
|
|
+ const tag = createXmlTag("terminal_output")
|
|
|
|
|
+ try {
|
|
|
|
|
+ const terminalOutput = await getLatestTerminalOutput()
|
|
|
|
|
+ return wrapContent(terminalOutput, tag)
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ return wrapContent(handleError(error, "fetching terminal output"), tag)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Define handlers array
|
|
|
|
|
+const handlers: HandlerConfig[] = [
|
|
|
|
|
+ urlHandler,
|
|
|
|
|
+ fileHandler,
|
|
|
|
|
+ problemsHandler,
|
|
|
|
|
+ gitChangesHandler,
|
|
|
|
|
+ commitHandler,
|
|
|
|
|
+ terminalHandler,
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+export async function parseMentions(
|
|
|
|
|
+ text: string,
|
|
|
|
|
+ cwd: string,
|
|
|
|
|
+ urlContentFetcher: UrlContentFetcher,
|
|
|
|
|
+ osInfo: string = "unix",
|
|
|
|
|
+): 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)`
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (
|
|
|
|
|
+ (osInfo !== "win32" && osInfo !== undefined && mention.startsWith("/")) ||
|
|
|
|
|
+ (osInfo === "win32" && mention.startsWith("\\"))
|
|
|
|
|
+ ) {
|
|
|
|
|
+ const mentionPath = mention.slice(1)
|
|
|
|
|
+ return mentionPath.endsWith("/") || mentionPath.endsWith("\\")
|
|
|
|
|
+ ? `'${mentionPath}' (see below for folder content)`
|
|
|
|
|
+ : `'${mentionPath}' (see below for file content)`
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (mention === "problems") {
|
|
|
|
|
+ return `Workspace Problems (see below for diagnostics)`
|
|
|
|
|
+ }
|
|
|
|
|
+ if (mention === "git-changes") {
|
|
|
|
|
+ return `Working directory changes (see below for details)`
|
|
|
|
|
+ }
|
|
|
|
|
+ if (/^[a-f0-9]{7,40}$/.test(mention)) {
|
|
|
|
|
+ return `Git commit '${mention}' (see below for commit info)`
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (mention === "terminal") {
|
|
|
|
|
+ return `Terminal Output (see below for output)`
|
|
|
|
|
+ }
|
|
|
|
|
+ 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}`)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const context: MentionContext = {
|
|
|
|
|
+ cwd,
|
|
|
|
|
+ urlContentFetcher,
|
|
|
|
|
+ launchBrowserError,
|
|
|
|
|
+ osInfo,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const mentionResults = await Promise.all(
|
|
|
|
|
+ Array.from(mentions).map(async (mention) => {
|
|
|
|
|
+ for (const handler of handlers) {
|
|
|
|
|
+ if (handler.test(mention, context)) {
|
|
|
|
|
+ return handler.handler(mention, context)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return ""
|
|
|
|
|
+ }),
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ parsedText += mentionResults.join("")
|
|
|
|
|
+
|
|
|
|
|
+ if (urlMention) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await urlContentFetcher.closeBrowser()
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(`Error closing browser: ${error.message}`)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return parsedText
|
|
|
|
|
+}
|