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)