Files
clide/clide/widgets/components/terminal_pane.py
T
Jeroen SchweitzerandClaude Opus 4.5 1ed21d2a0f Restructure project and add user settings persistence
- Move src/clide/ to clide/ (flatten directory structure)
- Add SettingsService for persistent user preferences
- Settings stored in ~/.clide/settings.json
- Persist theme, panel visibility, and compact mode state
- Fix terminal rendering glitch with throttled refresh (~60fps)
- Update pyproject.toml, Makefile, and tests for new structure

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 00:42:46 +01:00

141 lines
3.9 KiB
Python

"""Terminal pane component."""
from pathlib import Path
from textual.app import ComposeResult
from textual.containers import Vertical
from textual.message import Message
from textual.widgets import Input, RichLog, Static
class TerminalPane(Vertical):
"""Simple terminal/command runner pane."""
DEFAULT_CSS = """
TerminalPane {
height: 100%;
}
TerminalPane .terminal-header {
height: 1;
background: $surface;
padding: 0 1;
}
TerminalPane RichLog {
height: 1fr;
background: $background;
}
TerminalPane Input {
dock: bottom;
height: 1;
}
TerminalPane .prompt {
color: $primary;
}
TerminalPane .output {
color: $foreground;
}
TerminalPane .error {
color: $error;
}
"""
class CommandSubmitted(Message):
"""Emitted when a command is submitted."""
def __init__(self, command: str) -> None:
self.command = command
super().__init__()
def __init__(self, cwd: Path | None = None, **kwargs) -> None:
super().__init__(**kwargs)
self._cwd = cwd or Path.cwd()
self._history: list[str] = []
self._history_index = 0
def compose(self) -> ComposeResult:
yield Static(f"Terminal - {self._cwd}", classes="terminal-header")
yield RichLog(id="terminal-log", highlight=True, markup=True)
yield Input(placeholder="Enter command...", id="terminal-input")
@property
def cwd(self) -> Path:
"""Get current working directory."""
return self._cwd
@cwd.setter
def cwd(self, path: Path) -> None:
"""Set current working directory."""
self._cwd = path
header = self.query_one(".terminal-header", Static)
header.update(f"Terminal - {path}")
def write_output(self, text: str, style: str = "output") -> None:
"""Write output to terminal.
Args:
text: Text to write
style: Style class (output, error, prompt)
"""
log = self.query_one("#terminal-log", RichLog)
if style == "error":
log.write(f"[red]{text}[/]")
elif style == "prompt":
log.write(f"[bold cyan]$ {text}[/]")
else:
log.write(text)
def write_command(self, command: str) -> None:
"""Write a command to terminal (with prompt)."""
self.write_output(command, "prompt")
def write_error(self, error: str) -> None:
"""Write an error to terminal."""
self.write_output(error, "error")
def clear(self) -> None:
"""Clear terminal output."""
log = self.query_one("#terminal-log", RichLog)
log.clear()
def on_input_submitted(self, event: Input.Submitted) -> None:
"""Handle command submission."""
command = event.value.strip()
if not command:
return
# Add to history
self._history.append(command)
self._history_index = len(self._history)
# Clear input
event.input.clear()
# Write command to output
self.write_command(command)
# Post message for handling
self.post_message(self.CommandSubmitted(command))
def history_up(self) -> None:
"""Navigate history up."""
if self._history and self._history_index > 0:
self._history_index -= 1
input_widget = self.query_one("#terminal-input", Input)
input_widget.value = self._history[self._history_index]
def history_down(self) -> None:
"""Navigate history down."""
input_widget = self.query_one("#terminal-input", Input)
if self._history_index < len(self._history) - 1:
self._history_index += 1
input_widget.value = self._history[self._history_index]
else:
self._history_index = len(self._history)
input_widget.clear()