2121from pathlib import Path # Cross-platform path handling (Windows: \, Unix: /)
2222
2323
24+ DEFAULT_VSCODE_SEARCH_EXCLUDE = {
25+ "**/.venv" : True ,
26+ "**/.venv/**" : True ,
27+ }
28+
29+ DEFAULT_VSCODE_FILES_WATCHER_EXCLUDE = {
30+ "**/.venv/**" : True ,
31+ }
32+
33+ DEFAULT_VSCODE_FILES_EXCLUDE = {
34+ "**/.venv" : True ,
35+ }
36+
37+ DEFAULT_PYTHON_ANALYSIS_EXCLUDE = [
38+ "**/node_modules" ,
39+ "**/__pycache__" ,
40+ ".git" ,
41+ "**/build" ,
42+ "env/**" ,
43+ "**/.venv/**" ,
44+ ]
45+
46+
47+ def _merge_bool_map (existing : object , required : dict [str , bool ]) -> dict [str , bool ]:
48+ """Merge boolean map settings while enforcing required keys.
49+
50+ For VS Code exclude maps, required keys are forced to True.
51+ """
52+
53+ if isinstance (existing , dict ):
54+ merged : dict [str , bool ] = {str (k ): bool (v ) for k , v in existing .items ()}
55+ else :
56+ merged = {}
57+
58+ for key in required :
59+ merged [key ] = True
60+
61+ return merged
62+
63+
64+ def _normalize_string_list (value : object ) -> list [str ]:
65+ if value is None :
66+ return []
67+ if isinstance (value , list ):
68+ return [str (item ) for item in value if str (item ).strip ()]
69+ if isinstance (value , str ) and value .strip ():
70+ return [value ]
71+ return []
72+
73+
74+ def _merge_string_list (existing : object , required : list [str ]) -> list [str ]:
75+ """Merge lists while preserving order and avoiding duplicates.
76+
77+ Required items come first, followed by any existing items.
78+ """
79+
80+ existing_list = _normalize_string_list (existing )
81+ merged : list [str ] = []
82+
83+ for item in required :
84+ if item not in merged :
85+ merged .append (item )
86+ for item in existing_list :
87+ if item not in merged :
88+ merged .append (item )
89+
90+ return merged
91+
92+
2493def get_project_root () -> Path :
2594 """
2695 Get the absolute path to the project root directory.
@@ -183,6 +252,8 @@ def create_vscode_settings():
183252 vscode_dir .mkdir (exist_ok = True )
184253
185254 # Settings to update for kernel and Python configuration
255+ # Note: exclude settings (search/files watcher/Pylance) are merged separately
256+ # to avoid clobbering any user customizations.
186257 required_settings = {
187258 "files.trimTrailingWhitespace" : True ,
188259 "files.insertFinalNewline" : True ,
@@ -246,6 +317,24 @@ def create_vscode_settings():
246317 # Merge required settings with existing ones
247318 existing_settings .update (required_settings )
248319
320+ # Merge performance excludes without overwriting other patterns
321+ existing_settings ["search.exclude" ] = _merge_bool_map (
322+ existing_settings .get ("search.exclude" ),
323+ DEFAULT_VSCODE_SEARCH_EXCLUDE ,
324+ )
325+ existing_settings ["files.watcherExclude" ] = _merge_bool_map (
326+ existing_settings .get ("files.watcherExclude" ),
327+ DEFAULT_VSCODE_FILES_WATCHER_EXCLUDE ,
328+ )
329+ existing_settings ["files.exclude" ] = _merge_bool_map (
330+ existing_settings .get ("files.exclude" ),
331+ DEFAULT_VSCODE_FILES_EXCLUDE ,
332+ )
333+ existing_settings ["python.analysis.exclude" ] = _merge_string_list (
334+ existing_settings .get ("python.analysis.exclude" ),
335+ DEFAULT_PYTHON_ANALYSIS_EXCLUDE ,
336+ )
337+
249338 # Write back the merged settings
250339 with open (settings_file , 'w' , encoding = 'utf-8' ) as f :
251340 json .dump (existing_settings , f , indent = 4 )
@@ -254,6 +343,7 @@ def create_vscode_settings():
254343 print (" - Existing settings preserved" )
255344 print (" - Default kernel set to 'apim-samples'" )
256345 print (" - Python interpreter configured for .venv" )
346+ print (" - .venv excluded from search/watcher/Pylance indexing" )
257347
258348 except (json .JSONDecodeError , IOError ):
259349 print ("⚠️ Existing settings.json has comments or formatting issues" )
@@ -265,12 +355,18 @@ def create_vscode_settings():
265355 else :
266356 # Create new settings file
267357 try :
358+ required_settings ["search.exclude" ] = DEFAULT_VSCODE_SEARCH_EXCLUDE
359+ required_settings ["files.watcherExclude" ] = DEFAULT_VSCODE_FILES_WATCHER_EXCLUDE
360+ required_settings ["files.exclude" ] = DEFAULT_VSCODE_FILES_EXCLUDE
361+ required_settings ["python.analysis.exclude" ] = DEFAULT_PYTHON_ANALYSIS_EXCLUDE
362+
268363 with open (settings_file , 'w' , encoding = 'utf-8' ) as f :
269364 json .dump (required_settings , f , indent = 4 )
270365
271366 print (f"✅ VS Code settings created: { settings_file } " )
272367 print (" - Default kernel set to 'apim-samples'" )
273368 print (" - Python interpreter configured for .venv" )
369+ print (" - .venv excluded from search/watcher/Pylance indexing" )
274370 except (ImportError , IOError ) as e :
275371 print (f"❌ Failed to create VS Code settings: { e } " )
276372 return False
@@ -365,6 +461,13 @@ def force_kernel_consistency():
365461 ]
366462 }
367463
464+ performance_exclude_settings = {
465+ "search.exclude" : DEFAULT_VSCODE_SEARCH_EXCLUDE ,
466+ "files.watcherExclude" : DEFAULT_VSCODE_FILES_WATCHER_EXCLUDE ,
467+ "files.exclude" : DEFAULT_VSCODE_FILES_EXCLUDE ,
468+ "python.analysis.exclude" : DEFAULT_PYTHON_ANALYSIS_EXCLUDE ,
469+ }
470+
368471 try :
369472 # Read existing settings or create new ones
370473 existing_settings = {}
@@ -378,6 +481,24 @@ def force_kernel_consistency():
378481 # Merge settings, with our strict kernel settings taking priority
379482 existing_settings .update (strict_kernel_settings )
380483
484+ # Merge performance excludes without clobbering user patterns
485+ existing_settings ["search.exclude" ] = _merge_bool_map (
486+ existing_settings .get ("search.exclude" ),
487+ performance_exclude_settings ["search.exclude" ],
488+ )
489+ existing_settings ["files.watcherExclude" ] = _merge_bool_map (
490+ existing_settings .get ("files.watcherExclude" ),
491+ performance_exclude_settings ["files.watcherExclude" ],
492+ )
493+ existing_settings ["files.exclude" ] = _merge_bool_map (
494+ existing_settings .get ("files.exclude" ),
495+ performance_exclude_settings ["files.exclude" ],
496+ )
497+ existing_settings ["python.analysis.exclude" ] = _merge_string_list (
498+ existing_settings .get ("python.analysis.exclude" ),
499+ performance_exclude_settings ["python.analysis.exclude" ],
500+ )
501+
381502 # Write updated settings
382503 with open (settings_file , 'w' , encoding = 'utf-8' ) as f :
383504 json .dump (existing_settings , f , indent = 4 )
0 commit comments