Skip to content

Commit

Permalink
<F2> mapping to rename all occurrences of variable
Browse files Browse the repository at this point in the history
 * The plug-in now defines a buffer local <F2> mapping inside Lua
   buffers which renames all occurrences of the variable under the
   text cursor.

 * I've structured the communication between Vim and Lua so that more
   modes/actions can be easily added and so that when, for example, a
   syntax error is found while trying to perform the rename action, the
   plug-in will recognize and highlight the syntax error instead of
   doing nothing (because the syntax error makes the AST unavailable).

 * Just for reference: The Python script I use to package the plug-in
   now includes the luainspect README/COPYRIGHT & metalua LICENSE files
  • Loading branch information
xolox committed Aug 11, 2010
1 parent 4c2c0bf commit 0c52221
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 55 deletions.
10 changes: 8 additions & 2 deletions README.md
@@ -1,6 +1,12 @@
# Semantic highlighting for Lua in Vim

The Vim plug-in `luainspect.vim` uses the [LuaInspect](http://lua-users.org/wiki/LuaInspect) tool to (automatically) perform semantic highlighting of variables in Lua source code. It was inspired by [lua2-mode](http://www.enyo.de/fw/software/lua-emacs/lua2-mode.html) (for [Emacs](http://www.gnu.org/software/emacs/)) and the [SciTE](http://www.scintilla.org/SciTE.html) plug-in included with LuaInspect.
The Vim plug-in `luainspect.vim` uses the [LuaInspect](http://lua-users.org/wiki/LuaInspect) tool to (automatically) perform semantic highlighting of variables in Lua source code. It was inspired by [lua2-mode](http://www.enyo.de/fw/software/lua-emacs/lua2-mode.html) (for [Emacs](http://www.gnu.org/software/emacs/)) and the [SciTE](http://www.scintilla.org/SciTE.html) plug-in included with LuaInspect. In addition to the semantic highlighting the following features are currently supported:

* If the text cursor is on a variable while the highlighting is refreshed then all occurrences of the variable will be marked in the style of [Vim's cursorline option](http://vimdoc.sourceforge.net/htmldoc/options.html#%27cursorline%27).

* If you press `<F2>` with the text cursor on a variable then the plug-in will prompt you to rename the variable.

* When a syntax error is found (during highlighting or using the rename functionality) the lines where the error is reported will be marked like a spelling error.

![Screenshot of semantic highlighting](http://peterodding.com/code/vim/luainspect/screenshot.png)

Expand Down Expand Up @@ -30,7 +36,7 @@ You don't need to use this command unless you've disabled automatic highlighting
* <span style="background: #D3D3D3">luaInspectSelectedVariable</span>
* <span style="border-bottom: 1px dotted red">luaInspectSyntaxError</span>

When a syntax error is found no highlighting can be performed but the lines where the error is reported will be marked like a spelling error. If you don't like one or more of the default styles the Vim documentation [describes how to change them](http://vimdoc.sourceforge.net/htmldoc/syntax.html#:hi-default). If you want to disable the semantic highlighting in a specific Vim buffer execute `:LuaInspect!` in that buffer. When you want to reenable the highlighting execute `:LuaInspect` again, but now without the [bang](http://vimdoc.sourceforge.net/htmldoc/map.html#:command-bang).
If you don't like one or more of the default styles the Vim documentation [describes how to change them](http://vimdoc.sourceforge.net/htmldoc/syntax.html#:hi-default). If you want to disable the semantic highlighting in a specific Vim buffer execute `:LuaInspect!` in that buffer. When you want to reenable the highlighting execute `:LuaInspect` again, but now without the [bang](http://vimdoc.sourceforge.net/htmldoc/map.html#:command-bang).

### The `g:loaded_luainspect` option

Expand Down
2 changes: 1 addition & 1 deletion TODO.md
Expand Up @@ -2,6 +2,6 @@

* Right now the highlighting styles used by `luainspect.vim` are the same as those used by the SciTE plug-in and they don't work well on dark backgrounds. As soon as I get around to picking some alternate colors I'll include those in the plug-in.

* Bindings for other features of LuaInspect like renaming variables on command and showing tooltips for identifiers. This might be a lot of work but could prove to be really useful in making Lua easy to use in Vim.
* Bindings for other features of LuaInspect such as showing tooltips for variables, goto definition (`gd` mapping) for variables, omni completion for in scope variables (including display of library function signatures), etc. This might be a lot of work but could prove to be really useful in making Lua easy to use in Vim.

* Document the g:lua_inspect_path option.
119 changes: 93 additions & 26 deletions luainspect.vim
@@ -1,8 +1,8 @@
" Vim plug-in
" Author: Peter Odding <peter@peterodding.com>
" Last Change: August 10, 2010
" Last Change: August 11, 2010
" URL: http://peterodding.com/code/vim/lua-inspect/
" Version: 0.2.4
" Version: 0.3
" License: MIT

" Support for automatic update using the GLVS plug-in.
Expand Down Expand Up @@ -55,13 +55,13 @@ let s:groups['SyntaxError'] = 'SpellBad'

" (Automatic) command definitions. {{{1

command! -bar -bang LuaInspect call s:run_lua_inspect(<q-bang> != '!')
command! -bar -bang LuaInspect call s:run_lua_inspect('highlight', <q-bang> != '!')

augroup PluginLuaInspect
" Clear existing automatic commands.
autocmd!
" Disable easytags.vim because it doesn't play nice with luainspect.vim!
autocmd BufReadPost * if s:check_plugin_enabled() | let b:easytags_nohl = 1 | endif
autocmd BufNewFile,BufReadPost,BufWritePost * call s:init_lua_buffer()
" Define the configured automatic commands.
for s:event in split(g:lua_inspect_events, ',')
execute 'autocmd' s:event '* if s:check_plugin_enabled() | LuaInspect | endif'
Expand All @@ -74,15 +74,44 @@ function! s:check_plugin_enabled()
return &ft == 'lua' && !&diff && !exists('b:luainspect_disabled')
endfunction

function! s:run_lua_inspect(enabled) " {{{2
if s:set_plugin_enabled(a:enabled)
function! s:init_lua_buffer()
if s:check_plugin_enabled()
let b:easytags_nohl = 1
inoremap <buffer> <silent> <F2> <C-o>:call <Sid>run_lua_inspect('rename', 1)<CR>
nnoremap <buffer> <silent> <F2> :call <Sid>run_lua_inspect('rename', 1)<CR>
endif
endfunction

function! s:run_lua_inspect(action, enable) " {{{2
if s:set_plugin_enabled(a:enable)
let lines = getline(1, "$")
call insert(lines, col('.'))
call insert(lines, line('.'))
call insert(lines, a:action)
call s:parse_text(join(lines, "\n"), s:prepare_search_path())
call s:define_default_styles()
call s:clear_previous_matches()
call s:highlight_variables()
if !empty(b:luainspect_output)
let response = b:luainspect_output[0]
if response == 'syntax_error' && len(b:luainspect_output) >= 4
let linenum = b:luainspect_output[1] + 0
let colnum = b:luainspect_output[2] + 0
let linenum2 = b:luainspect_output[3] + 0
" TODO Can we do something useful with this?!
" let message = b:luainspect_output[4]
let error_cmd = 'syntax match luaInspectSyntaxError /\%%>%il\%%<%il.*/ containedin=ALLBUT,lua*Comment*'
execute printf(error_cmd, linenum - 1, (linenum2 ? linenum2 : line('$')) + 1)
return
elseif response == 'highlight'
call s:define_default_styles()
call s:clear_previous_matches()
call s:highlight_variables()
elseif response == 'rename'
if len(b:luainspect_output) == 1
call xolox#warning("No variable under cursor!")
else
call s:rename_variable()
endif
endif
endif
endif
endfunction

Expand All @@ -102,7 +131,7 @@ function! s:prepare_search_path() " {{{2
let code = ''
if !(has('lua') && g:lua_inspect_internal && exists('s:changed_path'))
let template = 'package.path = ''%s/?.lua;'' .. package.path'
let code = printf(template, escape(expand(g:lua_inspect_path), '"\'))
let code = printf(template, escape(expand(g:lua_inspect_path), '"\'''))
if has('lua') && g:lua_inspect_internal
execute 'lua' code
let s:changed_path = 1
Expand All @@ -122,7 +151,7 @@ function! s:parse_text(input, search_path) " {{{2
" Ignore missing shell.vim plug-in.
let b:luainspect_output = split(system(command, a:input), "\n")
if v:shell_error
let msg = "Failed to execute lua-inspect as external process! %s"
let msg = "Failed to execute luainspect as external process! %s"
throw printf(msg, strtrans(join(b:luainspect_output, "\n")))
endif
endtry
Expand Down Expand Up @@ -172,24 +201,62 @@ function! s:clear_previous_matches() " {{{2
endfunction

function! s:highlight_variables() " {{{2
if len(b:luainspect_output) == 1
let line = b:luainspect_output[0]
let errinfo = matchlist(line, '^\(\d\+\)\s*\(\d*\)$')
if len(errinfo) >= 3
let error_cmd = 'syntax match luaInspectSyntaxError /\%%>%il\%%<%il.*/ containedin=ALLBUT,lua*Comment*'
execute printf(error_cmd, errinfo[1] - 1, (errinfo[2] != '' ? errinfo[2] : line('$')) + 1)
return
for line in b:luainspect_output[1:-1]
if s:check_output(line, '^\w\+\(\s\+\d\+\)\{3}$')
let [hlgroup, linenum, firstcol, lastcol] = split(line)
let pattern = s:highlight_position(linenum + 0, firstcol - 1, lastcol + 2)
execute 'syntax match' hlgroup '/' . pattern . '/'
endif
endif
for line in b:luainspect_output
if match(line, '^\w\+\(\s\+\d\+\)\{3}$') == -1
call xolox#warning("Invalid output from luainspect4vim.lua: %s", strtrans(line))
return
endfor
endfunction

function! s:rename_variable() " {{{2
" Highlight occurrences of variable before rename.
let highlights = []
for line in b:luainspect_output[1:-1]
if s:check_output(line, '^\d\+\(\s\+\d\+\)\{2}$')
let [linenum, firstcol, lastcol] = split(line)
let pattern = s:highlight_position(linenum + 0, firstcol - 1, lastcol + 2)
call add(highlights, matchadd('IncSearch', pattern))
endif
let [type, lnum, start, end] = split(line)
let syntax_cmd = 'syntax match %s /\%%%il\%%>%ic\<\w\+\>\%%<%ic/'
execute printf(syntax_cmd, type, lnum, start - 1, end + 2)
endfor
redraw
" Prompt for new name.
let oldname = expand('<cword>')
let prompt = "Please enter the new name for %s: "
let newname = input(printf(prompt, oldname), oldname)
" Clear highlighting of occurrences.
call map(highlights, 'matchdelete(v:val)')
" Perform rename?
if newname != '' && newname != oldname
let num_renamed = 0
for fields in reverse(b:luainspect_output[1:-1])
let [linenum, firstcol, lastcol] = split(fields)
let linenum += 0
let firstcol -= 2
let lastcol += 0
let line = getline(linenum)
let prefix = firstcol > 0 ? line[0 : firstcol] : ''
let suffix = lastcol < len(line) ? line[lastcol : -1] : ''
call setline(linenum, prefix . newname . suffix)
let num_renamed += 1
endfor
let msg = "Renamed %i occurrences of %s to %s"
call xolox#message(msg, num_renamed, oldname, newname)
endif
endfunction

function! s:check_output(line, pattern) " {{{2
if match(a:line, a:pattern) >= 0
return 1
else
call xolox#warning("Invalid output from luainspect4vim.lua: '%s'", strtrans(a:line))
return 0
endif
endfunction

function! s:highlight_position(linenum, firstcol, lastcol) " {{{2
return printf('\%%%il\%%>%ic\<\w\+\>\%%<%ic', a:linenum, a:firstcol, a:lastcol)
endfunction

" }}}1
Expand Down
76 changes: 50 additions & 26 deletions luainspect4vim.lua
Expand Up @@ -3,13 +3,16 @@
This module is part of the luainspect.vim plug-in for the Vim text editor.
Author: Peter Odding <peter@peterodding.com>
Last Change: August 10, 2010
Last Change: August 11, 2010
URL: http://peterodding.com/code/vim/lua-inspect/
License: MIT
--]]
local myprint
local LI = require 'luainspect.init'
local LA = require 'luainspect.ast'
local myprint, getcurvar, highlight, rename
if type(vim) == 'table' and vim.eval then
-- The Lua interface for Vim redefines print() so it prints inside Vim.
myprint = print
Expand All @@ -20,7 +23,7 @@ else
function myprint(text) io.write(text, '\n') end
end
local function getcurvar(tokenlist, line, column)
function getcurvar(tokenlist, line, column)
for i, token in ipairs(tokenlist) do
if token.ast.lineinfo then
local l1, c1 = unpack(token.ast.lineinfo.first, 1, 2)
Expand All @@ -32,26 +35,8 @@ local function getcurvar(tokenlist, line, column)
end
end
return function(src)
local LI = require 'luainspect.init'
local LA = require 'luainspect.ast'
local line, column, src = src:match '^(%d+)\n(%d+)\n(.*)$'
line = tonumber(line)
column = tonumber(column)
src = LA.remove_shebang(src)
local f, err, linenum, colnum, linenum2 = LA.loadstring(src)
if not f then
if not linenum2 then
myprint(linenum)
else
myprint(linenum2 .. ' ' .. linenum)
end
return
end
local ast; ast, err, linenum, colnum, linenum2 = LA.ast_from_string(src, "noname.lua")
if not ast then return end
local tokenlist = LA.ast_to_tokenlist(ast, src)
LI.inspect(ast, tokenlist)
function highlight(tokenlist, line, column)
myprint 'highlight'
local curvar = getcurvar(tokenlist, line, column)
for i, token in ipairs(tokenlist) do
local kind
Expand Down Expand Up @@ -85,13 +70,52 @@ return function(src)
if kind then
local l1, c1 = unpack(token.ast.lineinfo.first, 1, 2)
local l2, c2 = unpack(token.ast.lineinfo.last, 1, 2)
if l1 == l2 then
myprint(kind .. ' ' .. l1 .. ' ' .. c1 .. ' ' .. c2)
end
if l1 == l2 then myprint(kind .. ' ' .. l1 .. ' ' .. c1 .. ' ' .. c2) end
end
end
end
function rename(tokenlist, line, column)
myprint 'rename'
local curvar = getcurvar(tokenlist, line, column)
for i, token in ipairs(tokenlist) do
if curvar and curvar.ast.id == token.ast.id then
local l1, c1 = unpack(token.ast.lineinfo.first, 1, 2)
local l2, c2 = unpack(token.ast.lineinfo.last, 1, 2)
if l1 == l2 then myprint(l1 .. ' ' .. c1 .. ' ' .. c2) end
end
end
end
return function(src)
local action, line, column, src = src:match '^(%S+)\n(%d+)\n(%d+)\n(.*)$'
line = tonumber(line)
column = tonumber(column)
src = LA.remove_shebang(src)
-- Quickly parse the source code using loadstring() to check for syntax errors.
local f, err, linenum, colnum, linenum2 = LA.loadstring(src)
if not f then
myprint 'syntax_error'
myprint(linenum)
myprint(colnum)
myprint(linenum2 or 0)
myprint(err or '')
return
end
-- Now parse the source code using metalua to build an abstract syntax tree.
local ast; ast, err, linenum, colnum, linenum2 = LA.ast_from_string(src, "noname.lua")
if not ast then return end
-- Create a list of tokens from the AST and decorate it using luainspect.
local tokenlist = LA.ast_to_tokenlist(ast, src)
LI.inspect(ast, tokenlist)
-- Branch on the requested action.
if action == 'highlight' then
highlight(tokenlist, line, column)
elseif action == 'rename' then
rename(tokenlist, line, column)
end
end
-- Enable type checking of ast.* expressions.
--! require 'luainspect.typecheck' (context)
Expand Down

0 comments on commit 0c52221

Please sign in to comment.