1 from PyQt4
import QtCore
2 from PyQt4
import QtGui
3 from PyQt4
.QtCore
import Qt
4 from PyQt4
.QtCore
import SIGNAL
8 from cola
import qtutils
9 from cola
.cmds
import do
10 from cola
.git
import git
11 from cola
.i18n
import N_
12 from cola
.qtutils
import diff_font
13 from cola
.widgets
import defs
14 from cola
.widgets
.standard
import Dialog
15 from cola
.widgets
.text
import HintedTextView
, HintedLineEdit
19 """Prompt and use 'git grep' to find the content."""
20 widget
= new_grep(parent
=qtutils
.active_window())
26 def new_grep(text
=None, parent
=None):
27 widget
= Grep(parent
=parent
)
29 widget
.search_for(text
)
34 """Called when Search -> Grep's right-click 'goto' action."""
35 filename
, line_number
, contents
= line
.split(':', 2)
36 do(cmds
.Edit
, [filename
], line_number
=line_number
)
39 class GrepThread(QtCore
.QThread
):
41 def __init__(self
, parent
):
42 QtCore
.QThread
.__init
__(self
, parent
)
45 self
.regexp_mode
= '--basic-regexp'
48 if self
.query
is None:
52 args
= utils
.shell_split(query
)
55 status
, out
, err
= git
.grep(self
.regexp_mode
, n
=True, *args
)
56 if query
== self
.query
:
57 self
.emit(SIGNAL('result'), status
, out
, err
)
64 def __init__(self
, parent
=None):
65 Dialog
.__init
__(self
, parent
)
66 self
.setAttribute(Qt
.WA_MacMetalStyle
)
67 self
.setWindowTitle(N_('Search'))
68 if parent
is not None:
69 self
.setWindowModality(Qt
.WindowModal
)
71 self
.input_label
= QtGui
.QLabel('git grep')
72 self
.input_label
.setFont(diff_font())
74 self
.input_txt
= HintedLineEdit(N_('command-line arguments'), self
)
75 self
.input_txt
.enable_hint(True)
77 self
.regexp_combo
= combo
= QtGui
.QComboBox()
78 combo
.setToolTip(N_('Choose the "git grep" regular expression mode'))
79 items
= [N_('Basic Regexp'), N_('Extended Regexp'), N_('Fixed String')]
81 combo
.setCurrentIndex(0)
82 combo
.setEditable(False)
84 N_('Search using a POSIX basic regular expression'),
87 N_('Search using a POSIX extended regular expression'),
90 N_('Search for a fixed string'),
92 combo
.setItemData(0, '--basic-regexp', Qt
.UserRole
)
93 combo
.setItemData(1, '--extended-regexp', Qt
.UserRole
)
94 combo
.setItemData(2, '--fixed-strings', Qt
.UserRole
)
96 self
.result_txt
= GrepTextView(N_('grep result...'), self
)
97 self
.result_txt
.enable_hint(True)
99 self
.edit_button
= QtGui
.QPushButton(N_('Edit'))
100 self
.edit_button
.setIcon(qtutils
.open_file_icon())
101 self
.edit_button
.setEnabled(False)
102 self
.edit_button
.setShortcut(cmds
.Edit
.SHORTCUT
)
104 self
.refresh_button
= QtGui
.QPushButton(N_('Refresh'))
105 self
.refresh_button
.setIcon(qtutils
.reload_icon())
106 self
.refresh_button
.setShortcut(QtGui
.QKeySequence
.Refresh
)
108 self
.shell_checkbox
= QtGui
.QCheckBox(N_('Shell arguments'))
109 self
.shell_checkbox
.setToolTip(
110 N_('Parse arguments using a shell.\n'
111 'Queries with spaces will require "double quotes".'))
112 self
.shell_checkbox
.setChecked(False)
114 self
.close_button
= QtGui
.QPushButton(N_('Close'))
116 self
.input_layout
= QtGui
.QHBoxLayout()
117 self
.input_layout
.setMargin(0)
118 self
.input_layout
.setSpacing(defs
.button_spacing
)
120 self
.bottom_layout
= QtGui
.QHBoxLayout()
121 self
.bottom_layout
.setMargin(0)
122 self
.bottom_layout
.setSpacing(defs
.button_spacing
)
124 self
.mainlayout
= QtGui
.QVBoxLayout()
125 self
.mainlayout
.setMargin(defs
.margin
)
126 self
.mainlayout
.setSpacing(defs
.spacing
)
128 self
.input_layout
.addWidget(self
.input_label
)
129 self
.input_layout
.addWidget(self
.input_txt
)
130 self
.input_layout
.addWidget(self
.regexp_combo
)
132 self
.bottom_layout
.addWidget(self
.edit_button
)
133 self
.bottom_layout
.addWidget(self
.refresh_button
)
134 self
.bottom_layout
.addWidget(self
.shell_checkbox
)
135 self
.bottom_layout
.addStretch()
136 self
.bottom_layout
.addWidget(self
.close_button
)
138 self
.mainlayout
.addLayout(self
.input_layout
)
139 self
.mainlayout
.addWidget(self
.result_txt
)
140 self
.mainlayout
.addLayout(self
.bottom_layout
)
141 self
.setLayout(self
.mainlayout
)
143 self
.grep_thread
= GrepThread(self
)
145 self
.connect(self
.grep_thread
, SIGNAL('result'),
148 self
.connect(self
.input_txt
, SIGNAL('textChanged(QString)'),
149 lambda s
: self
.search())
151 self
.connect(self
.regexp_combo
, SIGNAL('currentIndexChanged(int)'),
152 lambda x
: self
.search())
154 self
.connect(self
.result_txt
, SIGNAL('leave()'),
155 lambda: self
.input_txt
.setFocus())
157 qtutils
.add_action(self
.input_txt
, 'FocusResults',
158 lambda: self
.result_txt
.setFocus(),
159 Qt
.Key_Down
, Qt
.Key_Enter
, Qt
.Key_Return
)
160 qtutils
.add_action(self
, 'FocusSearch',
161 lambda: self
.input_txt
.setFocus(),
163 qtutils
.connect_button(self
.edit_button
, self
.edit
)
164 qtutils
.connect_button(self
.refresh_button
, self
.search
)
165 qtutils
.connect_toggle(self
.shell_checkbox
, lambda x
: self
.search())
166 qtutils
.connect_button(self
.close_button
, self
.close
)
167 qtutils
.add_close_action(self
)
169 if not qtutils
.apply_state(self
):
170 self
.resize(666, 420)
172 def done(self
, exit_code
):
173 qtutils
.save_state(self
)
174 return Dialog
.done(self
, exit_code
)
176 def regexp_mode(self
):
177 idx
= self
.regexp_combo
.currentIndex()
178 data
= self
.regexp_combo
.itemData(idx
, Qt
.UserRole
).toPyObject()
182 self
.edit_button
.setEnabled(False)
183 self
.refresh_button
.setEnabled(False)
184 query
= self
.input_txt
.value()
186 self
.result_txt
.set_value('')
188 self
.grep_thread
.query
= query
189 self
.grep_thread
.shell
= self
.shell_checkbox
.isChecked()
190 self
.grep_thread
.regexp_mode
= self
.regexp_mode()
191 self
.grep_thread
.start()
193 def search_for(self
, txt
):
194 self
.input_txt
.set_value(txt
)
197 def text_scroll(self
):
198 scrollbar
= self
.result_txt
.verticalScrollBar()
200 return scrollbar
.value()
203 def set_text_scroll(self
, scroll
):
204 scrollbar
= self
.result_txt
.verticalScrollBar()
205 if scrollbar
and scroll
is not None:
206 scrollbar
.setValue(scroll
)
208 def text_offset(self
):
209 return self
.result_txt
.textCursor().position()
211 def set_text_offset(self
, offset
):
212 cursor
= self
.result_txt
.textCursor()
213 cursor
.setPosition(offset
)
214 self
.result_txt
.setTextCursor(cursor
)
216 def process_result(self
, status
, out
, err
):
221 value
= 'git grep: ' + out
+ err
225 # save scrollbar and text cursor
226 scroll
= self
.text_scroll()
227 offset
= min(len(value
), self
.text_offset())
229 self
.result_txt
.set_value(value
)
231 self
.set_text_scroll(scroll
)
232 self
.set_text_offset(offset
)
234 self
.edit_button
.setEnabled(status
== 0)
235 self
.refresh_button
.setEnabled(status
== 0)
238 goto_grep(self
.result_txt
.selected_line()),
241 class GrepTextView(HintedTextView
):
243 def __init__(self
, hint
, parent
):
244 HintedTextView
.__init
__(self
, hint
, parent
)
245 self
.goto_action
= qtutils
.add_action(self
, 'Launch Editor', self
.edit
)
246 self
.goto_action
.setShortcut(cmds
.Edit
.SHORTCUT
)
248 qtutils
.add_action(self
, 'Up',
249 lambda: self
.move(QtGui
.QTextCursor
.Up
),
252 qtutils
.add_action(self
, 'Down',
253 lambda: self
.move(QtGui
.QTextCursor
.Down
),
256 qtutils
.add_action(self
, 'Left',
257 lambda: self
.move(QtGui
.QTextCursor
.Left
),
260 qtutils
.add_action(self
, 'Right',
261 lambda: self
.move(QtGui
.QTextCursor
.Right
),
264 qtutils
.add_action(self
, 'StartOfLine',
265 lambda: self
.move(QtGui
.QTextCursor
.StartOfLine
),
268 qtutils
.add_action(self
, 'EndOfLine',
269 lambda: self
.move(QtGui
.QTextCursor
.EndOfLine
),
272 qtutils
.add_action(self
, 'WordLeft',
273 lambda: self
.move(QtGui
.QTextCursor
.WordLeft
),
276 qtutils
.add_action(self
, 'WordRight',
277 lambda: self
.move(QtGui
.QTextCursor
.WordRight
),
280 qtutils
.add_action(self
, 'PageUp',
281 lambda: self
.page(-self
.height()/2),
284 qtutils
.add_action(self
, 'PageDown',
285 lambda: self
.page(self
.height()/2),
288 def contextMenuEvent(self
, event
):
289 menu
= self
.createStandardContextMenu(event
.pos())
291 menu
.addAction(self
.goto_action
)
292 menu
.exec_(self
.mapToGlobal(event
.pos()))
295 goto_grep(self
.selected_line())
297 def page(self
, offset
):
298 rect
= self
.cursorRect()
300 y
= rect
.y() + offset
301 new_cursor
= self
.cursorForPosition(QtCore
.QPoint(x
, y
))
302 if new_cursor
is not None:
303 self
.set_text_cursor(new_cursor
)
305 def set_text_cursor(self
, cursor
):
306 self
.setTextCursor(cursor
)
307 self
.ensureCursorVisible()
308 self
.viewport().update()
310 def move(self
, direction
):
311 cursor
= self
.textCursor()
312 if cursor
.movePosition(direction
):
313 self
.set_text_cursor(cursor
)
315 def paintEvent(self
, event
):
316 HintedTextView
.paintEvent(self
, event
)
318 # Qt doesn't redraw the cursor when using movePosition().
319 # So.. draw our own cursor.
320 rect
= self
.cursorRect()
321 painter
= QtGui
.QPainter(self
.viewport())
322 painter
.fillRect(rect
, Qt
.SolidPattern
)
324 def keyPressEvent(self
, event
):
325 if event
.key() == Qt
.Key_Up
:
326 cursor
= self
.textCursor()
327 position
= cursor
.position()
328 if position
== 0 and not cursor
.hasSelection():
329 # The cursor is at the beginning of the line.
330 # If we have selection then simply reset the cursor.
331 # Otherwise, emit a signal so that the parent can
333 self
.emit(SIGNAL('leave()'))
334 return HintedTextView
.keyPressEvent(self
, event
)