mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 12:15:10 +00:00
LS: support glob ignores using pathspec
This commit is contained in:
1 parent
8e68e2f4c3
commit
c4384682f2
5 files changed
+71
-16
No files matched your search
@@ -30,6 +30,7 @@ dependencies = [
|
||||
"pyyaml>=6.0.2",
|
||||
"jinja2>=3.1.6",
|
||||
"dotenv>=0.9.9",
|
||||
"pathspec>=0.12.1",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -16,14 +16,15 @@ import pickle
|
||||
import re
|
||||
import threading
|
||||
from collections import defaultdict
|
||||
from urllib.parse import urlparse
|
||||
from contextlib import asynccontextmanager, contextmanager
|
||||
from copy import copy
|
||||
from typing import Any, AsyncGenerator, Dict, Generator, List, Optional, Set, Tuple, Union
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
from fnmatch import fnmatch
|
||||
from pathlib import Path, PurePath
|
||||
from typing import AsyncIterator, Dict, Iterator, List, Optional, Tuple, Union, cast
|
||||
|
||||
import pathspec
|
||||
|
||||
from serena.text_utils import LineType, MatchedConsecutiveLines, TextLine, search_text
|
||||
from . import multilspy_types
|
||||
from .lsp_protocol_handler import lsp_types as LSPTypes
|
||||
@@ -181,14 +182,6 @@ class LanguageServer:
|
||||
self.repository_root_path: str = repository_root_path
|
||||
self.completions_available = asyncio.Event()
|
||||
|
||||
# for all absolute paths in ignored_dirs, convert them to relative paths
|
||||
self.ignored_paths = []
|
||||
for path in set(config.ignored_paths):
|
||||
if os.path.isabs(path):
|
||||
path = str(Path(path).resolve().relative_to(self.repository_root_path))
|
||||
self.ignored_paths.append(path)
|
||||
|
||||
|
||||
if config.trace_lsp_communication:
|
||||
|
||||
def logging_fn(source, target, msg):
|
||||
@@ -213,27 +206,56 @@ class LanguageServer:
|
||||
self._cache_has_changed = bool
|
||||
self.language = Language(language_id)
|
||||
|
||||
# Helper function to check if a relative path should be ignored
|
||||
# Set up the pathspec matcher for the ignored paths
|
||||
# for all absolute paths in ignored_paths, convert them to relative paths
|
||||
processed_patterns = []
|
||||
for pattern in set(config.ignored_paths):
|
||||
# Normalize separators (pathspec expects forward slashes)
|
||||
pattern = pattern.replace(os.path.sep, '/')
|
||||
processed_patterns.append(pattern)
|
||||
# Create a pathspec matcher from the processed patterns
|
||||
self.ignore_spec = pathspec.PathSpec.from_lines(
|
||||
pathspec.patterns.GitWildMatchPattern,
|
||||
processed_patterns
|
||||
)
|
||||
|
||||
|
||||
def should_ignore_path(self, relative_path: str) -> bool:
|
||||
"""
|
||||
Determine if a path should be ignored based on file type
|
||||
and ignore patterns.
|
||||
|
||||
:param relative_path: Relative path to check
|
||||
|
||||
:return: True if the path should be ignored, False otherwise
|
||||
"""
|
||||
# Check file extension if it's a file
|
||||
fn_matcher = self.language.get_source_fn_matcher()
|
||||
if os.path.isfile(relative_path) and not fn_matcher.is_relevant_filename(relative_path):
|
||||
return True
|
||||
|
||||
# Normalize path separators for consistent comparison
|
||||
# Create normalized path for consistent handling
|
||||
rel_path = Path(relative_path)
|
||||
for ignored_path in self.ignored_paths:
|
||||
if rel_path.is_relative_to(ignored_path):
|
||||
return True
|
||||
|
||||
# Check each part of the path against always fulfilled ignore conditions
|
||||
for part in rel_path.parts:
|
||||
if not part: # Skip empty parts (e.g., from leading '/')
|
||||
if not part: # Skip empty parts (e.g., from leading '/')
|
||||
continue
|
||||
# Check standard ignores
|
||||
if self.should_always_ignore(part):
|
||||
return True
|
||||
|
||||
# Use pathspec for gitignore-style pattern matching
|
||||
# Normalize path separators for pathspec (it expects forward slashes)
|
||||
normalized_path = str(rel_path).replace(os.path.sep, '/')
|
||||
|
||||
# Use the pathspec matcher to check if the path matches any ignore pattern
|
||||
if self.ignore_spec.match_file(normalized_path):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def start_server(self) -> AsyncIterator["LanguageServer"]:
|
||||
"""
|
||||
|
||||
@@ -88,6 +88,11 @@ class PathUtils:
|
||||
parsed = urlparse(uri)
|
||||
host = "{0}{0}{mnt}{0}".format(os.path.sep, mnt=parsed.netloc)
|
||||
return os.path.normpath(os.path.join(host, url2pathname(unquote(parsed.path))))
|
||||
|
||||
@staticmethod
|
||||
def is_glob_pattern(pattern: str) -> bool:
|
||||
"""Check if a pattern contains glob-specific characters."""
|
||||
return any(c in pattern for c in '*?[]!')
|
||||
|
||||
class FileUtils:
|
||||
"""
|
||||
|
||||
@@ -44,3 +44,28 @@ def test_find_references_ignores_dir(ls_with_ignored_dirs: SyncLanguageServer):
|
||||
|
||||
# assert that scripts does not appear in the references
|
||||
assert not any("scripts" in ref["relativePath"] for ref in references)
|
||||
|
||||
|
||||
def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None:
|
||||
"""Tests that refs and symbols with glob patterns are ignored."""
|
||||
config = MultilspyConfig(
|
||||
code_language=Language.PYTHON,
|
||||
trace_lsp_communication=False,
|
||||
ignored_paths=["*ipts", "custom_t*"],
|
||||
)
|
||||
logger = MultilspyLogger()
|
||||
ls = SyncLanguageServer.create(config, logger, str(repo_path))
|
||||
ls.start()
|
||||
# same as in the above tests
|
||||
root = ls.request_full_symbol_tree()[0]
|
||||
root_children = root["children"]
|
||||
children_names = {child["name"] for child in root_children}
|
||||
assert children_names == {"test_repo", "examples"}
|
||||
|
||||
# test that the refs and symbols with glob patterns are ignored
|
||||
definition_file = "test_repo/models.py"
|
||||
definition_line = 56
|
||||
definition_col = 6
|
||||
|
||||
references = ls.request_references(definition_file, definition_line, definition_col)
|
||||
assert not any("scripts" in ref["relativePath"] for ref in references)
|
||||
@@ -950,6 +950,7 @@ dependencies = [
|
||||
{ name = "jinja2" },
|
||||
{ name = "mcp" },
|
||||
{ name = "overrides" },
|
||||
{ name = "pathspec" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pyright" },
|
||||
{ name = "python-dotenv" },
|
||||
@@ -997,6 +998,7 @@ requires-dist = [
|
||||
{ name = "mcp", specifier = ">=1.5.0" },
|
||||
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.4.1" },
|
||||
{ name = "overrides", specifier = ">=7.7.0,<8" },
|
||||
{ name = "pathspec", specifier = ">=0.12.1" },
|
||||
{ name = "poethepoet", marker = "extra == 'dev'", specifier = ">=0.20.0" },
|
||||
{ name = "pydantic", specifier = ">=2.10.6" },
|
||||
{ name = "pyright", specifier = ">=1.1.396,<2" },
|
||||
|
||||
Reference in new issue
Block a user