git-cola v4.3.1
[git-cola.git] / cola / widgets / finder.py
blob5a6ec1cfd4d0fe45cf6da048d7ee81cedfe8fe6c
1 """File finder widgets"""
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 import os
4 from functools import partial
6 from qtpy import QtCore
7 from qtpy import QtWidgets
8 from qtpy.QtCore import Qt
9 from qtpy.QtCore import Signal
11 from ..i18n import N_
12 from ..qtutils import get
13 from ..utils import Group
14 from .. import cmds
15 from .. import core
16 from .. import gitcmds
17 from .. import hotkeys
18 from .. import icons
19 from .. import utils
20 from .. import qtutils
21 from . import completion
22 from . import defs
23 from . import filetree
24 from . import standard
25 from . import text
28 def finder(context, paths=None):
29 """Prompt and use 'git grep' to find the content."""
30 parent = qtutils.active_window()
31 widget = new_finder(context, paths=paths, parent=parent)
32 widget.show()
33 widget.raise_()
34 return widget
37 def new_finder(context, paths=None, parent=None):
38 """Create a finder widget"""
39 widget = Finder(context, parent=parent)
40 widget.search_for(paths or '')
41 return widget
44 def add_wildcards(arg):
45 """Add "*" around user input to generate ls-files pathspecs matches
47 >>> '*x*' == \
48 add_wildcards('x') == \
49 add_wildcards('*x') == \
50 add_wildcards('x*') == \
51 add_wildcards('*x*')
52 True
54 """
55 if not arg.startswith('*'):
56 arg = '*' + arg
57 if not arg.endswith('*'):
58 arg = arg + '*'
59 return arg
62 def show_help(context):
63 """Show the help page"""
64 help_text = N_(
65 """
66 Keyboard Shortcuts
67 ------------------
68 J, Down = Move Down
69 K, Up = Move Up
70 Enter = Edit Selected Files
71 Spacebar = Open File Using Default Application
72 Ctrl + L = Focus Text Entry Field
73 ? = Show Help
75 The up and down arrows change focus between the text entry field
76 and the results.
77 """
79 title = N_('Help - Find Files')
80 return text.text_dialog(context, help_text, title)
83 class FindFilesThread(QtCore.QThread):
84 """Finds files asynchronously"""
86 result = Signal(object)
88 def __init__(self, context, parent):
89 QtCore.QThread.__init__(self, parent)
90 self.context = context
91 self.query = None
93 def run(self):
94 context = self.context
95 query = self.query
96 if query is None:
97 args = []
98 else:
99 args = [add_wildcards(arg) for arg in utils.shell_split(query)]
100 filenames = gitcmds.tracked_files(context, *args)
101 if query == self.query:
102 self.result.emit(filenames)
103 else:
104 self.run()
107 class Finder(standard.Dialog):
108 """File Finder dialog"""
110 def __init__(self, context, parent=None):
111 standard.Dialog.__init__(self, parent)
112 self.context = context
113 self.setWindowTitle(N_('Find Files'))
114 if parent is not None:
115 self.setWindowModality(Qt.WindowModal)
117 label = os.path.basename(core.getcwd()) + '/'
118 self.input_label = QtWidgets.QLabel(label)
119 self.input_txt = completion.GitTrackedLineEdit(context, hint=N_('<path> ...'))
121 self.tree = filetree.FileTree(parent=self)
123 self.edit_button = qtutils.edit_button(default=True)
124 self.edit_button.setShortcut(hotkeys.EDIT)
126 name = cmds.OpenDefaultApp.name()
127 icon = icons.default_app()
128 self.open_default_button = qtutils.create_button(text=name, icon=icon)
129 self.open_default_button.setShortcut(hotkeys.PRIMARY_ACTION)
131 self.button_group = Group(self.edit_button, self.open_default_button)
132 self.button_group.setEnabled(False)
134 self.refresh_button = qtutils.refresh_button()
135 self.refresh_button.setShortcut(hotkeys.REFRESH)
137 self.help_button = qtutils.create_button(
138 text=N_('Help'), tooltip=N_('Show help\nShortcut: ?'), icon=icons.question()
141 self.close_button = qtutils.close_button()
143 self.input_layout = qtutils.hbox(
144 defs.no_margin, defs.button_spacing, self.input_label, self.input_txt
147 self.bottom_layout = qtutils.hbox(
148 defs.no_margin,
149 defs.button_spacing,
150 self.help_button,
151 self.refresh_button,
152 qtutils.STRETCH,
153 self.close_button,
154 self.open_default_button,
155 self.edit_button,
158 self.main_layout = qtutils.vbox(
159 defs.margin,
160 defs.no_spacing,
161 self.input_layout,
162 self.tree,
163 self.bottom_layout,
165 self.setLayout(self.main_layout)
166 self.setFocusProxy(self.input_txt)
168 thread = self.worker_thread = FindFilesThread(context, self)
169 thread.result.connect(self.process_result, type=Qt.QueuedConnection)
171 # pylint: disable=no-member
172 self.input_txt.textChanged.connect(lambda s: self.search())
173 self.input_txt.activated.connect(self.focus_tree)
174 self.input_txt.down.connect(self.focus_tree)
175 self.input_txt.enter.connect(self.focus_tree)
177 item_selection_changed = self.tree_item_selection_changed
178 self.tree.itemSelectionChanged.connect(item_selection_changed)
179 self.tree.up.connect(self.focus_input)
180 self.tree.space.connect(self.open_default)
182 qtutils.add_action(
183 self, 'Focus Input', self.focus_input, hotkeys.FOCUS, hotkeys.FINDER
186 self.show_help_action = qtutils.add_action(
187 self, N_('Show Help'), partial(show_help, context), hotkeys.QUESTION
190 qtutils.connect_button(self.edit_button, self.edit)
191 qtutils.connect_button(self.open_default_button, self.open_default)
192 qtutils.connect_button(self.refresh_button, self.search)
193 qtutils.connect_button(self.help_button, partial(show_help, context))
194 qtutils.connect_button(self.close_button, self.close)
195 qtutils.add_close_action(self)
197 self.init_size(parent=parent)
199 def focus_tree(self):
200 self.tree.setFocus()
202 def focus_input(self):
203 self.input_txt.setFocus()
205 def search(self):
206 self.button_group.setEnabled(False)
207 self.refresh_button.setEnabled(False)
208 query = get(self.input_txt)
209 self.worker_thread.query = query
210 self.worker_thread.start()
212 def search_for(self, txt):
213 self.input_txt.set_value(txt)
214 self.focus_input()
216 def process_result(self, filenames):
217 self.tree.set_filenames(filenames, select=True)
218 self.refresh_button.setEnabled(True)
220 def edit(self):
221 context = self.context
222 paths = self.tree.selected_filenames()
223 cmds.do(cmds.Edit, context, paths, background_editor=True)
225 def open_default(self):
226 context = self.context
227 paths = self.tree.selected_filenames()
228 cmds.do(cmds.OpenDefaultApp, context, paths)
230 def tree_item_selection_changed(self):
231 enabled = bool(self.tree.selected_item())
232 self.button_group.setEnabled(enabled)