grep: Add keyboard navigation
[git-cola.git] / cola / widgets / grep.py
blobf14087ee42d2888b70ca51b2ac33de890394622b
1 from PyQt4 import QtCore
2 from PyQt4 import QtGui
3 from PyQt4.QtCore import Qt
4 from PyQt4.QtCore import SIGNAL
6 from cola import core
7 from cola import guicmds
8 from cola import utils
9 from cola import qtutils
10 from cola.git import git
11 from cola.prefs import diff_font
12 from cola.widgets import defs
13 from cola.widgets.standard import Dialog
14 from cola.widgets.text import HintedTextView, HintedLineEdit
17 class GrepThread(QtCore.QThread):
18 def __init__(self, parent):
19 QtCore.QThread.__init__(self, parent)
20 self.txt = None
21 self.shell = False
23 def run(self):
24 if self.txt is None:
25 return
26 query = self.txt
28 if self.shell:
29 args = utils.shell_split(query)
30 else:
31 args = [query]
32 status, out = git.grep(with_status=True, with_stderr=True,
33 n=True, *args)
34 if query == self.txt:
35 self.emit(SIGNAL('result'), status, core.decode(out))
36 else:
37 self.run()
40 class Grep(Dialog):
41 def __init__(self, parent):
42 Dialog.__init__(self, parent)
43 self.setAttribute(Qt.WA_MacMetalStyle)
44 self.setWindowModality(Qt.WindowModal)
46 self.input_label = QtGui.QLabel('git grep')
47 self.input_label.setFont(diff_font())
49 hint = 'command-line arguments'
50 self.input_txt = GrepLineEdit(hint, self)
51 self.input_txt.enable_hint(True)
53 hint = 'grep result...'
54 self.result_txt = GrepTextView(hint, self)
55 self.result_txt.enable_hint(True)
57 self.edit_button = QtGui.QPushButton(self.tr('Edit'))
58 self.edit_button.setIcon(qtutils.open_file_icon())
59 self.edit_button.setEnabled(False)
60 self.edit_button.setShortcut(defs.editor_shortcut)
62 self.shell_checkbox = QtGui.QCheckBox(self.tr('Shell arguments'))
63 self.shell_checkbox.setToolTip(
64 'Parse arguments using a shell.\n'
65 'Queries with spaces will require "double quotes".')
66 self.shell_checkbox.setChecked(False)
68 self.close_button = QtGui.QPushButton(self.tr('Close'))
70 self.input_layout = QtGui.QHBoxLayout()
71 self.input_layout.setMargin(0)
72 self.input_layout.setSpacing(defs.button_spacing)
74 self.bottom_layout = QtGui.QHBoxLayout()
75 self.bottom_layout.setMargin(0)
76 self.bottom_layout.setSpacing(defs.button_spacing)
78 self.mainlayout = QtGui.QVBoxLayout()
79 self.mainlayout.setMargin(defs.margin)
80 self.mainlayout.setSpacing(defs.spacing)
82 self.input_layout.addWidget(self.input_label)
83 self.input_layout.addWidget(self.input_txt)
85 self.bottom_layout.addWidget(self.edit_button)
86 self.bottom_layout.addWidget(self.shell_checkbox)
87 self.bottom_layout.addStretch()
88 self.bottom_layout.addWidget(self.close_button)
90 self.mainlayout.addLayout(self.input_layout)
91 self.mainlayout.addWidget(self.result_txt)
92 self.mainlayout.addLayout(self.bottom_layout)
93 self.setLayout(self.mainlayout)
95 self.grep_thread = GrepThread(self)
97 self.connect(self.grep_thread, SIGNAL('result'),
98 self.process_result)
100 self.connect(self.input_txt, SIGNAL('textChanged(QString)'),
101 self.input_txt_changed)
103 self.connect(self.input_txt, SIGNAL('returnPressed()'),
104 lambda: self.result_txt.setFocus())
106 qtutils.connect_button(self.edit_button, self.edit)
107 qtutils.connect_button(self.close_button, self.close)
108 qtutils.add_close_action(self)
110 if not qtutils.apply_state(self):
111 self.resize(666, 420)
113 def done(self, exit_code):
114 qtutils.save_state(self)
115 return Dialog.done(self, exit_code)
117 def input_txt_changed(self, txt):
118 has_query = len(unicode(txt)) > 1
119 if has_query:
120 self.search()
122 def search(self):
123 self.edit_button.setEnabled(False)
125 self.grep_thread.txt = self.input_txt.as_unicode()
126 self.grep_thread.shell = self.shell_checkbox.isChecked()
127 self.grep_thread.start()
129 def search_for(self, txt):
130 self.input_txt.set_value(txt)
131 self.run()
133 def process_result(self, status, out):
134 self.result_txt.set_value(out)
135 self.edit_button.setEnabled(status == 0)
137 def edit(self):
138 guicmds.goto_grep(self.result_txt.selected_line()),
141 class GrepLineEdit(HintedLineEdit):
142 def __init__(self, hint, parent):
143 HintedLineEdit.__init__(self, hint, parent)
145 def keyPressEvent(self, event):
146 if event.key() in (Qt.Key_Return, Qt.Key_Enter):
147 self.emit(SIGNAL('returnPressed()'))
148 else:
149 HintedLineEdit.keyPressEvent(self, event)
152 class GrepTextView(HintedTextView):
153 def __init__(self, hint, parent):
154 HintedTextView.__init__(self, hint, parent)
155 self.goto_action = qtutils.add_action(
156 self, 'Launch Editor',
157 lambda: guicmds.goto_grep(self.selected_line()))
158 self.goto_action.setShortcut(defs.editor_shortcut)
160 qtutils.add_action(self, 'Up',
161 lambda: self.move(QtGui.QTextCursor.Up),
162 Qt.Key_K)
164 qtutils.add_action(self, 'Down',
165 lambda: self.move(QtGui.QTextCursor.Down),
166 Qt.Key_J)
168 qtutils.add_action(self, 'Left',
169 lambda: self.move(QtGui.QTextCursor.Left),
170 Qt.Key_H)
172 qtutils.add_action(self, 'Right',
173 lambda: self.move(QtGui.QTextCursor.Right),
174 Qt.Key_L)
176 qtutils.add_action(self, 'StartOfLine',
177 lambda: self.move(QtGui.QTextCursor.StartOfLine),
178 Qt.Key_0)
180 qtutils.add_action(self, 'EndOfLine',
181 lambda: self.move(QtGui.QTextCursor.EndOfLine),
182 Qt.Key_Dollar)
184 qtutils.add_action(self, 'WordLeft',
185 lambda: self.move(QtGui.QTextCursor.WordLeft),
186 Qt.Key_B)
188 qtutils.add_action(self, 'WordRight',
189 lambda: self.move(QtGui.QTextCursor.WordRight),
190 Qt.Key_W)
192 qtutils.add_action(self, 'PageUp',
193 lambda: self.page(-self.height()/2),
194 'Shift+Space')
196 qtutils.add_action(self, 'PageDown',
197 lambda: self.page(self.height()/2),
198 Qt.Key_Space)
200 def contextMenuEvent(self, event):
201 menu = self.createStandardContextMenu(event.pos())
202 menu.addSeparator()
203 menu.addAction(self.goto_action)
204 menu.exec_(self.mapToGlobal(event.pos()))
206 def page(self, offset):
207 rect = self.cursorRect()
208 x = rect.x()
209 y = rect.y() + offset
210 new_cursor = self.cursorForPosition(QtCore.QPoint(x, y))
211 if new_cursor is not None:
212 self.set_text_cursor(new_cursor)
214 def set_text_cursor(self, cursor):
215 self.setTextCursor(cursor)
216 self.ensureCursorVisible()
217 self.viewport().update()
219 def move(self, direction):
220 cursor = self.textCursor()
221 if cursor.movePosition(direction):
222 self.set_text_cursor(cursor)
224 def paintEvent(self, event):
225 HintedTextView.paintEvent(self, event)
226 if self.hasFocus():
227 # Qt doesn't redraw the cursor when using movePosition().
228 # So.. draw our own cursor.
229 rect = self.cursorRect()
230 painter = QtGui.QPainter(self.viewport())
231 painter.fillRect(rect, Qt.SolidPattern)
234 def run_grep(txt=None, parent=None):
235 widget = Grep(parent)
236 if txt is not None:
237 widget.search_for(txt)
238 return widget