locate.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import os
  2. import sys
  3. import curses
  4. import textwrap
  5. import ioex.selector
  6. def update_locate_database(root_path = None, database_path = None, require_visibility = None):
  7. import subprocess
  8. cmd = ['updatedb']
  9. if root_path:
  10. cmd.append('--database-root')
  11. cmd.append(root_path)
  12. if database_path:
  13. cmd.append('--output')
  14. cmd.append(database_path)
  15. if require_visibility is not None:
  16. cmd.append('--require-visibility')
  17. cmd.append('yes' if require_visibility else 'no')
  18. subprocess.check_call(cmd)
  19. def locate(patterns, match_all = False, ignore_case = False, database_path = None):
  20. if type(patterns) is not list:
  21. patterns = [str(patterns)]
  22. patterns = [str(p) for p in patterns]
  23. options = []
  24. if match_all:
  25. options.append("--all")
  26. if ignore_case:
  27. options.append("--ignore-case")
  28. if database_path:
  29. options.append('--database')
  30. options.append(database_path)
  31. import subprocess
  32. try:
  33. output = subprocess.check_output(["locate"] + options + patterns)
  34. except subprocess.CalledProcessError as e:
  35. if e.returncode == 1:
  36. return []
  37. else:
  38. raise e
  39. return output.decode(sys.getfilesystemencoding()).strip().split('\n')
  40. class LocateRootNode(ioex.selector.Node):
  41. def __init__(
  42. self,
  43. window,
  44. patterns,
  45. match_all,
  46. ignore_case,
  47. database_path,
  48. update_database,
  49. update_require_visibility
  50. ):
  51. super(LocateRootNode, self).__init__()
  52. self._window = window
  53. self._patterns = [p for p in patterns if len(p) > 0]
  54. self._match_all = match_all
  55. self._ignore_case = ignore_case
  56. self._database_path = database_path
  57. self._update_database = update_database
  58. self._update_require_visibility = update_require_visibility
  59. self.refresh_children()
  60. def get_window_height(self):
  61. return self._window.getmaxyx()[0]
  62. def get_window_width(self):
  63. return self._window.getmaxyx()[1]
  64. def get_patterns_height(self):
  65. return self._patterns_height
  66. def get_common_prefix_display_height(self):
  67. return self._common_prefix_display_height
  68. def showing_common_prefix(self):
  69. return self._common_prefix_display_height > 0
  70. def get_header_height(self):
  71. return self.get_patterns_height() + self.get_common_prefix_display_height()
  72. def show_patterns(self):
  73. label = 'patterns: '
  74. self._window.addstr(0, 0, label, curses.A_BOLD)
  75. # join patterns so textwrap.fill may be applied
  76. patterns_text = textwrap.fill(
  77. '\1'.join(self._patterns),
  78. # fixme: unwanted new lines occur if width is not decremented
  79. self.get_window_width() - 1,
  80. initial_indent = '#' * len(label),
  81. subsequent_indent = ' ' * len(label)
  82. )
  83. patterns_wrapped = patterns_text[len(label):].split('\1')
  84. for pattern_wrapped in patterns_wrapped:
  85. self._window.addstr(pattern_wrapped, curses.A_REVERSE)
  86. self._window.addstr(' ')
  87. self._window.refresh()
  88. self._patterns_height = patterns_text.count('\n') + 1
  89. # remove A_REVERSE attribute from ident blocks
  90. for row_index in range(1, self._patterns_height):
  91. self._window.addstr(row_index, 0, ' ' * len(label))
  92. def show_common_prefix(self):
  93. if len(self.get_children()) > 1 and len(self.common_prefix) > len('/'):
  94. self._window.addstr(self.get_patterns_height(), 0, 'common prefix: ', curses.A_BOLD)
  95. self._window.addstr(self.common_prefix)
  96. self._window.clrtoeol()
  97. self._window.refresh()
  98. self._common_prefix_display_height = 1
  99. else:
  100. self._common_prefix_display_height = 0
  101. def show_header(self):
  102. self.show_patterns()
  103. self.show_common_prefix()
  104. def refresh_children(self):
  105. self._window.clear()
  106. self.show_patterns()
  107. self._clear_children()
  108. if self._update_database:
  109. self._window.addstr(self.get_patterns_height(), 0, 'updating database... ')
  110. self._window.refresh()
  111. update_locate_database(
  112. database_path = self._database_path,
  113. require_visibility = self._update_require_visibility
  114. )
  115. self._window.addstr(self.get_patterns_height(), 0, 'searching database... ')
  116. self._window.clrtoeol()
  117. self._window.refresh()
  118. paths = locate(
  119. self._patterns,
  120. match_all = self._match_all,
  121. ignore_case = self._ignore_case,
  122. database_path = self._database_path
  123. )
  124. if len(paths) == 0:
  125. self._window.addstr(self.get_patterns_height(), 0, 'nothing found')
  126. self._window.clrtoeol()
  127. self._window.refresh()
  128. else:
  129. for path in paths:
  130. self._append_child(LocatePathNode(path))
  131. self.common_prefix = os.path.dirname(os.path.commonprefix(paths))
  132. self.show_common_prefix()
  133. class LocatePathNode(ioex.selector.Node):
  134. def __init__(self, path):
  135. super(LocatePathNode, self).__init__()
  136. self.path = path
  137. def get_label(self):
  138. if self.get_parent().showing_common_prefix():
  139. return self.path[len(self.get_parent().common_prefix) + 1:]
  140. else:
  141. return self.path
  142. def locate_select(
  143. stdscr,
  144. patterns = [],
  145. match_all = False,
  146. ignore_case = False,
  147. update_database = False,
  148. multiple = False,
  149. database_path = None,
  150. update_require_visibility = None
  151. ):
  152. curses.curs_set(0)
  153. root = LocateRootNode(
  154. stdscr,
  155. patterns,
  156. match_all,
  157. ignore_case,
  158. database_path,
  159. update_database,
  160. update_require_visibility
  161. )
  162. selection = ioex.selector.select(
  163. stdscr,
  164. root,
  165. multiple = multiple
  166. )
  167. if selection is None:
  168. return None
  169. else:
  170. return [n.path for n in selection]