na 1.2.35 → 1.2.38

Sign up to get free protection for your applications and to get access to all the features.
data/bin/commands/next.rb CHANGED
@@ -37,7 +37,7 @@ class App
37
37
 
38
38
  c.desc 'Filter results using search terms'
39
39
  c.arg_name 'QUERY'
40
- c.flag %i[search], multiple: true
40
+ c.flag %i[search find grep], multiple: true
41
41
 
42
42
  c.desc 'Search query is regular expression'
43
43
  c.switch %i[regex], negatable: false
@@ -52,10 +52,10 @@ class App
52
52
  c.switch %i[done]
53
53
 
54
54
  c.desc 'Output actions nested by file'
55
- c.switch %[nest], negatable: false
55
+ c.switch %i[nest], negatable: false
56
56
 
57
57
  c.desc 'Output actions nested by file and project'
58
- c.switch %[omnifocus], negatable: false
58
+ c.switch %i[omnifocus], negatable: false
59
59
 
60
60
  c.action do |global_options, options, args|
61
61
  if global_options[:add]
@@ -75,73 +75,106 @@ class App
75
75
  options[:depth].nil? ? global_options[:depth].to_i : options[:depth].to_i
76
76
  end
77
77
 
78
- all_req = options[:tagged].join(' ') !~ /[+!\-]/ && !options[:or]
78
+ if options[:exact] || options[:regex]
79
+ search = options[:search].join(' ')
80
+ else
81
+ rx = [
82
+ '(?<=\A|[ ,])(?<req>[+!-])?@(?<tag>[^ *=<>$~\^,@(]+)',
83
+ '(?:\((?<value>.*?)\)| *(?<op>=~|[=<>~]{1,2}|[*$\^]=) *',
84
+ '(?<val>.*?(?=\Z|[,@])))?'
85
+ ].join('')
86
+ search = options[:search].join(' ').gsub(Regexp.new(rx)) do
87
+ m = Regexp.last_match
88
+ string = if m['value']
89
+ "#{m['req']}#{m['tag']}=#{m['value']}"
90
+ else
91
+ m[0]
92
+ end
93
+ options[:tagged] << string.sub(/@/, '')
94
+ ''
95
+ end
96
+ end
97
+
98
+ search = search.gsub(/,/, '').gsub(/ +/, ' ') unless search.nil?
99
+
100
+ all_req = options[:tagged].join(' ') !~ /(?<=[, ])[+!-]/ && !options[:or]
79
101
  tags = []
80
102
  options[:tagged].join(',').split(/ *, */).each do |arg|
81
- m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
103
+ m = arg.match(/^(?<req>[+!-])?(?<tag>[^ =<>$*~\^]+?) *(?:(?<op>[=<>~]{1,2}|[*$\^]=) *(?<val>.*?))?$/)
82
104
 
83
105
  tags.push({
84
106
  tag: m['tag'].wildcard_to_rx,
85
107
  comp: m['op'],
86
108
  value: m['val'],
87
109
  required: all_req || (!m['req'].nil? && m['req'] == '+'),
88
- negate: !m['req'].nil? && m['req'] =~ /[!\-]/
110
+ negate: !m['req'].nil? && m['req'] =~ /[!-]/ ? true : false
89
111
  })
90
112
  end
91
113
 
92
114
  args.concat(options[:in])
93
115
  if args.count.positive?
94
- all_req = args.join(' ') !~ /[+!\-]/
116
+ all_req = args.join(' ') !~ /(?<=[, ])[+!-]/
95
117
 
96
118
  tokens = []
97
119
  args.each do |arg|
98
120
  arg.split(/ *, */).each do |a|
99
- m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
121
+ m = a.match(/^(?<req>[+!-])?(?<tok>.*?)$/)
100
122
  tokens.push({
101
123
  token: m['tok'],
102
124
  required: !m['req'].nil? && m['req'] == '+',
103
- negate: !m['req'].nil? && m['req'] =~ /[!\-]/
125
+ negate: !m['req'].nil? && m['req'] =~ /[!-]/ ? true : false
104
126
  })
