1
+
local M = {}
2
+
3
+
M.properties = {}
4
+
5
+
--- Modified version of the builtin assert that does not include error position information
6
+
---
7
+
---@param v any Condition
8
+
---@param message string Error message to display if condition is false or nil
9
+
---@return any v if not false or nil, otherwise an error is displayed
10
+
---
11
+
---@private
12
+
local function assert(v, message)
13
+
return v or error(message, 0)
14
+
end
15
+
16
+
--- Show a warning message
17
+
---
18
+
---@param msg string Message to show
19
+
---
20
+
---@private
21
+
local function warn(msg, ...)
22
+
vim.notify(string.format(msg, ...), vim.log.levels.WARN, {
23
+
title = 'editorconfig',
24
+
})
25
+
end
26
+
27
+
function M.properties.charset(bufnr, val)
28
+
assert(
29
+
vim.tbl_contains({ 'utf-8', 'utf-8-bom', 'latin1', 'utf-16be', 'utf-16le' }, val),
30
+
'charset must be one of "utf-8", "utf-8-bom", "latin1", "utf-16be", or "utf-16le"'
31
+
)
32
+
if val == 'utf-8' or val == 'utf-8-bom' then
33
+
vim.bo[bufnr].fileencoding = 'utf-8'
34
+
vim.bo[bufnr].bomb = val == 'utf-8-bom'
35
+
elseif val == 'utf-16be' then
36
+
vim.bo[bufnr].fileencoding = 'utf-16'
37
+
else
38
+
vim.bo[bufnr].fileencoding = val
39
+
end
40
+
end
41
+
42
+
function M.properties.end_of_line(bufnr, val)
43
+
vim.bo[bufnr].fileformat = assert(
44
+
({ lf = 'unix', crlf = 'dos', cr = 'mac' })[val],
45
+
'end_of_line must be one of "lf", "crlf", or "cr"'
46
+
)
47
+
end
48
+
49
+
function M.properties.indent_style(bufnr, val, opts)
50
+
assert(val == 'tab' or val == 'space', 'indent_style must be either "tab" or "space"')
51
+
vim.bo[bufnr].expandtab = val == 'space'
52
+
if val == 'tab' and not opts.indent_size then
53
+
vim.bo[bufnr].shiftwidth = 0
54
+
vim.bo[bufnr].softtabstop = 0
55
+
end
56
+
end
57
+
58
+
function M.properties.indent_size(bufnr, val, opts)
59
+
if val == 'tab' then
60
+
vim.bo[bufnr].shiftwidth = 0
61
+
vim.bo[bufnr].softtabstop = 0
62
+
else
63
+
local n = assert(tonumber(val), 'indent_size must be a number')
64
+
vim.bo[bufnr].shiftwidth = n
65
+
vim.bo[bufnr].softtabstop = -1
66
+
if not opts.tab_width then
67
+
vim.bo[bufnr].tabstop = n
68
+
end
69
+
end
70
+
end
71
+
72
+
function M.properties.tab_width(bufnr, val)
73
+
vim.bo[bufnr].tabstop = assert(tonumber(val), 'tab_width must be a number')
74
+
end
75
+
76
+
function M.properties.max_line_length(bufnr, val)
77
+
local n = tonumber(val)
78
+
if n then
79
+
vim.bo[bufnr].textwidth = n
80
+
else
81
+
assert(val == 'off', 'max_line_length must be a number or "off"')
82
+
vim.bo[bufnr].textwidth = 0
83
+
end
84
+
end
85
+
86
+
function M.properties.trim_trailing_whitespace(bufnr, val)
87
+
assert(
88
+
val == 'true' or val == 'false',
89
+
'trim_trailing_whitespace must be either "true" or "false"'
90
+
)
91
+
if val == 'true' then
92
+
vim.api.nvim_create_autocmd('BufWritePre', {
93
+
group = 'editorconfig',
94
+
buffer = bufnr,
95
+
callback = function()
96
+
local view = vim.fn.winsaveview()
97
+
vim.api.nvim_command('silent! undojoin')
98
+
vim.api.nvim_command('silent keepjumps keeppatterns %s/\\s\\+$//e')
99
+
vim.fn.winrestview(view)
100
+
end,
101
+
})
102
+
else
103
+
vim.api.nvim_clear_autocmds({
104
+
event = 'BufWritePre',
105
+
group = 'editorconfig',
106
+
buffer = bufnr,
107
+
})
108
+
end
109
+
end
110
+
111
+
function M.properties.insert_final_newline(bufnr, val)
112
+
assert(val == 'true' or val == 'false', 'insert_final_newline must be either "true" or "false"')
113
+
vim.bo[bufnr].fixendofline = val == 'true'
114
+
vim.bo[bufnr].endofline = val == 'true'
115
+
end
116
+
117
+
--- Modified version of |glob2regpat()| that does not match path separators on *.
118
+
---
119
+
--- This function replaces single instances of * with the regex pattern [^/]*. However, the star in
120
+
--- the replacement pattern also gets interpreted by glob2regpat, so we insert a placeholder, pass
121
+
--- it through glob2regpat, then replace the placeholder with the actual regex pattern.
122
+
---
123
+
---@param glob string Glob to convert into a regular expression
124
+
---@return string Regular expression
125
+
---
126
+
---@private
127
+
local function glob2regpat(glob)
128
+
local placeholder = '@@PLACEHOLDER@@'
129
+
return (
130
+
string.gsub(
131
+
vim.fn.glob2regpat(
132
+
vim.fn.substitute(
133
+
string.gsub(glob, '{(%d+)%.%.(%d+)}', '[%1-%2]'),
134
+
'\\*\\@<!\\*\\*\\@!',
135
+
placeholder,
136
+
'g'
137
+
)
138
+
),
139
+
placeholder,
140
+
'[^/]*'
141
+
)
142
+
)
143
+
end
144
+
145
+
--- Parse a single line in an EditorConfig file
146
+
---
147
+
---@param line string Line
148
+
---@return string|nil If the line contains a pattern, the glob pattern
149
+
---@return string|nil If the line contains a key-value pair, the key
150
+
---@return string|nil If the line contains a key-value pair, the value
151
+
---
152
+
---@private
153
+
local function parse_line(line)
154
+
if line:find('^%s*[^ #;]') then
155
+
local glob = (line:match('%b[]') or ''):match('^%s*%[(.*)%]%s*$')
156
+
if glob then
157
+
return glob, nil, nil
158
+
end
159
+
160
+
local key, val = line:match('^%s*([^:= ][^:=]-)%s*[:=]%s*(.-)%s*$')
161
+
if key ~= nil and val ~= nil then
162
+
return nil, key:lower(), val:lower()
163
+
end
164
+
end
165
+
end
166
+
167
+
--- Parse options from an .editorconfig file
168
+
---
169
+
---@param filepath string File path of the file to apply EditorConfig settings to
170
+
---@param dir string Current directory
171
+
---@return table Table of options to apply to the given file
172
+
---
173
+
---@private
174
+
local function parse(filepath, dir)
175
+
local pat = nil
176
+
local opts = {}
177
+
local f = io.open(dir .. '/.editorconfig')
178
+
if f then
179
+
for line in f:lines() do
180
+
local glob, key, val = parse_line(line)
181
+
if glob then
182
+
glob = glob:find('/') and (dir .. '/' .. glob:gsub('^/', '')) or ('**/' .. glob)
183
+
local ok, regpat = pcall(glob2regpat, glob)
184
+
if ok then
185
+
pat = vim.regex(regpat)
186
+
else
187
+
pat = nil
188
+
warn('editorconfig: Error occurred while parsing glob pattern "%s": %s', glob, regpat)
189
+
end
190
+
elseif key ~= nil and val ~= nil then
191
+
if key == 'root' then
192
+
opts.root = val == 'true'
193
+
elseif pat and pat:match_str(filepath) then
194
+
opts[key] = val
195
+
end
196
+
end
197
+
end
198
+
f:close()
199
+
end
200
+
return opts
201
+
end
202
+
203
+
--- Configure the given buffer with options from an .editorconfig file
204
+
---
205
+
---@param bufnr number Buffer number to configure
206
+
---
207
+
---@private
208
+
function M.config(bufnr)
209
+
bufnr = bufnr or vim.api.nvim_get_current_buf()
210
+
local path = vim.fs.normalize(vim.api.nvim_buf_get_name(bufnr))
211
+
if vim.bo[bufnr].buftype ~= '' or not vim.bo[bufnr].modifiable or path == '' then
212
+
return
213
+
end
214
+
215
+
local opts = {}
216
+
for parent in vim.fs.parents(path) do
217
+
for k, v in pairs(parse(path, parent)) do
218
+
if opts[k] == nil then
219
+
opts[k] = v
220
+
end
221
+
end
222
+
223
+
if opts.root then
224
+
break
225
+
end
226
+
end
227
+
228
+
local applied = {}
229
+
for opt, val in pairs(opts) do
230
+
if val ~= 'unset' then
231
+
local func = M.properties[opt]
232
+
if func then
233
+
local ok, err = pcall(func, bufnr, val, opts)
234
+
if ok then
235
+
applied[opt] = val
236
+
else
237
+
warn('editorconfig: invalid value for option %s: %s. %s', opt, val, err)
238
+
end
239
+
end
240
+
end
241
+
end
242
+
243
+
vim.b[bufnr].editorconfig = applied
244
+
end
245
+
246
+
return M
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4