Module: Rvim::Spell

Defined in:
lib/rvim/spell.rb

Constant Summary collapse

DEFAULT_DICT_PATHS =
[
  '/usr/share/dict/words',
  '/usr/share/dict/american-english',
  '/usr/share/dict/british-english',
].freeze

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.bad_setObject



25
26
27
# File 'lib/rvim/spell.rb', line 25

def bad_set
  @bad_set ||= load_user_set('bad.txt')
end

.dictObject



17
18
19
# File 'lib/rvim/spell.rb', line 17

def dict
  @dict ||= load_dictionary
end

.good_setObject



21
22
23
# File 'lib/rvim/spell.rb', line 21

def good_set
  @good_set ||= load_user_set('good.txt')
end

Class Method Details

.add_bad(word) ⇒ Object



62
63
64
65
66
# File 'lib/rvim/spell.rb', line 62

def add_bad(word)
  bad_set << word.downcase
  good_set.delete(word.downcase)
  persist('bad.txt', bad_set)
end

.add_good(word) ⇒ Object



56
57
58
59
60
# File 'lib/rvim/spell.rb', line 56

def add_good(word)
  good_set << word.downcase
  bad_set.delete(word.downcase)
  persist('good.txt', good_set)
end

.cache_dirObject



100
101
102
103
104
# File 'lib/rvim/spell.rb', line 100

def cache_dir
  base = ENV['XDG_CACHE_HOME']
  base = File.expand_path('~/.cache') if base.nil? || base.empty?
  File.join(base, 'rvim', 'spell')
end

.distance(a, b) ⇒ Object



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/rvim/spell.rb', line 81

def distance(a, b)
  m = a.length
  n = b.length
  return n if m.zero?
  return m if n.zero?

  prev = (0..n).to_a
  cur = Array.new(n + 1, 0)
  (1..m).each do |i|
    cur[0] = i
    (1..n).each do |j|
      cost = a[i - 1] == b[j - 1] ? 0 : 1
      cur[j] = [cur[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost].min
    end
    prev, cur = cur, prev
  end
  prev[n]
end

.load_dictionary(paths = DEFAULT_DICT_PATHS) ⇒ Object



29
30
31
32
33
34
35
36
37
# File 'lib/rvim/spell.rb', line 29

def load_dictionary(paths = DEFAULT_DICT_PATHS)
  paths.each do |p|
    next unless File.exist?(p)

    words = File.foreach(p).map { |l| l.chomp.downcase }
    return words.to_set
  end
  Set.new
end

.load_user_set(filename) ⇒ Object



106
107
108
109
110
111
112
113
# File 'lib/rvim/spell.rb', line 106

def load_user_set(filename)
  path = File.join(cache_dir, filename)
  return Set.new unless File.exist?(path)

  File.foreach(path).each_with_object(Set.new) { |l, s| s << l.chomp.downcase }
rescue
  Set.new
end

.misspelled?(word) ⇒ Boolean

Returns:

  • (Boolean)


45
46
47
48
49
50
51
52
53
54
# File 'lib/rvim/spell.rb', line 45

def misspelled?(word)
  return false if word.nil? || word.empty?
  return false unless word.match?(/\A[A-Za-z]/)

  w = word.downcase
  return true if bad_set.include?(w)
  return false if good_set.include?(w)

  !dict.include?(w)
end

.persist(filename, words) ⇒ Object



115
116
117
118
119
120
# File 'lib/rvim/spell.rb', line 115

def persist(filename, words)
  FileUtils.mkdir_p(cache_dir)
  File.write(File.join(cache_dir, filename), words.to_a.sort.join("\n"))
rescue
  nil
end

.reset!Object



39
40
41
42
43
# File 'lib/rvim/spell.rb', line 39

def reset!
  @dict = nil
  @good_set = nil
  @bad_set = nil
end

.suggest(word, n: 5, max_dist: 3) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/rvim/spell.rb', line 68

def suggest(word, n: 5, max_dist: 3)
  w = word.downcase
  scored = []
  dict.each do |entry|
    delta = (entry.length - w.length).abs
    next if delta > max_dist

    d = distance(w, entry)
    scored << [d, entry] if d <= max_dist
  end
  scored.sort.first(n).map { |_, e| e }
end