spellr 0.6.0 → 0.7.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/README.md +34 -15
  4. data/exe/spellr +8 -1
  5. data/lib/spellr/backports.rb +48 -21
  6. data/lib/spellr/base_reporter.rb +0 -4
  7. data/lib/spellr/check.rb +7 -47
  8. data/lib/spellr/check_dry_run.rb +14 -0
  9. data/lib/spellr/check_interactive.rb +24 -0
  10. data/lib/spellr/check_parallel.rb +23 -0
  11. data/lib/spellr/cli.rb +5 -90
  12. data/lib/spellr/cli_options.rb +98 -0
  13. data/lib/spellr/column_location.rb +2 -4
  14. data/lib/spellr/config.rb +39 -41
  15. data/lib/spellr/config_loader.rb +3 -29
  16. data/lib/spellr/config_validator.rb +63 -0
  17. data/lib/spellr/file_list.rb +7 -24
  18. data/lib/spellr/interactive.rb +80 -45
  19. data/lib/spellr/interactive_add.rb +20 -16
  20. data/lib/spellr/interactive_replacement.rb +52 -29
  21. data/lib/spellr/key_tuner/naive_bayes.rb +7 -59
  22. data/lib/spellr/key_tuner/possible_key.rb +5 -24
  23. data/lib/spellr/key_tuner/stats.rb +2 -0
  24. data/lib/spellr/language.rb +7 -8
  25. data/lib/spellr/line_location.rb +2 -7
  26. data/lib/spellr/line_tokenizer.rb +1 -9
  27. data/lib/spellr/output.rb +5 -7
  28. data/lib/spellr/output_stubbed.rb +16 -16
  29. data/lib/spellr/quiet_reporter.rb +1 -0
  30. data/lib/spellr/reporter.rb +0 -4
  31. data/lib/spellr/string_format.rb +8 -15
  32. data/lib/spellr/token.rb +13 -29
  33. data/lib/spellr/token_regexps.rb +2 -2
  34. data/lib/spellr/tokenizer.rb +0 -10
  35. data/lib/spellr/validations.rb +31 -0
  36. data/lib/spellr/version.rb +1 -1
  37. data/lib/spellr/wordlist.rb +9 -17
  38. data/lib/spellr/wordlist_reporter.rb +0 -4
  39. data/lib/spellr.rb +12 -0
  40. data/spellr.gemspec +12 -7
  41. data/wordlists/ruby.txt +1 -0
  42. metadata +59 -26
data/lib/spellr/config.rb CHANGED
@@ -3,38 +3,22 @@
3
3
  require_relative '../spellr'
4
4
  require_relative 'config_loader'
5
5
  require_relative 'language'
6
- require_relative 'reporter'
6
+ require_relative 'config_validator'
7
7
  require 'pathname'
8
8
 
9
9
  module Spellr
10
10
  class Config
11
11
  attr_writer :reporter
12
+ attr_writer :checker
12
13
  attr_reader :config_file
13
14
  attr_accessor :quiet
14
15
  alias_method :quiet?, :quiet
16
+ attr_accessor :dry_run
15
17
 
16
18
  def initialize
17
19
  @config = ConfigLoader.new
18
20
  end
19
21
 
20
- def valid?
21
- only_has_one_key_per_language
22
- keys_are_single_characters
23
- errors.empty?
24
- end
25
-
26
- def print_errors
27
- if $stderr.tty?
28
- errors.each { |e| warn "\e[31m#{e}\e[0m" }
29
- else
30
- errors.each { |e| warn e }
31
- end
32
- end
33
-
34
- def errors
35
- @errors ||= []
36
- end
37
-
38
22
  def word_minimum_length
39
23
  @word_minimum_length ||= @config[:word_minimum_length]
40
24
  end
@@ -55,10 +39,6 @@ module Spellr
55
39
  @excludes ||= @config[:excludes] || []
56
40
  end
57
41
 
58
- def color
59
- @config[:color]
60
- end
61
-
62
42
  def languages
63
43
  @languages ||= @config[:languages].map do |key, args|
64
44
  Spellr::Language.new(key, **args)
@@ -66,7 +46,11 @@ module Spellr
66
46
  end
67
47
 
68
48
  def pwd
69
- @pwd ||= Pathname.pwd
49
+ @pwd ||= Pathname.new(ENV['SPELLR_TEST_PWD'] || Dir.pwd)
50
+ end
51
+
52
+ def pwd_s
53
+ @pwd_s ||= pwd.to_s
70
54
  end
