mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-21 21:01:06 -05:00
Retrieve workspace filepaths for context menu
This commit is contained in:
@@ -11,7 +11,7 @@ import { serializeError } from "serialize-error"
|
||||
import * as vscode from "vscode"
|
||||
import { ApiHandler, buildApiHandler } from "./api"
|
||||
import { TerminalManager } from "./integrations/TerminalManager"
|
||||
import { LIST_FILES_LIMIT, listFiles, parseSourceCodeForDefinitionsTopLevel } from "./parse-source-code"
|
||||
import { listFiles, parseSourceCodeForDefinitionsTopLevel } from "./parse-source-code"
|
||||
import { ClaudeDevProvider } from "./providers/ClaudeDevProvider"
|
||||
import { ApiConfiguration } from "./shared/api"
|
||||
import { ClaudeRequestResult } from "./shared/ClaudeRequestResult"
|
||||
@@ -1187,8 +1187,8 @@ export class ClaudeDev {
|
||||
try {
|
||||
const recursive = recursiveRaw?.toLowerCase() === "true"
|
||||
const absolutePath = path.resolve(cwd, relDirPath)
|
||||
const files = await listFiles(absolutePath, recursive)
|
||||
const result = this.formatFilesList(absolutePath, files)
|
||||
const [files, didHitLimit] = await listFiles(absolutePath, recursive, 200)
|
||||
const result = this.formatFilesList(absolutePath, files, didHitLimit)
|
||||
|
||||
const message = JSON.stringify({
|
||||
tool: recursive ? "listFilesRecursive" : "listFilesTopLevel",
|
||||
@@ -1245,7 +1245,7 @@ export class ClaudeDev {
|
||||
}
|
||||
}
|
||||
|
||||
formatFilesList(absolutePath: string, files: string[]): string {
|
||||
formatFilesList(absolutePath: string, files: string[], didHitLimit: boolean): string {
|
||||
const sorted = files
|
||||
.map((file) => {
|
||||
// convert absolute path to relative path
|
||||
@@ -1273,11 +1273,12 @@ export class ClaudeDev {
|
||||
// the shorter one comes first
|
||||
return aParts.length - bParts.length
|
||||
})
|
||||
if (sorted.length >= LIST_FILES_LIMIT) {
|
||||
const truncatedList = sorted.slice(0, LIST_FILES_LIMIT).join("\n")
|
||||
return `${truncatedList}\n\n(Truncated at ${LIST_FILES_LIMIT} results. Try listing files in subdirectories if you need to explore further.)`
|
||||
if (didHitLimit) {
|
||||
return `${sorted.join(
|
||||
"\n"
|
||||
)}\n\n(Truncated at 200 results. Try listing files in subdirectories if you need to explore further.)`
|
||||
} else if (sorted.length === 0 || (sorted.length === 1 && sorted[0] === "")) {
|
||||
return "No files found or you do not have permission to view this directory."
|
||||
return "No files found."
|
||||
} else {
|
||||
return sorted.join("\n")
|
||||
}
|
||||
@@ -1937,8 +1938,8 @@ ${this.customInstructions.trim()}
|
||||
|
||||
if (includeFileDetails) {
|
||||
const isDesktop = cwd === path.join(os.homedir(), "Desktop")
|
||||
const files = await listFiles(cwd, !isDesktop)
|
||||
const result = this.formatFilesList(cwd, files)
|
||||
const [files, didHitLimit] = await listFiles(cwd, !isDesktop, 200)
|
||||
const result = this.formatFilesList(cwd, files, didHitLimit)
|
||||
details += `\n\n# Current Working Directory (${cwd}) Files\n${result}${
|
||||
isDesktop
|
||||
? "\n(Note: Only top-level contents shown for Desktop by default. Use list_files to explore further if necessary.)"
|
||||
|
||||
108
src/integrations/WorkspaceTracker.ts
Normal file
108
src/integrations/WorkspaceTracker.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import * as vscode from "vscode"
|
||||
import * as path from "path"
|
||||
import { listFiles } from "../parse-source-code/index"
|
||||
import { ClaudeDevProvider } from "../providers/ClaudeDevProvider"
|
||||
|
||||
class WorkspaceTracker {
|
||||
private providerRef: WeakRef<ClaudeDevProvider>
|
||||
private disposables: vscode.Disposable[] = []
|
||||
private filePaths: Set<string> = new Set()
|
||||
|
||||
constructor(provider: ClaudeDevProvider) {
|
||||
this.providerRef = new WeakRef(provider)
|
||||
this.registerListeners()
|
||||
}
|
||||
|
||||
async initializeFilePaths() {
|
||||
// should not auto get filepaths for desktop since it would immediately show permission popup before claude every creates a file
|
||||
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0)
|
||||
if (!cwd) {
|
||||
return
|
||||
}
|
||||
const [files, _] = await listFiles(cwd, true, 500)
|
||||
files.forEach((file) => this.filePaths.add(file))
|
||||
this.workspaceDidUpdate()
|
||||
}
|
||||
|
||||
private registerListeners() {
|
||||
// Listen for file creation
|
||||
this.disposables.push(vscode.workspace.onDidCreateFiles(this.onFilesCreated.bind(this)))
|
||||
|
||||
// Listen for file deletion
|
||||
this.disposables.push(vscode.workspace.onDidDeleteFiles(this.onFilesDeleted.bind(this)))
|
||||
|
||||
// Listen for file renaming
|
||||
this.disposables.push(vscode.workspace.onDidRenameFiles(this.onFilesRenamed.bind(this)))
|
||||
|
||||
// Listen for file changes
|
||||
this.disposables.push(vscode.workspace.onDidChangeTextDocument(this.onFileChanged.bind(this)))
|
||||
|
||||
// Listen for workspace folder changes
|
||||
this.disposables.push(vscode.workspace.onDidChangeWorkspaceFolders(this.onWorkspaceFoldersChanged.bind(this)))
|
||||
}
|
||||
|
||||
private onFilesCreated(event: vscode.FileCreateEvent) {
|
||||
event.files.forEach(async (file) => {
|
||||
this.filePaths.add(file.fsPath)
|
||||
this.workspaceDidUpdate()
|
||||
})
|
||||
}
|
||||
|
||||
private onFilesDeleted(event: vscode.FileDeleteEvent) {
|
||||
event.files.forEach((file) => {
|
||||
if (this.filePaths.delete(file.fsPath)) {
|
||||
this.workspaceDidUpdate()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private onFilesRenamed(event: vscode.FileRenameEvent) {
|
||||
event.files.forEach(async (file) => {
|
||||
this.filePaths.delete(file.oldUri.fsPath)
|
||||
this.filePaths.add(file.newUri.fsPath)
|
||||
this.workspaceDidUpdate()
|
||||
})
|
||||
}
|
||||
|
||||
private async onFileChanged(event: vscode.TextDocumentChangeEvent) {
|
||||
const filePath = event.document.uri.fsPath
|
||||
if (!this.filePaths.has(filePath)) {
|
||||
this.filePaths.add(filePath)
|
||||
this.workspaceDidUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
private async onWorkspaceFoldersChanged(event: vscode.WorkspaceFoldersChangeEvent) {
|
||||
for (const folder of event.added) {
|
||||
const [files, _] = await listFiles(folder.uri.fsPath, true, 50)
|
||||
files.forEach((file) => this.filePaths.add(file))
|
||||
}
|
||||
for (const folder of event.removed) {
|
||||
this.filePaths.forEach((filePath) => {
|
||||
if (filePath.startsWith(folder.uri.fsPath)) {
|
||||
this.filePaths.delete(filePath)
|
||||
}
|
||||
})
|
||||
}
|
||||
this.workspaceDidUpdate()
|
||||
}
|
||||
|
||||
private workspaceDidUpdate() {
|
||||
console.log("Workspace updated. Current file paths:", Array.from(this.filePaths))
|
||||
// Add your logic here for when the workspace is updated
|
||||
this.providerRef.deref()?.postMessageToWebview({
|
||||
type: "workspaceUpdated",
|
||||
filePaths: Array.from(this.filePaths),
|
||||
})
|
||||
}
|
||||
|
||||
public getFilePaths(): string[] {
|
||||
return Array.from(this.filePaths)
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.disposables.forEach((d) => d.dispose())
|
||||
}
|
||||
}
|
||||
|
||||
export default WorkspaceTracker
|
||||
@@ -4,8 +4,6 @@ import os from "os"
|
||||
import * as path from "path"
|
||||
import { LanguageParser, loadRequiredLanguageParsers } from "./languageParser"
|
||||
|
||||
export const LIST_FILES_LIMIT = 200
|
||||
|
||||
// TODO: implement caching behavior to avoid having to keep analyzing project for new tasks.
|
||||
export async function parseSourceCodeForDefinitionsTopLevel(dirPath: string): Promise<string> {
|
||||
// check if the path exists
|
||||
@@ -18,7 +16,7 @@ export async function parseSourceCodeForDefinitionsTopLevel(dirPath: string): Pr
|
||||
}
|
||||
|
||||
// Get all files at top level (not gitignored)
|
||||
const allFiles = await listFiles(dirPath, false)
|
||||
const [allFiles, _] = await listFiles(dirPath, false, 200)
|
||||
|
||||
let result = ""
|
||||
|
||||
@@ -55,18 +53,18 @@ export async function parseSourceCodeForDefinitionsTopLevel(dirPath: string): Pr
|
||||
return result ? result : "No source code definitions found."
|
||||
}
|
||||
|
||||
export async function listFiles(dirPath: string, recursive: boolean): Promise<string[]> {
|
||||
export async function listFiles(dirPath: string, recursive: boolean, limit: number): Promise<[string[], boolean]> {
|
||||
const absolutePath = path.resolve(dirPath)
|
||||
// Do not allow listing files in root or home directory, which Claude tends to want to do when the user's prompt is vague.
|
||||
const root = process.platform === "win32" ? path.parse(absolutePath).root : "/"
|
||||
const isRoot = absolutePath === root
|
||||
if (isRoot) {
|
||||
return [root]
|
||||
return [[root], false]
|
||||
}
|
||||
const homeDir = os.homedir()
|
||||
const isHomeDir = absolutePath === homeDir
|
||||
if (isHomeDir) {
|
||||
return [homeDir]
|
||||
return [[homeDir], false]
|
||||
}
|
||||
|
||||
const dirsToIgnore = [
|
||||
@@ -98,26 +96,24 @@ export async function listFiles(dirPath: string, recursive: boolean): Promise<st
|
||||
onlyFiles: false, // true by default, false means it will list directories on their own too
|
||||
}
|
||||
// * globs all files in one dir, ** globs files in nested directories
|
||||
const files = recursive
|
||||
? await globbyLevelByLevel(options)
|
||||
: (await globby("*", options)).slice(0, LIST_FILES_LIMIT)
|
||||
return files
|
||||
const files = recursive ? await globbyLevelByLevel(limit, options) : (await globby("*", options)).slice(0, limit)
|
||||
return [files, files.length >= limit]
|
||||
}
|
||||
|
||||
// globby doesnt natively support top down level by level globbing, so we implement it ourselves
|
||||
async function globbyLevelByLevel(options?: Options) {
|
||||
async function globbyLevelByLevel(limit: number, options?: Options) {
|
||||
let results: string[] = []
|
||||
const globbingProcess = async () => {
|
||||
let currentLevel = 0
|
||||
while (results.length < LIST_FILES_LIMIT) {
|
||||
while (results.length < limit) {
|
||||
const pattern = currentLevel === 0 ? "*" : `${"*/".repeat(currentLevel)}*`
|
||||
const filesAtLevel = await globby(pattern, options)
|
||||
if (filesAtLevel.length === 0) {
|
||||
break
|
||||
}
|
||||
results.push(...filesAtLevel)
|
||||
if (results.length >= LIST_FILES_LIMIT) {
|
||||
results = results.slice(0, LIST_FILES_LIMIT)
|
||||
if (results.length >= limit) {
|
||||
results = results.slice(0, limit)
|
||||
break
|
||||
}
|
||||
currentLevel++
|
||||
|
||||
@@ -11,6 +11,7 @@ import { HistoryItem } from "../shared/HistoryItem"
|
||||
import axios from "axios"
|
||||
import { getTheme } from "../utils/getTheme"
|
||||
import { openFile, openImage } from "../utils/open-file"
|
||||
import WorkspaceTracker from "../integrations/WorkspaceTracker"
|
||||
|
||||
/*
|
||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||
@@ -50,11 +51,13 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
private disposables: vscode.Disposable[] = []
|
||||
private view?: vscode.WebviewView | vscode.WebviewPanel
|
||||
private claudeDev?: ClaudeDev
|
||||
private workspaceTracker?: WorkspaceTracker
|
||||
private latestAnnouncementId = "sep-14-2024" // update to some unique identifier when we add a new announcement
|
||||
|
||||
constructor(readonly context: vscode.ExtensionContext, private readonly outputChannel: vscode.OutputChannel) {
|
||||
this.outputChannel.appendLine("ClaudeDevProvider instantiated")
|
||||
ClaudeDevProvider.activeInstances.add(this)
|
||||
this.workspaceTracker = new WorkspaceTracker(this)
|
||||
this.revertKodu()
|
||||
}
|
||||
|
||||
@@ -98,6 +101,8 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
x.dispose()
|
||||
}
|
||||
}
|
||||
this.workspaceTracker?.dispose()
|
||||
this.workspaceTracker = undefined
|
||||
this.outputChannel.appendLine("Disposed all disposables")
|
||||
ClaudeDevProvider.activeInstances.delete(this)
|
||||
}
|
||||
@@ -306,6 +311,7 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
await this.postStateToWebview()
|
||||
const theme = await getTheme()
|
||||
await this.postMessageToWebview({ type: "theme", text: JSON.stringify(theme) })
|
||||
this.workspaceTracker?.initializeFilePaths()
|
||||
break
|
||||
case "newTask":
|
||||
// Code that should run in response to the hello message command
|
||||
|
||||
@@ -5,12 +5,13 @@ import { HistoryItem } from "./HistoryItem"
|
||||
|
||||
// webview will hold state
|
||||
export interface ExtensionMessage {
|
||||
type: "action" | "state" | "selectedImages" | "ollamaModels" | "theme"
|
||||
type: "action" | "state" | "selectedImages" | "ollamaModels" | "theme" | "workspaceUpdated"
|
||||
text?: string
|
||||
action?: "chatButtonTapped" | "settingsButtonTapped" | "historyButtonTapped" | "didBecomeVisible"
|
||||
state?: ExtensionState
|
||||
images?: string[]
|
||||
models?: string[]
|
||||
filePaths?: string[]
|
||||
}
|
||||
|
||||
export interface ExtensionState {
|
||||
|
||||
Reference in New Issue
Block a user