Clide is being rebuilt as a Flutter desktop app. The Python Textual implementation moves wholesale into legacy/ rather than being deleted: its pane model, panel set, git skills, and panel communication design are real thought that should remain readable next to the new code while the rebuild finds its shape. Git's rename tracking preserves history, so `git log -- legacy/` still works. The Flutter rebuild lives at the repo root alongside a Go sidecar (the architecture claudian was heading toward, which folds into clide as a core component rather than a separate plugin project). Bootstrap of the new shape lands in subsequent commits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
92 lines
2.4 KiB
Python
92 lines
2.4 KiB
Python
"""Terminal pane component with full PTY support."""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from textual.app import ComposeResult
|
|
from textual.containers import Vertical
|
|
from textual.widgets import Static
|
|
|
|
from clide.widgets.components.terminal_display import TerminalDisplay
|
|
|
|
|
|
class TerminalPane(Vertical):
|
|
"""Full PTY terminal pane running an interactive shell."""
|
|
|
|
DEFAULT_CSS = """
|
|
TerminalPane {
|
|
height: 100%;
|
|
}
|
|
|
|
TerminalPane .terminal-header {
|
|
height: 1;
|
|
background: $surface;
|
|
padding: 0 1;
|
|
}
|
|
|
|
TerminalPane TerminalDisplay {
|
|
height: 1fr;
|
|
}
|
|
"""
|
|
|
|
def __init__(self, cwd: Path | None = None, **kwargs) -> None:
|
|
super().__init__(**kwargs)
|
|
self._cwd = cwd or Path.cwd()
|
|
self._terminal: TerminalDisplay | None = None
|
|
self._shell = self._find_shell()
|
|
|
|
@staticmethod
|
|
def _find_shell() -> str:
|
|
"""Find the user's real shell, ignoring Zellij overrides."""
|
|
import pwd
|
|
|
|
# Get shell from /etc/passwd (most reliable)
|
|
try:
|
|
return pwd.getpwuid(os.getuid()).pw_shell
|
|
except KeyError:
|
|
pass
|
|
# Fallback
|
|
return "/bin/bash"
|
|
|
|
def compose(self) -> ComposeResult:
|
|
yield Static(f"Terminal - {self._cwd}", classes="terminal-header")
|
|
self._terminal = TerminalDisplay()
|
|
yield self._terminal
|
|
|
|
def on_mount(self) -> None:
|
|
"""Start the shell when mounted."""
|
|
if self._terminal:
|
|
self._terminal.start(self._shell, str(self._cwd))
|
|
|
|
@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 focus_terminal(self) -> None:
|
|
"""Focus the terminal display."""
|
|
if self._terminal:
|
|
self._terminal.focus()
|
|
|
|
def stop(self) -> None:
|
|
"""Stop the terminal process."""
|
|
if self._terminal:
|
|
self._terminal.stop()
|
|
|
|
def on_unmount(self) -> None:
|
|
"""Clean up PTY when widget is destroyed."""
|
|
self.stop()
|
|
|
|
def restart(self) -> None:
|
|
"""Restart the shell."""
|
|
if self._terminal:
|
|
self._terminal.stop()
|
|
self._terminal.start(self._shell, str(self._cwd))
|