71
55
 
72
56
  def languages_for(file)
@@ -78,7 +62,7 @@ module Spellr
78
62
  end
79
63
 
80
64
  def config_file=(value)
81
- ::File.read(value) # raise Errno::ENOENT if the file doesn't exist
65
+ reset!
82
66
  @config = ConfigLoader.new(value)
83
67
  end
84
68
 
@@ -86,32 +70,46 @@ module Spellr
86
70
  @reporter ||= default_reporter
87
71
  end
88
72
 
89
- private
73
+ def checker
74
+ return dry_run_checker if @dry_run
90
75
 
91
- def only_has_one_key_per_language
92
- languages_with_conflicting_keys.each do |conflicts|
93
- errors << "Error: #{conflicts.map(&:name).join(' & ')} share the same language key "\
94
- "(#{conflicts.first.key}). Please define one to be different with `key:`"
95
- end
76
+ @checker ||= default_checker
96
77
  end
97
78
 
98
- def languages_with_conflicting_keys
99
- languages.select(&:addable?).group_by(&:key).values.select do |g|
100
- g.length > 1
101
- end
79
+ def valid?
80
+ Spellr::ConfigValidator.new.valid?
102
81
  end
103
82
 
104
- def keys_are_single_characters
105
- bad_languages = languages.select { |l| l.key.length > 1 }
83
+ def reset! # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
84
+ @config = ConfigLoader.new
85
+ remove_instance_variable(:@languages) if defined?(@languages)
86
+ remove_instance_variable(:@excludes) if defined?(@excludes)
87
+ remove_instance_variable(:@includes) if defined?(@includes)
88
+ remove_instance_variable(:@word_minimum_length) if defined?(@word_minimum_length)
89
+ remove_instance_variable(:@key_heuristic_weight) if defined?(@key_heuristic_weight)
90
+ remove_instance_variable(:@key_minimum_length) if defined?(@key_minimum_length)
91
+ end
106
92
 
107
- bad_languages.each do |language|
108
- errors << "Error: #{language.name} defines a key that is too long (#{language.key}). "\
109
- 'Please change it to be a single character'
110
- end
93
+ private
94
+
95
+ def dry_run_checker
96
+ require_relative 'check_dry_run'
97
+ Spellr::CheckDryRun
111
98
  end
112
99
 
113
100
  def default_reporter
101
+ require_relative 'reporter'
114
102
  Spellr::Reporter.new
115
103
  end
104
+
105
+ def default_checker
106
+ require_relative 'check_parallel'
107
+ Spellr::CheckParallel
108
+ end
109
+
110
+ def clear_pwd
111
+ remove_instance_variable(:@pwd) if defined?(@pwd)
112
+ remove_instance_variable(:@pwd_s) if defined?(@pwd_s)
113
+ end
116
114
  end
117
115
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'yaml'
4
+ require_relative 'backports'
4
5
 
5
6
  module Spellr
6
7
  class ConfigLoader
@@ -10,15 +11,8 @@ module Spellr
10
11
  @config_file = config_file
11
12
  end
12
13
 
13
- def config_file=(value)
14
- # TODO: nicer error message
15
- ::File.read(value) # raise Errno::ENOENT if the file doesn't exist
16
- @config_file = value
17
- @config = nil
18
- end
19
-
20
14
  def [](value)
21
- load_config unless @config
15
+ load_config unless defined?(@config)
22
16
  @config[value]
23
17
  end
24
18
 
@@ -34,27 +28,7 @@ module Spellr
34
28
  def load_yaml(path)
35
29
  return {} unless ::File.exist?(path)
36
30
 
37
- symbolize_yaml_safe_load(path)
38
- end
39
-
40
- def symbolize_yaml_safe_load(path)
41
- if RUBY_VERSION >= '2.5'
42
- YAML.safe_load(::File.read(path), symbolize_names: true)
43
- else
44
- symbolize_names!(YAML.safe_load(::File.read(path)))
45
- end
46
- end
47
-
48
- def symbolize_names!(obj) # rubocop:disable Metrics/MethodLength
49
- case obj
50
- when Hash
51
- obj.keys.each do |key|
52
- obj[key.to_sym] = symbolize_names!(obj.delete(key))
53
- end
54
- when Array
55
- obj.map! { |ea| symbolize_names!(ea) }
56
- end
57
- obj
31
+ YAML.safe_load(::File.read(path), symbolize_names: true)
58
32
  end
59
33
 