105
127
  end
106
128
  end
107
129
  end
108
130
 
109
- search = nil
110
- if options[:search]
111
- if options[:exact]
112
- search = options[:search].join(' ')
113
- elsif options[:regex]
114
- search = Regexp.new(options[:search].join(' '), Regexp::IGNORECASE)
115
- else
116
- search = []
117
- all_req = options[:search].join(' ') !~ /[+!\-]/ && !options[:or]
118
-
119
- options[:search].join(' ').split(/ /).each do |arg|
120
- m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
121
- search.push({
122
- token: m['tok'],
123
- required: all_req || (!m['req'].nil? && m['req'] == '+'),
124
- negate: !m['req'].nil? && m['req'] =~ /[!\-]/
125
- })
126
- end
131
+ search_for_done = false
132
+ tags.each { |tag| search_for_done = true if tag[:tag] =~ /done/ }
133
+ options[:done] = true if search_for_done
134
+
135
+ search_tokens = nil
136
+ if options[:exact]
137
+ search_tokens = search
138
+ elsif options[:regex]
139
+ search_tokens = Regexp.new(search, Regexp::IGNORECASE)
140
+ else
141
+ search_tokens = []
142
+ all_req = search !~ /(?<=[, ])[+!-]/ && !options[:or]
143
+
144
+ search.split(/ /).each do |arg|
145
+ m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
146
+ search_tokens.push({
147
+ token: m['tok'],
148
+ required: all_req || (!m['req'].nil? && m['req'] == '+'),
149
+ negate: !m['req'].nil? && m['req'] =~ /[!-]/ ? true : false
150
+ })
127
151
  end
128
152
  end
129
153
 
130
154
  NA.na_tag = options[:tag] unless options[:tag].nil?
131
155
  require_na = true
132
156
 
133
- tag = [{ tag: NA.na_tag, value: nil }]
157
+ tag = [{ tag: NA.na_tag, value: nil, required: true, negate: false }]
134
158
  tag << { tag: 'done', value: nil, negate: true } unless options[:done]
135
159
  tag.concat(tags)
