filecon 0.4.0 → 0.5.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 27fa8d3a100f9d752db3fde969136cb2c1435c809970691629e46e0c897b2a46
4
- data.tar.gz: 87bd4e4845433cbda8c0c35f70c4e7dcc94ff95c026cfa0f84fd9cecc211201d
3
+ metadata.gz: 5974a832fd4c71067467f52e319ccbc571cac95c740e04ff5324699c1ed9644e
4
+ data.tar.gz: 825ea4d8a0c3ceef5741b05fda14cf1fb323423db521ce4d4f73dec731d4f97d
5
5
  SHA512:
6
- metadata.gz: 9cc34718bdfdd72ebbadf9cf91cd3d654df2ac6b5350c9c9160ba03577d692541e8d117ead9452cba6f1a5b8f9a05a3195691a53caccce496b8790e53ed7e5eb
7
- data.tar.gz: 4ddf3aefa8c71081089b61ebfc03a731c31f763478310091aa5cce43ff1aa16bc129a1e158c5620f7aad17b7a917090fd14ae22cb61ec435f61f44f8b6a372f8
6
+ metadata.gz: 8a10b9a8136a7582e08a2f5aa82483e34035781f01a16ca8a50705a2b4728dc70dca191da64272980888d8a8ba84f436493c40378c7b3399e3e751c465531402
7
+ data.tar.gz: fb1d09dc92c41f2165ee12f5f4089675dd4e28d414e02821a5eb74c4cb1c349130c0921a650f9d43ea322c3d21fc39acc169d17dd7b53cb35e049a8be469fd31
data/CHANGELOG.md CHANGED
@@ -2,6 +2,34 @@
2
2
 
3
3
  ## [vx.y.z] — __DATE__
4
4
 
