Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Let's identify the files relevant to implementing the `@` file referencing feature in the prompt window. This feature likely involves:
- 1. Detecting `@` in the webview input.
- 2. Communicating with the extension backend to search for files.
- 3. Displaying suggestions in the webview.
- 4. Handling selection and adding the file reference (and potentially content) to the context.
- 5. Parsing the mention in the backend (`Cline.ts`) before sending to the API.
- Based on this, here are the relevant files:
- 1. **`src/core/webview/webviewMessageHandler.ts`**: This file likely handles messages *from* the webview. It would need to process messages like `userTyped@`, `searchFilesForMention`, or `fileMentionSelected`.
- ```typescript
- // src/core/webview/webviewMessageHandler.ts
- import * as vscode from "vscode"
- // ... other imports
- import { searchWorkspaceFiles } from "../../services/search/file-search" // Needed for file searching
- import { openMention } from "../mentions" // Might be reused or adapted
- import { getWorkspacePath } from "../../utils/path"
- export const webviewMessageHandler = async (provider: ClineProvider, message: WebviewMessage) => {
- // ... (existing message handlers)
- switch (message.type) {
- // ... other cases
- case "searchFiles": { // Example: New message type for file search
- const workspacePath = getWorkspacePath();
- if (!workspacePath) {
- await provider.postMessageToWebview({
- type: "fileSearchResults",
- results: [],
- requestId: message.requestId,
- error: "No workspace path available",
- });
- break;
- }
- try {
- const results = await searchWorkspaceFiles(
- message.query || "",
- workspacePath,
- 20, // Limit results for suggestions
- );
- await provider.postMessageToWebview({
- type: "fileSearchResults",
- results,
- requestId: message.requestId,
- });
- } catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- await provider.postMessageToWebview({
- type: "fileSearchResults",
- results: [],
- error: errorMessage,
- requestId: message.requestId,
- });
- }
- break;
- }
- case "openMention":
- openMention(message.text)
- break;
- // ... other cases
- }
- }
- // ...
- ```
- 2. **`src/core/webview/ClineProvider.ts`**: This acts as the bridge. It sends state updates (like the list of workspace files for initial suggestions) to the webview and receives messages handled by `webviewMessageHandler`. It holds the `Cline` instance.
- ```typescript
- // src/core/webview/ClineProvider.ts
- // ... other imports
- import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker" // Used to get workspace files
- import { WebviewMessage } from "../../shared/WebviewMessage" // Defines message structure
- import { webviewMessageHandler } from "./webviewMessageHandler" // Handles incoming messages
- export class ClineProvider extends EventEmitter<ClineProviderEvents> implements vscode.WebviewViewProvider {
- // ... existing properties
- private _workspaceTracker?: WorkspaceTracker // Tracks workspace files
- constructor(
- // ... existing parameters
- ) {
- // ...
- this._workspaceTracker = new WorkspaceTracker(this)
- // ...
- }
- // ...
- private setWebviewMessageListener(webview: vscode.Webview) {
- // Process messages from the webview
- const onReceiveMessage = async (message: WebviewMessage) => webviewMessageHandler(this, message);
- webview.onDidReceiveMessage(onReceiveMessage, null, this.disposables);
- }
- async getStateToPostToWebview() {
- // ... existing state gathering
- const cwd = this.cwd;
- // Include necessary state for mentions (e.g., workspace files)
- // The workspace tracker already sends updates via postMessageToWebview
- // but initial state might be useful.
- return {
- // ... existing state properties
- cwd,
- // ...
- }
- }
- // ... other methods
- }
- ```
- 3. **`src/core/mentions/index.ts`**: This seems to be the core logic for parsing mentions (like `@/path/to/file`) and fetching their content to include in the prompt. It will need to be adapted or used by `Cline.ts`.
- ```typescript
- // src/core/mentions/index.ts
- import fs from "fs/promises"
- import * as path from "path"
- // ... other imports
- import { mentionRegexGlobal } from "../../shared/context-mentions" // Regex definition
- import { extractTextFromFile } from "../../integrations/misc/extract-text" // File reading utility
- import { FileContextTracker } from "../context-tracking/FileContextTracker" // Potentially track mentioned files
- // ... existing openMention function
- export async function parseMentions(
- text: string,
- cwd: string,
- urlContentFetcher: UrlContentFetcher,
- fileContextTracker?: FileContextTracker, // Added for tracking
- ): Promise<string> {
- const mentions: Set<string> = new Set()
- // The regex needs to match file paths correctly
- 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("/")) { // File paths
- const mentionPath = mention.slice(1)
- return mentionPath.endsWith("/")
- ? `'${mentionPath}' (see below for folder content)`
- : `'${mentionPath}' (see below for file content)` // File mention format
- }
- // ... other mention types (problems, git, terminal)
- return match
- })
- // ... (URL handling)
- for (const mention of mentions) {
- // ... (URL handling)
- if (mention.startsWith("/")) { // Handle file/folder mentions
- const mentionPath = mention.slice(1)
- try {
- // Use getFileOrFolderContent to fetch content
- const content = await getFileOrFolderContent(mentionPath, cwd)
- if (mention.endsWith("/")) { // Folder
- parsedText += `\n\n<folder_content path="${mentionPath}">\n${content}\n</folder_content>`
- } else { // File
- 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")
- }
- }
- } catch (error) {
- // ... error handling
- }
- }
- // ... other mention types
- }
- // ... (URL closing)
- return parsedText
- }
- async function getFileOrFolderContent(mentionPath: string, cwd: string): Promise<string> {
- const absPath = path.resolve(cwd, mentionPath);
- try {
- const stats = await fs.stat(absPath);
- if (stats.isFile()) {
- try {
- // Use extractTextFromFile to get numbered content
- const content = await extractTextFromFile(absPath);
- return content;
- } catch (error: any) {
- return `(Failed to read contents of ${mentionPath}): ${error.message}`;
- }
- } else if (stats.isDirectory()) {
- // ... (folder listing logic - might be too verbose for prompt, maybe just list files?)
- const entries = await fs.readdir(absPath);
- return `Folder contents:\n${entries.join('\n')}`;
- } else {
- return `(Cannot read contents of ${mentionPath})`;
- }
- } catch (error: any) {
- throw new Error(`Failed to access path "${mentionPath}": ${error.message}`);
- }
- }
- // ... other functions
- ```
- 4. **`src/shared/context-mentions.ts`**: Defines the regex for matching mentions. This will need to be updated or a new regex created specifically for file path mentions if the current one isn't suitable.
- ```typescript
- // src/shared/context-mentions.ts
- /*
- Updated Regex Idea (needs refinement):
- - Start with @
- - Optionally match /
- - Match file/folder characters (alphanumeric, ., -, _, /)
- - Ensure it's followed by whitespace or end of string, excluding trailing punctuation.
- Example: /@([/\w.-]+[^\s.,;:!?])(?=[.,;:!?]?(?:\s|$))/
- */
- export const mentionRegex =
- /@((?:\/|\w+:\/\/)[^\s]+?|[a-f0-9]{7,40}\b|problems\b|git-changes\b|terminal\b)(?=[.,;:!?]?(?=[\s\r\n]|$))/
- // ^^^ This regex needs to be updated to reliably capture file paths like @/src/file.ts or @relative/path.js
- // Suggestion for a file-path specific regex (might need combining with the original):
- // const filePathMentionRegex = /@([./\w-]+)(?=\s|$|[.,;:!?])/;
- export const mentionRegexGlobal = new RegExp(mentionRegex.source, "g")
- // ... rest of the file (interfaces for suggestions)
- ```
- 5. **`src/core/Cline.ts`**: This class orchestrates the conversation. It will need to call `parseMentions` on the user's input before sending the combined prompt + context to the AI.
- ```typescript
- // src/core/Cline.ts
- // ... other imports
- import { parseMentions } from "./mentions"; // Import the mention parser
- import { FileContextTracker } from "./context-tracking/FileContextTracker"; // To track mentioned files
- export class Cline extends EventEmitter<ClineEvents> {
- // ... existing properties
- private fileContextTracker: FileContextTracker; // Add this
- constructor({ /* ... */ }) {
- // ... existing constructor logic
- this.fileContextTracker = new FileContextTracker(provider, this.taskId);
- // ...
- }
- // ...
- async loadContext(userContent: UserContent, includeFileDetails: boolean = false) {
- // Process userContent array, which contains various block types...
- // It needs to handle TextBlockParam which might contain @mentions.
- const parsedUserContent = await Promise.all(
- userContent.map(async (block) => {
- // ... (existing logic for feedback/answer tags)
- if (block.type === "text") {
- // Parse mentions within the text block
- return {
- ...block,
- // Pass the fileContextTracker here
- text: await parseMentions(block.text, this.cwd, this.urlContentFetcher, this.fileContextTracker),
- };
- }
- // ... (handle other block types like tool_result if necessary)
- return block;
- })
- );
- const environmentDetails = await this.getEnvironmentDetails(includeFileDetails);
- return [parsedUserContent, environmentDetails];
- }
- // ... other methods like recursivelyMakeClineRequests which calls loadContext
- }
- ```
- 6. **`src/services/search/file-search.ts`**: Contains the logic for searching workspace files, likely using `ripgrep`. This will be called by the `webviewMessageHandler` when the webview requests file suggestions.
- ```typescript
- // src/services/search/file-search.ts
- import * as vscode from "vscode"
- import * as path from "path"
- // ... other imports
- import { Fzf } from "fzf"; // Used for fuzzy finding
- // ... existing executeRipgrep functions
- export async function searchWorkspaceFiles(
- query: string,
- workspacePath: string,
- limit: number = 20, // Limit results for suggestions
- ): Promise<{ path: string; type: "file" | "folder"; label?: string }[]> {
- try {
- // Get all files and directories
- const allItems = await executeRipgrepForFiles(workspacePath, 5000); // Get a larger set initially
- // Filter out ignored files using RooIgnoreController if available
- // This needs access to the RooIgnoreController instance, which might require passing it down or making it accessible globally/statically.
- // Example (assuming access):
- // const allowedItems = rooIgnoreController.filterPaths(allItems.map(item => item.path));
- // const filteredItems = allItems.filter(item => allowedItems.includes(item.path));
- // For now, let's assume allItems are pre-filtered or filtering happens elsewhere
- // If no query, return top items (maybe prioritize recently used or open files?)
- if (!query.trim()) {
- // Consider returning recently opened/used files first here
- return allItems.slice(0, limit);
- }
- // Create search items for FZF
- const searchItems = allItems.map((item) => ({
- original: item,
- // Searching on the relative path seems most intuitive
- searchStr: `${item.path} ${item.label || ""}`,
- }));
- // Run fzf search
- const fzf = new Fzf(searchItems, {
- selector: (item) => item.searchStr,
- // tiebreakers: [byLengthAsc], // Optional: configure tiebreakers
- limit: limit, // Apply the limit
- });
- // Get matching results
- const fzfResults = fzf.find(query).map((result) => result.item.original);
- // Verify types (this might be redundant if executeRipgrepForFiles is reliable)
- // ... (verification logic remains the same)
- return fzfResults; // Return the fuzzy-found results
- } catch (error) {
- console.error("Error in searchWorkspaceFiles:", error);
- return [];
- }
- }
- ```
- 7. **`src/core/ignore/RooIgnoreController.ts`**: This is crucial for ensuring that ignored files (e.g., from `.git`, `node_modules`, or a custom `.rooignore`) are *not* suggested or included in the context. The file search logic must use this.
- ```typescript
- // src/core/ignore/RooIgnoreController.ts
- import path from "path";
- import ignore, { Ignore } from "ignore";
- // ... other imports
- export class RooIgnoreController {
- // ... existing properties and methods
- /**
- * Filter an array of paths, removing those that should be ignored.
- * @param paths - Array of paths to filter (relative to cwd).
- * @returns Array of allowed paths.
- */
- filterPaths(paths: string[]): string[] {
- try {
- return paths.filter((p) => this.validateAccess(p)); // Use validateAccess to filter
- } catch (error) {
- console.error("Error filtering paths:", error);
- return []; // Fail closed for security
- }
- }
- // ... other methods
- }
- ```
- 8. **`src/integrations/workspace/WorkspaceTracker.ts`**: This tracks files in the workspace. It could provide the initial list of files to the webview or update the list dynamically for suggestions.
- ```typescript
- // src/integrations/workspace/WorkspaceTracker.ts
- import * as vscode from "vscode";
- // ... other imports
- import { listFiles } from "../../services/glob/list-files"; // Used to get initial files
- import { toRelativePath } from "../../utils/path";
- import { getWorkspacePath } from "../../utils/path";
- class WorkspaceTracker {
- // ... existing properties
- private filePaths: Set<string> = new Set(); // Store normalized absolute paths
- // ... constructor and registerListeners
- async initializeFilePaths() {
- // ... (existing logic)
- const tempCwd = this.cwd;
- if (!tempCwd) return; // Check if cwd is valid
- // Get files and apply filtering here if possible, or ensure searchWorkspaceFiles does it
- const [files, _] = await listFiles(tempCwd, true, MAX_INITIAL_FILES);
- // Ensure we only update if the workspace hasn't changed during async op
- if (this.prevWorkSpacePath !== tempCwd) {
- return;
- }
- // Clear existing paths before adding new ones
- this.filePaths.clear();
- files.slice(0, MAX_INITIAL_FILES).forEach((file) => this.filePaths.add(this.normalizeFilePath(file)));
- this.workspaceDidUpdate(); // Update webview
- }
- // ...
- // Update webview with relative paths
- private workspaceDidUpdate() {
- if (this.updateTimer) {
- clearTimeout(this.updateTimer);
- }
- this.updateTimer = setTimeout(() => {
- const currentCwd = this.cwd; // Get current cwd
- if (!currentCwd) return; // Exit if no cwd
- // Convert absolute paths to relative before sending
- const relativeFilePaths = Array.from(this.filePaths).map((file) => toRelativePath(file, currentCwd));
- this.providerRef.deref()?.postMessageToWebview({
- type: "workspaceUpdated",
- filePaths: relativeFilePaths, // Send relative paths
- openedTabs: this.getOpenedTabsInfo(),
- });
- this.updateTimer = null;
- }, 300);
- }
- // Normalizes and resolves paths, adding trailing slash for dirs
- private normalizeFilePath(filePath: string): string {
- const currentCwd = this.cwd; // Get current cwd
- // Resolve path relative to cwd if it exists, otherwise resolve absolutely
- const resolvedPath = currentCwd ? path.resolve(currentCwd, filePath) : path.resolve(filePath);
- // Add trailing slash if it originally had one (likely a directory)
- return filePath.endsWith(path.sep) || filePath.endsWith('/') ? resolvedPath + path.sep : resolvedPath;
- }
- // Adds a file path, normalizing and checking type
- private async addFilePath(filePath: string): Promise<string> {
- const MAX_TOTAL_FILES = 2000; // Example limit
- if (this.filePaths.size >= MAX_TOTAL_FILES) {
- console.warn(`WorkspaceTracker: Reached file limit (${MAX_TOTAL_FILES})`);
- return filePath; // Don't add more files if limit is reached
- }
- const normalizedPath = this.normalizeFilePath(filePath);
- try {
- const stat = await vscode.workspace.fs.stat(vscode.Uri.file(normalizedPath));
- const isDirectory = (stat.type & vscode.FileType.Directory) !== 0;
- // Ensure directories always have a trailing slash for consistency
- const pathWithSlash = isDirectory && !normalizedPath.endsWith(path.sep) ? normalizedPath + path.sep : normalizedPath;
- this.filePaths.add(pathWithSlash);
- return pathWithSlash;
- } catch (error) {
- // If stat fails, add the path as is (might be a temp file)
- console.warn(`WorkspaceTracker: Could not stat file ${normalizedPath}. Adding as is. Error: ${error}`);
- this.filePaths.add(normalizedPath);
- return normalizedPath;
- }
- }
- // Removes a file path, handling potential trailing slashes
- private async removeFilePath(filePath: string): Promise<boolean> {
- const normalizedPath = this.normalizeFilePath(filePath);
- // Try removing both with and without trailing slash for robustness
- const removedWithoutSlash = this.filePaths.delete(normalizedPath);
- const removedWithSlash = this.filePaths.delete(normalizedPath + path.sep);
- return removedWithoutSlash || removedWithSlash;
- }
- // ...
- }
- ```
- These files form the core components needed for the `@` file referencing feature, covering UI interaction, backend logic, file system access, searching, filtering, and context management.
Advertisement
Add Comment
Please, Sign In to add comment