60
34
  def merge_config(default, project) # rubocop:disable Metrics/MethodLength
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'validations'
4
+ require 'timeout'
5
+ require 'io/console'
6
+
7
+ module Spellr
8
+ class ConfigValidator
9
+ include Spellr::Validations
10
+
11
+ validate :checker_and_reporter_coexist
12
+ validate :interactive_is_interactive
13
+ validate :only_has_one_key_per_language
14
+ validate :languages_with_conflicting_keys
15
+ validate :keys_are_single_characters
16
+
17
+ def valid?
18
+ raise Spellr::Config::Invalid, errors.join("\n") unless super
19
+ end
20
+
21
+ def interactive_is_interactive # rubocop:disable Metrics/MethodLength
22
+ return unless Spellr.config.reporter.class.name == 'Spellr::Interactive'
23
+
24
+ # I have no idea how to check for this other than call it
25
+ Timeout.timeout(0.0000000000001) do
26
+ $stdin.getch
27
+ end
28
+ rescue Errno::ENOTTY, Errno::ENODEV
29
+ errors << 'CLI error: --interactive is unavailable in a non-interactive terminal'
30
+ rescue Timeout::Error
31
+ nil
32
+ end
33
+
34
+ def checker_and_reporter_coexist
35
+ if Spellr.config.reporter.class.name == 'Spellr::Interactive' &&
36
+ Spellr.config.checker.name == 'Spellr::CheckParallel'
37
+ errors << 'CLI error: --interactive is incompatible with --parallel'
38
+ end
39
+ end
40
+
41
+ def only_has_one_key_per_language
42
+ languages_with_conflicting_keys.each do |conflicts|
43
+ errors << "Config error: #{conflicts.map(&:name).join(' & ')} share the same language key "\
44
+ "(#{conflicts.first.key}). Please define one to be different with `key:`"
45
+ end
46
+ end
47
+
48
+ def languages_with_conflicting_keys
49
+ Spellr.config.languages.select(&:addable?).group_by(&:key).values.select do |g|
50
+ g.length > 1
51
+ end
52
+ end
53
+
54
+ def keys_are_single_characters
55
+ bad_languages = Spellr.config.languages.select { |l| l.key.length > 1 }
56
+
57
+ bad_languages.each do |lang|
58
+ errors << "Config error: #{lang.name} defines a key that is too long (#{lang.key}). "\
59
+ 'Please change it to be a single character'
60
+ end
61
+ end
62
+ end
63
+ end
@@ -8,17 +8,15 @@ module Spellr
8
8
  class FileList
9
9
  include Enumerable
10
10
 
11
- def initialize(*patterns)
11
+ def initialize(patterns = nil)
12
12
  @patterns = patterns
13
13
  end
14
14
 
15
15
  def each
16
- fast_ignore.each do |file|
17
- next unless cli_patterns_ignore.allowed?(file)
18
-
19
- file = Spellr::File.new(file)
16
+ return enum_for(:each) unless block_given?
20
17
 
21
- yield(file)
18
+ fast_ignore.each do |file|
19
+ yield(Spellr::File.new(file))
22
20
  end
23
21
  end
24
22
 
@@ -28,28 +26,13 @@ module Spellr
28
26
 
29
27
  private
30
28
 
31
- # anchored patterns are significantly faster on large codebases
32
- def cli_patterns
33
- @patterns.map do |pattern|
34
- pattern.sub(%r{^(?!(?:[/~*]|\.{1,2}/))}, '/')
35
- end
36
- end
37
-
38
- def gitignore_path
39
- gitignore = ::File.join(Dir.pwd, '.gitignore')
40
- gitignore if ::File.exist?(gitignore)
41
- end
42
-
43
- def fast_ignore
29
+ def fast_ignore # rubocop:disable Metrics/MethodLength
44
30
  FastIgnore.new(
45
31
  ignore_rules: Spellr.config.excludes,
46
32
  include_rules: Spellr.config.includes,
47
- gitignore: gitignore_path
33
+ argv_rules: @patterns,
34
+ root: Spellr.config.pwd_s
48
35
  )
49
36
  end
50
-
51
- def cli_patterns_ignore
52
- @cli_patterns_ignore ||= FastIgnore.new(include_rules: cli_patterns, gitignore: false)
53
- end
54
37
  end
55
38
  end
@@ -1,18 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'io/console'
4
- require 'readline'
5
4
  require_relative '../spellr'
6
5
  require_relative 'interactive_add'
