diff --git a/plugin/core/windows.py b/plugin/core/windows.py index 306690e9c..03335a0e9 100644 --- a/plugin/core/windows.py +++ b/plugin/core/windows.py @@ -359,19 +359,10 @@ def should_ignore_diagnostics(self, uri: DocumentUri, configuration: ClientConfi if matches_pattern(path, settings.get("file_exclude_patterns")): return "matches a pattern in file_exclude_patterns" patterns = [sublime_pattern_to_glob(pattern, True) for pattern in settings.get("folder_exclude_patterns") or []] - project_data = self.window.project_data() - if project_data: - for folder in project_data.get('folders', []): - for pattern in folder.get('folder_exclude_patterns', []): - if pattern.startswith('//'): - patterns.append(sublime_pattern_to_glob(pattern, True, folder['path'])) - elif pattern.startswith('/'): - patterns.append(sublime_pattern_to_glob(pattern, True)) - else: - patterns.append(sublime_pattern_to_glob('//' + pattern, True, folder['path'])) - patterns.append(sublime_pattern_to_glob('//**/' + pattern, True, folder['path'])) if matches_pattern(path, patterns): return "matches a pattern in folder_exclude_patterns" + if self._workspace.includes_excluded_path(path): + return "matches a project's folder_exclude_patterns" return None def on_post_exit_async(self, session: Session, exit_code: int, exception: Optional[Exception]) -> None: diff --git a/plugin/core/workspace.py b/plugin/core/workspace.py index e905f7783..10897767c 100644 --- a/plugin/core/workspace.py +++ b/plugin/core/workspace.py @@ -1,5 +1,7 @@ from .protocol import WorkspaceFolder as LspWorkspaceFolder from .types import diff +from .types import matches_pattern +from .types import sublime_pattern_to_glob from .typing import Any, List, Union from .url import filename_to_uri import sublime @@ -56,9 +58,32 @@ class ProjectFolders(object): def __init__(self, window: sublime.Window) -> None: self._window = window self.folders = self._window.folders() # type: List[str] + # Per-folder ignore patterns. The list order matches the order of self.folders. + self._folders_exclude_patterns = [] # type: List[List[str]] + self._update_exclude_patterns(self.folders) + + def _update_exclude_patterns(self, folders: List[str]) -> None: + self._folders_exclude_patterns = [] + project_data = self._window.project_data() + if not isinstance(project_data, dict): + return + for i, folder in enumerate(project_data.get('folders', [])): + exclude_patterns = [] + # Use canoncial path from `window.folders` rather than potentially relative path from project data. + path = folders[i] + for pattern in folder.get('folder_exclude_patterns', []): + if pattern.startswith('//'): + exclude_patterns.append(sublime_pattern_to_glob(pattern, True, path)) + elif pattern.startswith('/'): + exclude_patterns.append(sublime_pattern_to_glob(pattern, True)) + else: + exclude_patterns.append(sublime_pattern_to_glob('//' + pattern, True, path)) + exclude_patterns.append(sublime_pattern_to_glob('//**/' + pattern, True, path)) + self._folders_exclude_patterns.append(exclude_patterns) def update(self) -> bool: new_folders = self._window.folders() + self._update_exclude_patterns(new_folders) added, removed = diff(self.folders, new_folders) if added or removed: self.folders = new_folders @@ -68,8 +93,22 @@ def update(self) -> bool: def includes_path(self, file_path: str) -> bool: if self.folders: return any(is_subpath_of(file_path, folder) for folder in self.folders) - else: - return True + return True + + def includes_excluded_path(self, file_path: str) -> bool: + """Path is excluded if it's within one or more workspace folders and in at least one of the folders it's not + excluded using `folder_exclude_patterns`.""" + if not self.folders: + return False + is_excluded = False + for i, folder in enumerate(self.folders): + if not is_subpath_of(file_path, folder): + continue + exclude_patterns = self._folders_exclude_patterns[i] + is_excluded = matches_pattern(file_path, exclude_patterns) + if not is_excluded: + break + return is_excluded def contains(self, view_or_file_name: Union[str, sublime.View]) -> bool: file_path = view_or_file_name.file_name() if isinstance(view_or_file_name, sublime.View) else view_or_file_name