1- import { type IsIgnored , contextFiltersProvider } from '@sourcegraph/cody-shared'
2- import type * as vscode from 'vscode'
1+ import { ContextFiltersProvider , type IsIgnored , contextFiltersProvider } from '@sourcegraph/cody-shared'
2+ import * as vscode from 'vscode'
33import { type CodyIgnoreFeature , showCodyIgnoreNotification } from './notification'
44
5+ type IgnoreRecord = Record < string , boolean >
6+
7+ interface CachedExcludeData {
8+ gitignoreExclude : IgnoreRecord
9+ ignoreExclude : IgnoreRecord
10+ sgignoreExclude : IgnoreRecord
11+ }
12+
13+ const excludeCache = new Map < string , CachedExcludeData > ( )
14+ const fileWatchers = new Map < string , vscode . FileSystemWatcher > ( )
15+
16+ function getCacheKey ( workspaceFolder : vscode . WorkspaceFolder | null ) : string {
17+ return workspaceFolder ?. uri . toString ( ) ?? 'no-workspace'
18+ }
19+
20+ export async function initializeCache ( workspaceFolder : vscode . WorkspaceFolder | null ) : Promise < void > {
21+ const cacheKey = getCacheKey ( workspaceFolder )
22+ if ( excludeCache . has ( cacheKey ) ) {
23+ return
24+ }
25+
26+ const useIgnoreFiles = vscode . workspace
27+ . getConfiguration ( '' , workspaceFolder )
28+ . get < boolean > ( 'search.useIgnoreFiles' )
29+
30+ let gitignoreExclude : IgnoreRecord = { }
31+ let ignoreExclude : IgnoreRecord = { }
32+ let sgignoreExclude : IgnoreRecord = { }
33+
34+ if ( useIgnoreFiles && workspaceFolder ) {
35+ gitignoreExclude = await readIgnoreFile ( vscode . Uri . joinPath ( workspaceFolder . uri , '.gitignore' ) )
36+ ignoreExclude = await readIgnoreFile ( vscode . Uri . joinPath ( workspaceFolder . uri , '.ignore' ) )
37+ sgignoreExclude = await readIgnoreFile (
38+ vscode . Uri . joinPath ( workspaceFolder . uri , '.sourcegraph' , '.ignore' )
39+ )
40+
41+ setupFileWatcher ( workspaceFolder , '.gitignore' )
42+ setupFileWatcher ( workspaceFolder , '.ignore' )
43+ setupFileWatcher ( workspaceFolder , '.sourcegraph/.ignore' )
44+ }
45+
46+ excludeCache . set ( cacheKey , { gitignoreExclude, ignoreExclude, sgignoreExclude } )
47+ }
48+
49+ function setupFileWatcher ( workspaceFolder : vscode . WorkspaceFolder , filename : string ) : void {
50+ const watcherKey = `${ workspaceFolder . uri . toString ( ) } :${ filename } `
51+ if ( fileWatchers . has ( watcherKey ) ) {
52+ return
53+ }
54+
55+ const pattern = new vscode . RelativePattern ( workspaceFolder , filename )
56+ const watcher = vscode . workspace . createFileSystemWatcher ( pattern )
57+
58+ const updateCache = async ( ) => {
59+ const cacheKey = getCacheKey ( workspaceFolder )
60+ const cached = excludeCache . get ( cacheKey )
61+ if ( ! cached ) return
62+
63+ const fileUri = vscode . Uri . joinPath ( workspaceFolder . uri , filename )
64+ const ignoreData = await readIgnoreFile ( fileUri )
65+
66+ if ( filename === '.gitignore' ) {
67+ cached . gitignoreExclude = ignoreData
68+ } else if ( filename === '.ignore' ) {
69+ cached . ignoreExclude = ignoreData
70+ } else if ( filename === '.sourcegraph/.ignore' ) {
71+ cached . sgignoreExclude = ignoreData
72+ }
73+ }
74+
75+ watcher . onDidChange ( updateCache )
76+ watcher . onDidCreate ( updateCache )
77+ watcher . onDidDelete ( ( ) => {
78+ const cacheKey = getCacheKey ( workspaceFolder )
79+ const cached = excludeCache . get ( cacheKey )
80+ if ( ! cached ) return
81+
82+ if ( filename === '.gitignore' ) {
83+ cached . gitignoreExclude = { }
84+ } else if ( filename === '.ignore' ) {
85+ cached . ignoreExclude = { }
86+ } else if ( filename === '.sourcegraph/.ignore' ) {
87+ cached . sgignoreExclude = { }
88+ }
89+ } )
90+
91+ fileWatchers . set ( watcherKey , watcher )
92+ }
93+
94+ export async function getExcludePattern (
95+ workspaceFolder : vscode . WorkspaceFolder | null
96+ ) : Promise < string > {
97+ await initializeCache ( workspaceFolder )
98+
99+ const config = vscode . workspace . getConfiguration ( '' , workspaceFolder )
100+ const filesExclude = config . get < IgnoreRecord > ( 'files.exclude' , { } )
101+ const searchExclude = config . get < IgnoreRecord > ( 'search.exclude' , { } )
102+
103+ const cacheKey = getCacheKey ( workspaceFolder )
104+ const cached = excludeCache . get ( cacheKey )
105+ const gitignoreExclude = cached ?. gitignoreExclude ?? { }
106+ const ignoreExclude = cached ?. ignoreExclude ?? { }
107+ const sgignoreExclude = cached ?. sgignoreExclude ?? { }
108+
109+ const mergedExclude : IgnoreRecord = {
110+ ...filesExclude ,
111+ ...searchExclude ,
112+ ...gitignoreExclude ,
113+ ...ignoreExclude ,
114+ ...sgignoreExclude ,
115+ }
116+ const excludePatterns = Object . keys ( mergedExclude ) . filter ( key => mergedExclude [ key ] === true )
117+ return `{${ excludePatterns . join ( ',' ) } }`
118+ }
119+
120+ async function readIgnoreFile ( uri : vscode . Uri ) : Promise < IgnoreRecord > {
121+ const ignore : IgnoreRecord = { }
122+ try {
123+ const data = await vscode . workspace . fs . readFile ( uri )
124+ for ( let line of Buffer . from ( data ) . toString ( 'utf-8' ) . split ( '\n' ) ) {
125+ if ( line . startsWith ( '!' ) ) {
126+ continue
127+ }
128+
129+ // Strip comment and trailing whitespace.
130+ line = line . replace ( / \s * ( # .* ) ? $ / , '' )
131+
132+ if ( line === '' ) {
133+ continue
134+ }
135+
136+ if ( line . endsWith ( '/' ) ) {
137+ line = line . slice ( 0 , - 1 )
138+ }
139+ if ( ! line . startsWith ( '/' ) && ! line . startsWith ( '**/' ) ) {
140+ line = `**/${ line } `
141+ }
142+ ignore [ line ] = true
143+ }
144+ } catch { }
145+ return ignore
146+ }
147+
148+ /**
149+ * Dispose all file watchers and clear caches. Call this when the extension is deactivated.
150+ */
151+ function disposeFileWatchers ( ) : void {
152+ for ( const watcher of fileWatchers . values ( ) ) {
153+ watcher . dispose ( )
154+ }
155+ fileWatchers . clear ( )
156+ excludeCache . clear ( )
157+ }
158+
5159export async function isUriIgnoredByContextFilterWithNotification (
6160 uri : vscode . Uri ,
7161 feature : CodyIgnoreFeature
@@ -12,3 +166,20 @@ export async function isUriIgnoredByContextFilterWithNotification(
12166 }
13167 return isIgnored
14168}
169+
170+ /**
171+ * Initialize the ContextFiltersProvider with exclude pattern getter.
172+ * Returns a disposable that cleans up the configuration when disposed.
173+ */
174+ export function initializeContextFiltersProvider ( ) : vscode . Disposable {
175+ // Set up exclude pattern getter for ContextFiltersProvider
176+ ContextFiltersProvider . excludePatternGetter = {
177+ getExcludePattern,
178+ getWorkspaceFolder : ( uri : vscode . Uri ) => vscode . workspace . getWorkspaceFolder ( uri ) ?? null ,
179+ }
180+
181+ // Return disposable that cleans up the configuration
182+ return {
183+ dispose : disposeFileWatchers ,
184+ }
185+ }
0 commit comments