xefm 1.0.1__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.
- xefm/__init__.py +27 -0
- xefm/__main__.py +7 -0
- xefm/_config.py +592 -0
- xefm/app.py +4599 -0
- xefm/archive.py +2148 -0
- xefm/backend_detector.py +109 -0
- xefm/background_shaders.py +3788 -0
- xefm/batch_rename_dialog.py +321 -0
- xefm/candidate_list.py +170 -0
- xefm/colors.py +754 -0
- xefm/compare_dialog.py +386 -0
- xefm/compare_selection.py +182 -0
- xefm/completion.py +263 -0
- xefm/config.py +984 -0
- xefm/const.py +48 -0
- xefm/dialog_geometry.py +149 -0
- xefm/diff_viewer.py +599 -0
- xefm/directory_diff_viewer.py +1612 -0
- xefm/external_programs.py +429 -0
- xefm/file_list_manager.py +630 -0
- xefm/file_monitor_manager.py +661 -0
- xefm/file_monitor_observer.py +416 -0
- xefm/file_operations.py +737 -0
- xefm/file_pane.py +705 -0
- xefm/filter_list_dialog.py +307 -0
- xefm/image_viewer.py +576 -0
- xefm/input_dialog.py +413 -0
- xefm/isearch_bar.py +246 -0
- xefm/log_manager.py +727 -0
- xefm/logging_handlers.py +361 -0
- xefm/pane_manager.py +339 -0
- xefm/path.py +1578 -0
- xefm/progress_animator.py +190 -0
- xefm/progress_manager.py +323 -0
- xefm/progressive_search_dialog.py +429 -0
- xefm/s3.py +1440 -0
- xefm/ssh.py +670 -0
- xefm/ssh_cache.py +330 -0
- xefm/ssh_config.py +140 -0
- xefm/ssh_connection.py +1488 -0
- xefm/state_manager.py +894 -0
- xefm/str_format.py +217 -0
- xefm/task.py +383 -0
- xefm/text_dialog.py +260 -0
- xefm/text_layout.py +1429 -0
- xefm/text_viewer.py +1232 -0
- xefm/tools/bcompare_dirs.py +63 -0
- xefm/tools/bcompare_files.py +101 -0
- xefm/tools/kiro.py +129 -0
- xefm/tools/vscode.py +134 -0
- xefm/viewer_registry.py +130 -0
- xefm-1.0.1.dist-info/METADATA +463 -0
- xefm-1.0.1.dist-info/RECORD +57 -0
- xefm-1.0.1.dist-info/WHEEL +5 -0
- xefm-1.0.1.dist-info/entry_points.txt +2 -0
- xefm-1.0.1.dist-info/licenses/LICENSE +35 -0
- xefm-1.0.1.dist-info/top_level.txt +1 -0
xefm/__init__.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
XeFM - a dual-pane file manager for the desktop and the terminal
|
|
3
|
+
|
|
4
|
+
Runs as a native desktop app on Windows and macOS and as a terminal (TUI) app on
|
|
5
|
+
Windows, macOS and Linux — the same widget code on either, chosen with
|
|
6
|
+
``--backend``. Supports local and remote filesystems (S3, SFTP, archives).
|
|
7
|
+
|
|
8
|
+
``xefm.app`` is the entry module (``python -m xefm`` and the ``xefm`` console
|
|
9
|
+
script both land in ``xefm.app.main``); its siblings hold the storage-agnostic
|
|
10
|
+
business logic.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
#: Single source of truth for the version string -- the ONLY place the literal
|
|
14
|
+
#: appears in this repo. Bumping the version means editing this line and nothing
|
|
15
|
+
#: else; every consumer derives it:
|
|
16
|
+
#:
|
|
17
|
+
#: * pyproject.toml -- dynamic ``version`` (``attr = "xefm.__version__"``)
|
|
18
|
+
#: * xefm.app / .const -- re-export it (``--version`` output)
|
|
19
|
+
#: * macos_app/build.sh, macos_app/create_dmg.sh -- ``sed`` this literal out
|
|
20
|
+
#: * windows_app/build.ps1 -- ``Select-String`` this literal out (-> XeFM.rc)
|
|
21
|
+
#: * test/test_argparse_integration.py -- imports it, asserts no literal
|
|
22
|
+
#:
|
|
23
|
+
#: Don't reintroduce a copy: prose docs point here instead of restating the
|
|
24
|
+
#: number, and the bundle builders accept a ``VERSION`` override for one-off
|
|
25
|
+
#: builds without editing anything.
|
|
26
|
+
__version__ = "1.0.1"
|
|
27
|
+
__author__ = "Tomonori Shimomura"
|
xefm/__main__.py
ADDED
xefm/_config.py
ADDED
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
XeFM User Configuration
|
|
4
|
+
|
|
5
|
+
This file contains your personal XeFM configuration.
|
|
6
|
+
You can modify any of these settings to customize XeFM behavior.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import platform
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
# Import xefm_tool function and xefm_python variable for external program configuration
|
|
13
|
+
from xefm.external_programs import xefm_tool, xefm_python
|
|
14
|
+
|
|
15
|
+
# Import backend detector for runtime backend detection
|
|
16
|
+
from xefm.backend_detector import is_desktop_mode
|
|
17
|
+
|
|
18
|
+
class Config:
|
|
19
|
+
"""User configuration for XeFM"""
|
|
20
|
+
|
|
21
|
+
# --- Desktop (GUI) mode fonts (ignored in TUI mode) ----------------------
|
|
22
|
+
# UI_FONT_NAME : proportional default face (file names, labels,
|
|
23
|
+
# dialogs, markdown prose).
|
|
24
|
+
# MONO_FONT_NAME : monospaced face for aligned content (size/date
|
|
25
|
+
# columns, viewer, diffs); also grounds the layout
|
|
26
|
+
# grid, so it must be monospaced.
|
|
27
|
+
# FONT_SIZE : point size applied to BOTH faces.
|
|
28
|
+
# Missing glyphs use the OS's native font substitution.
|
|
29
|
+
#
|
|
30
|
+
# None = the OS system default face -- already a matched pair per platform:
|
|
31
|
+
# macOS -> San Francisco + SF Mono
|
|
32
|
+
# Windows -> Segoe UI + Consolas
|
|
33
|
+
#
|
|
34
|
+
# To use named fonts, uncomment ONE block below (it runs after the defaults
|
|
35
|
+
# and overrides them). `sys` is already imported at the top of this file.
|
|
36
|
+
|
|
37
|
+
# Default: the system pair on every platform (recommended).
|
|
38
|
+
UI_FONT_NAME = None
|
|
39
|
+
MONO_FONT_NAME = None
|
|
40
|
+
|
|
41
|
+
# Example -- a sans-serif pairing:
|
|
42
|
+
# if sys.platform == 'darwin': # macOS
|
|
43
|
+
# UI_FONT_NAME = 'Helvetica Neue'
|
|
44
|
+
# MONO_FONT_NAME = 'Menlo'
|
|
45
|
+
# elif sys.platform == 'win32': # Windows
|
|
46
|
+
# UI_FONT_NAME = 'Segoe UI'
|
|
47
|
+
# MONO_FONT_NAME = 'Consolas'
|
|
48
|
+
# else: # other
|
|
49
|
+
# UI_FONT_NAME = None
|
|
50
|
+
# MONO_FONT_NAME = None
|
|
51
|
+
|
|
52
|
+
# Example -- a serif pairing (serif UI + serif/slab monospace):
|
|
53
|
+
# if sys.platform == 'darwin': # macOS
|
|
54
|
+
# UI_FONT_NAME = 'Georgia'
|
|
55
|
+
# MONO_FONT_NAME = 'PT Mono'
|
|
56
|
+
# elif sys.platform == 'win32': # Windows
|
|
57
|
+
# UI_FONT_NAME = 'Georgia'
|
|
58
|
+
# MONO_FONT_NAME = 'Courier New'
|
|
59
|
+
# else: # other
|
|
60
|
+
# UI_FONT_NAME = None
|
|
61
|
+
# MONO_FONT_NAME = None
|
|
62
|
+
|
|
63
|
+
FONT_SIZE = 12 # point size for both faces (8-72)
|
|
64
|
+
|
|
65
|
+
# Display settings
|
|
66
|
+
SHOW_HIDDEN_FILES = False
|
|
67
|
+
DEFAULT_LEFT_PANE_RATIO = 0.5 # 0.1 to 0.9
|
|
68
|
+
DEFAULT_LOG_HEIGHT_RATIO = 0.25 # 0.1 to 0.5
|
|
69
|
+
DATE_FORMAT = 'short' # 'short' (YY-MM-DD HH:mm) or 'full' (YYYY-MM-DD HH:mm:ss)
|
|
70
|
+
|
|
71
|
+
# Sorting settings
|
|
72
|
+
DEFAULT_SORT_MODE = 'name' # 'name', 'size', 'date'
|
|
73
|
+
DEFAULT_SORT_REVERSE = False
|
|
74
|
+
|
|
75
|
+
# -----------------------------------------------------------------------
|
|
76
|
+
# Custom themes (optional)
|
|
77
|
+
# -----------------------------------------------------------------------
|
|
78
|
+
# Register your own named themes here. Each one is added to the theme picker
|
|
79
|
+
# (View > Theme) and the T-key cycle alongside the built-ins — Dark+, Monokai,
|
|
80
|
+
# Dracula, Nord, Solarized, Gruvbox Dark, Light+, Solarized Light — so you can
|
|
81
|
+
# switch between them at run time. XeFM starts on Dark+ and remembers whichever
|
|
82
|
+
# theme you last switched to across restarts.
|
|
83
|
+
#
|
|
84
|
+
# THEMES maps a display name to a dict of color overrides. A theme inherits a
|
|
85
|
+
# base and overrides only what differs: set 'base' to any built-in (or another
|
|
86
|
+
# theme you defined above) to inherit it; with no 'base' it builds on the theme
|
|
87
|
+
# of the same name if one exists (so {'Dark+': {...}} tweaks the built-in), else
|
|
88
|
+
# on 'Dark+'. A name matching an existing theme replaces it in place.
|
|
89
|
+
#
|
|
90
|
+
# Every available key (all optional). Colors are (R, G, B) tuples, 0-255:
|
|
91
|
+
#
|
|
92
|
+
# 'base': 'Dark+' # theme to inherit (see above for default)
|
|
93
|
+
# # --- base palette ---
|
|
94
|
+
# 'background': (30, 30, 30) # content surface / editor background
|
|
95
|
+
# 'foreground': (212, 212, 212) # primary text
|
|
96
|
+
# 'muted': (157, 157, 157) # secondary text, dividers
|
|
97
|
+
# 'accent': (0, 122, 204) # focus ring, selection fill, default bars
|
|
98
|
+
# 'accent2': (78, 201, 176) # secondary accent (i-search base, recipes)
|
|
99
|
+
# 'surface': (48, 48, 52) # raised panels (pane header / popup)
|
|
100
|
+
# 'selection': (10, 105, 178) # active selection fill
|
|
101
|
+
# # --- chrome bars (a solid color for the whole bar) ---
|
|
102
|
+
# 'status': (0, 122, 204) # bottom status bar (also the viewers')
|
|
103
|
+
# 'footer': (0, 122, 204) # per-pane info bar
|
|
104
|
+
# # --- file panes: per-type name colors (a sub-dict, like 'syntax';
|
|
105
|
+
# # override only the types you name) ---
|
|
106
|
+
# 'file_types': {'directory': (204, 204, 120), # dirs (default: soft yellow)
|
|
107
|
+
# 'file': (212, 212, 212), # files (default: foreground)
|
|
108
|
+
# 'link': (86, 194, 214)} # symlinks (default: cyan)
|
|
109
|
+
# # ('directory' may also be given as a flat top-level key — shorthand for
|
|
110
|
+
# # file_types['directory']. A symlink is colored as a link even when it
|
|
111
|
+
# # points at a directory.)
|
|
112
|
+
# # --- file pane cursor cue (a sub-dict; the row outline / [ ] bracket,
|
|
113
|
+
# # distinct from the selection fill) ---
|
|
114
|
+
# 'cursor': {'active': (231, 76, 76), # focused pane (default: red)
|
|
115
|
+
# 'inactive': (140, 92, 94)} # blurred pane (default: muted red)
|
|
116
|
+
# # --- incremental search ---
|
|
117
|
+
# 'isearch_match': (78, 201, 176) # match-highlight base (default: accent2)
|
|
118
|
+
# # --- text / diff viewer syntax colors (override only the tokens you name) ---
|
|
119
|
+
# 'syntax': {'keyword': (86, 156, 214), 'string': (206, 145, 120),
|
|
120
|
+
# 'comment': (106, 153, 85), 'number': (181, 206, 168),
|
|
121
|
+
# 'operator': (212, 212, 212), 'builtin': (78, 201, 176),
|
|
122
|
+
# 'name': (156, 220, 254)}
|
|
123
|
+
# # --- recommended post-processing effect (GUI backend only) ---
|
|
124
|
+
# # A full-screen CRT / phosphor "look" composited over the rendered
|
|
125
|
+
# # frame. XeFM turns it on when this theme becomes active and off when you
|
|
126
|
+
# # switch away. Only the GUI backend (`xefm/app.py --backend gui`) renders it;
|
|
127
|
+
# # a terminal has no pixels to filter and silently ignores it.
|
|
128
|
+
# # 'post_effect': 'crt' # preset: glow + bloom + scanlines + vignette + roll
|
|
129
|
+
# # 'post_effect': {'bloom': 0.3, 'vignette': 0.15, 'glow': 0.22,
|
|
130
|
+
# # 'scanline': 0.15, 'roll': 0.1} # custom (override any)
|
|
131
|
+
# # --- background behind the UI (GUI backend only) ---
|
|
132
|
+
# # One background of two kinds (else the plain theme color). On/off with the
|
|
133
|
+
# # theme, like post_effect; a terminal has no pixels and ignores it. NOTE the
|
|
134
|
+
# # 'background' key above is the base *color* — these choose the content:
|
|
135
|
+
# # * animation — a slow moving scene, anchored on this theme's own colors
|
|
136
|
+
# # (foreground for the scene, background for the backdrop) so it stays
|
|
137
|
+
# # on-palette:
|
|
138
|
+
# # 'starfield' stars streaming toward you, fading in with depth
|
|
139
|
+
# # 'rain' falling streaks with fading tails
|
|
140
|
+
# # 'constellation' drifting nodes linked to their near neighbours
|
|
141
|
+
# # 'grid' flying through a wireframe corridor, the camera
|
|
142
|
+
# # slowly drifting and turning as it goes
|
|
143
|
+
# # 'wave' a dense particle wave with its own colour gradient
|
|
144
|
+
# # Written as a bare type, or a dict to retune speed / opacity:
|
|
145
|
+
# # 'animation': 'starfield' # the tuned default
|
|
146
|
+
# # 'animation': {'type': 'rain', 'speed': 1.0, 'opacity': 0.8}
|
|
147
|
+
# # ('cube', a spinning wireframe, also works — it is the UI toolkit's
|
|
148
|
+
# # own reference scene rather than one of XeFM's.)
|
|
149
|
+
# # * wallpaper — a single image scaled to fill the window:
|
|
150
|
+
# # 'wallpaper': '~/Pictures/bg.png'
|
|
151
|
+
# # 'wallpaper': {'image': '~/bg.png', 'fit': 'fit', 'opacity': 0.8}
|
|
152
|
+
# # fit: 'fill' (cover, default) | 'fit' (contain) | 'stretch' | 'center'
|
|
153
|
+
# # --- surface opacity (GUI backend only) ---
|
|
154
|
+
# # How opaque the UI's pane/row backgrounds are (0..1); below 1 the
|
|
155
|
+
# # background behind them shows through. A single per-theme value, separate
|
|
156
|
+
# # from the background so it applies to any kind. 1 = fully opaque UI.
|
|
157
|
+
# # 'opacity': 0.6
|
|
158
|
+
#
|
|
159
|
+
# Example:
|
|
160
|
+
#
|
|
161
|
+
# THEMES = {
|
|
162
|
+
# 'Ocean': { # builds on Dark+
|
|
163
|
+
# 'accent': (38, 139, 210),
|
|
164
|
+
# 'file_types': {'directory': (120, 200, 220), 'link': (90, 200, 180)},
|
|
165
|
+
# 'syntax': {'keyword': (0, 175, 215)},
|
|
166
|
+
# },
|
|
167
|
+
# 'Paper': { # a light theme, from a light base
|
|
168
|
+
# 'base': 'Light+',
|
|
169
|
+
# 'file_types': {'directory': (150, 110, 0)},
|
|
170
|
+
# },
|
|
171
|
+
# }
|
|
172
|
+
THEMES = {
|
|
173
|
+
# Phosphor: a monochrome phosphor-green CRT terminal — every color is a
|
|
174
|
+
# shade of green on a near-black screen. A ready-made example of a full
|
|
175
|
+
# custom theme; select it from View > Theme or with the T key. On the GUI
|
|
176
|
+
# backend the 'post_effect' below adds a real CRT glow over the green.
|
|
177
|
+
'Phosphor': {
|
|
178
|
+
'post_effect': 'crt', # CRT glow/bloom/scanlines (GUI backend)
|
|
179
|
+
'animation': 'rain', # falling phosphor streaks (GUI backend)
|
|
180
|
+
'opacity': 0.6, # chrome opacity; < 1 lets the rain show through
|
|
181
|
+
'background': (4, 15, 7), # dark CRT green-black
|
|
182
|
+
'foreground': (51, 245, 121), # phosphor green
|
|
183
|
+
'muted': (33, 138, 74), # dim green (secondary text / dividers)
|
|
184
|
+
'accent': (60, 235, 122), # focus ring / selection accent
|
|
185
|
+
'accent2': (124, 255, 168), # pale mint (i-search match base)
|
|
186
|
+
'surface': (11, 38, 20), # raised panels (header / popup)
|
|
187
|
+
'selection': (24, 105, 54), # active selection fill
|
|
188
|
+
'status': (12, 40, 22), # status bar (dark green panel)
|
|
189
|
+
'footer': (22, 68, 40), # per-pane info bar (lighter, so the
|
|
190
|
+
# footer/status boundary reads on TUI)
|
|
191
|
+
'file_types': {
|
|
192
|
+
'directory': (150, 255, 150), # directories (brightest green)
|
|
193
|
+
'link': (124, 255, 168), # symlinks (pale mint)
|
|
194
|
+
},
|
|
195
|
+
'cursor': { # keep the cue on-palette, not red
|
|
196
|
+
'active': (180, 255, 180), # bright green frame (focused pane)
|
|
197
|
+
'inactive': (60, 150, 90), # dim green frame (blurred pane)
|
|
198
|
+
},
|
|
199
|
+
'syntax': {
|
|
200
|
+
'keyword': (130, 255, 150),
|
|
201
|
+
'string': (90, 220, 120),
|
|
202
|
+
'comment': (36, 140, 78),
|
|
203
|
+
'number': (150, 255, 130),
|
|
204
|
+
'operator': (70, 210, 110),
|
|
205
|
+
'builtin': (150, 255, 170),
|
|
206
|
+
'name': (60, 235, 120),
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
# Behavior settings
|
|
212
|
+
CONFIRM_DELETE = True # Show confirmation dialog before deleting files/directories
|
|
213
|
+
CONFIRM_QUIT = True # Show confirmation dialog before quitting XeFM
|
|
214
|
+
CONFIRM_COPY = True # Show confirmation dialog before copying files/directories
|
|
215
|
+
CONFIRM_MOVE = True # Show confirmation dialog before moving files/directories
|
|
216
|
+
CONFIRM_DUPLICATE = True # Show confirmation dialog before duplicating files/directories
|
|
217
|
+
CONFIRM_EXTRACT_ARCHIVE = True # Show confirmation dialog before extracting archives
|
|
218
|
+
CONFIRM_ARCHIVE_CREATE = True # Show confirmation dialog before creating archives
|
|
219
|
+
|
|
220
|
+
# Key bindings - customize your shortcuts
|
|
221
|
+
# Each action can have multiple keys assigned to it
|
|
222
|
+
#
|
|
223
|
+
# Supported formats:
|
|
224
|
+
# 1. Simple format: 'action': ['key1', 'key2']
|
|
225
|
+
# - Works regardless of selection status
|
|
226
|
+
# - Keys can be characters ('a', 'Q') or special key names ('HOME', 'END')
|
|
227
|
+
#
|
|
228
|
+
# 2. Extended format: 'action': {'keys': ['key1', 'key2'], 'selection': 'any|required|none'}
|
|
229
|
+
# - 'any': works regardless of selection status (default)
|
|
230
|
+
# - 'required': only works when at least one item is explicitly selected
|
|
231
|
+
# - 'none': only works when no items are explicitly selected
|
|
232
|
+
#
|
|
233
|
+
# Special key names (use these strings in the keys list):
|
|
234
|
+
# 'HOME', 'END', 'PPAGE', 'NPAGE', 'UP', 'DOWN',
|
|
235
|
+
# 'LEFT', 'RIGHT', 'BACKSPACE', 'DELETE', 'INSERT',
|
|
236
|
+
# 'F1' through 'F12'
|
|
237
|
+
#
|
|
238
|
+
KEY_BINDINGS = {
|
|
239
|
+
# === Application Control ===
|
|
240
|
+
'quit': ['Q'], # Exit XeFM application
|
|
241
|
+
'help': ['?'], # Show help dialog with all key bindings
|
|
242
|
+
'redraw': ['F5'], # Additional redraw trigger (Ctrl-L is always hardcoded)
|
|
243
|
+
|
|
244
|
+
# === Navigation ===
|
|
245
|
+
'cursor_up': ['UP'], # Move cursor up one item
|
|
246
|
+
'cursor_down': ['DOWN'], # Move cursor down one item
|
|
247
|
+
'page_up': ['PAGE_UP'], # Move cursor up one page
|
|
248
|
+
'page_down': ['PAGE_DOWN'], # Move cursor down one page
|
|
249
|
+
'open_item': ['ENTER'], # Open file/directory or enter directory
|
|
250
|
+
'open_with_os': ['Command-ENTER'], # Open file(s) with OS default application
|
|
251
|
+
'reveal_in_os': ['Alt-ENTER'], # Reveal focused file in OS file manager
|
|
252
|
+
'go_parent': ['BACKSPACE'], # Go to parent directory
|
|
253
|
+
'switch_pane': ['TAB'], # Switch between left and right panes
|
|
254
|
+
'nav_left': ['LEFT'], # Left pane: go to parent, Right pane: switch to left pane
|
|
255
|
+
'nav_right': ['RIGHT'], # Right pane: go to parent, Left pane: switch to right pane
|
|
256
|
+
|
|
257
|
+
# === File Selection ===
|
|
258
|
+
'select_file': ['SPACE'], # Toggle selection of current file
|
|
259
|
+
'select_file_up': ['Shift-SPACE'], # Toggle selection and move up
|
|
260
|
+
'select_all': ['HOME'], # Select all items (Home key)
|
|
261
|
+
'unselect_all': ['END'], # Unselect all items (End key)
|
|
262
|
+
'select_all_files': ['A'], # Toggle selection of all files in current pane
|
|
263
|
+
'select_all_items': ['Shift-A'], # Toggle selection of all items (files + dirs)
|
|
264
|
+
|
|
265
|
+
# === Clipboard (copy names/paths to the system clipboard) ===
|
|
266
|
+
'copy_names': ['Command-Shift-C'], # Copy selected/focused file name(s) to clipboard
|
|
267
|
+
'copy_paths': ['Command-Shift-P'], # Copy selected/focused full path(s) to clipboard
|
|
268
|
+
|
|
269
|
+
# === File Operations ===
|
|
270
|
+
'copy_files': {'keys': ['C'], 'selection': 'required'}, # Copy selected files to other pane
|
|
271
|
+
'move_files': {'keys': ['M'], 'selection': 'required'}, # Move selected files to other pane
|
|
272
|
+
'delete_files': {'keys': ['K', 'DELETE'], 'selection': 'required'}, # Delete selected files/directories
|
|
273
|
+
'rename_file': ['R'], # Rename selected file/directory
|
|
274
|
+
'create_file': ['Shift-E'], # Create new file (prompts for filename)
|
|
275
|
+
'create_directory': {'keys': ['M'], 'selection': 'none'}, # Create new directory (only when no files selected)
|
|
276
|
+
|
|
277
|
+
# === File Viewing & Editing ===
|
|
278
|
+
'view_file': ['V'], # View file using configured viewer
|
|
279
|
+
'edit_file': ['E'], # Edit selected file with configured text editor
|
|
280
|
+
'file_details': ['I'], # Show detailed file information dialog
|
|
281
|
+
|
|
282
|
+
# === File Comparison ===
|
|
283
|
+
'diff_files': ['EQUAL'], # Compare two selected files side-by-side
|
|
284
|
+
'diff_directories': ['Shift-EQUAL'], # Compare directories recursively
|
|
285
|
+
|
|
286
|
+
# === Archive Operations ===
|
|
287
|
+
'create_archive': {'keys': ['P'], 'selection': 'required'}, # Create archive from selected files
|
|
288
|
+
'extract_archive': ['U'], # Extract selected archive file
|
|
289
|
+
|
|
290
|
+
# === Search & Filter ===
|
|
291
|
+
'search': ['F'], # Enter incremental search mode (isearch)
|
|
292
|
+
'search_dialog': ['Shift-F'], # Show filename search dialog
|
|
293
|
+
'search_content': ['Shift-G'], # Show content search dialog (grep)
|
|
294
|
+
'filter': [';'], # Enter filter mode to show only matching files
|
|
295
|
+
'clear_filter': [':'], # Clear current file filter
|
|
296
|
+
|
|
297
|
+
# === Sorting ===
|
|
298
|
+
'sort_menu': ['S'], # Show sort options menu
|
|
299
|
+
'quick_sort_name': ['1'], # Quick sort by filename
|
|
300
|
+
'quick_sort_ext': ['2'], # Quick sort by file extension
|
|
301
|
+
'quick_sort_size': ['3'], # Quick sort by file size
|
|
302
|
+
'quick_sort_date': ['4'], # Quick sort by modification date
|
|
303
|
+
|
|
304
|
+
# === Directory Navigation ===
|
|
305
|
+
'favorites': ['J'], # Show favorite directories dialog
|
|
306
|
+
'jump_to_path': ['Shift-J'], # Jump to path
|
|
307
|
+
'history': ['H'], # Show history for current pane
|
|
308
|
+
'drives_dialog': ['D'], # Show drives/volumes dialog
|
|
309
|
+
|
|
310
|
+
# === Pane Management ===
|
|
311
|
+
'sync_current_to_other': ['O'], # Sync current pane directory to other pane
|
|
312
|
+
'sync_other_to_current': ['Shift-O'], # Sync other pane directory to current pane
|
|
313
|
+
'compare_selection': ['W'], # Show file and directory comparison options
|
|
314
|
+
'adjust_pane_left': ['['], # Make left pane smaller (move boundary left)
|
|
315
|
+
'adjust_pane_right': [']'], # Make left pane larger (move boundary right)
|
|
316
|
+
'reset_pane_boundary': ['-'], # Reset pane split to 50% | 50%
|
|
317
|
+
|
|
318
|
+
# === Log Pane Control ===
|
|
319
|
+
'adjust_log_up': ['{'], # Make log pane larger (Shift+[)
|
|
320
|
+
'adjust_log_down': ['}'], # Make log pane smaller (Shift+])
|
|
321
|
+
'reset_log_height': ['_'], # Reset log pane height to default (Shift+-)
|
|
322
|
+
'scroll_log_up': ['Shift-UP'], # Scroll log pane up one line
|
|
323
|
+
'scroll_log_down': ['Shift-DOWN'], # Scroll log pane down one line
|
|
324
|
+
'scroll_log_page_up': ['Shift-LEFT'], # Scroll log pane up one page (to older messages)
|
|
325
|
+
'scroll_log_page_down': ['Shift-RIGHT'], # Scroll log pane down one page (to newer messages)
|
|
326
|
+
|
|
327
|
+
# === Text / Diff Viewer ===
|
|
328
|
+
# Viewer-only actions. 'search' (F, above) opens incremental search inside
|
|
329
|
+
# the viewers too. 'toggle_wrap' intentionally shares 'W' with
|
|
330
|
+
# 'compare_selection': they never apply in the same context (file list vs.
|
|
331
|
+
# open viewer), and each context matches its own action by name via
|
|
332
|
+
# KeyBindings.is_action_for_event, so the shared key is unambiguous.
|
|
333
|
+
'toggle_wrap': ['W'], # Text viewer: toggle line wrapping
|
|
334
|
+
# 'M' likewise shares with 'move_files' / 'create_directory' (file list
|
|
335
|
+
# only); in an open viewer it toggles the raw text view and the file
|
|
336
|
+
# type's rich renderer (Markdown for *.md), matched by name in-context.
|
|
337
|
+
'toggle_view_mode': ['M'], # Text viewer: toggle rendered (Markdown) / raw text
|
|
338
|
+
|
|
339
|
+
# === Image Viewer ===
|
|
340
|
+
# Image-viewer-only actions, matched by name in-context like the text
|
|
341
|
+
# viewer's above. '-' and '_' intentionally share with
|
|
342
|
+
# 'reset_pane_boundary' / 'reset_log_height': those apply to the file
|
|
343
|
+
# list only, never to an open viewer, and each context matches its own
|
|
344
|
+
# action by name via KeyBindings.is_action_for_event, so the shared keys
|
|
345
|
+
# are unambiguous. Arrow keys pan and Home/End jump to the first/last
|
|
346
|
+
# image; those stay viewer-local (not rebindable), like the text viewer's
|
|
347
|
+
# scroll keys.
|
|
348
|
+
'image_zoom_in': ['+', '='], # Image viewer: zoom in ('=' is unshifted '+')
|
|
349
|
+
'image_zoom_out': ['-', '_'], # Image viewer: zoom out
|
|
350
|
+
'image_zoom_reset': ['0'], # Image viewer: fit the whole image to the window
|
|
351
|
+
'image_next': ['n'], # Image viewer: next image in the file list
|
|
352
|
+
'image_prev': ['p'], # Image viewer: previous image in the file list
|
|
353
|
+
|
|
354
|
+
# === Display & Appearance ===
|
|
355
|
+
'toggle_hidden': ['.'], # Toggle visibility of hidden files (dotfiles)
|
|
356
|
+
'toggle_color_scheme': ['T'], # Switch between dark and light color schemes
|
|
357
|
+
'toggle_fallback_colors': ['Shift-T'], # Toggle fallback color mode for compatibility
|
|
358
|
+
'view_options': ['Z'], # Show view options menu
|
|
359
|
+
'settings_menu': ['Shift-Z'], # Show settings and configuration menu
|
|
360
|
+
|
|
361
|
+
# === External Programs ===
|
|
362
|
+
'programs': ['X'], # Show external programs menu
|
|
363
|
+
'subshell': ['Shift-X'], # Enter subshell (command line) mode
|
|
364
|
+
|
|
365
|
+
# === Configuration ===
|
|
366
|
+
# Unbound by default (reachable via the Tools menu). Assign a key here to
|
|
367
|
+
# open/reload this file without leaving XeFM, e.g. 'edit_config': ['Y'].
|
|
368
|
+
'edit_config': [], # Edit this config.py in TEXT_EDITOR, then reload
|
|
369
|
+
'reload_config': [], # Re-read this config.py and apply live
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
# Windows has no Command key, and Alt-Enter is the platform fullscreen-toggle
|
|
373
|
+
# convention — so the Mac-centric defaults above are unreachable there. Remap
|
|
374
|
+
# them to Ctrl equivalents on Windows.
|
|
375
|
+
if sys.platform == 'win32':
|
|
376
|
+
KEY_BINDINGS['open_with_os'] = ['Ctrl-ENTER'] # Open file(s) with OS default application
|
|
377
|
+
KEY_BINDINGS['reveal_in_os'] = ['Ctrl-Shift-E'] # Reveal focused file in Explorer
|
|
378
|
+
KEY_BINDINGS['copy_names'] = ['Ctrl-Shift-C'] # Copy selected/focused file name(s) to clipboard
|
|
379
|
+
KEY_BINDINGS['copy_paths'] = ['Ctrl-Shift-P'] # Copy selected/focused full path(s) to clipboard
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
# Favorite directories - customize your frequently used directories
|
|
383
|
+
# Each entry should have 'name' and 'path' keys
|
|
384
|
+
FAVORITE_DIRECTORIES = [
|
|
385
|
+
{'name': 'Home', 'path': '~'},
|
|
386
|
+
{'name': 'Documents', 'path': '~/Documents'},
|
|
387
|
+
{'name': 'Downloads', 'path': '~/Downloads'},
|
|
388
|
+
{'name': 'Desktop', 'path': '~/Desktop'},
|
|
389
|
+
{'name': 'Projects', 'path': '~/Projects'},
|
|
390
|
+
{'name': 'Root', 'path': '/'},
|
|
391
|
+
{'name': 'Temp', 'path': '/tmp'},
|
|
392
|
+
{'name': 'Config', 'path': '~/.config'},
|
|
393
|
+
# Add your own favorites here:
|
|
394
|
+
# {'name': 'Work', 'path': '/path/to/work'},
|
|
395
|
+
# {'name': 'Scripts', 'path': '~/bin'},
|
|
396
|
+
]
|
|
397
|
+
|
|
398
|
+
# Performance settings
|
|
399
|
+
MAX_LOG_MESSAGES = 1000
|
|
400
|
+
|
|
401
|
+
# History settings
|
|
402
|
+
MAX_HISTORY_ENTRIES = 100 # Maximum number of history entries to keep
|
|
403
|
+
|
|
404
|
+
# Progress animation settings
|
|
405
|
+
PROGRESS_ANIMATION_PATTERN = 'spinner' # 'spinner', 'dots', 'progress', 'bounce', 'pulse', 'wave', 'clock', 'arrow'
|
|
406
|
+
PROGRESS_ANIMATION_SPEED = 0.2 # Animation frame update interval in seconds
|
|
407
|
+
|
|
408
|
+
# Motion settings
|
|
409
|
+
# Suppress decorative motion app-wide: dialogs appear at once instead of
|
|
410
|
+
# scaling in, and an animated theme background (Sci-Fi's starfield) coasts to
|
|
411
|
+
# a stop and holds a still frame. Everything lands in its FINAL state, never
|
|
412
|
+
# frozen mid-animation, so nothing is hidden by turning this on. Set it if
|
|
413
|
+
# motion is uncomfortable, or over a slow SSH link where every animated frame
|
|
414
|
+
# is a screen repaint. Functional updates (progress, file-list reloads,
|
|
415
|
+
# search results) are unaffected.
|
|
416
|
+
REDUCED_MOTION = False
|
|
417
|
+
|
|
418
|
+
# File display settings
|
|
419
|
+
SEPARATE_EXTENSIONS = True # Show file extensions separately from basenames
|
|
420
|
+
MAX_EXTENSION_LENGTH = 5 # Maximum extension length to show separately
|
|
421
|
+
|
|
422
|
+
# Text editor settings
|
|
423
|
+
# Supports both string and list formats:
|
|
424
|
+
# - String format: 'vim' (single command, no arguments)
|
|
425
|
+
# - List format: ['code', '--wait'] (command with arguments)
|
|
426
|
+
# Automatically set based on actual running backend mode:
|
|
427
|
+
# - Terminal mode (curses): vim
|
|
428
|
+
# - Desktop mode (coregraphics): code (VS Code)
|
|
429
|
+
TEXT_EDITOR = 'code' if is_desktop_mode() else 'vim'
|
|
430
|
+
|
|
431
|
+
# Text diff tool settings
|
|
432
|
+
# Tool invoked when pressing 'E' (edit_file) key in DiffViewer or DirectoryDiffViewer
|
|
433
|
+
# Supports both string and list formats:
|
|
434
|
+
# - String format: 'vimdiff' (single command, no arguments)
|
|
435
|
+
# - List format: ['code', '--diff'] (command with arguments)
|
|
436
|
+
# Automatically set based on actual running backend mode:
|
|
437
|
+
# - Terminal mode (curses): vimdiff (string format example)
|
|
438
|
+
# - Desktop mode (coregraphics): code --diff (list format example)
|
|
439
|
+
TEXT_DIFF = ['code', '--diff'] if is_desktop_mode() else 'vimdiff'
|
|
440
|
+
|
|
441
|
+
# S3 settings
|
|
442
|
+
S3_CACHE_TTL = 60 # S3 cache TTL in seconds (default: 60 seconds)
|
|
443
|
+
|
|
444
|
+
# SSH/SFTP cache settings
|
|
445
|
+
SSH_CACHE_TTL = 30 # SSH cache TTL in seconds for successful results (default: 30 seconds)
|
|
446
|
+
SSH_CACHE_ERROR_TTL = 300 # SSH cache TTL in seconds for cached errors (default: 300 seconds / 5 minutes)
|
|
447
|
+
|
|
448
|
+
# Archive cache settings
|
|
449
|
+
ARCHIVE_CACHE_MAX_OPEN = 5 # Maximum number of archives to keep open simultaneously
|
|
450
|
+
ARCHIVE_CACHE_TTL = 300 # Archive cache TTL in seconds (default: 300 seconds / 5 minutes)
|
|
451
|
+
|
|
452
|
+
# File monitoring settings
|
|
453
|
+
FILE_MONITORING_ENABLED = True # Enable/disable automatic file list reloading
|
|
454
|
+
FILE_MONITORING_COALESCE_DELAY_MS = 200 # Event coalescing window in milliseconds
|
|
455
|
+
FILE_MONITORING_MAX_RELOADS_PER_SECOND = 5 # Maximum reloads per second (rate limiting)
|
|
456
|
+
FILE_MONITORING_FALLBACK_POLL_INTERVAL_S = 5 # Polling interval for fallback mode (seconds)
|
|
457
|
+
|
|
458
|
+
# File extension associations
|
|
459
|
+
# Maps file patterns to what each action should do.
|
|
460
|
+
#
|
|
461
|
+
# There are two tiers of "open", and they are bound to different keys:
|
|
462
|
+
#
|
|
463
|
+
# 'enter' ENTER Casual open. Stays inside XeFM. The value names
|
|
464
|
+
# a built-in handler, NOT a program to launch:
|
|
465
|
+
# 'viewer' - the built-in text/markdown viewer
|
|
466
|
+
# 'navigate' - browse the file as an archive
|
|
467
|
+
# (handy for *.jar, *.whl, ...)
|
|
468
|
+
# None - do nothing
|
|
469
|
+
# With no rule, XeFM's default applies: directories
|
|
470
|
+
# and archives are entered, files open in the
|
|
471
|
+
# built-in viewer.
|
|
472
|
+
#
|
|
473
|
+
# 'open' Cmd/Ctrl-ENTER Deliberate open. Hands the file to an external
|
|
474
|
+
# program. Falls back to the OS default app.
|
|
475
|
+
#
|
|
476
|
+
# The other two actions are 'view' (V) and 'edit' (E), both external.
|
|
477
|
+
#
|
|
478
|
+
# Compact Format Features:
|
|
479
|
+
# 1. Multiple patterns in one entry: ['*.jpg', '*.jpeg', '*.png']
|
|
480
|
+
# 2. Combined actions: 'open|view' assigns same command to both actions
|
|
481
|
+
# 3. Commands: List ['open', '-a', 'Preview'] or string 'open -a Preview'
|
|
482
|
+
# 4. None: Action not available -- except 'view': None, which selects the
|
|
483
|
+
# built-in viewer, and 'enter': None, which does nothing.
|
|
484
|
+
#
|
|
485
|
+
# You do NOT declare whether a program takes over the terminal. That follows
|
|
486
|
+
# from the backend, not from the program: in terminal mode XeFM suspends and
|
|
487
|
+
# waits for the child (correct for less/vim; a launcher like `open -a` just
|
|
488
|
+
# returns straight away), and in desktop mode there is no terminal to hand
|
|
489
|
+
# over, so the child is detached and XeFM stays responsive. As with
|
|
490
|
+
# TEXT_EDITOR above, pick programs that suit the mode you run in.
|
|
491
|
+
#
|
|
492
|
+
# Format:
|
|
493
|
+
# {
|
|
494
|
+
# 'pattern': '*.pdf' or ['*.jpg', '*.png'], # Single or multiple fnmatch patterns
|
|
495
|
+
# 'enter': 'viewer', # Built-in handler for the ENTER key
|
|
496
|
+
# 'open|view': ['command'], # Same command for open and view
|
|
497
|
+
# 'edit': ['command'], # Different command for edit
|
|
498
|
+
# }
|
|
499
|
+
FILE_ASSOCIATIONS = [
|
|
500
|
+
# PDF files
|
|
501
|
+
{
|
|
502
|
+
'pattern': '*.pdf',
|
|
503
|
+
'open|view': ['open', '-a', 'Preview'],
|
|
504
|
+
'edit': None,
|
|
505
|
+
},
|
|
506
|
+
# Image files. 'view' is deliberately None so V opens XeFM's own image
|
|
507
|
+
# viewer (zoom / pan / prev-next, staying inside XeFM) rather than handing
|
|
508
|
+
# the file to Preview; 'open' still hands it to the OS app for editing or
|
|
509
|
+
# a full-fidelity look. Set 'open|view' back to Preview if you would
|
|
510
|
+
# rather V left XeFM. Formats beyond these four still reach the built-in
|
|
511
|
+
# viewer by falling through with no rule at all.
|
|
512
|
+
{
|
|
513
|
+
'pattern': ['*.jpg', '*.jpeg', '*.png', '*.gif'],
|
|
514
|
+
'open': ['open', '-a', 'Preview'],
|
|
515
|
+
'view': None,
|
|
516
|
+
'edit': ['open', '-a', 'GIMP'],
|
|
517
|
+
},
|
|
518
|
+
# Video files
|
|
519
|
+
{
|
|
520
|
+
'pattern': ['*.mp4', '*.mov'],
|
|
521
|
+
'open|view': ['open', '-a', 'QuickTime Player'],
|
|
522
|
+
'edit': None,
|
|
523
|
+
},
|
|
524
|
+
# Audio files
|
|
525
|
+
{
|
|
526
|
+
'pattern': ['*.mp3', '*.wav'],
|
|
527
|
+
'open': ['open', '-a', 'Music'],
|
|
528
|
+
'edit': None,
|
|
529
|
+
},
|
|
530
|
+
# Microsoft Word documents
|
|
531
|
+
{
|
|
532
|
+
'pattern': ['*.doc', '*.docx'],
|
|
533
|
+
'open|view|edit': ['open', '-a', 'Microsoft Word'],
|
|
534
|
+
},
|
|
535
|
+
# Microsoft Excel spreadsheets
|
|
536
|
+
{
|
|
537
|
+
'pattern': ['*.xls', '*.xlsx'],
|
|
538
|
+
'open|view|edit': ['open', '-a', 'Microsoft Excel'],
|
|
539
|
+
},
|
|
540
|
+
# Microsoft PowerPoint presentations
|
|
541
|
+
{
|
|
542
|
+
'pattern': ['*.ppt', '*.pptx'],
|
|
543
|
+
'open|view|edit': ['open', '-a', 'Microsoft PowerPoint'],
|
|
544
|
+
},
|
|
545
|
+
# Note there is deliberately no entry listing text/code extensions.
|
|
546
|
+
# Enter and V already fall through to the built-in viewer when no rule
|
|
547
|
+
# matches, and the viewer sniffs the bytes -- so it reads text with no
|
|
548
|
+
# configuration and shows a placeholder for binaries, including for
|
|
549
|
+
# files with no extension or an unknown one that a list would miss.
|
|
550
|
+
#
|
|
551
|
+
# Zip-shaped archives XeFM does not enter by extension. This one *is*
|
|
552
|
+
# worth stating, because it cannot be sniffed: .docx and .xlsx are zip
|
|
553
|
+
# files too, and you want Word for those, not a file listing.
|
|
554
|
+
{
|
|
555
|
+
'pattern': ['*.jar', '*.whl', '*.egg'],
|
|
556
|
+
'enter': 'navigate',
|
|
557
|
+
},
|
|
558
|
+
# Add your own file associations here:
|
|
559
|
+
# {
|
|
560
|
+
# 'pattern': ['*.ext1', '*.ext2'],
|
|
561
|
+
# 'enter': 'viewer', # what ENTER does (in XeFM)
|
|
562
|
+
# 'open|view': ['command', 'args'], # external programs
|
|
563
|
+
# 'edit': ['command', 'args'],
|
|
564
|
+
# },
|
|
565
|
+
# Terminal programs need no special marking -- in terminal mode XeFM
|
|
566
|
+
# hands the display over and waits:
|
|
567
|
+
# {
|
|
568
|
+
# 'pattern': '*.log',
|
|
569
|
+
# 'view': ['less'],
|
|
570
|
+
# },
|
|
571
|
+
]
|
|
572
|
+
|
|
573
|
+
# External programs - each item has "name", "command", and optional "options" fields
|
|
574
|
+
# The "command" field is a list for safe subprocess execution
|
|
575
|
+
# Relative paths in the first element are resolved relative to the XeFM root directory (where xefm/app.py is located)
|
|
576
|
+
# Use xefm_tool('tool_name') to search for tools in:
|
|
577
|
+
# 1. ~/.xefm/tools/ (user-specific tools, highest priority)
|
|
578
|
+
# 2. {xefm/app.py directory}/tools/ (system tools, fallback)
|
|
579
|
+
# The "options" field is a dictionary with program-specific options:
|
|
580
|
+
# - auto_return: if True, automatically returns to XeFM without waiting for user input
|
|
581
|
+
PROGRAMS = [
|
|
582
|
+
{'name': 'Compare Files (BeyondCompare)', 'command': [xefm_python, xefm_tool('bcompare_files.py')], 'options': {'auto_return': True}},
|
|
583
|
+
{'name': 'Compare Directories (BeyondCompare)', 'command': [xefm_python, xefm_tool('bcompare_dirs.py')], 'options': {'auto_return': True}},
|
|
584
|
+
{'name': 'Open in VSCode', 'command': [xefm_python, xefm_tool('vscode.py')], 'options': {'auto_return': True}},
|
|
585
|
+
{'name': 'Open in Kiro', 'command': [xefm_python, xefm_tool('kiro.py')], 'options': {'auto_return': True}},
|
|
586
|
+
|
|
587
|
+
# Add your own programs here:
|
|
588
|
+
# {'name': 'My Custom Tool', 'command': [xefm_python, xefm_tool('my_custom_tool.py')], 'options': {'auto_return': True}},
|
|
589
|
+
# {'name': 'My Script (direct path)', 'command': [xefm_python, '/path/to/script.py']},
|
|
590
|
+
# {'name': 'Python REPL', 'command': ['python3']},
|
|
591
|
+
# {'name': 'Quick Command', 'command': ['ls', '-la'], 'options': {'auto_return': True}},
|
|
592
|
+
]
|