LanguageTool Grammar Checking

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
}

Nice! For those unfamiliar with LanguageTool (like me), this is it right? https://dev.languagetool.org/

Yep, that’s it! You can either self host the server or use their software as a service to provide the grammar check API

Does this make the connection server-side? So if I'm hosting it on my home server, but access SilverBullet from abroad, it'll resolve the localhost uri correctly?

Also if you self host are you still limited on character limit like the saas free tier?

I used net.proxyFetch to do the API call, so I believe that fetches it server side. There shouldn't be any character limits if you self host, LanguageTool is an open source project.