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 +4 -4
- data/CHANGELOG.md +32 -2
- data/README.md +12 -11
- data/bin/filecon +3 -5
- data/config.yaml +119 -51
- data/filecon.1 +19 -7
- data/lib/filecon/version.rb +1 -1
- data/lib/filecon.rb +211 -62
- metadata +6 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5974a832fd4c71067467f52e319ccbc571cac95c740e04ff5324699c1ed9644e
|
|
4
|
+
data.tar.gz: 825ea4d8a0c3ceef5741b05fda14cf1fb323423db521ce4d4f73dec731d4f97d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
- `
|
|
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
|
|
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
|
|
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
|
-
- **
|
|
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; `
|
|
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, `
|
|
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
|
-
- `
|
|
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
|
|
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
|
-
|
|
40
|
+
prefixes: config["prefixes"],
|
|
41
|
+
bindings: config["bindings"],
|
|
44
42
|
max_history: config["max_history"] || 100,
|
|
45
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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:
|
|
73
|
-
audio:
|
|
74
|
-
video:
|
|
75
|
-
directory:
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
#
|
|
96
|
+
# PREFIXES
|
|
82
97
|
# =============================================================================
|
|
83
98
|
#
|
|
84
|
-
# Define
|
|
85
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
91
|
-
#
|
|
92
|
-
#
|
|
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
|
-
#
|
|
95
|
-
#
|
|
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
|
-
#
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
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
|
|
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-
|
|
1
|
+
.TH FILECON 1 "2026-09-30" "filecon 0.5.0" "User Commands"
|
|
2
2
|
.SH NAME
|
|
3
|
-
filecon \-
|
|
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
|
|
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
|
|
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
|
|
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/).
|
data/lib/filecon/version.rb
CHANGED
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);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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 =
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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}#{
|
|
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
|
|
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 =
|
|
307
|
+
query = @user.ask("#{prefix}#{prompt}" => :line)
|
|
177
308
|
return unless query
|
|
178
309
|
cmd += query
|
|
179
310
|
end
|
|
180
|
-
str =
|
|
311
|
+
str = post
|
|
181
312
|
end
|
|
182
|
-
cmd +=
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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(@
|
|
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 =
|
|
234
|
-
|
|
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
|
|
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
|
|
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
|
|
346
|
-
" setting in config.yaml (default 10) controls how many
|
|
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
|
|
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
|
-
@
|
|
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
|
+
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:
|
|
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
|
|
69
|
-
MIME-type based file operations, built on the
|
|
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:
|
|
107
|
+
summary: A file and command manager that lives between terminal and shell
|
|
107
108
|
test_files: []
|