#!/usr/bin/python # PYTHON_ARGCOMPLETE_OK import sys import osex import curses import pprint import locale import textwrap import argparse import argcomplete import ioex, ioex.cursesex, ioex.selector class LocateRootNode(ioex.selector.Node): def __init__( self, window, patterns, match_all, ignore_case, database_path, update_database, update_require_visibility ): super(LocateRootNode, self).__init__() self._window = window self._patterns = [p for p in patterns if len(p) > 0] self._match_all = match_all self._ignore_case = ignore_case self._database_path = database_path self._update_database = update_database self._update_require_visibility = update_require_visibility self.refresh_children() def get_window_height(self): return self._window.getmaxyx()[0] def get_window_width(self): return self._window.getmaxyx()[1] def get_header_height(self): return self._header_height def show_patterns(self): label = 'patterns: ' self._window.addstr(0, 0, label, curses.A_BOLD) # join patterns so textwrap.fill may be applied patterns_text = textwrap.fill( '\1'.join(self._patterns), # fixme: unwanted new lines occur if width is not decremented self.get_window_width() - 1, initial_indent = '#' * len(label), subsequent_indent = ' ' * len(label) ) patterns_wrapped = patterns_text[len(label):].split('\1') for pattern_wrapped in patterns_wrapped: self._window.addstr(pattern_wrapped, curses.A_REVERSE) self._window.addstr(' ') self._window.refresh() self._header_height = patterns_text.count('\n') + 1 # remove A_REVERSE attribute from ident blocks for row_index in range(1, self._header_height): self._window.addstr(row_index, 0, ' ' * len(label)) def show_header(self): self.show_patterns() def refresh_children(self): self._window.clear() self.show_header() self._clear_children() if self._update_database: self._window.addstr(self.get_header_height(), 0, 'updating database... ') self._window.refresh() osex.update_locate_database( database_path = self._database_path, require_visibility = self._update_require_visibility ) self._window.addstr(self.get_header_height(), 0, 'searching database... ') self._window.clrtoeol() self._window.refresh() paths = osex.locate( self._patterns, match_all = self._match_all, ignore_case = self._ignore_case, database_path = self._database_path ) if len(paths) == 0: self._window.addstr(self.get_header_height(), 0, 'nothing found') self._window.clrtoeol() self._window.refresh() else: for path in paths: self._append_child(ioex.selector.StaticNode(path)) def locate_select(stdscr, patterns, match_all, ignore_case, update_database, multiple, database_path, update_require_visibility): curses.curs_set(0) root = LocateRootNode( stdscr, patterns, match_all, ignore_case, database_path, update_database, update_require_visibility ) selection = ioex.selector.select( stdscr, root, multiple = multiple ) if selection is None: return None else: return [n.get_label() for n in selection] def _init_argparser(): argparser = argparse.ArgumentParser(description = None) argparser.add_argument('--database-path') argparser.add_argument('--ignore-case', action='store_true') argparser.add_argument('--match-all', action='store_true') argparser.add_argument('--multiple', action='store_true') argparser.add_argument('--update-database', action='store_true') argparser.add_argument('--update-require-visibility', choices = ['yes', 'no']) argparser.add_argument('patterns', metavar = 'pattern', nargs = '+') return argparser def main(argv): argparser = _init_argparser() argcomplete.autocomplete(argparser) args = argparser.parse_args(argv) params = vars(args) if args.update_require_visibility == 'yes': args.update_require_visibility = True elif args.update_require_visibility == 'no': args.update_require_visibility = False paths = ioex.cursesex.tty_wrapper(locate_select, **vars(args)) if paths is None: return 1 else: print('\n'.join(paths).encode(locale.getpreferredencoding())) return 0 if __name__ == "__main__": sys.exit(main(sys.argv[1:]))