-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
plugin: formatter: Add plugin to format files
- Loading branch information
Showing
4 changed files
with
612 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,278 @@ | ||
VERSION = '1.0.0' | ||
|
||
local micro = import('micro') | ||
local config = import('micro/config') | ||
local shell = import('micro/shell') | ||
|
||
local errors = import('errors') | ||
local fmt = import('fmt') | ||
local regexp = import('regexp') | ||
local runtime = import('runtime') | ||
local strings = import('strings') | ||
|
||
---@alias Error { Error: fun(): string } # Goland error | ||
|
||
---@class Buffer | ||
---@field Path string | ||
---@field AbsPath string | ||
---@field FileType fun(): string | ||
---@field ReOpen fun() | ||
|
||
---@class BufPane userdata # Micro BufPane | ||
---@field Buf Buffer | ||
|
||
-- luacheck: globals toString | ||
---@param value any | ||
function toString(value) | ||
if type(value) == 'string' then | ||
return value | ||
elseif type(value) == 'table' then | ||
return strings.Join(value, ' ') | ||
end | ||
return value | ||
end | ||
|
||
-- luacheck: globals contains | ||
---@param t table # Table to check. | ||
---@param e any # Element to verify. | ||
---@return boolean # If contains or not | ||
function contains(t, e) | ||
for i = 1, #t do | ||
if t[i] == e then | ||
return true | ||
end | ||
end | ||
return false | ||
end | ||
|
||
-- luacheck: globals Format | ||
---@class Format | ||
---@field cmd string | ||
---@field args string|string[] | ||
---@field name string | ||
---@field bind string | ||
---@field onSave boolean | ||
---@field filetypes string[] | ||
---@field os string[] | ||
---@field whitelist boolean | ||
---@field domatch boolean | ||
---@field callback fun(buf: Buffer): boolean | ||
local Format = {} | ||
|
||
---validates a formatter | ||
---@param format Format | ||
---@return Format?, Error? | ||
---@nodiscard | ||
function Format:new(format) | ||
---@type Format | ||
local f = {} | ||
|
||
if format.cmd == nil or type(format.cmd) ~= 'string' then | ||
return format, errors.New('Invalid "cmd"') | ||
elseif format.filetypes == nil or type(format.filetypes) ~= 'table' then | ||
return format, errors.New('Invalid "filetypes"') | ||
end | ||
|
||
f.cmd = format.cmd | ||
f.filetypes = format.filetypes | ||
|
||
---@type string[] | ||
local cmds | ||
|
||
if not format.name then | ||
cmds = strings.Split(format.cmd, ' ') | ||
f.name = fmt.Sprintf('%s', cmds[1]) | ||
else | ||
f.name = format.name | ||
end | ||
|
||
f.bind = format.bind | ||
f.args = toString(format.args) or '' | ||
f.onSave = format.onSave | ||
f.os = format.os | ||
f.whitelist = format.whitelist or false | ||
f.domatch = format.domatch or false | ||
f.callback = format.callback | ||
|
||
self.__index = self | ||
return setmetatable(f, self), nil | ||
end | ||
|
||
function Format:hasOS() | ||
if self.os == nil then | ||
return true | ||
end | ||
local has_os = contains(self.os, runtime.GOOS) | ||
if (not has_os and self.whitelist) or (has_os and not self.whitelist) then | ||
return false | ||
end | ||
|
||
return true | ||
end | ||
|
||
---@param buf Buffer | ||
---@param filter fun(f: Format): boolean | ||
---@return boolean | ||
function Format:hasFormat(buf, filter) | ||
if filter ~= nil and not filter(self) then | ||
return false | ||
end | ||
|
||
---@type string | ||
local filetype = buf:FileType() | ||
---@type string[] | ||
local filetypes = self.filetypes | ||
|
||
for _, ft in ipairs(filetypes) do | ||
if self.domatch then | ||
if regexp.MatchString(ft, buf.AbsPath) then | ||
return true | ||
end | ||
elseif ft == filetype then | ||
return true | ||
end | ||
end | ||
return false | ||
end | ||
|
||
function Format:hasCallback(buf) | ||
if self.callback ~= nil and type(self.callback) == 'function' and not self.callback(buf) then | ||
return false | ||
end | ||
return true | ||
end | ||
|
||
---run a formatter on a given file | ||
---@param buf Buffer | ||
---@return Error? | ||
function Format:run(buf) | ||
---@type string | ||
local args = self.args:gsub('%%f', buf.Path) | ||
---@type string | ||
local cmd = fmt.Sprintf('%s %s', self.cmd:gsub('%%f', buf.Path), args) | ||
-- err: Error? | ||
local _, err = shell.RunCommand(cmd) | ||
|
||
---@type string | ||
if err ~= nil then | ||
return err | ||
end | ||
end | ||
|
||
---@type Format[] | ||
-- luacheck: globals formatters | ||
formatters = {} | ||
|
||
-- luacheck: globals format | ||
---format a bufpane | ||
---@param bp BufPane | ||
---@param filter? fun(f: Format): boolean | ||
---@return Error? | ||
function format(bp, filter) | ||
if #formatters < 1 then | ||
return | ||
end | ||
|
||
---@type string | ||
local errs = '' | ||
for _, format in ipairs(formatters) do | ||
---@cast filter fun(f: Format): boolean | ||
if format:hasFormat(bp.Buf, filter) and format:hasOS() and format:hasCallback(bp.Buf) then | ||
local err = format:run(bp.Buf) | ||
if err ~= nil then | ||
errs = fmt.Sprintf('%s | %s', errs, format.name) | ||
end | ||
end | ||
end | ||
|
||
bp.Buf:ReOpen() | ||
|
||
if errs ~= '' then | ||
return micro.InfoBar():Error('💥 Error when using formatters: %s', errs) | ||
else | ||
micro.InfoBar():Message(fmt.Sprintf('🎬 File formatted successfully! %s ✨ 🍰 ✨', bp.Buf.Path)) | ||
end | ||
end | ||
|
||
-- luacheck: globals makeCommand | ||
---creates a formatter command (if the name is not specified, all found formatters will be executed) | ||
---@param name string # Format name | ||
---@return fun(bp: BufPane) | ||
function makeCommand(name) | ||
---@param bp BufPane | ||
return function(bp) | ||
return format(bp, function(format) | ||
return name == format.name | ||
end) | ||
end | ||
end | ||
|
||
-- luacheck: globals makeFormat | ||
---add a format | ||
---@param format Format | ||
---@return Error? | ||
function makeFormat(format) | ||
local err | ||
-- format: Format | ||
-- err: Error? | ||
format, err = Format:new(format) | ||
if err ~= nil then | ||
return err | ||
end | ||
table.insert(formatters, format) | ||
|
||
---@type boolean | ||
local makeCommands = config.GetGlobalOption('formatter.makeCommands') | ||
|
||
if makeCommands then | ||
config.MakeCommand(format.name, makeCommand(format.name), config.NoComplete) | ||
end | ||
|
||
if format.bind then | ||
if not makeCommands then | ||
config.MakeCommand(format.name, makeCommand(format.name), config.NoComplete) | ||
end | ||
|
||
config.TryBindKey(format.bind, 'command:' .. format.name, true) | ||
end | ||
end | ||
|
||
-- luacheck: globals setup | ||
---initialize formatters | ||
---@param formats Format[] | ||
function setup(formats) | ||
---@type string | ||
for _, format in ipairs(formats) do | ||
---@type Error? | ||
makeFormat(format) | ||
end | ||
end | ||
|
||
-- CALLBACK'S | ||
|
||
---runs formatters set to onSave | ||
---@param bp BufPane | ||
function onSave(bp) | ||
if #formatters < 1 then | ||
return true | ||
end | ||
|
||
---@type Error? | ||
local err = format(bp, function(format) | ||
return format.onSave == true | ||
end) | ||
|
||
if err ~= nil then | ||
micro.InfoBar():Error(fmt.Sprintf('%v', err)) | ||
else | ||
micro.InfoBar():Message(fmt.Sprintf('🎬 Saved! %s ✨ 🍰 ✨', bp.Buf.Path)) | ||
end | ||
return true | ||
end | ||
|
||
function init() | ||
config.RegisterCommonOption('formatter', 'makeCommands', false) | ||
config.AddRuntimeFile('formatter', config.RTHelp, 'help/formatter.md') | ||
config.MakeCommand('format', format, config.NoComplete) | ||
config.TryBindKey('Alt-f', 'command:format', false) | ||
end |
Oops, something went wrong.