7
6
  require_relative 'interactive_replacement'
8
7
  require_relative 'base_reporter'
9
8
 
10
9
  module Spellr
11
- class Interactive < BaseReporter
12
- def parallel?
13
- false
14
- end
15
-
10
+ class Interactive < BaseReporter # rubocop:disable Metrics/ClassLength
16
11
  def finish # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
17
12
  puts "\n"
18
13
  puts "#{pluralize 'file', counts[:checked]} checked"
@@ -25,21 +20,17 @@ module Spellr
25
20
  end
26
21
 
27
22
  def global_replacements
28
- @global_replacements ||= begin
29
- counts[:global_replacements] = {} unless counts.key?(:global_replacements)
30
- counts[:global_replacements]
31
- end
23
+ @global_replacements ||= counts[:global_replacements] = {}
32
24
  end
33
25
 
34
26
  def global_skips
35
- @global_skips ||= begin
36
- counts[:global_skips] = [] unless counts.key?(:global_skips)
37
- counts[:global_skips]
38
- end
27
+ @global_skips ||= counts[:global_skips] = []
39
28
  end
40
29
 
41
30
  def call(token)
42
- return if attempt_global_replacement(token)
31
+ # if attempt_global_replacement succeeds, then it throws,
32
+ # it acts like a guard clause all by itself.
33
+ attempt_global_replacement(token)
43
34
  return if attempt_global_skip(token)
44
35
 
45
36
  super
@@ -47,24 +38,71 @@ module Spellr
47
38
  prompt(token)
48
39
  end
49
40
 
50
- def stdin_getch
41
+ def prompt_for_key
42
+ print "[ ]\e[2D"
43
+ end
44
+
45
+ def loop_within(seconds) # rubocop:disable Metrics/MethodLength
46
+ # timeout is just because it gets stuck sometimes
47
+ Timeout.timeout(seconds * 10) do
48
+ start_time = monotonic_time
49
+ yield until start_time + seconds < monotonic_time
50
+ end
51
+ rescue Timeout::Error
52
+ # :nocov:
53
+ nil
54
+ # :nocov:
55
+ end
56
+
57
+ def monotonic_time
58
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
59
+ end
60
+
61
+ def stdin_getch(legal_chars) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
51
62
  choice = output.stdin.getch
52
- clear_current_line
53
- choice
63
+
64
+ if legal_chars.include?(choice)
65
+ puts "\e[0K#{bold print_keypress(choice)}]\e[1C"
66
+ choice
67
+ elsif choice == "\e" # mac sends \e[A when up is pressed. thanks.
68
+ print "\a"
69
+ loop_within(0.001) { output.stdin.getch }
70
+
71
+ stdin_getch(legal_chars)
72
+ else
73
+ print "\a"
74
+ stdin_getch(legal_chars)
75
+ end
54
76
  end
55
77
 
56
- private
78
+ ALPHABET = ('A'..'Z').to_a.join
79
+ CTRL = ("\u0001".."\u0026").freeze
80
+ CTRL_STR = CTRL.to_a.join
81
+ def print_keypress(char)
82
+ return char unless CTRL.cover?(char)
57
83
 
58
- def total
59
- counts[:total_skipped] + counts[:total_fixed] + counts[:total_added]
84
+ "^#{char.tr(CTRL_STR, ALPHABET)}"
60
85
  end
61
86
 
62
87
  def prompt(token)
63
- print bold('[r,R,s,S,a,e,?]')
88
+ print "#{key 'add'}, #{key 'replace'}, #{key 'skip'}, #{key 'help'}, [^#{bold 'C'}] to exit: "
89
+ prompt_for_key
64
90
 
65
91
  handle_response(token)
66
- rescue Interrupt
67
- puts '^C again to exit'
92
+ end
93
+
94
+ def clear_line(lines = 1)
95
+ print "\r\e[K"
96
+ (lines - 1).times do
97
+ sleep 0.01
98
+ print "\r\e[1T\e[2K"
99
+ end
100
+ end
101
+
102
+ private
103
+
104
+ def total
105
+ counts[:total_skipped] + counts[:total_fixed] + counts[:total_added]
68
106
  end
69
107
 
70
108
  def attempt_global_skip(token)
@@ -83,15 +121,13 @@ module Spellr
83
121
  throw :check_file_from, token
84
122
  end
85
123
 
