select-locate 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. #!/usr/bin/python
  2. # PYTHON_ARGCOMPLETE_OK
  3. import sys
  4. import osex
  5. import curses
  6. import pprint
  7. import locale
  8. import textwrap
  9. import argparse
  10. import argcomplete
  11. import ioex, ioex.cursesex, ioex.selector
  12. class LocateRootNode(ioex.selector.Node):
  13. def __init__(
  14. self,
  15. window,
  16. patterns,
  17. match_all,
  18. ignore_case,
  19. database_path,
  20. update_database,
  21. update_require_visibility
  22. ):
  23. super(LocateRootNode, self).__init__()
  24. self._window = window
  25. self._patterns = [p for p in patterns if len(p) > 0]
  26. self._match_all = match_all
  27. self._ignore_case = ignore_case
  28. self._database_path = database_path
  29. self._update_database = update_database
  30. self._update_require_visibility = update_require_visibility
  31. self.refresh_children()
  32. def get_window_height(self):
  33. return self._window.getmaxyx()[0]
  34. def get_window_width(self):
  35. return self._window.getmaxyx()[1]
  36. def get_header_height(self):
  37. return self._header_height
  38. def show_patterns(self):
  39. label = 'patterns: '
  40. self._window.addstr(0, 0, label, curses.A_BOLD)
  41. # join patterns so textwrap.fill may be applied
  42. patterns_text = textwrap.fill(
  43. '\1'.join(self._patterns),
  44. # fixme: unwanted new lines occur if width is not decremented
  45. self.get_window_width() - 1,
  46. initial_indent = '#' * len(label),
  47. subsequent_indent = ' ' * len(label)
  48. )
  49. patterns_wrapped = patterns_text[len(label):].split('\1')
  50. for pattern_wrapped in patterns_wrapped:
  51. self._window.addstr(pattern_wrapped, curses.A_REVERSE)
  52. self._window.addstr(' ')
  53. self._window.refresh()
  54. self._header_height = patterns_text.count('\n') + 1
  55. # remove A_REVERSE attribute from ident blocks
  56. for row_index in range(1, self._header_height):
  57. self._window.addstr(row_index, 0, ' ' * len(label))
  58. def show_header(self):
  59. self.show_patterns()
  60. def refresh_children(self):
  61. self._window.clear()
  62. self.show_header()
  63. self._clear_children()
  64. if self._update_database:
  65. self._window.addstr(self.get_header_height(), 0, 'updating database... ')
  66. self._window.refresh()
  67. osex.update_locate_database(
  68. database_path = self._database_path,
  69. require_visibility = self._update_require_visibility
  70. )
  71. self._window.addstr(self.get_header_height(), 0, 'searching database... ')
  72. self._window.clrtoeol()
  73. self._window.refresh()
  74. paths = osex.locate(
  75. self._patterns,
  76. match_all = self._match_all,
  77. ignore_case = self._ignore_case,
  78. database_path = self._database_path
  79. )
  80. if len(paths) == 0:
  81. self._window.addstr(self.get_header_height(), 0, 'nothing found')
  82. self._window.clrtoeol()
  83. self._window.refresh()
  84. else:
  85. for path in paths:
  86. self._append_child(ioex.selector.StaticNode(path))
  87. def locate_select(stdscr, patterns, match_all, ignore_case, update_database, multiple, database_path, update_require_visibility):
  88. curses.curs_set(0)
  89. root = LocateRootNode(
  90. stdscr,
  91. patterns,
  92. match_all,
  93. ignore_case,
  94. database_path,
  95. update_database,
  96. update_require_visibility
  97. )
  98. selection = ioex.selector.select(
  99. stdscr,
  100. root,
  101. multiple = multiple
  102. )
  103. if selection is None:
  104. return None
  105. else:
  106. return [n.get_label() for n in selection]
  107. def _init_argparser():
  108. argparser = argparse.ArgumentParser(description = None)
  109. argparser.add_argument('--database-path')
  110. argparser.add_argument('--ignore-case', action='store_true')
  111. argparser.add_argument('--match-all', action='store_true')
  112. argparser.add_argument('--multiple', action='store_true')
  113. argparser.add_argument('--update-database', action='store_true')
  114. argparser.add_argument('--update-require-visibility', choices = ['yes', 'no'])
  115. argparser.add_argument('patterns', metavar = 'pattern', nargs = '+')
  116. return argparser
  117. def main(argv):
  118. argparser = _init_argparser()
  119. argcomplete.autocomplete(argparser)
  120. args = argparser.parse_args(argv)
  121. params = vars(args)
  122. if args.update_require_visibility == 'yes':
  123. args.update_require_visibility = True
  124. elif args.update_require_visibility == 'no':
  125. args.update_require_visibility = False
  126. paths = ioex.cursesex.tty_wrapper(locate_select, **vars(args))
  127. if paths is None:
  128. return 1
  129. else:
  130. print('\n'.join(paths).encode(locale.getpreferredencoding()))
  131. return 0
  132. if __name__ == "__main__":
  133. sys.exit(main(sys.argv[1:]))