136
- files, actions, = NA.parse_actions(depth: depth,
137
- done: options[:done],
138
- query: tokens,
139
- tag: tag,
140
- search: search,
141
- project: options[:project],
142
- require_na: require_na)
143
-
144
- NA.output_actions(actions, depth, files: files, notes: options[:notes], nest: options[:nest], nest_projects: options[:omnifocus])
160
+
161
+ todo = NA::Todo.new({ depth: depth,
162
+ done: options[:done],
163
+ query: tokens,
164
+ tag: tag,
165
+ search: search_tokens,
166
+ project: options[:project],
167
+ require_na: require_na })
168
+ if todo.files.empty?
169
+ NA.notify("#{NA.theme[:error]}No matches found for #{tokens[0][:token]}.
170
+ Run `na todos` to see available todo files.")
171
+ end
172
+ NA::Pager.paginate = false if options[:omnifocus]
173
+ todo.actions.output(depth,
174
+ files: todo.files,
175
+ notes: options[:notes],
176
+ nest: options[:nest],
177
+ nest_projects: options[:omnifocus])
145
178
  end
146
179
  end
147
180
  end
@@ -16,7 +16,7 @@ class App
16
16
 
17
17
  c.action do |_global_options, options, args|
18
18
  if args.count.positive?
19
- all_req = args.join(' ') !~ /[+!-]/
19
+ all_req = args.join(' ') !~ /(?<=[, ])[+!-]/
20
20
 
21
21
  tokens = [{ token: '*', required: all_req, negate: false }]
22
22
  args.each do |arg|
@@ -9,6 +9,8 @@ class App
9
9
  c.desc 'Output the prompt hook for the current shell to STDOUT. Pass an argument to
10
10
  specify a shell (zsh, bash, fish)'
11
11
  c.arg_name 'SHELL', optional: true
12
+ c.default_command :show
13
+
12
14
  c.command %i[show] do |s|
13
15
  s.action do |_global_options, _options, args|
14
16
  shell = if args.count.positive?
@@ -25,8 +25,8 @@ class App
25
25
 
26
26
  if args.empty?
27
27
  searches = NA.load_searches
28
- NA.notify("{bg}Saved searches stored in {bw}#{NA.database_path(file: 'saved_searches.yml')}")
29
- NA.notify(searches.map { |k, v| "{y}#{k}: {w}#{v}" }.join("\n"), exit_code: 0)
28
+ NA.notify("#{NA.theme[:success]}Saved searches stored in #{NA.database_path(file: 'saved_searches.yml').highlight_filename}")
29
+ NA.notify(searches.map { |k, v| "#{NA.theme[:filename]}#{k}: #{NA.theme[:values]}#{v}" }.join("\n"))
30
30
  else
31
31
  args.each do |arg|
32
32
  searches = NA.load_searches
@@ -34,13 +34,12 @@ class App
34
34
  NA.delete_search(arg) if options[:delete]
35
35
 
36
36
  keys = searches.keys.delete_if { |k| k !~ /#{arg}/ }
37
- NA.notify("{r}Search #{arg} not found", exit_code: 1) if keys.empty?
37
+ NA.notify("#{NA.theme[:error]}Search #{arg} not found", exit_code: 1) if keys.empty?
38
38
 
39
39
  key = keys[0]
40
40
  cmd = Shellwords.shellsplit(searches[key])
41
41
  run(cmd)
42
42
  end
43
- exit
44
43
  end
45
44
  end
46
45
  end
@@ -6,7 +6,7 @@ class App
6
6
  long_desc 'Finds actions with tags matching the arguments. An action is shown if it
7
7
  contains all of the tags listed. Add a + before a tag to make it required
8
8
  and others optional. You can specify values using TAG=VALUE pairs.
9
- Use <, >, and = for numeric comparisons, and *=, ^=, and $= for text comparisons.
9
+ Use <, >, and = for numeric comparisons, and *=, ^=, $=, or =~ (regex) for text comparisons.
10
10
  Date comparisons use natural language (`na tagged "due<=today"`) and
11
11
  are detected automatically.'
12
12
  arg_name 'TAG[=VALUE]'
@@ -38,7 +38,7 @@ class App
38
38
 
39
39
  c.desc 'Filter results using search terms'
40
40
  c.arg_name 'QUERY'
41
- c.flag %i[search], multiple: true
41
+ c.flag %i[search find grep], multiple: true
42
42
 
43
43
  c.desc 'Search query is regular expression'
44
44
  c.switch %i[regex], negatable: false
@@ -57,17 +57,18 @@ class App
57
57
  c.flag %i[save]
58
58
 
59
59
  c.desc 'Output actions nested by file'
60
- c.switch %[nest], negatable: false
60
+ c.switch %i[nest], negatable: false
61
61
 
62
62
  c.desc 'Output actions nested by file and project'
63
- c.switch %[omnifocus], negatable: false
63
+ c.switch %i[omnifocus], negatable: false
64
64
 
65
65
  c.action do |global_options, options, args|
66
66
  options[:nest] = true if options[:omnifocus]
67
67
 
68
68
  if options[:save]
69
69
  title = options[:save].gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
70
- NA.save_search(title, "#{NA.command_line.join(' ').sub(/ --save[= ]*\S+/, '').split(' ').map { |t| %("#{t}") }.join(' ')}")
70
+ cmd = NA.command_line.join(' ').sub(/ --save[= ]*\S+/, '').split(' ').map { |t| %("#{t}") }.join(' ')
71
+ NA.save_search(title, cmd)
71
72
  end
72
73
 
73
74
  depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
@@ -78,9 +79,9 @@ class App
78
79
 
79
80
  tags = []
80
81
 
81
- all_req = args.join(' ') !~ /[+!\-]/ && !options[:or]
82
+ all_req = args.join(' ') !~ /(?<=[, ])[+!-]/ && !options[:or]
82
83
  args.join(',').split(/ *, */).each do |arg|
83
- m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?) *(?:(?<op>[=<>]{1,2}|[*$\^]=) *(?<val>.*?))?$/)
84
+ m = arg.match(/^(?<req>[+!-])?(?<tag>[^ =<>$*~\^]+?) *(?:(?<op>[=<>~]{1,2}|[*$\^]=) *(?<val>.*?))?$/)
84
85
  next if m.nil?
85
86
 
86
87
  tags.push({
@@ -88,13 +89,13 @@ class App
88
89
  comp: m['op'],
89
90
  value: m['val'],
90
91
  required: all_req || (!m['req'].nil? && m['req'] == '+'),
91
- negate: !m['req'].nil? && m['req'] =~ /[!\-]/
92
+ negate: !m['req'].nil? && m['req'] =~ /[!-]/
92
93
  })
93
94
  end
94
95
 
95
96
  search_for_done = false
96
97
  tags.each { |tag| search_for_done = true if tag[:tag] =~ /done/ }
97
- tags.push({ tag: 'done', value: nil, negate: true}) unless search_for_done || options[:done]
98
+ tags.push({ tag: 'done', value: nil, negate: true }) unless search_for_done || options[:done]
98
99
  options[:done] = true if search_for_done
99
100
 
100
101
  tokens = nil
@@ -105,49 +106,55 @@ class App
105
106
  tokens = Regexp.new(options[:search].join(' '), Regexp::IGNORECASE)
106
107
  else
107
108
  tokens = []
108
- all_req = options[:search].join(' ') !~ /[+!\-]/ && !options[:or]
109
+ all_req = options[:search].join(' ') !~ /(?<=[, ])[+!-]/ && !options[:or]
109
110
 
110
111
  options[:search].join(' ').split(/ /).each do |arg|
111
112
  m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
112
113
  tokens.push({
113
114
  token: m['tok'],
114
115
  required: all_req || (!m['req'].nil? && m['req'] == '+'),
115
- negate: !m['req'].nil? && m['req'] =~ /[!\-]/
116
+ negate: !m['req'].nil? && m['req'] =~ /[!-]/
116
117
  })
117
118
  end
118
119
  end
119
120
  end
120
121
 
121
- todo = nil
122
+ todos = nil
122
123
  if options[:in]
123
- todo = []
124
+ todos = []
125
+ all_req = options[:in] !~ /(?<=[, ])[+!-]/ && !options[:or]
124
126
  options[:in].split(/ *, */).each do |a|
125
127
  m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
126
- todo.push({
127
- token: m['tok'],
128
- required: all_req || (!m['req'].nil? && m['req'] == '+'),
129
- negate: !m['req'].nil? && m['req'] =~ /[!\-]/
130
- })
128
+ todos.push({
129
+ token: m['tok'],
130
+ required: all_req || (!m['req'].nil? && m['req'] == '+'),
131
+ negate: !m['req'].nil? && m['req'] =~ /[!-]/
132
+ })
131
133
  end
132
134
  end
133
135
 
134
- NA.notify('{br}No actions matched search', exit_code: 1) if tags.empty? && tokens.empty?
135
-
136
- files, actions, = NA.parse_actions(depth: depth,
137
- done: options[:done],
138
- query: todo,
139
- search: tokens,
140
- tag: tags,
141
- negate: options[:invert],
142
- project: options[:project],
143
- require_na: false)
144
- # regexes = tags.delete_if { |token| token[:negate] }.map { |token| token[:token] }
136
+ NA.notify("#{NA.theme[:error]}No actions matched search", exit_code: 1) if tags.empty? && tokens.empty?
137
+
138
+ todo = NA::Todo.new({ depth: depth,
139
+ done: options[:done],
140
+ query: todos,
141
+ search: tokens,
142
+ tag: tags,
143
+ negate: options[:invert],
144
+ project: options[:project],
145
+ require_na: false })
146
+
145
147
  regexes = if tokens.is_a?(Array)
146
148
  tokens.delete_if { |token| token[:negate] }.map { |token| token[:token] }
147
149
  else
148
150
  [tokens]
149
151
  end
150
- NA.output_actions(actions, depth, files: files, regexes: regexes, notes: options[:notes], nest: options[:nest], nest_projects: options[:omnifocus])
152
+ todo.actions.output(depth,
153
+ files: todo.files,
154
+ regexes: regexes,
155
+ notes: options[:notes],
156
+ nest: options[:nest],
157
+ nest_projects: options[:omnifocus])
151
158
  end
152
159
  end
153
160
  end
@@ -8,24 +8,34 @@ class App
8
8
  /, :, or a space, e.g. `na todos code/marked`'
9
9
  arg_name 'QUERY', optional: true
10
10
  command %i[todos] do |c|
11
- c.action do |_global_options, _options, args|
12
- if args.count.positive?
13
- all_req = args.join(' ') !~ /[+!\-]/
11
+ c.desc 'Open the todo database in an editor for manual modification'
12
+ c.switch %i[e edit]
14
13
 
15
- tokens = [{ token: '*', required: all_req, negate: false }]
16
- args.each do |arg|
17
- arg.split(/ *, */).each do |a|
18
- m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
19
- tokens.push({
20
- token: m['tok'],
21
- required: all_req || (!m['req'].nil? && m['req'] == '+'),
22
- negate: !m['req'].nil? && m['req'] =~ /[!\-]/
23
- })
14
+ c.action do |_global_options, options, args|
15
+ if options[:edit]
16
+ system("#{NA::Editor.default_editor(prefer_git_editor: false)} #{NA.database_path}")
17
+ editor = NA::Editor.default_editor(prefer_git_editor: false).highlight_filename
18
+ database = NA.database_path.highlight_filename
19
+ NA.notify("{b}#{NA.theme[:success]}Opened #{database}#{NA.theme[:success]} in #{editor}")
20
+ else
21
+ if args.count.positive?
22
+ all_req = args.join(' ') !~ /(?<=[, ])[+!-]/
23
+
24
+ tokens = [{ token: '*', required: all_req, negate: false }]
25
+ args.each do |arg|
26
+ arg.split(/ *, */).each do |a|
27
+ m = a.match(/^(?<req>[+!-])?(?<tok>.*?)$/)
28
+ tokens.push({
29
+ token: m['tok'],
30
+ required: all_req || (!m['req'].nil? && m['req'] == '+'),
31
+ negate: (!m['req'].nil? && m['req'] =~ /[!-]/) ? true : false
32
+ })
33
+ end
24
34
  end
25
35
  end
26
- end
27
36
 
28
- NA.list_todos(query: tokens)
37
+ NA.list_todos(query: tokens)
38
+ end
29
39
  end
30
40
  end
31
41
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ class App
4
+ extend GLI::App
5
+ desc 'Undo the last change'
6
+ long_desc 'Run without argument to undo most recent change'
7
+ arg_name 'FILE', optional: true, multiple: true
8
+ command %i[undo] do |c|
9
+ c.example 'na undo', desc: 'Undo the last change'
10
+ c.example 'na undo myproject', desc: 'Undo the last change to a file matching "myproject"'
11
+
12
+ c.action do |_global_options, options, args|
13
+ if args.empty?
14
+ NA.restore_last_modified_file
15
+ else
16
+ args.each do |arg|
17
+ NA.restore_last_modified_file(search: arg)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -63,7 +63,7 @@ class App
63
63
  c.desc 'Delete an action'
64
64
  c.switch %i[delete], negatable: false
65
65
 
66
- c.desc "Open action in editor (#{NA.default_editor}).
66
+ c.desc "Open action in editor (#{NA::Editor.default_editor}).
67
67
  Natural language dates will be parsed and converted in date-based tags."
68
68
  c.switch %i[edit], negatable: false
69
69
 
@@ -97,6 +97,8 @@ class App
97
97
  options[:tagged] << '+done'
98
98
  elsif !options[:remove].nil? && !options[:remove].empty?
99
99
  options[:tagged].concat(options[:remove])
100
+ elsif options[:finish]
101
+ options[:tagged] << '-done'
100
102
  end
101
103
 
102
104
  action = if args.count.positive?
@@ -109,8 +111,8 @@ class App
109
111
  ]
