vis-config

lua scripts to configure vis editor

git clone https://9o.is/git/vis-config.git

modelines.lua

(5196B)


      1 --
      2 -- vis-modelines
      3 --
      4 -- Vim's modelines are very useful for setting per-file settings in Vim.
      5 -- For example, the filetype can't always be reliably inferred from the
      6 -- filename, i.e. for templates with generic file extensions or script files
      7 -- that omit the file extension altogether.
      8 --
      9 -- This Vis plugin tries to read standard Vim modelines and set the following
     10 -- (Vis) settings:
     11 --
     12 -- autoindent, expandtab, numbers, tabwidth, syntax.
     13 --
     14 -- Vim (by default) looks for modelines in the first 5 and last 5 lines of the
     15 -- file. This will emulate this behaviour, but omit the setting to change this
     16 -- threshold, as no sane person would change it (it would break everybody
     17 -- else's Vim).
     18 --
     19 -- This parser assumes you will only use *one* modeline per file, to avoid
     20 -- having to resolve conflicts. It will use the first modeline it finds from
     21 -- the top.
     22 --
     23 
     24 local lpeg = require("lpeg")
     25 local P, C, Ct, R, S = lpeg.P, lpeg.C, lpeg.Ct, lpeg.R, lpeg.S
     26 
     27 -- vim has two styles for modelines:
     28 --
     29 -- 'set' style, can be delimeted with a : at the end. Delimiter is whitespace.
     30 -- E.g.: vim: set ft=lua sw=2 ts=2 autoindent:
     31 --
     32 -- Colon style, modeline continues to the end of the line. Delimeter is
     33 -- whitespace or ':'.
     34 -- E.g.: vim: noai:ts=4:sw=4 ft=lua
     35 --
     36 -- (All parsing based on the information in :help modeline)
     37 
     38 local digit         = R("09")
     39 local num           = digit * digit * digit
     40 local ver           = S("><=") * num + num + P("")
     41 local vim           = P(" vim")*ver*":" + " Vim"*ver*":" + " vi:" + " ex:"
     42 
     43 local whitespace    = S("\t ")^1
     44 local optwhitespace = whitespace + P("")
     45 local prefix        = (1-vim)^0 * vim * optwhitespace
     46 
     47 local optchars      = R("az", "AZ") + R("09") + S("_\"\'")
     48 local option        = Ct(C(optchars^1) * "=" * C(optchars^1)) + C(optchars^1)
     49 
     50 local set           = P("set") + P("se")
     51 local setstyle      = set * (whitespace * option)^0 * P(":")^-1
     52 local separator     = (optwhitespace * P(":") * optwhitespace) + whitespace
     53 local colonstyle    = option * (separator * option)^0
     54 
     55 -- matches & captures options
     56 local modeline = Ct(prefix * (setstyle + colonstyle))
     57 
     58 -- detects modelines
     59 local modeline_detect = prefix * optwhitespace
     60 
     61 -- Simple Vim settings like 'autoindent' without options are mapped directly to
     62 -- a command for Vis. If the settings is a variable (like 'ft=lua'), a mapping
     63 -- function is used to generate the Vis command.
     64 
     65 local sw_set = false
     66 
     67 local function sw_f(value)
     68     sw_set = true
     69     return "set tw "..value
     70 end
     71 
     72 local function ts_f(value)
     73     if not sw_set then
     74         return "set tw "..value
     75     else
     76         return nil
     77     end
     78 end
     79 
     80 local command_mapping = {
     81     autoindent      = "set ai on",
     82     noautoindent    = "set ai off",
     83     ai              = "set ai on",
     84     noai            = "set ai off",
     85     expandtab       = "set et on",
     86     noexpandtab     = "set et off",
     87     et              = "set et on",
     88     noet            = "set et off",
     89     number          = "set nu on",
     90     nonumber        = "set nu off",
     91     nu              = "set nu on",
     92     nonu            = "set nu off",
     93     ft              = function(value) return "set syntax "..value end,
     94     filetype        = function(value) return "set syntax "..value end,
     95 
     96     -- Vis only knows the 'tabwidth' setting instead of 'sw' and 'ts' settings.
     97     -- We prefer the 'sw' setting for 'tabwidth', because it is what the
     98     -- modeline wants to be inserted. How it is displayed is somewhat less
     99     -- important.
    100     sw              = sw_f,
    101     shiftwidth      = sw_f,
    102     ts              = ts_f,
    103     tabstop         = ts_f
    104 }
    105 
    106 local function parse_modeline(line)
    107     return modeline:match(line)
    108 end
    109 
    110 local function map_options(line)
    111     local commands = {}
    112     local opts = parse_modeline(line)
    113     if not opts then return nil end
    114 
    115     for _,o in pairs(opts) do
    116         if type(o) == "string" then     -- simple options are stored as strings
    117             local c = command_mapping[o]
    118             if c then
    119                 table.insert(commands, c)
    120             end
    121         elseif type(o) == "table" then  -- variables are stored as a table
    122             local map_f = command_mapping[o[1]]
    123             if map_f then
    124                 local c = map_f(o[2])
    125                 if c then table.insert(commands, c) end
    126             end
    127         end
    128     end
    129     return commands
    130 end
    131 
    132 local function find_modeline(lines)
    133     if not lines then return nil end
    134     if #lines < 10 then
    135         for i=1,#lines do
    136             local l = lines[i]
    137             if modeline_detect:match(l) then
    138                 return l
    139             end
    140         end
    141     else
    142         for i=1,5 do
    143             local l = lines[i]
    144             if modeline_detect:match(l) then
    145                 return l
    146             end
    147         end
    148         for i=0,4 do
    149             local l = lines[#lines - i]
    150             if modeline_detect:match(l) then
    151                 return l
    152             end
    153         end
    154     end
    155     return nil
    156 end
    157 
    158 vis.events.subscribe(vis.events.START, function()
    159     local file = vis.win.file
    160     if not file then return end
    161     local ml = find_modeline(file.lines)
    162     if not ml then return end
    163     local commands = map_options(ml)
    164     for _,c in pairs(commands) do
    165         vis:command(c)
    166     end
    167 end)