aline-ai 0.5.4__py3-none-any.whl → 0.5.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aline_ai-0.5.4.dist-info → aline_ai-0.5.6.dist-info}/METADATA +1 -1
- aline_ai-0.5.6.dist-info/RECORD +95 -0
- realign/__init__.py +1 -1
- realign/adapters/antigravity.py +28 -20
- realign/adapters/base.py +46 -50
- realign/adapters/claude.py +14 -14
- realign/adapters/codex.py +7 -7
- realign/adapters/gemini.py +11 -11
- realign/adapters/registry.py +14 -10
- realign/claude_detector.py +2 -2
- realign/claude_hooks/__init__.py +3 -3
- realign/claude_hooks/permission_request_hook_installer.py +31 -32
- realign/claude_hooks/stop_hook.py +4 -1
- realign/claude_hooks/stop_hook_installer.py +30 -31
- realign/cli.py +23 -4
- realign/codex_detector.py +11 -11
- realign/commands/add.py +88 -65
- realign/commands/config.py +3 -12
- realign/commands/context.py +3 -1
- realign/commands/export_shares.py +86 -127
- realign/commands/import_shares.py +145 -155
- realign/commands/init.py +166 -30
- realign/commands/restore.py +18 -6
- realign/commands/search.py +14 -42
- realign/commands/upgrade.py +155 -11
- realign/commands/watcher.py +98 -219
- realign/commands/worker.py +29 -6
- realign/config.py +25 -20
- realign/context.py +1 -3
- realign/dashboard/app.py +34 -24
- realign/dashboard/screens/__init__.py +10 -1
- realign/dashboard/screens/create_agent.py +244 -0
- realign/dashboard/screens/create_event.py +3 -1
- realign/dashboard/screens/event_detail.py +14 -6
- realign/dashboard/screens/help_screen.py +114 -0
- realign/dashboard/screens/session_detail.py +3 -1
- realign/dashboard/screens/share_import.py +7 -3
- realign/dashboard/tmux_manager.py +54 -9
- realign/dashboard/widgets/config_panel.py +85 -1
- realign/dashboard/widgets/events_table.py +314 -70
- realign/dashboard/widgets/header.py +2 -1
- realign/dashboard/widgets/search_panel.py +37 -27
- realign/dashboard/widgets/sessions_table.py +404 -85
- realign/dashboard/widgets/terminal_panel.py +155 -175
- realign/dashboard/widgets/watcher_panel.py +6 -2
- realign/dashboard/widgets/worker_panel.py +10 -1
- realign/db/__init__.py +1 -1
- realign/db/base.py +5 -15
- realign/db/locks.py +0 -1
- realign/db/migration.py +82 -76
- realign/db/schema.py +2 -6
- realign/db/sqlite_db.py +23 -41
- realign/events/__init__.py +0 -1
- realign/events/event_summarizer.py +27 -15
- realign/events/session_summarizer.py +29 -15
- realign/file_lock.py +1 -0
- realign/hooks.py +150 -60
- realign/logging_config.py +12 -15
- realign/mcp_server.py +30 -51
- realign/mcp_watcher.py +0 -1
- realign/models/event.py +29 -20
- realign/prompts/__init__.py +7 -7
- realign/prompts/presets.py +15 -11
- realign/redactor.py +99 -59
- realign/triggers/__init__.py +9 -9
- realign/triggers/antigravity_trigger.py +30 -28
- realign/triggers/base.py +4 -3
- realign/triggers/claude_trigger.py +104 -85
- realign/triggers/codex_trigger.py +15 -5
- realign/triggers/gemini_trigger.py +57 -47
- realign/triggers/next_turn_trigger.py +3 -1
- realign/triggers/registry.py +6 -2
- realign/triggers/turn_status.py +3 -1
- realign/watcher_core.py +306 -131
- realign/watcher_daemon.py +8 -8
- realign/worker_core.py +3 -1
- realign/worker_daemon.py +3 -1
- aline_ai-0.5.4.dist-info/RECORD +0 -93
- {aline_ai-0.5.4.dist-info → aline_ai-0.5.6.dist-info}/WHEEL +0 -0
- {aline_ai-0.5.4.dist-info → aline_ai-0.5.6.dist-info}/entry_points.txt +0 -0
- {aline_ai-0.5.4.dist-info → aline_ai-0.5.6.dist-info}/licenses/LICENSE +0 -0
- {aline_ai-0.5.4.dist-info → aline_ai-0.5.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Help screen modal for the dashboard."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from textual.app import ComposeResult
|
|
6
|
+
from textual.binding import Binding
|
|
7
|
+
from textual.containers import Container, Vertical
|
|
8
|
+
from textual.screen import ModalScreen
|
|
9
|
+
from textual.widgets import Button, Static
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class HelpScreen(ModalScreen):
|
|
13
|
+
"""Modal showing keyboard shortcuts and help information."""
|
|
14
|
+
|
|
15
|
+
BINDINGS = [
|
|
16
|
+
Binding("escape", "close", "Close", show=False),
|
|
17
|
+
Binding("?", "close", "Close", show=False),
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
DEFAULT_CSS = """
|
|
21
|
+
HelpScreen {
|
|
22
|
+
align: center middle;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
HelpScreen #help-root {
|
|
26
|
+
width: 50;
|
|
27
|
+
height: auto;
|
|
28
|
+
max-height: 80%;
|
|
29
|
+
padding: 1 2;
|
|
30
|
+
background: $background;
|
|
31
|
+
border: solid $accent;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
HelpScreen #help-title {
|
|
35
|
+
height: auto;
|
|
36
|
+
margin-bottom: 1;
|
|
37
|
+
text-style: bold;
|
|
38
|
+
text-align: center;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
HelpScreen .shortcut-section {
|
|
42
|
+
height: auto;
|
|
43
|
+
margin-bottom: 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
HelpScreen .section-title {
|
|
47
|
+
height: auto;
|
|
48
|
+
color: $text-muted;
|
|
49
|
+
margin-bottom: 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
HelpScreen .shortcut-row {
|
|
53
|
+
height: auto;
|
|
54
|
+
padding: 0 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
HelpScreen .shortcut-key {
|
|
58
|
+
width: 14;
|
|
59
|
+
height: auto;
|
|
60
|
+
color: $accent;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
HelpScreen .shortcut-desc {
|
|
64
|
+
width: 1fr;
|
|
65
|
+
height: auto;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
HelpScreen #close-btn {
|
|
69
|
+
margin-top: 1;
|
|
70
|
+
width: 100%;
|
|
71
|
+
}
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def compose(self) -> ComposeResult:
|
|
75
|
+
with Container(id="help-root"):
|
|
76
|
+
yield Static("Keyboard Shortcuts", id="help-title")
|
|
77
|
+
|
|
78
|
+
with Vertical(classes="shortcut-section"):
|
|
79
|
+
yield Static("Navigation", classes="section-title")
|
|
80
|
+
yield self._shortcut_row("Tab", "Next tab")
|
|
81
|
+
yield self._shortcut_row("Shift+Tab", "Previous tab")
|
|
82
|
+
yield self._shortcut_row("n", "Next page")
|
|
83
|
+
yield self._shortcut_row("p", "Previous page")
|
|
84
|
+
|
|
85
|
+
with Vertical(classes="shortcut-section"):
|
|
86
|
+
yield Static("Actions", classes="section-title")
|
|
87
|
+
yield self._shortcut_row("Enter", "Open selected item")
|
|
88
|
+
yield self._shortcut_row("Space", "Toggle selection")
|
|
89
|
+
yield self._shortcut_row("c", "Create event")
|
|
90
|
+
yield self._shortcut_row("l", "Load context")
|
|
91
|
+
yield self._shortcut_row("y", "Import share")
|
|
92
|
+
yield self._shortcut_row("s", "Switch view")
|
|
93
|
+
yield self._shortcut_row("r", "Refresh")
|
|
94
|
+
|
|
95
|
+
with Vertical(classes="shortcut-section"):
|
|
96
|
+
yield Static("General", classes="section-title")
|
|
97
|
+
yield self._shortcut_row("?", "Show this help")
|
|
98
|
+
yield self._shortcut_row("Ctrl+C x2", "Quit")
|
|
99
|
+
|
|
100
|
+
yield Button("Close", id="close-btn", variant="primary")
|
|
101
|
+
|
|
102
|
+
def _shortcut_row(self, key: str, description: str) -> Static:
|
|
103
|
+
"""Create a shortcut row with key and description."""
|
|
104
|
+
return Static(f"[bold $accent]{key:<12}[/] {description}", classes="shortcut-row")
|
|
105
|
+
|
|
106
|
+
def on_mount(self) -> None:
|
|
107
|
+
self.query_one("#close-btn", Button).focus()
|
|
108
|
+
|
|
109
|
+
def action_close(self) -> None:
|
|
110
|
+
self.app.pop_screen()
|
|
111
|
+
|
|
112
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
113
|
+
if event.button.id == "close-btn":
|
|
114
|
+
self.app.pop_screen()
|
|
@@ -162,7 +162,9 @@ class SessionDetailScreen(ModalScreen):
|
|
|
162
162
|
session_title = getattr(self._session, "session_title", None) if self._session else None
|
|
163
163
|
session_type = getattr(self._session, "session_type", None) if self._session else None
|
|
164
164
|
workspace_path = getattr(self._session, "workspace_path", None) if self._session else None
|
|
165
|
-
last_activity_at =
|
|
165
|
+
last_activity_at = (
|
|
166
|
+
getattr(self._session, "last_activity_at", None) if self._session else None
|
|
167
|
+
)
|
|
166
168
|
started_at = getattr(self._session, "started_at", None) if self._session else None
|
|
167
169
|
|
|
168
170
|
source_map = {
|
|
@@ -74,7 +74,9 @@ class ShareImportScreen(ModalScreen):
|
|
|
74
74
|
placeholder="Paste share URL (e.g. https://.../share/abc123)",
|
|
75
75
|
)
|
|
76
76
|
with Horizontal(classes="row"):
|
|
77
|
-
yield Input(
|
|
77
|
+
yield Input(
|
|
78
|
+
id="share-password", placeholder="Password (optional)", password=True
|
|
79
|
+
)
|
|
78
80
|
with Horizontal(classes="row"):
|
|
79
81
|
yield Checkbox("Force re-import (override duplicates)", id="share-force")
|
|
80
82
|
with Horizontal(id="share-import-actions", classes="row"):
|
|
@@ -107,7 +109,10 @@ class ShareImportScreen(ModalScreen):
|
|
|
107
109
|
self.query_one("#cancel", Button).disabled = busy
|
|
108
110
|
|
|
109
111
|
def _start_import(self) -> None:
|
|
110
|
-
if self._worker is not None and self._worker.state in (
|
|
112
|
+
if self._worker is not None and self._worker.state in (
|
|
113
|
+
WorkerState.PENDING,
|
|
114
|
+
WorkerState.RUNNING,
|
|
115
|
+
):
|
|
111
116
|
return
|
|
112
117
|
|
|
113
118
|
share_url = self.query_one("#share-url", Input).value.strip()
|
|
@@ -181,4 +186,3 @@ class ShareImportScreen(ModalScreen):
|
|
|
181
186
|
message = f"{message}: {stderr_text}"
|
|
182
187
|
status.update(f"[red]{message}[/red]")
|
|
183
188
|
self.app.notify(message, title="Share Import", severity="error", timeout=6)
|
|
184
|
-
|
|
@@ -96,7 +96,9 @@ def _run_tmux(args: Sequence[str], *, capture: bool = False) -> subprocess.Compl
|
|
|
96
96
|
)
|
|
97
97
|
|
|
98
98
|
|
|
99
|
-
def _run_outer_tmux(
|
|
99
|
+
def _run_outer_tmux(
|
|
100
|
+
args: Sequence[str], *, capture: bool = False
|
|
101
|
+
) -> subprocess.CompletedProcess[str]:
|
|
100
102
|
"""Run tmux commands against the dedicated outer server socket."""
|
|
101
103
|
return _run_tmux(["-L", OUTER_SOCKET, *args], capture=capture)
|
|
102
104
|
|
|
@@ -110,7 +112,11 @@ def _run_inner_tmux(
|
|
|
110
112
|
def _python_dashboard_command() -> str:
|
|
111
113
|
# Use the current interpreter for predictable environments (venv, editable installs).
|
|
112
114
|
python_cmd = shlex.join(
|
|
113
|
-
[
|
|
115
|
+
[
|
|
116
|
+
sys.executable,
|
|
117
|
+
"-c",
|
|
118
|
+
"from realign.dashboard.app import run_dashboard; run_dashboard()",
|
|
119
|
+
]
|
|
114
120
|
)
|
|
115
121
|
return f"{MANAGED_ENV}=1 {python_cmd}"
|
|
116
122
|
|
|
@@ -209,7 +215,16 @@ def _aline_tmux_conf_path() -> Path:
|
|
|
209
215
|
def _source_aline_tmux_config(run_fn) -> None: # type: ignore[no-untyped-def]
|
|
210
216
|
"""Best-effort source ~/.aline/tmux/tmux.conf if present."""
|
|
211
217
|
try:
|
|
212
|
-
|
|
218
|
+
# Ensure the config exists and is parseable.
|
|
219
|
+
# Users may run `aline dashboard` before `aline init`, or have older auto-generated configs
|
|
220
|
+
# that included unquoted `#` bindings (tmux treats `#` as a comment delimiter).
|
|
221
|
+
try:
|
|
222
|
+
from ..commands.init import _initialize_tmux_config
|
|
223
|
+
|
|
224
|
+
conf = _initialize_tmux_config()
|
|
225
|
+
except Exception:
|
|
226
|
+
conf = _aline_tmux_conf_path()
|
|
227
|
+
|
|
213
228
|
if conf.exists():
|
|
214
229
|
run_fn(["source-file", str(conf)])
|
|
215
230
|
except Exception:
|
|
@@ -290,7 +305,11 @@ def _maximize_terminal_window() -> None:
|
|
|
290
305
|
|
|
291
306
|
if front_app == "Terminal":
|
|
292
307
|
proc = subprocess.run(
|
|
293
|
-
[
|
|
308
|
+
[
|
|
309
|
+
"osascript",
|
|
310
|
+
"-e",
|
|
311
|
+
'tell application "Terminal" to set zoomed of front window to true',
|
|
312
|
+
],
|
|
294
313
|
capture_output=True,
|
|
295
314
|
text=True,
|
|
296
315
|
timeout=2,
|
|
@@ -377,6 +396,17 @@ def bootstrap_dashboard_into_tmux() -> None:
|
|
|
377
396
|
# Enable mouse for the managed session only.
|
|
378
397
|
_run_outer_tmux(["set-option", "-t", OUTER_SESSION, "mouse", "on"])
|
|
379
398
|
|
|
399
|
+
# Disable status bar for cleaner UI (Aline sessions only).
|
|
400
|
+
_run_outer_tmux(["set-option", "-t", OUTER_SESSION, "status", "off"])
|
|
401
|
+
|
|
402
|
+
# Pane border styling - use double lines for wider, more visible borders.
|
|
403
|
+
# This helps users identify the resizable border area more easily and reduces
|
|
404
|
+
# accidental drag-to-resize when trying to select text near the border.
|
|
405
|
+
_run_outer_tmux(["set-option", "-t", OUTER_SESSION, "pane-border-lines", "double"])
|
|
406
|
+
_run_outer_tmux(["set-option", "-t", OUTER_SESSION, "pane-border-style", "fg=brightblack"])
|
|
407
|
+
_run_outer_tmux(["set-option", "-t", OUTER_SESSION, "pane-active-border-style", "fg=blue"])
|
|
408
|
+
_run_outer_tmux(["set-option", "-t", OUTER_SESSION, "pane-border-indicators", "arrows"])
|
|
409
|
+
|
|
380
410
|
# Ensure dashboard window exists.
|
|
381
411
|
windows_out = (
|
|
382
412
|
_run_outer_tmux(
|
|
@@ -422,6 +452,18 @@ def ensure_inner_session() -> bool:
|
|
|
422
452
|
|
|
423
453
|
# Dedicated inner server; safe to enable mouse globally there.
|
|
424
454
|
_run_inner_tmux(["set-option", "-g", "mouse", "on"])
|
|
455
|
+
|
|
456
|
+
# Disable status bar for cleaner UI.
|
|
457
|
+
_run_inner_tmux(["set-option", "-t", INNER_SESSION, "status", "off"])
|
|
458
|
+
|
|
459
|
+
# Pane border styling - use double lines for wider, more visible borders.
|
|
460
|
+
# This helps users identify the resizable border area more easily and reduces
|
|
461
|
+
# accidental drag-to-resize when trying to select text near the border.
|
|
462
|
+
_run_inner_tmux(["set-option", "-g", "pane-border-lines", "double"])
|
|
463
|
+
_run_inner_tmux(["set-option", "-g", "pane-border-style", "fg=brightblack"])
|
|
464
|
+
_run_inner_tmux(["set-option", "-g", "pane-active-border-style", "fg=blue"])
|
|
465
|
+
_run_inner_tmux(["set-option", "-g", "pane-border-indicators", "arrows"])
|
|
466
|
+
|
|
425
467
|
_source_aline_tmux_config(_run_inner_tmux)
|
|
426
468
|
return True
|
|
427
469
|
|
|
@@ -436,7 +478,13 @@ def ensure_right_pane(width_percent: int = 50) -> bool:
|
|
|
436
478
|
|
|
437
479
|
panes_out = (
|
|
438
480
|
_run_tmux(
|
|
439
|
-
[
|
|
481
|
+
[
|
|
482
|
+
"list-panes",
|
|
483
|
+
"-t",
|
|
484
|
+
f"{OUTER_SESSION}:{OUTER_WINDOW}",
|
|
485
|
+
"-F",
|
|
486
|
+
"#{pane_index}",
|
|
487
|
+
],
|
|
440
488
|
capture=True,
|
|
441
489
|
).stdout
|
|
442
490
|
or ""
|
|
@@ -647,10 +695,7 @@ def clear_attention(window_id: str) -> bool:
|
|
|
647
695
|
"""Clear the attention state for a window (e.g., after user acknowledges permission request)."""
|
|
648
696
|
if not ensure_inner_session():
|
|
649
697
|
return False
|
|
650
|
-
return (
|
|
651
|
-
_run_inner_tmux(["set-option", "-w", "-t", window_id, OPT_ATTENTION, ""]).returncode
|
|
652
|
-
== 0
|
|
653
|
-
)
|
|
698
|
+
return _run_inner_tmux(["set-option", "-w", "-t", window_id, OPT_ATTENTION, ""]).returncode == 0
|
|
654
699
|
|
|
655
700
|
|
|
656
701
|
def get_active_claude_context_id() -> str | None:
|
|
@@ -6,7 +6,9 @@ from typing import Optional
|
|
|
6
6
|
|
|
7
7
|
from textual.app import ComposeResult
|
|
8
8
|
from textual.containers import Horizontal
|
|
9
|
-
from textual.widgets import Button, DataTable, Input, Static
|
|
9
|
+
from textual.widgets import Button, DataTable, Input, Static, Switch
|
|
10
|
+
|
|
11
|
+
from ..tmux_manager import _run_outer_tmux, OUTER_SESSION
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
class ConfigPanel(Static):
|
|
@@ -51,6 +53,27 @@ class ConfigPanel(Static):
|
|
|
51
53
|
ConfigPanel .button-row Button {
|
|
52
54
|
margin-right: 1;
|
|
53
55
|
}
|
|
56
|
+
|
|
57
|
+
ConfigPanel .tmux-settings {
|
|
58
|
+
height: auto;
|
|
59
|
+
margin-top: 1;
|
|
60
|
+
padding: 1;
|
|
61
|
+
border: solid $secondary;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
ConfigPanel .tmux-settings .setting-row {
|
|
65
|
+
height: 3;
|
|
66
|
+
align: left middle;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
ConfigPanel .tmux-settings .setting-label {
|
|
70
|
+
width: auto;
|
|
71
|
+
margin-right: 1;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
ConfigPanel .tmux-settings Switch {
|
|
75
|
+
width: auto;
|
|
76
|
+
}
|
|
54
77
|
"""
|
|
55
78
|
|
|
56
79
|
def __init__(self) -> None:
|
|
@@ -58,6 +81,8 @@ class ConfigPanel(Static):
|
|
|
58
81
|
self._config_path: Optional[Path] = None
|
|
59
82
|
self._config_data: dict = {}
|
|
60
83
|
self._selected_key: Optional[str] = None
|
|
84
|
+
self._border_resize_enabled: bool = True # Track tmux border resize state
|
|
85
|
+
self._syncing_switch: bool = False # Flag to prevent recursive switch updates
|
|
61
86
|
|
|
62
87
|
def compose(self) -> ComposeResult:
|
|
63
88
|
"""Compose the config panel layout."""
|
|
@@ -71,6 +96,13 @@ class ConfigPanel(Static):
|
|
|
71
96
|
yield Button("Save", id="save-btn", variant="primary")
|
|
72
97
|
yield Button("Reload", id="reload-btn", variant="default")
|
|
73
98
|
|
|
99
|
+
# Tmux settings section
|
|
100
|
+
with Static(classes="tmux-settings"):
|
|
101
|
+
yield Static("[bold]Tmux Settings[/bold]", classes="section-title")
|
|
102
|
+
with Horizontal(classes="setting-row"):
|
|
103
|
+
yield Static("Allow border resize:", classes="setting-label")
|
|
104
|
+
yield Switch(value=True, id="border-resize-switch")
|
|
105
|
+
|
|
74
106
|
def on_mount(self) -> None:
|
|
75
107
|
"""Set up the panel on mount."""
|
|
76
108
|
table = self.query_one("#config-table", DataTable)
|
|
@@ -80,6 +112,9 @@ class ConfigPanel(Static):
|
|
|
80
112
|
# Load initial data
|
|
81
113
|
self.refresh_data()
|
|
82
114
|
|
|
115
|
+
# Query and set the actual tmux border resize state
|
|
116
|
+
self._sync_border_resize_switch()
|
|
117
|
+
|
|
83
118
|
def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
|
|
84
119
|
"""Handle row selection in the config table."""
|
|
85
120
|
table = self.query_one("#config-table", DataTable)
|
|
@@ -114,6 +149,55 @@ class ConfigPanel(Static):
|
|
|
114
149
|
self.refresh_data()
|
|
115
150
|
self.app.notify("Configuration reloaded", title="Config")
|
|
116
151
|
|
|
152
|
+
def on_switch_changed(self, event: Switch.Changed) -> None:
|
|
153
|
+
"""Handle switch toggle events."""
|
|
154
|
+
if self._syncing_switch:
|
|
155
|
+
return # Ignore events during sync
|
|
156
|
+
if event.switch.id == "border-resize-switch":
|
|
157
|
+
self._toggle_border_resize(event.value)
|
|
158
|
+
|
|
159
|
+
def _sync_border_resize_switch(self) -> None:
|
|
160
|
+
"""Query tmux state and sync the switch to match."""
|
|
161
|
+
try:
|
|
162
|
+
# Check if MouseDrag1Border is bound by listing keys
|
|
163
|
+
result = _run_outer_tmux(["list-keys", "-T", "root"], capture=True)
|
|
164
|
+
output = result.stdout or ""
|
|
165
|
+
|
|
166
|
+
# If MouseDrag1Border is in the output, resize is enabled
|
|
167
|
+
is_enabled = "MouseDrag1Border" in output
|
|
168
|
+
self._border_resize_enabled = is_enabled
|
|
169
|
+
|
|
170
|
+
# Update switch without triggering the toggle action
|
|
171
|
+
self._syncing_switch = True
|
|
172
|
+
try:
|
|
173
|
+
switch = self.query_one("#border-resize-switch", Switch)
|
|
174
|
+
switch.value = is_enabled
|
|
175
|
+
finally:
|
|
176
|
+
self._syncing_switch = False
|
|
177
|
+
except Exception:
|
|
178
|
+
# If we can't query, assume enabled (default tmux behavior)
|
|
179
|
+
pass
|
|
180
|
+
|
|
181
|
+
def _toggle_border_resize(self, enabled: bool) -> None:
|
|
182
|
+
"""Enable or disable tmux border resize functionality."""
|
|
183
|
+
try:
|
|
184
|
+
if enabled:
|
|
185
|
+
# Re-enable border resize by binding MouseDrag1Border to default resize behavior
|
|
186
|
+
_run_outer_tmux([
|
|
187
|
+
"bind", "-n", "MouseDrag1Border", "resize-pane", "-M"
|
|
188
|
+
])
|
|
189
|
+
self._border_resize_enabled = True
|
|
190
|
+
self.app.notify("Border resize enabled", title="Tmux")
|
|
191
|
+
else:
|
|
192
|
+
# Disable border resize by unbinding MouseDrag1Border
|
|
193
|
+
_run_outer_tmux([
|
|
194
|
+
"unbind", "-n", "MouseDrag1Border"
|
|
195
|
+
])
|
|
196
|
+
self._border_resize_enabled = False
|
|
197
|
+
self.app.notify("Border resize disabled", title="Tmux")
|
|
198
|
+
except Exception as e:
|
|
199
|
+
self.app.notify(f"Error toggling border resize: {e}", title="Tmux", severity="error")
|
|
200
|
+
|
|
117
201
|
def _save_config(self) -> None:
|
|
118
202
|
"""Save the edited configuration."""
|
|
119
203
|
if not self._selected_key:
|