110
112
  `gum input #{opts.join(' ')}`.strip
111
113
  elsif $stdin.isatty && options[:tagged].empty?
112
- puts NA::Color.template('{bm}Enter search string:{x}')
113
- reader.read_line(NA::Color.template('{by}> {bw}')).strip
114
+ NA.notify("#{NA.theme[:prompt]}Enter search string:")
115
+ reader.read_line(NA::Color.template("#{NA.theme[:filename]}> #{NA.theme[:action]}")).strip
114
116
  end
115
117
 
116
118
  if action
@@ -121,35 +123,34 @@ class App
121
123
  tokens = Regexp.new(action, Regexp::IGNORECASE)
122
124
  else
123
125
  tokens = []
124
- all_req = action !~ /[+!\-]/ && !options[:or]
126
+ all_req = action !~ /[+!-]/ && !options[:or]
125
127
 
126
128
  action.split(/ /).each do |arg|
127
129
  m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
128
130
  tokens.push({
129
131
  token: m['tok'],
130
132
  required: all_req || (!m['req'].nil? && m['req'] == '+'),
131
- negate: !m['req'].nil? && m['req'] =~ /[!\-]/
133
+ negate: !m['req'].nil? && m['req'] =~ /[!-]/ ? true : false
132
134
  })
133
135
  end
