Skip to main content

DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/ProcessManagement/
WorkspaceContainsGlob.rs

1//! Workspace-contains glob matcher for VS Code `workspaceContains:<pattern>`
2//! activation events.
3//!
4//! Matching semantics mirror VS Code's `ExtensionService.scanExtensions`:
5//! - Bare filename → exact match at workspace root
6//! - Path with slashes → direct descendant match
7//! - `**/pattern` → any descendant up to depth 3
8//! - Single `*` → one path segment wildcard
9//!
10//! Bounded to depth 3 and 4096 entries per root so activation checks stay
11//! sub-100 ms on large monorepos.
12
13/// Return the subset of `Patterns` for which at least one workspace folder
14/// contains a matching file or directory.
15pub fn FindMatchingWorkspaceContainsPatterns(Folders:&[std::path::PathBuf], Patterns:&[String]) -> Vec<String> {
16	use std::collections::HashSet;
17
18	const MAX_DEPTH:usize = 3;
19
20	const MAX_ENTRIES_PER_ROOT:usize = 4096;
21
22	let mut Matched:HashSet<String> = HashSet::new();
23
24	for Folder in Folders {
25		if !Folder.is_dir() {
26			continue;
27		}
28
29		let mut Entries:Vec<String> = Vec::new();
30
31		let mut Stack:Vec<(std::path::PathBuf, usize)> = vec![(Folder.clone(), 0)];
32
33		while let Some((Current, Depth)) = Stack.pop() {
34			if Entries.len() >= MAX_ENTRIES_PER_ROOT {
35				break;
36			}
37
38			let ReadDir = match std::fs::read_dir(&Current) {
39				Ok(R) => R,
40
41				Err(_) => continue,
42			};
43
44			for Entry in ReadDir.flatten() {
45				if Entries.len() >= MAX_ENTRIES_PER_ROOT {
46					break;
47				}
48
49				let Path = Entry.path();
50
51				let Relative = match Path.strip_prefix(Folder) {
52					Ok(R) => R.to_string_lossy().replace('\\', "/"),
53
54					Err(_) => continue,
55				};
56
57				let IsDir = Entry.file_type().map(|T| T.is_dir()).unwrap_or(false);
58
59				Entries.push(Relative.clone());
60
61				if IsDir && Depth + 1 < MAX_DEPTH {
62					Stack.push((Path, Depth + 1));
63				}
64			}
65		}
66
67		for Pattern in Patterns {
68			if Matched.contains(Pattern) {
69				continue;
70			}
71
72			if PatternMatchesAnyEntry(Pattern, &Entries) {
73				Matched.insert(Pattern.clone());
74			}
75		}
76	}
77
78	Matched.into_iter().collect()
79}
80
81/// Check whether `Pattern` matches any entry in `Entries`.
82/// Supports literal paths, `*` (one segment), and `**` (any segments).
83/// Case-sensitive per the VS Code spec.
84pub fn PatternMatchesAnyEntry(Pattern:&str, Entries:&[String]) -> bool {
85	let HasWildcard = Pattern.contains('*') || Pattern.contains('?');
86
87	if !HasWildcard {
88		return Entries.iter().any(|E| E == Pattern);
89	}
90
91	let PatternSegments:Vec<&str> = Pattern.split('/').collect();
92
93	Entries
94		.iter()
95		.any(|E| SegmentMatch(&PatternSegments, &E.split('/').collect::<Vec<_>>()))
96}
97
98/// Recursive segment-by-segment glob match. `**` consumes zero or more
99/// path segments; `*` matches exactly one segment via `SingleSegmentMatch`.
100pub fn SegmentMatch(Pattern:&[&str], Entry:&[&str]) -> bool {
101	if Pattern.is_empty() {
102		return Entry.is_empty();
103	}
104
105	let Head = Pattern[0];
106
107	if Head == "**" {
108		for Consumed in 0..=Entry.len() {
109			if SegmentMatch(&Pattern[1..], &Entry[Consumed..]) {
110				return true;
111			}
112		}
113
114		return false;
115	}
116
117	if Entry.is_empty() {
118		return false;
119	}
120
121	if SingleSegmentMatch(Head, Entry[0]) {
122		return SegmentMatch(&Pattern[1..], &Entry[1..]);
123	}
124
125	false
126}
127
128/// Match a single path segment against a pattern that may contain `*`.
129/// `?` is not supported (rare in workspaceContains patterns) and falls
130/// through to literal equality.
131pub fn SingleSegmentMatch(Pattern:&str, Segment:&str) -> bool {
132	if Pattern == "*" {
133		return true;
134	}
135
136	if !Pattern.contains('*') && !Pattern.contains('?') {
137		return Pattern == Segment;
138	}
139
140	let Fragments:Vec<&str> = Pattern.split('*').collect();
141
142	let mut Cursor = 0usize;
143
144	for (Index, Fragment) in Fragments.iter().enumerate() {
145		if Fragment.is_empty() {
146			continue;
147		}
148
149		if Index == 0 {
150			if !Segment[Cursor..].starts_with(Fragment) {
151				return false;
152			}
153
154			Cursor += Fragment.len();
155
156			continue;
157		}
158
159		match Segment[Cursor..].find(Fragment) {
160			Some(Offset) => Cursor += Offset + Fragment.len(),
161
162			None => return false,
163		}
164	}
165
166	if let Some(Last) = Fragments.last()
167		&& !Last.is_empty()
168	{
169		return Segment.ends_with(Last);
170	}
171
172	true
173}