Path: blob/main/extensions/copilot/src/extension/chatSessions/claude/node/claudeCodeFolderMru.ts
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { IGitService } from '../../../../platform/git/common/gitService';6import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';7import { raceTimeout } from '../../../../util/vs/base/common/async';8import { CancellationToken } from '../../../../util/vs/base/common/cancellation';9import { ResourceMap, ResourceSet } from '../../../../util/vs/base/common/map';10import { URI } from '../../../../util/vs/base/common/uri';11import { FolderRepositoryMRUEntry, IChatFolderMruService } from '../../common/folderRepositoryManager';12import { IClaudeCodeSessionService } from './sessionParser/claudeCodeSessionService';1314type Mutable<T> = { -readonly [K in keyof T]: T[K] };1516const WORKTREE_PATH_PATTERNS = ['.claude/worktrees/', '.worktrees/copilot-'] as const;1718function isWorktreePath(path: string): boolean {19return WORKTREE_PATH_PATTERNS.some(pattern => path.includes(pattern));20}2122export class ClaudeCodeFolderMruService implements IChatFolderMruService {23declare _serviceBrand: undefined;24private readonly removedFolders = new ResourceSet();25private cachedEntries: FolderRepositoryMRUEntry[] | undefined = undefined;2627constructor(28@IClaudeCodeSessionService private readonly sessionService: IClaudeCodeSessionService,29@IGitService private readonly gitService: IGitService,30@IWorkspaceService private readonly workspaceService: IWorkspaceService,31) { }3233async getRecentlyUsedFolders(token: CancellationToken): Promise<FolderRepositoryMRUEntry[]> {34const cachedEntries = this.cachedEntries;35const entries = this.getRecentlyUsedFoldersImpl(token).then(entries => {36this.cachedEntries = entries;37return entries;38});3940return (cachedEntries ? cachedEntries : await entries).filter(e => !this.removedFolders.has(e.folder));41}4243private async getRecentlyUsedFoldersImpl(token: CancellationToken): Promise<FolderRepositoryMRUEntry[]> {44const mruEntries = new ResourceMap<Mutable<FolderRepositoryMRUEntry>>();4546// We're getting MRU, don't delay session retrieve by more than 5s47const sessions = await raceTimeout(this.sessionService.getAllSessions(token), 5_000);4849for (const session of (sessions ?? [])) {50if (!session.cwd) {51continue;52}53if (isWorktreePath(session.cwd)) {54continue;55}56const folderUri = URI.file(session.cwd);57const lastAccessed = session.lastRequestEnded ?? session.lastRequestStarted ?? session.created ?? 0;58mruEntries.set(folderUri, {59folder: folderUri,60repository: undefined,61lastAccessed,62});63}6465// Add recent git repositories66for (const repo of this.gitService.getRecentRepositories()) {67if (isWorktreePath(repo.rootUri.path)) {68continue;69}70const existingEntry = mruEntries.get(repo.rootUri);71if (existingEntry) {72existingEntry.lastAccessed = Math.max(existingEntry.lastAccessed, repo.lastAccessTime);73existingEntry.repository = repo.rootUri;74continue;75}76mruEntries.set(repo.rootUri, {77folder: repo.rootUri,78repository: repo.rootUri,79lastAccessed: repo.lastAccessTime,80});81}8283// If in multi-root folder add the folders as well, but on top.84for (const folder of this.workspaceService.getWorkspaceFolders()) {85const existingEntry = mruEntries.get(folder);86if (existingEntry) {87continue;88}89mruEntries.set(folder, {90folder,91repository: undefined,92lastAccessed: Date.now(),93});94}9596return Array.from(mruEntries.values())97.sort((a, b) => b.lastAccessed - a.lastAccessed);98}99100async deleteRecentlyUsedFolder(folder: URI): Promise<void> {101this.removedFolders.add(folder);102}103}104105106