174 lines
4.7 KiB
Python
174 lines
4.7 KiB
Python
"""
|
|
Base completion engine for DSL autocompletion.
|
|
|
|
Provides an abstract base class that specific DSL implementations
|
|
can extend to provide context-aware autocompletion.
|
|
"""
|
|
|
|
from abc import ABC, abstractmethod
|
|
from typing import Any
|
|
|
|
from . import utils
|
|
from .base_provider import BaseMetadataProvider
|
|
from .types import Position, Suggestion, CompletionResult
|
|
|
|
|
|
class BaseCompletionEngine(ABC):
|
|
"""
|
|
Abstract base class for DSL completion engines.
|
|
|
|
Subclasses must implement:
|
|
- detect_scope(): Find the current scope from previous lines
|
|
- detect_context(): Determine what kind of completion is expected
|
|
- get_suggestions(): Generate suggestions for the detected context
|
|
|
|
The main entry point is get_completions(), which orchestrates the flow.
|
|
"""
|
|
|
|
def __init__(self, provider: BaseMetadataProvider):
|
|
"""
|
|
Initialize the completion engine.
|
|
|
|
Args:
|
|
provider: Metadata provider for context-aware suggestions
|
|
"""
|
|
self.provider = provider
|
|
self._id = type(self).__name__
|
|
|
|
def get_completions(self, text: str, cursor: Position) -> CompletionResult:
|
|
"""
|
|
Get autocompletion suggestions for the given cursor position.
|
|
|
|
This is the main entry point. It:
|
|
1. Checks if cursor is in a comment (no suggestions)
|
|
2. Detects the current scope (e.g., which column)
|
|
3. Detects the completion context (what kind of token is expected)
|
|
4. Generates and filters suggestions
|
|
|
|
Args:
|
|
text: The full DSL document text
|
|
cursor: Cursor position
|
|
|
|
Returns:
|
|
CompletionResult with suggestions and replacement range
|
|
"""
|
|
# Get the current line up to cursor
|
|
line = utils.get_line_at(text, cursor.line)
|
|
line_to_cursor = utils.get_line_up_to_cursor(text, cursor)
|
|
|
|
# Check if in comment - no suggestions
|
|
if utils.is_in_comment(line, cursor.ch):
|
|
return self._empty_result(cursor)
|
|
|
|
# Find word boundaries for replacement range
|
|
word_range = utils.find_word_boundaries(line, cursor.ch)
|
|
prefix = line[word_range.start: cursor.ch]
|
|
|
|
# Detect scope from previous lines
|
|
scope = self.detect_scope(text, cursor.line)
|
|
|
|
# Detect completion context
|
|
context = self.detect_context(text, cursor, scope)
|
|
|
|
# Get suggestions for this context
|
|
suggestions = self.get_suggestions(context, scope, prefix)
|
|
|
|
# Filter suggestions by prefix
|
|
if prefix:
|
|
suggestions = self._filter_suggestions(suggestions, prefix)
|
|
|
|
# Build result with correct positions
|
|
from_pos = Position(line=cursor.line, ch=word_range.start)
|
|
to_pos = Position(line=cursor.line, ch=word_range.end)
|
|
|
|
return CompletionResult(
|
|
from_pos=from_pos,
|
|
to_pos=to_pos,
|
|
suggestions=suggestions,
|
|
)
|
|
|
|
@abstractmethod
|
|
def detect_scope(self, text: str, current_line: int) -> Any:
|
|
"""
|
|
Detect the current scope by scanning previous lines.
|
|
|
|
The scope determines which data context we're in (e.g., which column
|
|
for column values suggestions).
|
|
|
|
Args:
|
|
text: The full document text
|
|
current_line: Current line number (0-based)
|
|
|
|
Returns:
|
|
Scope object (type depends on the specific DSL)
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def detect_context(self, text: str, cursor: Position, scope: Any) -> Any:
|
|
"""
|
|
Detect the completion context at the cursor position.
|
|
|
|
Analyzes the current line to determine what kind of token
|
|
is expected (e.g., keyword, preset name, operator).
|
|
|
|
Args:
|
|
text: The full document text
|
|
cursor: Cursor position
|
|
scope: The detected scope
|
|
|
|
Returns:
|
|
Context identifier (type depends on the specific DSL)
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_suggestions(self, context: Any, scope: Any, prefix: str) -> list[Suggestion]:
|
|
"""
|
|
Generate suggestions for the given context.
|
|
|
|
Args:
|
|
context: The detected completion context
|
|
scope: The detected scope
|
|
prefix: The current word prefix (for filtering)
|
|
|
|
Returns:
|
|
List of suggestions
|
|
"""
|
|
pass
|
|
|
|
def _filter_suggestions(
|
|
self, suggestions: list[Suggestion], prefix: str
|
|
) -> list[Suggestion]:
|
|
"""
|
|
Filter suggestions by prefix (case-insensitive).
|
|
|
|
Args:
|
|
suggestions: List of suggestions
|
|
prefix: Prefix to filter by
|
|
|
|
Returns:
|
|
Filtered list of suggestions
|
|
"""
|
|
prefix_lower = prefix.lower()
|
|
return [s for s in suggestions if s.label.lower().startswith(prefix_lower)]
|
|
|
|
def _empty_result(self, cursor: Position) -> CompletionResult:
|
|
"""
|
|
Return an empty completion result.
|
|
|
|
Args:
|
|
cursor: Cursor position
|
|
|
|
Returns:
|
|
CompletionResult with no suggestions
|
|
"""
|
|
return CompletionResult(
|
|
from_pos=cursor,
|
|
to_pos=cursor,
|
|
suggestions=[],
|
|
)
|
|
|
|
def get_id(self):
|
|
return self._id
|