filecon 0.4.1 → 0.5.1
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 +35 -2
- data/README.md +11 -10
- data/bin/filecon +3 -5
- data/config.yaml +117 -49
- data/filecon.1 +19 -7
- data/lib/filecon/version.rb +1 -1
- data/lib/filecon.rb +199 -56
- metadata +8 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 244a74bf9a48f8731545f30724284976ac03efaea2dd99696d29f6756b952e76
|
|
4
|
+
data.tar.gz: 161554ce9508b8ff066710d0757f339cadc4f46dde7f54ac28218509e82055b3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8409685b7f649c3f4b10b3a0a7eed1ef1ddac5052a374763238865c19ba821b6b5b1918f2d262d39e2b6721acc9def64fffc57bffa79e6763e94a0c0ee944fb5
|
|
7
|
+
data.tar.gz: 3af13651f23eda4eb2d74c2011402a959aeb981c31b0533af338c310a340ef2f0ad002d71870b254b92f5b964152c595f1a98e8a594f5f0597a6197384d92710
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [vx.y.z] — __DATE__
|
|
4
|
+
|
|
5
|
+
## [v0.5.1] — 2026-09-30
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- Filecon now requires typr `~> 1.6` because it uses the typr 1.6 printable-column constant and `Typr.drain_input`
|
|
9
|
+
|
|
10
|
+
## [v0.5.0] — 2026-09-30
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `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
|
|
14
|
+
- `%` 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
|
|
15
|
+
- 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
|
|
16
|
+
- 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
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- `%file`/`%dir`/`%cmd` substitution tokens blink on the command line while browsing for a pick
|
|
20
|
+
- `=` 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
|
|
21
|
+
- `e[x]ecute` (PATH browser) is now `[e]xecutables` on `e`, freeing `x` for the new command-line runner
|
|
22
|
+
- 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)
|
|
23
|
+
- 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
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- 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
|
|
27
|
+
- 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
|
|
28
|
+
- 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
|
|
29
|
+
- 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
|
|
30
|
+
- 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
|
|
31
|
+
- 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)
|
|
32
|
+
- `%name` joins array ids with commas in `!` (Ruby) commands so multi-select builds to valid argument lists; shell commands keep space-joined quoting
|
|
33
|
+
- 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
|
|
34
|
+
- 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
|
|
35
|
+
|
|
3
36
|
## [v0.4.1] — 2026-09-11
|
|
4
37
|
|
|
5
38
|
## [v0.4.0] — 2026-09-05
|
|
@@ -22,6 +55,7 @@
|
|
|
22
55
|
- 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
|
|
23
56
|
- 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)
|
|
24
57
|
- 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
|
|
58
|
+
- 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
|
|
25
59
|
- 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
|
|
26
60
|
- 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
|
|
27
61
|
|
|
@@ -63,12 +97,11 @@
|
|
|
63
97
|
- `open_current` (`` ` ``) — opens the directory under the cursor in its default app
|
|
64
98
|
- 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)
|
|
65
99
|
- `@directory` accepts an Array (for multi-path listings like `execute`); `draw` joins Array entries with `": "` for the status display
|
|
66
|
-
- `
|
|
100
|
+
- `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
|
|
67
101
|
- `filter_dirs` config flag (default true) — active filters also hide directories that don't match
|
|
68
102
|
- 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
|
|
69
103
|
- `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
|
|
70
104
|
- `build` prompts for `%dir`/`%file`/`%str` prepend the colored command `name:` prefix, so the partial command stays identifiable while being filled in
|
|
71
|
-
- `term_hold` config — hold flag appended to `@term` at init (e.g. `foot --hold`)
|
|
72
105
|
|
|
73
106
|
### Changed
|
|
74
107
|
- 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
|
|
@@ -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.
|
|
89
106
|
#
|
|
90
|
-
#
|
|
91
|
-
|
|
92
|
-
|
|
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
|
+
# =============================================================================
|
|
118
|
+
#
|
|
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,37 +171,53 @@ 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
|
-
edit: "+micro %name"
|
|
132
|
-
view: "+moor %name"
|
|
133
190
|
info: "=file %name"
|
|
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"
|
|
@@ -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.1" "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,23 +4,43 @@ 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
30
|
dirs = dir.filter_map { |d| d = File.expand_path(d.to_s);
|
|
17
31
|
Dir.exist?(d) && File.readable?(d) && File.executable?(d) ? d : nil }
|
|
18
|
-
dirs.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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("/")
|
|
22
39
|
}
|
|
40
|
+
prefix = "/" if prefix.empty?
|
|
41
|
+
dirs.each_with_index { |d, i| super d, i.positive?, prefix.size }
|
|
23
42
|
@directory = dirs
|
|
43
|
+
Dir.chdir(prefix)
|
|
24
44
|
change_view @view if dirs.any?
|
|
25
45
|
nil
|
|
26
46
|
else
|
|
@@ -36,12 +56,30 @@ class Filecon::App < Typr::Browser
|
|
|
36
56
|
end
|
|
37
57
|
end
|
|
38
58
|
|
|
39
|
-
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
|
|
40
78
|
|
|
41
79
|
def pick type='file', **kw
|
|
42
80
|
return super unless type == :cmd
|
|
43
81
|
dir = @directory
|
|
44
|
-
|
|
82
|
+
executables
|
|
45
83
|
result = super
|
|
46
84
|
cd dir
|
|
47
85
|
result
|
|
@@ -51,7 +89,11 @@ class Filecon::App < Typr::Browser
|
|
|
51
89
|
return unless id
|
|
52
90
|
id = @views.keys[id] if id.is_a? Integer
|
|
53
91
|
view = @views[id]
|
|
54
|
-
|
|
92
|
+
if view.is_a? Array
|
|
93
|
+
super
|
|
94
|
+
return
|
|
95
|
+
end
|
|
96
|
+
Typr.flush_overtype
|
|
55
97
|
data = @data.map.with_index{ |row, id| row + view.values.map{ |cmd|
|
|
56
98
|
next unless cmd.is_a?(String)
|
|
57
99
|
built = build cmd, id
|
|
@@ -70,10 +112,7 @@ class Filecon::App < Typr::Browser
|
|
|
70
112
|
@header.index(head.to_s) || (@header += [head.to_s]; @header.count-1) }
|
|
71
113
|
clear
|
|
72
114
|
self << data
|
|
73
|
-
if @show_path
|
|
74
|
-
@data.each { |row| row[PATH] = File.dirname(row[NAME]) } if @data.any?
|
|
75
|
-
@sequence.insert(@sequence.index(NAME) || 0, PATH) unless @sequence.include?(PATH)
|
|
76
|
-
end
|
|
115
|
+
@sequence.insert(0, PATH) if @show_path
|
|
77
116
|
@current = id
|
|
78
117
|
@view = id
|
|
79
118
|
end
|
|
@@ -87,9 +126,9 @@ class Filecon::App < Typr::Browser
|
|
|
87
126
|
eval id.map{ |id| @data[id].values_at(SUBTYPE,TYPE).to_s }.join(?&)
|
|
88
127
|
else @data[id].values_at(SUBTYPE, TYPE)
|
|
89
128
|
end - [??] + ["default"]
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
93
132
|
|
|
94
133
|
commands = @commands.values_at( *types ).map(&:to_a).flatten( 1 ).uniq
|
|
95
134
|
alt = false
|
|
@@ -112,9 +151,11 @@ class Filecon::App < Typr::Browser
|
|
|
112
151
|
cmd = build pair.last, id, pair.first
|
|
113
152
|
return unless cmd
|
|
114
153
|
if alt
|
|
115
|
-
edited =
|
|
154
|
+
edited = @user.cli("> ", initial: cmd, redraw: method(:show),
|
|
155
|
+
prefixes: @prefixes&.keys&.join)
|
|
116
156
|
return unless edited
|
|
117
|
-
cmd = edited
|
|
157
|
+
cmd = build(edited, id, pair.first)
|
|
158
|
+
return unless cmd
|
|
118
159
|
end
|
|
119
160
|
if cmd[0] == ?|
|
|
120
161
|
display = cmd[1..-1]
|
|
@@ -159,47 +200,133 @@ class Filecon::App < Typr::Browser
|
|
|
159
200
|
@selected[:rows] = []
|
|
160
201
|
end
|
|
161
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
|
+
|
|
162
285
|
def build str, id, name=nil
|
|
163
286
|
nc, ac = @colors[:commands] || [:green, :red]
|
|
164
287
|
reset = color_code( @colors[:default] )
|
|
165
288
|
prefix = name ? "#{color_code(nc)}#{name}#{color_code(ac)}:" : ""
|
|
166
289
|
cmd = ""
|
|
167
290
|
while str[/\%(name|cmd|dir|file|str)/]
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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}" } :
|
|
171
296
|
id.is_a?( Array ) ? id.map{ |id|
|
|
172
|
-
@data[id][NAME] }.join(
|
|
173
|
-
@data[id][NAME] )+?"
|
|
297
|
+
@data[id][NAME].gsub(esc){ |m| "\\#{m}" } }.join(sep) :
|
|
298
|
+
@data[id][NAME].gsub(esc){ |m| "\\#{m}" } )+?"
|
|
174
299
|
when '%dir', '%file', '%cmd'
|
|
175
|
-
@user.show "> #{prefix}#{cmd}#{
|
|
176
|
-
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] )
|
|
177
302
|
return unless result
|
|
178
|
-
cmd
|
|
303
|
+
esc = cmd[0] == ?! ? /[\\"$#]/ : /[\\"$`]/
|
|
304
|
+
cmd += ?" + result.gsub(esc){ |m| "\\#{m}" } + ?"
|
|
179
305
|
when '%str'
|
|
180
306
|
prompt = cmd.empty? || cmd.end_with?(' ') ? cmd : "#{cmd} "
|
|
181
|
-
query =
|
|
307
|
+
query = @user.ask("#{prefix}#{prompt}" => :line)
|
|
182
308
|
return unless query
|
|
183
309
|
cmd += query
|
|
184
310
|
end
|
|
185
|
-
str =
|
|
311
|
+
str = post
|
|
186
312
|
end
|
|
187
|
-
cmd +=
|
|
313
|
+
cmd += str
|
|
188
314
|
return cmd
|
|
189
315
|
end
|
|
190
316
|
|
|
191
317
|
def run cmd=nil
|
|
192
318
|
return unless cmd
|
|
193
319
|
cmd = @cmd_history[cmd][1] if cmd.is_a? Integer
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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")
|
|
201
329
|
end
|
|
202
|
-
return io
|
|
203
330
|
end
|
|
204
331
|
|
|
205
332
|
def cmd_history; history @cmd_history, :cmd end
|
|
@@ -210,7 +337,7 @@ class Filecon::App < Typr::Browser
|
|
|
210
337
|
data = data.drop 1 unless is_cmd
|
|
211
338
|
return unless data.any?
|
|
212
339
|
|
|
213
|
-
top = data.max_by(@
|
|
340
|
+
top = data.max_by(@top_history) { |cnt, _| cnt }
|
|
214
341
|
top_set = top.filter_map { |_, e| e }.to_h { |e| [e, true] }
|
|
215
342
|
ordered = top + data.reject { |_, e| top_set[e] }
|
|
216
343
|
|
|
@@ -219,12 +346,12 @@ class Filecon::App < Typr::Browser
|
|
|
219
346
|
[File.basename(e), e]
|
|
220
347
|
}
|
|
221
348
|
|
|
222
|
-
fc = is_cmd ? [:red, :blue] : [:yellow, :brown]
|
|
349
|
+
fc = is_cmd ? (@colors[:cmd_history] || [:red, :blue]) : (@colors[:dir_history] || [:yellow, :brown])
|
|
223
350
|
fields = {}
|
|
224
351
|
ordered.each_with_index { |(cnt, e), i|
|
|
225
352
|
next unless top_set[e]
|
|
226
|
-
fields[[i,0]] = [fc[0], :grey20]
|
|
227
|
-
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]
|
|
228
355
|
}
|
|
229
356
|
|
|
230
357
|
picker = popup(input: pairs, format:[:min,:max],
|
|
@@ -235,10 +362,11 @@ class Filecon::App < Typr::Browser
|
|
|
235
362
|
|
|
236
363
|
entry = ordered[n][1]
|
|
237
364
|
if is_cmd and picker.modifier == :alt
|
|
238
|
-
edited =
|
|
239
|
-
|
|
365
|
+
edited = @user.cli("> ", initial: entry, redraw: -> { picker.show },
|
|
366
|
+
prefixes: @prefixes&.keys&.join)
|
|
240
367
|
return unless edited
|
|
241
|
-
entry = edited
|
|
368
|
+
entry = build(edited, nil)
|
|
369
|
+
return unless entry
|
|
242
370
|
end
|
|
243
371
|
record type, entry
|
|
244
372
|
|
|
@@ -333,26 +461,27 @@ class Filecon::App < Typr::Browser
|
|
|
333
461
|
|
|
334
462
|
def help
|
|
335
463
|
grid = @user.list
|
|
336
|
-
y = color_code :yellow
|
|
464
|
+
y = color_code (@colors[:help] || :yellow)
|
|
337
465
|
d = color_code @colors[:default]
|
|
338
|
-
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" \
|
|
339
467
|
"#{y}HINTS:#{d}\n press a number (1-9,0) to open that file with its default action.\n" \
|
|
340
468
|
" Alt+number opens the command list for that file instead.\n" \
|
|
341
469
|
" Right-click a file to open its command list.\n" \
|
|
342
470
|
" Mark files with m, then Return opens their command list.\n" \
|
|
343
471
|
" TAB cycles pages of hints.\n\n" \
|
|
344
472
|
"#{y}COMMANDS:#{d}\n" \
|
|
345
|
-
" 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" \
|
|
346
475
|
" [R]ecurse expand the current directory tree (subdirectories listed inline)\n" \
|
|
347
476
|
" [d]irectories jump to a recently visited directory\n" \
|
|
348
477
|
" [c]ommands re-run a recent command\n\n" \
|
|
349
478
|
" Directory and command history popups list the most frequently used\n" \
|
|
350
|
-
" entries first, then the rest in most-recent order. The
|
|
351
|
-
" 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" \
|
|
352
481
|
" lead the list.\n\n" \
|
|
353
482
|
"#{y}CONFIG:#{d}\n ~/.config/filecon/config.yaml\n",
|
|
354
483
|
top: 2, right: -2, bottom: -3,
|
|
355
|
-
colors: { border:[:white
|
|
484
|
+
colors: { border: @colors[:help_border] || [:white, :grey10] },
|
|
356
485
|
border: :light )
|
|
357
486
|
text.left = -> { grid.right + 2 }
|
|
358
487
|
loop do
|
|
@@ -380,18 +509,13 @@ class Filecon::App < Typr::Browser
|
|
|
380
509
|
|
|
381
510
|
def initialize args={}
|
|
382
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
|
|
383
514
|
@rawsort[NAME] = false
|
|
384
515
|
@cmd_history ||= []
|
|
385
516
|
@dir_history ||= []
|
|
386
517
|
@path ||= {}
|
|
387
|
-
@
|
|
388
|
-
@keymap[:open_current] = ?`
|
|
389
|
-
@keymap[:execute] = ?x
|
|
390
|
-
@keymap[:recurse] = ?R
|
|
391
|
-
@user.bindings.insert 7, "[R]ecurse"
|
|
392
|
-
@user.bindings.insert 8, "[c]ommands"
|
|
393
|
-
@user.bindings.insert 9, "e[x]ecute"
|
|
394
|
-
@user.bindings.insert 11, "[`]:open_current"
|
|
518
|
+
@top_history = args[:top_history] || 10
|
|
395
519
|
@user.reset
|
|
396
520
|
end
|
|
397
521
|
|
|
@@ -409,12 +533,31 @@ class Filecon::App < Typr::Browser
|
|
|
409
533
|
@directory = dir
|
|
410
534
|
end
|
|
411
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
|
+
|
|
412
548
|
def fly
|
|
413
549
|
loop do
|
|
414
550
|
show
|
|
415
551
|
draw_hints :rows
|
|
416
552
|
key = Typr.read_key
|
|
417
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
|
|
418
561
|
case key
|
|
419
562
|
when KEY_ESCAPE; exit
|
|
420
563
|
when Typr::Mouse
|
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.1
|
|
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
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '1.
|
|
18
|
+
version: '1.6'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '1.
|
|
25
|
+
version: '1.6'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: unicode-display_width
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -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: []
|