1
2 Author:peter@peterodding.com
3 Last Change:
4 URL:http://peterodding.com/code/vim/lua-inspect/
5 License:
6
7 let s:script = expand('<sfile>:p:~')
8
9 function! luainspect#auto_enable()
10 if !&diff && !exists('b:luainspect_disabled')
11
12 let b:easytags_nohl = 1
13
14 inoremap <buffer> <silent> <F2> <C-o>:call luainspect#make_request('rename')<CR>
15 nnoremap <buffer> <silent> <F2> :call luainspect#make_request('rename')<CR>
16 nnoremap <buffer> <silent> gd :call luainspect#make_request('goto')<CR>
17
18 setlocal ballooneval balloonexpr=LuaInspectToolTip()
19
20 for event in split(g:lua_inspect_events, ',')
21 execute 'autocmd!' event '<buffer> LuaInspect'
22 endfor
23 endif
24 endfunction
25
26 function! luainspect#highlight_cmd(disable)
27 if a:disable
28 call s:clear_previous_matches()
29 unlet! b:luainspect_input
30 unlet! b:luainspect_output
31 unlet! b:luainspect_warnings
32 let b:luainspect_disabled = 1
33 else
34 unlet! b:luainspect_disabled
35 call luainspect#make_request('highlight')
36 endif
37 endfunction
38
39 function! luainspect#make_request(action)
40 let starttime = xolox#timer#start()
41 let bufnr = a:action != 'tooltip' ? bufnr('%') : v:beval_bufnr
42 let bufname = bufname(bufnr)
43 if bufname != ''
44 let bufname = fnamemodify(bufname, ':p')
45 endif
46 if a:action == 'tooltip'
47 let lines = getbufline(v:beval_bufnr, 1, "$")
48 call insert(lines, v:beval_col)
49 call insert(lines, v:beval_lnum)
50 else
51 let lines = getline(1, "$")
52 call insert(lines, col('.'))
53 call insert(lines, line('.'))
54 endif
55 call insert(lines, bufname)
56 call insert(lines, a:action)
57 call s:parse_text(join(lines, "\n"), s:prepare_search_path())
58 if !empty(b:luainspect_output)
59 let response = b:luainspect_output[0]
60 if bufname == ''
61 let friendlyname = 'buffer #' . bufnr
62 else
63 let friendlyname = fnamemodify(bufname, ':~')
64 endif
65 if response == 'syntax_error' && len(b:luainspect_output) >= 4
66
67 let linenum = b:luainspect_output[1] + 0
68 let colnum = b:luainspect_output[2] + 0
69 let linenum2 = b:luainspect_output[3] + 0
70 let b:luainspect_syntax_error = b:luainspect_output[4]
71 if a:action != 'tooltip' || v:beval_bufnr == bufnr('%')
72 let error_cmd = 'syntax match luaInspectSyntaxError /\%%>%il\%%<%il.*/ containedin=ALLBUT,lua*Comment*'
73 execute printf(error_cmd, linenum - 1, (linenum2 ? linenum2 : line('$')) + 1)
74 endif
75 call xolox#timer#stop("%s: Found a syntax error in %s in %s.", s:script, friendlyname, starttime)
76
77 call xolox#warning("Syntax error around line %i in %s: %s", linenum, friendlyname, b:luainspect_syntax_error)
78 return
79 endif
80 unlet! b:luainspect_syntax_error
81 if response == 'highlight'
82 call s:define_default_styles()
83 call s:clear_previous_matches()
84 call s:highlight_variables()
85 call xolox#timer#stop("%s: Highlighted variables in %s in %s.", s:script, friendlyname, starttime)
86 elseif response == 'goto'
87 if len(b:luainspect_output) < 3
88 call xolox#warning("No variable under cursor!")
89 else
90 let linenum = b:luainspect_output[1] + 0
91 let colnum = b:luainspect_output[2] + 1
92 call setpos('.', [0, linenum, colnum, 0])
93 call xolox#timer#stop("%s: Jumped to definition in %s in %s.", s:script, friendlyname, starttime)
94 if &verbose == 0
95 "No variable under cursor!"
96 call xolox#message("")
97 endif
98 endif
99 elseif response == 'tooltip'
100 if len(b:luainspect_output) > 1
101 call xolox#timer#stop("%s: Rendered tool tip for %s in %s.", s:script, friendlyname, starttime)
102 return join(b:luainspect_output[1:-1], "\n")
103 endif
104 elseif response == 'rename'
105 if len(b:luainspect_output) > 1
106 call xolox#timer#stop("%s: Prepared for rename in %s in %s.", s:script, friendlyname, starttime)
107 call s:rename_variable()
108 else
109 call xolox#warning("No variable under cursor!")
110 endif
111 endif
112 endif
113 endfunction
114
115 function! s:prepare_search_path()
116 let code = ''
117 if !(has('lua') && g:lua_inspect_internal && exists('s:changed_path'))
118 let template = 'package.path = ''%s/?.lua;'' .. package.path'
119 let code = printf(template, escape(expand(g:lua_inspect_path), '"\'''))
120 if has('lua') && g:lua_inspect_internal
121 execute 'lua' code
122 let s:changed_path = 1
123 endif
124 endif
125 return code
126 endfunction
127
128 function! s:parse_text(input, search_path)
129 if !(exists('b:luainspect_input')
130 \ && exists('b:luainspect_output')
131 \ && b:luainspect_input == a:input)
132 if !(has('lua') && g:lua_inspect_internal)
133 let template = 'lua -e "%s; require ''luainspect4vim'' (io.read ''*a'')"'
134 let command = printf(template, a:search_path)
135 try
136 let b:luainspect_output = xolox#shell#execute(command, 1, a:input)
137 catch /^Vim\%((\a\+)\)\=:E117/
138
139 let b:luainspect_output = split(system(command, a:input), "\n")
140 if v:shell_error
141 let msg = "Failed to execute LuaInspect as external process! %s"
142 throw printf(msg, strtrans(join(b:luainspect_output, "\n")))
143 endif
144 endtry
145 else
146 redir => output
147 silent lua require 'luainspect4vim' (vim.eval 'a:input')
148 redir END
149 let b:luainspect_output = split(output, "\n")
150 endif
151
152 let b:luainspect_input = a:input
153 endif
154 endfunction
155
156 function! s:define_default_styles()
157
158
159 for [group, styles] in items(s:groups)
160 let group = 'luaInspect' . group
161 if type(styles) == type('')
162 let defgroup = styles
163 else
164 let defgroup = 'luaInspectDefault' . group
165 let style = &bg == 'light' ? styles[0] : styles[1]
166 execute 'highlight' defgroup style
167 endif
168
169
170 :help:hi
171 execute 'highlight def link' group defgroup
172 unlet styles
173 endfor
174 endfunction
175
176 function! s:clear_previous_matches()
177
178 call clearmatches()
179 for group in keys(s:groups)
180 let group = 'luaInspect' . group
181 if hlexists(group)
182 execute 'syntax clear' group
183 endif
184 endfor
185 endfunction
186
187 function! s:highlight_variables()
188 call clearmatches()
189 let num_warnings = b:luainspect_output[1] + 0
190 call s:update_warnings(num_warnings > 0 ? b:luainspect_output[2 : num_warnings+1] : [])
191 let other_output = b:luainspect_output[num_warnings+2 : -1]
192 for line in other_output
193 if s:check_output(line, '^\w\+\(\s\+\d\+\)\{4}$')
194 let [group, l1, c1, l2, c2] = split(line)
195
196 let l1 += 0
197 let l2 += 0
198
199 let c1 += 0
200 let c2 += 3
201 if group == 'luaInspectWrongArgCount'
202 call matchadd(group, s:highlight_position(l1, c1, l2, c2, 0))
203 elseif group == 'luaInspectSelectedVariable'
204 call matchadd(group, s:highlight_position(l1, c1, l2, c2, 1), 20)
205 else
206 let pattern = s:highlight_position(l1, c1, l2, c2, 1)
207 execute 'syntax match' group '/' . pattern . '/'
208 endif
209 endif
210 endfor
211 endfunction
212
213 function! s:update_warnings(warnings)
214 if !g:lua_inspect_warnings
215 return
216 endif
217 let list = []
218 for line in a:warnings
219 if s:check_output(line, '^line\s\+\d\+\s\+column\s\+\d\+\s\+-\s\+\S')
220 let fields = split(line)
221 let linenum = fields[1] + 0
222 let colnum = fields[3] + 0
223 let message = join(fields[5:-1])
224 call add(list, { 'bufnr': bufnr('%'), 'lnum': linenum, 'col': colnum, 'text': message })
225 endif
226 endfor
227 call setloclist(winnr(), list)
228 let b:luainspect_warnings = list
229 if !empty(list)
230 lopen
231 if winheight(winnr()) > 4
232 resize 4
233 endif
234 let warnings = len(list) > 1 ? 'warnings' : 'warning'
235 let w:quickfix_title = printf('%i %s reported by LuaInspect', len(list), warnings)
236 wincmd w
237 else
238 lclose
239 endif
240 endfunction
241
242 function! s:rename_variable()
243
244 let highlights = []
245 for line in b:luainspect_output[1:-1]
246 if s:check_output(line, '^\d\+\(\s\+\d\+\)\{2}$')
247 let [l1, c1, c2] = split(line)
248
249 let l1 += 0
250
251 let c1 += 0
252 let c2 += 3
253 let pattern = s:highlight_position(l1, c1, l1, c2, 1)
254 call add(highlights, matchadd('IncSearch', pattern))
255 endif
256 endfor
257 redraw
258
259 let oldname = expand('<cword>')
260 let prompt = "Please enter the new name for %s: "
261 let newname = input(printf(prompt, oldname), oldname)
262
263 call map(highlights, 'matchdelete(v:val)')
264
265 if newname != '' && newname !=# oldname
266 let num_renamed = 0
267 for fields in reverse(b:luainspect_output[1:-1])
268 let [linenum, firstcol, lastcol] = split(fields)
269
270 let linenum += 0
271
272 let firstcol -= 1
273 let lastcol += 1
274 let line = getline(linenum)
275 let prefix = firstcol > 0 ? line[0 : firstcol] : ''
276 let suffix = lastcol < len(line) ? line[lastcol : -1] : ''
277 call setline(linenum, prefix . newname . suffix)
278 let num_renamed += 1
279 endfor
280 let msg = "Renamed %i occurrences of %s to %s"
281 call xolox#message(msg, num_renamed, oldname, newname)
282 endif
283 endfunction
284
285 function! s:check_output(line, pattern)
286 if match(a:line, a:pattern) >= 0
287 return 1
288 else
289 call xolox#warning("Invalid output from luainspect4vim.lua: '%s'", strtrans(a:line))
290 return 0
291 endif
292 endfunction
293
294 function! s:highlight_position(l1, c1, l2, c2, ident_only)
295 let l1 = a:l1 >= 1 ? (a:l1 - 1) : a:l1
296 let p = '\%>' . l1 . 'l\%>' . a:c1 . 'c'
297 let p .= a:ident_only ? '\<\w\+\>' : '\_.\+'
298 return p . '\%<' . (a:l2 + 1) . 'l\%<' . a:c2 . 'c'
299 endfunction
300
301
302
303 let s:groups = {}
304 let s:groups['GlobalDefined'] = ['guifg=#600000', 'guifg=#ffc080']
305 let s:groups['GlobalUndefined'] = 'ErrorMsg'
306 let s:groups['LocalUnused'] = ['guifg=#ffffff guibg=#000080', 'guifg=#ffffff guibg=#000080']
307 let s:groups['LocalMutated'] = ['gui=italic guifg=#000080', 'gui=italic guifg=#c0c0ff']
308 let s:groups['UpValue'] = ['guifg=#0000ff', 'guifg=#e8e8ff']
309 let s:groups['Param'] = ['guifg=#000040', 'guifg=#8080ff']
310 let s:groups['Local'] = ['guifg=#000040', 'guifg=#c0c0ff']
311 let s:groups['FieldDefined'] = ['guifg=#600000', 'guifg=#ffc080']
312 let s:groups['FieldUndefined'] = ['guifg=#c00000', 'guifg=#ff0000']
313 let s:groups['SelectedVariable'] = 'CursorLine'
314 let s:groups['SyntaxError'] = 'SpellBad'
315 let s:groups['WrongArgCount'] = 'SpellLocal'