mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-15 18:16:02 +00:00
LS: option to add_gitignore_content_to_config
Defaults to true since it's probably always a good idea
This commit is contained in:
1 parent
c3e1d0d9ac
commit
54588962b7
4 files changed
+187
-13
No files matched your search
@@ -81,18 +81,17 @@ class LSPFileBuffer:
|
||||
|
||||
|
||||
class LanguageServer:
|
||||
|
||||
# To be overridden and extended by subclasses
|
||||
def should_always_ignore(self, dirname: str) -> bool:
|
||||
return dirname.startswith('.')
|
||||
|
||||
|
||||
"""
|
||||
The LanguageServer class provides a language agnostic interface to the Language Server Protocol.
|
||||
It is used to communicate with Language Servers of different programming languages.
|
||||
"""
|
||||
|
||||
# To be overridden and extended by subclasses
|
||||
def should_always_ignore(self, dirname: str) -> bool:
|
||||
return dirname.startswith('.')
|
||||
|
||||
@classmethod
|
||||
def create(cls, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str) -> "LanguageServer":
|
||||
def create(cls, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str, add_gitignore_content_to_config: bool = True) -> "LanguageServer":
|
||||
"""
|
||||
Creates a language specific LanguageServer instance based on the given configuration, and appropriate settings for the programming language.
|
||||
|
||||
@@ -102,9 +101,29 @@ class LanguageServer:
|
||||
:param repository_root_path: The root path of the repository.
|
||||
:param config: The Multilspy configuration.
|
||||
:param logger: The logger to use.
|
||||
:param add_gitignore_content_to_config: whether to add the content of the .gitignore file (if any found) to the config, so that
|
||||
the paths ignored there are also ignored by the language server
|
||||
|
||||
:return LanguageServer: A language specific LanguageServer instance.
|
||||
"""
|
||||
config = copy(config) # prevent mutation
|
||||
if add_gitignore_content_to_config:
|
||||
gitignore_path = os.path.join(repository_root_path, ".gitignore")
|
||||
if not os.path.exists(gitignore_path):
|
||||
logger.log(
|
||||
f"Should ignore all files in gitignore not not .gitignore found at {gitignore_path}. Skipping.",
|
||||
logging.WARNING
|
||||
)
|
||||
gitignore_file_content = None
|
||||
else:
|
||||
if config.gitignore_file_content is not None:
|
||||
raise ValueError(
|
||||
f"Asked to add gitignore content to the config for {repository_root_path=} but there already is a non-empty entry"
|
||||
)
|
||||
with open(gitignore_path) as f:
|
||||
gitignore_file_content = f.read()
|
||||
config.gitignore_file_content = gitignore_file_content
|
||||
|
||||
if config.code_language == Language.PYTHON:
|
||||
from multilspy.language_servers.pyright_language_server.pyright_server import (
|
||||
PyrightServer,
|
||||
@@ -167,7 +186,7 @@ class LanguageServer:
|
||||
:param config: The Multilspy configuration.
|
||||
:param logger: The logger to use.
|
||||
:param repository_root_path: The root path of the repository.
|
||||
:param cmd: Each language server has a specific command used to start the server.
|
||||
:param process_launch_info: Each language server has a specific command used to start the server.
|
||||
This parameter is the command to launch the language server process.
|
||||
The command must pass appropriate flags to the binary, so that it runs in the stdio mode,
|
||||
as opposed to HTTP, TCP modes supported by some language servers.
|
||||
@@ -213,13 +232,16 @@ class LanguageServer:
|
||||
# Normalize separators (pathspec expects forward slashes)
|
||||
pattern = pattern.replace(os.path.sep, '/')
|
||||
processed_patterns.append(pattern)
|
||||
# Combine explicitly passed patterns with
|
||||
if config.gitignore_file_content is not None:
|
||||
processed_patterns.extend(config.gitignore_file_content.splitlines())
|
||||
|
||||
# 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
|
||||
@@ -249,6 +271,11 @@ class LanguageServer:
|
||||
# Normalize path separators for pathspec (it expects forward slashes)
|
||||
normalized_path = str(rel_path).replace(os.path.sep, '/')
|
||||
|
||||
# pathspec can't handle the matching of directories if they don't end with a slash!
|
||||
# see https://github.com/cpburnz/python-pathspec/issues/89
|
||||
if os.path.isdir(os.path.join(self.repository_root_path, normalized_path)) and not normalized_path.endswith('/'):
|
||||
normalized_path = normalized_path + '/'
|
||||
|
||||
# Use the pathspec matcher to check if the path matches any ignore pattern
|
||||
if self.ignore_spec.match_file(normalized_path):
|
||||
return True
|
||||
@@ -1500,7 +1527,7 @@ class SyncLanguageServer:
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str
|
||||
cls, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str, add_gitignore_content_to_config=True,
|
||||
) -> "SyncLanguageServer":
|
||||
"""
|
||||
Creates a language specific LanguageServer instance based on the given configuration, and appropriate settings for the programming language.
|
||||
@@ -1510,10 +1537,12 @@ class SyncLanguageServer:
|
||||
:param repository_root_path: The root path of the repository.
|
||||
:param config: The Multilspy configuration.
|
||||
:param logger: The logger to use.
|
||||
:param add_gitignore_content_to_config: whether to add the content of the .gitignore file (if any found) to the config, so that
|
||||
the paths ignored there are also ignored by the language server
|
||||
|
||||
:return SyncLanguageServer: A language specific LanguageServer instance.
|
||||
"""
|
||||
return SyncLanguageServer(LanguageServer.create(config, logger, repository_root_path))
|
||||
return SyncLanguageServer(LanguageServer.create(config, logger, repository_root_path, add_gitignore_content_to_config=add_gitignore_content_to_config))
|
||||
|
||||
@contextmanager
|
||||
def open_file(self, relative_file_path: str) -> Iterator[LSPFileBuffer]:
|
||||
|
||||
@@ -67,7 +67,10 @@ class MultilspyConfig:
|
||||
"""
|
||||
code_language: Language
|
||||
trace_lsp_communication: bool = False
|
||||
ignored_paths: List[str] = field(default_factory=list)
|
||||
ignored_paths: list[str] = field(default_factory=list)
|
||||
"""Paths, dirs or glob-like patterns. The matching will follow the same logic as for .gitignore entries"""
|
||||
gitignore_file_content: str | None = None
|
||||
"""Optional content of the gitignore file. If passed, will be used in addition to the explicitly passed ignored_paths for deciding which paths to ignore."""
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, env: dict):
|
||||
@@ -78,4 +81,4 @@ class MultilspyConfig:
|
||||
return cls(**{
|
||||
k: v for k, v in env.items()
|
||||
if k in inspect.signature(cls).parameters
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1 @@
|
||||
ignore_this_dir*/
|
||||
@@ -0,0 +1,141 @@
|
||||
"""
|
||||
Example demonstrating user management with the test_repo module.
|
||||
|
||||
This example showcases:
|
||||
- Creating and managing users
|
||||
- Using various object types and relationships
|
||||
- Type annotations and complex Python patterns
|
||||
"""
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from test_repo.models import User, create_user_object
|
||||
from test_repo.services import UserService
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserStats:
|
||||
"""Statistics about user activity."""
|
||||
|
||||
user_id: str
|
||||
login_count: int = 0
|
||||
last_active_days: int = 0
|
||||
engagement_score: float = 0.0
|
||||
|
||||
def is_active(self) -> bool:
|
||||
"""Check if the user is considered active."""
|
||||
return self.last_active_days < 30
|
||||
|
||||
|
||||
class UserManager:
|
||||
"""Example class demonstrating complex user management."""
|
||||
|
||||
def __init__(self, service: UserService):
|
||||
self.service = service
|
||||
self.active_users: dict[str, User] = {}
|
||||
self.user_stats: dict[str, UserStats] = {}
|
||||
|
||||
def register_user(self, name: str, email: str, roles: list[str] | None = None) -> User:
|
||||
"""Register a new user."""
|
||||
logger.info(f"Registering new user: {name} ({email})")
|
||||
user = self.service.create_user(name=name, email=email, roles=roles)
|
||||
self.active_users[user.id] = user
|
||||
self.user_stats[user.id] = UserStats(user_id=user.id)
|
||||
return user
|
||||
|
||||
def get_user(self, user_id: str) -> User | None:
|
||||
"""Get a user by ID."""
|
||||
if user_id in self.active_users:
|
||||
return self.active_users[user_id]
|
||||
|
||||
# Try to fetch from service
|
||||
user = self.service.get_user(user_id)
|
||||
if user:
|
||||
self.active_users[user.id] = user
|
||||
return user
|
||||
|
||||
def update_user_stats(self, user_id: str, login_count: int, days_since_active: int) -> None:
|
||||
"""Update statistics for a user."""
|
||||
if user_id not in self.user_stats:
|
||||
self.user_stats[user_id] = UserStats(user_id=user_id)
|
||||
|
||||
stats = self.user_stats[user_id]
|
||||
stats.login_count = login_count
|
||||
stats.last_active_days = days_since_active
|
||||
|
||||
# Calculate engagement score based on activity
|
||||
engagement = (100 - min(days_since_active, 100)) * 0.8
|
||||
engagement += min(login_count, 20) * 0.2
|
||||
stats.engagement_score = engagement
|
||||
|
||||
def get_active_users(self) -> list[User]:
|
||||
"""Get all active users."""
|
||||
active_user_ids = [user_id for user_id, stats in self.user_stats.items() if stats.is_active()]
|
||||
return [self.active_users[user_id] for user_id in active_user_ids if user_id in self.active_users]
|
||||
|
||||
def get_user_by_email(self, email: str) -> User | None:
|
||||
"""Find a user by their email address."""
|
||||
for user in self.active_users.values():
|
||||
if user.email == email:
|
||||
return user
|
||||
return None
|
||||
|
||||
|
||||
# Example function demonstrating type annotations
|
||||
def process_user_data(users: list[User], include_inactive: bool = False, transform_func: callable | None = None) -> dict[str, Any]:
|
||||
"""Process user data with optional transformations."""
|
||||
result: dict[str, Any] = {"users": [], "total": 0, "admin_count": 0}
|
||||
|
||||
for user in users:
|
||||
if transform_func:
|
||||
user_data = transform_func(user.to_dict())
|
||||
else:
|
||||
user_data = user.to_dict()
|
||||
|
||||
result["users"].append(user_data)
|
||||
result["total"] += 1
|
||||
|
||||
if "admin" in user.roles:
|
||||
result["admin_count"] += 1
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function demonstrating the usage of UserManager."""
|
||||
# Initialize service and manager
|
||||
service = UserService()
|
||||
manager = UserManager(service)
|
||||
|
||||
# Register some users
|
||||
admin = manager.register_user("Admin User", "admin@example.com", ["admin"])
|
||||
user1 = manager.register_user("Regular User", "user@example.com", ["user"])
|
||||
user2 = manager.register_user("Another User", "another@example.com", ["user"])
|
||||
|
||||
# Update some stats
|
||||
manager.update_user_stats(admin.id, 100, 5)
|
||||
manager.update_user_stats(user1.id, 50, 10)
|
||||
manager.update_user_stats(user2.id, 10, 45) # Inactive user
|
||||
|
||||
# Get active users
|
||||
active_users = manager.get_active_users()
|
||||
logger.info(f"Active users: {len(active_users)}")
|
||||
|
||||
# Process user data
|
||||
user_data = process_user_data(active_users, transform_func=lambda u: {**u, "full_name": u.get("name", "")})
|
||||
|
||||
logger.info(f"Processed {user_data['total']} users, {user_data['admin_count']} admins")
|
||||
|
||||
# Example of calling create_user directly
|
||||
external_user = create_user_object(id="ext123", name="External User", email="external@example.org", roles=["external"])
|
||||
logger.info(f"Created external user: {external_user.name}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user