Guest User

Untitled

a guest
Apr 29th, 2025
71
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 20.15 KB | None | 0 0
  1. Let's identify the files relevant to implementing the `@` file referencing feature in the prompt window. This feature likely involves:
  2.  
  3. 1. Detecting `@` in the webview input.
  4. 2. Communicating with the extension backend to search for files.
  5. 3. Displaying suggestions in the webview.
  6. 4. Handling selection and adding the file reference (and potentially content) to the context.
  7. 5. Parsing the mention in the backend (`Cline.ts`) before sending to the API.
  8.  
  9. Based on this, here are the relevant files:
  10.  
  11. 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`.
  12. ```typescript
  13. // src/core/webview/webviewMessageHandler.ts
  14. import * as vscode from "vscode"
  15. // ... other imports
  16. import { searchWorkspaceFiles } from "../../services/search/file-search" // Needed for file searching
  17. import { openMention } from "../mentions" // Might be reused or adapted
  18. import { getWorkspacePath } from "../../utils/path"
  19.  
  20. export const webviewMessageHandler = async (provider: ClineProvider, message: WebviewMessage) => {
  21. // ... (existing message handlers)
  22.  
  23. switch (message.type) {
  24. // ... other cases
  25.  
  26. case "searchFiles": { // Example: New message type for file search
  27. const workspacePath = getWorkspacePath();
  28. if (!workspacePath) {
  29. await provider.postMessageToWebview({
  30. type: "fileSearchResults",
  31. results: [],
  32. requestId: message.requestId,
  33. error: "No workspace path available",
  34. });
  35. break;
  36. }
  37. try {
  38. const results = await searchWorkspaceFiles(
  39. message.query || "",
  40. workspacePath,
  41. 20, // Limit results for suggestions
  42. );
  43. await provider.postMessageToWebview({
  44. type: "fileSearchResults",
  45. results,
  46. requestId: message.requestId,
  47. });
  48. } catch (error) {
  49. const errorMessage = error instanceof Error ? error.message : String(error);
  50. await provider.postMessageToWebview({
  51. type: "fileSearchResults",
  52. results: [],
  53. error: errorMessage,
  54. requestId: message.requestId,
  55. });
  56. }
  57. break;
  58. }
  59.  
  60. case "openMention":
  61. openMention(message.text)
  62. break;
  63. // ... other cases
  64. }
  65. }
  66.  
  67. // ...
  68. ```
  69.  
  70. 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.
  71. ```typescript
  72. // src/core/webview/ClineProvider.ts
  73. // ... other imports
  74. import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker" // Used to get workspace files
  75. import { WebviewMessage } from "../../shared/WebviewMessage" // Defines message structure
  76. import { webviewMessageHandler } from "./webviewMessageHandler" // Handles incoming messages
  77.  
  78. export class ClineProvider extends EventEmitter<ClineProviderEvents> implements vscode.WebviewViewProvider {
  79. // ... existing properties
  80.  
  81. private _workspaceTracker?: WorkspaceTracker // Tracks workspace files
  82.  
  83. constructor(
  84. // ... existing parameters
  85. ) {
  86. // ...
  87. this._workspaceTracker = new WorkspaceTracker(this)
  88. // ...
  89. }
  90.  
  91. // ...
  92.  
  93. private setWebviewMessageListener(webview: vscode.Webview) {
  94. // Process messages from the webview
  95. const onReceiveMessage = async (message: WebviewMessage) => webviewMessageHandler(this, message);
  96. webview.onDidReceiveMessage(onReceiveMessage, null, this.disposables);
  97. }
  98.  
  99. async getStateToPostToWebview() {
  100. // ... existing state gathering
  101. const cwd = this.cwd;
  102.  
  103. // Include necessary state for mentions (e.g., workspace files)
  104. // The workspace tracker already sends updates via postMessageToWebview
  105. // but initial state might be useful.
  106.  
  107. return {
  108. // ... existing state properties
  109. cwd,
  110. // ...
  111. }
  112. }
  113.  
  114. // ... other methods
  115. }
  116. ```
  117.  
  118. 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`.
  119. ```typescript
  120. // src/core/mentions/index.ts
  121. import fs from "fs/promises"
  122. import * as path from "path"
  123. // ... other imports
  124. import { mentionRegexGlobal } from "../../shared/context-mentions" // Regex definition
  125. import { extractTextFromFile } from "../../integrations/misc/extract-text" // File reading utility
  126. import { FileContextTracker } from "../context-tracking/FileContextTracker" // Potentially track mentioned files
  127.  
  128. // ... existing openMention function
  129.  
  130. export async function parseMentions(
  131. text: string,
  132. cwd: string,
  133. urlContentFetcher: UrlContentFetcher,
  134. fileContextTracker?: FileContextTracker, // Added for tracking
  135. ): Promise<string> {
  136. const mentions: Set<string> = new Set()
  137. // The regex needs to match file paths correctly
  138. let parsedText = text.replace(mentionRegexGlobal, (match, mention) => {
  139. mentions.add(mention)
  140. if (mention.startsWith("http")) {
  141. return `'${mention}' (see below for site content)`
  142. } else if (mention.startsWith("/")) { // File paths
  143. const mentionPath = mention.slice(1)
  144. return mentionPath.endsWith("/")
  145. ? `'${mentionPath}' (see below for folder content)`
  146. : `'${mentionPath}' (see below for file content)` // File mention format
  147. }
  148. // ... other mention types (problems, git, terminal)
  149. return match
  150. })
  151.  
  152. // ... (URL handling)
  153.  
  154. for (const mention of mentions) {
  155. // ... (URL handling)
  156. if (mention.startsWith("/")) { // Handle file/folder mentions
  157. const mentionPath = mention.slice(1)
  158. try {
  159. // Use getFileOrFolderContent to fetch content
  160. const content = await getFileOrFolderContent(mentionPath, cwd)
  161. if (mention.endsWith("/")) { // Folder
  162. parsedText += `\n\n<folder_content path="${mentionPath}">\n${content}\n</folder_content>`
  163. } else { // File
  164. parsedText += `\n\n<file_content path="${mentionPath}">\n${content}\n</file_content>`
  165. // Track that this file was mentioned and its content was included
  166. if (fileContextTracker) {
  167. await fileContextTracker.trackFileContext(mentionPath, "file_mentioned")
  168. }
  169. }
  170. } catch (error) {
  171. // ... error handling
  172. }
  173. }
  174. // ... other mention types
  175. }
  176.  
  177. // ... (URL closing)
  178.  
  179. return parsedText
  180. }
  181.  
  182. async function getFileOrFolderContent(mentionPath: string, cwd: string): Promise<string> {
  183. const absPath = path.resolve(cwd, mentionPath);
  184.  
  185. try {
  186. const stats = await fs.stat(absPath);
  187.  
  188. if (stats.isFile()) {
  189. try {
  190. // Use extractTextFromFile to get numbered content
  191. const content = await extractTextFromFile(absPath);
  192. return content;
  193. } catch (error: any) {
  194. return `(Failed to read contents of ${mentionPath}): ${error.message}`;
  195. }
  196. } else if (stats.isDirectory()) {
  197. // ... (folder listing logic - might be too verbose for prompt, maybe just list files?)
  198. const entries = await fs.readdir(absPath);
  199. return `Folder contents:\n${entries.join('\n')}`;
  200. } else {
  201. return `(Cannot read contents of ${mentionPath})`;
  202. }
  203. } catch (error: any) {
  204. throw new Error(`Failed to access path "${mentionPath}": ${error.message}`);
  205. }
  206. }
  207.  
  208. // ... other functions
  209. ```
  210.  
  211. 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.
  212. ```typescript
  213. // src/shared/context-mentions.ts
  214. /*
  215. Updated Regex Idea (needs refinement):
  216. - Start with @
  217. - Optionally match /
  218. - Match file/folder characters (alphanumeric, ., -, _, /)
  219. - Ensure it's followed by whitespace or end of string, excluding trailing punctuation.
  220.  
  221. Example: /@([/\w.-]+[^\s.,;:!?])(?=[.,;:!?]?(?:\s|$))/
  222. */
  223. export const mentionRegex =
  224. /@((?:\/|\w+:\/\/)[^\s]+?|[a-f0-9]{7,40}\b|problems\b|git-changes\b|terminal\b)(?=[.,;:!?]?(?=[\s\r\n]|$))/
  225. // ^^^ This regex needs to be updated to reliably capture file paths like @/src/file.ts or @relative/path.js
  226.  
  227. // Suggestion for a file-path specific regex (might need combining with the original):
  228. // const filePathMentionRegex = /@([./\w-]+)(?=\s|$|[.,;:!?])/;
  229.  
  230. export const mentionRegexGlobal = new RegExp(mentionRegex.source, "g")
  231.  
  232. // ... rest of the file (interfaces for suggestions)
  233. ```
  234.  
  235. 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.
  236. ```typescript
  237. // src/core/Cline.ts
  238. // ... other imports
  239. import { parseMentions } from "./mentions"; // Import the mention parser
  240. import { FileContextTracker } from "./context-tracking/FileContextTracker"; // To track mentioned files
  241.  
  242. export class Cline extends EventEmitter<ClineEvents> {
  243. // ... existing properties
  244. private fileContextTracker: FileContextTracker; // Add this
  245.  
  246. constructor({ /* ... */ }) {
  247. // ... existing constructor logic
  248. this.fileContextTracker = new FileContextTracker(provider, this.taskId);
  249. // ...
  250. }
  251.  
  252. // ...
  253.  
  254. async loadContext(userContent: UserContent, includeFileDetails: boolean = false) {
  255. // Process userContent array, which contains various block types...
  256. // It needs to handle TextBlockParam which might contain @mentions.
  257. const parsedUserContent = await Promise.all(
  258. userContent.map(async (block) => {
  259. // ... (existing logic for feedback/answer tags)
  260.  
  261. if (block.type === "text") {
  262. // Parse mentions within the text block
  263. return {
  264. ...block,
  265. // Pass the fileContextTracker here
  266. text: await parseMentions(block.text, this.cwd, this.urlContentFetcher, this.fileContextTracker),
  267. };
  268. }
  269. // ... (handle other block types like tool_result if necessary)
  270. return block;
  271. })
  272. );
  273.  
  274. const environmentDetails = await this.getEnvironmentDetails(includeFileDetails);
  275.  
  276. return [parsedUserContent, environmentDetails];
  277. }
  278.  
  279. // ... other methods like recursivelyMakeClineRequests which calls loadContext
  280. }
  281. ```
  282.  
  283. 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.
  284. ```typescript
  285. // src/services/search/file-search.ts
  286. import * as vscode from "vscode"
  287. import * as path from "path"
  288. // ... other imports
  289. import { Fzf } from "fzf"; // Used for fuzzy finding
  290.  
  291. // ... existing executeRipgrep functions
  292.  
  293. export async function searchWorkspaceFiles(
  294. query: string,
  295. workspacePath: string,
  296. limit: number = 20, // Limit results for suggestions
  297. ): Promise<{ path: string; type: "file" | "folder"; label?: string }[]> {
  298. try {
  299. // Get all files and directories
  300. const allItems = await executeRipgrepForFiles(workspacePath, 5000); // Get a larger set initially
  301.  
  302. // Filter out ignored files using RooIgnoreController if available
  303. // This needs access to the RooIgnoreController instance, which might require passing it down or making it accessible globally/statically.
  304. // Example (assuming access):
  305. // const allowedItems = rooIgnoreController.filterPaths(allItems.map(item => item.path));
  306. // const filteredItems = allItems.filter(item => allowedItems.includes(item.path));
  307.  
  308. // For now, let's assume allItems are pre-filtered or filtering happens elsewhere
  309.  
  310. // If no query, return top items (maybe prioritize recently used or open files?)
  311. if (!query.trim()) {
  312. // Consider returning recently opened/used files first here
  313. return allItems.slice(0, limit);
  314. }
  315.  
  316. // Create search items for FZF
  317. const searchItems = allItems.map((item) => ({
  318. original: item,
  319. // Searching on the relative path seems most intuitive
  320. searchStr: `${item.path} ${item.label || ""}`,
  321. }));
  322.  
  323. // Run fzf search
  324. const fzf = new Fzf(searchItems, {
  325. selector: (item) => item.searchStr,
  326. // tiebreakers: [byLengthAsc], // Optional: configure tiebreakers
  327. limit: limit, // Apply the limit
  328. });
  329.  
  330. // Get matching results
  331. const fzfResults = fzf.find(query).map((result) => result.item.original);
  332.  
  333. // Verify types (this might be redundant if executeRipgrepForFiles is reliable)
  334. // ... (verification logic remains the same)
  335.  
  336. return fzfResults; // Return the fuzzy-found results
  337. } catch (error) {
  338. console.error("Error in searchWorkspaceFiles:", error);
  339. return [];
  340. }
  341. }
  342. ```
  343.  
  344. 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.
  345. ```typescript
  346. // src/core/ignore/RooIgnoreController.ts
  347. import path from "path";
  348. import ignore, { Ignore } from "ignore";
  349. // ... other imports
  350.  
  351. export class RooIgnoreController {
  352. // ... existing properties and methods
  353.  
  354. /**
  355. * Filter an array of paths, removing those that should be ignored.
  356. * @param paths - Array of paths to filter (relative to cwd).
  357. * @returns Array of allowed paths.
  358. */
  359. filterPaths(paths: string[]): string[] {
  360. try {
  361. return paths.filter((p) => this.validateAccess(p)); // Use validateAccess to filter
  362. } catch (error) {
  363. console.error("Error filtering paths:", error);
  364. return []; // Fail closed for security
  365. }
  366. }
  367.  
  368. // ... other methods
  369. }
  370. ```
  371.  
  372. 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.
  373. ```typescript
  374. // src/integrations/workspace/WorkspaceTracker.ts
  375. import * as vscode from "vscode";
  376. // ... other imports
  377. import { listFiles } from "../../services/glob/list-files"; // Used to get initial files
  378. import { toRelativePath } from "../../utils/path";
  379. import { getWorkspacePath } from "../../utils/path";
  380.  
  381. class WorkspaceTracker {
  382. // ... existing properties
  383. private filePaths: Set<string> = new Set(); // Store normalized absolute paths
  384.  
  385. // ... constructor and registerListeners
  386.  
  387. async initializeFilePaths() {
  388. // ... (existing logic)
  389. const tempCwd = this.cwd;
  390. if (!tempCwd) return; // Check if cwd is valid
  391.  
  392. // Get files and apply filtering here if possible, or ensure searchWorkspaceFiles does it
  393. const [files, _] = await listFiles(tempCwd, true, MAX_INITIAL_FILES);
  394.  
  395. // Ensure we only update if the workspace hasn't changed during async op
  396. if (this.prevWorkSpacePath !== tempCwd) {
  397. return;
  398. }
  399.  
  400. // Clear existing paths before adding new ones
  401. this.filePaths.clear();
  402. files.slice(0, MAX_INITIAL_FILES).forEach((file) => this.filePaths.add(this.normalizeFilePath(file)));
  403. this.workspaceDidUpdate(); // Update webview
  404. }
  405.  
  406. // ...
  407.  
  408. // Update webview with relative paths
  409. private workspaceDidUpdate() {
  410. if (this.updateTimer) {
  411. clearTimeout(this.updateTimer);
  412. }
  413. this.updateTimer = setTimeout(() => {
  414. const currentCwd = this.cwd; // Get current cwd
  415. if (!currentCwd) return; // Exit if no cwd
  416.  
  417. // Convert absolute paths to relative before sending
  418. const relativeFilePaths = Array.from(this.filePaths).map((file) => toRelativePath(file, currentCwd));
  419.  
  420. this.providerRef.deref()?.postMessageToWebview({
  421. type: "workspaceUpdated",
  422. filePaths: relativeFilePaths, // Send relative paths
  423. openedTabs: this.getOpenedTabsInfo(),
  424. });
  425. this.updateTimer = null;
  426. }, 300);
  427. }
  428.  
  429. // Normalizes and resolves paths, adding trailing slash for dirs
  430. private normalizeFilePath(filePath: string): string {
  431. const currentCwd = this.cwd; // Get current cwd
  432. // Resolve path relative to cwd if it exists, otherwise resolve absolutely
  433. const resolvedPath = currentCwd ? path.resolve(currentCwd, filePath) : path.resolve(filePath);
  434. // Add trailing slash if it originally had one (likely a directory)
  435. return filePath.endsWith(path.sep) || filePath.endsWith('/') ? resolvedPath + path.sep : resolvedPath;
  436. }
  437.  
  438. // Adds a file path, normalizing and checking type
  439. private async addFilePath(filePath: string): Promise<string> {
  440. const MAX_TOTAL_FILES = 2000; // Example limit
  441. if (this.filePaths.size >= MAX_TOTAL_FILES) {
  442. console.warn(`WorkspaceTracker: Reached file limit (${MAX_TOTAL_FILES})`);
  443. return filePath; // Don't add more files if limit is reached
  444. }
  445. const normalizedPath = this.normalizeFilePath(filePath);
  446. try {
  447. const stat = await vscode.workspace.fs.stat(vscode.Uri.file(normalizedPath));
  448. const isDirectory = (stat.type & vscode.FileType.Directory) !== 0;
  449. // Ensure directories always have a trailing slash for consistency
  450. const pathWithSlash = isDirectory && !normalizedPath.endsWith(path.sep) ? normalizedPath + path.sep : normalizedPath;
  451. this.filePaths.add(pathWithSlash);
  452. return pathWithSlash;
  453. } catch (error) {
  454. // If stat fails, add the path as is (might be a temp file)
  455. console.warn(`WorkspaceTracker: Could not stat file ${normalizedPath}. Adding as is. Error: ${error}`);
  456. this.filePaths.add(normalizedPath);
  457. return normalizedPath;
  458. }
  459. }
  460.  
  461. // Removes a file path, handling potential trailing slashes
  462. private async removeFilePath(filePath: string): Promise<boolean> {
  463. const normalizedPath = this.normalizeFilePath(filePath);
  464. // Try removing both with and without trailing slash for robustness
  465. const removedWithoutSlash = this.filePaths.delete(normalizedPath);
  466. const removedWithSlash = this.filePaths.delete(normalizedPath + path.sep);
  467. return removedWithoutSlash || removedWithSlash;
  468. }
  469. // ...
  470. }
  471. ```
  472.  
  473. 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