commandertui 1.1.0__tar.gz → 1.2.0__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.
- {commandertui-1.1.0 → commandertui-1.2.0}/PKG-INFO +53 -7
- {commandertui-1.1.0 → commandertui-1.2.0}/README.md +52 -6
- commandertui-1.2.0/commandertui/__init__.py +1 -0
- commandertui-1.2.0/commandertui/app.py +70 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/commandertui/bookmarks.py +4 -1
- {commandertui-1.1.0 → commandertui-1.2.0}/commandertui/cli.py +6 -1
- {commandertui-1.1.0 → commandertui-1.2.0}/commandertui/compare_screen.py +4 -4
- commandertui-1.2.0/commandertui/main_screen.py +443 -0
- commandertui-1.2.0/commandertui/modals.py +626 -0
- commandertui-1.2.0/commandertui/netlocations.py +118 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/commandertui/progress_screen.py +38 -3
- {commandertui-1.1.0 → commandertui-1.2.0}/commandertui/sync.py +13 -2
- commandertui-1.2.0/commandertui/theme.py +255 -0
- commandertui-1.2.0/commandertui/updater.py +163 -0
- commandertui-1.2.0/commandertui/widgets.py +357 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/commandertui.egg-info/PKG-INFO +53 -7
- {commandertui-1.1.0 → commandertui-1.2.0}/commandertui.egg-info/SOURCES.txt +4 -0
- commandertui-1.2.0/tests/test_app_smoke.py +761 -0
- commandertui-1.2.0/tests/test_netlocations.py +51 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/tests/test_sync.py +19 -0
- commandertui-1.2.0/tests/test_updater.py +109 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/tests/test_widgets.py +13 -0
- commandertui-1.1.0/commandertui/__init__.py +0 -1
- commandertui-1.1.0/commandertui/app.py +0 -40
- commandertui-1.1.0/commandertui/main_screen.py +0 -211
- commandertui-1.1.0/commandertui/modals.py +0 -250
- commandertui-1.1.0/commandertui/theme.py +0 -94
- commandertui-1.1.0/commandertui/widgets.py +0 -134
- commandertui-1.1.0/tests/test_app_smoke.py +0 -129
- {commandertui-1.1.0 → commandertui-1.2.0}/LICENSE +0 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/commandertui/ascii_font.py +0 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/commandertui/boot.py +0 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/commandertui/diff.py +0 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/commandertui/executor.py +0 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/commandertui/models.py +0 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/commandertui/plan.py +0 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/commandertui/run_queue.py +0 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/commandertui/scanner.py +0 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/commandertui/trash.py +0 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/commandertui.egg-info/dependency_links.txt +0 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/commandertui.egg-info/entry_points.txt +0 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/commandertui.egg-info/requires.txt +0 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/commandertui.egg-info/top_level.txt +0 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/pyproject.toml +0 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/setup.cfg +0 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/tests/test_bookmarks.py +0 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/tests/test_diff.py +0 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/tests/test_executor.py +0 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/tests/test_plan.py +0 -0
- {commandertui-1.1.0 → commandertui-1.2.0}/tests/test_scanner.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: commandertui
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Dual-pane retro terminal file manager: compare, sync, copy and move folders
|
|
5
5
|
Author: Humberto Barchini
|
|
6
6
|
License-Expression: MIT
|
|
@@ -22,7 +22,7 @@ Dual-pane terminal file manager: compare two folders, sync them, or move
|
|
|
22
22
|
files/folders between them (and to USB/SMB/any mounted path) — with a
|
|
23
23
|
phosphor-green 80s-terminal look and a short modem-style boot animation.
|
|
24
24
|
|
|
25
|
-
Status: **v1.
|
|
25
|
+
Status: **v1.2.0** — 108/108 tests passing. MIT licensed.
|
|
26
26
|
|
|
27
27
|
---
|
|
28
28
|
|
|
@@ -32,7 +32,7 @@ A safe, simple alternative to heavier dual-pane file managers. It was
|
|
|
32
32
|
written after an earlier attempt of the same idea had to be scrapped for
|
|
33
33
|
running an unauthenticated local HTTP server. This project deliberately:
|
|
34
34
|
|
|
35
|
-
- **Never opens a network port.** Pure terminal UI (Textual), no HTTP server,
|
|
35
|
+
- **Never opens a network port.** Pure terminal UI (Textual), no HTTP server (the only outgoing request is the daily update check, which you can turn off),
|
|
36
36
|
no webview.
|
|
37
37
|
- **Never does a raw `rm -rf`.** Deletes always go through the desktop trash
|
|
38
38
|
(`send2trash` → `gio trash` → manual move into `~/.local/share/Trash`, in
|
|
@@ -58,13 +58,19 @@ makepkg -si
|
|
|
58
58
|
|
|
59
59
|
Remove it with `sudo pacman -R commandertui`.
|
|
60
60
|
|
|
61
|
-
**
|
|
61
|
+
**PyPI (Linux / macOS / WSL).** It is a standard package on [PyPI](https://pypi.org/project/commandertui/) (Python 3.11+). Recommended via `pipx`:
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
|
-
pipx install
|
|
64
|
+
pipx install commandertui
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
Or inside an activated virtual environment:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip install commandertui
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**macOS.** Same Python package (`pipx install commandertui`), plus
|
|
68
74
|
[kitty](https://sw.kovidgoyal.net/kitty/) for the standalone window. Places
|
|
69
75
|
lists the volumes under `/Volumes` (system volumes such as Recovery and
|
|
70
76
|
Preboot are hidden) and FUSE mounts inside your home folder. To get a
|
|
@@ -120,13 +126,17 @@ o.window("^commandertui$", { float = true, center = true, size = { 1174, 637 } }
|
|
|
120
126
|
| `↑`/`↓`, `Enter` | Navigate, open directory |
|
|
121
127
|
| `Backspace` | Go to parent directory |
|
|
122
128
|
| `Space` | Mark/unmark item under cursor |
|
|
129
|
+
| `F2` | Rename the item under the cursor (never overwrites an existing name) |
|
|
123
130
|
| `F5` | Copy marked (or item under cursor) to the other pane |
|
|
124
131
|
| `F6` | Move marked to the other pane |
|
|
125
132
|
| `F7` | Create a folder in the active pane |
|
|
126
133
|
| `F8` / `Delete` | Delete marked (to trash) |
|
|
134
|
+
| `/` | Filter the active pane by name (contains, ignores case) as you type; `Enter` keeps it, `Esc` clears it |
|
|
135
|
+
| `+` / `-` / `*` | Mark / unmark by pattern (`*.iso`, or plain text = contains) / invert marks, over the rows shown |
|
|
136
|
+
| `u` | Update: shows the pending new version (see *Updates*) |
|
|
127
137
|
| `i` | Size: file/folder count and total bytes of marked items (background, `Esc` stops it) |
|
|
128
138
|
| `h` | Show/hide hidden (dot) files in both panes |
|
|
129
|
-
| `p` | Places: jump to a mounted drive (USB/SMB/rclone) or saved bookmark |
|
|
139
|
+
| `p` | Places: jump to a mounted drive (USB/SMB/rclone), saved bookmark or saved network location. Inside: `a` add a network location, `s` save the active pane's folder as a bookmark, `d` delete a saved one |
|
|
130
140
|
| `c` | Compare: full recursive diff between the two panes' current folders |
|
|
131
141
|
| `r` | Refresh both panes |
|
|
132
142
|
| `q` | Quit |
|
|
@@ -141,6 +151,41 @@ half-written), and a move never removes a source it didn't fully copy.
|
|
|
141
151
|
on-demand mount point doesn't show), GVFS/Nautilus network shares, FUSE
|
|
142
152
|
mounts inside `$HOME` (e.g. `rclone mount`), and saved bookmarks.
|
|
143
153
|
|
|
154
|
+
**Network locations (Linux, needs `gio`/GVFS):** in Places press `a`, pick
|
|
155
|
+
SMB, SFTP or FTP and type the server's IP or name. For SMB the app lists the
|
|
156
|
+
shares on the server and you pick one; SFTP/FTP connect straight in. The
|
|
157
|
+
location is saved in `~/.config/commandertui/network.json` (protocol, host
|
|
158
|
+
and share only -- never a password) and reappears in Places, mounting itself
|
|
159
|
+
when picked. If the server asks for a login, `gio` asks for it in the
|
|
160
|
+
terminal and can remember it in the system keyring, exactly like Nautilus.
|
|
161
|
+
|
|
162
|
+
Every key also has a clickable box on the bottom bar: one row on wide windows
|
|
163
|
+
(168+ columns), two rows when narrower (110 columns is the practical minimum).
|
|
164
|
+
The active pane has the thick border and the inverted path bar; each path bar
|
|
165
|
+
shows the free space of that disk, and the marked count and total size.
|
|
166
|
+
|
|
167
|
+
## Updates
|
|
168
|
+
|
|
169
|
+
Once a day, in the background and only after the start-up animation, the app
|
|
170
|
+
asks PyPI for the latest version (a plain GET, nothing about you is sent). If
|
|
171
|
+
a newer one exists you get a dialog -- **Update now / Later / Skip this
|
|
172
|
+
version** -- and the header keeps saying `update X available (press u)`.
|
|
173
|
+
It never installs on its own. **Update now** hands the terminal over and runs
|
|
174
|
+
the command that matches how it was installed:
|
|
175
|
+
|
|
176
|
+
| Installed as | Update runs |
|
|
177
|
+
|---|---|
|
|
178
|
+
| Arch package (`makepkg`) | rebuilds from the release's PKGBUILD with `makepkg -si` (your sudo password is typed in the terminal, never read by the app) |
|
|
179
|
+
| pipx | `pipx upgrade commandertui` |
|
|
180
|
+
| pip | `pip install --upgrade commandertui` |
|
|
181
|
+
| git checkout | no button: the dialog tells you to `git pull` |
|
|
182
|
+
|
|
183
|
+
Turn the check off with `commandertui --no-update-check` or by setting
|
|
184
|
+
`COMMANDERTUI_NO_UPDATE_CHECK=1`. `COMMANDERTUI_FAKE_LATEST=9.9.9` shows the
|
|
185
|
+
dialog without a real release, to see how it looks. The logic lives in one
|
|
186
|
+
self-contained file, `commandertui/updater.py`, written to be copied into
|
|
187
|
+
other apps.
|
|
188
|
+
|
|
144
189
|
Inside **Compare**:
|
|
145
190
|
|
|
146
191
|
| Key | Action |
|
|
@@ -167,6 +212,7 @@ commandertui/
|
|
|
167
212
|
├── trash.py # 3-layer safe delete (send2trash / gio trash / manual)
|
|
168
213
|
├── executor.py # runs an OperationQueue against the filesystem
|
|
169
214
|
├── bookmarks.py # detected mounts (USB/SMB/rclone) + saved places
|
|
215
|
+
├── netlocations.py # SMB/SFTP/FTP locations via gio (list shares, mount, save)
|
|
170
216
|
├── theme.py # green/amber phosphor palettes
|
|
171
217
|
├── boot.py # startup "modem" reveal animation
|
|
172
218
|
├── widgets.py # FilePanel: one pane's directory listing
|
|
@@ -4,7 +4,7 @@ Dual-pane terminal file manager: compare two folders, sync them, or move
|
|
|
4
4
|
files/folders between them (and to USB/SMB/any mounted path) — with a
|
|
5
5
|
phosphor-green 80s-terminal look and a short modem-style boot animation.
|
|
6
6
|
|
|
7
|
-
Status: **v1.
|
|
7
|
+
Status: **v1.2.0** — 108/108 tests passing. MIT licensed.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -14,7 +14,7 @@ A safe, simple alternative to heavier dual-pane file managers. It was
|
|
|
14
14
|
written after an earlier attempt of the same idea had to be scrapped for
|
|
15
15
|
running an unauthenticated local HTTP server. This project deliberately:
|
|
16
16
|
|
|
17
|
-
- **Never opens a network port.** Pure terminal UI (Textual), no HTTP server,
|
|
17
|
+
- **Never opens a network port.** Pure terminal UI (Textual), no HTTP server (the only outgoing request is the daily update check, which you can turn off),
|
|
18
18
|
no webview.
|
|
19
19
|
- **Never does a raw `rm -rf`.** Deletes always go through the desktop trash
|
|
20
20
|
(`send2trash` → `gio trash` → manual move into `~/.local/share/Trash`, in
|
|
@@ -40,13 +40,19 @@ makepkg -si
|
|
|
40
40
|
|
|
41
41
|
Remove it with `sudo pacman -R commandertui`.
|
|
42
42
|
|
|
43
|
-
**
|
|
43
|
+
**PyPI (Linux / macOS / WSL).** It is a standard package on [PyPI](https://pypi.org/project/commandertui/) (Python 3.11+). Recommended via `pipx`:
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
pipx install
|
|
46
|
+
pipx install commandertui
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Or inside an activated virtual environment:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install commandertui
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**macOS.** Same Python package (`pipx install commandertui`), plus
|
|
50
56
|
[kitty](https://sw.kovidgoyal.net/kitty/) for the standalone window. Places
|
|
51
57
|
lists the volumes under `/Volumes` (system volumes such as Recovery and
|
|
52
58
|
Preboot are hidden) and FUSE mounts inside your home folder. To get a
|
|
@@ -102,13 +108,17 @@ o.window("^commandertui$", { float = true, center = true, size = { 1174, 637 } }
|
|
|
102
108
|
| `↑`/`↓`, `Enter` | Navigate, open directory |
|
|
103
109
|
| `Backspace` | Go to parent directory |
|
|
104
110
|
| `Space` | Mark/unmark item under cursor |
|
|
111
|
+
| `F2` | Rename the item under the cursor (never overwrites an existing name) |
|
|
105
112
|
| `F5` | Copy marked (or item under cursor) to the other pane |
|
|
106
113
|
| `F6` | Move marked to the other pane |
|
|
107
114
|
| `F7` | Create a folder in the active pane |
|
|
108
115
|
| `F8` / `Delete` | Delete marked (to trash) |
|
|
116
|
+
| `/` | Filter the active pane by name (contains, ignores case) as you type; `Enter` keeps it, `Esc` clears it |
|
|
117
|
+
| `+` / `-` / `*` | Mark / unmark by pattern (`*.iso`, or plain text = contains) / invert marks, over the rows shown |
|
|
118
|
+
| `u` | Update: shows the pending new version (see *Updates*) |
|
|
109
119
|
| `i` | Size: file/folder count and total bytes of marked items (background, `Esc` stops it) |
|
|
110
120
|
| `h` | Show/hide hidden (dot) files in both panes |
|
|
111
|
-
| `p` | Places: jump to a mounted drive (USB/SMB/rclone) or saved bookmark |
|
|
121
|
+
| `p` | Places: jump to a mounted drive (USB/SMB/rclone), saved bookmark or saved network location. Inside: `a` add a network location, `s` save the active pane's folder as a bookmark, `d` delete a saved one |
|
|
112
122
|
| `c` | Compare: full recursive diff between the two panes' current folders |
|
|
113
123
|
| `r` | Refresh both panes |
|
|
114
124
|
| `q` | Quit |
|
|
@@ -123,6 +133,41 @@ half-written), and a move never removes a source it didn't fully copy.
|
|
|
123
133
|
on-demand mount point doesn't show), GVFS/Nautilus network shares, FUSE
|
|
124
134
|
mounts inside `$HOME` (e.g. `rclone mount`), and saved bookmarks.
|
|
125
135
|
|
|
136
|
+
**Network locations (Linux, needs `gio`/GVFS):** in Places press `a`, pick
|
|
137
|
+
SMB, SFTP or FTP and type the server's IP or name. For SMB the app lists the
|
|
138
|
+
shares on the server and you pick one; SFTP/FTP connect straight in. The
|
|
139
|
+
location is saved in `~/.config/commandertui/network.json` (protocol, host
|
|
140
|
+
and share only -- never a password) and reappears in Places, mounting itself
|
|
141
|
+
when picked. If the server asks for a login, `gio` asks for it in the
|
|
142
|
+
terminal and can remember it in the system keyring, exactly like Nautilus.
|
|
143
|
+
|
|
144
|
+
Every key also has a clickable box on the bottom bar: one row on wide windows
|
|
145
|
+
(168+ columns), two rows when narrower (110 columns is the practical minimum).
|
|
146
|
+
The active pane has the thick border and the inverted path bar; each path bar
|
|
147
|
+
shows the free space of that disk, and the marked count and total size.
|
|
148
|
+
|
|
149
|
+
## Updates
|
|
150
|
+
|
|
151
|
+
Once a day, in the background and only after the start-up animation, the app
|
|
152
|
+
asks PyPI for the latest version (a plain GET, nothing about you is sent). If
|
|
153
|
+
a newer one exists you get a dialog -- **Update now / Later / Skip this
|
|
154
|
+
version** -- and the header keeps saying `update X available (press u)`.
|
|
155
|
+
It never installs on its own. **Update now** hands the terminal over and runs
|
|
156
|
+
the command that matches how it was installed:
|
|
157
|
+
|
|
158
|
+
| Installed as | Update runs |
|
|
159
|
+
|---|---|
|
|
160
|
+
| Arch package (`makepkg`) | rebuilds from the release's PKGBUILD with `makepkg -si` (your sudo password is typed in the terminal, never read by the app) |
|
|
161
|
+
| pipx | `pipx upgrade commandertui` |
|
|
162
|
+
| pip | `pip install --upgrade commandertui` |
|
|
163
|
+
| git checkout | no button: the dialog tells you to `git pull` |
|
|
164
|
+
|
|
165
|
+
Turn the check off with `commandertui --no-update-check` or by setting
|
|
166
|
+
`COMMANDERTUI_NO_UPDATE_CHECK=1`. `COMMANDERTUI_FAKE_LATEST=9.9.9` shows the
|
|
167
|
+
dialog without a real release, to see how it looks. The logic lives in one
|
|
168
|
+
self-contained file, `commandertui/updater.py`, written to be copied into
|
|
169
|
+
other apps.
|
|
170
|
+
|
|
126
171
|
Inside **Compare**:
|
|
127
172
|
|
|
128
173
|
| Key | Action |
|
|
@@ -149,6 +194,7 @@ commandertui/
|
|
|
149
194
|
├── trash.py # 3-layer safe delete (send2trash / gio trash / manual)
|
|
150
195
|
├── executor.py # runs an OperationQueue against the filesystem
|
|
151
196
|
├── bookmarks.py # detected mounts (USB/SMB/rclone) + saved places
|
|
197
|
+
├── netlocations.py # SMB/SFTP/FTP locations via gio (list shares, mount, save)
|
|
152
198
|
├── theme.py # green/amber phosphor palettes
|
|
153
199
|
├── boot.py # startup "modem" reveal animation
|
|
154
200
|
├── widgets.py # FilePanel: one pane's directory listing
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.2.0"
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from textual.app import App
|
|
6
|
+
|
|
7
|
+
from . import updater
|
|
8
|
+
from .boot import BootScreen
|
|
9
|
+
from .main_screen import MainScreen
|
|
10
|
+
from .theme import css_for
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CommanderApp(App):
|
|
14
|
+
"""Owns only the screen stack. All pane behavior lives in MainScreen --
|
|
15
|
+
see its docstring for why that split matters for key bindings.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
CSS = css_for("green")
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self, left_path: str | None = None, right_path: str | None = None, check_updates: bool = True
|
|
22
|
+
) -> None:
|
|
23
|
+
super().__init__()
|
|
24
|
+
self.check_updates = check_updates and not updater.updates_disabled()
|
|
25
|
+
self.update_info: updater.UpdateInfo | None = None
|
|
26
|
+
self.update_prompted = False
|
|
27
|
+
self.left_path = os.path.abspath(left_path or os.path.expanduser("~"))
|
|
28
|
+
self.right_path = os.path.abspath(right_path or os.path.expanduser("~"))
|
|
29
|
+
|
|
30
|
+
def on_mount(self) -> None:
|
|
31
|
+
self.push_screen(MainScreen(self.left_path, self.right_path))
|
|
32
|
+
self.push_screen(BootScreen(), self._after_boot)
|
|
33
|
+
|
|
34
|
+
def _after_boot(self, _result=None) -> None:
|
|
35
|
+
# Looked up only once the boot animation is over, so a quick answer
|
|
36
|
+
# can never pop a dialog over it.
|
|
37
|
+
if self.check_updates:
|
|
38
|
+
self.run_worker(self._look_for_update, thread=True)
|
|
39
|
+
|
|
40
|
+
def _look_for_update(self) -> None:
|
|
41
|
+
info = updater.check()
|
|
42
|
+
if info:
|
|
43
|
+
self.call_from_thread(self.update_found, info)
|
|
44
|
+
|
|
45
|
+
def update_found(self, info: updater.UpdateInfo) -> None:
|
|
46
|
+
"""Remember the newer version and say so in the header; the dialog
|
|
47
|
+
itself waits until the main screen is actually showing (not the boot
|
|
48
|
+
animation)."""
|
|
49
|
+
self.update_info = info
|
|
50
|
+
self.sub_title = f"update {info.latest} available (press u)"
|
|
51
|
+
if isinstance(self.screen, MainScreen):
|
|
52
|
+
self.screen.prompt_update()
|
|
53
|
+
|
|
54
|
+
def clear_update(self) -> None:
|
|
55
|
+
self.update_info = None
|
|
56
|
+
self.sub_title = ""
|
|
57
|
+
|
|
58
|
+
# Convenience passthroughs so existing tests/tools that poke at the app
|
|
59
|
+
# directly (e.g. app._panel("left")) keep working without knowing about
|
|
60
|
+
# the MainScreen split.
|
|
61
|
+
def _panel(self, side: str):
|
|
62
|
+
return self.main_screen._panel(side)
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def main_screen(self) -> MainScreen:
|
|
66
|
+
return next(s for s in self.screen_stack if isinstance(s, MainScreen))
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def active_side(self) -> str:
|
|
70
|
+
return self.main_screen.active_side
|
|
@@ -186,4 +186,7 @@ def remove_bookmark(path: str) -> None:
|
|
|
186
186
|
|
|
187
187
|
def all_places() -> list[Place]:
|
|
188
188
|
home = Place(label="Home", path=os.path.expanduser("~"), kind="home")
|
|
189
|
-
|
|
189
|
+
from .netlocations import load_locations # lazy: netlocations imports CONFIG_DIR from here
|
|
190
|
+
|
|
191
|
+
saved_net = [Place(label=loc.label, path=loc.uri, kind="net-saved") for loc in load_locations()]
|
|
192
|
+
return [home] + detect_mounted() + detect_network_mounts() + detect_home_mounts() + load_saved() + saved_net
|
|
@@ -11,10 +11,15 @@ def main() -> None:
|
|
|
11
11
|
parser = argparse.ArgumentParser(prog="commandertui", description="Dual-pane TUI file manager")
|
|
12
12
|
parser.add_argument("left", nargs="?", default=os.path.expanduser("~"))
|
|
13
13
|
parser.add_argument("right", nargs="?", default=os.path.expanduser("~"))
|
|
14
|
+
parser.add_argument(
|
|
15
|
+
"--no-update-check",
|
|
16
|
+
action="store_true",
|
|
17
|
+
help="never contact PyPI to look for a newer version",
|
|
18
|
+
)
|
|
14
19
|
parser.add_argument("--version", action="version", version=f"commandertui {__version__}")
|
|
15
20
|
args = parser.parse_args()
|
|
16
21
|
|
|
17
|
-
app = CommanderApp(left_path=args.left, right_path=args.right)
|
|
22
|
+
app = CommanderApp(left_path=args.left, right_path=args.right, check_updates=not args.no_update_check)
|
|
18
23
|
app.run()
|
|
19
24
|
|
|
20
25
|
|
|
@@ -4,13 +4,14 @@ from textual.app import ComposeResult
|
|
|
4
4
|
from textual.binding import Binding
|
|
5
5
|
from textual.containers import Vertical
|
|
6
6
|
from textual.screen import Screen
|
|
7
|
-
from textual.widgets import DataTable,
|
|
7
|
+
from textual.widgets import DataTable, Static
|
|
8
8
|
|
|
9
9
|
from .diff import compare
|
|
10
10
|
from .modals import MessageScreen
|
|
11
11
|
from .models import DiffStatus, OpKind, OpSide, OperationQueue, QueuedOp
|
|
12
12
|
from .plan import plan_mirror, plan_two_way, plan_update
|
|
13
13
|
from .run_queue import run_queue_with_progress
|
|
14
|
+
from .widgets import CompareBar
|
|
14
15
|
|
|
15
16
|
STATUS_LABEL = {
|
|
16
17
|
DiffStatus.LEFT_ONLY: "< only left",
|
|
@@ -45,7 +46,7 @@ class CompareScreen(Screen):
|
|
|
45
46
|
def compose(self) -> ComposeResult:
|
|
46
47
|
yield Static("", id="compare-summary")
|
|
47
48
|
yield DataTable(id="compare-table", cursor_type="row")
|
|
48
|
-
yield
|
|
49
|
+
yield CompareBar()
|
|
49
50
|
|
|
50
51
|
def on_mount(self) -> None:
|
|
51
52
|
self.reload()
|
|
@@ -65,8 +66,7 @@ class CompareScreen(Screen):
|
|
|
65
66
|
summary = self.query_one("#compare-summary", Static)
|
|
66
67
|
summary.update(
|
|
67
68
|
f"{self.left_root} <-> {self.right_root}\n"
|
|
68
|
-
f"{non_identical} differences
|
|
69
|
-
"[m]irror [u]pdate [b]idirectional [space] mark [c]opy marked [esc] back"
|
|
69
|
+
f"{non_identical} differences"
|
|
70
70
|
)
|
|
71
71
|
|
|
72
72
|
def action_back(self) -> None:
|