11import { isError } from 'lodash'
22import isEqual from 'lodash/isEqual'
33import { LRUCache } from 'lru-cache'
4+ import { minimatch } from 'minimatch'
45import type { Observable } from 'observable-fns'
56import { RE2JS as RE2 } from 're2js'
67import type * as vscode from 'vscode'
@@ -22,6 +23,8 @@ import {
2223import { wrapInActiveSpan } from '../tracing'
2324import { createSubscriber } from '../utils'
2425
26+ type GetExcludePattern = ( workspaceFolder : vscode . WorkspaceFolder | null ) => Promise < string >
27+
2528interface ParsedContextFilters {
2629 include : null | ParsedContextFilterItem [ ]
2730 exclude : null | ParsedContextFilterItem [ ]
@@ -32,13 +35,21 @@ interface ParsedContextFilterItem {
3235 filePathPatterns ?: RE2 [ ]
3336}
3437
38+ enum ContextFiltersProviderError {
39+ NoRepoFound = 'no-repo-found' ,
40+ NonFileUri = 'non-file-uri' ,
41+ HasIgnoreEverythingFilters = 'has-ignore-everything-filters' ,
42+ ExcludePatternMatch = 'exclude-pattern-match' ,
43+ }
44+
3545// Note: This can not be an empty string to make all non `false` values truthy.
3646export type IsIgnored =
3747 | false
3848 | Error
39- | 'has-ignore-everything-filters'
40- | 'non-file-uri'
41- | 'no-repo-found'
49+ | ContextFiltersProviderError . NoRepoFound
50+ | ContextFiltersProviderError . NonFileUri
51+ | ContextFiltersProviderError . HasIgnoreEverythingFilters
52+ | ContextFiltersProviderError . ExcludePatternMatch
4253 | `repo:${string } `
4354
4455export type GetRepoNamesContainingUri = (
@@ -92,6 +103,11 @@ export class ContextFiltersProvider implements vscode.Disposable {
92103 private readonly contextFiltersSubscriber = createSubscriber < ContextFilters | Error > ( )
93104 public readonly onContextFiltersChanged = this . contextFiltersSubscriber . subscribe
94105
106+ static excludePatternGetter : {
107+ getExcludePattern : GetExcludePattern
108+ getWorkspaceFolder : ( uri : vscode . Uri ) => vscode . WorkspaceFolder | null
109+ }
110+
95111 // Fetches context filters and updates the cached filter results
96112 private async fetchContextFilters ( ) : Promise < RefetchIntervalHint > {
97113 try {
@@ -223,12 +239,19 @@ export class ContextFiltersProvider implements vscode.Disposable {
223239
224240 await this . fetchIfNeeded ( )
225241
242+ // Check VS Code exclude patterns
243+ if ( ContextFiltersProvider . excludePatternGetter ) {
244+ if ( await this . isExcludedByPatterns ( uri ) ) {
245+ return ContextFiltersProviderError . ExcludePatternMatch
246+ }
247+ }
248+
226249 if ( this . hasAllowEverythingFilters ( ) ) {
227250 return false
228251 }
229252
230253 if ( this . hasIgnoreEverythingFilters ( ) ) {
231- return 'has-ignore-everything-filters'
254+ return ContextFiltersProviderError . HasIgnoreEverythingFilters
232255 }
233256
234257 const maybeError = this . lastContextFiltersResponse
@@ -239,7 +262,7 @@ export class ContextFiltersProvider implements vscode.Disposable {
239262 // TODO: process non-file URIs https://github.com/sourcegraph/cody/issues/3893
240263 if ( ! isFileURI ( uri ) ) {
241264 logDebug ( 'ContextFiltersProvider' , 'isUriIgnored' , `non-file URI ${ uri . scheme } ` )
242- return 'non-file-uri'
265+ return ContextFiltersProviderError . NonFileUri
243266 }
244267
245268 if ( ! ContextFiltersProvider . repoNameResolver ) {
@@ -254,7 +277,7 @@ export class ContextFiltersProvider implements vscode.Disposable {
254277 )
255278
256279 if ( ! repoNames ?. length ) {
257- return 'no-repo-found'
280+ return ContextFiltersProviderError . NoRepoFound
258281 }
259282
260283 const ignoredRepo = repoNames . find ( repoName => this . isRepoNameIgnored__noFetch ( repoName ) )
@@ -265,6 +288,38 @@ export class ContextFiltersProvider implements vscode.Disposable {
265288 return false
266289 }
267290
291+ private async isExcludedByPatterns ( uri : vscode . Uri ) : Promise < boolean > {
292+ try {
293+ const workspaceFolder = ContextFiltersProvider . excludePatternGetter . getWorkspaceFolder ( uri )
294+ const excludePatternString =
295+ await ContextFiltersProvider . excludePatternGetter . getExcludePattern ( workspaceFolder )
296+
297+ // Parse the pattern string {pattern1,pattern2,...} into individual patterns
298+ const patterns = this . parseExcludePatternString ( excludePatternString )
299+
300+ // Get the relative path from workspace folder
301+ const relativePath = workspaceFolder
302+ ? uri . fsPath . substring ( workspaceFolder . uri . fsPath . length + 1 )
303+ : uri . fsPath
304+
305+ // Check if any pattern matches the file path
306+ return patterns . some ( pattern => minimatch ( relativePath , pattern , { dot : true } ) )
307+ } catch ( error ) {
308+ logDebug ( 'ContextFiltersProvider' , 'isExcludedByPatterns error' , { error } )
309+ return false
310+ }
311+ }
312+
313+ private parseExcludePatternString ( patternString : string ) : string [ ] {
314+ // Check if pattern string has the expected format {pattern1,pattern2,...}
315+ if ( ! patternString . startsWith ( '{' ) || ! patternString . endsWith ( '}' ) ) {
316+ return [ ]
317+ }
318+ // Remove the surrounding braces and split by comma
319+ const content = patternString . slice ( 1 , - 1 )
320+ return content ? content . split ( ',' ) : [ ]
321+ }
322+
268323 private reset ( ) : void {
269324 this . lastFetchTimestamp = 0
270325 this . lastResultLifetime = Promise . resolve ( TRANSIENT_REFETCH_INTERVAL_HINT )
0 commit comments