Skip to content

Commit 27a05f8

Browse files
committed
feat: improve multi-folder workspace support
- Fix Task to store initial workspace folder and use it consistently throughout task lifetime - Fix file mentions to use relative paths in multi-folder workspaces - Fix .roo folder detection to check if .roo is a workspace folder itself - Add support for workspace folder selection based on active editor - Add tests for multi-folder workspace functionality Fixes #6897 and #6720
1 parent f53fd39 commit 27a05f8

File tree

6 files changed

+441
-18
lines changed

6 files changed

+441
-18
lines changed

src/core/config/CustomModesManager.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,18 @@ export class CustomModesManager {
9696
return undefined
9797
}
9898

99-
const workspaceRoot = getWorkspacePath()
100-
const roomodesPath = path.join(workspaceRoot, ROOMODES_FILENAME)
99+
// Check if .roo is a workspace folder itself
100+
const rooWorkspaceFolder = workspaceFolders.find((folder) => path.basename(folder.uri.fsPath) === ".roo")
101+
102+
let roomodesPath: string
103+
if (rooWorkspaceFolder) {
104+
// .roo is a workspace folder itself, look for .roomodes directly in it
105+
roomodesPath = path.join(rooWorkspaceFolder.uri.fsPath, ROOMODES_FILENAME.replace(".roo/", ""))
106+
} else {
107+
// Use the current workspace path (which considers the active file's workspace)
108+
const workspaceRoot = getWorkspacePath()
109+
roomodesPath = path.join(workspaceRoot, ROOMODES_FILENAME)
110+
}
101111
const exists = await fileExistsAtPath(roomodesPath)
102112
return exists ? roomodesPath : undefined
103113
}

src/core/mentions/index.ts

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -274,20 +274,41 @@ async function getFileOrFolderContent(
274274
maxReadFileLine?: number,
275275
): Promise<string> {
276276
const unescapedPath = unescapeSpaces(mentionPath)
277-
const absPath = path.resolve(cwd, unescapedPath)
277+
278+
// Check if this is an absolute path from a different workspace folder
279+
let absPath: string
280+
let displayPath: string = mentionPath
281+
282+
if (path.isAbsolute(unescapedPath)) {
283+
absPath = unescapedPath
284+
285+
// In multi-folder workspaces, convert absolute paths to relative paths
286+
// based on the workspace folder they belong to
287+
if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 1) {
288+
const workspaceFolder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(absPath))
289+
if (workspaceFolder) {
290+
// Use relative path from the workspace folder
291+
const relativePath = path.relative(workspaceFolder.uri.fsPath, absPath)
292+
displayPath = path.join(workspaceFolder.name, relativePath).toPosix()
293+
}
294+
}
295+
} else {
296+
absPath = path.resolve(cwd, unescapedPath)
297+
displayPath = mentionPath
298+
}
278299

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

282303
if (stats.isFile()) {
283304
if (rooIgnoreController && !rooIgnoreController.validateAccess(absPath)) {
284-
return `(File ${mentionPath} is ignored by .rooignore)`
305+
return `(File ${displayPath} is ignored by .rooignore)`
285306
}
286307
try {
287308
const content = await extractTextFromFile(absPath, maxReadFileLine)
288309
return content
289310
} catch (error) {
290-
return `(Failed to read contents of ${mentionPath}): ${error.message}`
311+
return `(Failed to read contents of ${displayPath}): ${error.message}`
291312
}
292313
} else if (stats.isDirectory()) {
293314
const entries = await fs.readdir(absPath, { withFileTypes: true })
@@ -315,8 +336,24 @@ async function getFileOrFolderContent(
315336
if (entry.isFile()) {
316337
folderContent += `${linePrefix}${displayName}\n`
317338
if (!isIgnored) {
318-
const filePath = path.join(mentionPath, entry.name)
319339
const absoluteFilePath = path.resolve(absPath, entry.name)
340+
341+
// Determine the display path for the file
342+
let fileDisplayPath: string
343+
if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 1) {
344+
const workspaceFolder = vscode.workspace.getWorkspaceFolder(
345+
vscode.Uri.file(absoluteFilePath),
346+
)
347+
if (workspaceFolder) {
348+
const relativePath = path.relative(workspaceFolder.uri.fsPath, absoluteFilePath)
349+
fileDisplayPath = path.join(workspaceFolder.name, relativePath).toPosix()
350+
} else {
351+
fileDisplayPath = path.join(displayPath, entry.name).toPosix()
352+
}
353+
} else {
354+
fileDisplayPath = path.join(displayPath, entry.name).toPosix()
355+
}
356+
320357
fileContentPromises.push(
321358
(async () => {
322359
try {
@@ -325,7 +362,7 @@ async function getFileOrFolderContent(
325362
return undefined
326363
}
327364
const content = await extractTextFromFile(absoluteFilePath, maxReadFileLine)
328-
return `<file_content path="${filePath.toPosix()}">\n${content}\n</file_content>`
365+
return `<file_content path="${fileDisplayPath}">\n${content}\n</file_content>`
329366
} catch (error) {
330367
return undefined
331368
}
@@ -341,10 +378,10 @@ async function getFileOrFolderContent(
341378
const fileContents = (await Promise.all(fileContentPromises)).filter((content) => content)
342379
return `${folderContent}\n${fileContents.join("\n\n")}`.trim()
343380
} else {
344-
return `(Failed to read contents of ${mentionPath})`
381+
return `(Failed to read contents of ${displayPath})`
345382
}
346383
} catch (error) {
347-
throw new Error(`Failed to access path "${mentionPath}": ${error.message}`)
384+
throw new Error(`Failed to access path "${displayPath}": ${error.message}`)
348385
}
349386
}
350387

src/core/task/Task.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -279,10 +279,23 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
279279

280280
this.taskId = historyItem ? historyItem.id : crypto.randomUUID()
281281

282-
// Normal use-case is usually retry similar history task with new workspace.
283-
this.workspacePath = parentTask
284-
? parentTask.workspacePath
285-
: getWorkspacePath(path.join(os.homedir(), "Desktop"))
282+
// Store the initial workspace path when the task is created
283+
// In multi-folder workspaces, we want to maintain the same workspace context throughout the task
284+
if (parentTask) {
285+
// Inherit workspace from parent task
286+
this.workspacePath = parentTask.workspacePath
287+
} else {
288+
// Determine workspace based on current context
289+
// If there's an active editor, use its workspace folder
290+
// Otherwise, use the first workspace folder or fallback to Desktop
291+
const currentFileUri = vscode.window.activeTextEditor?.document.uri
292+
if (currentFileUri) {
293+
const workspaceFolder = vscode.workspace.getWorkspaceFolder(currentFileUri)
294+
this.workspacePath = workspaceFolder?.uri.fsPath || getWorkspacePath(path.join(os.homedir(), "Desktop"))
295+
} else {
296+
this.workspacePath = getWorkspacePath(path.join(os.homedir(), "Desktop"))
297+
}
298+
}
286299

287300
this.instanceId = crypto.randomUUID().slice(0, 8)
288301
this.taskNumber = -1

0 commit comments

Comments
 (0)