86
- def clear_current_line
87
- print "\r\e[K"
88
- end
89
-
90
- def handle_response(token) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
91
- case stdin_getch
92
- when "\u0003" # ctrl c
124
+ def handle_response(token) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
125
+ # :nocov:
126
+ case stdin_getch("qaAsSrR?h\u0003\u0004")
127
+ # :nocov:
128
+ when 'q', "\u0003" # ctrl c
93
129
  exit 1
94
- when 'a'
130
+ when 'a', 'A'
95
131
  Spellr::InteractiveAdd.new(token, self)
96
132
  when 's', "\u0004" # ctrl d
97
133
  handle_skip(token)
@@ -101,13 +137,8 @@ module Spellr
101
137
  Spellr::InteractiveReplacement.new(token, self).global_replace
102
138
  when 'r'
103
139
  Spellr::InteractiveReplacement.new(token, self).replace
104
- when 'e'
105
- Spellr::InteractiveReplacement.new(token, self).replace_line
106
- when '?'
140
+ when '?', 'h'
107
141
  handle_help(token)
108
- else
109
- clear_current_line
110
- call(token)
111
142
  end
112
143
  end
113
144
 
@@ -118,13 +149,17 @@ module Spellr
118
149
  end
119
150
 
120
151
  def handle_help(token) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
121
- puts "#{bold '[r]'} Replace #{red token}"
122
- puts "#{bold '[R]'} Replace all future instances of #{red token}"
123
- puts "#{bold '[s]'} Skip #{red token}"
124
- puts "#{bold '[S]'} Skip all future instances of #{red token}"
125
- puts "#{bold '[a]'} Add #{red token} to a word list"
126
- puts "#{bold '[e]'} Edit the whole line"
127
- puts "#{bold '[?]'} Show this help"
152
+ clear_line(2)
153
+ puts ''
154
+ puts "#{key 'a'} Add #{red token} to a word list"
155
+ puts "#{key 'r'} Replace #{red token}"
156
+ puts "#{key 'R'} Replace this and all future instances of #{red token}"
157
+ puts "#{key 's'} Skip #{red token}"
158
+ puts "#{key 'S'} Skip this and all future instances of #{red token}"
159
+ puts "#{key 'h'} Show this help"
160
+ puts "[ctrl] + #{key 'C'} Exit spellr"
161
+ puts ''
162
+ print "What do you want to do? [ ]\e[2D"
128
163
  handle_response(token)
129
164
  end
130
165
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../spellr'
3
4
  require_relative 'string_format'
4
5
 
5
6
  module Spellr
@@ -12,6 +13,7 @@ module Spellr
12
13
  @token = token
13
14
  @reporter = reporter
14
15
 
16
+ puts ''
15
17
  ask_wordlist
16
18
  end
17
19
 
@@ -28,39 +30,41 @@ module Spellr
28
30
  end
29
31
 
30
32
  def ask_wordlist
31
- puts "Add #{red(token)} to wordlist:"
33
+ addable_languages.each { |l| puts " #{key l.key} #{l.name}" }
34
+ puts " [^#{bold 'C'}] to go back"
35
+ print " Add #{red(token)} to which wordlist? "
36
+ reporter.prompt_for_key
32
37
 
33
- addable_languages.each do |language|
34
- puts "[#{language.key}] #{language.name}"
35
- end
36
-
37
- handle_wordlist_choice(reporter.stdin_getch)
38
+ handle_wordlist_choice
38
39
  end
39
40
 
40
41
  def handle_ctrl_c
41
- puts '^C again to exit'
42
+ reporter.clear_line(language_keys.length + 6)
42
43
  reporter.call(token)
43
44
  end
44
45
 
45
- def handle_wordlist_choice(choice) # rubocop:disable Metrics/MethodLength
46
+ def handle_wordlist_choice
47
+ choice = reporter.stdin_getch("#{language_keys.join}\u0003")
48
+ # :nocov:
46
49
  case choice
47
- when "\u0003"
48
- handle_ctrl_c
49
- when *language_keys
50
- add_to_wordlist(choice)
51
- else
52
- ask_wordlist
50
+ # :nocov:
51
+ when "\u0003" then handle_ctrl_c
52
+ when *language_keys then add_to_wordlist(choice)
53
53
  end
54
54
  end
55
55
 
56
56
  def add_to_wordlist(choice)
57
- wordlist = addable_languages.find { |w| w.key == choice }.project_wordlist
57
+ wordlist = find_wordlist(choice)
58
58
  wordlist << token
59
59
  reporter.increment(:total_added)
