doc: add Thomas to the credits
[git-cola.git] / cola / widgets / finder.py
blob1446d4c472c1d90a8f6c2f5685c01a754420a5b5
1 """File finder widgets"""
2 from __future__ import division, absolute_import, 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 Keyboard Shortcuts
66 ------------------
67 J, Down = Move Down
68 K, Up = Move Up
69 Enter = Edit Selected Files
70 Spacebar = Open File Using Default Application
71 Ctrl + L = Focus Text Entry Field
72 ? = Show Help
74 The up and down arrows change focus between the text entry field
75 and the results.
76 """)
77 title = N_('Help - Find Files')
78 return text.text_dialog(context, help_text, title)
81 class FindFilesThread(QtCore.QThread):
82 """Finds files asynchronously"""
84 result = Signal(object)
86 def __init__(self, context, parent):
87 QtCore.QThread.__init__(self, parent)
88 self.context = context
89 self.query = None
91 def run(self):
92 context = self.context
93 query = self.query
94 if query is None:
95 args = []
96 else:
97 args = [add_wildcards(arg) for arg in utils.shell_split(query)]
98 filenames = gitcmds.tracked_files(context, *args)
99 if query == self.query:
100 self.result.emit(filenames)
101 else:
102 self.run()
105 class Finder(standard.Dialog):
106 """File Finder dialog"""
108 def __init__(self, context, parent=None):
109 standard.Dialog.__init__(self, parent)
110 self.context = context
111 self.setWindowTitle(N_('Find Files'))
112 if parent is not None:
113 self.setWindowModality(Qt.WindowModal)
115 label = os.path.basename(core.getcwd()) + '/'
116 self.input_label = QtWidgets.QLabel(label)
117 self.input_txt = completion.GitTrackedLineEdit(
118 context, hint=N_('<path> ...'))
120 self.tree = filetree.FileTree(parent=self)
122 self.edit_button = qtutils.edit_button(default=True)
123 self.edit_button.setShortcut(hotkeys.EDIT)
125 name = cmds.OpenDefaultApp.name()
126 icon = icons.default_app()
127 self.open_default_button = qtutils.create_button(text=name, icon=icon)
128 self.open_default_button.setShortcut(hotkeys.PRIMARY_ACTION)
130 self.button_group = Group(self.edit_button, self.open_default_button)
131 self.button_group.setEnabled(False)
133 self.refresh_button = qtutils.refresh_button()
134 self.refresh_button.setShortcut(hotkeys.REFRESH)
136 self.help_button = qtutils.create_button(
137 text=N_('Help'), tooltip=N_('Show help\nShortcut: ?'),
138 icon=icons.question())
140 self.close_button = qtutils.close_button()
142 self.input_layout = qtutils.hbox(defs.no_margin, defs.button_spacing,
143 self.input_label, self.input_txt)
145 self.bottom_layout = qtutils.hbox(defs.no_margin, defs.button_spacing,
146 self.close_button,
147 qtutils.STRETCH,
148 self.help_button,
149 self.refresh_button,
150 self.open_default_button,
151 self.edit_button)
153 self.main_layout = qtutils.vbox(defs.margin, defs.no_spacing,
154 self.input_layout,
155 self.tree,
156 self.bottom_layout)
157 self.setLayout(self.main_layout)
158 self.setFocusProxy(self.input_txt)
160 thread = self.worker_thread = FindFilesThread(context, self)
161 thread.result.connect(self.process_result, type=Qt.QueuedConnection)
163 self.input_txt.textChanged.connect(lambda s: self.search())
164 self.input_txt.activated.connect(self.focus_tree)
165 self.input_txt.down.connect(self.focus_tree)
166 self.input_txt.enter.connect(self.focus_tree)
168 item_selection_changed = self.tree_item_selection_changed
169 self.tree.itemSelectionChanged.connect(item_selection_changed)
170 self.tree.up.connect(self.focus_input)
171 self.tree.space.connect(self.open_default)
173 qtutils.add_action(self, 'Focus Input', self.focus_input,
174 hotkeys.FOCUS, hotkeys.FINDER)
176 self.show_help_action = qtutils.add_action(
177 self, N_('Show Help'), partial(show_help, context),
178 hotkeys.QUESTION)
180 qtutils.connect_button(self.edit_button, self.edit)
181 qtutils.connect_button(self.open_default_button, self.open_default)
182 qtutils.connect_button(self.refresh_button, self.search)
183 qtutils.connect_button(self.help_button, show_help)
184 qtutils.connect_button(self.close_button, self.close)
185 qtutils.add_close_action(self)
187 self.init_size(parent=parent)
189 def focus_tree(self):
190 self.tree.setFocus()
192 def focus_input(self):
193 self.input_txt.setFocus()
195 def search(self):
196 self.button_group.setEnabled(False)
197 self.refresh_button.setEnabled(False)
198 query = get(self.input_txt)
199 self.worker_thread.query = query
200 self.worker_thread.start()
202 def search_for(self, txt):
203 self.input_txt.set_value(txt)
204 self.focus_input()
206 def process_result(self, filenames):
207 self.tree.set_filenames(filenames, select=True)
208 self.refresh_button.setEnabled(True)
210 def edit(self):
211 context = self.context
212 paths = self.tree.selected_filenames()
213 cmds.do(cmds.Edit, context, paths, background_editor=True)
215 def open_default(self):
216 context = self.context
217 paths = self.tree.selected_filenames()
218 cmds.do(cmds.OpenDefaultApp, context, paths)
220 def tree_item_selection_changed(self):
221 enabled = bool(self.tree.selected_item())
222 self.button_group.setEnabled(enabled)