gitdirector 1.7.2__tar.gz → 1.7.3__tar.gz
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.
- {gitdirector-1.7.2 → gitdirector-1.7.3}/PKG-INFO +5 -2
- {gitdirector-1.7.2 → gitdirector-1.7.3}/README.md +3 -1
- {gitdirector-1.7.2 → gitdirector-1.7.3}/pyproject.toml +2 -1
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/capture.py +1 -2
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/gd_send.py +2 -1
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/gd_tmux.py +6 -19
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/help.py +4 -1
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/info.py +11 -20
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/link.py +15 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/__init__.py +1 -2
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/app.py +12 -19
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/app_groups.py +1 -1
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/app_panels.py +16 -15
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/app_repos.py +16 -8
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/app_sessions.py +4 -7
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/app_ui.py +10 -0
- gitdirector-1.7.3/src/gitdirector/commands/tui/panel_view.py +211 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/panels.py +112 -16
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/screens/panels.py +16 -6
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/terminal_widget.py +6 -2
- gitdirector-1.7.3/src/gitdirector/commands/unlink.py +48 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/config.py +45 -17
- gitdirector-1.7.3/src/gitdirector/integrations/tmux/__init__.py +106 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/integrations/tmux/core.py +28 -9
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/integrations/tmux/monitor.py +6 -1
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/integrations/tmux/panels.py +7 -1
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/manager.py +83 -1
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/repo.py +7 -0
- gitdirector-1.7.2/src/gitdirector/commands/tui/panel_view.py +0 -478
- gitdirector-1.7.2/src/gitdirector/commands/unlink.py +0 -48
- gitdirector-1.7.2/src/gitdirector/integrations/tmux/__init__.py +0 -5
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/__init__.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/cli.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/__init__.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/autoclean.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/cd.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/completion.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/listt.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/pull.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/reset.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/status.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/constants.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/diff_renderer.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/screens/__init__.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/screens/_shared.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/screens/commit.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/screens/diff.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/screens/diff_files.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/screens/groups.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/screens/repos.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/screens/session_actions.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/screens/sessions.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/table_text.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/commands/tui/terminal_caps.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/github_credential_helper.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/info.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/integrations/__init__.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/storage.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/ui_theme.py +0 -0
- {gitdirector-1.7.2 → gitdirector-1.7.3}/src/gitdirector/version_check.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: gitdirector
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.3
|
|
4
4
|
Summary: A terminal based control plane for developers working across multiple repositories. Launch multiple AI coding agents, multiple tmux sessions and track changes across all your repos in one place.
|
|
5
5
|
Keywords: git,repository,manager,cli,synchronization,batch
|
|
6
6
|
Author: Anito Anto
|
|
@@ -24,6 +24,7 @@ Requires-Dist: rich>=12.0
|
|
|
24
24
|
Requires-Dist: textual>=8.2.1
|
|
25
25
|
Requires-Dist: tiktoken>=0.5.0
|
|
26
26
|
Requires-Dist: pyte>=0.8.2
|
|
27
|
+
Requires-Dist: pathspec>=1.0.0
|
|
27
28
|
Requires-Dist: pytest>=7.0 ; extra == 'dev'
|
|
28
29
|
Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
|
|
29
30
|
Requires-Dist: pytest-cov>=4.0 ; extra == 'dev'
|
|
@@ -67,7 +68,7 @@ If you find GitDirector useful, please star this repository on GitHub, we need m
|
|
|
67
68
|
| Command | Description |
|
|
68
69
|
| ---------------------------------------------------------- | ------------------------------------------------------ |
|
|
69
70
|
| `gitdirector console` | Open the interactive TUI dashboard |
|
|
70
|
-
| `gitdirector link PATH [--discover]` | Link a repository or discover all under a path
|
|
71
|
+
| `gitdirector link PATH [--discover]` | Link a repository or discover all under a path (no-op if none) |
|
|
71
72
|
| `gitdirector unlink PATH\|NAME [--discover]` | Unlink a repository by path, name, or all under a path |
|
|
72
73
|
| `gitdirector list` | List all tracked repositories with live status |
|
|
73
74
|
| `gitdirector status` | Show repositories with staged/unstaged files |
|
|
@@ -89,6 +90,8 @@ gitdirector link /path/to/repo
|
|
|
89
90
|
gitdirector link /path/to/folder --discover # recursively find and link all repos
|
|
90
91
|
```
|
|
91
92
|
|
|
93
|
+
`link --discover` succeeds without changes when the path contains no new repositories.
|
|
94
|
+
|
|
92
95
|
### console
|
|
93
96
|
|
|
94
97
|
```bash
|
|
@@ -25,7 +25,7 @@ If you find GitDirector useful, please star this repository on GitHub, we need m
|
|
|
25
25
|
| Command | Description |
|
|
26
26
|
| ---------------------------------------------------------- | ------------------------------------------------------ |
|
|
27
27
|
| `gitdirector console` | Open the interactive TUI dashboard |
|
|
28
|
-
| `gitdirector link PATH [--discover]` | Link a repository or discover all under a path
|
|
28
|
+
| `gitdirector link PATH [--discover]` | Link a repository or discover all under a path (no-op if none) |
|
|
29
29
|
| `gitdirector unlink PATH\|NAME [--discover]` | Unlink a repository by path, name, or all under a path |
|
|
30
30
|
| `gitdirector list` | List all tracked repositories with live status |
|
|
31
31
|
| `gitdirector status` | Show repositories with staged/unstaged files |
|
|
@@ -47,6 +47,8 @@ gitdirector link /path/to/repo
|
|
|
47
47
|
gitdirector link /path/to/folder --discover # recursively find and link all repos
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
`link --discover` succeeds without changes when the path contains no new repositories.
|
|
51
|
+
|
|
50
52
|
### console
|
|
51
53
|
|
|
52
54
|
```bash
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gitdirector"
|
|
7
|
-
version = "1.7.
|
|
7
|
+
version = "1.7.3"
|
|
8
8
|
description = "A terminal based control plane for developers working across multiple repositories. Launch multiple AI coding agents, multiple tmux sessions and track changes across all your repos in one place."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -37,6 +37,7 @@ dependencies = [
|
|
|
37
37
|
"textual>=8.2.1",
|
|
38
38
|
"tiktoken>=0.5.0",
|
|
39
39
|
"pyte>=0.8.2",
|
|
40
|
+
"pathspec>=1.0.0",
|
|
40
41
|
]
|
|
41
42
|
|
|
42
43
|
[project.optional-dependencies]
|
|
@@ -18,6 +18,7 @@ from __future__ import annotations
|
|
|
18
18
|
import click
|
|
19
19
|
|
|
20
20
|
from ..integrations.tmux import capture_pane
|
|
21
|
+
from ..integrations.tmux.core import _parse_gd_session_name
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
def _resolve_session_name(name: str) -> str:
|
|
@@ -26,8 +27,6 @@ def _resolve_session_name(name: str) -> str:
|
|
|
26
27
|
gd-capture refuses anything that doesn't match the ``gd/{repo}/{purpose}/{N}``
|
|
27
28
|
shape — that way a typo can't be silently routed to the wrong session.
|
|
28
29
|
"""
|
|
29
|
-
from ..integrations.tmux import _parse_gd_session_name
|
|
30
|
-
|
|
31
30
|
parsed = _parse_gd_session_name(name)
|
|
32
31
|
if parsed is None:
|
|
33
32
|
raise click.BadParameter(
|
|
@@ -2,7 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import click
|
|
4
4
|
|
|
5
|
-
from ..integrations.tmux import
|
|
5
|
+
from ..integrations.tmux import send_key_to_session, send_text_to_session
|
|
6
|
+
from ..integrations.tmux.core import _parse_gd_session_name
|
|
6
7
|
|
|
7
8
|
SUPPORTED_KEYS = ("C-c",)
|
|
8
9
|
|
|
@@ -3,7 +3,6 @@ from pathlib import Path
|
|
|
3
3
|
import click
|
|
4
4
|
|
|
5
5
|
from ..manager import RepositoryManager
|
|
6
|
-
from ..storage import normalize_repository_path
|
|
7
6
|
from . import console
|
|
8
7
|
from .completion import complete_repository_names
|
|
9
8
|
|
|
@@ -17,24 +16,12 @@ def _resolve_repo(target: str) -> Path | None:
|
|
|
17
16
|
or ambiguous targets.
|
|
18
17
|
"""
|
|
19
18
|
manager = RepositoryManager()
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
or candidate.is_absolute()
|
|
26
|
-
or target.startswith("~")
|
|
27
|
-
or candidate.exists()
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
if is_path_like:
|
|
31
|
-
normalized = normalize_repository_path(candidate)
|
|
32
|
-
if manager.config.has_repository(normalized):
|
|
33
|
-
return normalized
|
|
34
|
-
console.print(f"\n [red]No tracked repository at path: {normalized}[/red]\n")
|
|
19
|
+
repo_path, matches, path_attempted = manager.resolve_repository_target(target)
|
|
20
|
+
if repo_path is not None:
|
|
21
|
+
return repo_path
|
|
22
|
+
if path_attempted:
|
|
23
|
+
console.print(f"\n [red]No tracked repository at path: {target}[/red]\n")
|
|
35
24
|
return None
|
|
36
|
-
|
|
37
|
-
matches = [r for r in manager.config.repositories if r.name == target]
|
|
38
25
|
if not matches:
|
|
39
26
|
console.print(f"\n [red]No tracked repository named: {target}[/red]\n")
|
|
40
27
|
return None
|
|
@@ -45,7 +32,7 @@ def _resolve_repo(target: str) -> Path | None:
|
|
|
45
32
|
f"{paths_list}\n"
|
|
46
33
|
)
|
|
47
34
|
return None
|
|
48
|
-
return
|
|
35
|
+
return None
|
|
49
36
|
|
|
50
37
|
|
|
51
38
|
def register(cli: click.Group):
|
|
@@ -26,7 +26,10 @@ def show_help():
|
|
|
26
26
|
cmd_table.add_column("desc", style="dim")
|
|
27
27
|
|
|
28
28
|
for cmd, desc in [
|
|
29
|
-
(
|
|
29
|
+
(
|
|
30
|
+
"link PATH [--discover]",
|
|
31
|
+
"Link a repository or discover all repos under a path (no-op if none)",
|
|
32
|
+
),
|
|
30
33
|
("unlink PATH|NAME [--discover]", "Unlink a repository or all repos under a path"),
|
|
31
34
|
("list", "List all tracked repositories"),
|
|
32
35
|
("status", "Show status summary and per-repo details"),
|
|
@@ -46,29 +46,20 @@ def register(cli: click.Group):
|
|
|
46
46
|
def info(target: str, full: bool):
|
|
47
47
|
"""Show file statistics for a repository."""
|
|
48
48
|
manager = RepositoryManager()
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
target_lower = target.lower()
|
|
56
|
-
exact = [r for r in repos if r.name.lower() == target_lower]
|
|
57
|
-
if exact:
|
|
58
|
-
matches = exact
|
|
59
|
-
else:
|
|
60
|
-
matches = [r for r in repos if target_lower in r.name.lower()]
|
|
61
|
-
|
|
49
|
+
repo_path, matches, _path_attempted = manager.resolve_repository_target(
|
|
50
|
+
target,
|
|
51
|
+
allow_untracked_git_path=True,
|
|
52
|
+
fuzzy_names=True,
|
|
53
|
+
)
|
|
54
|
+
if repo_path is None:
|
|
62
55
|
if not matches:
|
|
63
56
|
console.print(f"\n [red]Repository '{target}' not found[/red]\n")
|
|
64
57
|
raise SystemExit(1)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
raise SystemExit(1)
|
|
71
|
-
repo_path = matches[0]
|
|
58
|
+
console.print(f"\n [red]Multiple repositories match '{target}':[/red]")
|
|
59
|
+
for match in matches:
|
|
60
|
+
console.print(f" {match}")
|
|
61
|
+
console.print()
|
|
62
|
+
raise SystemExit(1)
|
|
72
63
|
|
|
73
64
|
result = gather_repo_info(repo_path, full=full)
|
|
74
65
|
_render_info_cli(result, repo_path.name, repo_path)
|
|
@@ -28,6 +28,21 @@ def register(cli: click.Group):
|
|
|
28
28
|
else:
|
|
29
29
|
for repo_path in repos:
|
|
30
30
|
console.print(f" [green]+[/green] {repo_path}")
|
|
31
|
+
elif (
|
|
32
|
+
discover
|
|
33
|
+
and not repos
|
|
34
|
+
and message
|
|
35
|
+
in {
|
|
36
|
+
"No git repositories found",
|
|
37
|
+
"No new repositories found",
|
|
38
|
+
}
|
|
39
|
+
):
|
|
40
|
+
console.print(f" {message}")
|
|
41
|
+
for repo_path in skipped:
|
|
42
|
+
console.print(
|
|
43
|
+
f" [dim yellow]\\[skipped][/dim yellow] "
|
|
44
|
+
f"[bright_black]{repo_path}[/bright_black]"
|
|
45
|
+
)
|
|
31
46
|
else:
|
|
32
47
|
console.print(f" [red]{message}[/red]")
|
|
33
48
|
console.print()
|
|
@@ -13,7 +13,7 @@ from .constants import (
|
|
|
13
13
|
_changes_label,
|
|
14
14
|
_changes_sort_key,
|
|
15
15
|
)
|
|
16
|
-
from .panel_view import
|
|
16
|
+
from .panel_view import PaneWidget
|
|
17
17
|
from .panels import Panel, PanelStore
|
|
18
18
|
from .screens import SortMenuScreen
|
|
19
19
|
from .screens.diff import DiffReviewScreen
|
|
@@ -51,7 +51,6 @@ __all__ = [
|
|
|
51
51
|
"Panel",
|
|
52
52
|
"PanelActionMenuScreen",
|
|
53
53
|
"PanelStore",
|
|
54
|
-
"PanelViewScreen",
|
|
55
54
|
"_PANELS_SORT_COLUMN_NAMES",
|
|
56
55
|
"PullLoadingScreen",
|
|
57
56
|
"PullResultScreen",
|
|
@@ -126,25 +126,18 @@ class GitDirectorConsole(
|
|
|
126
126
|
padding: 0 1;
|
|
127
127
|
scrollbar-size-horizontal: 0;
|
|
128
128
|
}
|
|
129
|
-
#
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
content-align: center middle;
|
|
136
|
-
}
|
|
137
|
-
#no-sessions-message {
|
|
138
|
-
height: 1fr;
|
|
129
|
+
#repo-table,
|
|
130
|
+
#sessions-table,
|
|
131
|
+
#panels-table,
|
|
132
|
+
#no-repos-message,
|
|
133
|
+
#no-sessions-message,
|
|
134
|
+
#no-panels-message {
|
|
139
135
|
display: none;
|
|
140
|
-
align: center middle;
|
|
141
|
-
color: $text-muted;
|
|
142
|
-
padding: 2 4;
|
|
143
|
-
content-align: center middle;
|
|
144
136
|
}
|
|
137
|
+
#no-repos-message,
|
|
138
|
+
#no-sessions-message,
|
|
145
139
|
#no-panels-message {
|
|
146
140
|
height: 1fr;
|
|
147
|
-
display: none;
|
|
148
141
|
align: center middle;
|
|
149
142
|
color: $text-muted;
|
|
150
143
|
padding: 2 4;
|
|
@@ -204,7 +197,7 @@ class GitDirectorConsole(
|
|
|
204
197
|
Binding("1", "tab_repos", "Repos", show=False),
|
|
205
198
|
Binding("2", "tab_sessions", "Sessions", show=False),
|
|
206
199
|
Binding("3", "tab_panels", "Panels", show=False),
|
|
207
|
-
Binding("space", "toggle_group", "Toggle
|
|
200
|
+
Binding("space", "toggle_group", "Toggle", show=True),
|
|
208
201
|
Binding("n", "new_panel", "New Panel", show=True),
|
|
209
202
|
]
|
|
210
203
|
|
|
@@ -493,7 +486,7 @@ class GitDirectorConsole(
|
|
|
493
486
|
skip_config_sync: bool = False,
|
|
494
487
|
callback: Callable[[object], None] | None = None,
|
|
495
488
|
) -> None:
|
|
496
|
-
from ...integrations.tmux import _parse_gd_session_name
|
|
489
|
+
from ...integrations.tmux.core import _parse_gd_session_name
|
|
497
490
|
|
|
498
491
|
title = "session"
|
|
499
492
|
loading_hint = "waiting for session to initialize\u2026"
|
|
@@ -690,7 +683,7 @@ class GitDirectorConsole(
|
|
|
690
683
|
"""Open the description editor for the currently highlighted session row."""
|
|
691
684
|
if self._active_tab != "sessions":
|
|
692
685
|
return
|
|
693
|
-
from ...integrations.tmux import _get_session_description
|
|
686
|
+
from ...integrations.tmux.core import _get_session_description
|
|
694
687
|
|
|
695
688
|
table = self.query_one("#sessions-table", DataTable)
|
|
696
689
|
session_name = self._get_selected_row_key(table)
|
|
@@ -705,7 +698,7 @@ class GitDirectorConsole(
|
|
|
705
698
|
def _handle_description_edit(self, session_name: str, value: str | None) -> None:
|
|
706
699
|
if value is None:
|
|
707
700
|
return
|
|
708
|
-
from ...integrations.tmux import _set_session_description
|
|
701
|
+
from ...integrations.tmux.core import _set_session_description
|
|
709
702
|
|
|
710
703
|
_set_session_description(session_name, value)
|
|
711
704
|
for entry in self._sessions_entries:
|
|
@@ -44,7 +44,7 @@ def detect_repo_groups(repo_paths: Iterable[Path]) -> list[RepoGroup]:
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
def group_session_repo_label(path: Path) -> str:
|
|
47
|
-
from ...integrations.tmux import _sanitize_repo_name
|
|
47
|
+
from ...integrations.tmux.core import _sanitize_repo_name
|
|
48
48
|
|
|
49
49
|
return f"group_{_sanitize_repo_name(path.name) or 'repo'}"
|
|
50
50
|
|
|
@@ -71,7 +71,7 @@ class ConsolePanelsMixin:
|
|
|
71
71
|
def _panel_matches_search(
|
|
72
72
|
self, panel: Panel, query: str, live_sessions: set[str] | None = None
|
|
73
73
|
) -> bool:
|
|
74
|
-
from ...integrations.tmux import make_panel_session_name
|
|
74
|
+
from ...integrations.tmux.core import make_panel_session_name
|
|
75
75
|
|
|
76
76
|
normalized_query = query.replace("×", "x")
|
|
77
77
|
live_panes = self._live_panel_pane_count(panel, live_sessions)
|
|
@@ -88,7 +88,7 @@ class ConsolePanelsMixin:
|
|
|
88
88
|
return any(normalized_query in haystack.replace("×", "x") for haystack in haystacks)
|
|
89
89
|
|
|
90
90
|
def _panel_sort_key_func(self):
|
|
91
|
-
from ...integrations.tmux import make_panel_session_name
|
|
91
|
+
from ...integrations.tmux.core import make_panel_session_name
|
|
92
92
|
|
|
93
93
|
col = self._panels_sort_column
|
|
94
94
|
if col == 1:
|
|
@@ -115,7 +115,7 @@ class ConsolePanelsMixin:
|
|
|
115
115
|
return lambda panel: panel.name.lower()
|
|
116
116
|
|
|
117
117
|
def _apply_panels_filter_and_sort(self, live_sessions: set[str] | None = None) -> None:
|
|
118
|
-
from ...integrations.tmux import _list_sessions, make_panel_session_name
|
|
118
|
+
from ...integrations.tmux.core import _list_sessions, make_panel_session_name
|
|
119
119
|
|
|
120
120
|
try:
|
|
121
121
|
table = self.query_one("#panels-table", DataTable)
|
|
@@ -128,7 +128,6 @@ class ConsolePanelsMixin:
|
|
|
128
128
|
preserved_row_key, preserved_row_index, restore_focus = self._capture_table_selection(
|
|
129
129
|
table
|
|
130
130
|
)
|
|
131
|
-
table.clear()
|
|
132
131
|
no_msg = self.query_one("#no-panels-message", Static)
|
|
133
132
|
|
|
134
133
|
panels = list(self._panels_entries)
|
|
@@ -148,12 +147,10 @@ class ConsolePanelsMixin:
|
|
|
148
147
|
|
|
149
148
|
panels.sort(key=self._panel_sort_key_func(), reverse=self._panels_sort_reverse)
|
|
150
149
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
table.display = True
|
|
156
|
-
no_msg.display = False
|
|
150
|
+
is_empty = not panels and total == 0 and not self._search_query
|
|
151
|
+
self._set_table_empty_state(table, no_msg, is_empty=is_empty)
|
|
152
|
+
table.clear()
|
|
153
|
+
if not is_empty:
|
|
157
154
|
for panel in panels:
|
|
158
155
|
filled = self._live_panel_pane_count(panel, live_sessions)
|
|
159
156
|
total_panes = panel.total_panes
|
|
@@ -279,19 +276,23 @@ class ConsolePanelsMixin:
|
|
|
279
276
|
def _do_rename_panel(self, old_name: str, new_name: str | None) -> None:
|
|
280
277
|
if new_name is None or new_name == old_name:
|
|
281
278
|
return
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
279
|
+
validation_message = CreatePanelScreen.validate_new_panel_name(
|
|
280
|
+
self._panel_store,
|
|
281
|
+
new_name,
|
|
282
|
+
current_name=old_name,
|
|
283
|
+
)
|
|
284
|
+
if validation_message:
|
|
285
|
+
self._update_status(validation_message)
|
|
285
286
|
return
|
|
286
287
|
self._panel_store.rename(old_name, new_name)
|
|
287
288
|
self._load_panels()
|
|
288
289
|
|
|
289
290
|
def _open_panel(self, panel_name: str) -> None:
|
|
290
|
-
from ...integrations.tmux import
|
|
291
|
+
from ...integrations.tmux import rebuild_panel_tmux_session
|
|
292
|
+
from ...integrations.tmux.core import (
|
|
291
293
|
_protect_session,
|
|
292
294
|
_session_exists,
|
|
293
295
|
make_panel_session_name,
|
|
294
|
-
rebuild_panel_tmux_session,
|
|
295
296
|
)
|
|
296
297
|
|
|
297
298
|
panel = self._panel_store.get(panel_name)
|
|
@@ -98,8 +98,8 @@ class ConsoleReposMixin:
|
|
|
98
98
|
|
|
99
99
|
def _populate_initial_rows(self) -> None:
|
|
100
100
|
table = self.query_one("#repo-table", DataTable)
|
|
101
|
-
self.query_one("#no-repos-message", Static)
|
|
102
|
-
table
|
|
101
|
+
no_msg = self.query_one("#no-repos-message", Static)
|
|
102
|
+
self._set_table_empty_state(table, no_msg, is_empty=False)
|
|
103
103
|
preserved_row_key = None
|
|
104
104
|
preserved_row_index = None
|
|
105
105
|
restore_focus = False
|
|
@@ -133,8 +133,10 @@ class ConsoleReposMixin:
|
|
|
133
133
|
logger.debug("Failed to update repo row %s", row_key, exc_info=True)
|
|
134
134
|
|
|
135
135
|
def _show_no_repos(self) -> None:
|
|
136
|
-
self.query_one("#repo-table", DataTable)
|
|
137
|
-
self.query_one("#no-repos-message", Static)
|
|
136
|
+
table = self.query_one("#repo-table", DataTable)
|
|
137
|
+
no_msg = self.query_one("#no-repos-message", Static)
|
|
138
|
+
self._set_table_empty_state(table, no_msg, is_empty=True)
|
|
139
|
+
table.clear()
|
|
138
140
|
self._visible_repo_count = 0
|
|
139
141
|
self._visible_group_count = 0
|
|
140
142
|
self._update_status("No repositories linked")
|
|
@@ -314,8 +316,7 @@ class ConsoleReposMixin:
|
|
|
314
316
|
|
|
315
317
|
def _apply_filter_and_sort(self) -> None:
|
|
316
318
|
table = self.query_one("#repo-table", DataTable)
|
|
317
|
-
self.query_one("#no-repos-message", Static)
|
|
318
|
-
table.display = True
|
|
319
|
+
no_msg = self.query_one("#no-repos-message", Static)
|
|
319
320
|
preserved_row_key = None
|
|
320
321
|
preserved_row_index = None
|
|
321
322
|
restore_focus = False
|
|
@@ -328,7 +329,14 @@ class ConsoleReposMixin:
|
|
|
328
329
|
infos = list(self._results.values())
|
|
329
330
|
total = len(infos)
|
|
330
331
|
infos = self._filter_repo_infos_for_search(infos)
|
|
331
|
-
self.
|
|
332
|
+
is_empty = total == 0 and not self._repo_paths and not self._search_query
|
|
333
|
+
self._set_table_empty_state(table, no_msg, is_empty=is_empty)
|
|
334
|
+
table.clear()
|
|
335
|
+
if is_empty:
|
|
336
|
+
self._visible_repo_count = 0
|
|
337
|
+
self._visible_group_count = 0
|
|
338
|
+
else:
|
|
339
|
+
self._render_repo_info_rows(table, infos)
|
|
332
340
|
|
|
333
341
|
if self._resume_selection_tab == "repos":
|
|
334
342
|
self._restore_resume_selection("repos")
|
|
@@ -369,7 +377,7 @@ class ConsoleReposMixin:
|
|
|
369
377
|
|
|
370
378
|
msg += " ↑↓/jk navigate [enter] actions"
|
|
371
379
|
if group_count:
|
|
372
|
-
msg += " [space] toggle
|
|
380
|
+
msg += " [space] toggle"
|
|
373
381
|
msg += " g git / search s sort r refresh q quit"
|
|
374
382
|
if self._search_query:
|
|
375
383
|
msg += " [esc] clear search"
|
|
@@ -97,7 +97,6 @@ class ConsoleSessionsMixin:
|
|
|
97
97
|
preserved_row_key, preserved_row_index, restore_focus = self._capture_table_selection(
|
|
98
98
|
table
|
|
99
99
|
)
|
|
100
|
-
table.clear()
|
|
101
100
|
no_msg = self.query_one("#no-sessions-message", Static)
|
|
102
101
|
|
|
103
102
|
entries = list(self._sessions_entries)
|
|
@@ -130,12 +129,10 @@ class ConsoleSessionsMixin:
|
|
|
130
129
|
)
|
|
131
130
|
entries.sort(key=key_func, reverse=self._sessions_sort_reverse)
|
|
132
131
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
table.display = True
|
|
138
|
-
no_msg.display = False
|
|
132
|
+
is_empty = not entries and total == 0 and not self._search_query
|
|
133
|
+
self._set_table_empty_state(table, no_msg, is_empty=is_empty)
|
|
134
|
+
table.clear()
|
|
135
|
+
if not is_empty:
|
|
139
136
|
for entry in entries:
|
|
140
137
|
status = entry.get("status", "running")
|
|
141
138
|
description = _wrap_session_description(
|
|
@@ -24,6 +24,16 @@ logger = logging.getLogger(__name__)
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class ConsoleUIHelpersMixin:
|
|
27
|
+
def _set_table_empty_state(
|
|
28
|
+
self,
|
|
29
|
+
table: DataTable,
|
|
30
|
+
empty_message: Static,
|
|
31
|
+
*,
|
|
32
|
+
is_empty: bool,
|
|
33
|
+
) -> None:
|
|
34
|
+
table.display = not is_empty
|
|
35
|
+
empty_message.display = is_empty
|
|
36
|
+
|
|
27
37
|
def _compose_status_message(self, message: str) -> str:
|
|
28
38
|
notice = getattr(self, "_update_notice", None)
|
|
29
39
|
if not notice:
|