spellr 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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)