filecon 0.1.2 → 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 +5 -9
- data/bin/filecon +30 -17
- data/config.yaml +70 -30
- data/filecon.1 +6 -0
- data/lib/filecon/version.rb +1 -1
- data/lib/filecon.rb +233 -50
- 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
|
|
@@ -79,14 +76,13 @@ Config is stored at `~/.config/filecon/config.yaml` (YAML format) and includes:
|
|
|
79
76
|
|
|
80
77
|
**Substitution variables:** `%name` (file path), `%dir` (directory picker), `%file` (file picker), `%str` (text input)
|
|
81
78
|
|
|
82
|
-
See `filecon(1)` or the installed `
|
|
79
|
+
See `filecon(1)` or the installed `filecon.yaml` for full details.
|
|
83
80
|
|
|
84
81
|
## Development
|
|
85
82
|
|
|
86
83
|
```bash
|
|
87
84
|
git clone https://codeberg.org/typr/filecon
|
|
88
85
|
cd filecon
|
|
89
|
-
bundle install
|
|
90
86
|
rake test
|
|
91
87
|
```
|
|
92
88
|
|
data/bin/filecon
CHANGED
|
@@ -1,39 +1,52 @@
|
|
|
1
1
|
#!/usr/bin/ruby
|
|
2
2
|
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "yaml"
|
|
3
5
|
require_relative "../lib/filecon"
|
|
4
6
|
|
|
5
7
|
begin
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
dh_path = File.join(state_dir, "dir_history")
|
|
18
|
+
ch_path = File.join(state_dir, "cmd_history")
|
|
19
|
+
|
|
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]) || {}
|
|
15
25
|
|
|
16
26
|
term = config["term"] || ENV["TERM"] || ""
|
|
17
|
-
|
|
27
|
+
term += config["term_hold"] if config["term_hold"]
|
|
18
28
|
|
|
19
29
|
Typr.init config["colors"]["default"]
|
|
20
30
|
Typr.clear
|
|
21
31
|
|
|
22
32
|
app = Filecon::App.new( right: -1, bottom: -1,
|
|
23
|
-
dir_history:
|
|
24
|
-
cmd_history:
|
|
25
|
-
|
|
26
|
-
|
|
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"] || {},
|
|
27
39
|
views: config["views"] || {},
|
|
28
40
|
commands: config["commands"] || {},
|
|
29
41
|
term: term,
|
|
30
|
-
term_hold: term_hold,
|
|
31
42
|
max_history: config["max_history"] || 100,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Typr.on_resize { app.
|
|
43
|
+
frequency_limit: config["frequency_limit"] || 10,
|
|
44
|
+
filter_dirs: config["filter_dirs"] )
|
|
45
|
+
Typr.on_resize { app.show }
|
|
46
|
+
app.import_shell_history
|
|
35
47
|
app.fly
|
|
36
48
|
rescue => e
|
|
49
|
+
$stderr.puts "#{e.class}: #{e.message}\n#{e.backtrace.first(10).join("\n")}"
|
|
37
50
|
File.write File.join(Dir.tmpdir, "filecon_error.log"),
|
|
38
51
|
"#{e.class}: #{e.message}\n#{e.backtrace.first(10).join("\n")}"
|
|
39
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,39 +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
|
-
disk_use: "|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
|
-
new_file: "|
|
|
108
|
-
new_dir: "|mkdir %str"
|
|
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"
|
|
109
151
|
|
|
110
152
|
text:
|
|
111
153
|
view: "+moor %name"
|
|
112
154
|
edit: "+micro %name"
|
|
113
|
-
count: "|wc -l %name"
|
|
155
|
+
count: "|wc -l %name | cut -wf1"
|
|
114
156
|
|
|
115
157
|
troff:
|
|
116
158
|
manual: "+man %name"
|
|
117
159
|
|
|
118
160
|
"x-ruby":
|
|
119
|
-
run: "
|
|
161
|
+
run: "|ruby %name"
|
|
162
|
+
run_terminal: "+ruby %name"
|
|
120
163
|
|
|
121
164
|
html:
|
|
122
165
|
terminal: "+cha %name"
|
|
123
166
|
graphics: "|vimb %name"
|
|
124
167
|
|
|
125
168
|
image:
|
|
126
|
-
|
|
169
|
+
view: "|pqiv %name"
|
|
127
170
|
|
|
128
171
|
video:
|
|
129
172
|
play: "|mpv %name"
|
|
@@ -134,12 +177,6 @@ commands:
|
|
|
134
177
|
pdf:
|
|
135
178
|
view: "|mupdf %name"
|
|
136
179
|
|
|
137
|
-
"x-pie-executable":
|
|
138
|
-
run: "|%name"
|
|
139
|
-
run_terminal: "+%name"
|
|
140
|
-
help: "=%name --help"
|
|
141
|
-
manual: "+man %name"
|
|
142
|
-
|
|
143
180
|
"x-python":
|
|
144
181
|
run: "+python3 %name"
|
|
145
182
|
|
|
@@ -178,23 +215,23 @@ commands:
|
|
|
178
215
|
view: "=cat %name | jq ."
|
|
179
216
|
|
|
180
217
|
"zip":
|
|
181
|
-
list: "
|
|
218
|
+
list: "=unzip -l %name"
|
|
182
219
|
extract: "|unzip %name -d %dir"
|
|
183
220
|
|
|
184
221
|
"x-gzip":
|
|
185
|
-
list: "
|
|
222
|
+
list: "=tar -tzf %name"
|
|
186
223
|
extract: "|tar -xzf %name -C %dir"
|
|
187
224
|
|
|
188
225
|
"x-bzip2":
|
|
189
|
-
list: "
|
|
226
|
+
list: "=tar -tjf %name"
|
|
190
227
|
extract: "|tar -xjf %name -C %dir"
|
|
191
228
|
|
|
192
229
|
"x-xz":
|
|
193
|
-
list: "
|
|
230
|
+
list: "=tar -tJf %name"
|
|
194
231
|
extract: "|tar -xJf %name -C %dir"
|
|
195
232
|
|
|
196
233
|
"x-tar":
|
|
197
|
-
list: "
|
|
234
|
+
list: "=tar -tf %name"
|
|
198
235
|
extract: "|tar -xf %name -C %dir"
|
|
199
236
|
|
|
200
237
|
"epub+zip":
|
|
@@ -219,10 +256,10 @@ commands:
|
|
|
219
256
|
# - A string Command to execute (with %name substitution)
|
|
220
257
|
# - A symbol Reference to a system field (e.g., :size, :type, :subtype)
|
|
221
258
|
#
|
|
222
|
-
# Prefix ! to evaluate as Ruby
|
|
259
|
+
# Prefix ! to evaluate as Ruby
|
|
223
260
|
#
|
|
224
261
|
# Available system fields:
|
|
225
|
-
# target, permissions, owner, group, size,
|
|
262
|
+
# target, permissions, executable, owner, group, size,
|
|
226
263
|
# accessed, modified, created, type, subtype
|
|
227
264
|
#
|
|
228
265
|
# When a view is activated, its columns are appended to the default file
|
|
@@ -234,15 +271,18 @@ views:
|
|
|
234
271
|
content:
|
|
235
272
|
content: "head -n 50 %name"
|
|
236
273
|
|
|
237
|
-
|
|
274
|
+
types:
|
|
238
275
|
type: true
|
|
239
276
|
subtype: true
|
|
240
277
|
description: "file -b %name"
|
|
241
278
|
|
|
279
|
+
disk_use:
|
|
280
|
+
du: "du -hs %name | cut -wf1"
|
|
281
|
+
|
|
242
282
|
count:
|
|
243
|
-
chars: "wc -m %name"
|
|
244
|
-
words: "wc -w %name"
|
|
245
|
-
lines: "wc -l %name"
|
|
283
|
+
chars: "wc -m %name | cut -wf1"
|
|
284
|
+
words: "wc -w %name | cut -wf1"
|
|
285
|
+
lines: "wc -l %name | cut -wf1"
|
|
246
286
|
|
|
247
287
|
whatis:
|
|
248
|
-
whatis: "
|
|
288
|
+
whatis: "whatis $(basename %name) 2>&1 | sed 's/.* - //'"
|
data/filecon.1
CHANGED
|
@@ -77,6 +77,12 @@ 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
|
data/lib/filecon/version.rb
CHANGED
data/lib/filecon.rb
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
require "fileutils"
|
|
2
|
-
require "yaml"
|
|
3
|
-
|
|
4
1
|
begin
|
|
5
2
|
require_relative "../../typr/lib/typr"
|
|
6
3
|
rescue LoadError
|
|
@@ -9,19 +6,30 @@ end
|
|
|
9
6
|
|
|
10
7
|
require_relative "filecon/version"
|
|
11
8
|
|
|
12
|
-
module Filecon
|
|
13
|
-
PATH = File.join(ENV["HOME"], ".config/filecon/")
|
|
14
|
-
end
|
|
15
|
-
|
|
16
9
|
class Filecon::App < Typr::Browser
|
|
17
10
|
include Typr
|
|
18
11
|
|
|
19
|
-
def cd dir, append=false
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
22
|
+
else
|
|
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
|
|
27
|
+
end
|
|
23
28
|
end
|
|
24
29
|
|
|
30
|
+
def execute; cd ENV["PATH"].split(":"); add_path_column end
|
|
31
|
+
|
|
32
|
+
#switch_view
|
|
25
33
|
def switch_to id=nil
|
|
26
34
|
return unless id
|
|
27
35
|
id = @views.keys[id] if id.is_a? Integer
|
|
@@ -30,9 +38,13 @@ class Filecon::App < Typr::Browser
|
|
|
30
38
|
data = @data.map.with_index{ |row, id| row + view.values.map{ |cmd|
|
|
31
39
|
next unless cmd.is_a?(String)
|
|
32
40
|
built = build cmd, id
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
36
48
|
io.read(width).to_s.scrub.gsub(/[\n\t]/, ?|)
|
|
37
49
|
}.compact }
|
|
38
50
|
@sequence = [ NAME ] + view.keys.map{ |head|
|
|
@@ -40,55 +52,79 @@ class Filecon::App < Typr::Browser
|
|
|
40
52
|
clear
|
|
41
53
|
self << data
|
|
42
54
|
@current = id
|
|
55
|
+
@view = id
|
|
43
56
|
end
|
|
44
57
|
|
|
45
58
|
def open_file id=nil, default=false, types=nil
|
|
46
59
|
unless id || types
|
|
47
60
|
return unless id ||= @selected[:rows].empty? ?
|
|
48
|
-
@user.ask( open:[self, :row] ) : @selected[:rows]
|
|
61
|
+
@user.ask( open:[self, :row, NAME] ) : @selected[:rows]
|
|
49
62
|
end
|
|
50
63
|
types ||= if id.is_a? Array
|
|
51
64
|
eval id.map{ |id| @data[id].values_at(SUBTYPE,TYPE).to_s }.join(?&)
|
|
52
65
|
else @data[id].values_at(SUBTYPE, TYPE)
|
|
53
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
|
|
54
70
|
|
|
55
|
-
commands = @commands.values_at( *types ).map(&:to_a).flatten 1
|
|
56
|
-
|
|
71
|
+
commands = @commands.values_at( *types ).map(&:to_a).flatten( 1 ).uniq
|
|
72
|
+
pair = if default; commands.first
|
|
57
73
|
else move left+1,top+1
|
|
58
|
-
if id.is_a?(Array);
|
|
74
|
+
if id.is_a?(Array); draw "#{id.count.to_s} files: (#{
|
|
59
75
|
types.join ?, })".ljust width
|
|
60
76
|
else print id end
|
|
61
|
-
|
|
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,
|
|
62
80
|
input:commands, border: :light, format: [:min, :max],
|
|
63
|
-
borders: { top: nil }, alternate: false,
|
|
64
|
-
colors: {columns: [:green, :red] } ).pick( :row )
|
|
65
|
-
commands[ cmd ]
|
|
81
|
+
borders: { top: nil }, alternate: false, hints: "1234567890",
|
|
82
|
+
colors: {columns: @colors[:commands] || [:green, :red] } ).pick( :row, column: 0 )
|
|
83
|
+
commands[ cmd ]
|
|
66
84
|
end
|
|
67
|
-
cmd = build
|
|
85
|
+
cmd = build pair.last, id, pair.first
|
|
86
|
+
return unless cmd
|
|
68
87
|
if cmd[0] == ?|
|
|
69
88
|
display = cmd[1..-1]
|
|
70
|
-
|
|
89
|
+
record :cmd, cmd
|
|
90
|
+
io = run( cmd )
|
|
71
91
|
output = io.read.to_s.scrub.strip
|
|
72
|
-
@
|
|
73
|
-
|
|
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
|
|
74
96
|
else
|
|
75
|
-
|
|
76
|
-
|
|
97
|
+
record :cmd, cmd unless cmd[0..2] == '!cd'
|
|
98
|
+
run( cmd )
|
|
99
|
+
show
|
|
77
100
|
end
|
|
78
101
|
end
|
|
79
102
|
|
|
80
|
-
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)}:" : ""
|
|
81
107
|
cmd = ""
|
|
82
108
|
while str[/\%(name|dir|file|str)/]
|
|
83
109
|
cmd += $`
|
|
84
110
|
case $&
|
|
85
111
|
when '%name'; cmd += ?"+( id.nil? ? @directory :
|
|
86
112
|
id.is_a?( Array ) ? id.map{ |id|
|
|
87
|
-
@
|
|
88
|
-
@
|
|
89
|
-
when '%dir'
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
92
128
|
end
|
|
93
129
|
str = str[($`+$&).size..-1]
|
|
94
130
|
end
|
|
@@ -96,32 +132,168 @@ class Filecon::App < Typr::Browser
|
|
|
96
132
|
return cmd
|
|
97
133
|
end
|
|
98
134
|
|
|
99
|
-
def run cmd=nil
|
|
135
|
+
def run cmd=nil
|
|
100
136
|
return unless cmd
|
|
101
|
-
cmd = @cmd_history[cmd] if cmd.is_a? Integer
|
|
137
|
+
cmd = @cmd_history[cmd][1] if cmd.is_a? Integer
|
|
102
138
|
case cmd[0]
|
|
103
139
|
when ?! then io = StringIO.new eval( cmd[1..-1] ).to_s
|
|
104
140
|
when ?| then io = open( cmd + " 2>&1" )
|
|
105
141
|
when ?+ then io = open( ?| + @term +' '+ cmd[1..-1] + " 2>&1" )
|
|
106
142
|
when ?= then Text.new(top: 2, right:-1, bottom:-2, border: :light,
|
|
107
143
|
input:open( ?|+cmd[1..-1]+" 2>&1" ) ).pick :none
|
|
108
|
-
|
|
109
|
-
if store
|
|
110
|
-
@cmd_history.unshift cmd
|
|
111
|
-
@cmd_history.uniq!
|
|
112
|
-
File.write @cmd_history_path, @cmd_history[0..@max_history].join($/)
|
|
144
|
+
else io = open( ?| + @term +' '+ cmd + " 2>&1" )
|
|
113
145
|
end
|
|
114
146
|
return io
|
|
115
147
|
end
|
|
116
148
|
|
|
117
|
-
def cmd_history;
|
|
118
|
-
|
|
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
|
|
119
282
|
end
|
|
120
283
|
|
|
121
284
|
def help
|
|
122
285
|
info = "\nFILECON - a terminal file manager\n\n" \
|
|
123
286
|
"HINTS: press a number (1-9,0) to open that file with its default action.\n" \
|
|
124
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" \
|
|
125
297
|
"CONFIG: ~/.config/filecon/config.yaml\n\n" \
|
|
126
298
|
"KEYBINDINGS:\n\n" + @user.bindings.join(?\n)
|
|
127
299
|
Text.new( input: info, top:2, left:3, right: -3, bottom: -3, header: "HELP",
|
|
@@ -131,24 +303,35 @@ class Filecon::App < Typr::Browser
|
|
|
131
303
|
|
|
132
304
|
def initialize args={}
|
|
133
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
|
|
134
314
|
@user.bindings.insert 1, "[o]pen"
|
|
135
|
-
@user.bindings.insert 7, "[
|
|
136
|
-
@user.
|
|
137
|
-
|
|
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
|
|
138
320
|
end
|
|
139
321
|
|
|
322
|
+
def open_current; open_file nil, false, ["directory", "inode"] - [??] + ["default"] end
|
|
323
|
+
|
|
140
324
|
def fly
|
|
141
325
|
loop do
|
|
142
|
-
|
|
326
|
+
show
|
|
143
327
|
draw_hints :rows
|
|
144
|
-
key = Typr.
|
|
328
|
+
key = Typr.read_key
|
|
145
329
|
next unless key
|
|
146
330
|
case key
|
|
147
331
|
when KEY_ESCAPE; exit
|
|
148
|
-
when ?`; open_file nil, false, ["directory", "inode"] - [??] + ["default"]
|
|
149
332
|
else
|
|
150
333
|
id = send( key )
|
|
151
|
-
open_file id, true if id.is_a? Integer
|
|
334
|
+
open_file( id, true) if id.is_a? Integer
|
|
152
335
|
@user.reset
|
|
153
336
|
end
|
|
154
337
|
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: []
|