Skip to content

Commit

Permalink
chore: restructure
Browse files Browse the repository at this point in the history
  • Loading branch information
phil65 committed Dec 30, 2024
1 parent 0e023dc commit 180f976
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 72 deletions.
15 changes: 15 additions & 0 deletions src/slashed/textual_adapter/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Textual integration for Slashed."""

from __future__ import annotations

from slashed.textual_adapter.app import (
SlashedApp,
TextualOutputWriter,
)
from slashed.textual_adapter.suggester import SlashedSuggester

__all__ = [
"SlashedApp",
"SlashedSuggester",
"TextualOutputWriter",
]
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,19 @@

from typing import TYPE_CHECKING, Any, ClassVar

from prompt_toolkit.document import Document
from textual.app import App
from textual.containers import VerticalScroll
from textual.suggester import Suggester
from textual.widgets import Input, Label

from slashed.base import BaseCommand, OutputWriter
from slashed.completion import CompletionContext
from slashed.log import get_logger
from slashed.store import CommandStore
from slashed.textual_adapter.suggester import SlashedSuggester


if TYPE_CHECKING:
from collections.abc import Awaitable, Callable

from slashed.base import CommandContext
from slashed.commands import SlashedCommand

logger = get_logger(__name__)
Expand Down Expand Up @@ -73,74 +70,6 @@ async def print(self, message: str, output_id: str | None = None) -> None:
widget.update(message) # type: ignore


class SlashedSuggester(Suggester):
"""Adapts a Slashed CompletionProvider to Textual's Suggester interface."""

def __init__(
self,
store: CommandStore,
context: CommandContext[Any],
case_sensitive: bool = False,
) -> None:
"""Initialize suggester with store and context.
Args:
store: Command store for looking up commands and completers
context: Command execution context
case_sensitive: Whether to use case-sensitive matching
"""
super().__init__(case_sensitive=case_sensitive)
self._store = store
self.context = context

async def get_suggestion(self, value: str) -> str | None: # noqa: PLR0911
"""Get completion suggestion for current input value."""
if not value.startswith("/"):
return None

if value == "/":
return None

# Create document for current input
document = Document(text=value, cursor_position=len(value))
completion_context = CompletionContext(
document=document, command_context=self.context
)

try:
# If we have a command, use its completer
if " " in value: # Has arguments
cmd_name = value.split()[0][1:] # Remove slash
if command := self._store.get_command(cmd_name): # noqa: SIM102
if completer := command.get_completer():
current_word = completion_context.current_word
# Find first matching completion
for completion in completer.get_completions(completion_context):
if not current_word or completion.text.startswith(
current_word
):
# For argument completion, preserve the cmd part
cmd_part = value[: value.find(" ") + 1]
# If we have a current word, replace it
if current_word:
cmd_part = value[: -len(current_word)]
return f"{cmd_part}{completion.text}"
return None

return None

# Otherwise complete command names
word = value[1:] # Remove slash
for cmd in self._store.list_commands():
if cmd.name.startswith(word):
return f"/{cmd.name}"

except Exception: # noqa: BLE001
return None

return None


class SlashedApp[TContext, TResult](App[TResult]): # type: ignore[type-var]
"""Base app with slash command support.
Expand Down
86 changes: 86 additions & 0 deletions src/slashed/textual_adapter/suggester.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""Textual suggester adapter for Slashed."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from prompt_toolkit.document import Document
from textual.suggester import Suggester

from slashed.completion import CompletionContext
from slashed.log import get_logger


if TYPE_CHECKING:
from slashed.base import CommandContext
from slashed.store import CommandStore

logger = get_logger(__name__)


class SlashedSuggester(Suggester):
"""Adapts a Slashed CompletionProvider to Textual's Suggester interface."""

def __init__(
self,
store: CommandStore,
context: CommandContext[Any],
case_sensitive: bool = False,
) -> None:
"""Initialize suggester with store and context.
Args:
store: Command store for looking up commands and completers
context: Command execution context
case_sensitive: Whether to use case-sensitive matching
"""
super().__init__(case_sensitive=case_sensitive)
self._store = store
self.context = context

async def get_suggestion(self, value: str) -> str | None: # noqa: PLR0911
"""Get completion suggestion for current input value."""
if not value.startswith("/"):
return None

if value == "/":
return None

# Create document for current input
document = Document(text=value, cursor_position=len(value))
completion_context = CompletionContext(
document=document, command_context=self.context
)

try:
# If we have a command, use its completer
if " " in value: # Has arguments
cmd_name = value.split()[0][1:] # Remove slash
if command := self._store.get_command(cmd_name): # noqa: SIM102
if completer := command.get_completer():
current_word = completion_context.current_word
# Find first matching completion
for completion in completer.get_completions(completion_context):
if not current_word or completion.text.startswith(
current_word
):
# For argument completion, preserve the cmd part
cmd_part = value[: value.find(" ") + 1]
# If we have a current word, replace it
if current_word:
cmd_part = value[: -len(current_word)]
return f"{cmd_part}{completion.text}"
return None

return None

# Otherwise complete command names
word = value[1:] # Remove slash
for cmd in self._store.list_commands():
if cmd.name.startswith(word):
return f"/{cmd.name}"

except Exception: # noqa: BLE001
return None

return None

0 comments on commit 180f976

Please sign in to comment.