aboutsummaryrefslogblamecommitdiffstats
path: root/edconf.lua
blob: 7c92f4da2c3397bd57c881109b0099acc32be38e (plain) (tree)
1
2
3
4
5
6
             
                                 
            

                 
                                     










                                               
                                     






                                                       
                                      







                                                   




                                                                        
                                         



                                                                     
                                                                



                                
                                        








                                                                       
                                             
                                        






                                                               
                                     
                                        






                                                           
                                   
                                        






                                                           


                                                                  
                                    
                                        

                           
                                                         








                                                                
                                                    




                                       
                 
                                 
                                            











                                                           

                                            

      
                                         






                                                                             

                                                                 





















                                                                       
                                         



                                                           
                         
              

                    


                      
                                               
                         


                                 







                                    




                                                                 



       
 
                                                
                        
                                      
 

                                                        

    



                                                          
        
require "vis"
local ec = require "editorconfig"
local M = {}

-- Simple wrapper
local function vis_set(option, value)
  if type(value) == "boolean" then
    if value then
      value = "yes"
    else
      value = "no"
    end
  end

  vis:command("set " .. option .. " " .. value)
end

local function set_pre_save(f, value)
  if value == "true" then
    vis.events.subscribe(vis.events.FILE_SAVE_PRE, f)
  else
    vis.events.unsubscribe(vis.events.FILE_SAVE_PRE, f)
  end
end

local function set_file_open(f, value)
  if value == "true" then
    vis.events.subscribe(vis.events.FILE_OPEN, f)
  else
    vis.events.unsubscribe(vis.events.FILE_OPEN, f)
  end
end

-- Custom functionality
M.hooks_enabled = false
vis:option_register("edconfhooks", "bool", function(value)
  M.hooks_enabled = value
end, "Enable optional pre-save-hooks for certain editorconfig settings")

local function insert_final_newline(file)
  -- Technically speaking, this is a pre-save-hook as well and could
  -- therefore respect edconf_hooks_enabled. Since this function runs
  -- blazingly fast and scales with a complexity of O(1), however,
  -- there is no need to disable it.
  if file.size > 0 and file:content(file.size-1, 1) ~= '\n' then
    file:insert(file.size, '\n')
  end
end

local function strip_final_newline(file)
  -- In theory, this would have a complexity of O(n) as well and could
  -- thus be made optional via edconf_hooks_enabled. On the other hand,
  -- this is probably a very rare edge case, so stripping all trailing
  -- newline characters is probably safe enough.
  while file:content(file.size-1, 1) == '\n' do
    file:delete(file.size-1, 1)
  end
end

local function trim_trailing_whitespace(file)
  if not M.hooks_enabled then return end
  for i=1, #file.lines do
    if string.match(file.lines[i], '[ \t]$') then
      file.lines[i] = string.gsub(file.lines[i], '[ \t]*$', '')
    end
  end
end

local function enforce_crlf_eol(file)
  if not M.hooks_enabled then return end
  for i=1, #file.lines do
    if not string.match(file.lines[i], '\r$') then
      file.lines[i] = string.gsub(file.lines[i], '$', '\r')
    end
  end
end

local function enforce_lf_eol(file)
  if not M.hooks_enabled then return end
  for i=1, #file.lines do
    if string.match(file.lines[i], '\r$') then
      file.lines[i] = string.gsub(file.lines[i], '\r$', '')
    end
  end
end

M.max_line_length = 80     -- This is ugly, but we do want to use
                           -- single function that we can register
                           -- or unregister as needed
local function max_line_length(file)
  if not M.hooks_enabled then return end
  local overlong_lines = {}
  for i=1, #file.lines do
    if string.len(file.lines[i]) > M.max_line_length then
      table.insert(overlong_lines, i)
    end
  end
  if #overlong_lines > 0 then
    local lines_are = (function(x)
        if x>1 then return "lines are" else return "line is" end
    end)(#overlong_lines)
    vis:info(string.format(
      "%d %s longer than %d characters: %s",
      #overlong_lines, lines_are, M.max_line_length,
      table.concat(overlong_lines, ",")
    ))
  end
end

local OPTIONS = {
  indent_style = function (value)
    vis_set("expandtab", (value == "space"))
  end,

  indent_size = function (value)
    if value ~= "tab" then -- tab_width is a synonym anyway
      vis_set("tabwidth", value)
    end
  end,

  tab_width = function (value)
    vis_set("tabwidth", value)
  end,

  spelling_language = function (value, file)
    file.spelling_language = value
  end,

  insert_final_newline = function (value)
    -- According to the editorconfig specification, insert_final_newline
    -- false is supposed to mean stripping the final newline, if present.
    -- See https://editorconfig-specification.readthedocs.io/#supported-pairs
    --
    -- Quote: insert_final_newline Set to true ensure file ends with a
    -- newline when saving and false to ensure it doesn’t.
    --
    set_pre_save(insert_final_newline, tostring(value == "true"))
    set_pre_save(strip_final_newline, tostring(value == "false"))
  end,

  trim_trailing_whitespace = function (value)
    set_pre_save(trim_trailing_whitespace, value)
  end,

  -- End of line is only partially implemented. While vis does not
  -- support customized newlines, it does work well enough with crlf
  -- newlines. Therefore, setting end_of_line=crlf will just ensure
  -- that there is a cr at the end of each line. Setting end_of_line=lf
  -- will strip any cr characters at the end of lines. This hopefully
  -- eases the pain of working with crlf files a little.
  end_of_line = function (value)
    set_pre_save(enforce_crlf_eol, tostring(value == "crlf"))
    set_pre_save(enforce_lf_eol, tostring(value == "lf"))
  end,

  -- There is probably no straightforward way to enforce a maximum line
  -- length across different programming languages. If a maximum line
  -- length is set, we can at least issue a warning, however.
  max_line_length = function(value)
    if value ~= "off" then
      M.max_line_length = tonumber(value)
    end
    set_pre_save(max_line_length, tostring(value ~= "off"))
  end,

  -- Not supported by vis
  --   charset
  -- Partial support
  --   end_of_line
  --   max_line_length
}

-- Compatible with editorconfig-core-lua v0.3.0
local function ec_iter(p)
  local i = 0
  local props, keys = ec.parse(p)
  local n = #keys
  return function ()
    i = i + 1
    if i <= n then
      return keys[i], props[keys[i]]
    end
  end
end

local function ec_set_values(win)
  if not win or not win.file or not win.file.path then return end
  for name, value in ec_iter(win.file.path) do
    if OPTIONS[name] then
      OPTIONS[name](value, win.file)
    end
  end
end


vis:command_register("econfig_parse", function()
  ec_set_values(vis.win)
end, "(Re)parse an editorconfig file")

vis.events.subscribe(vis.events.WIN_OPEN, function (win)
  ec_set_values(win)
end)

vis.events.subscribe(vis.events.FILE_SAVE_POST, function()
  ec_set_values(vis.win)
end)

return M