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+ const excludeCache = new Map < string , IgnoreRecord > ( )
8+ const fileWatchers = new Map < string , vscode . FileSystemWatcher > ( )
9+
10+ function getCacheKey ( workspaceFolder : vscode . WorkspaceFolder | null ) : string {
11+ return workspaceFolder ?. uri . toString ( ) ?? 'no-workspace'
12+ }
13+
14+ export async function initializeCache ( workspaceFolder : vscode . WorkspaceFolder | null ) : Promise < void > {
15+ const cacheKey = getCacheKey ( workspaceFolder )
16+ if ( excludeCache . has ( cacheKey ) ) {
17+ return
18+ }
19+
20+ const useIgnoreFiles = vscode . workspace
21+ . getConfiguration ( '' , workspaceFolder )
22+ . get < boolean > ( 'search.useIgnoreFiles' )
23+
24+ let sgignoreExclude : IgnoreRecord = { }
25+
26+ if ( useIgnoreFiles && workspaceFolder ) {
27+ sgignoreExclude = await readIgnoreFile (
28+ vscode . Uri . joinPath ( workspaceFolder . uri , '.sourcegraph' , 'ignore' )
29+ )
30+
31+ setupFileWatcher ( workspaceFolder , '.sourcegraph/ignore' )
32+ }
33+
34+ excludeCache . set ( cacheKey , sgignoreExclude )
35+ }
36+
37+ function setupFileWatcher ( workspaceFolder : vscode . WorkspaceFolder , filename : string ) : void {
38+ const watcherKey = `${ workspaceFolder . uri . toString ( ) } :${ filename } `
39+ if ( fileWatchers . has ( watcherKey ) ) {
40+ return
41+ }
42+
43+ const pattern = new vscode . RelativePattern ( workspaceFolder , filename )
44+ const watcher = vscode . workspace . createFileSystemWatcher ( pattern )
45+
46+ const updateCache = async ( ) => {
47+ const cacheKey = getCacheKey ( workspaceFolder )
48+ const cached = excludeCache . get ( cacheKey )
49+ if ( ! cached ) return
50+
51+ const fileUri = vscode . Uri . joinPath ( workspaceFolder . uri , filename )
52+ const ignoreData = await readIgnoreFile ( fileUri )
53+
54+ if ( filename === '.sourcegraph/ignore' ) {
55+ excludeCache . set ( cacheKey , ignoreData )
56+ }
57+ }
58+
59+ watcher . onDidChange ( updateCache )
60+ watcher . onDidCreate ( updateCache )
61+ watcher . onDidDelete ( ( ) => {
62+ const cacheKey = getCacheKey ( workspaceFolder )
63+ const cached = excludeCache . get ( cacheKey )
64+ if ( ! cached ) return
65+
66+ if ( filename === '.sourcegraph/ignore' ) {
67+ excludeCache . delete ( cacheKey )
68+ }
69+ } )
70+
71+ fileWatchers . set ( watcherKey , watcher )
72+ }
73+
74+ export async function getExcludePattern (
75+ workspaceFolder : vscode . WorkspaceFolder | null
76+ ) : Promise < string > {
77+ await initializeCache ( workspaceFolder )
78+
79+ const cacheKey = getCacheKey ( workspaceFolder )
80+ const cached = excludeCache . get ( cacheKey )
81+ const excludePatterns = Object . keys ( cached ?? { } ) . filter ( key => cached ?. [ key ] === true )
82+ return `{${ excludePatterns . join ( ',' ) } }`
83+ }
84+
85+ /**
86+ * Dispose all file watchers and clear caches. Call this when the extension is deactivated.
87+ */
88+ function disposeFileWatchers ( ) : void {
89+ for ( const watcher of fileWatchers . values ( ) ) {
90+ watcher . dispose ( )
91+ }
92+ fileWatchers . clear ( )
93+ excludeCache . clear ( )
94+ }
95+
596export async function isUriIgnoredByContextFilterWithNotification (
697 uri : vscode . Uri ,
798 feature : CodyIgnoreFeature
@@ -12,3 +103,54 @@ export async function isUriIgnoredByContextFilterWithNotification(
12103 }
13104 return isIgnored
14105}
106+
107+ /**
108+ * Initialize the ContextFiltersProvider with exclude pattern getter.
109+ * Returns a disposable that cleans up the configuration when disposed.
110+ */
111+ export function initializeContextFiltersProvider ( ) : vscode . Disposable {
112+ // Set up exclude pattern getter for ContextFiltersProvider
113+ ContextFiltersProvider . excludePatternGetter = {
114+ getExcludePattern,
115+ getWorkspaceFolder : ( uri : vscode . Uri ) => vscode . workspace . getWorkspaceFolder ( uri ) ?? null ,
116+ }
117+
118+ // Return disposable that cleans up the configuration
119+ return {
120+ dispose : disposeFileWatchers ,
121+ }
122+ }
123+
124+ export async function readIgnoreFile ( uri : vscode . Uri ) : Promise < IgnoreRecord > {
125+ const ignore : IgnoreRecord = { }
126+ try {
127+ const data = await vscode . workspace . fs . readFile ( uri )
128+ for ( let line of Buffer . from ( data ) . toString ( 'utf-8' ) . split ( '\n' ) ) {
129+ if ( line . startsWith ( '!' ) ) {
130+ continue
131+ }
132+
133+ // Strip comment and whitespace.
134+ line = line . replace ( / \s * ( # .* ) ? $ / , '' ) . trim ( )
135+
136+ if ( line === '' ) {
137+ continue
138+ }
139+
140+ // Replace , with . that contain commas to avoid typos for entries such as
141+ // *,something
142+ if ( line . includes ( ',' ) ) {
143+ line = line . replace ( ',' , '.' )
144+ }
145+
146+ if ( line . endsWith ( '/' ) ) {
147+ line = line . slice ( 0 , - 1 )
148+ }
149+ if ( ! line . startsWith ( '/' ) && ! line . startsWith ( '**/' ) ) {
150+ line = `**/${ line } `
151+ }
152+ ignore [ line ] = true
153+ }
154+ } catch { }
155+ return ignore
156+ }
0 commit comments