5
+ ## [v0.5.0] — 2026-09-30
6
+
7
+ ### Added
8
+ - `x` — `execute` types a command line (with TAB completion from the shell) and runs it: `|` output goes to a status line with the last line shown until a keypress, plain commands run through the shell
9
+ - `%` substitutions in command edits — alt-editing a command in the `open_file` list or in command history runs the substitution parser again on the result, so `%name`/`%str`/`%dir`/`%file`/`%cmd` work in edits (`%name` resolves to the current directory for history edits), and a cancelled substitution aborts the command
10
+ - Command groups for a file are collected in a canonical order — `printable`, `executable`, subtype, type, `default` — so the printable and executable categories lead the picker (and the printable group's first command becomes the default action) ahead of MIME-derived groups
11
+ - Interactive copy/move — `!transfer(:copy|:move, %name, %dir)` runs copy/move in Ruby with per-conflict pickers (skip/overwrite/older/smaller/size-differs/rename plus alt-number apply-to-all), a from/to info grid, rename prompts, progress and end tally; unreadable entries count as failed instead of aborting
12
+
13
+ ### Changed
14
+ - `%file`/`%dir`/`%cmd` substitution tokens blink on the command line while browsing for a pick
15
+ - `=` command output — the text area drops its border and shows the command string as a header one row higher, so the full height goes to the output
16
+ - `e[x]ecute` (PATH browser) is now `[e]xecutables` on `e`, freeing `x` for the new command-line runner
17
+ - Filecon is described as "a file and command manager that lives between terminal and shell" throughout the documentation (README, man page, help panel, config header, gemspec)
18
+ - Conflicts are decided by name alone — a destination that exists (or is a dangling symlink) prompts, even when the content is identical; prompts are collected on the `@user` line via `Line#ask` instead of scattered `Typr.read_line` calls
19
+
20
+ ### Fixed
21
+ - Key bindings whose value is a prefixed command (for example `+ls`) no longer raise `NameError` — the key was passed to the method dispatcher, which evaluated `+ls` as unary `+` on a method named `ls`; a prefixed binding is now looked up when the key is pressed and run through the same `build`/`run` procedure as a command from the file list, including `%` substitution, and without being added to the command history. `!method` and bare-name bindings keep dispatching to their method
22
+ - Clicking a bottom-bar label whose binding is a prefixed command runs it like a keypress — the click path previously looked the binding up and dispatched the raw `+ls` to the method dispatcher, which raised `NameError` and did nothing
23
+ - Commands that open a terminal window (`+` and any other prefix whose format is not Ruby) no longer block the browser when launched from `e[x]ecute` — the launcher used to be read to EOF, freezing the browser for the whole window and echoing every keypress and mouse report as `^[OB`/`^[[<0;…M` garbage on the screen; it is now left running unattended in its own process group, exactly as the command list and command history already did
24
+ - The `term:` config setting is removed — a terminal window is opened by the prefix format itself (`$TERM --hold %s`), expanded by the shell from the environment
25
+ - Filenames containing `'` or `$` no longer break command execution — `%name`/`%dir`/`%file`/`%cmd` values are escaped for their quoting context when `build` wraps them in double quotes (shell and Ruby `!` modes), and `run` escapes `\`/`'` when the prefix format wraps `%s` in single quotes (`|`, `=`) so the `eval` parses; intentional `$` expansion in typed commands (`|echo $HOME`) still works
26
+ - Command editing prompts are fixed — `execute`, alt-edit in `open_file`, and command-history edits render a single `> ` prompt through `Line#cli` instead of the doubled `> > ` line `ask` produced, and the completion grid tints command candidates with `cmd_name` (red) and options with `cmd_options` (blue)
27
+ - `%name` joins array ids with commas in `!` (Ruby) commands so multi-select builds to valid argument lists; shell commands keep space-joined quoting
28
+ - Conflict picker drains stale input on open and the transfer shows its end tally with a keypress pause, so a buffered confirm can neither auto-answer overwrite nor vanish unseen
29
+ - Commands spawned right after an auto-confirmed pick start with the terminal input queue drained, so keys overtyped at the selection cannot reach the editor or the launched window
30
+
31
+ ## [v0.4.1] — 2026-09-11
32
+
5
33
  ## [v0.4.0] — 2026-09-05
6
34
 
7
35
  ### Added
@@ -20,6 +48,9 @@
20
48
  - `cd` owns the path column flag — array `cd` sets `@show_path` (single dirs clear it), so `reset_view` preserves the path listing after `execute`/`recurse` instead of dropping it; `recurse` just builds the recursive directory array and delegates the loading to `cd`
21
49
  - Array `cd` fills the path column after the full load — previously only the first directory's rows got values (the rest stayed blank on `reset_view`), and custom Hash views dropped the column entirely; the trailing `change_view` now runs once on complete data, and computed views keep PATH too
22
50
  - Clickable header path — left-clicking the username goes home and each directory segment navigates there, while right-click opens the command list for that path without leaving the current directory
51
+ - Alt+click opens the command list — Alt+left-click on a row or header path behaves like right-click, for touchscreens where right-click is hard to send (Termux)
52
+ - Failed `cd` keeps history and the path flag — directory history, selection clearing, and `@show_path` only commit after a successful navigation, and array loads skip unreadable entries up front
53
+ - Base colors definable in config — the eight basic names (`black`..`white`) accept Integer/RGB values under `colors:`, shipped mirroring `.Xresources` color0-7 with the terminal default pair; commenting them out restores terminal colors
23
54
  - A binding picked in help now runs — `fly` re-dispatches a String return from `send` (as it already did for mouse clicks), so pressing `h` and clicking e.g. `[R]ecurse` executes that action instead of just closing the help page
24
55
  - The help text panel sits flush with the terminal edge — the text panel's `right: -2` border now draws on the last column, matching the browser edge; the previous inset was caused by an off-by-one in the `draw_border` guard
25
56
 
@@ -61,12 +92,11 @@
61
92
  - `open_current` (`` ` ``) — opens the directory under the cursor in its default app
62
93
  - Full-path filenames: `cd` stores absolute paths in `@data`; `@procs[NAME]` displays basenames via `File.basename`; `build` uses `@data[id][NAME]` directly (no `@directory` prefix)
63
94
  - `@directory` accepts an Array (for multi-path listings like `execute`); `draw` joins Array entries with `": "` for the status display
64
- - `frequency_limit` config var (default 10) — only the top N most-used entries get frequency coloring in history popups, listed frequency-first then most-recent
95
+ - `top_history` config var (default 10) — only the top N most-used entries get frequency coloring in history popups, listed frequency-first then most-recent
65
96
  - `filter_dirs` config flag (default true) — active filters also hide directories that don't match
66
97
  - Shell history importer — `import_shell_history` reads `~/.bash_history`/`~/.zsh_history` once at startup, validates commands against `$PATH` and built-ins, resolves aliases from `.zshrc`/`.bashrc`, and merges new entries into `cmd_history`; a `shell_sync` flag in the state dir prevents re-import
67
98
  - `open_file` status line now shows the picked command name first in `colors.commands` colors — `> name:display : output` (name in `colors.commands[0]`, the built command in `colors.commands[1]`; default `["green", "red"]`); the command picker popup uses the same configurable colors
68
99
  - `build` prompts for `%dir`/`%file`/`%str` prepend the colored command `name:` prefix, so the partial command stays identifiable while being filled in
69
- - `term_hold` config — hold flag appended to `@term` at init (e.g. `foot --hold`)
70
100
 
71
101
  ### Changed
72
102
  - Config location moved to `~/.config/filecon/config.yaml` — on first run the bundled `config.yaml` is copied there; state/history remain under `~/.local/share/filecon/`; string-keyed `colors:` settings (as loaded from YAML) are now honored
data/README.md CHANGED
@@ -4,12 +4,12 @@
4
4
 
5
5
  ## Description
6
6
 
7
- Filecon is a terminal-based file manager built on the [Typr](https://codeberg.org/typr/typr) terminal UI toolkit. Every file, command, and directory is reachable in a few keystrokes, and the most common operations take the fewest key presses.
7
+ Filecon is a file and command manager that lives between terminal and shell, built on the [Typr](https://codeberg.org/typr/typr) terminal UI toolkit. Every file, command, and directory is reachable in a few keystrokes, and the most common operations take the fewest key presses.
8
8
 
9
- Filecon is designed to be **intuitive**, **complete**, **powerful**, and the **fastest possible** file manager. The whole interface is built around that principle:
9
+ Filecon is designed to be **intuitive**, **complete**, **powerful**, and **fast**. The whole interface is built around that principle:
10
10
 
11
11
  - **Hint navigation** — every visible row carries a number hint (1–9, 0); pressing it acts on that row immediately, no cursor movement needed.
12
- - **Live search in popups** — command and history popups open in `/` search mode; type to distill the list, then pick one of the few remaining results with a number.
12
+ - **Live search in popups** — command and history popups open in `/` search mode; type to distill the list, then pick one of the few remaining results with a number. return select the first. auto selection of a single filter result.
13
13
  - **Frequency-ordered history** — directory and command history are ranked by use count, so the most likely choice is always near the top.
14
14
 
15
15
  Filecon runs on Typr, whose self-contained terminal layer ships its own precompiled terminfo key table and MIME-type database in the gem — no ncurses or external tooling required — so Filecon works on any raw, ANSI-capable terminal (including Termux).
@@ -17,14 +17,14 @@ Filecon runs on Typr, whose self-contained terminal layer ships its own precompi
17
17
  ## Features
18
18
 
19
19
  - **Directory browsing with MIME-type detection** — files are typed from a packaged MIME database and colored accordingly, with optional `file --mime-type` magic detection for unknowns
20
- - **Fastest possible navigation** — hint numbers (1–9, 0) open rows directly; TAB pages hints beyond the first ten
20
+ - **Fast navigation** — hint numbers (1–9, 0) open rows directly; TAB pages hints beyond the first ten
21
21
  - **Default `[/]:search` in popups** — command and history popups open in search mode: start typing to distill the list down to the matches, then press a number hint to pick from the few remaining results. The listing itself searches the same way
22
22
  - **Mouse support** — click a row to open it with its default action, right-click to open its command list, click a binding on the bottom bar or a filter/sort/views column to run that action; the mouse wheel scrolls the listing (pointer-only mode is planned)
23
23
  - **Command execution modes** — commands run in one of four ways, chosen by prefix: `|` status line, `=` text viewer, `+` new terminal window, `!` Ruby eval; per-MIME-type command sets with `%name`, `%dir`, `%file`, `%cmd`, `%str` substitution
24
24
  - **Customizable column views** — view columns run shell commands with `%name` substitution (content preview, char/word/line counts, type info, disk usage, man-page descriptions)
25
- - **Directory and command history with a frequency counter** — entries stored as `[count, entry]` pairs, most-used first then most-recent; `frequency_limit` controls how many frequent entries lead the list
25
+ - **Directory and command history with a frequency counter** — entries stored as `[count, entry]` pairs, most-used first then most-recent; `top_history` controls how many most-used entries lead the list
26
26
  - **Shell history import** — on first run, `~/.bash_history` and `~/.zsh_history` are validated, alias-resolved, and merged into the command history; directory paths referenced by those commands are collected into the directory history
27
- - **Power browsing** — recursive directory listing, `execute` ($PATH browser), marking, filtering, sorting, and switchable column views
27
+ - **Power browsing** — recursive directory listing, `executables` ($PATH browser), typing a command line to run (`execute`), marking, filtering, sorting, and switchable column views
28
28
  - **Fully documented configuration** — a self-documenting `config.yaml` covers colors, commands, views, history, and terminal settings
29
29
 
30
30
  ## Installation
@@ -49,7 +49,8 @@ filecon [directory]
49
49
 
50
50
  **Actions**
51
51
  - `c` — Command history
52
- - `x` — List all executables from $PATH directories
52
+ - `e` — List all executables from $PATH directories
53
+ - `x` — Type a command line and run it in the shell (TAB completion)
53
54
  - `h` — Help
54
55
  - `v` — Views
55
56
  - `s` — Sort view
@@ -76,7 +77,7 @@ filecon [directory]
76
77
 
77
78
  ### Mouse
78
79
 
79
- Click a row to open it with its default action, right-click to open its command list; click a binding label on the bottom bar or a filter/sort/views column on the head to run that action; the mouse wheel scrolls the listing. Clicking outside a popup dismisses it. `h` shows a two-pane help with the keybinding grid (from the line's `list`) beside the help text — clicking a binding row returns its trigger key, and clicking outside both panes closes it. Mouse reporting is enabled automatically when Filecon starts.
80
+ Click a row to open it with its default action, right-click (or Alt+click) to open its command list; click a binding label on the bottom bar or a filter/sort/views column on the head to run that action; the mouse wheel scrolls the listing. Clicking outside a popup dismisses it. `h` shows a two-pane help with the keybinding grid (from the line's `list`) beside the help text — clicking a binding row returns its trigger key, and clicking outside both panes closes it. Mouse reporting is enabled automatically when Filecon starts.
80
81
 
81
82
  ### Popups and search
82
83
 
@@ -86,17 +87,17 @@ Command and history popups open in `/` search mode. Type a few characters to **d
86
87
 
87
88
  Config is stored at `~/.config/filecon/config.yaml` (YAML). On first run the bundled, fully documented `config.yaml` is copied there. It includes:
88
89
 
89
- - Color settings (named colors, 8-bit palette, RGB triplets) — including the `colors.commands` pair used by the command picker and status line
90
+ - Color settings (named colors, 8-bit palette, RGB triplets, base-palette overrides) — including the `colors.commands` pair used by the command picker and status line
90
91
  - Command definitions per MIME type, with execution-mode prefixes
91
92
  - View configurations (columns that run shell commands)
92
- - History limits and terminal settings
93
+ - History limits
93
94
 
94
95
  State and history files live under `~/.local/share/filecon/`.
95
96
 
96
97
  **Command execution modes:**
97
98
 
98
99
  - `|` — pipe output to the status line
99
- - `+` — run in a new terminal window
100
+ - `+` — run in a new terminal window (in its own process group; the browser stays responsive)
100
101
  - `=` — display output in the text viewer
101
102
  - `!` — evaluate as Ruby code
102
103
 
data/bin/filecon CHANGED
@@ -25,9 +25,6 @@ begin
25
25
 
26
26
  config = YAML.safe_load(File.read(config_path), permitted_classes: [Symbol]) || {}
27
27
 
28
- term = config["term"] || ENV["TERM"] || ""
29
- term += config["term_hold"] if config["term_hold"]
30
-
31
28
  Typr.init config["colors"]["default"]
32
29
  Typr.clear
33
30
 
@@ -40,9 +37,10 @@ begin
40
37
  colors: config["colors"] || {},
41
38
  views: config["views"] || {},
42
39
  commands: config["commands"] || {},
43
- term: term,
40
+ prefixes: config["prefixes"],
41
+ bindings: config["bindings"],
44
42
  max_history: config["max_history"] || 100,
45
- frequency_limit: config["frequency_limit"] || 10,
43
+ top_history: config["top_history"] || 10,
46
44
  filter_dirs: config["filter_dirs"] )
47
45
  Typr.on_resize { app.show }
48
46
  app.import_shell_history
data/config.yaml CHANGED
@@ -2,28 +2,18 @@
2
2
  # Filecon Configuration
3
3
  # =============================================================================
4
4
  #
5
- # This is the default configuration file for Filecon, a terminal file manager.
5
+ # This is the default configuration file for Filecon, a file and command manager that lives between terminal and shell.
6
6
  #
7
7
  # All settings are optional. Filecon will use sensible defaults if values
8
8
  # are not specified.
9
9
  #
10
10
  # =============================================================================
11
11
 
12
- # Terminal settings
13
- # -----------------
14
- # The terminal emulator to use for Filecon's interface.
15
- # Defaults to $TERM environment variable if not set.
16
- # term: "kitty"
17
-
18
- # Suffix appended to term for opening files in a new window.
19
- # Example: if term is "foot", term_hold becomes "foot --hold"
20
- # term_hold: " --hold"
21
-
22
12
  # Maximum number of history entries to retain.
23
13
  # Older entries beyond this limit are automatically pruned.
24
14
  max_history: 10000
25
15
 
26
- frequency_limit: 10
16
+ top_history: 6
27
17
  # When true, active filters also hide directories that don't match.
28
18
  filter_dirs: true
29
19
 
@@ -43,6 +33,10 @@ filter_dirs: true
43
33
  # - Grey shades: grey0 through grey100 (e.g., grey62 for 62% grey)
44
34
  # - 8-bit palette: 0 - 255 (maps to terminal's 256-color palette)
45
35
  # - RGB triplets: [0-255, 0-255, 0-255] (true color terminals)
36
+ # - Base palette: redefine black, red, green, yellow, blue, magenta,
37
+ # cyan, white to override the terminal palette;
38
+ # comment these lines out (or delete them) to use
39
+ # the terminal colors instead
46
40
  #
47
41
  # Color settings passed to Typr spaces:
48
42
  # default global default foreground/background [fg, bg]
@@ -53,9 +47,14 @@ filter_dirs: true
53
47
  # selected highlighted / selected row background
54
48
  # alternate alternating row background in lists
55
49
  # hints hint numbers/digits
56
- # actions named action colors: "[f]ilter", "[s]ort", "[v]iews"
50
+ # actions named action colors: filter, sort, views
57
51
  # commands command picker and status line: [name, args] colors
58
52
  # search search-match highlight (text viewer)
53
+ # dir_history directory history picker: [name, path] columns
54
+ # cmd_history command history picker: [prefix, command] columns
55
+ # top_history top-history field background color
56
+ # help help text title color
57
+ # help_border help text border: [fg, bg]
59
58
  #
60
59
  # Usage:
61
60
  # Foreground color only: "blue"
@@ -64,44 +63,97 @@ filter_dirs: true
64
63
  # =============================================================================
65
64
 
66
65
  colors:
67
- default: ["white", "black"]
68
- # [name, args] colors for the command picker and the status line
69
- # shown while building/running a command
70
- commands: ["green", "red"]
66
+ # Base palette (comment out to use terminal colors)
67
+ black: [21, 21, 21]
68
+ red: [172, 65, 66]
69
+ green: [126, 141, 80]
70
+ yellow: [229, 181, 102]
71
+ blue: [108, 153, 186]
72
+ magenta: [158, 78, 133]
73
+ cyan: [125, 213, 207]
74
+ white: [208, 208, 208]
75
+
76
+ default: [white, black]
77
+ commands: [green, red]
71
78
  types:
72
- image: "blue"
73
- audio: "green"
74
- video: "cyan"
75
- directory: "yellow"
76
- "x-pie-executable": "red"
77
- # executable: "red"
79
+ image: blue
80
+ audio: green
81
+ video: cyan
82
+ directory: yellow
83
+ executable: red
84
+ actions:
85
+ filter: red
86
+ sort: blue
87
+ views: green
88
+ dir_history: [yellow, brown]
89
+ cmd_history: [red, blue]
90
+ top_history: grey20
91
+ help: yellow
92
+ help_border: [white, grey10]
78
93
 
79
94
 
80
95
  # =============================================================================
81
- # COMMANDS
96
+ # PREFIXES
82
97
  # =============================================================================
83
98
  #
84
- # Define commands available for different file types. Each file type (MIME type
85
- # category) maps to a set of named commands that can be executed on files of
86
- # that type.
99
+ # Define command prefixes for the execute prompt. Each prefix character maps to
100
+ # a format string. %s is replaced with the command body.
87
101
  #
88
- # COMMAND PREFIXES (execution type):
102
+ # Format strings starting with ! are evaluated as Ruby and their result is
103
+ # read; every other format is a terminal window, started in its own process
104
+ # group through the shell (so $TERM and friends expand from the environment)
105
+ # and left running while the browser keeps going.
106
+ #
107
+ # =============================================================================
108
+
109
+ prefixes:
110
+ "!": "!StringIO.new((%s).to_s)"
111
+ "|": "!open('|%s 2>&1')"
112
+ "+": "$TERM --hold %s"
113
+ "=": "!Text.new(top: 1, right:-1, bottom:-2, header: '%s', input: open('|%s 2>&1')).pick :none"
114
+
115
+ # =============================================================================
116
+ # BINDINGS
117
+ # =============================================================================
89
118
  #
90
- # | Pipe output to status line
91
- # The command's stdout is displayed in the status bar at the bottom.
92
- # Example: "|wc -l %name" shows line count in status
119
+ # Key bindings for the status bar. Each binding maps a label (with the key
120
+ # character in brackets) to either `!method` — a method call, dispatched
121
+ # directly — or any prefixed command, which is substituted with `build` and run
122
+ # like a command from the file list (`%name` resolves to the current directory).
123
+ # Commands from bindings are never added to the command history.
93
124
  #
94
- # = Display output in text area
95
- # The command's output replaces the file listing with a text view.
96
- # Example: "=file %name" shows file info in the text area
125
+ # Only printable single characters work as key bindings.
126
+ # Non-printable keys (arrows, escape, return, etc.) are hardcoded.
97
127
  #
98
- # + Run in new terminal window
99
- # Opens a new terminal window to run the command.
100
- # Example: "+less %name" opens a pager in a new window
128
+ # =============================================================================
129
+
130
+ bindings:
131
+ "[h]elp": "!help"
132
+ "[/]:search": "!search"
133
+ "[f]ilter": "!filter_view"
134
+ "[s]ort": "!sort_view"
135
+ "[v]iews": "!views"
136
+ "[r]eset": "!reset_view"
137
+ "[d]irectories": "!dir_history"
138
+ "[c]ommands": "!cmd_history"
139
+ "[e]xecutables": "!executables"
140
+ "e[x]ecute": "!execute"
141
+ "[R]ecurse": "!recurse"
142
+ "[m]ark": "!mark"
143
+ "[M]ark_range": "!mark_range"
144
+ "mark_[a]ll": "!mark_all"
145
+ "[i]nvert_mark": "!mark_invert"
146
+ "[~]:home": "!home"
147
+ "[H]idden_files": "!toggle_hidden"
148
+ "[`]:open_current": "!open_current"
149
+
150
+ # =============================================================================
151
+ # COMMANDS
152
+ # =============================================================================
101
153
  #
102
- # ! Evaluate as Ruby code
103
- # The command string is evaluated as Ruby code.
104
- # Example: "!cd %name" executes Ruby's cd command
154
+ # Define commands available for different file types. Each file type (MIME type
155
+ # category) maps to a set of named commands that can be executed on files of
156
+ # that type.
105
157
  #
106
158
  # SUBSTITUTION VARIABLES:
107
159
  #
@@ -119,43 +171,59 @@ colors:
119
171
  #
120
172
  # SPECIAL CATEGORIES:
121
173
  #
174
+ # Command groups for a file are collected in this order:
175
+ # printable, executable, subtype, type, default.
176
+ #
122
177
  # executable - Added to the command picker for any file whose
123
178
  # executable bit is set for the current user, regardless
124
179
  # of its MIME type (e.g. shell scripts with shebangs).
125
180
  #
181
+ # printable - Added to the command picker for any file whose content
182
+ # is printable text (no NUL byte, valid UTF-8 in the first
183
+ # 8KB), regardless of its MIME type.
184
+ #
126
185
  # =============================================================================
127
186
 
128
187
  commands:
129
188
  default:
130
189
  # copy: "|rsync --progress %name %dir"
131
190
  info: "=file %name"
132
- view: "+moor %name"
133
- edit: "+micro %name"
134
- copy: "|cp -R %name %dir"
135
- move: "|mv %name %dir"
191
+ symlink: "ln -s %name %dir"
192
+ copy: "!transfer(:copy, %name, %dir)"
193
+ move: "!transfer(:move, %name, %dir)"
194
+ # copy_shell: "|cp -R %name %dir"
195
+ # move_shell: "|mv %name %dir"
136
196
  rename: "|mv %name %str"
137
197
  delete: "|rm -rf %name"
138
198
  open_with: "|%cmd %name %str"
139
- open_with_terminal: "+%cmd %name %str"
199
+ # open_with_terminal: "+%cmd %name %str"
140
200
  xdg_open: "|xdg-open %name"
141
201
 
142
202
  executable:
143
- run: "|%name %str"
144
- run_terminal: "+%name %str"
203
+ whatis: "=whatis %name"
145
204
  help: "=%name --help"
146
205
  manual: "+man %name"
206
+ run: "|%name %str"
207
+ run_terminal: "+%name %str"
208
+
209
+ printable:
210
+ edit: "+micro %name"
211
+ view: "=cat %name"
212
+ count: "|wc -l %name | cut -wf1"
213
+ search: "=grep -n %str %name"
147
214
 
148
215
  directory:
149
216
  cd: "!cd %name"
150
217
  new_file: "|touch %name/%str"
151
218
  new_dir: "|mkdir %name/%str"
152
- shell: "+$SHELL -c 'cd %name && exec $SHELL'"
219
+ shell: "+$SHELL "
220
+ # shell: "+$SHELL -c 'cd %name && exec $SHELL'"
153
221
  # zsh: "+zsh -c 'cd %name && exec zsh'"
154
222
  disk_use: "|du -hs %name | cut -wf1"
155
223
  play: "+mpv %name"
156
224
 
157
- text:
158
- count: "|wc -l %name | cut -wf1"
225
+ # text:
226
+ # count: "|wc -l %name | cut -wf1"
159
227
 
160
228
  troff:
161
229
  manual: "+man %name"
@@ -262,7 +330,7 @@ commands:
262
330
  # Prefix ! to evaluate as Ruby
263
331
  #
264
332
  # Available system fields:
265
- # target, permissions, executable, owner, group, size,
333
+ # target, permissions, executable, printable, owner, group, size,
266
334
  # accessed, modified, created, type, subtype
267
335
  #
268
336
  # When a view is activated, its columns are appended to the default file
data/filecon.1 CHANGED
@@ -1,12 +1,13 @@
1
- .TH FILECON 1 "2026-07-27" "filecon 0.1.1" "User Commands"
1
+ .TH FILECON 1 "2026-09-30" "filecon 0.5.0" "User Commands"
2
2
  .SH NAME
3
- filecon \- interactive terminal file manager with hint-based navigation
3
+ filecon \- a file and command manager that lives between terminal and shell
4
4
  .SH SYNOPSIS
5
5
  .B filecon
6
6
  .RI [ directory ]
7
7
  .SH DESCRIPTION
8
8
  .B filecon
9
- is a terminal-based file manager built on top of the
9
+ is a file and command manager that lives between terminal and shell, built
10
+ on top of the
10
11
  .B Typr
11
12
  Ruby library. It displays directory entries in a grid with columns for name,
12
13
  symlink target, permissions, owner, group, size, timestamps, and MIME type.
@@ -75,10 +76,20 @@ Quit
75
76
  .TP
76
77
  .B c
77
78
  Browse command history
79
+ .TP
80
+ .B e
81
+ List all executables from all PATH directories, showing each binary's
82
+ containing directory in the
83
+ .B path
84
+ column
85
+ .TP
86
+ .B x
87
+ Type a command line and run it in the shell (TAB completes
88
+ commands, arguments, and files from the shell configuration)
78
89
  .PP
79
90
  Directory and command history popups list the most frequently used entries
80
91
  first, then the remaining entries in most-recent order. The
81
- .B frequency_limit
92
+ .B top_history
82
93
  setting in config.yaml (default 10) controls how many frequent entries lead
83
94
  the list.
84
95
  .SH CONFIGURATION
@@ -89,7 +100,7 @@ On first run, the default
89
100
  .SS Config File Locations
90
101
  .TP
91
102
  .B ~/.config/filecon/config.yaml
92
- Main configuration (colors, commands, views, term)
103
+ Main configuration (colors, commands, views)
93
104
  .TP
94
105
  .B ~/.config/filecon/dir_history
95
106
  Directory navigation history
@@ -102,7 +113,7 @@ Command execution history
102
113
  Pipe output to status line
103
114
  .TP
104
115
  .B +
105
- Run in a new terminal window
116
+ Run in a new terminal window, in its own process group, so the browser stays responsive
106
117
  .TP
107
118
  .B =
108
119
  Display output in the text viewer
@@ -138,7 +149,8 @@ Custom views can be defined in config.yaml by adding entries to the views hash.
138
149
  .SH ENVIRONMENT
139
150
  .TP
140
151
  .B TERM
141
- Terminal type used for terminal initialization.
152
+ Terminal type used for terminal initialization, and expanded by the shell in
153
+ prefix formats that open a terminal window.
142
154
  .TP
143
155
  .B HOME
144
156
  Used to resolve ~ and config directory path (~/.config/filecon/).
@@ -1,3 +1,3 @@
1
1
  module Filecon
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
data/lib/filecon.rb CHANGED
@@ -4,39 +4,82 @@ rescue LoadError
4
4
  require "typr"
5
5
  end
6
6
 
7
+ require "fileutils"
8
+ require "find"
9
+
7
10
  require_relative "filecon/version"
8
11
 
9
12
  class Filecon::App < Typr::Browser
10
13
  include Typr
11
14
 
15
+ def directory
16
+ @directory.is_a?(Array) ? @directory.inject { |a, b|
17
+ common = []
18
+ a.split("/").zip(b.split("/")).each { |x, y|
19
+ break if x != y
20
+ common << x
21
+ }
22
+ common.join("/")
23
+ } : @directory
24
+ end
25
+
12
26
  def cd dir, append=false
13
27
  @selected[:rows] = [] unless append
14
28
  if dir.is_a? Array
15
29
  @show_path = true
16
- dirs = dir.filter_map { |d| d = File.expand_path(d.to_s); Dir.exist?(d) ? d : nil }
17
- dirs.each_with_index { |d, i|
18
- @directory = d if i.positive?
19
- Dir.chdir(d) if i.positive?
20
- super d, i.positive?
30
+ dirs = dir.filter_map { |d| d = File.expand_path(d.to_s);
31
+ Dir.exist?(d) && File.readable?(d) && File.executable?(d) ? d : nil }
32
+ prefix = dirs.inject { |a, b|
33
+ common = []
34
+ a.split("/").zip(b.split("/")).each { |x, y|
35
+ break if x != y
36
+ common << x
37
+ }
38
+ common.join("/")
21
39
  }
40
+ prefix = "/" if prefix.empty?
41
+ dirs.each_with_index { |d, i| super d, i.positive?, prefix.size }
22
42
  @directory = dirs
43
+ Dir.chdir(prefix)
23
44
  change_view @view if dirs.any?
24
45
  nil
25
46
  else
26
- @show_path = false unless append
27
47
  dir = @dir_history[dir+1][1] if dir.is_a? Integer
28
- record :dir, File.expand_path(dir) unless append || [?/, ENV["HOME"]].include?(dir)
29
- super dir, append
48
+ saved = @show_path
49
+ @show_path = false unless append
50
+ if ( super dir, append ) == false
51
+ @show_path = saved
52
+ else
53
+ record :dir, File.expand_path(dir) unless append || [?/, ENV["HOME"]].include?(dir)
54
+ end
30
55
  nil
31
56
  end
32
57
  end
33
58
 
34
- def execute; cd ENV["PATH"].split(":") end
59
+ def execute
60
+ cmd = @user.cli("> ", redraw: method(:show),
61
+ history: @cmd_history.map { |_, cmd| cmd },
62
+ prefixes: @prefixes&.keys&.join)
63
+ return unless cmd
64
+ record :cmd, cmd unless cmd[0..2] == '!cd'
65
+ io = run cmd
66
+ if io and @prefixes&.[](cmd[0].to_s)&.start_with? '!'
67
+ output = io.read.to_s.scrub.strip
68
+ nc = (@colors[:commands] || [:green, :red]).first
69
+ @user.show "> #{color_code(nc)}#{cmd[1..-1]}#{color_code(@colors[:default])} : #{output.lines.last}"
70
+ show
71
+ Typr.read_key
72
+ else
73
+ show
74
+ end
75
+ end
76
+
77
+ def executables; cd ENV["PATH"].split(":") end
35
78
 
36
79
  def pick type='file', **kw
37
80
  return super unless type == :cmd
38
81
  dir = @directory
39
- execute
82
+ executables
40
83
  result = super
41
84
  cd dir
42
85
  result
@@ -46,7 +89,11 @@ class Filecon::App < Typr::Browser
46
89
  return unless id
47
90
  id = @views.keys[id] if id.is_a? Integer
48
91
  view = @views[id]
49
- return super if view.is_a? Array
92
+ if view.is_a? Array
93
+ super
94
+ return
95
+ end
96
+ Typr.flush_overtype
50
97
  data = @data.map.with_index{ |row, id| row + view.values.map{ |cmd|
51
98
  next unless cmd.is_a?(String)
52
99
  built = build cmd, id
@@ -65,10 +112,7 @@ class Filecon::App < Typr::Browser
65
112
  @header.index(head.to_s) || (@header += [head.to_s]; @header.count-1) }
66
113
  clear
67
114
  self << data
68
- if @show_path
69
- @data.each { |row| row[PATH] = File.dirname(row[NAME]) } if @data.any?
70
- @sequence.insert(@sequence.index(NAME) || 0, PATH) unless @sequence.include?(PATH)
71
- end
115
+ @sequence.insert(0, PATH) if @show_path
72
116
  @current = id
73
117
  @view = id
74
118
  end
@@ -82,9 +126,9 @@ class Filecon::App < Typr::Browser
82
126
  eval id.map{ |id| @data[id].values_at(SUBTYPE,TYPE).to_s }.join(?&)
83
127
  else @data[id].values_at(SUBTYPE, TYPE)
84
128
  end - [??] + ["default"]
85
- if id && (id.is_a?(Array) ? id.all?{ |i| @data[i][EXECUTABLE] } : @data[id][EXECUTABLE])
86
- types.insert(types.index("default") || types.size, "executable")
87
- end
129
+ special = { printable: PRINTABLE, executable: EXECUTABLE }.filter_map { |f, col|
130
+ f.to_s if id && (id.is_a?(Array) ? id.all?{ |i| @data[i][col] } : @data[id][col]) }
131
+ types = special + types
88
132
 
89
133
  commands = @commands.values_at( *types ).map(&:to_a).flatten( 1 ).uniq
90
134
  alt = false
@@ -107,9 +151,11 @@ class Filecon::App < Typr::Browser
107
151
  cmd = build pair.last, id, pair.first
108
152
  return unless cmd
109
153
  if alt
110
- edited = Typr.read_line "> ", left: left, top: @user.top, initial: cmd, completions: true, redraw: method(:show)
154
+ edited = @user.cli("> ", initial: cmd, redraw: method(:show),
155
+ prefixes: @prefixes&.keys&.join)
111
156
  return unless edited
112
- cmd = edited
157
+ cmd = build(edited, id, pair.first)
158
+ return unless cmd
113
159
  end
114
160
  if cmd[0] == ?|
115
161
  display = cmd[1..-1]
@@ -154,47 +200,133 @@ class Filecon::App < Typr::Browser
154
200
  @selected[:rows] = []
155
201
  end
156
202
 
203
+ def transfer op, *args
204
+ dest, files = args.pop, args
205
+ return unless dest and files.any?
206
+ if files.any? { |f| dest.start_with?( f + ?/ ) }
207
+ @user.show "cannot #{op} into itself"
208
+ return Typr.read_key
209
+ end
210
+ pairs = files.flat_map { |src|
211
+ next [[src, File.join(dest, File.basename(src))]] unless
212
+ File.directory?( src ) and !File.symlink?( src )
213
+ [[src, File.join(dest, File.basename(src))]] +
214
+ Find.find( src ).filter_map { |path|
215
+ [path, File.join(dest, File.basename(src), path[(src.length + 1)..-1])] unless path == src }
216
+ }
217
+ policy, done, skipped, failed = nil, 0, 0, 0
218
+ pairs.each_with_index do |(src, dst), i|
219
+ @user.show "#{op} #{i + 1}/#{pairs.size}: #{File.basename(src)}"
220
+ exists = File.exist?( dst ) || File.symlink?( dst )
221
+ action = exists ? policy : nil
222
+ while action.nil? and exists
223
+ action, policy = resolve_conflict( src, dst )
224
+ if action.is_a?( String )
225
+ dst = action
226
+ action = :overwrite
227
+ end
228
+ end
229
+ action = case action ||= :overwrite
230
+ when :older then (File.mtime(dst) < File.mtime(src) rescue false) ? :overwrite : :skip
231
+ when :smaller then (File.size(dst) < File.size(src) rescue false) ? :overwrite : :skip
232
+ when :size_differs then (File.size(dst) != File.size(src) rescue false) ? :overwrite : :skip
233
+ else action end
234
+ case action
235
+ when :abort; break
236
+ when :skip; skipped += 1
237
+ else
238
+ dst = action if action.is_a?( String )
239
+ if File.directory?( src ) and !File.symlink?( src )
240
+ FileUtils.mkdir_p( dst )
241
+ elsif op == :move
242
+ FileUtils.mv( src, dst )
243
+ else
244
+ FileUtils.cp( src, dst )
245
+ end
246
+ done += 1
247
+ end
248
+ rescue SystemCallError
249
+ failed += 1
250
+ end
251
+ if op == :move
252
+ files.select { |f| File.directory?( f ) and !File.symlink?( f ) }.each do |d|
253
+ Find.find( d ).to_a.sort.reverse.each { |p| Dir.rmdir( p ) rescue nil }
254
+ end
255
+ end
256
+ tally = "#{op}: #{done} done, #{skipped} skipped" + ( failed > 0 ? ", #{failed} failed" : "" )
257
+ @user.show tally
258
+ show
259
+ Typr.read_key
260
+ tally
261
+ end
262
+
263
+ def resolve_conflict src, dst
264
+ Typr.drain_input
265
+ info_top = top + 1
266
+ info = Grid.new(left:left+1, top:info_top, right:-1, bottom:info_top+2, header:"file exists. overwrite?", input:[["from", File.basename(src), (File.size(src) rescue 0), (File.mtime(src).to_i rescue 0)], ["to", File.basename(dst), (File.size(dst) rescue 0), (File.mtime(dst).to_i rescue 0)]], format:[:min,:min,:min,:min], border: :light, alternate:false, procs:{2=>:magnitudes, 3=>proc{|s| Time.at(s).strftime("%Y-%m-%d %H:%M:%S") rescue "?" }}, colors:{columns:@colors[:commands] || [:green, :red]})
267
+ act_top = info.bottom + 2
268
+ choices = %w[skip overwrite older smaller size_differs rename]
269
+ picker = Grid.new(left:left+1, top:act_top, right:-1, bottom:[act_top+6, Typr.height].min, header:"alt-number remembers choice", input:choices, format:[:min], border: :light, alternate:false, colors:{columns:@colors[:commands] || [:green, :red]})
270
+ info.show
271
+ idx = picker.pick(:row, column:0)
272
+ show
273
+ return [:abort, nil] unless idx.is_a?(Integer)
274
+ mods = picker.modifier
275
+ choice = choices[idx]
276
+ if choice == "rename"
277
+ renamed = @user.ask("rename to: " => [:line, {initial: File.basename(dst)}])
278
+ renamed ? [File.join(File.dirname(dst), renamed), nil] : [:skip, nil]
279
+ else
280
+ sym = choice.to_sym
281
+ [sym, mods == :alt ? sym : nil]
282
+ end
283
+ end
284
+
157
285
  def build str, id, name=nil
158
286
  nc, ac = @colors[:commands] || [:green, :red]
159
287
  reset = color_code( @colors[:default] )
160
288
  prefix = name ? "#{color_code(nc)}#{name}#{color_code(ac)}:" : ""
161
289
  cmd = ""
162
290
  while str[/\%(name|cmd|dir|file|str)/]
163
- cmd += $`
164
- case $&
165
- when '%name'; cmd += ?"+( id.nil? ? @directory :
291
+ pre, tok, post = $`, $&, $'
292
+ cmd += pre
293
+ case tok
294
+ when '%name'; sep = cmd[0] == ?! ? '", "' : '" "'; esc = cmd[0] == ?! ? /[\\"$#]/ : /[\\"$`]/
295
+ cmd += ?"+( id.nil? ? @directory.gsub(esc){ |m| "\\#{m}" } :
166
296
  id.is_a?( Array ) ? id.map{ |id|
167
- @data[id][NAME] }.join('" "') :
168
- @data[id][NAME] )+?"
297
+ @data[id][NAME].gsub(esc){ |m| "\\#{m}" } }.join(sep) :
298
+ @data[id][NAME].gsub(esc){ |m| "\\#{m}" } )+?"
169
299
  when '%dir', '%file', '%cmd'
170
- @user.show "> #{prefix}#{cmd}#{$&}#{$'}#{reset}"
171
- result = pick( { '%dir' => :directory, '%file' => :file, '%cmd' => :cmd }[$&] )
300
+ @user.show "> #{prefix}#{cmd}\e[5m#{tok}\e[25m#{post}#{reset}"
301
+ result = pick( { '%dir' => :directory, '%file' => :file, '%cmd' => :cmd }[tok] )
172
302
  return unless result
173
- cmd += ?" + result + ?"
303
+ esc = cmd[0] == ?! ? /[\\"$#]/ : /[\\"$`]/
304
+ cmd += ?" + result.gsub(esc){ |m| "\\#{m}" } + ?"
174
305
  when '%str'
175
306
  prompt = cmd.empty? || cmd.end_with?(' ') ? cmd : "#{cmd} "
176
- query = Typr.read_line "> #{prefix}#{prompt}", left: left, top: @user.top
307
+ query = @user.ask("#{prefix}#{prompt}" => :line)
177
308
  return unless query
178
309
  cmd += query
179
310
  end
180
- str = str[($`+$&).size..-1]
311
+ str = post
181
312
  end
182
- cmd += $' || str
313
+ cmd += str
183
314
  return cmd
184
315
  end
185
316
 
186
317
  def run cmd=nil
187
318
  return unless cmd
188
319
  cmd = @cmd_history[cmd][1] if cmd.is_a? Integer
189
- case cmd[0]
190
- when ?! then io = StringIO.new eval( cmd[1..-1] ).to_s
191
- when ?| then io = open( cmd + " 2>&1" )
192
- when ?+ then io = open( ?| + @term +' '+ cmd[1..-1] + " 2>&1" )
193
- when ?= then Text.new(top: 2, right:-1, bottom:-2, border: :light,
194
- input:open( ?|+cmd[1..-1]+" 2>&1" ) ).pick :none
195
- else io = open( ?| + @term +' '+ cmd + " 2>&1" )
320
+ Typr.flush_overtype
321
+ fmt = @prefixes&.[](cmd[0].to_s)
322
+ if fmt
323
+ body = cmd[1..-1]
324
+ body = body.gsub(/[\\']/) { |m| "\\#{m}" } if fmt =~ /'[^']*%s/
325
+ result = fmt.gsub('%s') { body }
326
+ result.start_with?('!') ? eval(result[1..-1]) : IO.popen(result, pgroup: true)
327
+ else
328
+ IO.popen(cmd + " 2>&1")
196
329
  end
197
- return io
198
330
  end
199
331
 
200
332
  def cmd_history; history @cmd_history, :cmd end
@@ -205,7 +337,7 @@ class Filecon::App < Typr::Browser
205
337
  data = data.drop 1 unless is_cmd
206
338
  return unless data.any?
207
339
 
208
- top = data.max_by(@frequency_limit) { |cnt, _| cnt }
340
+ top = data.max_by(@top_history) { |cnt, _| cnt }
209
341
  top_set = top.filter_map { |_, e| e }.to_h { |e| [e, true] }
210
342
  ordered = top + data.reject { |_, e| top_set[e] }
211
343
 
@@ -214,12 +346,12 @@ class Filecon::App < Typr::Browser
214
346
  [File.basename(e), e]
215
347
  }
216
348
 
217
- fc = is_cmd ? [:red, :blue] : [:yellow, :brown]
349
+ fc = is_cmd ? (@colors[:cmd_history] || [:red, :blue]) : (@colors[:dir_history] || [:yellow, :brown])
218
350
  fields = {}
219
351
  ordered.each_with_index { |(cnt, e), i|
220
352
  next unless top_set[e]
221
- fields[[i,0]] = [fc[0], :grey20]
222
- fields[[i,1]] = [fc[1], :grey20]
353
+ fields[[i,0]] = [fc[0], @colors[:top_history] || :grey20]
354
+ fields[[i,1]] = [fc[1], @colors[:top_history] || :grey20]
223
355
  }
224
356
 
225
357
  picker = popup(input: pairs, format:[:min,:max],
@@ -230,10 +362,11 @@ class Filecon::App < Typr::Browser
230
362
 
231
363
  entry = ordered[n][1]
232
364
  if is_cmd and picker.modifier == :alt
233
- edited = Typr.read_line "> ", left: left, top: @user.top, initial: entry,
234
- completions: true, redraw: -> { picker.show }
365
+ edited = @user.cli("> ", initial: entry, redraw: -> { picker.show },
366
+ prefixes: @prefixes&.keys&.join)
235
367
  return unless edited
236
- entry = edited
368
+ entry = build(edited, nil)
369
+ return unless entry
237
370
  end
238
371
  record type, entry
239
372
 
@@ -328,26 +461,27 @@ class Filecon::App < Typr::Browser
328
461
 
329
462
  def help
330
463
  grid = @user.list
331
- y = color_code :yellow
464
+ y = color_code (@colors[:help] || :yellow)
332
465
  d = color_code @colors[:default]
333
- text = Text.new( input: "\n#{y}FILECON#{d} - a terminal file manager\n\n" \
466
+ text = Text.new( input: "\n#{y}FILECON#{d} - a file and command manager that lives between terminal and shell\n\n" \
334
467
  "#{y}HINTS:#{d}\n press a number (1-9,0) to open that file with its default action.\n" \
335
468
  " Alt+number opens the command list for that file instead.\n" \
336
469
  " Right-click a file to open its command list.\n" \
337
470
  " Mark files with m, then Return opens their command list.\n" \
338
471
  " TAB cycles pages of hints.\n\n" \
339
472
  "#{y}COMMANDS:#{d}\n" \
340
- " e[x]ecute browse all executables on your PATH\n" \
473
+ " [e]xecutables browse all executables on your PATH\n" \
474
+ " e[x]ecute type a command line and run it in the shell\n" \
341
475
  " [R]ecurse expand the current directory tree (subdirectories listed inline)\n" \
342
476
  " [d]irectories jump to a recently visited directory\n" \
343
477
  " [c]ommands re-run a recent command\n\n" \
344
478
  " Directory and command history popups list the most frequently used\n" \
345
- " entries first, then the rest in most-recent order. The frequency_limit\n" \
346
- " setting in config.yaml (default 10) controls how many frequent entries\n" \
479
+ " entries first, then the rest in most-recent order. The top_history\n" \
480
+ " setting in config.yaml (default 10) controls how many most-used entries\n" \
347
481
  " lead the list.\n\n" \
348
482
  "#{y}CONFIG:#{d}\n ~/.config/filecon/config.yaml\n",
349
483
  top: 2, right: -2, bottom: -3,
350
- colors: { border:[:white,:grey10] },
484
+ colors: { border: @colors[:help_border] || [:white, :grey10] },
351
485
  border: :light )
352
486
  text.left = -> { grid.right + 2 }
353
487
  loop do
@@ -375,18 +509,13 @@ class Filecon::App < Typr::Browser
375
509
 
376
510
  def initialize args={}
377
511
  super
512
+ $palette = @colors.filter_map { |k, v|
513
+ [k.to_sym, v] if %i[black red green yellow blue magenta cyan white].include?(k.to_s.to_sym) }.to_h
378
514
  @rawsort[NAME] = false
379
515
  @cmd_history ||= []
380
516
  @dir_history ||= []
381
517
  @path ||= {}
382
- @frequency_limit = args[:frequency_limit] || 10
383
- @keymap[:open_current] = ?`
384
- @keymap[:execute] = ?x
385
- @keymap[:recurse] = ?R
386
- @user.bindings.insert 7, "[R]ecurse"
387
- @user.bindings.insert 8, "[c]ommands"
388
- @user.bindings.insert 9, "e[x]ecute"
389
- @user.bindings.insert 11, "[`]:open_current"
518
+ @top_history = args[:top_history] || 10
390
519
  @user.reset
391
520
  end
392
521
 
@@ -404,20 +533,40 @@ class Filecon::App < Typr::Browser
404
533
  @directory = dir
405
534
  end
406
535
 
536
+ # The command of a prefixed binding picked by key or by clicking its label on
537
+ # the bottom bar, or nil when the input is neither.
538
+ def prefixed_binding key
539
+ if key.is_a? Typr::Mouse
540
+ return unless key.press? and key.left?
541
+ key = extract_key( @user&.data_at( key.x, key.y ) )
542
+ end
543
+ return unless key.is_a? String
544
+ act = @bindings&.find{ |label, _| extract_key( label ) == key }&.last
545
+ act if act and @prefixes&.key? act[0] and !act.start_with? '!'
546
+ end
547
+
407
548
  def fly
408
549
  loop do
409
550
  show
410
551
  draw_hints :rows
411
552
  key = Typr.read_key
412
553
  next unless key
554
+ if act = prefixed_binding( key )
555
+ cmd = build act, nil
556
+ next unless cmd
557
+ show
558
+ run cmd
559
+ next
560
+ end
413
561
  case key
414
562
  when KEY_ESCAPE; exit
415
563
  when Typr::Mouse
564
+ alt = key.alt?
416
565
  if key.press? and ( key.left? or key.right? ) and target = @head.path_at( key.x, key.y )
417
- key.right? ? open_path( target ) : cd( target )
566
+ ( key.right? or alt ) ? open_path( target ) : cd( target )
418
567
  else
419
568
  value = send( key )
420
- open_file( value, !key.right? ) if value.is_a?( Integer )
569
+ open_file( value, !( key.right? or alt ) ) if value.is_a?( Integer )
421
570
  end
422
571
  @user.reset
423
572
  else
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: filecon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kilian Reitmayr
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
10
+ date: 2026-09-30 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: typr
@@ -65,8 +65,9 @@ dependencies:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
67
  version: '13.0'
68
- description: A terminal-based file manager with customizable views, commands, and
69
- MIME-type based file operations, built on the Typr terminal UI toolkit.
68
+ description: A file and command manager that lives between terminal and shell, with
69
+ customizable views, commands, and MIME-type based file operations, built on the
70
+ Typr terminal UI toolkit.
70
71
  email:
71
72
  - reitmayr@protonmail.com
72
73
  executables:
@@ -103,5 +104,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
104
  requirements: []
104
105
  rubygems_version: 3.6.9
105
106
  specification_version: 4
106
- summary: Terminal file manager built on Typr
107
+ summary: A file and command manager that lives between terminal and shell
107
108
  test_files: []