134
136
  end
135
137
  end
136
138
 
137
139
  if (action.nil? || action.empty?) && options[:tagged].empty?
138
- puts 'Empty input, cancelled'
139
- Process.exit 1
140
+ NA.notify("#{NA.theme[:error]}Empty input, cancelled", exit_code: 1)
140
141
  end
141
142
 
142
- all_req = options[:tagged].join(' ') !~ /[+!\-]/ && !options[:or]
143
+ all_req = options[:tagged].join(' ') !~ /[+!-]/ && !options[:or]
143
144
  tags = []
144
145
  options[:tagged].join(',').split(/ *, */).each do |arg|
145
- m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
146
+ m = arg.match(/^(?<req>[+!-])?(?<tag>[^ =<>$*~\^]+?) *(?:(?<op>[=<>~]{1,2}|[*$\^]=) *(?<val>.*?))?$/)
146
147
 
147
148
  tags.push({
148
149
  tag: m['tag'].wildcard_to_rx,
149
150
  comp: m['op'],
150
151
  value: m['val'],
151
152
  required: all_req || (!m['req'].nil? && m['req'] == '+'),
152
- negate: !m['req'].nil? && m['req'] =~ /[!\-]/
153
+ negate: !m['req'].nil? && m['req'] =~ /[!-]/ ? true : false
153
154
  })
