na 1.2.80 → 1.2.81
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/.rubocop.yml +8 -2
- data/.rubocop_todo.yml +33 -538
- data/CHANGELOG.md +19 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +27 -10
- data/README.md +46 -5
- data/Rakefile +6 -0
- data/bin/commands/next.rb +4 -0
- data/bin/commands/scan.rb +84 -0
- data/bin/commands/update.rb +1 -1
- data/bin/na +7 -7
- data/lib/na/action.rb +101 -35
- data/lib/na/actions.rb +79 -77
- data/lib/na/array.rb +11 -7
- data/lib/na/benchmark.rb +21 -9
- data/lib/na/colors.rb +84 -86
- data/lib/na/editor.rb +22 -22
- data/lib/na/hash.rb +32 -9
- data/lib/na/help_monkey_patch.rb +9 -1
- data/lib/na/next_action.rb +314 -245
- data/lib/na/pager.rb +38 -14
- data/lib/na/project.rb +14 -1
- data/lib/na/prompt.rb +25 -3
- data/lib/na/string.rb +90 -130
- data/lib/na/theme.rb +37 -31
- data/lib/na/todo.rb +149 -131
- data/lib/na/version.rb +3 -1
- data/lib/na.rb +1 -0
- data/na.gemspec +4 -2
- data/scripts/generate-fish-completions.rb +18 -21
- data/src/_README.md +14 -4
- data/test_performance.rb +5 -5
- metadata +53 -24
data/lib/na/actions.rb
CHANGED
|
@@ -5,24 +5,19 @@ module NA
|
|
|
5
5
|
class Actions < Array
|
|
6
6
|
def initialize(actions = [])
|
|
7
7
|
super
|
|
8
|
-
concat(actions)
|
|
9
8
|
end
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
## @option config [Boolean] :no_files Whether to include files in the output
|
|
23
|
-
##
|
|
24
|
-
## @return [String] The output string
|
|
25
|
-
##
|
|
10
|
+
# Pretty print a list of actions
|
|
11
|
+
#
|
|
12
|
+
# @param depth [Integer] The depth of the action
|
|
13
|
+
# @param config [Hash] The configuration options
|
|
14
|
+
# @option config [Array] :files The files to include in the output
|
|
15
|
+
# @option config [Array] :regexes The regexes to match against
|
|
16
|
+
# @option config [Boolean] :notes Whether to include notes in the output
|
|
17
|
+
# @option config [Boolean] :nest Whether to nest the output
|
|
18
|
+
# @option config [Boolean] :nest_projects Whether to nest projects in the output
|
|
19
|
+
# @option config [Boolean] :no_files Whether to include files in the output
|
|
20
|
+
# @return [String] The output string
|
|
26
21
|
def output(depth, config = {})
|
|
27
22
|
NA::Benchmark.measure('Actions.output') do
|
|
28
23
|
defaults = {
|
|
@@ -31,87 +26,94 @@ module NA
|
|
|
31
26
|
notes: false,
|
|
32
27
|
nest: false,
|
|
33
28
|
nest_projects: false,
|
|
34
|
-
no_files: false
|
|
29
|
+
no_files: false
|
|
35
30
|
}
|
|
36
31
|
config = defaults.merge(config)
|
|
37
32
|
|
|
38
|
-
|
|
33
|
+
return if config[:files].nil?
|
|
39
34
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
if config[:nest]
|
|
36
|
+
template = NA.theme[:templates][:default]
|
|
37
|
+
template = NA.theme[:templates][:no_file] if config[:no_files]
|
|
43
38
|
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
parent_files = {}
|
|
40
|
+
out = []
|
|
46
41
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
42
|
+
if config[:nest_projects]
|
|
43
|
+
each do |action|
|
|
44
|
+
parent_files[action.file] ||= []
|
|
45
|
+
parent_files[action.file].push(action)
|
|
46
|
+
end
|
|
52
47
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
48
|
+
parent_files.each do |file, acts|
|
|
49
|
+
projects = NA.project_hierarchy(acts)
|
|
50
|
+
out.push("#{file.sub(%r{^./}, '').shorten_path}:")
|
|
51
|
+
out.concat(NA.output_children(projects, 0))
|
|
52
|
+
end
|
|
53
|
+
else
|
|
54
|
+
template = NA.theme[:templates][:default]
|
|
55
|
+
template = NA.theme[:templates][:no_file] if config[:no_files]
|
|
61
56
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
57
|
+
each do |action|
|
|
58
|
+
parent_files[action.file] ||= []
|
|
59
|
+
parent_files[action.file].push(action)
|
|
60
|
+
end
|
|
66
61
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
62
|
+
parent_files.each do |file, acts|
|
|
63
|
+
out.push("#{file.sub(%r{^\./}, '')}:")
|
|
64
|
+
acts.each do |a|
|
|
65
|
+
out.push("\t- [#{a.parent.join('/')}] #{a.action}")
|
|
66
|
+
out.push("\t\t#{a.note.join("\n\t\t")}") unless a.note.empty?
|
|
67
|
+
end
|
|
72
68
|
end
|
|
73
69
|
end
|
|
74
|
-
|
|
75
|
-
NA::Pager.page out.join("\n")
|
|
76
|
-
else
|
|
77
|
-
# Optimize template selection
|
|
78
|
-
template = case
|
|
79
|
-
when config[:no_files]
|
|
80
|
-
NA.theme[:templates][:no_file]
|
|
81
|
-
when config[:files]&.count&.positive?
|
|
82
|
-
config[:files].count == 1 ? NA.theme[:templates][:single_file] : NA.theme[:templates][:multi_file]
|
|
83
|
-
when depth > 1
|
|
84
|
-
NA.theme[:templates][:multi_file]
|
|
70
|
+
NA::Pager.page out.join("\n")
|
|
85
71
|
else
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
72
|
+
# Optimize template selection
|
|
73
|
+
template = if config[:no_files]
|
|
74
|
+
NA.theme[:templates][:no_file]
|
|
75
|
+
elsif config[:files]&.count&.positive?
|
|
76
|
+
config[:files].count == 1 ? NA.theme[:templates][:single_file] : NA.theme[:templates][:multi_file]
|
|
77
|
+
elsif depth > 1
|
|
78
|
+
NA.theme[:templates][:multi_file]
|
|
79
|
+
else
|
|
80
|
+
NA.theme[:templates][:default]
|
|
81
|
+
end
|
|
82
|
+
template += '%note' if config[:notes]
|
|
89
83
|
|
|
90
|
-
|
|
91
|
-
|
|
84
|
+
# Show './' for current directory only when listing also includes subdir files
|
|
85
|
+
if template == NA.theme[:templates][:multi_file]
|
|
86
|
+
has_subdir = config[:files]&.any? { |f| File.dirname(f) != '.' } || depth > 1
|
|
87
|
+
NA.show_cwd_indicator = !has_subdir.nil?
|
|
88
|
+
else
|
|
89
|
+
NA.show_cwd_indicator = false
|
|
90
|
+
end
|
|
92
91
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
# Skip debug output if not verbose
|
|
93
|
+
config[:files]&.each { |f| NA.notify(f, debug: true) } if config[:files] && NA.verbose
|
|
94
|
+
|
|
95
|
+
# Optimize output generation - compile all output first, then apply regexes
|
|
96
|
+
output = String.new
|
|
97
|
+
NA::Benchmark.measure('Generate action strings') do
|
|
98
|
+
each_with_index do |action, idx|
|
|
99
|
+
# Generate raw output without regex processing
|
|
100
|
+
output << action.pretty(template: { templates: { output: template } }, regexes: [], notes: config[:notes])
|
|
101
|
+
output << "\n" unless idx == size - 1
|
|
102
|
+
end
|
|
100
103
|
end
|
|
101
|
-
end
|
|
102
104
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
# Apply regex highlighting to the entire output at once
|
|
106
|
+
if config[:regexes].any?
|
|
107
|
+
NA::Benchmark.measure('Apply regex highlighting') do
|
|
108
|
+
output = output.highlight_search(config[:regexes])
|
|
109
|
+
end
|
|
107
110
|
end
|
|
108
|
-
end
|
|
109
111
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
NA::Benchmark.measure('Pager.page call') do
|
|
113
|
+
NA::Pager.page(output)
|
|
114
|
+
end
|
|
112
115
|
end
|
|
113
116
|
end
|
|
114
|
-
end
|
|
115
117
|
end
|
|
116
118
|
end
|
|
117
119
|
end
|
data/lib/na/array.rb
CHANGED
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class ::Array
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
## @return [Array] Array without "bad" elements
|
|
9
|
-
##
|
|
4
|
+
# Like Array#compact -- removes nil items, but also
|
|
5
|
+
# removes empty strings, zero or negative numbers and FalseClass items
|
|
6
|
+
#
|
|
7
|
+
# @return [Array] Array without "bad" elements
|
|
10
8
|
def remove_bad
|
|
11
9
|
compact.map { |x| x.is_a?(String) ? x.strip : x }.select(&:good?)
|
|
12
10
|
end
|
|
13
11
|
|
|
12
|
+
# Wrap each string in the array to the given width and indent, with color
|
|
13
|
+
#
|
|
14
|
+
# @param width [Integer] Maximum line width
|
|
15
|
+
# @param indent [Integer] Indentation spaces
|
|
16
|
+
# @param color [String] Color code to apply
|
|
17
|
+
# @return [Array, String] Wrapped and colorized lines
|
|
14
18
|
def wrap(width, indent, color)
|
|
15
19
|
return map { |l| "#{color} #{l.wrap(width, 2)}" } if width < 60
|
|
16
20
|
|
|
17
21
|
map! do |l|
|
|
18
|
-
"#{color}#{' ' * indent
|
|
22
|
+
"#{color}#{' ' * indent}• #{l.wrap(width, indent)}{x}"
|
|
19
23
|
end
|
|
20
24
|
"\n#{join("\n")}"
|
|
21
25
|
end
|
data/lib/na/benchmark.rb
CHANGED
|
@@ -5,12 +5,19 @@ module NA
|
|
|
5
5
|
class << self
|
|
6
6
|
attr_accessor :enabled, :timings
|
|
7
7
|
|
|
8
|
+
# Initialize benchmarking state
|
|
9
|
+
#
|
|
10
|
+
# @return [void]
|
|
8
11
|
def init
|
|
9
|
-
@enabled =
|
|
12
|
+
@enabled = %w[1 true].include?(ENV.fetch('NA_BENCHMARK', nil))
|
|
10
13
|
@timings = []
|
|
11
14
|
@start_time = Time.now
|
|
12
15
|
end
|
|
13
16
|
|
|
17
|
+
# Measure the execution time of a block
|
|
18
|
+
#
|
|
19
|
+
# @param label [String] Label for the measurement
|
|
20
|
+
# @return [Object] Result of the block
|
|
14
21
|
def measure(label)
|
|
15
22
|
return yield unless @enabled
|
|
16
23
|
|
|
@@ -21,24 +28,29 @@ module NA
|
|
|
21
28
|
result
|
|
22
29
|
end
|
|
23
30
|
|
|
31
|
+
# Output a performance report to STDERR
|
|
32
|
+
#
|
|
33
|
+
# @return [void]
|
|
24
34
|
def report
|
|
25
35
|
return unless @enabled
|
|
26
36
|
|
|
27
37
|
total = @timings.sum { |t| t[:duration] }
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
warn "\n#{NA::Color.template('{y}=== NA Performance Report ===')}"
|
|
39
|
+
warn NA::Color.template("{dw}Total: {bw}#{total.round(2)}ms{x}")
|
|
40
|
+
warn NA::Color.template("{dw}GC Count: {bw}#{GC.count}{x}") if defined?(GC)
|
|
41
|
+
if defined?(GC)
|
|
42
|
+
warn NA::Color.template("{dw}Memory: {bw}#{(GC.stat[:heap_live_slots] * 40 / 1024.0).round(1)}KB{x}")
|
|
43
|
+
end
|
|
44
|
+
warn ''
|
|
33
45
|
|
|
34
46
|
@timings.each do |timing|
|
|
35
|
-
pct = total
|
|
47
|
+
pct = total.positive? ? ((timing[:duration] / total) * 100).round(1) : 0
|
|
36
48
|
bar = '█' * [(pct / 2).round, 50].min
|
|
37
|
-
|
|
49
|
+
warn NA::Color.template(
|
|
38
50
|
"{dw}[{y}#{bar.ljust(25)}{dw}] {bw}#{timing[:duration].to_s.rjust(7)}ms {dw}(#{pct.to_s.rjust(5)}%) {x}#{timing[:label]}"
|
|
39
51
|
)
|
|
40
52
|
end
|
|
41
|
-
|
|
53
|
+
warn NA::Color.template("{y}#{'=' * 50}{x}\n")
|
|
42
54
|
end
|
|
43
55
|
end
|
|
44
56
|
end
|
data/lib/na/colors.rb
CHANGED
|
@@ -5,7 +5,7 @@ module NA
|
|
|
5
5
|
# Terminal output color functions.
|
|
6
6
|
module Color
|
|
7
7
|
# Regexp to match excape sequences
|
|
8
|
-
ESCAPE_REGEX = /(?<=\[)(?:(?:(?:[349]|10)[0-9]|[0-9])?;?)+(?=m)
|
|
8
|
+
ESCAPE_REGEX = /(?<=\[)(?:(?:(?:[349]|10)[0-9]|[0-9])?;?)+(?=m)/.freeze
|
|
9
9
|
|
|
10
10
|
# All available color names. Available as methods and string extensions.
|
|
11
11
|
#
|
|
@@ -99,35 +99,33 @@ module NA
|
|
|
99
99
|
|
|
100
100
|
# Template coloring
|
|
101
101
|
class ::String
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
## @return [String] a valid color name
|
|
110
|
-
##
|
|
102
|
+
# Extract the longest valid %color name from a string.
|
|
103
|
+
#
|
|
104
|
+
# Allows %colors to bleed into other text and still
|
|
105
|
+
# be recognized, e.g. %greensomething still finds
|
|
106
|
+
# %green.
|
|
107
|
+
#
|
|
108
|
+
# @return [String] a valid color name
|
|
111
109
|
def validate_color
|
|
112
110
|
valid_color = nil
|
|
113
111
|
compiled = ''
|
|
114
|
-
normalize_color.
|
|
112
|
+
normalize_color.chars.each do |char|
|
|
115
113
|
compiled += char
|
|
116
|
-
|
|
114
|
+
if Color.attributes.include?(compiled.to_sym) || compiled =~ /^([fb]g?)?#([a-f0-9]{6})$/i
|
|
115
|
+
valid_color = compiled
|
|
116
|
+
end
|
|
117
117
|
end
|
|
118
118
|
|
|
119
119
|
valid_color
|
|
120
120
|
end
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
## @return [String] Normalized color name
|
|
128
|
-
##
|
|
122
|
+
# Normalize a color name, removing underscores,
|
|
123
|
+
# replacing "bright" with "bold", and converting
|
|
124
|
+
# bgbold to boldbg
|
|
125
|
+
#
|
|
126
|
+
# @return [String] Normalized color name
|
|
129
127
|
def normalize_color
|
|
130
|
-
gsub(
|
|
128
|
+
gsub('_', '').sub(/bright/i, 'bold').sub('bgbold', 'boldbg')
|
|
131
129
|
end
|
|
132
130
|
|
|
133
131
|
# Get the calculated ANSI color at the end of the
|
|
@@ -159,20 +157,20 @@ module NA
|
|
|
159
157
|
rgbb = c
|
|
160
158
|
end
|
|
161
159
|
else
|
|
162
|
-
c.split(
|
|
160
|
+
c.split(';').each do |i|
|
|
163
161
|
x = i.to_i
|
|
164
162
|
if x <= 9
|
|
165
163
|
em << x
|
|
166
|
-
elsif x
|
|
164
|
+
elsif x.between?(30, 39)
|
|
167
165
|
rgbf = nil
|
|
168
166
|
fg = x
|
|
169
|
-
elsif x
|
|
167
|
+
elsif x.between?(40, 49)
|
|
170
168
|
rgbb = nil
|
|
171
169
|
bg = x
|
|
172
|
-
elsif x
|
|
170
|
+
elsif x.between?(90, 97)
|
|
173
171
|
rgbf = nil
|
|
174
172
|
fg = x
|
|
175
|
-
elsif x
|
|
173
|
+
elsif x.between?(100, 107)
|
|
176
174
|
rgbb = nil
|
|
177
175
|
bg = x
|
|
178
176
|
end
|
|
@@ -208,48 +206,48 @@ module NA
|
|
|
208
206
|
# Pre-computed colors hash (expensive to create, so we cache it)
|
|
209
207
|
def colors_hash
|
|
210
208
|
@colors_hash ||= { w: white, k: black, g: green, l: blue,
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
209
|
+
y: yellow, c: cyan, m: magenta, r: red,
|
|
210
|
+
W: bgwhite, K: bgblack, G: bggreen, L: bgblue,
|
|
211
|
+
Y: bgyellow, C: bgcyan, M: bgmagenta, R: bgred,
|
|
212
|
+
d: dark, b: bold, u: underline, i: italic, x: reset }
|
|
215
213
|
end
|
|
216
214
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
215
|
+
#
|
|
216
|
+
# Enables colored output
|
|
217
|
+
#
|
|
218
|
+
# @example Turn color on or off based on TTY
|
|
219
|
+
# NA::Color.coloring = STDOUT.isatty
|
|
222
220
|
def coloring
|
|
223
221
|
@coloring ||= true
|
|
224
222
|
end
|
|
225
223
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
224
|
+
#
|
|
225
|
+
# Convert a template string to a colored string.
|
|
226
|
+
# Colors are specified with single letters inside
|
|
227
|
+
# curly braces. Uppercase changes background color.
|
|
228
|
+
#
|
|
229
|
+
# w: white, k: black, g: green, l: blue, y: yellow,
|
|
230
|
+
# c: cyan, m: magenta, r: red, b: bold, u: underline,
|
|
231
|
+
# i: italic, x: reset (remove background, color,
|
|
232
|
+
# emphasis)
|
|
233
|
+
#
|
|
234
|
+
# Also accepts {#RGB} and {#RRGGBB} strings. Put a b
|
|
235
|
+
# before the hash to make it a background color
|
|
236
|
+
#
|
|
237
|
+
# @example Convert a templated string
|
|
238
|
+
# Color.template('{Rwb}Warning:{x} {w}you look a
|
|
239
|
+
# little {g}ill{x}')
|
|
240
|
+
#
|
|
241
|
+
# @example Convert using RGB colors
|
|
242
|
+
# Color.template('{#f0a}This is an RGB color')
|
|
243
|
+
#
|
|
244
|
+
# @param input [String, Array] The template
|
|
245
|
+
# string. If this is an array, the
|
|
246
|
+
# elements will be joined with a
|
|
247
|
+
# space.
|
|
248
|
+
#
|
|
249
|
+
# @return [String] Colorized string
|
|
250
|
+
#
|
|
253
251
|
def template(input)
|
|
254
252
|
input = input.join(' ') if input.is_a? Array
|
|
255
253
|
return input.gsub(/(?<!\\)\{#?(\w+)\}/i, '') unless NA::Color.coloring?
|
|
@@ -265,9 +263,9 @@ module NA
|
|
|
265
263
|
end
|
|
266
264
|
|
|
267
265
|
# Convert to format string
|
|
268
|
-
fmt = processed_input.gsub(
|
|
266
|
+
fmt = processed_input.gsub('%', '%%')
|
|
269
267
|
fmt = fmt.gsub(/(?<!\\)\{(\w+)\}/i) do
|
|
270
|
-
Regexp.last_match(1).
|
|
268
|
+
Regexp.last_match(1).chars.map { |c| "%<#{c}>s" }.join
|
|
271
269
|
end
|
|
272
270
|
|
|
273
271
|
# Use pre-computed colors hash
|
|
@@ -283,18 +281,18 @@ module NA
|
|
|
283
281
|
new_method = <<-EOSCRIPT
|
|
284
282
|
# Color string as #{c}
|
|
285
283
|
def #{c}(string = nil)
|
|
286
|
-
result = ''
|
|
287
|
-
result << "\e[#{v}m" if NA::Color.coloring?
|
|
284
|
+
result = ''.dup
|
|
285
|
+
result << "\e[#{v}m".dup if NA::Color.coloring?
|
|
288
286
|
if block_given?
|
|
289
287
|
result << yield
|
|
290
288
|
elsif string.respond_to?(:to_str)
|
|
291
|
-
result << string.to_str
|
|
289
|
+
result << string.to_str.dup
|
|
292
290
|
elsif respond_to?(:to_str)
|
|
293
|
-
result << to_str
|
|
291
|
+
result << to_str.dup
|
|
294
292
|
else
|
|
295
293
|
return result #only switch on
|
|
296
294
|
end
|
|
297
|
-
result << "\e[0m" if NA::Color.coloring?
|
|
295
|
+
result << "\e[0m".dup if NA::Color.coloring?
|
|
298
296
|
result
|
|
299
297
|
end
|
|
300
298
|
EOSCRIPT
|
|
@@ -306,19 +304,19 @@ module NA
|
|
|
306
304
|
# Accept brightwhite in addition to boldwhite
|
|
307
305
|
new_method = <<-EOSCRIPT
|
|
308
306
|
# color string as #{c}
|
|
309
|
-
def #{c.to_s.sub(
|
|
310
|
-
result = ''
|
|
311
|
-
result << "\e[#{v}m" if NA::Color.coloring?
|
|
307
|
+
def #{c.to_s.sub('bold', 'bright')}(string = nil)
|
|
308
|
+
result = ''.dup
|
|
309
|
+
result << "\e[#{v}m".dup if NA::Color.coloring?
|
|
312
310
|
if block_given?
|
|
313
311
|
result << yield
|
|
314
312
|
elsif string.respond_to?(:to_str)
|
|
315
|
-
result << string.to_str
|
|
313
|
+
result << string.to_str.dup
|
|
316
314
|
elsif respond_to?(:to_str)
|
|
317
|
-
result << to_str
|
|
315
|
+
result << to_str.dup
|
|
318
316
|
else
|
|
319
317
|
return result #only switch on
|
|
320
318
|
end
|
|
321
|
-
result << "\e[0m" if NA::Color.coloring?
|
|
319
|
+
result << "\e[0m".dup if NA::Color.coloring?
|
|
322
320
|
result
|
|
323
321
|
end
|
|
324
322
|
EOSCRIPT
|
|
@@ -326,13 +324,13 @@ module NA
|
|
|
326
324
|
module_eval(new_method)
|
|
327
325
|
end
|
|
328
326
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
327
|
+
#
|
|
328
|
+
# Generate escape codes for hex colors
|
|
329
|
+
#
|
|
330
|
+
# @param hex [String] The hexadecimal color code
|
|
331
|
+
#
|
|
332
|
+
# @return [String] ANSI escape string
|
|
333
|
+
#
|
|
336
334
|
def rgb(hex)
|
|
337
335
|
is_bg = hex.match(/^bg?#/) ? true : false
|
|
338
336
|
hex_string = hex.sub(/^([fb]g?)?#/, '')
|
|
@@ -345,13 +343,12 @@ module NA
|
|
|
345
343
|
t << parts[e]
|
|
346
344
|
t << parts[e]
|
|
347
345
|
end
|
|
348
|
-
hex_string = t.join
|
|
346
|
+
hex_string = t.join
|
|
349
347
|
end
|
|
350
348
|
|
|
351
349
|
parts = hex_string.match(/(?<r>..)(?<g>..)(?<b>..)/)
|
|
352
|
-
t = []
|
|
353
|
-
|
|
354
|
-
t << parts[e].hex
|
|
350
|
+
t = %w[r g b].map do |e|
|
|
351
|
+
parts[e].hex
|
|
355
352
|
end
|
|
356
353
|
|
|
357
354
|
"\e[#{is_bg ? '48' : '38'};2;#{t.join(';')}m"
|
|
@@ -359,7 +356,7 @@ module NA
|
|
|
359
356
|
|
|
360
357
|
# Regular expression that is used to scan for ANSI-sequences while
|
|
361
358
|
# uncoloring strings.
|
|
362
|
-
COLORED_REGEXP = /\e\[(?:(?:(?:[349]|10)[0-9]|[0-9])?;?)+m
|
|
359
|
+
COLORED_REGEXP = /\e\[(?:(?:(?:[349]|10)[0-9]|[0-9])?;?)+m/.freeze
|
|
363
360
|
|
|
364
361
|
# Returns an uncolored version of the string, that is all
|
|
365
362
|
# ANSI-sequences are stripped from the string.
|
|
@@ -379,6 +376,7 @@ module NA
|
|
|
379
376
|
def attributes
|
|
380
377
|
ATTRIBUTE_NAMES
|
|
381
378
|
end
|
|
379
|
+
|
|
382
380
|
extend self
|
|
383
381
|
end
|
|
384
382
|
end
|
data/lib/na/editor.rb
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'English'
|
|
4
|
+
|
|
1
5
|
module NA
|
|
2
6
|
module Editor
|
|
3
7
|
class << self
|
|
4
8
|
def default_editor(prefer_git_editor: true)
|
|
5
|
-
if prefer_git_editor
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
editor ||= if prefer_git_editor
|
|
10
|
+
ENV['NA_EDITOR'] || ENV['GIT_EDITOR'] || ENV.fetch('EDITOR', nil)
|
|
11
|
+
else
|
|
12
|
+
ENV['NA_EDITOR'] || ENV['EDITOR'] || ENV.fetch('GIT_EDITOR', nil)
|
|
13
|
+
end
|
|
10
14
|
|
|
11
15
|
return editor if editor&.good? && TTY::Which.exist?(editor)
|
|
12
16
|
|
|
13
|
-
NA.notify(
|
|
17
|
+
NA.notify('No EDITOR environment variable, testing available editors', debug: true)
|
|
14
18
|
editors = %w[vim vi code subl mate mvim nano emacs]
|
|
15
19
|
editors.each do |ed|
|
|
16
20
|
try = TTY::Which.which(ed)
|
|
@@ -43,11 +47,10 @@ module NA
|
|
|
43
47
|
"#{editor} #{args.join(' ')}"
|
|
44
48
|
end
|
|
45
49
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
##
|
|
50
|
+
# Create a process for an editor and wait for the file handle to return
|
|
51
|
+
#
|
|
52
|
+
# @param input [String] Text input for editor
|
|
53
|
+
# @return [String] Edited text
|
|
51
54
|
def fork_editor(input = '', message: :default)
|
|
52
55
|
# raise NonInteractive, 'Non-interactive terminal' unless $stdout.isatty || ENV['DOING_EDITOR_TEST']
|
|
53
56
|
|
|
@@ -78,8 +81,8 @@ module NA
|
|
|
78
81
|
Process.wait(pid)
|
|
79
82
|
|
|
80
83
|
begin
|
|
81
|
-
if
|
|
82
|
-
input =
|
|
84
|
+
if $CHILD_STATUS.exitstatus.zero?
|
|
85
|
+
input = File.read(tmpfile.path)
|
|
83
86
|
else
|
|
84
87
|
exit_now! 'Cancelled'
|
|
85
88
|
end
|
|
@@ -88,16 +91,13 @@ module NA
|
|
|
88
91
|
tmpfile.unlink
|
|
89
92
|
end
|
|
90
93
|
|
|
91
|
-
input.split(
|
|
94
|
+
input.split("\n").delete_if(&:ignore?).join("\n")
|
|
92
95
|
end
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
##
|
|
99
|
-
## @return [Array] [[String]title, [Note]note]
|
|
100
|
-
##
|
|
97
|
+
# Takes a multi-line string and formats it as an entry
|
|
98
|
+
#
|
|
99
|
+
# @param input [String] The string to parse
|
|
100
|
+
# @return [Array] [[String]title, [Note]note]
|
|
101
101
|
def format_input(input)
|
|
102
102
|
NA.notify("#{NA.theme[:error]}No content in entry", exit_code: 1) if input.nil? || input.strip.empty?
|
|
103
103
|
|
|
@@ -108,7 +108,7 @@ module NA
|
|
|
108
108
|
title = title.expand_date_tags
|
|
109
109
|
|
|
110
110
|
note = if input_lines.length > 1
|
|
111
|
-
input_lines[1
|
|
111
|
+
input_lines[1..]
|
|
112
112
|
else
|
|
113
113
|
[]
|
|
114
114
|
end
|