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)