cless 0.3.20
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 +7 -0
- data/LICENSE +674 -0
- data/Readme.md +1 -0
- data/bin/cless +287 -0
- data/lib/cless/assert.rb +3 -0
- data/lib/cless/cless.rb +816 -0
- data/lib/cless/data.rb +633 -0
- data/lib/cless/display.rb +639 -0
- data/lib/cless/export.rb +92 -0
- data/lib/cless/help.rb +97 -0
- data/lib/cless/optionsdb.rb +29 -0
- data/lib/cless/version.rb +1 -0
- metadata +70 -0
data/Readme.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# cless
|
data/bin/cless
ADDED
@@ -0,0 +1,287 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
trap("SIGTERM") { exit }
|
4
|
+
trap("SIGINT") { exit } # Will be redefined
|
5
|
+
trap("SIGQUIT") { exit }
|
6
|
+
trap("SIGHUP") { exit }
|
7
|
+
|
8
|
+
require 'cless/cless'
|
9
|
+
require 'cless/version'
|
10
|
+
require 'optparse'
|
11
|
+
require 'yaml'
|
12
|
+
|
13
|
+
options = {
|
14
|
+
:column => false,
|
15
|
+
:col_start => 1,
|
16
|
+
:line => false,
|
17
|
+
:line_offset => false,
|
18
|
+
:col_space => 1,
|
19
|
+
:line_highlight => false,
|
20
|
+
:line_highlight_period => 2,
|
21
|
+
:line_highlight_shift => 0,
|
22
|
+
:col_highlight => false,
|
23
|
+
:col_highlight_period => 2,
|
24
|
+
:col_highlight_shift => 0,
|
25
|
+
:col_width => 50,
|
26
|
+
:parse_header => true,
|
27
|
+
:foreground => "none",
|
28
|
+
:background => "none",
|
29
|
+
:attribute => "bold",
|
30
|
+
:names => nil,
|
31
|
+
:profile => nil,
|
32
|
+
:formats => [],
|
33
|
+
:ignore => [],
|
34
|
+
:split_regexp => nil,
|
35
|
+
:line_highlight_regexp => nil,
|
36
|
+
:hide => [],
|
37
|
+
:tmp_dir => Dir.tmpdir,
|
38
|
+
:options_db => [],
|
39
|
+
:separator => " ",
|
40
|
+
:padding => " ",
|
41
|
+
:start_line => 1,
|
42
|
+
:header_line => nil,
|
43
|
+
:history_file => File.expand_path("~/.cless_history")
|
44
|
+
}
|
45
|
+
onoff = proc { |k| "(" + (options[k] ? "on" : "off") + ")" }
|
46
|
+
val = proc { |k| "(" + (options[k] || "none").to_s + ")" }
|
47
|
+
|
48
|
+
def die(msg, code = 1)
|
49
|
+
$stderr.puts(msg)
|
50
|
+
$stderr.puts("Use --help for detailed options")
|
51
|
+
exit(code)
|
52
|
+
end
|
53
|
+
|
54
|
+
opts = nil
|
55
|
+
loop do
|
56
|
+
again = false
|
57
|
+
opts = OptionParser.new do |opts|
|
58
|
+
opts.banner = "Usage: cless [options] [file]\n" +
|
59
|
+
"Column oriented less-like pager\n" +
|
60
|
+
"Options: (default values in parentheses)"
|
61
|
+
|
62
|
+
opts.on("--[no-]column", "Display column number #{onoff[:column]}") { |v|
|
63
|
+
options[:column] = v
|
64
|
+
}
|
65
|
+
opts.on("--[no-]line", "Display line number #{onoff[:line]}") { |v|
|
66
|
+
options[:line] = v
|
67
|
+
}
|
68
|
+
opts.on("--[no-]offset", "Display offset instead of line number " +
|
69
|
+
onoff[:line_offset]) { |v|
|
70
|
+
options[:line_offset] = v
|
71
|
+
}
|
72
|
+
opts.on("--[no-]line-highlight", "Hilight every other line " +
|
73
|
+
onoff[:line_highlight]) { |v|
|
74
|
+
options[:line_highlight] = v
|
75
|
+
}
|
76
|
+
opts.on("--line-period PERIOD", "Hilight period for lines " +
|
77
|
+
val[:line_highlight_period]) { |v|
|
78
|
+
options[:line_highlight_period] = v.to_i
|
79
|
+
}
|
80
|
+
opts.on("--line-highlight-regexp REGEXP",
|
81
|
+
"Hilight line on regular expression" +
|
82
|
+
val[:line_highlight_regexp]) { |v|
|
83
|
+
options[:line_highlight_regexp] = Regexp.new(v)
|
84
|
+
}
|
85
|
+
opts.on("--line-shift SHIFT", "Hilight shift for lines " +
|
86
|
+
val[:line_highlight_shift]) { |v|
|
87
|
+
options[:line_highlight_shift] = v.to_i
|
88
|
+
}
|
89
|
+
opts.on("--[no-]column-highlight", "Hilight every other column " +
|
90
|
+
onoff[:col_highlight]) { |v|
|
91
|
+
options[:col_highlight] = v
|
92
|
+
}
|
93
|
+
opts.on("--column-period PERIOD", "Hilight period for columns " +
|
94
|
+
val[:col_highlight_period]) { |v|
|
95
|
+
options[:col_highlight_period] = v.to_i
|
96
|
+
}
|
97
|
+
opts.on("--column-shift SHIFT", "Hilight shift for columns " +
|
98
|
+
val[:col_highlight_shift]) { |v|
|
99
|
+
options[:col_highlight_shift] = v.to_i
|
100
|
+
}
|
101
|
+
opts.on("--[no-]column-names", "Display column names " +
|
102
|
+
"#{onoff[:col_names]}") { |v|
|
103
|
+
options[:col_names] = v
|
104
|
+
}
|
105
|
+
opts.on("--[no-]parse-header", "Parse header for options " +
|
106
|
+
"#{onoff[:parse_header]}") { |v|
|
107
|
+
options[:parse_header] = v
|
108
|
+
}
|
109
|
+
opts.on("--foreground COLOR", "Foreground color for hilight #{val[:foreground]}") { |v|
|
110
|
+
options[:foreground] = v
|
111
|
+
}
|
112
|
+
opts.on("--background COLOR", "Background color for hilight #{val[:background]}") { |v|
|
113
|
+
options[:background] = v
|
114
|
+
}
|
115
|
+
opts.on("--column-space NB", "Number of spaces between columns " +
|
116
|
+
val[:col_space]) { |v|
|
117
|
+
options[:col_space] = v.to_i
|
118
|
+
}
|
119
|
+
opts.on("--attribute ATTR", "Attribute for hilight #{val[:attribute]}") { |v|
|
120
|
+
options[:attribute] = v
|
121
|
+
}
|
122
|
+
opts.on("--hide COLUMNS",
|
123
|
+
"Comma separated list of columns to hide") { |v|
|
124
|
+
a = v.split(/[,\s]+/).collect { |x| x.to_i }
|
125
|
+
a.delete_if { |x| x < 0 }
|
126
|
+
options[:hide] += a
|
127
|
+
}
|
128
|
+
opts.on("--column-start INDEX", Integer,
|
129
|
+
"first column index #{val[:col_start]}") { |v|
|
130
|
+
options[:col_start] = v
|
131
|
+
}
|
132
|
+
opts.on("--col-width WIDTH", Integer,
|
133
|
+
"default maximum column width") { |v|
|
134
|
+
options[:col_width] = [v, 5].max
|
135
|
+
}
|
136
|
+
opts.on("--start-line LINE", Integer,
|
137
|
+
"first line to display #{val[:start_line]}") { |v|
|
138
|
+
options[:start_line] = v
|
139
|
+
}
|
140
|
+
opts.on("--names NAMES", "Comma separated list of column names") { |v|
|
141
|
+
options[:names] = v.split_with_quotes("\s,")
|
142
|
+
}
|
143
|
+
opts.on("--header-line LINE", Integer,
|
144
|
+
"use given line as headers") { |v|
|
145
|
+
options[:header_line] = v.to_i
|
146
|
+
}
|
147
|
+
opts.on("--options-db DB", "Path of a options db") { |v|
|
148
|
+
options[:options_db] << v
|
149
|
+
}
|
150
|
+
opts.on("--format FORMAT", "Format for a column") { |v|
|
151
|
+
options[:formats] << v
|
152
|
+
}
|
153
|
+
opts.on("--ignore PATTERN", "Line to ignore") { |v|
|
154
|
+
options[:ignore] << v
|
155
|
+
}
|
156
|
+
opts.on("--split-regexp REGEXP", "Regular expression to split lines") { |v|
|
157
|
+
v.gsub!(%r{^/|/$}, '')
|
158
|
+
options[:split_regexp] = Regexp.new(v)
|
159
|
+
}
|
160
|
+
opts.on("--profile PROFILE", "Option profile") { |v|
|
161
|
+
options[:profile] = v
|
162
|
+
}
|
163
|
+
opts.on("-T", "--tmp-dir DIR", "Temporary directory #{val[:tmp_dir]}") { |v|
|
164
|
+
options[:tmp_dir] = v
|
165
|
+
}
|
166
|
+
opts.on("--separator SEP", "Separator caracter between columns " +
|
167
|
+
"(#{val[:separator]}") { |v|
|
168
|
+
v = v[0, 1]
|
169
|
+
options[:separator] = v unless v.empty?
|
170
|
+
}
|
171
|
+
opts.on("--padding PAD", "Padding caracter (#{val[:padding]})") { |v|
|
172
|
+
options[:padding] = v
|
173
|
+
}
|
174
|
+
opts.on_tail("-v", "--version", "Show version and exit") {
|
175
|
+
puts(Version.join('.'))
|
176
|
+
exit(0)
|
177
|
+
}
|
178
|
+
opts.on_tail("-h", "--help", "This message") {
|
179
|
+
puts(opts)
|
180
|
+
exit(0)
|
181
|
+
}
|
182
|
+
end
|
183
|
+
|
184
|
+
if ENV["CLESS"]
|
185
|
+
args = ENV["CLESS"].split_with_quotes
|
186
|
+
ENV["CLESS"] = nil
|
187
|
+
again = true
|
188
|
+
else
|
189
|
+
args = ARGV
|
190
|
+
end
|
191
|
+
begin
|
192
|
+
opts.parse!(args)
|
193
|
+
rescue => e
|
194
|
+
die("Error (#{e.class}): #{e.message}")
|
195
|
+
end
|
196
|
+
break unless again
|
197
|
+
end
|
198
|
+
|
199
|
+
# Move around the file descriptor if not a tty!
|
200
|
+
if !$stdout.tty?
|
201
|
+
exec("cat", *ARGV)
|
202
|
+
end
|
203
|
+
if ARGV.empty? && $stdin.tty?
|
204
|
+
die("Cannot read from data tty")
|
205
|
+
end
|
206
|
+
if !$stdin.tty?
|
207
|
+
stdin = $stdin.dup
|
208
|
+
$stdin.reopen("/dev/fd/1", "r")
|
209
|
+
end
|
210
|
+
|
211
|
+
class KeyboardInterrupt < StandardError; end
|
212
|
+
|
213
|
+
ptr = if ARGV.empty?
|
214
|
+
MappedStream.new(stdin, options)
|
215
|
+
else
|
216
|
+
first = $have_mmap
|
217
|
+
begin
|
218
|
+
first ? MappedFile.new(ARGV[0]) :
|
219
|
+
MappedStream.new(open(ARGV[0]), options)
|
220
|
+
rescue => e
|
221
|
+
if first
|
222
|
+
first = false
|
223
|
+
retry
|
224
|
+
else
|
225
|
+
die("Error opening file '#{ARGV[0]}': #{e.message}")
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
$interrupt = Interrupt.new
|
230
|
+
|
231
|
+
begin
|
232
|
+
# Finish parsing options
|
233
|
+
db = OptionsDB.new
|
234
|
+
options[:options_db].each do |f|
|
235
|
+
begin
|
236
|
+
db.parse_file(f)
|
237
|
+
rescue => e
|
238
|
+
$stderr.puts("Error with db #{f}: #{e.message}")
|
239
|
+
end
|
240
|
+
end
|
241
|
+
if a = db[options[:profile]] || a = db.match(ARGV[0])
|
242
|
+
begin
|
243
|
+
opts.parse(a)
|
244
|
+
rescue => e
|
245
|
+
$stderr.puts("Error with options from db: #{e.message}")
|
246
|
+
end
|
247
|
+
end
|
248
|
+
header_l = nil
|
249
|
+
if options[:parse_header]
|
250
|
+
header_l, a = ptr.parse_header(["profile", "names", "format", "ignore"])
|
251
|
+
opts.parse(a)
|
252
|
+
opts = nil
|
253
|
+
end
|
254
|
+
|
255
|
+
data = MapData.new(ptr, options[:split_regexp])
|
256
|
+
display = LineDisplay.new(data, options)
|
257
|
+
manager = Manager.new(data, display, db)
|
258
|
+
manager.load_history(options[:history_file]) if options[:history_file]
|
259
|
+
trap("SIGINT") { $interrupt.raise }
|
260
|
+
data.cache_fill(1)
|
261
|
+
|
262
|
+
data.highlight_regexp = options[:line_highlight_regexp]
|
263
|
+
display.col_headers = options[:names]
|
264
|
+
display.col_hide(*options[:hide])
|
265
|
+
options[:formats].each { |fmt| manager.column_format_inline(fmt) }
|
266
|
+
options[:ignore].each { |pat| manager.ignore_line(pat) }
|
267
|
+
manager.ignore_line("1-#{header_l}") if header_l && header_l > 0
|
268
|
+
if options[:header_line]
|
269
|
+
begin
|
270
|
+
manager.change_headers_to_line(options[:header_line])
|
271
|
+
rescue => e
|
272
|
+
$stderr.puts("--header-line #{options[:header_line]}: #{e.message}")
|
273
|
+
end
|
274
|
+
end
|
275
|
+
data.goto_line(options[:start_line])
|
276
|
+
|
277
|
+
# Start curses application
|
278
|
+
Curses.new do |curses|
|
279
|
+
display.initialize_curses
|
280
|
+
manager.main_loop
|
281
|
+
end
|
282
|
+
rescue KeyboardInterrupt
|
283
|
+
# Quit normally
|
284
|
+
ensure
|
285
|
+
ptr.munmap
|
286
|
+
manager.save_history(options[:history_file]) unless manager.nil?
|
287
|
+
end
|
data/lib/cless/assert.rb
ADDED
data/lib/cless/cless.rb
ADDED
@@ -0,0 +1,816 @@
|
|
1
|
+
#require 'ncurses'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'ncursesw'
|
4
|
+
begin
|
5
|
+
require 'mmap'
|
6
|
+
$have_mmap = true
|
7
|
+
rescue LoadError
|
8
|
+
$have_mmap = false
|
9
|
+
end
|
10
|
+
require 'tempfile'
|
11
|
+
|
12
|
+
require 'cless/data'
|
13
|
+
require 'cless/display'
|
14
|
+
require 'cless/optionsdb'
|
15
|
+
require 'cless/export'
|
16
|
+
require 'cless/help'
|
17
|
+
|
18
|
+
# For short :)
|
19
|
+
NC = Ncurses
|
20
|
+
C = Curses
|
21
|
+
|
22
|
+
def select_or_cancel(*fds)
|
23
|
+
ifds = [$stdin] + fds.dup
|
24
|
+
loop {
|
25
|
+
ofds = select(ifds)[0]
|
26
|
+
if ofds.delete($stdin)
|
27
|
+
return nil if Ncurses.getch == C::ESC
|
28
|
+
end
|
29
|
+
return ofds unless ofds.empty?
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
class String
|
34
|
+
def split_with_quotes(sep = '\s', q = '\'"')
|
35
|
+
r = / \G(?:^|[#{sep}]) # anchor the match
|
36
|
+
(?: [#{q}]((?>[^#{q}]*)(?>""[^#{q}]*)*)[#{q}] # find quoted fields
|
37
|
+
| # ... or ...
|
38
|
+
([^#{q}#{sep}]*) # unquoted fields
|
39
|
+
)/x
|
40
|
+
self.split(r).delete_if { |x| x.empty? }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Interrupt
|
45
|
+
def initialize; @raised = false; end
|
46
|
+
def raise; @raised = true; end
|
47
|
+
def reset; r, @raised = @raised, false; r; end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Manager
|
51
|
+
class Error < StandardError; end
|
52
|
+
|
53
|
+
Commands = {
|
54
|
+
"scroll_forward_line" => :scroll_forward_line,
|
55
|
+
"scroll_backward_line" => :scroll_backward_line,
|
56
|
+
"scroll_forward_half_screen" => :scroll_forward_half_screen,
|
57
|
+
"scroll_backward_half_screen" => :scroll_backward_half_screen,
|
58
|
+
"scroll_right" => :scroll_right,
|
59
|
+
"scroll_left" => :scroll_left,
|
60
|
+
"hide_columns" => :hide_columns_prompt,
|
61
|
+
"unhide_columns" => :unhide_columns,
|
62
|
+
"toggle_hide_ignored" => :toggle_hide_ignored,
|
63
|
+
"toggle_headers" => :show_hide_headers,
|
64
|
+
"change_headers_to_line" => :change_headers_to_line_content_prompt,
|
65
|
+
"toggle_line_highlight" => :toggle_line_highlight,
|
66
|
+
"toggle_column_highlight" => :toggle_column_highlight,
|
67
|
+
"shift_line_highlight" => :shift_line_highlight,
|
68
|
+
"shift_column_highlight" => :shift_column_highlight,
|
69
|
+
"regexp_line_highlight" => :regexp_line_highlight_prompt,
|
70
|
+
"column_align_left" => :column_align_left,
|
71
|
+
"column_align_right" => :column_align_right,
|
72
|
+
"column_align_center" => :column_align_center,
|
73
|
+
"column_align_auto" => :column_align_auto,
|
74
|
+
"right_align_regexp" => :right_align_regexp_prompt,
|
75
|
+
"column_width" => :column_width_prompt,
|
76
|
+
"forward_search" => :forward_search,
|
77
|
+
"backward_search" => :backward_search,
|
78
|
+
"repeat_search" => :repeat_search,
|
79
|
+
"save_to_file" => :save_file_prompt,
|
80
|
+
"goto_position" => :goto_position_prompt,
|
81
|
+
"format_column" => :column_format_prompt,
|
82
|
+
"column_start_index" => :change_column_start_prompt,
|
83
|
+
"ignore_line" => :ignore_line_prompt,
|
84
|
+
"remove_ignore_line" => :ignore_line_remove_prompt,
|
85
|
+
"split_regexp" => :change_split_pattern_prompt,
|
86
|
+
"separator_character" => :change_separator_prompt,
|
87
|
+
"separator_padding" => :change_padding_prompt,
|
88
|
+
"export" => :export_prompt,
|
89
|
+
"help" => :display_help,
|
90
|
+
}
|
91
|
+
|
92
|
+
attr_accessor :max_search_history, :search_history
|
93
|
+
def initialize(data, display, db)
|
94
|
+
@data = data
|
95
|
+
@display = display
|
96
|
+
@db = db
|
97
|
+
@done = false
|
98
|
+
@status = ""
|
99
|
+
@prebuff = ""
|
100
|
+
@half_screen_lines = nil
|
101
|
+
@full_screen_lines = nil
|
102
|
+
@scroll_columns = nil
|
103
|
+
@interrupt = false
|
104
|
+
@search_history = []
|
105
|
+
@max_search_history = 100
|
106
|
+
end
|
107
|
+
|
108
|
+
def done; @done = true; end
|
109
|
+
def interrupt_set; @interrupt = true; end
|
110
|
+
def interrupt_reset; i, @interrupt = @interrupt, false; i; end
|
111
|
+
|
112
|
+
def ttyname
|
113
|
+
[Proc.new { File.readlink("/proc/self/fd/0") },
|
114
|
+
Proc.new { `tty`.chomp }].each { |m|
|
115
|
+
begin
|
116
|
+
return m.call
|
117
|
+
rescue Errno::ENOENT
|
118
|
+
end
|
119
|
+
}
|
120
|
+
return "/dev/unknown"
|
121
|
+
end
|
122
|
+
|
123
|
+
def load_history(file)
|
124
|
+
tty = ttyname
|
125
|
+
history = File.open(file, "r") { |fd|
|
126
|
+
fd.flock(File::LOCK_SH)
|
127
|
+
YAML::load(fd)
|
128
|
+
}
|
129
|
+
history = {} unless Hash === history
|
130
|
+
history["tty"] = {} unless Hash === history["tty"]
|
131
|
+
tty_history = history["tty"][tty] || history["tty"][history["recent_tty"]] || {}
|
132
|
+
@search_history = tty_history["search"] || []
|
133
|
+
rescue Errno::EACCES
|
134
|
+
rescue Errno::ENOENT
|
135
|
+
end
|
136
|
+
|
137
|
+
def save_history(file)
|
138
|
+
tty = ttyname
|
139
|
+
File.open(file, File::RDWR|File::CREAT, 0644) { |fd|
|
140
|
+
fd.flock(File::LOCK_EX)
|
141
|
+
|
142
|
+
history = YAML::load(fd)
|
143
|
+
history = {} unless Hash === history
|
144
|
+
history["tty"] = {} unless Hash === history["tty"]
|
145
|
+
history["tty"][tty] = {} unless Hash === history["tty"][tty]
|
146
|
+
history["tty"][tty]["search"] = @search_history
|
147
|
+
history["recent_tty"] = tty
|
148
|
+
|
149
|
+
fd.rewind
|
150
|
+
fd.print(history.to_yaml)
|
151
|
+
fd.flush
|
152
|
+
fd.truncate(fd.pos)
|
153
|
+
}
|
154
|
+
rescue Errno::EACCES
|
155
|
+
rescue Errno::ENOENT
|
156
|
+
end
|
157
|
+
|
158
|
+
def main_loop
|
159
|
+
if @status.empty?
|
160
|
+
@status = "Help? Press ~ or F1"
|
161
|
+
end
|
162
|
+
while !@done do
|
163
|
+
@data.cache_fill(@display.nb_lines)
|
164
|
+
@display.refresh
|
165
|
+
wait_for_key or break
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def prebuff; @prebuff.empty? ? nil : @prebuff.to_i; end
|
170
|
+
|
171
|
+
def wait_for_key
|
172
|
+
status = nil
|
173
|
+
esc = false
|
174
|
+
while !@done do
|
175
|
+
nc = false # Set to true if no data change
|
176
|
+
data_fd = @data.select_fd(@display.nb_lines)
|
177
|
+
prompt = data_fd ? "+:" : ":"
|
178
|
+
@display.wait_status(@status, prompt + @prebuff)
|
179
|
+
if data_fd
|
180
|
+
@display.flush
|
181
|
+
in_fds = select([$stdin, data_fd])
|
182
|
+
if in_fds[0].include?(data_fd)
|
183
|
+
status = :more
|
184
|
+
nc = true
|
185
|
+
break
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
k = Ncurses.getch
|
190
|
+
status =
|
191
|
+
case k
|
192
|
+
when NC::KEY_DOWN, NC::KEY_ENTER, C::CTRL_N, ?e.ord, C::CTRL_E, ?j.ord, C::CTRL_J, ?\n.ord, ?\r.ord
|
193
|
+
scroll_forward_line
|
194
|
+
when NC::KEY_UP, ?y.ord, C::CTRL_Y, C::CTRL_P, ?k.ord, C::CTRL_K
|
195
|
+
scroll_backward_line
|
196
|
+
when ?d.ord, C::CTRL_D; scroll_forward_half_screen(true)
|
197
|
+
when ?u.ord, C::CTRL_U; scroll_backward_half_screen(true)
|
198
|
+
when C::SPACE, NC::KEY_NPAGE, C::CTRL_V, ?f.ord, C::CTRL_F; scroll_forward_full_screen
|
199
|
+
when ?z.ord; scroll_forward_full_screen(true)
|
200
|
+
when NC::KEY_PPAGE, ?b.ord, C::CTRL_B; scroll_backward_full_screen
|
201
|
+
when ?w.ord; scroll_backward_full_screen(true)
|
202
|
+
when NC::KEY_HOME, ?g.ord, ?<.ord; goto_line(0)
|
203
|
+
when NC::KEY_END, ?G.ord, ?>.ord; goto_line(-1)
|
204
|
+
when NC::KEY_LEFT; scroll_left
|
205
|
+
when NC::KEY_RIGHT; scroll_right
|
206
|
+
when ?+.ord; @display.col_space += 1; true
|
207
|
+
when ?-.ord; @display.col_space -= 1; true
|
208
|
+
when ?F.ord; goto_position_prompt
|
209
|
+
when ?v.ord; column_format_prompt
|
210
|
+
when ?i.ord; ignore_line_prompt
|
211
|
+
when ?I.ord; ignore_line_remove_prompt
|
212
|
+
when ?o.ord; toggle_line_highlight
|
213
|
+
when ?O.ord; toggle_column_highlight
|
214
|
+
when ?m.ord; shift_line_highlight
|
215
|
+
when ?M.ord; shift_column_highlight
|
216
|
+
when ?c.ord; @display.column = !@display.column; true
|
217
|
+
when ?l.ord; @display.line = !@display.line; true
|
218
|
+
when ?L.ord; @display.line_offset = !@display.line_offset; true
|
219
|
+
when ?h.ord; hide_columns_prompt
|
220
|
+
when ?H.ord; hide_columns_prompt(:show)
|
221
|
+
when ?A.ord; column_alignment(:right)
|
222
|
+
when ?a.ord; column_alignment(:left)
|
223
|
+
when ?`.ord; change_column_start_prompt
|
224
|
+
when ?).ord; esc ? scroll_right : column_width_increase
|
225
|
+
when ?(.ord; esc ? scroll_left : column_width_decrease
|
226
|
+
when ?/.ord; search_prompt(:forward)
|
227
|
+
when ??.ord; search_prompt(:backward)
|
228
|
+
when ?n.ord; repeat_search
|
229
|
+
when ?N.ord; repeat_search(true)
|
230
|
+
when ?s.ord; save_file_prompt
|
231
|
+
when ?S.ord; change_split_pattern_prompt
|
232
|
+
when ?E.ord; export_prompt
|
233
|
+
when ?t.ord; show_hide_headers
|
234
|
+
when ?p.ord, ?%.ord; goto_percent
|
235
|
+
when ?|.ord; change_separator_prompt
|
236
|
+
when ?\\.ord; change_padding_prompt
|
237
|
+
when ?^.ord; change_headers_to_line_content_prompt
|
238
|
+
when ?r.ord, ?R.ord, C::CTRL_R, C::CTRL_L; @data.clear_cache; NC::endwin; NC::doupdate
|
239
|
+
when NC::KEY_RESIZE; nc = true # Will break to refresh display
|
240
|
+
when NC::KEY_F1, ?~.ord; display_help
|
241
|
+
when (?0.ord)..(?9.ord); @prebuff += k.chr; next
|
242
|
+
when NC::KEY_BACKSPACE, ?\b.ord; esc ? @prebuff = "" : @prebuff.chop!; next
|
243
|
+
when ?:.ord; long_command
|
244
|
+
when ?q.ord; return nil
|
245
|
+
when C::ESC; esc = true; next
|
246
|
+
when NC::KEY_SLEFT, ?[.ord; column_offset_left
|
247
|
+
when NC::KEY_SRIGHT, ?].ord; column_offset_right
|
248
|
+
when ?{.ord; column_offset_start
|
249
|
+
when ?}.ord; column_offset_end
|
250
|
+
else next
|
251
|
+
end
|
252
|
+
break
|
253
|
+
end
|
254
|
+
|
255
|
+
@prebuff = "" unless nc
|
256
|
+
@status =
|
257
|
+
case status
|
258
|
+
when String; status
|
259
|
+
when nil; "Cancelled"
|
260
|
+
when :more; @status
|
261
|
+
else ""
|
262
|
+
end
|
263
|
+
return true
|
264
|
+
end
|
265
|
+
|
266
|
+
# This is a little odd. Does it belong to display more?
|
267
|
+
def long_command
|
268
|
+
sub = CommandSubWindow.new(Commands.keys.map { |s| s.size }.max)
|
269
|
+
old_prompt_line = ""
|
270
|
+
sub.new_list(Commands.keys.sort)
|
271
|
+
extra = proc {
|
272
|
+
if old_prompt_line != @display.prompt_line
|
273
|
+
old_prompt_line = @display.prompt_line.dup
|
274
|
+
reg = Regexp.new(Regexp.quote(old_prompt_line))
|
275
|
+
sub.new_list(Commands.keys.grep(reg).sort)
|
276
|
+
Ncurses.refresh
|
277
|
+
end
|
278
|
+
}
|
279
|
+
other = proc { |ch|
|
280
|
+
r = true
|
281
|
+
case ch
|
282
|
+
when NC::KEY_DOWN, C::CTRL_N; sub.next_item
|
283
|
+
when NC::KEY_UP, C::CTRL_P; sub.previous_item
|
284
|
+
else r = false
|
285
|
+
end
|
286
|
+
Ncurses.refresh if r
|
287
|
+
}
|
288
|
+
s = @display.prompt("Filter: ", :extra => extra, :other => other)
|
289
|
+
sub.destroy
|
290
|
+
@display.refresh
|
291
|
+
self.__send__(Commands[sub.item]) if s
|
292
|
+
end
|
293
|
+
|
294
|
+
def str_to_range(str)
|
295
|
+
str.split_with_quotes().map { |r|
|
296
|
+
case r
|
297
|
+
when /^(\d+)$/
|
298
|
+
$1.to_i
|
299
|
+
when /^(\d+)(?:\.{2,3}|-)(\d+)$/
|
300
|
+
(($1.to_i)..($2.to_i)).to_a
|
301
|
+
else raise "Invalid range: #{r}"
|
302
|
+
end
|
303
|
+
}.flatten
|
304
|
+
end
|
305
|
+
|
306
|
+
def range_prompt(prompt)
|
307
|
+
s = @display.prompt(prompt) or return nil
|
308
|
+
str_to_range(s)
|
309
|
+
end
|
310
|
+
|
311
|
+
def scroll_forward_line
|
312
|
+
@data.scroll(prebuff || 1)
|
313
|
+
true
|
314
|
+
end
|
315
|
+
|
316
|
+
def scroll_backward_line
|
317
|
+
@data.scroll(-(prebuff || 1))
|
318
|
+
true
|
319
|
+
end
|
320
|
+
|
321
|
+
def scroll_forward_half_screen(save = false)
|
322
|
+
@half_screen_lines = prebuff if save && prebuff
|
323
|
+
@data.scroll(prebuff || @half_screen_lines || (@display.nb_lines / 2))
|
324
|
+
true
|
325
|
+
end
|
326
|
+
|
327
|
+
def scroll_backward_half_screen(save = false)
|
328
|
+
@half_screen_lines = prebuff if save && prebuff
|
329
|
+
@data.scroll(-(prebuff || @half_screen_lines || (@display.nb_lines / 2)))
|
330
|
+
true
|
331
|
+
end
|
332
|
+
|
333
|
+
def scroll_forward_full_screen(save = false)
|
334
|
+
@full_screen_lines = prebuff if save && prebuff
|
335
|
+
@data.scroll(prebuff || @full_screen_lines || (@display.nb_lines - 1))
|
336
|
+
true
|
337
|
+
end
|
338
|
+
|
339
|
+
def scroll_backward_full_screen(save = false)
|
340
|
+
@full_screen_lines = prebuff if save && prebuff
|
341
|
+
@data.scroll(-(prebuff || @full_screen_lines || (@display.nb_lines - 1)))
|
342
|
+
true
|
343
|
+
end
|
344
|
+
|
345
|
+
def scroll_sideways(dir)
|
346
|
+
@scroll_columns = prebuff if prebuff
|
347
|
+
to_scroll = @scroll_columns || 1
|
348
|
+
st_col = @display.st_col
|
349
|
+
to_scroll.times { |i|
|
350
|
+
st_col += dir
|
351
|
+
redo if @display.col_hidden(false).index(st_col)
|
352
|
+
}
|
353
|
+
@display.st_col = st_col
|
354
|
+
true
|
355
|
+
end
|
356
|
+
|
357
|
+
def scroll_left; scroll_sideways(-1); end
|
358
|
+
def scroll_right; scroll_sideways(1); end
|
359
|
+
|
360
|
+
def column_offset_sideways(dir)
|
361
|
+
if prebuff && prebuff >= @display.col_start
|
362
|
+
@offset_column = prebuff - @display.col_start
|
363
|
+
end
|
364
|
+
return if @offset_column.nil?
|
365
|
+
off = @display.col_offsets[@offset_column] || 0
|
366
|
+
off = [off + dir, 0].max
|
367
|
+
@display.col_offsets[@offset_column] = off
|
368
|
+
end
|
369
|
+
|
370
|
+
def column_offset_right; column_offset_sideways(1); end
|
371
|
+
def column_offset_left; column_offset_sideways(-1); end
|
372
|
+
def column_offset_start
|
373
|
+
if prebuff && prebuff >= @display.col_start
|
374
|
+
@offset_column = prebuff - @display.col_start
|
375
|
+
end
|
376
|
+
return if @offset_column.nil?
|
377
|
+
@display.col_offsets[@offset_column] = 0
|
378
|
+
end
|
379
|
+
|
380
|
+
def column_offset_end
|
381
|
+
if prebuff && prebuff >= @display.col_start
|
382
|
+
@offset_column = prebuff - @display.col_start
|
383
|
+
end
|
384
|
+
return if @offset_column.nil?
|
385
|
+
@display.col_offsets[@offset_column] =
|
386
|
+
[0, @data.sizes[@offset_column] - (@display.widths[@offset_column] || @display.col_width)].max
|
387
|
+
end
|
388
|
+
|
389
|
+
def goto_line(l)
|
390
|
+
if l == 0
|
391
|
+
return @data.goto_start ? "" : "Start of file"
|
392
|
+
elsif l < 0
|
393
|
+
@display.start_active_status("Skipping to end of file")
|
394
|
+
return @data.goto_end ? "" : "End of file"
|
395
|
+
else
|
396
|
+
@display.start_active_status("Skipping to line #{l}")
|
397
|
+
@data.goto_line(prebuff)
|
398
|
+
end
|
399
|
+
true
|
400
|
+
ensure
|
401
|
+
@display.end_active_status
|
402
|
+
end
|
403
|
+
|
404
|
+
def unhide_columns; hide_columns_prompt(true); end
|
405
|
+
def hide_columns_prompt(show = false)
|
406
|
+
i = prebuff
|
407
|
+
a = i ? [i] : range_prompt(show ? "Show: " : "Hide: ") or return nil
|
408
|
+
if a.empty?
|
409
|
+
@display.col_hide_clear
|
410
|
+
else
|
411
|
+
show ? @display.col_show(*a) : @display.col_hide(*a)
|
412
|
+
end
|
413
|
+
"Hidden: #{@display.col_hidden.join(" ")}"
|
414
|
+
rescue => e
|
415
|
+
return e.message
|
416
|
+
end
|
417
|
+
|
418
|
+
def toggle_hide_ignored; @display.hide_ignored = !@display.hide_ignored; end
|
419
|
+
|
420
|
+
def column_align_left; column_alignment(:left); end
|
421
|
+
def column_align_right; column_alignment(:right); end
|
422
|
+
def column_align_center; column_alignment(:center); end
|
423
|
+
def column_align_auto; column_alignment(nil); end
|
424
|
+
def column_alignment(align)
|
425
|
+
i = prebuff
|
426
|
+
a = i ? [i] : range_prompt("Columns to #{align || "auto"} align: ") or return nil
|
427
|
+
return if a.empty?
|
428
|
+
@display.col_align(align, a)
|
429
|
+
end
|
430
|
+
|
431
|
+
def column_width_prompt
|
432
|
+
i = prebuff
|
433
|
+
a = i ? [i] : range_prompt("Width of columns: ") or return nil
|
434
|
+
return nil if a.empty?
|
435
|
+
s = @display.prompt("Max width: ") or return nil
|
436
|
+
s = [s.to_i, 5].max
|
437
|
+
a.map { |x| @display.widths[x] = s }
|
438
|
+
end
|
439
|
+
|
440
|
+
def column_width_change(x)
|
441
|
+
if prebuff && prebuff >= @display.col_start
|
442
|
+
@offset_column = prebuff - @display.col_start
|
443
|
+
end
|
444
|
+
return if @offset_column.nil?
|
445
|
+
w = (@display.widths[@offset_column] || @display.col_width) + x
|
446
|
+
w = 5 if w < 5
|
447
|
+
@display.widths[@offset_column] = w
|
448
|
+
end
|
449
|
+
|
450
|
+
def column_width_increase; column_width_change(1); end
|
451
|
+
def column_width_decrease; column_width_change(-1); end
|
452
|
+
|
453
|
+
def show_hide_headers
|
454
|
+
return "No names defined" if !@display.col_names && !@display.col_headers
|
455
|
+
@display.col_names = !@display.col_names
|
456
|
+
true
|
457
|
+
end
|
458
|
+
|
459
|
+
def change_headers_to_line_content_prompt
|
460
|
+
i = prebuff
|
461
|
+
if i.nil?
|
462
|
+
i = @data.line + 1
|
463
|
+
s = @display.prompt("Header line: ", :init => i.to_s) or return nil
|
464
|
+
s.strip!
|
465
|
+
return "Bad line number #{s}" unless s =~ /^\d+$/
|
466
|
+
i = s.to_i
|
467
|
+
end
|
468
|
+
begin
|
469
|
+
change_headers_to_line(i)
|
470
|
+
rescue => e
|
471
|
+
return e.message
|
472
|
+
end
|
473
|
+
true
|
474
|
+
end
|
475
|
+
|
476
|
+
def toggle_line_highlight
|
477
|
+
i = prebuff
|
478
|
+
@display.line_highlight = !@display.line_highlight
|
479
|
+
@display.line_highlight_period = i if i
|
480
|
+
true
|
481
|
+
end
|
482
|
+
|
483
|
+
def toggle_column_highlight
|
484
|
+
i = prebuff
|
485
|
+
@display.col_highlight = !@display.col_highlight
|
486
|
+
@display.col_highlight_period = i if i
|
487
|
+
true
|
488
|
+
end
|
489
|
+
|
490
|
+
def shift_line_highlight
|
491
|
+
if i = prebuff
|
492
|
+
@display.line_highlight_shift = i
|
493
|
+
else
|
494
|
+
@display.line_highlight_shift += 1
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
def shift_column_highlight
|
499
|
+
if i = prebuff
|
500
|
+
@display.col_highlight_shift = i
|
501
|
+
else
|
502
|
+
@display.col_highlight_shift += 1
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
def change_headers_to_line(i)
|
507
|
+
raise Error, "Bad line number #{i}" if i < 1
|
508
|
+
i_bak = @data.line + 1
|
509
|
+
@data.goto_line(i)
|
510
|
+
@data.cache_fill(1)
|
511
|
+
line = nil
|
512
|
+
@data.lines(1) { |l| line = l }
|
513
|
+
@data.goto_line(i_bak) # Go back
|
514
|
+
raise Error, "No such line" unless line
|
515
|
+
raise Error, "Ignored line: can't use" if line.kind_of?(IgnoredLine)
|
516
|
+
@display.col_headers = line.onl_at(0..-1)
|
517
|
+
@display.col_names = true
|
518
|
+
true
|
519
|
+
end
|
520
|
+
|
521
|
+
# Return a status if an error occur, otherwise, returns nil
|
522
|
+
def forward_search; search_prompt(:forward); end
|
523
|
+
def backward_search; search_prompt(:backward); end
|
524
|
+
def search_prompt(dir = :forward)
|
525
|
+
s = @display.prompt("%s Search: " %
|
526
|
+
[(dir == :forward) ? "Forward" : "Backward"],
|
527
|
+
{ :history => @search_history })
|
528
|
+
s or return nil
|
529
|
+
if s =~ /^\s*$/
|
530
|
+
@data.search_clear
|
531
|
+
else
|
532
|
+
begin
|
533
|
+
@display.start_active_status("Searching '#{s}'")
|
534
|
+
|
535
|
+
hist_index = @search_history.index(s)
|
536
|
+
@search_history.slice!(hist_index) if hist_index
|
537
|
+
@search_history.unshift(s)
|
538
|
+
@search_history = @search_history[0, @max_search_history]
|
539
|
+
begin
|
540
|
+
@search_dir = dir
|
541
|
+
pattern = Regexp.new(s)
|
542
|
+
@data.search(pattern, dir) or return "Pattern not found!"
|
543
|
+
rescue RegexpError => e
|
544
|
+
return "Bad attr_reader :egexp: #{e.message}"
|
545
|
+
end
|
546
|
+
ensure
|
547
|
+
@display.end_active_status
|
548
|
+
end
|
549
|
+
end
|
550
|
+
true
|
551
|
+
end
|
552
|
+
|
553
|
+
# Return a status if an error occur, otherwise, returns nil
|
554
|
+
def repeat_search(reverse = false)
|
555
|
+
return "No pattern" if !@data.pattern
|
556
|
+
dir = if reverse
|
557
|
+
(@search_dir == :forward) ? :backward : :forward
|
558
|
+
else
|
559
|
+
@search_dir
|
560
|
+
end
|
561
|
+
@data.repeat_search(dir) or return "Pattern not found!"
|
562
|
+
true
|
563
|
+
end
|
564
|
+
|
565
|
+
def color_descr
|
566
|
+
descr = @display.attr_names
|
567
|
+
"Color: F %s B %s A %s" %
|
568
|
+
[descr[:foreground] || "-", descr[:background] || "-", descr[:attribute] || "-"]
|
569
|
+
end
|
570
|
+
|
571
|
+
def save_file_prompt
|
572
|
+
s = @display.prompt("Save to: ")
|
573
|
+
return nil if !s || s.empty?
|
574
|
+
if @data.file_path
|
575
|
+
begin
|
576
|
+
File.link(@data.file_path, s)
|
577
|
+
return "Hard linked"
|
578
|
+
rescue Errno::EXDEV => e
|
579
|
+
rescue Exception => e
|
580
|
+
return "Error: #{e.message}"
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
# Got here, hard link failed. Copy by hand.
|
585
|
+
nb_bytes = nil
|
586
|
+
begin
|
587
|
+
File.open(s, File::WRONLY|File::CREAT|File::EXCL) do |fd|
|
588
|
+
nb_bytes = @data.write_to(fd)
|
589
|
+
end
|
590
|
+
rescue Exception => e
|
591
|
+
return "Error: #{e.message}"
|
592
|
+
end
|
593
|
+
"Wrote #{nb_bytes} bytes"
|
594
|
+
end
|
595
|
+
|
596
|
+
def goto_percent
|
597
|
+
percent = prebuff or return true
|
598
|
+
@data.goto_percent(percent)
|
599
|
+
end
|
600
|
+
|
601
|
+
def goto_position_prompt
|
602
|
+
s = prebuff || @display.prompt("Goto: ") or return nil
|
603
|
+
s = s.to_s.strip
|
604
|
+
|
605
|
+
case s[-1]
|
606
|
+
when "p", "%"
|
607
|
+
s.slice!(-1)
|
608
|
+
f = s.to_f
|
609
|
+
return "Invalid percentage" if f <= 0.0 || f > 100.0
|
610
|
+
@display.start_active_status("Goto %d%%" % f.round)
|
611
|
+
@data.goto_percent(f)
|
612
|
+
when "o"
|
613
|
+
s.slice!(-1)
|
614
|
+
i = s.to_i
|
615
|
+
return "Invalid offset" if i < 0
|
616
|
+
@display.start_active_status("Goto offset #{i}")
|
617
|
+
@data.goto_offset(i)
|
618
|
+
else
|
619
|
+
i = s.to_i
|
620
|
+
return "Invalid line number" if i <= 0
|
621
|
+
@display.start_active_status("Goto line #{i}")
|
622
|
+
@data.goto_line(i)
|
623
|
+
end
|
624
|
+
true
|
625
|
+
ensure
|
626
|
+
@display.end_active_status
|
627
|
+
end
|
628
|
+
|
629
|
+
def column_format_prompt
|
630
|
+
i = prebuff
|
631
|
+
cols = i ? [i] : range_prompt("Format columns: ") or return nil
|
632
|
+
fmt = @display.prompt("Format string: ") or return nil
|
633
|
+
fmt.strip!
|
634
|
+
column_format(cols, fmt)
|
635
|
+
rescue => e
|
636
|
+
return e.message
|
637
|
+
end
|
638
|
+
|
639
|
+
def column_format_inline(str)
|
640
|
+
cols, fmt = str.split(/:/, 2)
|
641
|
+
cols = str_to_range(cols)
|
642
|
+
column_format(cols, fmt)
|
643
|
+
end
|
644
|
+
|
645
|
+
def column_format(cols, fmt)
|
646
|
+
inc = @display.col_start
|
647
|
+
if cols
|
648
|
+
cols = cols.map { |x| x.to_i - inc }
|
649
|
+
cols.delete_if { |x| x < 0 }
|
650
|
+
if fmt && !fmt.empty?
|
651
|
+
@data.set_format_column(fmt, *cols)
|
652
|
+
else
|
653
|
+
cols.each { |c| @data.unset_format_column(c) }
|
654
|
+
end
|
655
|
+
@data.refresh
|
656
|
+
end
|
657
|
+
cols = @data.formatted_column_list.sort.collect { |x| x + inc }
|
658
|
+
"Formatted: " + cols.join(" ")
|
659
|
+
end
|
660
|
+
|
661
|
+
def change_column_start_prompt
|
662
|
+
s = prebuff
|
663
|
+
s = @display.prompt("First column: ") unless s
|
664
|
+
return nil unless s
|
665
|
+
@display.col_start = s.to_i
|
666
|
+
true
|
667
|
+
end
|
668
|
+
|
669
|
+
def ignore_line_prompt
|
670
|
+
s = @display.prompt("Ignore: ") or return nil
|
671
|
+
s.strip!
|
672
|
+
ignore_line(s)
|
673
|
+
end
|
674
|
+
|
675
|
+
def ignore_line_list_each(str)
|
676
|
+
a = str.split_with_quotes('\s', '\/')
|
677
|
+
a.each do |spat|
|
678
|
+
opat = case spat
|
679
|
+
when /^(\d+)(?:\.{2}|-)(\d+)$/; ($1.to_i - 1)..($2.to_i - 1)
|
680
|
+
when /^(\d+)$/; $1.to_i - 1
|
681
|
+
else Regexp.new(spat) rescue nil
|
682
|
+
end
|
683
|
+
yield(spat, opat)
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
def ignore_line_list_display(a)
|
688
|
+
a.collect { |x|
|
689
|
+
o = case x
|
690
|
+
when Range; (x.begin + 1)..(x.end + 1)
|
691
|
+
when Integer; x + 1
|
692
|
+
else x
|
693
|
+
end
|
694
|
+
o.inspect
|
695
|
+
}
|
696
|
+
end
|
697
|
+
|
698
|
+
def ignore_line(str)
|
699
|
+
ignore_line_list_each(str) do |spat, opat|
|
700
|
+
if opat.nil? || !@data.add_ignore(opat)
|
701
|
+
return "Bad pattern #{spat}"
|
702
|
+
end
|
703
|
+
end
|
704
|
+
@data.refresh
|
705
|
+
"Ignored: " + ignore_line_list_display(@data.ignore_pattern_list).join(" ")
|
706
|
+
end
|
707
|
+
|
708
|
+
def ignore_line_remove_prompt
|
709
|
+
s = @display.prompt("Remove ignore: ") or return nil
|
710
|
+
s.strip!
|
711
|
+
ignore_line_remove(s)
|
712
|
+
end
|
713
|
+
|
714
|
+
def ignore_line_remove(str)
|
715
|
+
if !str || str.empty?
|
716
|
+
@data.remove_ignore(nil)
|
717
|
+
else
|
718
|
+
ignore_line_list_each(str) do |spat, opat|
|
719
|
+
opat && @data.remove_ignore(opat)
|
720
|
+
end
|
721
|
+
end
|
722
|
+
@data.refresh
|
723
|
+
"Ignored: " + ignore_line_list_display(@data.ignore_pattern_list).join(" ")
|
724
|
+
end
|
725
|
+
|
726
|
+
def change_split_pattern_prompt
|
727
|
+
s = @display.prompt("Split regexp(/#{@data.split_regexp}/): ")
|
728
|
+
return "Not changed" if !s
|
729
|
+
begin
|
730
|
+
s.gsub!(%r{^/|/$}, '')
|
731
|
+
regexp = s.empty? ? nil : Regexp.new(s)
|
732
|
+
rescue => e
|
733
|
+
return "Invalid regexp /#{s}/: #{e.message}"
|
734
|
+
end
|
735
|
+
@data.split_regexp = regexp
|
736
|
+
"New split regexp: /#{regexp}/"
|
737
|
+
end
|
738
|
+
|
739
|
+
def regexp_line_highlight_prompt
|
740
|
+
s = @display.prompt("Highlight regexp(/#{@data.split_regexp}/): ")
|
741
|
+
s.strip! if s
|
742
|
+
if !s || s.empty?
|
743
|
+
@data.highlight_regexp = nil
|
744
|
+
return "No line highlight by regexp"
|
745
|
+
end
|
746
|
+
begin
|
747
|
+
s.gsub!(%r{^/|/$}, '')
|
748
|
+
regexp = s.empty? ? nil : Regexp.new(s)
|
749
|
+
rescue => e
|
750
|
+
return "Invalid regexp /#{s}/: #{e.message}"
|
751
|
+
end
|
752
|
+
@data.highlight_regexp = regexp
|
753
|
+
"New highlight regexp: /#{regexp}/"
|
754
|
+
end
|
755
|
+
|
756
|
+
def right_align_regexp_prompt
|
757
|
+
current = @display.right_align_re == LineDisplay::ISNUM ? "number" : @display.right_align_re.to_s
|
758
|
+
s = @display.prompt("Right align regexp(/#{current}/): ")
|
759
|
+
s.strip! if s
|
760
|
+
if s.nil? || s.empty?
|
761
|
+
@display.right_align_re = LineDisplay::ISNUM
|
762
|
+
return "Automatic right alignment of numbers"
|
763
|
+
elsif s == "//"
|
764
|
+
@display.right_align_re = nil
|
765
|
+
return "No automatic right alignment"
|
766
|
+
end
|
767
|
+
|
768
|
+
begin
|
769
|
+
s.gsub!(%r{^/|/$}, '')
|
770
|
+
regexp = Regexp.new(s)
|
771
|
+
rescue => e
|
772
|
+
return "Invalid regexp /#{s}/: #{e.message}"
|
773
|
+
end
|
774
|
+
@display.right_align_re = regexp
|
775
|
+
"Automatic right alignment regexp: /#{regexp}/"
|
776
|
+
end
|
777
|
+
|
778
|
+
def change_separator_prompt
|
779
|
+
s = @display.prompt("Separator: ") or return nil
|
780
|
+
@display.separator = s
|
781
|
+
true
|
782
|
+
end
|
783
|
+
|
784
|
+
def change_padding_prompt
|
785
|
+
s = @display.prompt("Padding: ") or return nil
|
786
|
+
@display.padding = s
|
787
|
+
true
|
788
|
+
end
|
789
|
+
|
790
|
+
def display_help
|
791
|
+
Ncurses.endwin
|
792
|
+
Help.display
|
793
|
+
true
|
794
|
+
rescue => e
|
795
|
+
e.message
|
796
|
+
ensure
|
797
|
+
Ncurses.refresh
|
798
|
+
end
|
799
|
+
|
800
|
+
def export_prompt
|
801
|
+
format = @display.prompt("Format: ") or return nil
|
802
|
+
s = @display.prompt("Lines: ") or return nil
|
803
|
+
ls, le = s.split.map { |x| x.to_i }
|
804
|
+
file = @display.prompt("File: ") or return nil
|
805
|
+
qs = Export.questions(format)
|
806
|
+
opts = {}
|
807
|
+
qs && qs.each { |k, pt, init|
|
808
|
+
s = @display.prompt(pt + ": ", :init => init) or return nil
|
809
|
+
opts[k] = s
|
810
|
+
}
|
811
|
+
len = Export.export(file, format, ls..le, @data, @display, opts)
|
812
|
+
"Wrote #{len} bytes"
|
813
|
+
rescue => e
|
814
|
+
return "Error: #{e.message}"
|
815
|
+
end
|
816
|
+
end
|