dotfiles

configuration files for shell, text editor, graphical environment, etc.
git clone git://src.adamsgaard.dk/dotfiles
Log | Files | Refs | README | LICENSE

ctags.lua (5691B)


      1 require('vis')
      2 
      3 local positions = {}
      4 
      5 local function get_path(prefix, path)
      6 	if string.find(path, '^./') ~= nil then
      7 		path = path:sub(3)
      8 	end
      9 
     10 	return prefix .. path, path
     11 end
     12 
     13 local function find_tags(path)
     14 	for i = #path, 1, -1 do
     15 		if path:sub(i, i) == '/' then
     16 			local prefix = path:sub(1, i)
     17 			local filename = prefix .. 'tags'
     18 			local file = io.open(filename, 'r')
     19 
     20 			if file ~= nil then
     21 				return file, prefix
     22 			end
     23 		end
     24 	end
     25 end
     26 
     27 local function bsearch(file, word)
     28 	local buffer_size = 8096
     29 	local format = '\n(.-)\t(.-)\t(.-);\"\t'
     30 
     31 	local from = 0
     32 	local to = file:seek('end')
     33 	local startpos = nil
     34 
     35 	while from <= to do
     36 		local mid = from + math.floor((to - from) / 2)
     37 		file:seek('set', mid)
     38 
     39 		local content = file:read(buffer_size, '*line')
     40 		if content ~= nil then
     41 			local key, filename, excmd = string.match(content, format)
     42 			if key == nil then
     43 				break
     44 			end
     45 
     46 			if key == word then
     47 				startpos = mid
     48 			end
     49 
     50 			if key >= word then
     51 				to = mid - 1
     52 			else
     53 				from = mid + 1
     54 			end
     55 		else
     56 			to = mid - 1
     57 		end
     58 	end
     59 
     60 	if startpos ~= nil then
     61 		file:seek('set', startpos)
     62 
     63 		local result = {}
     64 		while true do
     65 			local content = file:read(buffer_size, '*line')
     66 			if content == nil then
     67 				break
     68 			end
     69 
     70 			for key, filename, excmd in string.gmatch(content, format) do
     71 				if key == word then
     72 					result[#result + 1] = {name = filename, excmd = excmd}
     73 				else
     74 					return result
     75 				end
     76 			end
     77 		end
     78 
     79 		return result
     80 	end
     81 end
     82 
     83 local function get_query()
     84 	local line = vis.win.selection.line
     85 	local pos = vis.win.selection.col
     86 	local str = vis.win.file.lines[line]
     87 
     88 	local from, to = 0, 0
     89 	while pos > to do
     90 		from, to = str:find('[%a_]+[%a%d_]*', to + 1)
     91 		if from == nil or from > pos then
     92 			return nil
     93 		end
     94 	end
     95 
     96 	return string.sub(str, from, to)
     97 end
     98 
     99 local function get_matches(word, path)
    100 	local file, prefix = find_tags(path)
    101 
    102 	if file ~= nil then
    103 		local results = bsearch(file, word)
    104 		file:close()
    105 
    106 		if results ~= nil then
    107 			local matches = {}
    108 			for i = 1, #results do
    109 				local result = results[i]
    110 				local path, name = get_path(prefix, result.name)
    111 				local desc = string.format('%s%s', name, tonumber(result.excmd) and ":"..result.excmd or "")
    112 
    113 				matches[#matches + 1] = {desc = desc, path = path, excmd = result.excmd}
    114 			end
    115 
    116 			return matches
    117 		end
    118 	end
    119 end
    120 
    121 local function get_match(word, path)
    122 	local matches = get_matches(word, path)
    123 	if matches ~= nil then
    124 		for i = 1, #matches do
    125 			if matches[i].path == path then
    126 				return matches[i]
    127 			end
    128 		end
    129 
    130 		return matches[1]
    131 	end
    132 end
    133 
    134 local function escape(text)
    135 	return text:gsub("[][)(}{|+?*.]", "\\%0")
    136 	:gsub("%^", "\\^"):gsub("^/\\%^", "/^")
    137 	:gsub("%$", "\\$"):gsub("\\%$/$", "$/")
    138 	:gsub("\\\\%$%$/$", "\\$$")
    139 end
    140 
    141 --[[
    142 - Can't test vis:command() as it will still return true if the edit command fails.
    143 - Can't test File.modified as the edit command can succeed if the current file is
    144   modified but open in another window and this behavior is useful.
    145 - Instead just check the path again after trying the edit command.
    146 ]]
    147 local function goto_pos(pos)
    148 	if pos.path ~= vis.win.file.path then
    149 		vis:command(string.format('e %s', pos.path))
    150 		if pos.path ~= vis.win.file.path then
    151 			return false
    152 		end
    153 	end
    154 	if tonumber(pos.excmd) then
    155 		vis.win.selection:to(pos.excmd, pos.col)
    156 	else
    157 		vis.win.selection:to(1, 1)
    158 		vis:command(escape(pos.excmd))
    159 		vis.win.selection.pos = vis.win.selection.range.start
    160 		vis.mode = vis.modes.NORMAL
    161 	end
    162 	return true
    163 end
    164 
    165 local function goto_tag(path, excmd)
    166 	local old = {
    167 		path = vis.win.file.path,
    168 		excmd = vis.win.selection.line,
    169 		col  = vis.win.selection.col,
    170 	}
    171 
    172 	local last_search = vis.registers['/']
    173 	if goto_pos({ path = path, excmd = excmd, col = 1 }) then
    174 		positions[#positions + 1] = old
    175 		vis.registers['/'] = last_search
    176 	end
    177 end
    178 
    179 local function pop_pos()
    180 	if #positions < 1 then
    181 		return
    182 	end
    183 	if goto_pos(positions[#positions]) then
    184 		table.remove(positions, #positions)
    185 	end
    186 end
    187 
    188 local function get_path()
    189 	if vis.win.file.path == nil then
    190 		return os.getenv('PWD') .. '/'
    191 	end
    192 	return vis.win.file.path
    193 end
    194 
    195 local function tag_cmd(tag)
    196 	local match = get_match(tag, get_path())
    197 	if match == nil then
    198 		vis:info(string.format('Tag not found: %s', tag))
    199 	else
    200 		goto_tag(match.path, match.excmd)
    201 	end
    202 end
    203 
    204 local function tselect_cmd(tag)
    205 	local matches = get_matches(tag, get_path())
    206 	if matches == nil then
    207 		vis:info(string.format('Tag not found: %s', tag))
    208 	else
    209 		local keys = {}
    210 		for i = 1, #matches do
    211 			table.insert(keys, matches[i].desc)
    212 		end
    213 
    214 		local command = string.format(
    215 			[[echo -e "%s" | vis-menu -p "Choose tag:"]], table.concat(keys, [[\n]]))
    216 
    217 		local status, output =
    218 			vis:pipe(vis.win.file, {start = 0, finish = 0}, command)
    219 
    220 		if status ~= 0 then
    221 			vis:info('Command failed')
    222 			return
    223 		end
    224 
    225 		local choice = string.match(output, '(.*)\n')
    226 		for i = 1, #matches do
    227 			local match = matches[i]
    228 			if match.desc == choice then
    229 				goto_tag(match.path, match.excmd)
    230 				break
    231 			end
    232 		end
    233 	end
    234 end
    235 
    236 vis:command_register("tag", function(argv, force, win, selection, range)
    237 	if #argv == 1 then
    238 		tag_cmd(argv[1])
    239 	end
    240 end)
    241 
    242 vis:command_register("tselect", function(argv, force, win, selection, range)
    243 	if #argv == 1 then
    244 		tselect_cmd(argv[1])
    245 	end
    246 end)
    247 
    248 vis:command_register("pop", function(argv, force, win, selection, range)
    249 	pop_pos()
    250 end)
    251 
    252 vis:map(vis.modes.NORMAL, '<C-]>', function(keys)
    253 	local query = get_query()
    254 	if query ~= nil then
    255 		tag_cmd(query)
    256 	end
    257 	return 0
    258 end)
    259 
    260 vis:map(vis.modes.NORMAL, 'g<C-]>', function(keys)
    261 	local query = get_query()
    262 	if query ~= nil then
    263 		tselect_cmd(query)
    264 	end
    265 	return 0
    266 end)
    267 
    268 vis:map(vis.modes.NORMAL, '<C-t>', function(keys)
    269 	pop_pos()
    270 	return 0
    271 end)