Files
MyFastHtml/src/myfasthtml/core/dsl/base_completion.py

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