-- Copyright (c) 2017-2019 Florian Fischer. All rights reserved.
-- Use of this source code is governed by a MIT license found in the LICENSE file.
local spellcheck = {}
spellcheck.lang = os.getenv("LANG"):sub(0,5) or "en_US"
local supress_output = ">/dev/null 2>/dev/null"
if os.execute("type enchant "..supress_output) then
spellcheck.cmd = "enchant -d %s -a"
spellcheck.list_cmd = "enchant -l -d %s -a"
elseif os.execute("type enchant-2 "..supress_output) then
spellcheck.cmd = "enchant-2 -d %s -a"
spellcheck.list_cmd = "enchant-2 -l -d %s -a"
elseif os.execute("type aspell "..supress_output) then
spellcheck.cmd = "aspell -l %s -a"
spellcheck.list_cmd = "aspell list -l %s -a"
elseif os.execute("type hunspell "..supress_output) then
spellcheck.cmd = "hunspell -d %s"
spellcheck.list_cmd = "hunspell -l -d %s"
else
return nil
end
spellcheck.typo_style = "fore:red"
spellcheck.enabled = {}
local ignored = {}
local last_viewport, last_typos = nil, ""
vis.events.subscribe(vis.events.WIN_HIGHLIGHT, function(win)
if not spellcheck.enabled[win] or not win:style_define(42, spellcheck.typo_style) then
return false
end
local viewport = win.viewport
local viewport_text = win.file:content(viewport)
local typos = ""
if last_viewport == viewport_text then
typos = last_typos
else
local cmd = spellcheck.list_cmd:format(spellcheck.lang)
local ret, so, se = vis:pipe(win.file, viewport, cmd)
if ret ~= 0 then
vis:message("calling " .. cmd .. " failed ("..se..")")
return false
end
typos = so or ""
end
local corrections_iter = typos:gmatch("(.-)\n")
local index = 0
for typo in corrections_iter do
if not ignored[typo] then
local start, finish = viewport_text:find(typo, index, true)
win:style(42, viewport.start + start - 1, viewport.start + finish)
index = finish
end
end
last_viewport = viewport_text
last_typos = typos
return true
end)
vis:map(vis.modes.NORMAL, "<C-w>e", function(keys)
spellcheck.enabled[vis.win] = true
return 0
end, "Enable spellchecking in the current window")
vis:map(vis.modes.NORMAL, "<C-w>d", function(keys)
spellcheck.enabled[vis.win] = nil
-- force new highlight
vis.win:draw()
return 0
end, "Disable spellchecking in the current window")
-- toggle spellchecking on <F7>
-- <F7> is used by some word processors (LibreOffice) for spellchecking
-- Thanks to @leorosa for the hint.
vis:map(vis.modes.NORMAL, "<F7>", function(keys)
if not spellcheck.enabled[vis.win] then
spellcheck.enabled[vis.win] = true
else
spellcheck.enabled[vis.win] = nil
vis.win:draw()
end
return 0
end, "Toggle spellchecking in the current window")
vis:map(vis.modes.NORMAL, "<C-w>w", function(keys)
local win = vis.win
local file = win.file
local pos = win.selection.pos
if not pos then return end
local range = file:text_object_word(pos);
if not range then return end
if range.start == range.finish then return end
local cmd = spellcheck.cmd:format(spellcheck.lang)
local ret, so, se = vis:pipe(win.file, range, cmd)
if ret ~= 0 then
vis:message("calling " .. cmd .. " failed ("..se..")")
return false
end
local suggestions = nil
local answer_line = so:match(".-\n(.-)\n.*")
local first_char = answer_line:sub(0,1)
if first_char == "*" then
vis:info(file:content(range).." is correctly spelled")
return true
elseif first_char == "#" then
vis:info("No corrections available for "..file:content(range))
return false
elseif first_char == "&" then
suggestions = answer_line:match("& %S+ %d+ %d+: (.*)")
else
vis:info("Unknown answer: "..answer_line)
return false
end
-- select a correction
local cmd = 'printf "' .. suggestions:gsub(", ", "\\n") .. '\\n" | vis-menu'
local f = io.popen(cmd)
local correction = f:read("*all")
f:close()
-- trim correction
correction = correction:match("^%s*(.-)%s*$")
if correction ~= "" then
win.file:delete(range)
win.file:insert(range.start, correction)
end
win.selection.pos = pos
win:draw()
return 0
end, "Correct misspelled word")
vis:map(vis.modes.NORMAL, "<C-w>i", function(keys)
local win = vis.win
local file = win.file
local pos = win.selection.pos
if not pos then return end
local range = file:text_object_word(pos);
if not range then return end
if range.start == range.finish then return end
ignored[file:content(range)] = true
win:draw()
return 0
end, "Ignore misspelled word")
vis:option_register("spelllang", "string", function(value, toggle)
spellcheck.lang = value
vis:info("Spellchecking language is now "..value)
-- force new highlight
last_viewport = nil
return true
end, "The language used for spellchecking")
return spellcheck