Skip to main content

DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/ProcessManagement/
WorkspaceContainsGlob.rs

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