def _fmt_file(self, tag, dups_fnames): """Format tag file.""" pmod = settings.get("project_search_modifier") bmod = settings.get("buffer_search_modifier") file = tag["file"] root = self.plug.project.get_root() # The user always wants the tag file displayed relative to the # current project root if it exists. Replacing the home with # '~' may be needed for files outside the current project that # are printed with the absolute path. if settings.get("tag_file_relative_to_project_root", bool): if root: f = file.replace(root, u"").replace(os.path.expanduser("~"), u"~") return f[1:] if f.startswith(os.path.sep) else f # If the `g:surfer_tag_file_custom_depth` is set, # cut the path according its value depth = settings.get("tag_file_custom_depth", int) if depth > 0: return os.path.join(*file.split(os.path.sep)[-depth:]) # If th file name is duplicate in among search results # then display also the container directory if file in dups_fnames and len(file.split(os.path.sep)) > 1: return os.path.join(*file.split(os.path.sep)[-2:]) # By default display only the file name return os.path.basename(file)
def _highlight_tags(self, tags, curr_line): """To highlight search results.""" vk_colors = settings.get("visual_kinds_colors") vk_shape = settings.get("visual_kinds_shape") indicator = settings.get("current_line_indicator") for i, tag in enumerate(tags): if i == curr_line: offset = len(indicator.encode(v.encoding())) else: offset = len(indicator) if settings.get("visual_kinds", bool): offset += len(vk_shape.encode(v.encoding())) kind = tag["exts"].get("kind") if kind in vk_colors: patt = u"\c\%{}l{}".format(i+1, vk_shape.replace(u"u",u"%u")) v.highlight("SurferVisualKind_" + kind, patt) patt = u"\c\%{}l\%{}c.*".format(i+1, offset+len(tag["name"])+1) v.highlight("SurferShade", patt) for pos in misc.as_byte_indexes(tag["match_positions"], tag["name"]): patt = u"\c\%{}l\%{}c.".format(i+1, offset+pos+1) v.highlight("SurferMatches", patt)
def _split_query(self, query): """To extract the search modifier from the query. The clean query is also returned.""" bmod = settings.get("buffer_search_modifier") pmod = settings.get("project_search_modifier") if query and query[0] in (pmod, bmod): return query[0], query[1:] return u"", query
def _files(self, modifier, curr_bufname): """To return all files for which tags need to be generated.""" if curr_bufname and modifier == settings.get("buffer_search_modifier"): files = [curr_bufname] elif modifier == settings.get("project_search_modifier"): files = self.plug.project.get_files() else: files = v.buffers() exclude = settings.get("exclude") fn = lambda path: not any(fnmatch(path, patt) for patt in exclude) return filter(fn, files)
def _render_line(self, tag, query, dups_fnames): """To format a single line with the tag information.""" visual_kind = u"" if settings.get("visual_kinds", bool): visual_kind = settings.get("visual_kinds_shape") debug = settings.get("debug", int) line_format = settings.get("line_format") return u"{}{}{}{}{}".format( u" "*len(settings.get("current_line_indicator")), visual_kind, tag["name"], u"".join(self.formatter.fmt(fmtstr, tag, dups_fnames) for fmtstr in line_format), u" [{}]".format(tag["similarity"]) if debug else "")
def _filetype_data(self, filetype): """To return filetype-specific data.""" if filetype == "*": prg = settings.get("ctags_prg") args = settings.get("ctags_args") kinds_map = {} exclude_kinds = settings.get("exclude_kinds") else: user_langs = settings.get("custom_languages") prg = user_langs[filetype].get("ctags_prg", "") args = user_langs[filetype].get("ctags_args", "") kinds_map = user_langs[filetype].get("kinds_map", {}) exclude_kinds = user_langs[filetype].get("exclude_kinds", []) return prg, args, kinds_map, exclude_kinds
def _setup_buffer(self): """To set sane options for the search results buffer.""" last_search = "" if v.eval("@/"): last_search = v.eval("@/").decode(v.encoding()).replace(u'"', u'\\"') self.exit_cmds.extend([ u"let @/=\"{}\"".format(last_search), u"set laststatus={}".format(v.opt("ls")), u"set guicursor={}".format(v.opt("gcr")), ]) commands = [ "let @/ = ''", "call clearmatches()" ] options = [ "buftype=nofile", "bufhidden=wipe", "nobuflisted", "noundofile", "nobackup", "noswapfile", "nowrap", "nonumber", "nolist", "textwidth=0", "colorcolumn=0", "laststatus=0", "norelativenumber", "nocursorcolumn", "nospell", "foldcolumn=0", "foldcolumn=0", "guicursor=a:hor5-Cursor-blinkwait100", ] if settings.get("cursorline", bool): options.append("cursorline") else: options.append("nocursorline") for opt in options: v.exe("try|setl {}|catch|endtry".format(opt)) for cmd in commands: v.exe(cmd)
def _render_curr_line(self, cursor_pos): """To add an indicator in front of the current line.""" if cursor_pos < 0: cursor_pos = len(v.buffer()) - 1 line = v.getline(cursor_pos) indicator = settings.get("current_line_indicator") v.setline(cursor_pos, indicator + line[len(indicator):]) return cursor_pos
def setup_colors(self): """To setup Surfer highlight groups.""" postfix = "" if v.opt("bg") == "light" else "_darkbg" colors = { "SurferShade": settings.get("shade_color{}".format(postfix)), "SurferMatches": settings.get("matches_color{}".format(postfix)), "SurferPrompt": settings.get("prompt_color{}".format(postfix)), "SurferError": "WarningMsg" } for group, color in colors.items(): if color: link = "" if "=" in color else "link" v.exe(u"hi {} {} {}".format(link, group, color)) colors = settings.get("visual_kinds_colors{}".format(postfix)) for kind, color in colors.items(): if color: link = "" if "=" in color else "link" v.exe(u"hi {} SurferVisualKind_{} {}".format(link, kind, color))
def _update(self): """To update search results.""" tags = [] error = "" if self.perform_new_search: try: max_results = settings.get('max_results', int) tags = self.plug.finder.find_tags( self.query, max_results, self.user_buf.name) self.search_results_cache = tags except ex.SurferException as e: error = e.message else: tags = self.search_results_cache self.mapper, self.cursor_pos = self.renderer.render( self.winnr, self.cursor_pos, self.query, tags, msg=error, iserror=bool(error))
def _group_files_by_filetype(self, files): """To group files by filetype. The filetype "*" groups all files that will be parsed with `surfer_ctags_prg`. """ user_langs = settings.get("custom_languages") extensions_map = {} for filetype, values in user_langs.items(): for extension in values.get("extensions", []): extensions_map[extension] = filetype groups = defaultdict(list) for f in files: extension = splitext(f)[1] groups[extensions_map.get(extension, "*")].append(f) return groups
def render(self, target_win, cursor_pos, query, tags, msg="", iserror=False): """To render all search results.""" v.exe('syntax clear') v.focus_win(target_win) mapper = {} if not tags and not msg: msg = settings.get("no_results_msg") if msg: v.setbuffer(msg) v.setwinh(len(msg.split("\n"))) (len(msg.split("\n"))) cursor_pos = 0 if iserror: self._highlight_err() else: # Find duplicates file names dups = {} for _, g in groupby(tags, key=lambda t: os.path.basename(t["file"])): # s is a set of unique paths but with the same basename s = set(t["file"] for t in g) if len(s) > 1: dups.update((file, True) for file in s) tags = tags[::-1] mapper = dict(enumerate(t for t in tags)) v.setbuffer([self._render_line(t, query, dups) for t in tags]) cursor_pos = self._render_curr_line(cursor_pos) self._highlight_tags(tags, cursor_pos) v.setwinh(len(tags)) v.cursor((cursor_pos + 1, 0)) v.exe("normal! 0") return mapper, cursor_pos
def _find(self, query, tags, max_results): """To find all matching tags for the given `query`.""" matches = [] smart_case = settings.get("smart_case", int) for tag in tags: similarity, positions = match(query, tag["name"], smart_case) if positions: matches.append({ "match_positions": positions, "similarity": similarity, "name": tag["name"], "file": tag["file"], "cmd": tag["cmd"], "exts": tag["exts"] }) l = len(matches) if max_results < 0 or max_results > l: max_results = l return sorted(matches, key=itemgetter("similarity"))[:max_results]
def open(self): """To open the Surfer user interface.""" # The Fugitive plugin seems to interfere with Surfer since it adds # some filenames to the vim option `tags`. Surfer does this too, # but if Fugitive is installed and the user is editing a file in a git # repository, it seems that Surfer cannot append anything to the # `tag` option. I haven't still figured out why this happens but this # seems to fix the issue. v.exe("exe 'set tags='.&tags") self.user_buf = self.BufInfo(v.bufname(), v.bufnr(), v.winnr()) pmod = settings.get("project_search_modifier") bmod = settings.get("buffer_search_modifier") prompt = u"echohl SurferPrompt | echon \"{}\" | echohl None".format( settings.get("prompt")) self._open_window() self.renderer.render(self.winnr, -1, "", [], "") v.redraw() # Start the input loop key = input.Input() while True: self.perform_new_search = True # Display the prompt and the current query v.exe(prompt) query = self.query.replace("\\", "\\\\").replace('"', '\\"') v.exe(u"echon \"{}\"".format(query)) # Wait for the next pressed key key.get() # Go to the tag on the current line if (key.RETURN or key.CTRL and key.CHAR in ('g', 'o', 'p', 's')): mode = key.CHAR if key.CHAR in ('s', 'p') else '' tag = self.mapper.get(self.cursor_pos) if tag: self._jump_to(tag, mode) break # Close the Surfer window elif key.ESC or key.INTERRUPT: self._close() break # Delete a character backward elif key.BS: query = self.query.strip() if query and query in (bmod, pmod): self.plug.generator.rebuild_tags = True self.query = u"{}".format(self.query)[:-1] self.cursor_pos = -1 # move the cursor to the bottom # Move the cursor up elif key.UP or key.TAB or key.CTRL and key.CHAR == 'k': self.perform_new_search = False if self.cursor_pos == 0: self.cursor_pos = len(v.buffer()) - 1 else: self.cursor_pos -= 1 # Move the cursor down elif key.DOWN or key.CTRL and key.CHAR == 'j': self.perform_new_search = False if self.cursor_pos == len(v.buffer()) - 1: self.cursor_pos = 0 else: self.cursor_pos += 1 # Clear the current search elif key.CTRL and key.CHAR == 'u': query = self.query.lstrip() if query and query[0] in (bmod, pmod): self.query = query[0] else: self.query = u"" self.cursor_pos = -1 # move the cursor to the bottom # A character has been pressed. elif key.CHAR: self.query += key.CHAR self.cursor_pos = -1 # move the cursor to the bottom if key.CHAR in (pmod, bmod) and len(self.query.strip()) == 1: self.plug.generator.rebuild_tags = True else: v.redraw() continue self._update() v.redraw()
def get_root(self): """To return the current project root.""" if not self.root_cache: self.root_cache = self._find_root(v.cwd(), settings.get("root_markers")) return self.root_cache