154
155
  end
155
156
 
@@ -168,7 +169,7 @@ class App
168
169
  args << '--width $(tput cols)'
169
170
  `gum write #{args.join(' ')}`.strip.split("\n")
170
171
  else
171
- puts NA::Color.template('{bm}Enter a note, {bw}CTRL-d{bm} to end editing{bw}')
172
+ NA.notify("#{NA.theme[:prompt]}Enter a note, {bw}CTRL-d#{NA.theme[:prompt]} to end editing:#{NA.theme[:action]}")
172
173
  reader.read_multiline
173
174
  end
174
175
  end
@@ -180,13 +181,11 @@ class App
180
181
  options[:project]
181
182
  elsif NA.cwd_is == :project
182
183
  NA.cwd
183
- else
184
- nil
185
184
  end
186
185
 
187
186
  if options[:file]
188
187
  file = File.expand_path(options[:file])
189
- NA.notify('{r}File not found', exit_code: 1) unless File.exist?(file)
188
+ NA.notify("#{NA.theme[:error]}File not found", exit_code: 1) unless File.exist?(file)
190
189
 
191
190
  targets = [file]
192
191
  elsif options[:todo]
@@ -196,7 +195,7 @@ class App
196
195
  todo.push({
197
196
  token: m['tok'],
198
197
  required: all_req || (!m['req'].nil? && m['req'] == '+'),
199
- negate: !m['req'].nil? && m['req'] =~ /[!\-]/
198
+ negate: !m['req'].nil? && m['req'] =~ /[!-]/ ? true : false
200
199
  })
