gitdirector 1.7.0__tar.gz → 1.7.2__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.0 → gitdirector-1.7.2}/PKG-INFO +27 -6
- {gitdirector-1.7.0 → gitdirector-1.7.2}/README.md +26 -5
- {gitdirector-1.7.0 → gitdirector-1.7.2}/pyproject.toml +1 -1
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/cli.py +2 -0
- gitdirector-1.7.2/src/gitdirector/commands/completion.py +74 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/help.py +4 -2
- gitdirector-1.7.2/src/gitdirector/commands/reset.py +82 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/app.py +48 -8
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/app_sessions.py +1 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/app_ui.py +2 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/screens/commit.py +38 -13
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/screens/diff.py +1 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/screens/panels.py +1 -1
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/screens/repos.py +7 -7
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/terminal_caps.py +4 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/terminal_widget.py +337 -20
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/config.py +85 -48
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/integrations/tmux/core.py +121 -10
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/integrations/tmux/monitor.py +2 -1
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/integrations/tmux/panels.py +78 -20
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/repo.py +4 -0
- gitdirector-1.7.0/src/gitdirector/commands/completion.py +0 -41
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/__init__.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/__init__.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/autoclean.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/capture.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/cd.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/gd_send.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/gd_tmux.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/info.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/link.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/listt.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/pull.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/status.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/__init__.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/app_groups.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/app_panels.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/app_repos.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/constants.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/diff_renderer.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/panel_view.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/panels.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/screens/__init__.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/screens/_shared.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/screens/diff_files.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/screens/groups.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/screens/session_actions.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/screens/sessions.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/tui/table_text.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/commands/unlink.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/github_credential_helper.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/info.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/integrations/__init__.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/integrations/tmux/__init__.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/manager.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/storage.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/src/gitdirector/ui_theme.py +0 -0
- {gitdirector-1.7.0 → gitdirector-1.7.2}/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.2
|
|
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
|
|
@@ -74,6 +74,7 @@ If you find GitDirector useful, please star this repository on GitHub, we need m
|
|
|
74
74
|
| `gitdirector pull [--yes]` | Pull latest changes for all tracked repositories |
|
|
75
75
|
| `gitdirector cd NAME` | Open or switch to a tmux session for a repository |
|
|
76
76
|
| `gitdirector autoclean` | Remove broken repository links from tracking |
|
|
77
|
+
| `gitdirector reset [--yes]` | Kill all sessions and panels, wipe ~/.gitdirector, and recreate the config |
|
|
77
78
|
| `gitdirector info PATH\|NAME [--full]` | Show file statistics for a repository |
|
|
78
79
|
| `gitdirector gd-tmux PATH\|NAME "command" [--description TEXT]` | Create a gd tmux session and run a command in it |
|
|
79
80
|
| `gitdirector gd-capture SESSION [--lines N] [--full]` | Print the scrollback of a live gd tmux session |
|
|
@@ -104,17 +105,17 @@ Features:
|
|
|
104
105
|
- `/` to filter the active tab
|
|
105
106
|
- `s` to sort the active table
|
|
106
107
|
- `i` to show repository info (file count, lines, tokens, max depth, top file types)
|
|
107
|
-
- `g` on the Repositories tab to open Git operations: status, timeline, branches, remotes, pull, and
|
|
108
|
+
- `g` on the Repositories tab to open Git operations: status, timeline, branches, remotes, pull, push, and a **Diff Viewer** section with **Review Diff** for uncommitted changes
|
|
108
109
|
- `r` to refresh all statuses
|
|
109
110
|
- Press `enter` on any repository to open an action menu:
|
|
110
111
|
- **New tmux session** — create and attach a session for the repository
|
|
111
|
-
- **Review Diff** — open a two-pane viewer for uncommitted changes (left: file list with GitHub-style status pills and +/- counts; right: per-file unified diff with GitHub-dark syntax highlighting, line numbers, and green/red backgrounds for added/removed lines). Navigate files with `j`/`k`, `n`/`p`, `g`/`G`, or `]`/`[`. Switch the focused panel with `tab`. `r` refreshes, `esc` closes.
|
|
112
112
|
- **Attach existing session** — switch to any already-running tmux session
|
|
113
113
|
- **Launch AI agent** — open OpenCode, Claude Code, GitHub Copilot, Codex, or Pi in a new tmux session
|
|
114
114
|
- **Remove session** — kill a running tmux session
|
|
115
|
+
- **Review Diff** (from the `g` Git menu) — open a two-pane viewer for uncommitted changes (left: file list with GitHub-style status pills and +/- counts; right: per-file unified diff with GitHub-dark syntax highlighting, line numbers, and green/red backgrounds for added/removed lines). Navigate files with `j`/`k`, `n`/`p`, `g`/`G`, or `]`/`[`. Switch the focused panel with `tab`. `r` refreshes, `esc` closes.
|
|
115
116
|
- **Repository groups** appear directly in the Repositories tab when linked repositories share the same parent directory. Group rows can be expanded/collapsed with `space`; press `enter` on a group to start, attach, remove, or launch AI-agent sessions rooted at that parent directory.
|
|
116
117
|
- **Sessions tab** (press `2`) lists every active `gd/*` tmux session with its status, purpose, repository, **description**, and full session name. The description column is free-form text stored on the session (default `"-"`), with a width that scales to your terminal and wraps long text. Highlight a row and press `d` to edit its description.
|
|
117
|
-
- **Panels tab** (press `3`) manages reusable tmux panel layouts. Press `n` to create a panel, or `enter` on a panel to open, reconfigure, rename, or delete it.
|
|
118
|
+
- **Panels tab** (press `3`) manages reusable tmux panel layouts. Press `n` to create a panel, or `enter` on a panel to open, reconfigure, rename, or delete it. Inside a panel you can **drag with the mouse to select text and copy it to the OS clipboard** on release; pressing `y` copies the entire visible pane as a fallback. GitDirector prefers native clipboard tools such as `pbcopy`, `wl-copy`, `xclip`, `xsel`, or `clip.exe`, and falls back to OSC 52 when needed.
|
|
118
119
|
|
|
119
120
|
### unlink
|
|
120
121
|
|
|
@@ -223,6 +224,15 @@ gitdirector autoclean
|
|
|
223
224
|
|
|
224
225
|
Scans the tracked repositories in `~/.gitdirector/config.yaml` for paths that no longer exist on disk and removes them from the config. Lists any broken links for review and asks for confirmation before deleting them.
|
|
225
226
|
|
|
227
|
+
### reset
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
gitdirector reset # asks for confirmation
|
|
231
|
+
gitdirector reset --yes # skip the confirmation prompt
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Destructive cleanup. Kills every `gd/*` tmux session (regular sessions and panels), wipes the entire `~/.gitdirector/` directory, and recreates an empty `config.yaml` with defaults. Useful when the local state is corrupted or you want to start fresh — all linked repositories, panel layouts, version-check cache, and tmux theme files are removed in one step.
|
|
235
|
+
|
|
226
236
|
### gd-capture
|
|
227
237
|
|
|
228
238
|
```bash
|
|
@@ -263,7 +273,7 @@ If you are an AI coding agent (Claude Code, OpenCode, GitHub Copilot, Codex, Pi,
|
|
|
263
273
|
|
|
264
274
|
## Configuration
|
|
265
275
|
|
|
266
|
-
|
|
276
|
+
Main config is stored at `~/.gitdirector/config.yaml`:
|
|
267
277
|
|
|
268
278
|
```yaml
|
|
269
279
|
repositories:
|
|
@@ -271,6 +281,11 @@ repositories:
|
|
|
271
281
|
- /path/to/repo2
|
|
272
282
|
max_workers: 10 # optional, valid range 1-32, default 10
|
|
273
283
|
theme: rose-pine # optional, default rose-pine
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Secrets (GitHub credentials) are stored in a separate file at `~/.gitdirector/secrets.yaml`:
|
|
287
|
+
|
|
288
|
+
```yaml
|
|
274
289
|
github_username: your-github-username # optional
|
|
275
290
|
github_PAT: github_pat_or_classic_pat # optional
|
|
276
291
|
```
|
|
@@ -281,7 +296,7 @@ GitDirector first runs git commands using your normal local git credentials. If
|
|
|
281
296
|
|
|
282
297
|
- This only applies to HTTPS GitHub remotes.
|
|
283
298
|
- SSH remotes still use your normal SSH key/agent setup.
|
|
284
|
-
- The PAT is stored in plaintext in `~/.gitdirector/
|
|
299
|
+
- The PAT is stored in plaintext in `~/.gitdirector/secrets.yaml`; use an appropriately scoped token and protect that file.
|
|
285
300
|
- The PAT is not passed on the command line or shown in TUI command output.
|
|
286
301
|
|
|
287
302
|
### Available Themes
|
|
@@ -304,6 +319,12 @@ eval "$(gitdirector completion zsh)"
|
|
|
304
319
|
gitdirector completion fish | source
|
|
305
320
|
```
|
|
306
321
|
|
|
322
|
+
The `zsh` script auto-loads `compinit` if `compdef` is not yet available, so the `eval` form works in fresh shells. For the fastest setup (no subprocess on every `TAB`), write the script to a file in your `$fpath` (commonly `~/.zsh/completions/_gitdirector`) and let `compinit` autoload it:
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
gitdirector completion zsh > "${fpath[1]}/_gitdirector"
|
|
326
|
+
```
|
|
327
|
+
|
|
307
328
|
## License
|
|
308
329
|
|
|
309
330
|
MIT
|
|
@@ -32,6 +32,7 @@ If you find GitDirector useful, please star this repository on GitHub, we need m
|
|
|
32
32
|
| `gitdirector pull [--yes]` | Pull latest changes for all tracked repositories |
|
|
33
33
|
| `gitdirector cd NAME` | Open or switch to a tmux session for a repository |
|
|
34
34
|
| `gitdirector autoclean` | Remove broken repository links from tracking |
|
|
35
|
+
| `gitdirector reset [--yes]` | Kill all sessions and panels, wipe ~/.gitdirector, and recreate the config |
|
|
35
36
|
| `gitdirector info PATH\|NAME [--full]` | Show file statistics for a repository |
|
|
36
37
|
| `gitdirector gd-tmux PATH\|NAME "command" [--description TEXT]` | Create a gd tmux session and run a command in it |
|
|
37
38
|
| `gitdirector gd-capture SESSION [--lines N] [--full]` | Print the scrollback of a live gd tmux session |
|
|
@@ -62,17 +63,17 @@ Features:
|
|
|
62
63
|
- `/` to filter the active tab
|
|
63
64
|
- `s` to sort the active table
|
|
64
65
|
- `i` to show repository info (file count, lines, tokens, max depth, top file types)
|
|
65
|
-
- `g` on the Repositories tab to open Git operations: status, timeline, branches, remotes, pull, and
|
|
66
|
+
- `g` on the Repositories tab to open Git operations: status, timeline, branches, remotes, pull, push, and a **Diff Viewer** section with **Review Diff** for uncommitted changes
|
|
66
67
|
- `r` to refresh all statuses
|
|
67
68
|
- Press `enter` on any repository to open an action menu:
|
|
68
69
|
- **New tmux session** — create and attach a session for the repository
|
|
69
|
-
- **Review Diff** — open a two-pane viewer for uncommitted changes (left: file list with GitHub-style status pills and +/- counts; right: per-file unified diff with GitHub-dark syntax highlighting, line numbers, and green/red backgrounds for added/removed lines). Navigate files with `j`/`k`, `n`/`p`, `g`/`G`, or `]`/`[`. Switch the focused panel with `tab`. `r` refreshes, `esc` closes.
|
|
70
70
|
- **Attach existing session** — switch to any already-running tmux session
|
|
71
71
|
- **Launch AI agent** — open OpenCode, Claude Code, GitHub Copilot, Codex, or Pi in a new tmux session
|
|
72
72
|
- **Remove session** — kill a running tmux session
|
|
73
|
+
- **Review Diff** (from the `g` Git menu) — open a two-pane viewer for uncommitted changes (left: file list with GitHub-style status pills and +/- counts; right: per-file unified diff with GitHub-dark syntax highlighting, line numbers, and green/red backgrounds for added/removed lines). Navigate files with `j`/`k`, `n`/`p`, `g`/`G`, or `]`/`[`. Switch the focused panel with `tab`. `r` refreshes, `esc` closes.
|
|
73
74
|
- **Repository groups** appear directly in the Repositories tab when linked repositories share the same parent directory. Group rows can be expanded/collapsed with `space`; press `enter` on a group to start, attach, remove, or launch AI-agent sessions rooted at that parent directory.
|
|
74
75
|
- **Sessions tab** (press `2`) lists every active `gd/*` tmux session with its status, purpose, repository, **description**, and full session name. The description column is free-form text stored on the session (default `"-"`), with a width that scales to your terminal and wraps long text. Highlight a row and press `d` to edit its description.
|
|
75
|
-
- **Panels tab** (press `3`) manages reusable tmux panel layouts. Press `n` to create a panel, or `enter` on a panel to open, reconfigure, rename, or delete it.
|
|
76
|
+
- **Panels tab** (press `3`) manages reusable tmux panel layouts. Press `n` to create a panel, or `enter` on a panel to open, reconfigure, rename, or delete it. Inside a panel you can **drag with the mouse to select text and copy it to the OS clipboard** on release; pressing `y` copies the entire visible pane as a fallback. GitDirector prefers native clipboard tools such as `pbcopy`, `wl-copy`, `xclip`, `xsel`, or `clip.exe`, and falls back to OSC 52 when needed.
|
|
76
77
|
|
|
77
78
|
### unlink
|
|
78
79
|
|
|
@@ -181,6 +182,15 @@ gitdirector autoclean
|
|
|
181
182
|
|
|
182
183
|
Scans the tracked repositories in `~/.gitdirector/config.yaml` for paths that no longer exist on disk and removes them from the config. Lists any broken links for review and asks for confirmation before deleting them.
|
|
183
184
|
|
|
185
|
+
### reset
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
gitdirector reset # asks for confirmation
|
|
189
|
+
gitdirector reset --yes # skip the confirmation prompt
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Destructive cleanup. Kills every `gd/*` tmux session (regular sessions and panels), wipes the entire `~/.gitdirector/` directory, and recreates an empty `config.yaml` with defaults. Useful when the local state is corrupted or you want to start fresh — all linked repositories, panel layouts, version-check cache, and tmux theme files are removed in one step.
|
|
193
|
+
|
|
184
194
|
### gd-capture
|
|
185
195
|
|
|
186
196
|
```bash
|
|
@@ -221,7 +231,7 @@ If you are an AI coding agent (Claude Code, OpenCode, GitHub Copilot, Codex, Pi,
|
|
|
221
231
|
|
|
222
232
|
## Configuration
|
|
223
233
|
|
|
224
|
-
|
|
234
|
+
Main config is stored at `~/.gitdirector/config.yaml`:
|
|
225
235
|
|
|
226
236
|
```yaml
|
|
227
237
|
repositories:
|
|
@@ -229,6 +239,11 @@ repositories:
|
|
|
229
239
|
- /path/to/repo2
|
|
230
240
|
max_workers: 10 # optional, valid range 1-32, default 10
|
|
231
241
|
theme: rose-pine # optional, default rose-pine
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Secrets (GitHub credentials) are stored in a separate file at `~/.gitdirector/secrets.yaml`:
|
|
245
|
+
|
|
246
|
+
```yaml
|
|
232
247
|
github_username: your-github-username # optional
|
|
233
248
|
github_PAT: github_pat_or_classic_pat # optional
|
|
234
249
|
```
|
|
@@ -239,7 +254,7 @@ GitDirector first runs git commands using your normal local git credentials. If
|
|
|
239
254
|
|
|
240
255
|
- This only applies to HTTPS GitHub remotes.
|
|
241
256
|
- SSH remotes still use your normal SSH key/agent setup.
|
|
242
|
-
- The PAT is stored in plaintext in `~/.gitdirector/
|
|
257
|
+
- The PAT is stored in plaintext in `~/.gitdirector/secrets.yaml`; use an appropriately scoped token and protect that file.
|
|
243
258
|
- The PAT is not passed on the command line or shown in TUI command output.
|
|
244
259
|
|
|
245
260
|
### Available Themes
|
|
@@ -262,6 +277,12 @@ eval "$(gitdirector completion zsh)"
|
|
|
262
277
|
gitdirector completion fish | source
|
|
263
278
|
```
|
|
264
279
|
|
|
280
|
+
The `zsh` script auto-loads `compinit` if `compdef` is not yet available, so the `eval` form works in fresh shells. For the fastest setup (no subprocess on every `TAB`), write the script to a file in your `$fpath` (commonly `~/.zsh/completions/_gitdirector`) and let `compinit` autoload it:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
gitdirector completion zsh > "${fpath[1]}/_gitdirector"
|
|
284
|
+
```
|
|
285
|
+
|
|
265
286
|
## License
|
|
266
287
|
|
|
267
288
|
MIT
|
|
@@ -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.2"
|
|
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" }
|
|
@@ -20,6 +20,7 @@ from .commands import (
|
|
|
20
20
|
listt,
|
|
21
21
|
print_update_notice,
|
|
22
22
|
pull,
|
|
23
|
+
reset,
|
|
23
24
|
status,
|
|
24
25
|
tui,
|
|
25
26
|
unlink,
|
|
@@ -59,6 +60,7 @@ cd.register(cli)
|
|
|
59
60
|
help.register(cli)
|
|
60
61
|
tui.register(cli)
|
|
61
62
|
autoclean.register(cli)
|
|
63
|
+
reset.register(cli)
|
|
62
64
|
info.register(cli)
|
|
63
65
|
gd_tmux.register(cli)
|
|
64
66
|
capture.register(cli)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from click.shell_completion import CompletionItem, get_completion_class
|
|
3
|
+
|
|
4
|
+
from ..config import Config
|
|
5
|
+
|
|
6
|
+
SUPPORTED_SHELLS = ("bash", "zsh", "fish")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _completion_var(prog_name: str) -> str:
|
|
10
|
+
return f"_{prog_name.replace('-', '_').replace('.', '_').upper()}_COMPLETE"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_ZSH_COMPINIT_BOOTSTRAP = (
|
|
14
|
+
"\n"
|
|
15
|
+
"# Ensure ``compdef`` is available when this script is ``eval``'d in a\n"
|
|
16
|
+
"# shell that has not yet loaded ``compinit``. Without this guard the\n"
|
|
17
|
+
"# ``compdef`` call below fails with ``command not found: compdef``.\n"
|
|
18
|
+
"if ! typeset -f compdef >/dev/null 2>&1; then\n"
|
|
19
|
+
" autoload -U +X compinit\n"
|
|
20
|
+
" compinit\n"
|
|
21
|
+
"fi\n"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _patch_zsh_source(source: str) -> str:
|
|
26
|
+
"""Inject a compinit bootstrap so ``eval`` works without prior setup.
|
|
27
|
+
|
|
28
|
+
Click's zsh output ends with ``compdef ...`` which requires the
|
|
29
|
+
``compdef`` builtin provided by ``compinit``. When the script is
|
|
30
|
+
eval'd in a fresh zsh that hasn't loaded ``compinit`` yet, that
|
|
31
|
+
final call fails. Inserting a bootstrap right after the
|
|
32
|
+
``#compdef`` directive ensures ``compdef`` is available without
|
|
33
|
+
changing behavior when the script is dropped into ``$fpath`` and
|
|
34
|
+
autoloaded by ``compinit`` (in which case ``compdef`` already
|
|
35
|
+
exists and the bootstrap is a no-op).
|
|
36
|
+
"""
|
|
37
|
+
marker = "\n_gitdirector_completion() {"
|
|
38
|
+
if marker not in source:
|
|
39
|
+
return source
|
|
40
|
+
return source.replace(marker, _ZSH_COMPINIT_BOOTSTRAP + "\n_gitdirector_completion() {", 1)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def completion_source(cli: click.Group, shell: str, *, prog_name: str = "gitdirector") -> str:
|
|
44
|
+
complete_cls = get_completion_class(shell)
|
|
45
|
+
if complete_cls is None:
|
|
46
|
+
raise click.ClickException(f"Unsupported shell: {shell}")
|
|
47
|
+
source = complete_cls(cli, {}, prog_name, _completion_var(prog_name)).source()
|
|
48
|
+
if shell == "zsh":
|
|
49
|
+
source = _patch_zsh_source(source)
|
|
50
|
+
return source
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def complete_repository_names(
|
|
54
|
+
_ctx: click.Context, _param: click.Parameter, incomplete: str
|
|
55
|
+
) -> list[CompletionItem]:
|
|
56
|
+
try:
|
|
57
|
+
repositories = Config().repositories
|
|
58
|
+
except (OSError, RuntimeError, ValueError):
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
prefix = incomplete.lower()
|
|
62
|
+
return [
|
|
63
|
+
CompletionItem(repo.name, help=str(repo))
|
|
64
|
+
for repo in sorted(repositories, key=lambda path: (path.name.lower(), str(path)))
|
|
65
|
+
if repo.name.lower().startswith(prefix)
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def register(cli: click.Group):
|
|
70
|
+
@cli.command()
|
|
71
|
+
@click.argument("shell", type=click.Choice(SUPPORTED_SHELLS))
|
|
72
|
+
def completion(shell: str):
|
|
73
|
+
"""Print shell completion setup for bash, zsh, or fish."""
|
|
74
|
+
click.echo(completion_source(cli, shell), nl=False)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import click
|
|
2
2
|
from rich.table import Table
|
|
3
|
+
from rich.text import Text
|
|
3
4
|
|
|
4
5
|
from . import console, get_version, print_update_notice
|
|
5
6
|
|
|
@@ -33,6 +34,7 @@ def show_help():
|
|
|
33
34
|
("cd NAME", "Open or switch to a tmux session for a repository"),
|
|
34
35
|
("console", "Interactive TUI for browsing and opening repositories"),
|
|
35
36
|
("autoclean", "Remove broken repository links from tracking"),
|
|
37
|
+
("reset [--yes]", "Kill all sessions and panels, wipe ~/.gitdirector, recreate config"),
|
|
36
38
|
("info PATH|NAME [--full]", "Show file statistics for a repository"),
|
|
37
39
|
(
|
|
38
40
|
'gd-tmux PATH|NAME "cmd" [--description "..."]',
|
|
@@ -42,11 +44,11 @@ def show_help():
|
|
|
42
44
|
"gd-capture SESSION [--lines N] [--full]",
|
|
43
45
|
"Print the current scrollback of a live gd tmux session",
|
|
44
46
|
),
|
|
45
|
-
("gd-send SESSION [TEXT] [--enter
|
|
47
|
+
("gd-send SESSION [TEXT] [--enter | --key C-c]", "Send text or C-c to a gd session"),
|
|
46
48
|
("completion SHELL", "Print shell completion setup for bash, zsh, or fish"),
|
|
47
49
|
("help", "Show this help message"),
|
|
48
50
|
]:
|
|
49
|
-
cmd_table.add_row(cmd, desc)
|
|
51
|
+
cmd_table.add_row(Text(cmd), desc)
|
|
50
52
|
|
|
51
53
|
console.print(cmd_table)
|
|
52
54
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
from ..config import Config
|
|
7
|
+
from . import console
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def register(cli: click.Group):
|
|
11
|
+
@cli.command()
|
|
12
|
+
@click.option(
|
|
13
|
+
"--yes",
|
|
14
|
+
"-y",
|
|
15
|
+
is_flag=True,
|
|
16
|
+
help="Skip the confirmation prompt and reset immediately.",
|
|
17
|
+
)
|
|
18
|
+
def reset(yes: bool):
|
|
19
|
+
"""Kill all sessions and panels, wipe ~/.gitdirector, and recreate config."""
|
|
20
|
+
_reset(confirm=not yes)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _reset(*, confirm: bool = True) -> None:
|
|
24
|
+
config_dir = Path.home() / ".gitdirector"
|
|
25
|
+
|
|
26
|
+
if confirm and not click.confirm(
|
|
27
|
+
f" This will kill every gd tmux session and permanently delete {config_dir}. Continue?",
|
|
28
|
+
default=False,
|
|
29
|
+
):
|
|
30
|
+
console.print()
|
|
31
|
+
console.print(" [dim]Cancelled.[/dim]")
|
|
32
|
+
console.print()
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
console.print()
|
|
36
|
+
|
|
37
|
+
killed = _kill_all_sessions()
|
|
38
|
+
if killed:
|
|
39
|
+
console.print(f" [green]Killed {len(killed)} session(s):[/green]")
|
|
40
|
+
for name in killed:
|
|
41
|
+
console.print(f" [dim]-[/dim] {name}")
|
|
42
|
+
else:
|
|
43
|
+
console.print(" [dim]No active gd tmux sessions to kill.[/dim]")
|
|
44
|
+
|
|
45
|
+
_wipe_config_dir(config_dir)
|
|
46
|
+
|
|
47
|
+
config = _recreate_config()
|
|
48
|
+
|
|
49
|
+
console.print()
|
|
50
|
+
console.print(f" [green]Wiped and recreated:[/green] {config.config_file}")
|
|
51
|
+
console.print()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _kill_all_sessions() -> list[str]:
|
|
55
|
+
try:
|
|
56
|
+
from ..integrations.tmux import kill_all_gd_sessions
|
|
57
|
+
except ImportError:
|
|
58
|
+
console.print(" [yellow]tmux integration unavailable; skipping session kill.[/yellow]")
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
return kill_all_gd_sessions()
|
|
63
|
+
except Exception as exc:
|
|
64
|
+
console.print(f" [yellow]Failed to kill some sessions: {exc}[/yellow]")
|
|
65
|
+
return []
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _wipe_config_dir(config_dir: Path) -> None:
|
|
69
|
+
if not config_dir.exists():
|
|
70
|
+
console.print(f" [dim]No existing {config_dir} to remove.[/dim]")
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
shutil.rmtree(config_dir)
|
|
75
|
+
except OSError as exc:
|
|
76
|
+
raise RuntimeError(f"Failed to remove {config_dir}: {exc}") from exc
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _recreate_config() -> Config:
|
|
80
|
+
config = Config()
|
|
81
|
+
config.clear()
|
|
82
|
+
return config
|
|
@@ -46,6 +46,7 @@ from .screens.repos import (
|
|
|
46
46
|
RepoInfoScreen,
|
|
47
47
|
)
|
|
48
48
|
from .screens.sessions import EditSessionDescriptionScreen, RemoveSessionScreen
|
|
49
|
+
from .terminal_caps import host_color_system, no_color_requested
|
|
49
50
|
|
|
50
51
|
_panel_row_height = _app_panels._panel_row_height
|
|
51
52
|
_render_panel_preview = _app_panels._render_panel_preview
|
|
@@ -88,6 +89,9 @@ class GitDirectorConsole(
|
|
|
88
89
|
background: $surface;
|
|
89
90
|
overflow: hidden;
|
|
90
91
|
}
|
|
92
|
+
HeaderTitle {
|
|
93
|
+
padding: 0 10 0 8;
|
|
94
|
+
}
|
|
91
95
|
#status-bar {
|
|
92
96
|
dock: bottom;
|
|
93
97
|
height: 1;
|
|
@@ -120,6 +124,7 @@ class GitDirectorConsole(
|
|
|
120
124
|
overflow-x: auto;
|
|
121
125
|
overflow-y: auto;
|
|
122
126
|
padding: 0 1;
|
|
127
|
+
scrollbar-size-horizontal: 0;
|
|
123
128
|
}
|
|
124
129
|
#no-repos-message {
|
|
125
130
|
height: 1fr;
|
|
@@ -194,7 +199,7 @@ class GitDirectorConsole(
|
|
|
194
199
|
Binding("s", "sort", "Sort", show=True),
|
|
195
200
|
Binding("g", "show_git_menu", "Git", show=True),
|
|
196
201
|
Binding("i", "show_info", "Info", show=True),
|
|
197
|
-
Binding("d", "edit_session_description", "Description", show=
|
|
202
|
+
Binding("d", "edit_session_description", "Description", show=True),
|
|
198
203
|
Binding("escape", "close_search", show=False),
|
|
199
204
|
Binding("1", "tab_repos", "Repos", show=False),
|
|
200
205
|
Binding("2", "tab_sessions", "Sessions", show=False),
|
|
@@ -204,6 +209,19 @@ class GitDirectorConsole(
|
|
|
204
209
|
]
|
|
205
210
|
|
|
206
211
|
def __init__(self) -> None:
|
|
212
|
+
# Ensure Textual's ``App.console`` auto-detects ``"truecolor"`` for
|
|
213
|
+
# its render Console. ``Strip.render()`` uses ``console._color_system``
|
|
214
|
+
# to pick the SGR format; if it resolves to ``"256"``, every
|
|
215
|
+
# truecolor segment produced by child agents gets quantised to
|
|
216
|
+
# the 256-colour palette — visible banding in gradients.
|
|
217
|
+
#
|
|
218
|
+
# Rich's auto-detection runs inside ``App.__init__`` using a
|
|
219
|
+
# snapshot of ``os.environ``, so the variable must be set
|
|
220
|
+
# *before* ``super().__init__()`` is called. We only force
|
|
221
|
+
# ``COLORTERM=truecolor`` when the host actually advertises
|
|
222
|
+
# truecolor — never downgrade a host that can't render it.
|
|
223
|
+
if not no_color_requested() and host_color_system() == "truecolor":
|
|
224
|
+
os.environ["COLORTERM"] = "truecolor"
|
|
207
225
|
super().__init__()
|
|
208
226
|
from ...integrations.tmux import TmuxMonitor
|
|
209
227
|
|
|
@@ -246,7 +264,7 @@ class GitDirectorConsole(
|
|
|
246
264
|
self._repo_status_executor: ThreadPoolExecutor | None = None
|
|
247
265
|
|
|
248
266
|
def compose(self) -> ComposeResult:
|
|
249
|
-
yield Header(show_clock=True)
|
|
267
|
+
yield Header(show_clock=True, icon="☰")
|
|
250
268
|
with TabbedContent(id="tabs"):
|
|
251
269
|
with TabPane("[1] Repositories", id="repos"):
|
|
252
270
|
yield Static("", id="repo-search-indicator", classes="search-indicator")
|
|
@@ -295,6 +313,7 @@ class GitDirectorConsole(
|
|
|
295
313
|
self._panels_col_keys = panels_table.add_columns(
|
|
296
314
|
"Map", "Name", "TMUX", "Layout", "Panes", "Status"
|
|
297
315
|
)
|
|
316
|
+
self._disable_tabs_widget_arrow_keybindings()
|
|
298
317
|
self.app_resume_signal.subscribe(self, self._handle_app_resume)
|
|
299
318
|
self._sync_tmux_theme_config(self.theme)
|
|
300
319
|
self._poll_timer = self.set_interval(
|
|
@@ -305,6 +324,21 @@ class GitDirectorConsole(
|
|
|
305
324
|
self._load_update_notice()
|
|
306
325
|
self._load_repos()
|
|
307
326
|
|
|
327
|
+
def _disable_tabs_widget_arrow_keybindings(self) -> None:
|
|
328
|
+
"""Remove the ``left``/``right`` bindings from every Tabs widget.
|
|
329
|
+
|
|
330
|
+
The ``Tabs`` widget binds ``left``/``right`` to ``previous_tab``/``next_tab``
|
|
331
|
+
so it can switch tabs when focused. We want those keys to scroll the
|
|
332
|
+
active table horizontally instead, so strip the bindings on the
|
|
333
|
+
underlying ContentTabs instances. Users can still switch tabs with the
|
|
334
|
+
``1``/``2``/``3`` number keys (and ``tab``/``shift+tab``).
|
|
335
|
+
"""
|
|
336
|
+
from textual.widgets._tabs import Tabs
|
|
337
|
+
|
|
338
|
+
for tabs_widget in self.query(Tabs):
|
|
339
|
+
tabs_widget._bindings.key_to_bindings.pop("left", None)
|
|
340
|
+
tabs_widget._bindings.key_to_bindings.pop("right", None)
|
|
341
|
+
|
|
308
342
|
def _background_shutdown_requested(self, worker: Worker | None = None) -> bool:
|
|
309
343
|
return self._shutdown_requested or (worker is not None and worker.is_cancelled)
|
|
310
344
|
|
|
@@ -434,8 +468,11 @@ class GitDirectorConsole(
|
|
|
434
468
|
kill_tmux_session(session_name)
|
|
435
469
|
self._update_status(f"tmux agent launch failed: {exc}")
|
|
436
470
|
return
|
|
437
|
-
self.
|
|
438
|
-
|
|
471
|
+
self._show_attach_loading_screen(
|
|
472
|
+
session_name,
|
|
473
|
+
path,
|
|
474
|
+
ready_marker=ready_marker,
|
|
475
|
+
skip_config_sync=True,
|
|
439
476
|
callback=refresh_after_launch,
|
|
440
477
|
)
|
|
441
478
|
else:
|
|
@@ -451,6 +488,7 @@ class GitDirectorConsole(
|
|
|
451
488
|
session_name: str,
|
|
452
489
|
path: Path | None = None,
|
|
453
490
|
row_key: str | None = None,
|
|
491
|
+
ready_marker: Path | None = None,
|
|
454
492
|
*,
|
|
455
493
|
skip_config_sync: bool = False,
|
|
456
494
|
callback: Callable[[object], None] | None = None,
|
|
@@ -478,6 +516,7 @@ class GitDirectorConsole(
|
|
|
478
516
|
AgentLoadingScreen(
|
|
479
517
|
title,
|
|
480
518
|
session_name,
|
|
519
|
+
ready_marker,
|
|
481
520
|
loading_hint=loading_hint,
|
|
482
521
|
on_attach=attach,
|
|
483
522
|
),
|
|
@@ -724,6 +763,8 @@ class GitDirectorConsole(
|
|
|
724
763
|
self._show_repo_git_remotes(path)
|
|
725
764
|
elif action == "push":
|
|
726
765
|
self._prompt_repo_push(path)
|
|
766
|
+
elif action == "review_diff":
|
|
767
|
+
self._open_review_diff(path)
|
|
727
768
|
|
|
728
769
|
def _show_repo_git_output(
|
|
729
770
|
self,
|
|
@@ -996,8 +1037,6 @@ class GitDirectorConsole(
|
|
|
996
1037
|
session_name = action[len("attach:") :]
|
|
997
1038
|
path = self._get_selected_path()
|
|
998
1039
|
self._attach_to_session(session_name, path)
|
|
999
|
-
elif action == "review_diff":
|
|
1000
|
-
self._open_review_diff()
|
|
1001
1040
|
elif action == "remove_session":
|
|
1002
1041
|
path = self._get_selected_path()
|
|
1003
1042
|
if path:
|
|
@@ -1006,8 +1045,9 @@ class GitDirectorConsole(
|
|
|
1006
1045
|
callback=self._handle_remove_selection,
|
|
1007
1046
|
)
|
|
1008
1047
|
|
|
1009
|
-
def _open_review_diff(self) -> None:
|
|
1010
|
-
path
|
|
1048
|
+
def _open_review_diff(self, path: Path | None = None) -> None:
|
|
1049
|
+
if path is None:
|
|
1050
|
+
path = self._get_selected_path()
|
|
1011
1051
|
if path is None:
|
|
1012
1052
|
return
|
|
1013
1053
|
info = self._results.get(str(path))
|
|
@@ -62,6 +62,8 @@ class ConsoleUIHelpersMixin:
|
|
|
62
62
|
return self._active_tab == "repos"
|
|
63
63
|
if action in {"show_git_menu", "show_info"}:
|
|
64
64
|
return self._active_tab == "repos"
|
|
65
|
+
if action == "edit_session_description":
|
|
66
|
+
return self._active_tab == "sessions"
|
|
65
67
|
return super().check_action(action, parameters)
|
|
66
68
|
|
|
67
69
|
def _arm_resume_new_panel_guard(self, restore_tab: str) -> None:
|
|
@@ -119,6 +119,20 @@ class StageFilesConfirmScreen(ModalScreen[bool]):
|
|
|
119
119
|
_COMMIT_MESSAGE_RE = re.compile(r"[\s\S]+", re.MULTILINE)
|
|
120
120
|
|
|
121
121
|
|
|
122
|
+
class _CommitActionOptionList(OptionList):
|
|
123
|
+
"""OptionList used as the commit/push picker with vim-style j/k keys.
|
|
124
|
+
|
|
125
|
+
The ``j``/``k`` bindings live on the widget itself so they only
|
|
126
|
+
fire when the picker has focus. This keeps typing ``j`` or ``k``
|
|
127
|
+
inside the commit message input working as expected.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
BINDINGS = [
|
|
131
|
+
Binding("j", "cursor_down", show=False),
|
|
132
|
+
Binding("k", "cursor_up", show=False),
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
|
|
122
136
|
class CommitMessageScreen(ModalScreen[Optional[tuple[str, bool]]]):
|
|
123
137
|
"""Collect a commit message and the final action (commit / commit & push).
|
|
124
138
|
|
|
@@ -127,18 +141,22 @@ class CommitMessageScreen(ModalScreen[Optional[tuple[str, bool]]]):
|
|
|
127
141
|
"commit & push" so the caller should run ``git push`` after the
|
|
128
142
|
commit succeeds.
|
|
129
143
|
|
|
130
|
-
Two
|
|
144
|
+
Two picker entries below the input act as the action
|
|
131
145
|
picker; the first option ("commit") is highlighted by default.
|
|
132
146
|
Pressing ``enter`` from the input field commits with the currently
|
|
133
147
|
highlighted action, matching the project's existing form
|
|
134
148
|
conventions (see ``CreatePanelScreen``).
|
|
149
|
+
|
|
150
|
+
Focus toggles between the input and the action picker with
|
|
151
|
+
``tab`` / ``shift+tab``. While the picker is focused, ``j`` /
|
|
152
|
+
``k`` (and ``up`` / ``down``) move the selection.
|
|
135
153
|
"""
|
|
136
154
|
|
|
137
155
|
BINDINGS = [
|
|
138
156
|
Binding("escape", "cancel", "Esc cancel", show=True),
|
|
139
157
|
Binding("ctrl+enter", "confirm", "Confirm", show=False),
|
|
140
|
-
Binding("tab", "
|
|
141
|
-
Binding("shift+tab", "
|
|
158
|
+
Binding("tab", "focus_toggle", "Tab switch focus", show=False),
|
|
159
|
+
Binding("shift+tab", "focus_toggle", "Shift+Tab switch focus", show=False),
|
|
142
160
|
Binding("down", "action_cursor_down", "\u2193 action", show=False),
|
|
143
161
|
Binding("up", "action_cursor_up", "\u2191 action", show=False),
|
|
144
162
|
]
|
|
@@ -218,13 +236,14 @@ class CommitMessageScreen(ModalScreen[Optional[tuple[str, bool]]]):
|
|
|
218
236
|
placeholder="Commit message\u2026",
|
|
219
237
|
id="commit-message-input",
|
|
220
238
|
)
|
|
221
|
-
yield
|
|
239
|
+
yield _CommitActionOptionList(
|
|
222
240
|
Option("[white]\u2713[/white] [bold]Commit[/bold]", id="commit"),
|
|
223
241
|
Option("[cyan]\u2191[/cyan] [bold]Commit and push[/bold]", id="commit_push"),
|
|
224
242
|
id="commit-message-action-list",
|
|
225
243
|
)
|
|
226
244
|
yield Static(
|
|
227
|
-
"type message [\u2191\u2193] pick action
|
|
245
|
+
"type message [tab] switch [\u2191\u2193/jk] pick action"
|
|
246
|
+
" [enter] confirm [esc] cancel",
|
|
228
247
|
id="commit-message-hint",
|
|
229
248
|
)
|
|
230
249
|
|
|
@@ -261,20 +280,26 @@ class CommitMessageScreen(ModalScreen[Optional[tuple[str, bool]]]):
|
|
|
261
280
|
self._in_message = True
|
|
262
281
|
self.query_one("#commit-message-input", Input).focus()
|
|
263
282
|
|
|
283
|
+
def action_focus_toggle(self) -> None:
|
|
284
|
+
"""Toggle focus between the message input and the action picker."""
|
|
285
|
+
if self._action_list_has_focus():
|
|
286
|
+
self.action_focus_message()
|
|
287
|
+
else:
|
|
288
|
+
self.action_focus_action()
|
|
289
|
+
|
|
290
|
+
def _action_list_has_focus(self) -> bool:
|
|
291
|
+
try:
|
|
292
|
+
action_list = self.query_one("#commit-message-action-list", OptionList)
|
|
293
|
+
except Exception:
|
|
294
|
+
return False
|
|
295
|
+
return self.focused is action_list
|
|
296
|
+
|
|
264
297
|
def action_cursor_down(self) -> None:
|
|
265
298
|
self.query_one("#commit-message-action-list", OptionList).action_cursor_down()
|
|
266
299
|
|
|
267
300
|
def action_cursor_up(self) -> None:
|
|
268
301
|
self.query_one("#commit-message-action-list", OptionList).action_cursor_up()
|
|
269
302
|
|
|
270
|
-
def action_cursor_down_global(self) -> None:
|
|
271
|
-
# Bound for the ``down`` key while focus is on the Input —
|
|
272
|
-
# moves the action list, not the cursor inside the Input.
|
|
273
|
-
if self._in_message:
|
|
274
|
-
self.action_cursor_down()
|
|
275
|
-
else:
|
|
276
|
-
super().action_cursor_down() # type: ignore[misc]
|
|
277
|
-
|
|
278
303
|
def _message(self) -> str:
|
|
279
304
|
return self.query_one("#commit-message-input", Input).value.strip()
|
|
280
305
|
|