I've been working on a (very much WIP) LanguageTool based grammar checker library. So far I've pointed it to my self-hosted LanguageTool server and it works pretty well. I hope you guys like it!
Also, any suggestions on how to render the feedback? Right now, I just insert the feedback as markdown at the user's cursor position, which isn't ideal. Is there a way I can render lines under the text similar to grammarly?
Code (click to open)
config.define("grammarcheck", {
description = "Configure Grammar Checker Library",
type = "object",
properties = {
url = {
type = "string",
default = "http://127.0.0.1:8081/v2/check",
description = "API Endpoint for languagetool",
ui = { category = "Grammar Checker", label = "API Endpoint" }
},
lang = {
type = "string",
default = "en-US",
description = "Language code to use for LanguageTool. (auto will autodetect language, your mileage may vary)",
ui = { category = "Grammar Checker", label = "Language" }
},
apiKey = {
type = "string",
description = "API Key for premium LanguageTool features",
ui = { category = "Grammar Checker", label = "API Key" }
}
},
additionalProperties = false
})
local function urlencode(str)
if str then
str = str:gsub("\n", "\r\n")
str = str:gsub("([^%w %-%_%.%~])", function(c)
return string.format("%%%02X", string.byte(c))
end)
str = str:gsub(" ", "+") -- Standard for application/x-www-form-urlencoded
end
return str
end
slashCommand.define {
name = "grammar",
description = "Check all grammar on this page, inserting feedback at cursor",
run = function()
local languagetool_url = config.get("grammarcheck.url", "http://127.0.0.1:8081/v2/check")
local lang = config.get("grammarcheck.lang", "en-US")
local text = urlencode(editor.getText())
local reqbody = string.format("language=%s&text=%s", lang, text)
if config.get("grammarcheck.apiKey", nil) then
body = body .. "&apiKey=" .. config.get("grammarcheck.apiKey", nil)
end
local resp = net.proxyFetch(languagetool_url, {
method = "POST",
headers = {
["accept"] = "application/json",
["Content-type"] = "application/x-www-form-urlencoded"
},
body = reqbody
})
if resp.ok and resp.status ~= 400 then
local body = resp.body
-- TODO: add highlighting to grammar errors in place
-- TODO: add ability to go through errors and approve changes.
local confirm = editor.confirm("Do you want to insert feedback?")
if confirm then
editor.insertAtCursor(string.format("### %d grammar errors\n\n", #body.matches))
for _, match in ipairs(body.matches) do
local blockquote = match.context.text
-- Insert the highlight
blockquote = blockquote:sub(1, match.context.offset) .. "==" .. blockquote:sub(match.context.offset + 1, match.context.offset + match.context.length) .. "==" .. blockquote:sub(match.context.offset + match.context.length + 1)
-- Reformat to block quote
blockquote = blockquote:gsub("\r", "")
blockquote = blockquote:gsub("\n", "\n> ")
local msg = match.message:gsub("\r", "")
msg = msg:gsub("\n", " ")
local feedback = "> [!WARNING] " .. msg .. "\n> " .. blockquote .. "\n\n"
editor.insertAtCursor(feedback)
end
end
else
editor.alert(string.format("Language tool call to url %s failed with code %d", languagetool_url, resp.status))
end
end
}