201
200
  end
202
201
  dirs = NA.match_working_dir(todo)
@@ -205,17 +204,25 @@ class App
205
204
  targets = [dirs[0]]
206
205
  elsif dirs.count.positive?
207
206
  targets = NA.select_file(dirs, multiple: true)
208
- NA.notify('{r}Cancelled', exit_code: 1) unless targets && targets.count.positive?
207
+ NA.notify("#{NA.theme[:error]}Cancelled", exit_code: 1) unless targets && targets.count.positive?
209
208
  else
210
- NA.notify('{r}Todo not found', exit_code: 1) unless targets && targets.count.positive?
209
+ NA.notify("#{NA.theme[:error]}Todo not found", exit_code: 1) unless targets && targets.count.positive?
211
210
 
212
211
  end
213
212
  else
214
- files = NA.find_files(depth: options[:depth])
215
- NA.notify('{r}No todo file found', exit_code: 1) if files.count.zero?
213
+ files = NA.find_files_matching({
214
+ depth: options[:depth],
215
+ done: options[:done],
216
+ project: target_proj,
217
+ regex: options[:regex],
218
+ require_na: false,
219
+ search: tokens,
220
+ tag: tags
221
+ })
222
+ NA.notify("#{NA.theme[:error]}No todo file found", exit_code: 1) if files.count.zero?
216
223
 
217
224
  targets = files.count > 1 ? NA.select_file(files, multiple: true) : [files[0]]
218
- NA.notify('{r}Cancelled{x}', exit_code: 1) unless files.count.positive?
225
+ NA.notify("#{NA.theme[:error]}Cancelled", exit_code: 1) unless files.count.positive?
219
226
 
220
227
  end
221
228
 
@@ -224,7 +231,7 @@ class App
224
231
  options[:project] = 'Archive'
225
232
  end
226
233
 
227
- NA.notify('{r}No search terms provided', exit_code: 1) if tokens.nil? && options[:tagged].empty?
234
+ NA.notify("#{NA.theme[:error]}No search terms provided", exit_code: 1) if tokens.nil? && options[:tagged].empty?
228
235
 
229
236
  targets.each do |target|
230
237
  NA.update_action(target, tokens,
data/bin/na CHANGED
@@ -37,6 +37,7 @@ class App
37
37
  default_command :next
38
38
 
39
39
  NA::Color.coloring = $stdin.isatty
40
+ NA::Pager.paginate = $stdin.isatty
40
41
 
41
42
  desc 'Add a next action (deprecated, for backwards compatibility)'
42
43
  switch %i[a add], negatable: false
@@ -84,7 +85,7 @@ class App
84
85
 
85
86
  pre do |global, _command, _options, _args|
86
87
  NA.verbose = global[:debug]
87
- NA::Pager.paginate = global[:pager]
88
+ NA::Pager.paginate = global[:pager] && $stdout.isatty
88
89
  NA::Color.coloring = global[:color]
89
90
  NA.extension = global[:ext]
90
91
  NA.na_tag = global[:na_tag]