60
- puts "Added #{red(token)} to #{wordlist.name} wordlist"
60
+ puts "\nAdded #{red(token)} to the #{bold wordlist.name} wordlist"
61
61
  throw :check_file_from, token
62
62
  end
63
63
 
64
+ def find_wordlist(key)
65
+ addable_languages.find { |w| w.key == key }.project_wordlist
66
+ end
67
+
64
68
  def puts(str)
65
69
  reporter.puts(str)
66
70
  end
@@ -1,13 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'readline'
4
+ require_relative 'string_format'
5
+
3
6
  module Spellr
4
7
  class InteractiveReplacement
5
8
  include Spellr::StringFormat
6
9
 
7
- attr_reader :token, :reporter, :original_token, :token_highlight, :suffix
10
+ attr_reader :token, :reporter, :token_highlight
11
+ attr_accessor :global
12
+ alias_method :global?, :global
8
13
 
9
14
  def initialize(token, reporter)
10
- @original_token = @token = token
15
+ @token = token
11
16
  @token_highlight = red(token)
12
17
  @reporter = reporter
13
18
  Readline.input = reporter.output.stdin
@@ -15,51 +20,69 @@ module Spellr
15
20
  end
16
21
 
17
22
  def global_replace
18
- replace { |replacement| reporter.global_replacements[token.to_s] = replacement }
19
- end
20
-
21
- def replace_line
22
- @token = token.line
23
- @token_highlight = token.highlight(original_token.char_range).chomp
24
- @suffix = "\n"
25
-
23
+ self.global = true
26
24
  replace
27
25
  end
28
26
 
29
27
  def complete_replacement(replacement)
30
- token.replace("#{replacement}#{suffix}")
28
+ handle_global_replacement(replacement)
29
+ token.replace(replacement)
31
30
 
32
31
  reporter.increment(:total_fixed)
33
- puts "Replaced #{red(token.chomp)} with #{green(replacement)}"
32
+ puts "\n\e[0mReplaced #{'all ' if global?}#{token_highlight} with #{green(replacement)}"
34
33
  throw :check_file_from, token
35
34
  end
36
35
 
37
- def replace # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
38
- readline_editable_print(token.chomp)
36
+ def handle_global_replacement(replacement)
37
+ reporter.global_replacements[token.to_s] = replacement if global?
38
+ end
39
39
 
40
- puts "#{aqua '>>'} #{token_highlight}"
41
- replacement = Readline.readline("#{aqua '=>'} ")
40
+ def ask_replacement
41
+ puts ''
42
+ puts " #{lighten '[^C] to go back'}"
43
+ prompt_replacement
44
+ end
45
+
46
+ def prompt_replacement
47
+ Readline.pre_input_hook = -> { pre_input_hook(token) }
48
+ prompt = " Replace #{'all ' if global?}#{token_highlight} with: \e[32m"
49
+ Readline.readline(prompt)
50
+ rescue Interrupt
51
+ handle_ctrl_c
52
+ end
42
53
 
43
- return reporter.call(token) if replacement.empty?
54
+ def re_ask_replacement
55
+ print "\e[0m\a\e[1T"
56
+
57
+ try_replace(prompt_replacement)
58
+ end
59
+
60
+ def try_replace(replacement)
61
+ return re_ask_replacement if replacement == token
62
+ return re_ask_replacement if replacement.empty?
44
63
 
45
- yield replacement if block_given?
46
64
  complete_replacement(replacement)
47
- rescue Interrupt
48
- puts '^C again to exit'
49
- reporter.call(original_token)
65
+ end
66
+
67
+ def replace
68
+ try_replace(ask_replacement)
69
+ end
70
+
71
+ def handle_ctrl_c
72
+ print "\e[0m"
73
+ reporter.clear_line(5)
74
+ reporter.call(token)
50
75
  end
51
76
 
52
77
  private
53
78
 
54
- def readline_editable_print(string) # rubocop:disable Metrics/MethodLength
55
- Readline.pre_input_hook = lambda {
56
- Readline.refresh_line
57
- Readline.insert_text string.to_s
58
- Readline.redisplay
79
+ def pre_input_hook(value)
80
+ Readline.refresh_line
81
+ Readline.insert_text value.to_s
82
+ Readline.redisplay
59
83
 
60
- # Remove the hook right away.
61
- Readline.pre_input_hook = nil
62
- }
84
+ # Remove the hook right away.
85
+ Readline.pre_input_hook = nil
63
86
  end
64
87
 
65
88
  def puts(str)