filecon 0.1.1 → 0.2.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/README.md +20 -13
- data/bin/filecon +41 -18
- data/config.yaml +72 -28
- data/filecon.1 +22 -3
- data/lib/filecon/version.rb +1 -1
- data/lib/filecon.rb +259 -94
- data/logo_alpha_small.png +0 -0
- metadata +6 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 47e7fc518a42a7037832b88feb1e3c7d3b23c64968de970928104d1fe5c47ef1
|
|
4
|
+
data.tar.gz: e7cc5f0746c241fc6b3d3866b1efb3220b1274870c7446f7a6c48ac8abd0aa10
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f34237e27ff2d45a8aee3e577afd2f6aa5e0bca8e1fbc03fcec77615500aab08b459575ac17715ed89a5c20eac959388e78d7afd0c446281e01fb502d8f41e5b
|
|
7
|
+
data.tar.gz: 9c639e9634aa690a09d4bad83732f16153b6852ffaee84967b55dc140e97ba27a151cd904ff93633c24d12b1dbeadd466c7021b8b75af7050bee87087f66facc
|
data/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
1
3
|
# Filecon
|
|
2
4
|
|
|
3
5
|
A terminal-based file manager built on the [Typr](https://codeberg.org/typr/typr) terminal UI toolkit.
|
|
@@ -16,12 +18,6 @@ A terminal-based file manager built on the [Typr](https://codeberg.org/typr/typr
|
|
|
16
18
|
gem install filecon
|
|
17
19
|
```
|
|
18
20
|
|
|
19
|
-
Or add to your Gemfile:
|
|
20
|
-
|
|
21
|
-
```ruby
|
|
22
|
-
gem "filecon", "~> 0.1"
|
|
23
|
-
```
|
|
24
|
-
|
|
25
21
|
## Usage
|
|
26
22
|
|
|
27
23
|
```bash
|
|
@@ -39,12 +35,13 @@ filecon [directory]
|
|
|
39
35
|
**Actions**
|
|
40
36
|
- `o` — Open list of all commands for selected file
|
|
41
37
|
- `c` — Command history
|
|
38
|
+
- `x` — List all executables from $PATH directories
|
|
42
39
|
- `h` — Help
|
|
43
40
|
- `v` — Views
|
|
44
41
|
- `s` — Sort view
|
|
45
42
|
- `f` — Filter view
|
|
46
43
|
- `r` — Reset view
|
|
47
|
-
- `R` —
|
|
44
|
+
- `R` — Recursive file listing
|
|
48
45
|
|
|
49
46
|
**Marking**
|
|
50
47
|
- `m` — Mark file
|
|
@@ -54,7 +51,7 @@ filecon [directory]
|
|
|
54
51
|
|
|
55
52
|
**Directory**
|
|
56
53
|
- `~` — Go to home directory
|
|
57
|
-
- `` ` `` —
|
|
54
|
+
- `` ` `` — Open commands for current directory
|
|
58
55
|
- `d` — Directory history
|
|
59
56
|
|
|
60
57
|
**Other**
|
|
@@ -63,19 +60,29 @@ filecon [directory]
|
|
|
63
60
|
|
|
64
61
|
### Configuration
|
|
65
62
|
|
|
66
|
-
Config is stored at `~/.config/filecon/config.yaml` and includes:
|
|
63
|
+
Config is stored at `~/.config/filecon/config.yaml` (YAML format) and includes:
|
|
67
64
|
|
|
68
|
-
- Color settings
|
|
69
|
-
- Command definitions per
|
|
65
|
+
- Color settings (named colors, 8-bit palette, RGB triplets)
|
|
66
|
+
- Command definitions per MIME type
|
|
70
67
|
- View configurations
|
|
71
|
-
- History limits
|
|
68
|
+
- History limits and terminal settings
|
|
69
|
+
|
|
70
|
+
**Command prefixes:**
|
|
71
|
+
|
|
72
|
+
- `|` — pipe output to status line
|
|
73
|
+
- `+` — run in a new terminal window
|
|
74
|
+
- `=` — display output in the text viewer
|
|
75
|
+
- `!` — evaluate as Ruby code
|
|
76
|
+
|
|
77
|
+
**Substitution variables:** `%name` (file path), `%dir` (directory picker), `%file` (file picker), `%str` (text input)
|
|
78
|
+
|
|
79
|
+
See `filecon(1)` or the installed `filecon.yaml` for full details.
|
|
72
80
|
|
|
73
81
|
## Development
|
|
74
82
|
|
|
75
83
|
```bash
|
|
76
84
|
git clone https://codeberg.org/typr/filecon
|
|
77
85
|
cd filecon
|
|
78
|
-
bundle install
|
|
79
86
|
rake test
|
|
80
87
|
```
|
|
81
88
|
|
data/bin/filecon
CHANGED
|
@@ -1,29 +1,52 @@
|
|
|
1
1
|
#!/usr/bin/ruby
|
|
2
2
|
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "yaml"
|
|
5
|
+
require_relative "../lib/filecon"
|
|
6
|
+
|
|
3
7
|
begin
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
config_dir = File.expand_path("~/.config/filecon")
|
|
9
|
+
config_path = File.join(config_dir, "config.yaml")
|
|
10
|
+
unless File.exist?(config_path)
|
|
11
|
+
FileUtils.mkpath config_dir
|
|
12
|
+
FileUtils.cp File.join(__dir__, "../config.yaml"), config_path
|
|
13
|
+
end
|
|
14
|
+
state_dir = File.expand_path("~/.local/share/filecon")
|
|
15
|
+
FileUtils.mkpath state_dir
|
|
8
16
|
|
|
9
|
-
|
|
17
|
+
dh_path = File.join(state_dir, "dir_history")
|
|
18
|
+
ch_path = File.join(state_dir, "cmd_history")
|
|
10
19
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
[dh_path, ch_path].each { |f| FileUtils.touch f }
|
|
21
|
+
|
|
22
|
+
parse = ->(p) { File.readlines(p).map { |l| s = l.chomp.split(",", 2); [s[0].to_i, s[1]] } rescue [] }
|
|
23
|
+
|
|
24
|
+
config = YAML.safe_load(File.read(config_path), permitted_classes: [Symbol]) || {}
|
|
25
|
+
|
|
26
|
+
term = config["term"] || ENV["TERM"] || ""
|
|
27
|
+
term += config["term_hold"] if config["term_hold"]
|
|
28
|
+
|
|
29
|
+
Typr.init config["colors"]["default"]
|
|
16
30
|
Typr.clear
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
|
|
32
|
+
app = Filecon::App.new( right: -1, bottom: -1,
|
|
33
|
+
dir_history: parse[dh_path],
|
|
34
|
+
cmd_history: parse[ch_path],
|
|
35
|
+
path: { config: config_path, state: state_dir, dir_history: dh_path,
|
|
36
|
+
cmd_history: ch_path },
|
|
37
|
+
directory: ARGV.first || ?., keymap: { open_file: ?o, cmd_history: ?c, dir_history: ?d, recurse: ?R, open_current: ?` },
|
|
38
|
+
colors: config["colors"] || {},
|
|
39
|
+
views: config["views"] || {},
|
|
40
|
+
commands: config["commands"] || {},
|
|
24
41
|
term: term,
|
|
25
|
-
|
|
42
|
+
max_history: config["max_history"] || 100,
|
|
43
|
+
frequency_limit: config["frequency_limit"] || 10,
|
|
44
|
+
filter_dirs: config["filter_dirs"] )
|
|
45
|
+
Typr.on_resize { app.show }
|
|
46
|
+
app.import_shell_history
|
|
47
|
+
app.fly
|
|
26
48
|
rescue => e
|
|
49
|
+
$stderr.puts "#{e.class}: #{e.message}\n#{e.backtrace.first(10).join("\n")}"
|
|
27
50
|
File.write File.join(Dir.tmpdir, "filecon_error.log"),
|
|
28
51
|
"#{e.class}: #{e.message}\n#{e.backtrace.first(10).join("\n")}"
|
|
29
52
|
ensure
|
data/config.yaml
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
# =============================================================================
|
|
4
4
|
#
|
|
5
5
|
# This is the default configuration file for Filecon, a terminal file manager.
|
|
6
|
-
# Copy this file to ~/.config/filecon/config.yaml and customize as needed.
|
|
7
6
|
#
|
|
8
7
|
# All settings are optional. Filecon will use sensible defaults if values
|
|
9
8
|
# are not specified.
|
|
@@ -22,7 +21,11 @@ term_hold: " --hold"
|
|
|
22
21
|
|
|
23
22
|
# Maximum number of history entries to retain.
|
|
24
23
|
# Older entries beyond this limit are automatically pruned.
|
|
25
|
-
max_history:
|
|
24
|
+
max_history: 10000
|
|
25
|
+
|
|
26
|
+
frequency_limit: 10
|
|
27
|
+
# When true, active filters also hide directories that don't match.
|
|
28
|
+
filter_dirs: true
|
|
26
29
|
|
|
27
30
|
|
|
28
31
|
# =============================================================================
|
|
@@ -32,11 +35,28 @@ max_history: 100
|
|
|
32
35
|
# Customize the color scheme for different file types in the browser view.
|
|
33
36
|
#
|
|
34
37
|
# Color values can be:
|
|
35
|
-
# - Named colors:
|
|
38
|
+
# - Named colors:
|
|
39
|
+
# Basic: black, red, green, yellow, blue, magenta, cyan, white, grey
|
|
40
|
+
# Shades: brown, orange, lime, pink, maroon, navy, teal, olive, coral, tan
|
|
41
|
+
# Dark: dark_red, dark_green, dark_yellow, dark_blue,
|
|
42
|
+
# dark_magenta, dark_cyan
|
|
36
43
|
# - Grey shades: grey0 through grey100 (e.g., grey62 for 62% grey)
|
|
37
44
|
# - 8-bit palette: 0 - 255 (maps to terminal's 256-color palette)
|
|
38
45
|
# - RGB triplets: [0-255, 0-255, 0-255] (true color terminals)
|
|
39
46
|
#
|
|
47
|
+
# Color settings passed to Typr spaces:
|
|
48
|
+
# default global default foreground/background [fg, bg]
|
|
49
|
+
# border widget borders
|
|
50
|
+
# header column/panel headers
|
|
51
|
+
# question prompt question text
|
|
52
|
+
# answer typed input text
|
|
53
|
+
# selected highlighted / selected row background
|
|
54
|
+
# alternate alternating row background in lists
|
|
55
|
+
# hints hint numbers/digits
|
|
56
|
+
# actions named action colors: "[f]ilter", "[s]ort", "[v]iews"
|
|
57
|
+
# commands command picker and status line: [name, args] colors
|
|
58
|
+
# search search-match highlight (text viewer)
|
|
59
|
+
#
|
|
40
60
|
# Usage:
|
|
41
61
|
# Foreground color only: "blue"
|
|
42
62
|
# Foreground + background: ["white", "black"]
|
|
@@ -45,12 +65,16 @@ max_history: 100
|
|
|
45
65
|
|
|
46
66
|
colors:
|
|
47
67
|
default: ["white", "black"]
|
|
68
|
+
# [name, args] colors for the command picker and the status line
|
|
69
|
+
# shown while building/running a command
|
|
70
|
+
commands: ["green", "red"]
|
|
48
71
|
types:
|
|
49
72
|
image: "blue"
|
|
50
73
|
audio: "green"
|
|
51
74
|
video: "cyan"
|
|
52
75
|
directory: "yellow"
|
|
53
76
|
"x-pie-executable": "red"
|
|
77
|
+
# executable: "red"
|
|
54
78
|
|
|
55
79
|
|
|
56
80
|
# =============================================================================
|
|
@@ -91,37 +115,58 @@ colors:
|
|
|
91
115
|
#
|
|
92
116
|
# %str - Prompts for text input, inserts the string
|
|
93
117
|
#
|
|
118
|
+
# SPECIAL CATEGORIES:
|
|
119
|
+
#
|
|
120
|
+
# executable - Added to the command picker for any file whose
|
|
121
|
+
# executable bit is set for the current user, regardless
|
|
122
|
+
# of its MIME type (e.g. shell scripts with shebangs).
|
|
123
|
+
#
|
|
94
124
|
# =============================================================================
|
|
95
125
|
|
|
96
126
|
commands:
|
|
97
127
|
default:
|
|
98
|
-
|
|
99
|
-
diskuse: "|du -hs %name"
|
|
128
|
+
# copy: "|rsync --progress %name %dir"
|
|
100
129
|
info: "=file %name"
|
|
101
|
-
copy: "
|
|
130
|
+
copy: "|cp -R %name %dir"
|
|
102
131
|
move: "|mv %name %dir"
|
|
103
|
-
|
|
132
|
+
rename: "|mv %name %str"
|
|
133
|
+
delete: "|rm -rf %name"
|
|
134
|
+
open_with: "|%file %name %str"
|
|
135
|
+
open_with_terminal: "+%file %name %str"
|
|
136
|
+
xdg_open: "|xdg-open %name"
|
|
137
|
+
|
|
138
|
+
executable:
|
|
139
|
+
run: "|%name %str"
|
|
140
|
+
run_terminal: "+%name %str"
|
|
141
|
+
help: "=%name --help"
|
|
142
|
+
manual: "+man %name"
|
|
104
143
|
|
|
105
144
|
directory:
|
|
106
145
|
cd: "!cd %name"
|
|
107
|
-
|
|
146
|
+
new_file: "|touch %name/%str"
|
|
147
|
+
new_dir: "|mkdir %name/%str"
|
|
148
|
+
shell: "+$SHELL -c 'cd %name && exec $SHELL'"
|
|
149
|
+
# zsh: "+zsh -c 'cd %name && exec zsh'"
|
|
150
|
+
disk_use: "|du -hs %name | cut -wf1"
|
|
151
|
+
|
|
108
152
|
text:
|
|
109
153
|
view: "+moor %name"
|
|
110
154
|
edit: "+micro %name"
|
|
111
|
-
count: "|wc -l %name"
|
|
155
|
+
count: "|wc -l %name | cut -wf1"
|
|
112
156
|
|
|
113
157
|
troff:
|
|
114
158
|
manual: "+man %name"
|
|
115
159
|
|
|
116
160
|
"x-ruby":
|
|
117
|
-
run: "
|
|
161
|
+
run: "|ruby %name"
|
|
162
|
+
run_terminal: "+ruby %name"
|
|
118
163
|
|
|
119
164
|
html:
|
|
120
165
|
terminal: "+cha %name"
|
|
121
166
|
graphics: "|vimb %name"
|
|
122
167
|
|
|
123
168
|
image:
|
|
124
|
-
|
|
169
|
+
view: "|pqiv %name"
|
|
125
170
|
|
|
126
171
|
video:
|
|
127
172
|
play: "|mpv %name"
|
|
@@ -132,12 +177,6 @@ commands:
|
|
|
132
177
|
pdf:
|
|
133
178
|
view: "|mupdf %name"
|
|
134
179
|
|
|
135
|
-
"x-pie-executable":
|
|
136
|
-
run: "|%name"
|
|
137
|
-
run_terminal: "+%name"
|
|
138
|
-
help: "=%name --help"
|
|
139
|
-
manual: "+man %name"
|
|
140
|
-
|
|
141
180
|
"x-python":
|
|
142
181
|
run: "+python3 %name"
|
|
143
182
|
|
|
@@ -176,23 +215,23 @@ commands:
|
|
|
176
215
|
view: "=cat %name | jq ."
|
|
177
216
|
|
|
178
217
|
"zip":
|
|
179
|
-
list: "
|
|
218
|
+
list: "=unzip -l %name"
|
|
180
219
|
extract: "|unzip %name -d %dir"
|
|
181
220
|
|
|
182
221
|
"x-gzip":
|
|
183
|
-
list: "
|
|
222
|
+
list: "=tar -tzf %name"
|
|
184
223
|
extract: "|tar -xzf %name -C %dir"
|
|
185
224
|
|
|
186
225
|
"x-bzip2":
|
|
187
|
-
list: "
|
|
226
|
+
list: "=tar -tjf %name"
|
|
188
227
|
extract: "|tar -xjf %name -C %dir"
|
|
189
228
|
|
|
190
229
|
"x-xz":
|
|
191
|
-
list: "
|
|
230
|
+
list: "=tar -tJf %name"
|
|
192
231
|
extract: "|tar -xJf %name -C %dir"
|
|
193
232
|
|
|
194
233
|
"x-tar":
|
|
195
|
-
list: "
|
|
234
|
+
list: "=tar -tf %name"
|
|
196
235
|
extract: "|tar -xf %name -C %dir"
|
|
197
236
|
|
|
198
237
|
"epub+zip":
|
|
@@ -217,10 +256,10 @@ commands:
|
|
|
217
256
|
# - A string Command to execute (with %name substitution)
|
|
218
257
|
# - A symbol Reference to a system field (e.g., :size, :type, :subtype)
|
|
219
258
|
#
|
|
220
|
-
# Prefix ! to evaluate as Ruby
|
|
259
|
+
# Prefix ! to evaluate as Ruby
|
|
221
260
|
#
|
|
222
261
|
# Available system fields:
|
|
223
|
-
# target, permissions, owner, group, size,
|
|
262
|
+
# target, permissions, executable, owner, group, size,
|
|
224
263
|
# accessed, modified, created, type, subtype
|
|
225
264
|
#
|
|
226
265
|
# When a view is activated, its columns are appended to the default file
|
|
@@ -232,13 +271,18 @@ views:
|
|
|
232
271
|
content:
|
|
233
272
|
content: "head -n 50 %name"
|
|
234
273
|
|
|
235
|
-
|
|
274
|
+
types:
|
|
236
275
|
type: true
|
|
237
276
|
subtype: true
|
|
238
277
|
description: "file -b %name"
|
|
239
278
|
|
|
240
|
-
|
|
241
|
-
|
|
279
|
+
disk_use:
|
|
280
|
+
du: "du -hs %name | cut -wf1"
|
|
281
|
+
|
|
282
|
+
count:
|
|
283
|
+
chars: "wc -m %name | cut -wf1"
|
|
284
|
+
words: "wc -w %name | cut -wf1"
|
|
285
|
+
lines: "wc -l %name | cut -wf1"
|
|
242
286
|
|
|
243
287
|
whatis:
|
|
244
|
-
whatis: "
|
|
288
|
+
whatis: "whatis $(basename %name) 2>&1 | sed 's/.* - //'"
|
data/filecon.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH FILECON 1 "2026-07-
|
|
1
|
+
.TH FILECON 1 "2026-07-27" "filecon 0.1.1" "User Commands"
|
|
2
2
|
.SH NAME
|
|
3
3
|
filecon \- interactive terminal file manager with hint-based navigation
|
|
4
4
|
.SH SYNOPSIS
|
|
@@ -60,7 +60,7 @@ Filter rows
|
|
|
60
60
|
Recurse directory tree
|
|
61
61
|
.TP
|
|
62
62
|
.B `
|
|
63
|
-
|
|
63
|
+
Open commands for current directory
|
|
64
64
|
.TP
|
|
65
65
|
.B ~
|
|
66
66
|
Navigate to home directory
|
|
@@ -77,10 +77,16 @@ Open list of all commands for selected file
|
|
|
77
77
|
.TP
|
|
78
78
|
.B c
|
|
79
79
|
Browse command history
|
|
80
|
+
.PP
|
|
81
|
+
Directory and command history popups list the most frequently used entries
|
|
82
|
+
first, then the remaining entries in most-recent order. The
|
|
83
|
+
.B frequency_limit
|
|
84
|
+
setting in config.yaml (default 10) controls how many frequent entries lead
|
|
85
|
+
the list.
|
|
80
86
|
.SH CONFIGURATION
|
|
81
87
|
On first run, the default
|
|
82
88
|
.B config.yaml
|
|
83
|
-
is copied from the installation directory to
|
|
89
|
+
(YAML format) is copied from the installation directory to
|
|
84
90
|
.BR ~/.config/filecon/ .
|
|
85
91
|
.SS Config File Locations
|
|
86
92
|
.TP
|
|
@@ -92,6 +98,19 @@ Directory navigation history
|
|
|
92
98
|
.TP
|
|
93
99
|
.B ~/.config/filecon/cmd_history
|
|
94
100
|
Command execution history
|
|
101
|
+
.SS Command Prefixes
|
|
102
|
+
.TP
|
|
103
|
+
.B |
|
|
104
|
+
Pipe output to status line
|
|
105
|
+
.TP
|
|
106
|
+
.B +
|
|
107
|
+
Run in a new terminal window
|
|
108
|
+
.TP
|
|
109
|
+
.B =
|
|
110
|
+
Display output in the text viewer
|
|
111
|
+
.TP
|
|
112
|
+
.B !
|
|
113
|
+
Evaluate as Ruby code
|
|
95
114
|
.SS Command Substitutions
|
|
96
115
|
.TP
|
|
97
116
|
.B %name
|
data/lib/filecon/version.rb
CHANGED
data/lib/filecon.rb
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
require "fileutils"
|
|
2
|
-
require "open3"
|
|
3
|
-
require "yaml"
|
|
4
|
-
|
|
5
1
|
begin
|
|
6
2
|
require_relative "../../typr/lib/typr"
|
|
7
3
|
rescue LoadError
|
|
@@ -10,54 +6,30 @@ end
|
|
|
10
6
|
|
|
11
7
|
require_relative "filecon/version"
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def self.dir_history_path
|
|
17
|
-
File.join(PATH, "dir_history")
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def self.cmd_history_path
|
|
21
|
-
File.join(PATH, "cmd_history")
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def self.setup_config
|
|
25
|
-
FileUtils.mkpath PATH unless File.exist? PATH
|
|
26
|
-
FileUtils.cp File.join(__dir__, "../config.yaml"), PATH unless File.exist? File.join(PATH, "config.yaml")
|
|
27
|
-
FileUtils.touch dir_history_path
|
|
28
|
-
FileUtils.touch cmd_history_path
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def self.deep_symbolize_keys(obj)
|
|
32
|
-
case obj
|
|
33
|
-
when Hash
|
|
34
|
-
obj.each_with_object({}) { |(k, v), h| h[k.to_sym] = deep_symbolize_keys(v) }
|
|
35
|
-
when Array
|
|
36
|
-
obj.map { |v| deep_symbolize_keys(v) }
|
|
37
|
-
else
|
|
38
|
-
obj
|
|
39
|
-
end
|
|
40
|
-
end
|
|
9
|
+
class Filecon::App < Typr::Browser
|
|
10
|
+
include Typr
|
|
41
11
|
|
|
42
|
-
def
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
12
|
+
def cd dir, append=false
|
|
13
|
+
if dir.is_a? Array
|
|
14
|
+
dirs = dir.filter_map { |d| d = File.expand_path(d.to_s); Dir.exist?(d) ? d : nil }
|
|
15
|
+
dirs.each_with_index { |d, i|
|
|
16
|
+
@directory = d if i.positive?
|
|
17
|
+
Dir.chdir(d) if i.positive?
|
|
18
|
+
super d, i.positive?
|
|
19
|
+
}
|
|
20
|
+
@directory = dirs
|
|
21
|
+
nil
|
|
46
22
|
else
|
|
47
|
-
|
|
23
|
+
dir = @dir_history[dir+1][1] if dir.is_a? Integer
|
|
24
|
+
record :dir, File.expand_path(dir) unless append || [?/, ENV["HOME"]].include?(dir)
|
|
25
|
+
super dir, append
|
|
26
|
+
nil
|
|
48
27
|
end
|
|
49
28
|
end
|
|
50
|
-
end
|
|
51
29
|
|
|
52
|
-
|
|
53
|
-
include Typr
|
|
54
|
-
|
|
55
|
-
def cd dir, append=false; super
|
|
56
|
-
File.write Filecon.dir_history_path,
|
|
57
|
-
@dir_history[0..Filecon.config["max_history"]].join($/)
|
|
58
|
-
return
|
|
59
|
-
end
|
|
30
|
+
def execute; cd ENV["PATH"].split(":"); add_path_column end
|
|
60
31
|
|
|
32
|
+
#switch_view
|
|
61
33
|
def switch_to id=nil
|
|
62
34
|
return unless id
|
|
63
35
|
id = @views.keys[id] if id.is_a? Integer
|
|
@@ -66,9 +38,13 @@ class Filecon::App < Typr::Browser
|
|
|
66
38
|
data = @data.map.with_index{ |row, id| row + view.values.map{ |cmd|
|
|
67
39
|
next unless cmd.is_a?(String)
|
|
68
40
|
built = build cmd, id
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
41
|
+
next unless built
|
|
42
|
+
io = if built[0] == ?!
|
|
43
|
+
StringIO.new eval( built[1..-1] ).to_s
|
|
44
|
+
else
|
|
45
|
+
cmd = built[0] == ?| ? built : ?| + built
|
|
46
|
+
open cmd + " 2>&1"
|
|
47
|
+
end
|
|
72
48
|
io.read(width).to_s.scrub.gsub(/[\n\t]/, ?|)
|
|
73
49
|
}.compact }
|
|
74
50
|
@sequence = [ NAME ] + view.keys.map{ |head|
|
|
@@ -76,51 +52,79 @@ class Filecon::App < Typr::Browser
|
|
|
76
52
|
clear
|
|
77
53
|
self << data
|
|
78
54
|
@current = id
|
|
55
|
+
@view = id
|
|
79
56
|
end
|
|
80
57
|
|
|
81
|
-
def open_file id=nil, default=false
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
58
|
+
def open_file id=nil, default=false, types=nil
|
|
59
|
+
unless id || types
|
|
60
|
+
return unless id ||= @selected[:rows].empty? ?
|
|
61
|
+
@user.ask( open:[self, :row, NAME] ) : @selected[:rows]
|
|
62
|
+
end
|
|
63
|
+
types ||= if id.is_a? Array
|
|
85
64
|
eval id.map{ |id| @data[id].values_at(SUBTYPE,TYPE).to_s }.join(?&)
|
|
86
65
|
else @data[id].values_at(SUBTYPE, TYPE)
|
|
87
|
-
end - [??] + [
|
|
66
|
+
end - [??] + ["default"]
|
|
67
|
+
if id && (id.is_a?(Array) ? id.all?{ |i| @data[i][EXECUTABLE] } : @data[id][EXECUTABLE])
|
|
68
|
+
types.insert(types.index("default") || types.size, "executable")
|
|
69
|
+
end
|
|
88
70
|
|
|
89
|
-
commands = @commands.values_at( *types
|
|
90
|
-
|
|
71
|
+
commands = @commands.values_at( *types ).map(&:to_a).flatten( 1 ).uniq
|
|
72
|
+
pair = if default; commands.first
|
|
91
73
|
else move left+1,top+1
|
|
92
|
-
if id.is_a?(Array);
|
|
74
|
+
if id.is_a?(Array); draw "#{id.count.to_s} files: (#{
|
|
93
75
|
types.join ?, })".ljust width
|
|
94
76
|
else print id end
|
|
95
|
-
|
|
77
|
+
pop = id ? top+2 : top
|
|
78
|
+
return unless cmd = Grid.new( left:left+1, top: pop, right: -1,
|
|
79
|
+
bottom: [ pop + commands.count - 1, Typr.height ].min,
|
|
96
80
|
input:commands, border: :light, format: [:min, :max],
|
|
97
|
-
borders: { top: nil }, alternate: false,
|
|
98
|
-
colors: {columns: [:green, :red] } ).pick( :row )
|
|
99
|
-
commands[ cmd ]
|
|
81
|
+
borders: { top: nil }, alternate: false, hints: "1234567890",
|
|
82
|
+
colors: {columns: @colors[:commands] || [:green, :red] } ).pick( :row, column: 0 )
|
|
83
|
+
commands[ cmd ]
|
|
84
|
+
end
|
|
85
|
+
cmd = build pair.last, id, pair.first
|
|
86
|
+
return unless cmd
|
|
87
|
+
if cmd[0] == ?|
|
|
88
|
+
display = cmd[1..-1]
|
|
89
|
+
record :cmd, cmd
|
|
90
|
+
io = run( cmd )
|
|
91
|
+
output = io.read.to_s.scrub.strip
|
|
92
|
+
nc, ac = @colors[:commands] || [:green, :red]
|
|
93
|
+
@user.show "> #{color_code(nc)}#{pair.first}#{color_code(ac)}:#{display}" \
|
|
94
|
+
"#{color_code(@colors[:default])} : #{output.lines.last}"
|
|
95
|
+
Typr.read_key
|
|
96
|
+
else
|
|
97
|
+
record :cmd, cmd unless cmd[0..2] == '!cd'
|
|
98
|
+
run( cmd )
|
|
99
|
+
show
|
|
100
100
|
end
|
|
101
|
-
cmd = build cmd, id
|
|
102
|
-
if cmd.start_with? ?|
|
|
103
|
-
fork { io = run( cmd, true )
|
|
104
|
-
until io.eof?
|
|
105
|
-
@user.draw io.readline.strip.scrub
|
|
106
|
-
sleep 0.1
|
|
107
|
-
end
|
|
108
|
-
}
|
|
109
|
-
else run( cmd, cmd[0..2] != '!cd' ) end
|
|
110
|
-
draw
|
|
111
101
|
end
|
|
112
102
|
|
|
113
|
-
def build str, id
|
|
103
|
+
def build str, id, name=nil
|
|
104
|
+
nc, ac = @colors[:commands] || [:green, :red]
|
|
105
|
+
reset = color_code( @colors[:default] )
|
|
106
|
+
prefix = name ? "#{color_code(nc)}#{name}#{color_code(ac)}:" : ""
|
|
114
107
|
cmd = ""
|
|
115
108
|
while str[/\%(name|dir|file|str)/]
|
|
116
|
-
|
|
109
|
+
cmd += $`
|
|
117
110
|
case $&
|
|
118
|
-
when '%name'; cmd += ?"+( id.
|
|
119
|
-
|
|
120
|
-
@
|
|
121
|
-
|
|
122
|
-
when '%
|
|
123
|
-
|
|
111
|
+
when '%name'; cmd += ?"+( id.nil? ? @directory :
|
|
112
|
+
id.is_a?( Array ) ? id.map{ |id|
|
|
113
|
+
@data[id][NAME] }.join('" "') :
|
|
114
|
+
@data[id][NAME] )+?"
|
|
115
|
+
when '%dir'
|
|
116
|
+
@user.show "> #{prefix}#{cmd}#{$&}#{$'}#{reset}"
|
|
117
|
+
return unless dir = pick(:directory)
|
|
118
|
+
cmd += ?" + dir + ?"
|
|
119
|
+
when '%file'
|
|
120
|
+
@user.show "> #{prefix}#{cmd}#{$&}#{$'}#{reset}"
|
|
121
|
+
return unless file = pick(:file)
|
|
122
|
+
cmd += ?" + file + ?"
|
|
123
|
+
when '%str'
|
|
124
|
+
prompt = cmd.empty? || cmd.end_with?(' ') ? cmd : "#{cmd} "
|
|
125
|
+
query = Typr.read_line "> #{prefix}#{prompt}", left: left, top: @user.top
|
|
126
|
+
return unless query
|
|
127
|
+
cmd += query
|
|
124
128
|
end
|
|
125
129
|
str = str[($`+$&).size..-1]
|
|
126
130
|
end
|
|
@@ -128,46 +132,207 @@ class Filecon::App < Typr::Browser
|
|
|
128
132
|
return cmd
|
|
129
133
|
end
|
|
130
134
|
|
|
131
|
-
def run cmd=nil
|
|
135
|
+
def run cmd=nil
|
|
132
136
|
return unless cmd
|
|
133
|
-
cmd = @cmd_history[cmd] if cmd.is_a? Integer
|
|
137
|
+
cmd = @cmd_history[cmd][1] if cmd.is_a? Integer
|
|
134
138
|
case cmd[0]
|
|
135
139
|
when ?! then io = StringIO.new eval( cmd[1..-1] ).to_s
|
|
136
140
|
when ?| then io = open( cmd + " 2>&1" )
|
|
137
141
|
when ?+ then io = open( ?| + @term +' '+ cmd[1..-1] + " 2>&1" )
|
|
138
142
|
when ?= then Text.new(top: 2, right:-1, bottom:-2, border: :light,
|
|
139
143
|
input:open( ?|+cmd[1..-1]+" 2>&1" ) ).pick :none
|
|
140
|
-
|
|
141
|
-
if store
|
|
142
|
-
@cmd_history.unshift cmd
|
|
143
|
-
@cmd_history.uniq!
|
|
144
|
-
File.write Filecon.cmd_history_path, @cmd_history[0..Filecon.config["max_history"]].join($/)
|
|
144
|
+
else io = open( ?| + @term +' '+ cmd + " 2>&1" )
|
|
145
145
|
end
|
|
146
146
|
return io
|
|
147
147
|
end
|
|
148
148
|
|
|
149
|
-
def cmd_history;
|
|
150
|
-
|
|
149
|
+
def cmd_history; history @cmd_history, :cmd end
|
|
150
|
+
def dir_history; history @dir_history, :dir end
|
|
151
|
+
|
|
152
|
+
def history data, type
|
|
153
|
+
is_cmd = type == :cmd
|
|
154
|
+
data = data.drop 1 unless is_cmd
|
|
155
|
+
return unless data.any?
|
|
156
|
+
|
|
157
|
+
top = data.max_by(@frequency_limit) { |cnt, _| cnt }
|
|
158
|
+
top_set = top.filter_map { |_, e| e }.to_h { |e| [e, true] }
|
|
159
|
+
ordered = top + data.reject { |_, e| top_set[e] }
|
|
160
|
+
|
|
161
|
+
pairs = ordered.map { |_, e|
|
|
162
|
+
is_cmd ? [e.split(' ', 2)[0], e.split(' ', 2)[1] || ""] :
|
|
163
|
+
[File.basename(e), e]
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fc = is_cmd ? [:red, :blue] : [:yellow, :brown]
|
|
167
|
+
fields = {}
|
|
168
|
+
ordered.each_with_index { |(cnt, e), i|
|
|
169
|
+
next unless top_set[e]
|
|
170
|
+
fields[[i,0]] = [fc[0], :grey20]
|
|
171
|
+
fields[[i,1]] = [fc[1], :grey20]
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
n = popup(input: pairs, hints:"1234567890", format:[:min,:max],
|
|
175
|
+
alternate: true, left: left, bottom: -2,
|
|
176
|
+
colors: { columns: fc, fields: fields }).pick(:row, column: :all)
|
|
177
|
+
return unless n
|
|
178
|
+
|
|
179
|
+
entry = ordered[n][1]
|
|
180
|
+
record type, entry
|
|
181
|
+
|
|
182
|
+
if is_cmd
|
|
183
|
+
begin; run entry; rescue => e; Text.new(top: 2, right:-1, bottom:-2, border: :light,
|
|
184
|
+
input: StringIO.new(e.message + "\n" + e.backtrace.first(3).join("\n"))).pick :none; end
|
|
185
|
+
else
|
|
186
|
+
unless Dir.exist? entry
|
|
187
|
+
@user.show "no such directory: #{entry}"; Typr.read_key; return
|
|
188
|
+
end
|
|
189
|
+
cd entry
|
|
190
|
+
nil
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def import_shell_history
|
|
195
|
+
sync = File.join(@path[:state], "shell_sync"); return if File.exist?(sync)
|
|
196
|
+
|
|
197
|
+
@user.show "importing shell history..."
|
|
198
|
+
valid = ENV["PATH"].split(":").each_with_object({}) { |d, v|
|
|
199
|
+
Dir.entries(d).each { |e| v[e] = true } rescue nil }
|
|
200
|
+
|
|
201
|
+
builtins = %w[cd . alias bg break builtin command continue declare dirs echo
|
|
202
|
+
eval exec exit export false fc fg getopts hash history jobs kill let limit
|
|
203
|
+
local logoff logout mapfile popd printf pushd pwd read readonly return set
|
|
204
|
+
shift shopt source suspend test times trap true type typeset ulimit umask
|
|
205
|
+
unalias unset wait whence which]
|
|
206
|
+
|
|
207
|
+
aliases = {}
|
|
208
|
+
[".zshrc", ".bashrc"].each { |rc|
|
|
209
|
+
File.readlines(File.join(ENV["HOME"], rc)).each { |l|
|
|
210
|
+
l =~ /\A\s*alias\s+(\w+)=(?:'([^']*)'|"([^"]*)"|(\S+))/ &&
|
|
211
|
+
aliases[$1] = ($2 || $3 || $4 || "").split.first } rescue nil }
|
|
212
|
+
|
|
213
|
+
last = 0
|
|
214
|
+
lines = 0
|
|
215
|
+
[File.expand_path("~/.bash_history"), File.expand_path("~/.zsh_history")].each { |p|
|
|
216
|
+
next unless File.exist?(p)
|
|
217
|
+
@user.show "importing shell history: #{File.basename(p)}..."
|
|
218
|
+
File.readlines(p).each { |l|
|
|
219
|
+
c = l.chomp.scrub(''); next if c.empty? || c[0] == '#'
|
|
220
|
+
if p.end_with?('zsh_history')
|
|
221
|
+
ts = c[/\A: (\d+):\d+;(.*)/, 1]&.to_i
|
|
222
|
+
c = c[/\A: \d+:\d+;(.*)/, 1] || c
|
|
223
|
+
last = ts if ts && ts > last
|
|
224
|
+
end
|
|
225
|
+
next if c.empty?
|
|
226
|
+
first = c.split.first; next unless first
|
|
227
|
+
r = first; 10.times { break unless aliases[r]; r = aliases[r] }
|
|
228
|
+
c = r + c[first.size..] if r != first
|
|
229
|
+
next unless valid[r] || builtins.include?(r)
|
|
230
|
+
record :cmd, c, write: false unless %w[cd pushd].include?(r)
|
|
231
|
+
c.split.each { |t|
|
|
232
|
+
t = t.delete_prefix('"').delete_prefix("'").delete_suffix('"').delete_suffix("'")
|
|
233
|
+
next unless t.start_with?('/', '~')
|
|
234
|
+
t = File.expand_path(t) rescue nil
|
|
235
|
+
record :dir, t, write: false if t && Dir.exist?(t)
|
|
236
|
+
}
|
|
237
|
+
lines += 1
|
|
238
|
+
@user.show "importing shell history: #{lines} lines..." if lines % 500 == 0
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
File.write(@path[:cmd_history], @cmd_history.map { |c, e| "#{c},#{e}" }.join($/))
|
|
242
|
+
File.write(@path[:dir_history], @dir_history.map { |c, e| "#{c},#{e}" }.join($/))
|
|
243
|
+
File.write(sync, last.to_s)
|
|
244
|
+
@user.reset
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def record type, entry, write: true
|
|
248
|
+
h = type == :cmd ? @cmd_history : @dir_history
|
|
249
|
+
idx = h.index { |_, e| e == entry }
|
|
250
|
+
cnt = idx ? (h.delete_at(idx)[0] + 1) : 1
|
|
251
|
+
h.unshift [cnt, entry]
|
|
252
|
+
h.slice!(@max_history..) if h.size > @max_history
|
|
253
|
+
File.write @path[:"#{type}_history"], h.map { |c, e| "#{c},#{e}" }.join($/) if write
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def recurse
|
|
257
|
+
return if @directory.is_a?(Array)
|
|
258
|
+
root = File.expand_path(@directory)
|
|
259
|
+
clear
|
|
260
|
+
cd root
|
|
261
|
+
Dir.glob("**/*", File::FNM_DOTMATCH, base: root).sort.each { |p|
|
|
262
|
+
next if !@show_hidden && p.split(?/).any? { |c| c.start_with?(?.) }
|
|
263
|
+
next unless File.directory?(File.join(root, p))
|
|
264
|
+
d = File.expand_path(p, root)
|
|
265
|
+
@directory = d
|
|
266
|
+
Dir.chdir d
|
|
267
|
+
cd d, true
|
|
268
|
+
}
|
|
269
|
+
Dir.chdir root
|
|
270
|
+
@directory = root
|
|
271
|
+
add_path_column
|
|
272
|
+
@current = 0
|
|
273
|
+
nil
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def add_path_column
|
|
277
|
+
Typr.const_set :PATH, @header.size unless Typr.const_defined?(:PATH)
|
|
278
|
+
@header << "path" unless @header.include?("path")
|
|
279
|
+
@data.each { |row| row[PATH] = File.dirname(row[NAME]) }
|
|
280
|
+
@sequence.insert(@sequence.index(NAME) || 0, PATH) unless @sequence.include?(PATH)
|
|
281
|
+
# @align[@header.size-1] = :right
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def help
|
|
285
|
+
info = "\nFILECON - a terminal file manager\n\n" \
|
|
286
|
+
"HINTS: press a number (1-9,0) to open that file with its default action.\n" \
|
|
287
|
+
" TAB cycles pages of hints.\n\n" \
|
|
288
|
+
"COMMANDS:\n" \
|
|
289
|
+
" e[x]ecute browse all executables on your PATH\n" \
|
|
290
|
+
" [R]ecurse expand the current directory tree (subdirectories listed inline)\n" \
|
|
291
|
+
" [d]irectories jump to a recently visited directory\n" \
|
|
292
|
+
" [c]ommands re-run a recent command\n\n" \
|
|
293
|
+
" Directory and command history popups list the most frequently used\n" \
|
|
294
|
+
" entries first, then the rest in most-recent order. The frequency_limit\n" \
|
|
295
|
+
" setting in config.yaml (default 10) controls how many frequent entries\n" \
|
|
296
|
+
" lead the list.\n\n" \
|
|
297
|
+
"CONFIG: ~/.config/filecon/config.yaml\n\n" \
|
|
298
|
+
"KEYBINDINGS:\n\n" + @user.bindings.join(?\n)
|
|
299
|
+
Text.new( input: info, top:2, left:3, right: -3, bottom: -3, header: "HELP",
|
|
300
|
+
colors: { header: [:black,:white], border:[:white,:grey10] },
|
|
301
|
+
border: :light ).pick :none
|
|
151
302
|
end
|
|
152
303
|
|
|
153
304
|
def initialize args={}
|
|
154
305
|
super
|
|
306
|
+
@rawsort[NAME] = false
|
|
307
|
+
@cmd_history ||= []
|
|
308
|
+
@dir_history ||= []
|
|
309
|
+
@path ||= {}
|
|
310
|
+
@frequency_limit = args[:frequency_limit] || 10
|
|
311
|
+
@keymap[:open_current] = ?`
|
|
312
|
+
@keymap[:execute] = ?x
|
|
313
|
+
@keymap[:recurse] = ?R
|
|
155
314
|
@user.bindings.insert 1, "[o]pen"
|
|
156
|
-
@user.bindings.insert 7, "[
|
|
157
|
-
@user.
|
|
315
|
+
@user.bindings.insert 7, "[R]ecurse"
|
|
316
|
+
@user.bindings.insert 8, "[c]ommands"
|
|
317
|
+
@user.bindings.insert 9, "e[x]ecute"
|
|
318
|
+
@user.bindings.insert 11, "[`]:open_current"
|
|
319
|
+
@user.reset
|
|
158
320
|
end
|
|
159
321
|
|
|
322
|
+
def open_current; open_file nil, false, ["directory", "inode"] - [??] + ["default"] end
|
|
323
|
+
|
|
160
324
|
def fly
|
|
161
325
|
loop do
|
|
162
|
-
|
|
326
|
+
show
|
|
163
327
|
draw_hints :rows
|
|
164
|
-
key = Typr.
|
|
165
|
-
|
|
328
|
+
key = Typr.read_key
|
|
329
|
+
next unless key
|
|
166
330
|
case key
|
|
167
331
|
when KEY_ESCAPE; exit
|
|
168
332
|
else
|
|
169
333
|
id = send( key )
|
|
170
|
-
open_file id, true if id.is_a? Integer
|
|
334
|
+
open_file( id, true) if id.is_a? Integer
|
|
335
|
+
@user.reset
|
|
171
336
|
end
|
|
172
337
|
end
|
|
173
338
|
end
|
|
Binary file
|
metadata
CHANGED
|
@@ -1,14 +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.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kilian Reitmayr
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: typr
|
|
@@ -16,14 +15,14 @@ dependencies:
|
|
|
16
15
|
requirements:
|
|
17
16
|
- - "~>"
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '1.
|
|
18
|
+
version: '1.2'
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - "~>"
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '1.
|
|
25
|
+
version: '1.2'
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
27
|
name: unicode-display_width
|
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -82,11 +81,11 @@ files:
|
|
|
82
81
|
- filecon.1
|
|
83
82
|
- lib/filecon.rb
|
|
84
83
|
- lib/filecon/version.rb
|
|
84
|
+
- logo_alpha_small.png
|
|
85
85
|
homepage: https://codeberg.org/typr/filecon
|
|
86
86
|
licenses:
|
|
87
87
|
- GPL-2.0-only
|
|
88
88
|
metadata: {}
|
|
89
|
-
post_install_message:
|
|
90
89
|
rdoc_options: []
|
|
91
90
|
require_paths:
|
|
92
91
|
- lib
|
|
@@ -101,8 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
101
100
|
- !ruby/object:Gem::Version
|
|
102
101
|
version: '0'
|
|
103
102
|
requirements: []
|
|
104
|
-
rubygems_version: 3.
|
|
105
|
-
signing_key:
|
|
103
|
+
rubygems_version: 3.6.9
|
|
106
104
|
specification_version: 4
|
|
107
105
|
summary: Terminal file manager built on Typr
|
|
108
106
|
test_files: []
|