filecon 0.5.1 → 0.7.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 +35 -0
- data/README.md +14 -4
- data/bin/filecon +4 -3
- data/config.yaml +43 -67
- data/filecon.1 +15 -4
- data/lib/filecon/version.rb +1 -1
- data/lib/filecon.rb +211 -63
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8a432bd710dcfce4c261fb29a0ae260e1114e9b47c6c191fd746542b6579282e
|
|
4
|
+
data.tar.gz: fe87db05c3626b48bf4f7986998cec818973038bf1217f493afd2cbc342ad058
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 13b3822013820db54e0aa2a3e85a132caad88448e0ee50e5a31c5e7b10ecfeabb90b0fdabbf1ce410cc503f20b2df9efa26534e867cc16758bbe3d80ada2cf40
|
|
7
|
+
data.tar.gz: d5989090b1e00c0e9aca4dfebe27ae64c2793d22de99146d68489deac175ac433e511e9e3a53b3c7c52812e4b34e65afb02d7473d16fcaac017f683b3988e387
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
## [vx.y.z] — __DATE__
|
|
4
4
|
|
|
5
|
+
## [v0.7.0] — 2026-10-12
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- Requires typr `~> 1.9` — the `!narrow` binding calls `Browser#narrow`, which does not exist in 1.8.x
|
|
9
|
+
- Dropped the two `Typr.flush_overtype` calls (custom view rebuild, command spawn) — typr's overtype guard now lives entirely behind `Grid#pick` selections, so there is nothing for filecon to arm or flush; keys typed during slow rebuilds count as intentional input at the next read
|
|
10
|
+
- `|` commands keep running after filecon exits: pipe writers spawn in their own process group (immune to the exit-time hangup) and the pipe is handed to a detached null-drainer, so writers never hit a closed pipe either
|
|
11
|
+
- Windowed (`+`) commands send the emulator's stderr to /dev/null, so terminal chatter (kitty parse errors, keyboard-protocol notices) no longer prints over the view; the inner command keeps its own terminal for its own errors
|
|
12
|
+
|
|
13
|
+
## [v0.6.0] — 2026-10-11
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Bottom-bar live narrowing is labeled `[/]:narrow` and wired to `!narrow` (was `[/]:search` / `!search`), matching what it does; `search` now means only find-in-text
|
|
17
|
+
- Help panels use round borders joined by T-pieces at the seam
|
|
18
|
+
- All shell strings run under `bash -c` (never `sh`) through one `shell` helper; aliases and rc files do not resolve anywhere. Startup aborts cleanly without bash
|
|
19
|
+
- Help headers render from the configured `header:` color and command names blue from inline palette codes, which survive rebuilds and resizes under paint-once text
|
|
20
|
+
- Typr widget colors are configurable from the bundled config: `border:`, `header:`, `question:`, `answer:`, `selected:`, `alternate:`, `hints:`, and `search:` join the color mapping with per-key inline docs; every color key is documented and column-aligned (pinned by spec). All widget borders (help panels, pickers, conflict grids, error popups, `=` viewer) render from the configured `border:` color instead of hardcoded pairs
|
|
21
|
+
- Config is the authority on color: the `conflict:`, `help:`, and `help_border:` keys are gone (conflict uses the base `black` background, help titles use palette yellow inline with the keybinding-list header from `header:`, help borders use `border:`) and all color fallbacks are removed. Help command names render blue; help documents bash completion, recursive listing, history alt-edit, and all config locations
|
|
22
|
+
- Conflict picker is a single filling column, so no view shows through its rows
|
|
23
|
+
- Conflict popups use round borders on the base black background with all foregrounds kept; errors use the `error:` pair (dark red by default)
|
|
24
|
+
- Command execution from open_file, execute, and cmd_history runs through one `dispatch` (record except `!cd`, refresh, error popups); `run` drops its unused integer form. Per-prefix output: `|` detaches stdin and shows the first line, if any, then detaches; leftover pipe output is drained in a background thread so chatty detached commands survive instead of dying on SIGPIPE, (the pipe format spawns with `in: File::NULL`, so interactive children get EOF instead of the shared keyboard) and shows the first line, if any, then detaches. Stale home configs keep the old `open()` pipe format with shared stdin until reseeded, `+` detaches immediately, `=` streams live into the viewer, `!` results show with a keypress pause. The `regex:` config key is gone; only the `e[x]ecute` prompt tints (`:shell`). Requires typr `~> 1.8`
|
|
25
|
+
- Layered configuration — bundled defaults, then `/etc/filecon/config.yaml` (read if present, never written), then the user file; fresh user files are seeded as a commented template overriding nothing, and an explicit null deletes an inherited key. Existing user files keep working untouched. A layer file that parses to a non-mapping raises immediately naming the file
|
|
26
|
+
- Token blinking routes through `mode_code` instead of inline escapes (byte-identical output)
|
|
27
|
+
- Empty command results stay silent: no status line, no keypress pause
|
|
28
|
+
- Copy/move runs through rsync with its own progress repainted on the bottom line only (`--progress` stdout is piped, never scrolls); stderr is collected and shown in a borderless popup with the configurable `error:` colors at the end of the transfer; falls back to `FileUtils` where rsync is unavailable
|
|
29
|
+
|
|
30
|
+
### Removed
|
|
31
|
+
- Bundled `+` prefix: no static terminal default is correct ($TERM names terminfo, not binaries); fresh seeds write the detected line, stale installs reseed by deleting the user file
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
- First-run seeding writes a live `+` prefix with the detected terminal (first of kitty, foot, alacritty -e, urxvt -e, xterm -e, konsole -e, gnome-terminal --, ghostty -e, xfce4-terminal -e, wezterm start --, terminator -e, st -e found on PATH, else `$TERMINAL`), so windowed commands work out of the box (falls back to a literal `$TERMINAL` reference when nothing is detected); the `+` format no longer holds the window open
|
|
35
|
+
- Archive list/extract actions for 7z (`x-7z-compressed`) and rar (`vnd.rar`); `-y`/`-o+` keep extracts non-interactive under detached stdin
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
- Terminal-window commands wrap the body in `bash -c`, so hashbang-less scripts run under terminals that exec directly instead of failing with exec format error
|
|
39
|
+
|
|
5
40
|
## [v0.5.1] — 2026-09-30
|
|
6
41
|
|
|
7
42
|
### Changed
|
data/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Filecon is a file and command manager that lives between terminal and shell, bui
|
|
|
9
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
|
|
12
|
+
- **Live narrowing in popups** — popups open in `/` narrow mode; type to narrow the list, then pick one of the few remaining results with a number. return key selects 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).
|
|
@@ -18,7 +18,7 @@ Filecon runs on Typr, whose self-contained terminal layer ships its own precompi
|
|
|
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
|
-
- **Default `[/]:
|
|
21
|
+
- **Default `[/]:narrow` in popups** — command and history popups open in narrow mode: start typing to narrow the list down to the matches, then press a number hint to pick from the few remaining results. The listing itself narrows 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)
|
|
@@ -33,6 +33,8 @@ Filecon runs on Typr, whose self-contained terminal layer ships its own precompi
|
|
|
33
33
|
gem install filecon
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
Requires `bash` (all shell commands run under `bash -c`, never `sh`; aliases and rc files do not resolve — full commands and bash syntax only) and `rsync` for transfers.
|
|
37
|
+
|
|
36
38
|
## Usage
|
|
37
39
|
|
|
38
40
|
```bash
|
|
@@ -81,13 +83,21 @@ Click a row to open it with its default action, right-click (or Alt+click) to op
|
|
|
81
83
|
|
|
82
84
|
### Popups and search
|
|
83
85
|
|
|
84
|
-
Command and history popups open in `/`
|
|
86
|
+
Command and history popups open in `/` narrow mode. Type a few characters to **narrow** the list down to the matching entries; when only a handful remain, press the number hint of the one you want. Large lists can be narrowed in a keystroke or two. The main listing narrows the same way when you press `/`.
|
|
85
87
|
|
|
86
88
|
### Configuration
|
|
87
89
|
|
|
88
|
-
Config is
|
|
90
|
+
Config is layered, lowest to highest precedence: the bundled defaults,
|
|
91
|
+
`/etc/filecon/config.yaml` (system-wide, read if present, never written),
|
|
92
|
+
then `~/.config/filecon/config.yaml` (yours). On first run the user file is
|
|
93
|
+
created from the bundled config with every setting commented out except a
|
|
94
|
+
live + prefix with your detected terminal — a documented template that
|
|
95
|
+
overrides nothing else until edited, so new defaults
|
|
96
|
+
flow through on upgrade. Set only what you change; an explicit empty value
|
|
97
|
+
deletes an inherited key. It includes:
|
|
89
98
|
|
|
90
99
|
- 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
|
|
100
|
+
- The `e[x]ecute` prompt always tints with the `:shell` scheme (first word red, `--options` blue); the `=` output viewer uses `:generic`
|
|
91
101
|
- Command definitions per MIME type, with execution-mode prefixes
|
|
92
102
|
- View configurations (columns that run shell commands)
|
|
93
103
|
- History limits
|
data/bin/filecon
CHANGED
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
require "tmpdir"
|
|
5
|
-
require "yaml"
|
|
6
5
|
require_relative "../lib/filecon"
|
|
7
6
|
|
|
8
7
|
log = nil
|
|
9
8
|
begin
|
|
9
|
+
abort "filecon requires bash" unless system("bash", "--version", out: File::NULL, err: File::NULL)
|
|
10
10
|
config_dir = File.expand_path("~/.config/filecon")
|
|
11
11
|
config_path = File.join(config_dir, "config.yaml")
|
|
12
|
+
bundled_path = File.join(__dir__, "../config.yaml")
|
|
12
13
|
unless File.exist?(config_path)
|
|
13
14
|
FileUtils.mkpath config_dir
|
|
14
|
-
|
|
15
|
+
File.write config_path, Filecon.config_template(File.read(bundled_path))
|
|
15
16
|
end
|
|
16
17
|
state_dir = File.expand_path("~/.local/share/filecon")
|
|
17
18
|
FileUtils.mkpath state_dir
|
|
@@ -23,7 +24,7 @@ begin
|
|
|
23
24
|
|
|
24
25
|
parse = ->(p) { File.readlines(p).map { |l| s = l.chomp.split(",", 2); [s[0].to_i, s[1]] } rescue [] }
|
|
25
26
|
|
|
26
|
-
config =
|
|
27
|
+
config = Filecon.load_config(bundled_path, "/etc/filecon/config.yaml", config_path)
|
|
27
28
|
|
|
28
29
|
Typr.init config["colors"]["default"]
|
|
29
30
|
Typr.clear
|
data/config.yaml
CHANGED
|
@@ -2,28 +2,23 @@
|
|
|
2
2
|
# Filecon Configuration
|
|
3
3
|
# =============================================================================
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# All settings are optional. Filecon will use sensible defaults if values
|
|
8
|
-
# are not specified.
|
|
5
|
+
# All settings are optional. Defaults are commented out.
|
|
9
6
|
#
|
|
10
7
|
# =============================================================================
|
|
11
8
|
|
|
12
9
|
# Maximum number of history entries to retain.
|
|
13
|
-
# Older entries beyond this limit are automatically pruned.
|
|
14
10
|
max_history: 10000
|
|
15
11
|
|
|
12
|
+
# most frequent entries to lead history lists before most recent entries
|
|
16
13
|
top_history: 6
|
|
14
|
+
|
|
17
15
|
# When true, active filters also hide directories that don't match.
|
|
18
16
|
filter_dirs: true
|
|
19
17
|
|
|
20
|
-
|
|
21
18
|
# =============================================================================
|
|
22
19
|
# COLORS
|
|
23
20
|
# =============================================================================
|
|
24
21
|
#
|
|
25
|
-
# Customize the color scheme for different file types in the browser view.
|
|
26
|
-
#
|
|
27
22
|
# Color values can be:
|
|
28
23
|
# - Named colors:
|
|
29
24
|
# Basic: black, red, green, yellow, blue, magenta, cyan, white, grey
|
|
@@ -38,24 +33,6 @@ filter_dirs: true
|
|
|
38
33
|
# comment these lines out (or delete them) to use
|
|
39
34
|
# the terminal colors instead
|
|
40
35
|
#
|
|
41
|
-
# Color settings passed to Typr spaces:
|
|
42
|
-
# default global default foreground/background [fg, bg]
|
|
43
|
-
# border widget borders
|
|
44
|
-
# header column/panel headers
|
|
45
|
-
# question prompt question text
|
|
46
|
-
# answer typed input text
|
|
47
|
-
# selected highlighted / selected row background
|
|
48
|
-
# alternate alternating row background in lists
|
|
49
|
-
# hints hint numbers/digits
|
|
50
|
-
# actions named action colors: filter, sort, views
|
|
51
|
-
# commands command picker and status line: [name, args] colors
|
|
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]
|
|
58
|
-
#
|
|
59
36
|
# Usage:
|
|
60
37
|
# Foreground color only: "blue"
|
|
61
38
|
# Foreground + background: ["white", "black"]
|
|
@@ -63,7 +40,7 @@ filter_dirs: true
|
|
|
63
40
|
# =============================================================================
|
|
64
41
|
|
|
65
42
|
colors:
|
|
66
|
-
# Base palette (comment out to use terminal colors)
|
|
43
|
+
# Base palette (comment out in bundled config to use terminal colors)
|
|
67
44
|
black: [21, 21, 21]
|
|
68
45
|
red: [172, 65, 66]
|
|
69
46
|
green: [126, 141, 80]
|
|
@@ -73,24 +50,30 @@ colors:
|
|
|
73
50
|
cyan: [125, 213, 207]
|
|
74
51
|
white: [208, 208, 208]
|
|
75
52
|
|
|
76
|
-
default: [white, black]
|
|
77
|
-
|
|
53
|
+
default: [white, black] # Global default foreground/background [fg, bg]
|
|
54
|
+
border: [white, black] # Widget borders
|
|
55
|
+
header: [black, grey50] # Column/panel headers
|
|
56
|
+
question: green # Prompt question text
|
|
57
|
+
answer: yellow # Typed input text
|
|
58
|
+
selected: grey25 # Highlighted / selected row background
|
|
59
|
+
alternate: grey10 # Alternating row background in lists
|
|
60
|
+
hints: [white, black] # Hint numbers/digits
|
|
61
|
+
commands: [green, red] # Command picker and status line: [name, args] colors
|
|
78
62
|
types:
|
|
79
63
|
image: blue
|
|
80
64
|
audio: green
|
|
81
65
|
video: cyan
|
|
82
66
|
directory: yellow
|
|
83
67
|
executable: red
|
|
84
|
-
actions:
|
|
68
|
+
actions: # Named action colors: filter, sort, views
|
|
85
69
|
filter: red
|
|
86
70
|
sort: blue
|
|
87
71
|
views: green
|
|
88
|
-
dir_history: [yellow, brown]
|
|
89
|
-
cmd_history: [red, blue]
|
|
90
|
-
top_history: grey20
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
72
|
+
dir_history: [yellow, brown] # Directory history picker: [name, path] columns
|
|
73
|
+
cmd_history: [red, blue] # Command history picker: [prefix, command] columns
|
|
74
|
+
top_history: grey20 # Top-history field background color
|
|
75
|
+
search: grey20 # Search-match highlight (text viewer)
|
|
76
|
+
error: [white, dark_red] # Error popup: [fg, bg]
|
|
94
77
|
|
|
95
78
|
# =============================================================================
|
|
96
79
|
# PREFIXES
|
|
@@ -100,17 +83,16 @@ colors:
|
|
|
100
83
|
# a format string. %s is replaced with the command body.
|
|
101
84
|
#
|
|
102
85
|
# Format strings starting with ! are evaluated as Ruby and their result is
|
|
103
|
-
# read; every other format
|
|
104
|
-
#
|
|
105
|
-
# and left running while the browser keeps going.
|
|
86
|
+
# read; every other format runs under bash. Aliases and rc files do not
|
|
87
|
+
# resolve anywhere; use full commands and bash syntax.
|
|
106
88
|
#
|
|
107
89
|
# =============================================================================
|
|
108
90
|
|
|
109
91
|
prefixes:
|
|
110
92
|
"!": "!StringIO.new((%s).to_s)"
|
|
111
|
-
"|": "!
|
|
112
|
-
"
|
|
113
|
-
"
|
|
93
|
+
"|": "!IO.popen('%s 2>&1', 'r', in: File::NULL, pgroup: true)"
|
|
94
|
+
"=": "!Text.new(top: 1, right:-1, bottom:-2, header: '%s', colors: {regex: :generic}, input: open('|%s 2>&1')).pick :none"
|
|
95
|
+
"-": "!Text.new(top: 1, right:-1, bottom:-2, header: '%s', input: open('|%s 2>&1')).pick :none"
|
|
114
96
|
|
|
115
97
|
# =============================================================================
|
|
116
98
|
# BINDINGS
|
|
@@ -118,18 +100,15 @@ prefixes:
|
|
|
118
100
|
#
|
|
119
101
|
# Key bindings for the status bar. Each binding maps a label (with the key
|
|
120
102
|
# character in brackets) to either `!method` — a method call, dispatched
|
|
121
|
-
# directly — or any prefixed command, which is
|
|
122
|
-
# like
|
|
103
|
+
# directly — or any prefixed command, which is run
|
|
104
|
+
# like any command (`%name` resolves to the current directory).
|
|
123
105
|
# Commands from bindings are never added to the command history.
|
|
124
106
|
#
|
|
125
|
-
# Only printable single characters work as key bindings.
|
|
126
|
-
# Non-printable keys (arrows, escape, return, etc.) are hardcoded.
|
|
127
|
-
#
|
|
128
107
|
# =============================================================================
|
|
129
108
|
|
|
130
109
|
bindings:
|
|
131
110
|
"[h]elp": "!help"
|
|
132
|
-
"[/]:
|
|
111
|
+
"[/]:narrow": "!narrow"
|
|
133
112
|
"[f]ilter": "!filter_view"
|
|
134
113
|
"[s]ort": "!sort_view"
|
|
135
114
|
"[v]iews": "!views"
|
|
@@ -151,9 +130,7 @@ bindings:
|
|
|
151
130
|
# COMMANDS
|
|
152
131
|
# =============================================================================
|
|
153
132
|
#
|
|
154
|
-
# Define commands
|
|
155
|
-
# category) maps to a set of named commands that can be executed on files of
|
|
156
|
-
# that type.
|
|
133
|
+
# Define commands for different MIME types.
|
|
157
134
|
#
|
|
158
135
|
# SUBSTITUTION VARIABLES:
|
|
159
136
|
#
|
|
@@ -186,21 +163,18 @@ bindings:
|
|
|
186
163
|
|
|
187
164
|
commands:
|
|
188
165
|
default:
|
|
189
|
-
# copy: "|rsync --progress %name %dir"
|
|
190
166
|
info: "=file %name"
|
|
191
|
-
symlink: "ln -s %name %dir"
|
|
192
167
|
copy: "!transfer(:copy, %name, %dir)"
|
|
193
168
|
move: "!transfer(:move, %name, %dir)"
|
|
194
|
-
# copy_shell: "|cp -R %name %dir"
|
|
195
|
-
# move_shell: "|mv %name %dir"
|
|
196
169
|
rename: "|mv %name %str"
|
|
197
170
|
delete: "|rm -rf %name"
|
|
171
|
+
symlink: "ln -s %name %dir"
|
|
198
172
|
open_with: "|%cmd %name %str"
|
|
199
|
-
|
|
173
|
+
open_in_terminal: "+%cmd %name %str"
|
|
200
174
|
xdg_open: "|xdg-open %name"
|
|
201
175
|
|
|
202
176
|
executable:
|
|
203
|
-
whatis: "
|
|
177
|
+
whatis: "|whatis %name"
|
|
204
178
|
help: "=%name --help"
|
|
205
179
|
manual: "+man %name"
|
|
206
180
|
run: "|%name %str"
|
|
@@ -217,14 +191,9 @@ commands:
|
|
|
217
191
|
new_file: "|touch %name/%str"
|
|
218
192
|
new_dir: "|mkdir %name/%str"
|
|
219
193
|
shell: "+$SHELL "
|
|
220
|
-
# shell: "+$SHELL -c 'cd %name && exec $SHELL'"
|
|
221
|
-
# zsh: "+zsh -c 'cd %name && exec zsh'"
|
|
222
194
|
disk_use: "|du -hs %name | cut -wf1"
|
|
223
195
|
play: "+mpv %name"
|
|
224
196
|
|
|
225
|
-
# text:
|
|
226
|
-
# count: "|wc -l %name | cut -wf1"
|
|
227
|
-
|
|
228
197
|
troff:
|
|
229
198
|
manual: "+man %name"
|
|
230
199
|
|
|
@@ -251,7 +220,7 @@ commands:
|
|
|
251
220
|
"x-python":
|
|
252
221
|
run: "+python3 %name"
|
|
253
222
|
|
|
254
|
-
"x
|
|
223
|
+
"x-perl":
|
|
255
224
|
run: "+perl %name"
|
|
256
225
|
|
|
257
226
|
"x-shellscript":
|
|
@@ -278,9 +247,9 @@ commands:
|
|
|
278
247
|
"x-c++":
|
|
279
248
|
run: "+g++ %name && ./a.out"
|
|
280
249
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
250
|
+
markdown:
|
|
251
|
+
view: "=glow %name"
|
|
252
|
+
edit: "+micro %name"
|
|
284
253
|
|
|
285
254
|
json:
|
|
286
255
|
view: "=cat %name | jq ."
|
|
@@ -305,6 +274,14 @@ commands:
|
|
|
305
274
|
list: "=tar -tf %name"
|
|
306
275
|
extract: "|tar -xf %name -C %dir"
|
|
307
276
|
|
|
277
|
+
"x-7z-compressed":
|
|
278
|
+
list: "=7z l %name"
|
|
279
|
+
extract: "|7z x -y %name -o%dir"
|
|
280
|
+
|
|
281
|
+
"vnd.rar":
|
|
282
|
+
list: "=unrar l %name"
|
|
283
|
+
extract: "|unrar x -o+ %name %dir"
|
|
284
|
+
|
|
308
285
|
"epub+zip":
|
|
309
286
|
view: "+folio %name"
|
|
310
287
|
|
|
@@ -325,7 +302,6 @@ commands:
|
|
|
325
302
|
# View values can be:
|
|
326
303
|
# - true Use the built-in column for this field
|
|
327
304
|
# - A string Command to execute (with %name substitution)
|
|
328
|
-
# - A symbol Reference to a system field (e.g., :size, :type, :subtype)
|
|
329
305
|
#
|
|
330
306
|
# Prefix ! to evaluate as Ruby
|
|
331
307
|
#
|
data/filecon.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH FILECON 1 "2026-
|
|
1
|
+
.TH FILECON 1 "2026-10-12" "filecon 0.7.0" "User Commands"
|
|
2
2
|
.SH NAME
|
|
3
3
|
filecon \- a file and command manager that lives between terminal and shell
|
|
4
4
|
.SH SYNOPSIS
|
|
@@ -93,12 +93,23 @@ first, then the remaining entries in most-recent order. The
|
|
|
93
93
|
setting in config.yaml (default 10) controls how many frequent entries lead
|
|
94
94
|
the list.
|
|
95
95
|
.SH CONFIGURATION
|
|
96
|
-
|
|
96
|
+
Configuration is layered, lowest to highest precedence: the bundled
|
|
97
97
|
.B config.yaml
|
|
98
|
-
|
|
99
|
-
.
|
|
98
|
+
in the installation directory,
|
|
99
|
+
.B /etc/filecon/config.yaml
|
|
100
|
+
(system-wide, read if present, never written),
|
|
101
|
+
then the user file below.
|
|
102
|
+
On first run a documented template is written to
|
|
103
|
+
.BR ~/.config/filecon/config.yaml :
|
|
104
|
+
the bundled defaults with every setting commented out except a live +
|
|
105
|
+
prefix with your detected terminal, overriding nothing else until edited.
|
|
106
|
+
Set only what you change; an explicit empty
|
|
107
|
+
value deletes an inherited key.
|
|
100
108
|
.SS Config File Locations
|
|
101
109
|
.TP
|
|
110
|
+
.B /etc/filecon/config.yaml
|
|
111
|
+
System-wide overrides (optional)
|
|
112
|
+
.TP
|
|
102
113
|
.B ~/.config/filecon/config.yaml
|
|
103
114
|
Main configuration (colors, commands, views)
|
|
104
115
|
.TP
|
data/lib/filecon/version.rb
CHANGED
data/lib/filecon.rb
CHANGED
|
@@ -6,9 +6,55 @@ end
|
|
|
6
6
|
|
|
7
7
|
require "fileutils"
|
|
8
8
|
require "find"
|
|
9
|
+
require "yaml"
|
|
10
|
+
require "tempfile"
|
|
9
11
|
|
|
10
12
|
require_relative "filecon/version"
|
|
11
13
|
|
|
14
|
+
module Filecon
|
|
15
|
+
|
|
16
|
+
# Layered configuration: bundled defaults < system (/etc) < user (~/.config).
|
|
17
|
+
# Merges +over+ into +base+: hashes recurse, scalars and arrays replace, an
|
|
18
|
+
# explicit null deletes an inherited key, absent keys fall through.
|
|
19
|
+
def self.layer base, over
|
|
20
|
+
over.each do |k, v|
|
|
21
|
+
if v.nil? and base.key?(k) then base.delete(k)
|
|
22
|
+
elsif v.is_a?(Hash) and base[k].is_a?(Hash) then layer(base[k], v)
|
|
23
|
+
else base[k] = v end
|
|
24
|
+
end
|
|
25
|
+
base
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Load and merge configuration layers in order; missing files are skipped.
|
|
29
|
+
# Every present layer must parse to a mapping; anything else raises naming
|
|
30
|
+
# the file, so a broken layer fails loud instead of failing deep in the
|
|
31
|
+
# merge or silently booting defaults.
|
|
32
|
+
def self.load_config *paths
|
|
33
|
+
paths.filter_map { |p|
|
|
34
|
+
next unless p and File.exist?(p)
|
|
35
|
+
cfg = YAML.safe_load(File.read(p), permitted_classes: [Symbol])
|
|
36
|
+
raise "#{p} must contain a YAML mapping, got #{cfg.class}" unless cfg.nil? or cfg.is_a?(Hash)
|
|
37
|
+
cfg
|
|
38
|
+
}.reduce({}) { |merged, cfg| layer(merged, cfg || {}) }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Documented template: every setting commented out, docs kept — except one
|
|
42
|
+
# live + prefix with the detected terminal, so windowed commands work out
|
|
43
|
+
# of the box. Parses to just that override until edited. -e flags ride on
|
|
44
|
+
# the entries that need them; the format itself stays shared.
|
|
45
|
+
def self.config_template text, terminal = nil
|
|
46
|
+
on_path = ->(t) { ENV["PATH"].to_s.split(File::PATH_SEPARATOR).any? { |d| File.executable?(File.join(d, t.split.first)) } }
|
|
47
|
+
terminal ||= ["kitty", "foot", "alacritty -e", "urxvt -e", "xterm -e",
|
|
48
|
+
"konsole -e", "gnome-terminal --", "ghostty -e", "xfce4-terminal -e",
|
|
49
|
+
"wezterm start --", "terminator -e", "st -e"].find { |e| on_path.(e) } || "$TERMINAL"
|
|
50
|
+
body = text.gsub(/^([ \t]*)(?=\S)(?!#)/m, '\1# ')
|
|
51
|
+
stanza = "# Terminal for + commands, detected on first run (edit freely).\nprefixes:\n \"+\": \"#{terminal} bash -c '%s'\"\n\n"
|
|
52
|
+
anchor = "# filter_dirs: true\n"
|
|
53
|
+
body.include?(anchor) ? body.sub(anchor) { |m| m + stanza } : stanza + body
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
57
|
+
|
|
12
58
|
class Filecon::App < Typr::Browser
|
|
13
59
|
include Typr
|
|
14
60
|
|
|
@@ -59,19 +105,9 @@ class Filecon::App < Typr::Browser
|
|
|
59
105
|
def execute
|
|
60
106
|
cmd = @user.cli("> ", redraw: method(:show),
|
|
61
107
|
history: @cmd_history.map { |_, cmd| cmd },
|
|
62
|
-
prefixes: @prefixes&.keys&.join)
|
|
108
|
+
prefixes: @prefixes&.keys&.join, regex: :shell)
|
|
63
109
|
return unless cmd
|
|
64
|
-
|
|
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
|
|
110
|
+
dispatch cmd
|
|
75
111
|
end
|
|
76
112
|
|
|
77
113
|
def executables; cd ENV["PATH"].split(":") end
|
|
@@ -93,7 +129,6 @@ class Filecon::App < Typr::Browser
|
|
|
93
129
|
super
|
|
94
130
|
return
|
|
95
131
|
end
|
|
96
|
-
Typr.flush_overtype
|
|
97
132
|
data = @data.map.with_index{ |row, id| row + view.values.map{ |cmd|
|
|
98
133
|
next unless cmd.is_a?(String)
|
|
99
134
|
built = build cmd, id
|
|
@@ -102,7 +137,7 @@ class Filecon::App < Typr::Browser
|
|
|
102
137
|
StringIO.new eval( built[1..-1] ).to_s
|
|
103
138
|
else
|
|
104
139
|
cmd = built[0] == ?| ? built : ?| + built
|
|
105
|
-
|
|
140
|
+
shell(cmd + " 2>&1")
|
|
106
141
|
end
|
|
107
142
|
cell = io.read(width).to_s.scrub.strip
|
|
108
143
|
value = coerce_type cell
|
|
@@ -142,7 +177,7 @@ class Filecon::App < Typr::Browser
|
|
|
142
177
|
bottom: [ pop + commands.count - 1, Typr.height ].min,
|
|
143
178
|
input:commands, border: :light, format: [:min, :max],
|
|
144
179
|
borders: { top: nil }, alternate: false,
|
|
145
|
-
colors: {columns: @colors[:commands]
|
|
180
|
+
colors: {columns: @colors[:commands], border: @colors[:border]} )
|
|
146
181
|
return unless cmd = picker.pick( :row, column: 0 )
|
|
147
182
|
alt = picker.modifier == :alt
|
|
148
183
|
commands[ cmd ]
|
|
@@ -157,23 +192,7 @@ class Filecon::App < Typr::Browser
|
|
|
157
192
|
cmd = build(edited, id, pair.first)
|
|
158
193
|
return unless cmd
|
|
159
194
|
end
|
|
160
|
-
|
|
161
|
-
display = cmd[1..-1]
|
|
162
|
-
record :cmd, cmd
|
|
163
|
-
io = run( cmd )
|
|
164
|
-
output = io.read.to_s.scrub.strip
|
|
165
|
-
nc, ac = @colors[:commands] || [:green, :red]
|
|
166
|
-
@user.show "> #{color_code(nc)}#{pair.first}#{color_code(ac)}:#{display}" \
|
|
167
|
-
"#{color_code(@colors[:default])} : #{output.lines.last}"
|
|
168
|
-
show
|
|
169
|
-
Typr.read_key
|
|
170
|
-
else
|
|
171
|
-
record :cmd, cmd unless cmd[0..2] == '!cd'
|
|
172
|
-
run( cmd )
|
|
173
|
-
end
|
|
174
|
-
Array(id).sort.reverse.each { |i| refresh_row i } if id && cmd[0..2] != '!cd' && @directory == dir
|
|
175
|
-
@selected[:rows] = [] if id && @directory != dir
|
|
176
|
-
show #unless cmd[0] == ?|
|
|
195
|
+
dispatch cmd, name: pair.first, id: id, dir: dir
|
|
177
196
|
end
|
|
178
197
|
|
|
179
198
|
def refresh_row id
|
|
@@ -215,6 +234,7 @@ class Filecon::App < Typr::Browser
|
|
|
215
234
|
[path, File.join(dest, File.basename(src), path[(src.length + 1)..-1])] unless path == src }
|
|
216
235
|
}
|
|
217
236
|
policy, done, skipped, failed = nil, 0, 0, 0
|
|
237
|
+
err = Tempfile.new("filecon-rsync")
|
|
218
238
|
pairs.each_with_index do |(src, dst), i|
|
|
219
239
|
@user.show "#{op} #{i + 1}/#{pairs.size}: #{File.basename(src)}"
|
|
220
240
|
exists = File.exist?( dst ) || File.symlink?( dst )
|
|
@@ -238,13 +258,52 @@ class Filecon::App < Typr::Browser
|
|
|
238
258
|
dst = action if action.is_a?( String )
|
|
239
259
|
if File.directory?( src ) and !File.symlink?( src )
|
|
240
260
|
FileUtils.mkdir_p( dst )
|
|
241
|
-
|
|
242
|
-
FileUtils.mv( src, dst )
|
|
261
|
+
done += 1
|
|
243
262
|
else
|
|
244
|
-
|
|
263
|
+
if @has_rsync.nil?
|
|
264
|
+
begin
|
|
265
|
+
pid = Process.spawn("rsync", "--version", out: File::NULL, err: File::NULL)
|
|
266
|
+
Process.wait(pid)
|
|
267
|
+
@has_rsync = $?.success?
|
|
268
|
+
rescue Errno::ENOENT
|
|
269
|
+
@has_rsync = false
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
if @has_rsync
|
|
273
|
+
FileUtils.mkdir_p(File.dirname(dst))
|
|
274
|
+
cmd = ["rsync", "-a", "--progress", src, dst]
|
|
275
|
+
cmd.insert(2, "--remove-source-files") if op == :move
|
|
276
|
+
begin
|
|
277
|
+
rd, wr = IO.pipe
|
|
278
|
+
pid = Process.spawn(*cmd, out: wr, err: err.path)
|
|
279
|
+
wr.close
|
|
280
|
+
seg, shown = nil, nil
|
|
281
|
+
begin
|
|
282
|
+
loop do
|
|
283
|
+
rd.readpartial(4096).split(/[\r\n]/).each do |line|
|
|
284
|
+
seg = line unless line.empty?
|
|
285
|
+
end
|
|
286
|
+
if seg != shown
|
|
287
|
+
shown = seg
|
|
288
|
+
@user.show "#{op} #{i + 1}/#{pairs.size}: #{shown}"
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
rescue EOFError
|
|
292
|
+
end
|
|
293
|
+
rd.close
|
|
294
|
+
Process.wait(pid)
|
|
295
|
+
ok = $?.success?
|
|
296
|
+
rescue Errno::ENOENT
|
|
297
|
+
@has_rsync = false
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
unless @has_rsync
|
|
301
|
+
if op == :move then FileUtils.mv(src, dst) else FileUtils.cp(src, dst) end
|
|
302
|
+
ok = true
|
|
303
|
+
end
|
|
304
|
+
if ok then done += 1 else failed += 1 end
|
|
305
|
+
end
|
|
245
306
|
end
|
|
246
|
-
done += 1
|
|
247
|
-
end
|
|
248
307
|
rescue SystemCallError
|
|
249
308
|
failed += 1
|
|
250
309
|
end
|
|
@@ -255,6 +314,14 @@ class Filecon::App < Typr::Browser
|
|
|
255
314
|
end
|
|
256
315
|
tally = "#{op}: #{done} done, #{skipped} skipped" + ( failed > 0 ? ", #{failed} failed" : "" )
|
|
257
316
|
@user.show tally
|
|
317
|
+
errs = File.read(err.path).strip
|
|
318
|
+
err.close!
|
|
319
|
+
unless errs.empty?
|
|
320
|
+
Text.new(left: 0.15, right: 0.85, top: 0.3, bottom: 0.7,
|
|
321
|
+
header: "rsync errors", input: StringIO.new(errs),
|
|
322
|
+
colors: { default: @colors[:error], border: @colors[:border] },
|
|
323
|
+
alternate: false).pick(:none)
|
|
324
|
+
end
|
|
258
325
|
show
|
|
259
326
|
Typr.read_key
|
|
260
327
|
tally
|
|
@@ -263,12 +330,16 @@ class Filecon::App < Typr::Browser
|
|
|
263
330
|
def resolve_conflict src, dst
|
|
264
331
|
Typr.drain_input
|
|
265
332
|
info_top = top + 1
|
|
266
|
-
|
|
333
|
+
bg = [Array(@colors[:default])[0], @colors[:black]]
|
|
334
|
+
info = Grid.new(left:left+1, top:info_top, bottom:info_top+2, margin:' ', 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)]], border: :round, alternate:false, procs:{2=>:magnitudes, 3=>proc{|s| Time.at(s).strftime("%Y-%m-%d %H:%M:%S") rescue "?" }}, colors:{columns:@colors[:commands], default: bg, border: @colors[:border]})
|
|
267
335
|
act_top = info.bottom + 2
|
|
268
336
|
choices = %w[skip overwrite older smaller size_differs rename]
|
|
269
|
-
picker = Grid.new(left:left+1, top:act_top,
|
|
337
|
+
picker = Grid.new(left:left+1, top:act_top, bottom:[act_top+6, Typr.height].min, header:"alt-number remembers choice", input:choices, format:[:max], border: :round, alternate:false, colors:{columns:@colors[:commands], default: bg, border: @colors[:border]})
|
|
338
|
+
picker.borders[:top]=' '
|
|
339
|
+
picker.borders[:top_right]='╭'
|
|
340
|
+
picker.borders[:top_left]='│'
|
|
270
341
|
info.show
|
|
271
|
-
idx = picker.pick(:row
|
|
342
|
+
idx = picker.pick(:row)
|
|
272
343
|
show
|
|
273
344
|
return [:abort, nil] unless idx.is_a?(Integer)
|
|
274
345
|
mods = picker.modifier
|
|
@@ -283,7 +354,7 @@ class Filecon::App < Typr::Browser
|
|
|
283
354
|
end
|
|
284
355
|
|
|
285
356
|
def build str, id, name=nil
|
|
286
|
-
nc, ac = @colors[:commands]
|
|
357
|
+
nc, ac = @colors[:commands]
|
|
287
358
|
reset = color_code( @colors[:default] )
|
|
288
359
|
prefix = name ? "#{color_code(nc)}#{name}#{color_code(ac)}:" : ""
|
|
289
360
|
cmd = ""
|
|
@@ -297,7 +368,7 @@ class Filecon::App < Typr::Browser
|
|
|
297
368
|
@data[id][NAME].gsub(esc){ |m| "\\#{m}" } }.join(sep) :
|
|
298
369
|
@data[id][NAME].gsub(esc){ |m| "\\#{m}" } )+?"
|
|
299
370
|
when '%dir', '%file', '%cmd'
|
|
300
|
-
@user.show "> #{prefix}#{cmd}
|
|
371
|
+
@user.show "> #{prefix}#{cmd}#{mode_code(:slow)}#{tok}#{mode_code(:steady)}#{post}#{reset}"
|
|
301
372
|
result = pick( { '%dir' => :directory, '%file' => :file, '%cmd' => :cmd }[tok] )
|
|
302
373
|
return unless result
|
|
303
374
|
esc = cmd[0] == ?! ? /[\\"$#]/ : /[\\"$`]/
|
|
@@ -314,19 +385,90 @@ class Filecon::App < Typr::Browser
|
|
|
314
385
|
return cmd
|
|
315
386
|
end
|
|
316
387
|
|
|
388
|
+
# Run +cmd+ (a full shell string) under bash, never sh. Accepts the
|
|
389
|
+
# same mode/opts as IO.popen; a leading | marker (Kernel#open heritage)
|
|
390
|
+
# is stripped since the array form needs none.
|
|
391
|
+
def shell cmd, mode="r", **opts
|
|
392
|
+
IO.popen(["bash", "-c", cmd.sub(/\A\|/, "")], mode, **opts)
|
|
393
|
+
end
|
|
394
|
+
|
|
317
395
|
def run cmd=nil
|
|
318
396
|
return unless cmd
|
|
319
|
-
cmd = @cmd_history[cmd][1] if cmd.is_a? Integer
|
|
320
|
-
Typr.flush_overtype
|
|
321
397
|
fmt = @prefixes&.[](cmd[0].to_s)
|
|
322
398
|
if fmt
|
|
323
399
|
body = cmd[1..-1]
|
|
324
400
|
body = body.gsub(/[\\']/) { |m| "\\#{m}" } if fmt =~ /'[^']*%s/
|
|
325
401
|
result = fmt.gsub('%s') { body }
|
|
326
|
-
result.start_with?('!') ? eval(result[1..-1]) :
|
|
402
|
+
result.start_with?('!') ? eval(result[1..-1]) : shell(result + " 2>/dev/null", pgroup: true)
|
|
327
403
|
else
|
|
328
|
-
|
|
404
|
+
shell(cmd + " 2>&1", "r", in: File::NULL)
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# Run a command line from any source — open_file, execute, history.
|
|
409
|
+
# Records history (except !cd), shows pipe and eval results with a
|
|
410
|
+
# keypress pause, reports errors in a popup, and refreshes rows picked
|
|
411
|
+
# from the listing. Returns the run result (or nil).
|
|
412
|
+
def dispatch cmd, name: nil, id: nil, dir: nil
|
|
413
|
+
return unless cmd
|
|
414
|
+
record :cmd, cmd unless cmd[0..2] == '!cd'
|
|
415
|
+
begin
|
|
416
|
+
io = run cmd unless cmd[0] == ?=
|
|
417
|
+
rescue => e
|
|
418
|
+
errs = e.message + "\n" + e.backtrace.first(3).join("\n")
|
|
419
|
+
Text.new(left: 0.15, right: 0.85, top: 0.3, bottom: 0.7,
|
|
420
|
+
header: "command error", input: StringIO.new(errs),
|
|
421
|
+
colors: { default: @colors[:error], border: @colors[:border] },
|
|
422
|
+
alternate: false).pick(:none)
|
|
423
|
+
return
|
|
424
|
+
end
|
|
425
|
+
if cmd[0] == ?=
|
|
426
|
+
body = cmd[1..-1]
|
|
427
|
+
text = Text.new(top: 1, right: -1, bottom: -2, header: body, margin: ' ',
|
|
428
|
+
colors: { regex: :generic, border: @colors[:border] }, input: "")
|
|
429
|
+
io = shell("#{body} 2>&1", "r", in: File::NULL)
|
|
430
|
+
begin
|
|
431
|
+
loop do
|
|
432
|
+
chunk = io.readpartial(4096)
|
|
433
|
+
text << chunk
|
|
434
|
+
text.show
|
|
435
|
+
end
|
|
436
|
+
rescue EOFError
|
|
437
|
+
end
|
|
438
|
+
io.close
|
|
439
|
+
text.pick(:none)
|
|
440
|
+
elsif cmd[0] == ?|
|
|
441
|
+
line = io.gets
|
|
442
|
+
if line
|
|
443
|
+
nc, ac = @colors[:commands]
|
|
444
|
+
head = name ? "#{color_code(nc)}#{name}#{color_code(ac)}:" : color_code(nc)
|
|
445
|
+
@user.show "> #{head}#{cmd[1..-1]}#{color_code(@colors[:default])} : #{line.strip}"
|
|
446
|
+
end
|
|
447
|
+
Process.detach(Process.spawn("cat", in: io, out: File::NULL, pgroup: true, close_others: true)) rescue nil
|
|
448
|
+
elsif io and io.respond_to?(:read) and
|
|
449
|
+
@prefixes&.[](cmd[0].to_s)&.start_with? '!'
|
|
450
|
+
output = io.read.to_s.scrub.strip
|
|
451
|
+
unless output.empty?
|
|
452
|
+
nc, ac = @colors[:commands]
|
|
453
|
+
head = name ? "#{color_code(nc)}#{name}#{color_code(ac)}:" : color_code(nc)
|
|
454
|
+
@user.show "> #{head}#{cmd[1..-1]}#{color_code(@colors[:default])} : #{output.lines.last}"
|
|
455
|
+
show
|
|
456
|
+
Typr.read_key
|
|
457
|
+
end
|
|
329
458
|
end
|
|
459
|
+
if io.is_a?(IO) and !io.closed?
|
|
460
|
+
Thread.new(io) do |pipe|
|
|
461
|
+
begin
|
|
462
|
+
loop { pipe.readpartial(16384) }
|
|
463
|
+
rescue EOFError, IOError
|
|
464
|
+
end
|
|
465
|
+
pipe.close rescue nil
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
Array(id).sort.reverse.each { |i| refresh_row i } if id && cmd[0..2] != '!cd' && @directory == dir
|
|
469
|
+
@selected[:rows] = [] if id && @directory != dir
|
|
470
|
+
show
|
|
471
|
+
io
|
|
330
472
|
end
|
|
331
473
|
|
|
332
474
|
def cmd_history; history @cmd_history, :cmd end
|
|
@@ -346,12 +488,12 @@ class Filecon::App < Typr::Browser
|
|
|
346
488
|
[File.basename(e), e]
|
|
347
489
|
}
|
|
348
490
|
|
|
349
|
-
fc = is_cmd ? (@colors[:cmd_history]
|
|
491
|
+
fc = is_cmd ? (@colors[:cmd_history]) : (@colors[:dir_history])
|
|
350
492
|
fields = {}
|
|
351
493
|
ordered.each_with_index { |(cnt, e), i|
|
|
352
494
|
next unless top_set[e]
|
|
353
|
-
fields[[i,0]] = [fc[0], @colors[:top_history]
|
|
354
|
-
fields[[i,1]] = [fc[1], @colors[:top_history]
|
|
495
|
+
fields[[i,0]] = [fc[0], @colors[:top_history]]
|
|
496
|
+
fields[[i,1]] = [fc[1], @colors[:top_history]]
|
|
355
497
|
}
|
|
356
498
|
|
|
357
499
|
picker = popup(input: pairs, format:[:min,:max],
|
|
@@ -368,12 +510,10 @@ class Filecon::App < Typr::Browser
|
|
|
368
510
|
entry = build(edited, nil)
|
|
369
511
|
return unless entry
|
|
370
512
|
end
|
|
371
|
-
record type, entry
|
|
372
|
-
|
|
373
513
|
if is_cmd
|
|
374
|
-
|
|
375
|
-
input: StringIO.new(e.message + "\n" + e.backtrace.first(3).join("\n"))).pick :none; end
|
|
514
|
+
dispatch entry
|
|
376
515
|
else
|
|
516
|
+
record type, entry
|
|
377
517
|
unless Dir.exist? entry
|
|
378
518
|
@user.show "no such directory: #{entry}"; Typr.read_key; return
|
|
379
519
|
end
|
|
@@ -461,7 +601,11 @@ class Filecon::App < Typr::Browser
|
|
|
461
601
|
|
|
462
602
|
def help
|
|
463
603
|
grid = @user.list
|
|
464
|
-
|
|
604
|
+
grid.border = :round
|
|
605
|
+
grid.colors[:header] = @colors[:header]
|
|
606
|
+
grid.colors[:border] = @colors[:border]
|
|
607
|
+
y = color_code @colors[:yellow]
|
|
608
|
+
b = color_code @colors[:blue]
|
|
465
609
|
d = color_code @colors[:default]
|
|
466
610
|
text = Text.new( input: "\n#{y}FILECON#{d} - a file and command manager that lives between terminal and shell\n\n" \
|
|
467
611
|
"#{y}HINTS:#{d}\n press a number (1-9,0) to open that file with its default action.\n" \
|
|
@@ -470,19 +614,23 @@ class Filecon::App < Typr::Browser
|
|
|
470
614
|
" Mark files with m, then Return opens their command list.\n" \
|
|
471
615
|
" TAB cycles pages of hints.\n\n" \
|
|
472
616
|
"#{y}COMMANDS:#{d}\n" \
|
|
473
|
-
" [e]xecutables browse all executables on your PATH\n" \
|
|
474
|
-
" e[x]ecute type a command line and run it in the shell\n" \
|
|
475
|
-
"
|
|
476
|
-
" [d
|
|
477
|
-
" [
|
|
617
|
+
" #{b}[e]xecutables#{d} browse all executables on your PATH\n" \
|
|
618
|
+
" #{b}e[x]ecute#{d} type a command line and run it in the shell\n" \
|
|
619
|
+
" (TAB completes from bash)\n" \
|
|
620
|
+
" #{b}[R]ecurse#{d} expand the listing to every file below (with paths)\n" \
|
|
621
|
+
" #{b}[d]irectories#{d} jump to a recently visited directory\n" \
|
|
622
|
+
" #{b}[c]ommands#{d} re-run a recent command\n\n" \
|
|
478
623
|
" Directory and command history popups list the most frequently used\n" \
|
|
479
624
|
" entries first, then the rest in most-recent order. The top_history\n" \
|
|
480
625
|
" setting in config.yaml (default 10) controls how many most-used entries\n" \
|
|
481
|
-
" lead the list.\n\n" \
|
|
482
|
-
"#{y}CONFIG:#{d}\n ~/.config/filecon/config.yaml\n"
|
|
626
|
+
" lead the list. In command history, Alt+number edits the picked entry instead of running it.\n\n" \
|
|
627
|
+
"#{y}CONFIG:#{d}\n ~/.config/filecon/config.yaml\n /etc/filecon/config.yaml\n" \
|
|
628
|
+
" (bundled defaults ship with the gem; delete your file to reseed its template)\n",
|
|
483
629
|
top: 2, right: -2, bottom: -3,
|
|
484
|
-
colors: { border: @colors[:
|
|
485
|
-
border: :
|
|
630
|
+
colors: { border: @colors[:border] },
|
|
631
|
+
border: :round )
|
|
632
|
+
text.borders[:top_left] = '┬'
|
|
633
|
+
text.borders[:bottom_left] = '┴'
|
|
486
634
|
text.left = -> { grid.right + 2 }
|
|
487
635
|
loop do
|
|
488
636
|
grid.show
|
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.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kilian Reitmayr
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-10-12 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.9'
|
|
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.9'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: unicode-display_width
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|