Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 51 additions & 23 deletions src/core/mentions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { UrlContentFetcher } from "../../services/browser/UrlContentFetcher"

import { FileContextTracker } from "../context-tracking/FileContextTracker"

import { RooIgnoreController } from "../ignore/RooIgnoreController"

export async function openMention(mention?: string): Promise<void> {
if (!mention) {
return
Expand Down Expand Up @@ -50,6 +52,8 @@ export async function parseMentions(
cwd: string,
urlContentFetcher: UrlContentFetcher,
fileContextTracker?: FileContextTracker,
rooIgnoreController?: RooIgnoreController,
showRooIgnoredFiles: boolean = true,
): Promise<string> {
const mentions: Set<string> = new Set()
let parsedText = text.replace(mentionRegexGlobal, (match, mention) => {
Expand Down Expand Up @@ -102,12 +106,11 @@ export async function parseMentions(
} else if (mention.startsWith("/")) {
const mentionPath = mention.slice(1)
try {
const content = await getFileOrFolderContent(mentionPath, cwd)
const content = await getFileOrFolderContent(mentionPath, cwd, rooIgnoreController, showRooIgnoredFiles)
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>`
// Track that this file was mentioned and its content was included
if (fileContextTracker) {
await fileContextTracker.trackFileContext(mentionPath, "file_mentioned")
}
Expand Down Expand Up @@ -161,15 +164,22 @@ export async function parseMentions(
return parsedText
}

async function getFileOrFolderContent(mentionPath: string, cwd: string): Promise<string> {
// Unescape spaces in the path before resolving it
async function getFileOrFolderContent(
mentionPath: string,
cwd: string,
rooIgnoreController?: any,
showRooIgnoredFiles: boolean = true,
): Promise<string> {
const unescapedPath = unescapeSpaces(mentionPath)
const absPath = path.resolve(cwd, unescapedPath)

try {
const stats = await fs.stat(absPath)

if (stats.isFile()) {
if (rooIgnoreController && !rooIgnoreController.validateAccess(absPath)) {
return `(File ${mentionPath} is ignored by .rooignore)`
}
try {
const content = await extractTextFromFile(absPath)
return content
Expand All @@ -180,33 +190,51 @@ async function getFileOrFolderContent(mentionPath: string, cwd: string): Promise
const entries = await fs.readdir(absPath, { withFileTypes: true })
let folderContent = ""
const fileContentPromises: Promise<string | undefined>[] = []
entries.forEach((entry, index) => {
const LOCK_SYMBOL = "🔒"

for (let index = 0; index < entries.length; index++) {
const entry = entries[index]
const isLast = index === entries.length - 1
const linePrefix = isLast ? "└── " : "├── "
const entryPath = path.join(absPath, entry.name)

let isIgnored = false
if (rooIgnoreController) {
isIgnored = !rooIgnoreController.validateAccess(entryPath)
}

if (isIgnored && !showRooIgnoredFiles) {
continue
}

const displayName = isIgnored ? `${LOCK_SYMBOL} ${entry.name}` : entry.name

if (entry.isFile()) {
folderContent += `${linePrefix}${entry.name}\n`
const filePath = path.join(mentionPath, entry.name)
const absoluteFilePath = path.resolve(absPath, entry.name)
fileContentPromises.push(
(async () => {
try {
const isBinary = await isBinaryFile(absoluteFilePath).catch(() => false)
if (isBinary) {
folderContent += `${linePrefix}${displayName}\n`
if (!isIgnored) {
const filePath = path.join(mentionPath, entry.name)
const absoluteFilePath = path.resolve(absPath, entry.name)
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
}
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`
folderContent += `${linePrefix}${displayName}/\n`
} else {
folderContent += `${linePrefix}${entry.name}\n`
folderContent += `${linePrefix}${displayName}\n`
}
})
}
const fileContents = (await Promise.all(fileContentPromises)).filter((content) => content)
return `${folderContent}\n${fileContents.join("\n\n")}`.trim()
} else {
Expand Down
24 changes: 22 additions & 2 deletions src/core/mentions/processUserContentMentions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ export async function processUserContentMentions({
cwd,
urlContentFetcher,
fileContextTracker,
rooIgnoreController,
showRooIgnoredFiles = true,
}: {
userContent: Anthropic.Messages.ContentBlockParam[]
cwd: string
urlContentFetcher: UrlContentFetcher
fileContextTracker: FileContextTracker
rooIgnoreController?: any
showRooIgnoredFiles?: boolean
}) {
// Process userContent array, which contains various block types:
// TextBlockParam, ImageBlockParam, ToolUseBlockParam, and ToolResultBlockParam.
Expand All @@ -35,7 +39,14 @@ export async function processUserContentMentions({
if (shouldProcessMentions(block.text)) {
return {
...block,
text: await parseMentions(block.text, cwd, urlContentFetcher, fileContextTracker),
text: await parseMentions(
block.text,
cwd,
urlContentFetcher,
fileContextTracker,
rooIgnoreController,
showRooIgnoredFiles,
),
}
}

Expand All @@ -45,7 +56,14 @@ export async function processUserContentMentions({
if (shouldProcessMentions(block.content)) {
return {
...block,
content: await parseMentions(block.content, cwd, urlContentFetcher, fileContextTracker),
content: await parseMentions(
block.content,
cwd,
urlContentFetcher,
fileContextTracker,
rooIgnoreController,
showRooIgnoredFiles,
),
}
}

Expand All @@ -61,6 +79,8 @@ export async function processUserContentMentions({
cwd,
urlContentFetcher,
fileContextTracker,
rooIgnoreController,
showRooIgnoredFiles,
),
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1096,11 +1096,15 @@ export class Task extends EventEmitter<ClineEvents> {
}),
)

const { showRooIgnoredFiles = true } = (await this.providerRef.deref()?.getState()) ?? {}

const parsedUserContent = await processUserContentMentions({
userContent,
cwd: this.cwd,
urlContentFetcher: this.urlContentFetcher,
fileContextTracker: this.fileContextTracker,
rooIgnoreController: this.rooIgnoreController,
showRooIgnoredFiles,
})

const environmentDetails = await getEnvironmentDetails(this, includeFileDetails)
Expand Down