garuda-common-settings/etc/skel/.config/micro/plug/aspell/aspell.lua

659 lines
14 KiB
Lua

VERSION = "1.3.0"
local micro = import("micro")
local shell = import("micro/shell")
local buffer = import("micro/buffer")
local config = import("micro/config")
local util = import("micro/util")
local utf = import("unicode/utf8")
config.RegisterCommonOption("aspell", "check", "auto")
config.RegisterCommonOption("aspell", "lang", "")
config.RegisterCommonOption("aspell", "dict", "")
config.RegisterCommonOption("aspell", "sugmode", "normal")
config.RegisterCommonOption("aspell", "args", "")
function init()
config.MakeCommand("addpersonal", addpersonal, config.NoComplete)
config.MakeCommand("acceptsug", acceptsug, config.NoComplete)
config.MakeCommand("togglecheck", togglecheck, config.NoComplete)
config.AddRuntimeFile("aspell", config.RTHelp, "help/aspell.md")
end
local filterModes = {
xml = "sgml",
["c++"] = "ccpp",
c = "ccpp",
html = "html",
html4 = "html",
html5 = "html",
perl = "perl",
perl6 = "perl",
tex = "tex",
markdown = "markdown",
groff = "nroff",
man = "nroff",
["git-commit"] = "url",
mail = "email"
-- Aspell has comment mode, in which only lines starting with # are checked
-- but it doesn't work for some reason
}
local lock = false
local next = nil
function runAspell(buf, onExit, ...)
local options = {"pipe", "--encoding=utf-8"}
if filterModes[buf:FileType()] then
options[#options + 1] = "--mode=" .. filterModes[buf:FileType()]
end
if buf.Settings["aspell.lang"] ~= "" then
options[#options + 1] = "--lang=" .. buf.Settings["aspell.lang"]
end
if buf.Settings["aspell.dict"] ~= "" then
options[#options + 1] = "--master=" .. buf.Settings["aspell.dict"]
end
if buf.Settings["aspell.sugmode"] ~= "" then
options[#options + 1] = "--sug-mode=" .. buf.Settings["aspell.sugmode"]
end
for _, argument in ipairs(split(buf.Settings["aspell.args"], " ")) do
options[#options + 1] = argument
end
local job = shell.JobSpawn("aspell", options, nil,
nil, onExit, buf, unpack(arg))
-- Enable terse mode
shell.JobSend(job, "!\n")
for i=0, buf:LinesNum() - 1 do
local line = util.String(buf:LineBytes(i))
-- Escape for aspell (it interprets lines that start
-- with % @ ^ ! etc.)
line = "^" .. line .. "\n"
shell.JobSend(job, line)
end
job.Stdin:Close()
end
function spellcheck(buf)
local check = buf.Settings["aspell.check"]
local readcheck = buf.Type.Readonly
if (check == "on" or (check == "auto" and filterModes[buf:FileType()])) and (not readcheck) then
if lock then
next = buf
else
lock = true
runAspell(buf, highlight)
end
else
-- If we aren't supposed to spellcheck, clear the messages
buf:ClearMessages("aspell")
end
end
-- Parses the output of Aspell and returns the list of all misspells.
function parseOutput(out)
local patterns = {"^# (.-) (%d+)$", "^& (.-) %d+ (%d+): (.+)$"}
if out:find("command not found") then
micro.InfoBar():Error(
"Make sure that Aspell is installed and available in your PATH")
return {}
elseif not out:find("International Ispell Version") then
-- Something went wrong, we'll show what Aspell has to say
micro.InfoBar():Error("Aspell: " .. out)
return {}
end
local misspells = {}
local linenumber = 1
local lines = split(out, "\n")
for _, line in ipairs(lines) do
if line == "" then
linenumber = linenumber + 1
else
for _, pattern in ipairs(patterns) do
if string.find(line, pattern) then
local word, offset, suggestions = string.match(line, pattern)
offset = tonumber(offset)
local len = utf.RuneCountInString(word)
misspells[#misspells + 1] = {
word = word,
mstart = buffer.Loc(offset - 1, linenumber - 1),
mend = buffer.Loc(offset - 1 + len, linenumber - 1),
suggestions = suggestions and split(suggestions, ", ") or {},
}
end
end
end
end
return misspells
end
function highlight(out, args)
local buf = args[1]
buf:ClearMessages("aspell")
-- This is a hack that keeps the text shifted two columns to the right
-- even when no gutter messages are shown
local msg = "This message shouldn't be visible (Aspell plugin)"
local bmsg = buffer.NewMessageAtLine("aspell", msg, 0, buffer.MTError)
buf:AddMessage(bmsg)
for _, misspell in ipairs(parseOutput(out)) do
local msg = nil
if #(misspell.suggestions) > 0 then
msg = misspell.word .. " -> " .. table.concat(misspell.suggestions, ", ")
else
msg = misspell.word .. " ->X"
end
local bmsg = buffer.NewMessage("aspell", msg, misspell.mstart,
misspell.mend, buffer.MTWarning)
buf:AddMessage(bmsg)
end
lock = false
if next ~= nil then
spellcheck(next)
next = nil
end
end
function parseMessages(messages)
local patterns = {"^(.-) %-> (.+)$", "^(.-) %->X$"}
if messages == nil then
return {}
end
local misspells = {}
for i=1, #messages do
local message = messages[i]
if message.Owner == "aspell" then
for _, pattern in ipairs(patterns) do
if string.find(message.Msg, pattern) then
local word, suggestions = string.match(message.Msg, pattern)
misspells[#misspells + 1] = {
word = word,
mstart = -message.Start,
mend = -message.End,
suggestions = suggestions and split(suggestions, ", ") or {},
}
end
end
end
end
return misspells
end
function togglecheck(bp, args)
local buf = bp.Buf
local check = buf.Settings["aspell.check"]
if check == "on" or (check == "auto" and filterModes[buf:FileType()]) then
buf.Settings["aspell.check"] = "off"
else
buf.Settings["aspell.check"] = "on"
end
spellcheck(buf)
if args then
return
end
return true
end
function addpersonal(bp, args)
local buf = bp.Buf
local loc = buf:GetActiveCursor().Loc
for _, misspell in ipairs(parseMessages(buf.Messages)) do
local wordInBuf = util.String(buf:Substr(misspell.mstart, misspell.mend))
if loc:GreaterEqual(misspell.mstart) and loc:LessEqual(misspell.mend)
and wordInBuf == misspell.word then
local options = {"pipe", "--encoding=utf-8"}
if buf.Settings["aspell.lang"] ~= "" then
options[#options + 1] = "--lang=" .. buf.Settings["aspell.lang"]
end
if buf.Settings["aspell.dict"] ~= "" then
options[#options + 1] = "--master=" .. buf.Settings["aspell.dict"]
end
for _, argument in ipairs(split(buf.Settings["aspell.args"], " ")) do
options[#options + 1] = argument
end
local job = shell.JobSpawn("aspell", options, nil, nil, function ()
spellcheck(buf)
end)
shell.JobSend(job, "*" .. misspell.word .. "\n#\n")
job.Stdin:Close()
if args then
return
end
return true
end
end
if args then
return
end
return false
end
function acceptsug(bp, args)
local buf = bp.Buf
local n = nil
if args and #args > 0 then
n = tonumber(args[1])
end
local loc = buf:GetActiveCursor().Loc
for _, misspell in ipairs(parseMessages(buf.Messages)) do
local wordInBuf = util.String(buf:Substr(misspell.mstart, misspell.mend))
if loc:GreaterEqual(misspell.mstart) and loc:LessEqual(misspell.mend)
and wordInBuf == misspell.word then
if misspell.suggestions[n] then
-- If n is in the range we'll accept n-th suggestion
buf:GetActiveCursor():GotoLoc(misspell.mend)
buf:Replace(misspell.mstart, misspell.mend, misspell.suggestions[n])
spellcheck(buf)
if args then
return
end
return true
elseif #(misspell.suggestions) > 0 then
-- If n is 0 indicating acceptsug was called with no arguments
-- we will cycle through the suggestions autocomplete-like
buf:GetActiveCursor():GotoLoc(misspell.mend)
buf:Remove(misspell.mstart, misspell.mend)
buf:Autocomplete(function ()
return misspell.suggestions, misspell.suggestions
end)
spellcheck(buf)
if args then
return
end
return true
end
end
end
if args then
return
end
return false
end
function split(str, pat)
local t = {}
local fpat = "(.-)" .. pat
local last_end = 1
local s, e, cap = str:find(fpat, 1)
while s do
if s ~= 1 or cap ~= "" then
table.insert(t, cap)
end
last_end = e+1
s, e, cap = str:find(fpat, last_end)
end
if last_end <= #str then
cap = str:sub(last_end)
table.insert(t, cap)
end
return t
end
-- We need to spellcheck every time, the buffer is modified. Sadly there's
-- no such thing as onBufferModified()
function onBufferOpen(buf)
spellcheck(buf)
end
-- The following callbacks are undocumented
function onRune(bp)
spellcheck(bp.Buf)
end
function onCycleAutocompleteBack(bp)
spellcheck(bp.Buf)
end
-- The following were copied from help keybindings
-- function onCursorUp(bp)
-- end
-- function onCursorDown(bp)
-- end
-- function onCursorPageUp(bp)
-- end
-- function onCursorPageDown(bp)
-- end
-- function onCursorLeft(bp)
-- end
-- function onCursorRight(bp)
-- end
-- function onCursorStart(bp)
-- end
-- function onCursorEnd(bp)
-- end
-- function onSelectToStart(bp)
-- end
-- function onSelectToEnd(bp)
-- end
-- function onSelectUp(bp)
-- end
-- function onSelectDown(bp)
-- end
-- function onSelectLeft(bp)
-- end
-- function onSelectRight(bp)
-- end
-- function onSelectToStartOfText(bp)
-- end
-- function onSelectToStartOfTextToggle(bp)
-- end
-- function onWordRight(bp)
-- end
-- function onWordLeft(bp)
-- end
-- function onSelectWordRight(bp)
-- end
-- function onSelectWordLeft(bp)
-- end
function onMoveLinesUp(bp)
spellcheck(bp.Buf)
end
function onMoveLinesDown(bp)
spellcheck(bp.Buf)
end
function onDeleteWordRight(bp)
spellcheck(bp.Buf)
end
function onDeleteWordLeft(bp)
spellcheck(bp.Buf)
end
-- function onSelectLine(bp)
-- end
-- function onSelectToStartOfLine(bp)
-- end
-- function onSelectToEndOfLine(bp)
-- end
function onInsertNewline(bp)
spellcheck(bp.Buf)
end
function onInsertSpace(bp)
spellcheck(bp.Buf)
end
function onBackspace(bp)
spellcheck(bp.Buf)
end
function onDelete(bp)
spellcheck(bp.Buf)
end
-- function onCenter(bp)
-- end
function onInsertTab(bp)
spellcheck(bp.Buf)
end
-- function onSave(bp)
-- end
-- function onSaveAll(bp)
-- end
-- function onSaveAs(bp)
-- end
-- function onFind(bp)
-- end
-- function onFindLiteral(bp)
-- end
-- function onFindNext(bp)
-- end
-- function onFindPrevious(bp)
-- end
function onUndo(bp)
spellcheck(bp.Buf)
end
function onRedo(bp)
spellcheck(bp.Buf)
end
-- function onCopy(bp)
-- end
-- function onCopyLine(bp)
-- end
function onCut(bp)
spellcheck(bp.Buf)
end
function onCutLine(bp)
spellcheck(bp.Buf)
end
function onDuplicateLine(bp)
spellcheck(bp.Buf)
end
function onDeleteLine(bp)
spellcheck(bp.Buf)
end
function onIndentSelection(bp)
spellcheck(bp.Buf)
end
function onOutdentSelection(bp)
spellcheck(bp.Buf)
end
function onOutdentLine(bp)
spellcheck(bp.Buf)
end
function onIndentLine(bp)
spellcheck(bp.Buf)
end
function onPaste(bp)
spellcheck(bp.Buf)
end
-- function onSelectAll(bp)
-- end
-- function onOpenFile(bp)
-- end
-- function onStart(bp)
-- end
-- function onEnd(bp)
-- end
-- function onPageUp(bp)
-- end
-- function onPageDown(bp)
-- end
-- function onSelectPageUp(bp)
-- end
-- function onSelectPageDown(bp)
-- end
-- function onHalfPageUp(bp)
-- end
-- function onHalfPageDown(bp)
-- end
-- function onStartOfLine(bp)
-- end
-- function onEndOfLine(bp)
-- end
-- function onStartOfText(bp)
-- end
-- function onStartOfTextToggle(bp)
-- end
-- function onParagraphPrevious(bp)
-- end
-- function onParagraphNext(bp)
-- end
-- function onToggleHelp(bp)
-- end
-- function onToggleDiffGutter(bp)
-- end
-- function onToggleRuler(bp)
-- end
-- function onJumpLine(bp)
-- end
-- function onClearStatus(bp)
-- end
-- function onShellMode(bp)
-- end
-- function onCommandMode(bp)
-- end
-- function onQuit(bp)
-- end
-- function onQuitAll(bp)
-- end
-- function onAddTab(bp)
-- end
-- function onPreviousTab(bp)
-- end
-- function onNextTab(bp)
-- end
-- function onNextSplit(bp)
-- end
-- function onUnsplit(bp)
-- end
-- function onVSplit(bp)
-- end
-- function onHSplit(bp)
-- end
-- function onPreviousSplit(bp)
-- end
-- function onToggleMacro(bp)
-- end
function onPlayMacro(bp)
spellcheck(bp.Buf)
end
-- function onSuspend(bp) -- Unix only
-- end
-- function onScrollUp(bp)
-- end
-- function onScrollDown(bp)
-- end
-- function onSpawnMultiCursor(bp)
-- end
-- function onSpawnMultiCursorUp(bp)
-- end
-- function onSpawnMultiCursorDown(bp)
-- end
-- function onSpawnMultiCursorSelect(bp)
-- end
-- function onRemoveMultiCursor(bp)
-- end
-- function onRemoveAllMultiCursors(bp)
-- end
-- function onSkipMultiCursor(bp)
-- end
-- function onNone(bp)
-- end
-- function onJumpToMatchingBrace(bp)
-- end
function onAutocomplete(bp)
spellcheck(bp.Buf)
end