1 """Provides the SearchView class."""
6 from PyQt4
import QtGui
7 from PyQt4
import QtCore
8 from PyQt4
.QtCore
import SIGNAL
10 from cola
import gitcmds
11 from cola
import utils
12 from cola
import qtutils
13 from cola
.git
import git
14 from cola
.qt
import create_toolbutton
15 from cola
.qtutils
import connect_button
16 from cola
.qtutils
import dir_icon
17 from cola
.views
import standard
18 from cola
.widgets
import defs
19 from cola
.widgets
.diff
import DiffTextEdit
23 return '%04d-%02d-%02d' % time
.localtime(timespec
)[:3]
26 class SearchOptions(object):
34 class SearchWidget(standard
.Dialog
):
35 def __init__(self
, parent
):
36 super(SearchWidget
, self
).__init
__(parent
)
37 self
.setWindowTitle(self
.tr('Search'))
39 self
.mode_combo
= QtGui
.QComboBox()
40 self
.browse_button
= create_toolbutton(icon
=dir_icon(),
42 self
.query
= QtGui
.QLineEdit()
44 self
.start_date
= QtGui
.QDateEdit()
45 self
.start_date
.setCurrentSection(QtGui
.QDateTimeEdit
.YearSection
)
46 self
.start_date
.setCalendarPopup(True)
47 self
.start_date
.setDisplayFormat(self
.tr('yyyy-MM-dd'))
49 self
.end_date
= QtGui
.QDateEdit()
50 self
.end_date
.setCurrentSection(QtGui
.QDateTimeEdit
.YearSection
)
51 self
.end_date
.setCalendarPopup(True)
52 self
.end_date
.setDisplayFormat(self
.tr('yyyy-MM-dd'))
54 self
.search_button
= QtGui
.QPushButton()
55 self
.search_button
.setText(self
.tr('Search'))
56 self
.search_button
.setDefault(True)
58 self
.max_count
= QtGui
.QSpinBox()
59 self
.max_count
.setMinimum(5)
60 self
.max_count
.setMaximum(9995)
61 self
.max_count
.setSingleStep(5)
62 self
.max_count
.setValue(500)
64 self
.commit_list
= QtGui
.QListWidget()
65 self
.commit_list
.setMinimumSize(QtCore
.QSize(1, 1))
66 self
.commit_list
.setAlternatingRowColors(True)
67 self
.commit_list
.setSelectionMode(QtGui
.QAbstractItemView
.SingleSelection
)
69 self
.commit_text
= DiffTextEdit(self
, whitespace
=False)
70 self
.commit_text
.setTabChangesFocus(True)
71 self
.commit_text
.setReadOnly(True)
73 self
.button_export
= QtGui
.QPushButton()
74 self
.button_export
.setText(self
.tr('Export Patches'))
76 self
.button_cherrypick
= QtGui
.QPushButton()
77 self
.button_cherrypick
.setText(self
.tr('Cherry Pick'))
79 self
.button_close
= QtGui
.QPushButton()
80 self
.button_close
.setText(self
.tr('Close'))
82 self
.top_layout
= QtGui
.QHBoxLayout()
83 self
.top_layout
.setMargin(0)
84 self
.top_layout
.setSpacing(defs
.button_spacing
)
86 self
.top_layout
.addWidget(self
.browse_button
)
87 self
.top_layout
.addWidget(self
.query
)
88 self
.top_layout
.addWidget(self
.start_date
)
89 self
.top_layout
.addWidget(self
.end_date
)
90 self
.top_layout
.addWidget(self
.search_button
)
91 self
.top_layout
.addStretch()
92 self
.top_layout
.addWidget(self
.mode_combo
)
93 self
.top_layout
.addWidget(self
.max_count
)
95 self
.splitter
= QtGui
.QSplitter()
96 self
.splitter
.setHandleWidth(defs
.handle_width
)
97 self
.splitter
.setOrientation(QtCore
.Qt
.Vertical
)
98 self
.splitter
.setChildrenCollapsible(True)
99 self
.splitter
.addWidget(self
.commit_list
)
100 self
.splitter
.addWidget(self
.commit_text
)
102 self
.bottom_layout
= QtGui
.QHBoxLayout()
103 self
.bottom_layout
.setMargin(0)
104 self
.bottom_layout
.setSpacing(defs
.spacing
)
105 self
.bottom_layout
.addWidget(self
.button_export
)
106 self
.bottom_layout
.addWidget(self
.button_cherrypick
)
107 self
.bottom_layout
.addStretch()
108 self
.bottom_layout
.addWidget(self
.button_close
)
110 self
.main_layout
= QtGui
.QVBoxLayout()
111 self
.main_layout
.setMargin(defs
.margin
)
112 self
.main_layout
.setSpacing(defs
.spacing
)
113 self
.main_layout
.addLayout(self
.top_layout
)
114 self
.main_layout
.addWidget(self
.splitter
)
115 self
.main_layout
.addLayout(self
.bottom_layout
)
116 self
.setLayout(self
.main_layout
)
118 self
.resize(self
.parent().width(), self
.parent().height())
122 """Return a callback to handle various search actions."""
123 search_commits(qtutils
.active_window())
126 class SearchEngine(object):
127 def __init__(self
, model
):
131 max_count
= self
.model
.max_count
134 'max-count': max_count
,
135 'pretty': 'format:%H %aN - %s - %ar',
138 def common_args(self
):
139 return (self
.model
.query
, self
.rev_args())
142 if not self
.validate():
144 return self
.results()
147 return len(self
.model
.query
) > 1
149 def revisions(self
, *args
, **kwargs
):
150 revlist
= git
.log(*args
, **kwargs
)
151 return gitcmds
.parse_rev_list(revlist
)
156 class RevisionSearch(SearchEngine
):
158 query
, opts
= self
.common_args()
159 args
= utils
.shell_split(query
)
160 return self
.revisions(all
=True, *args
, **opts
)
163 class PathSearch(SearchEngine
):
165 query
, args
= self
.common_args()
166 paths
= ['--'] + utils
.shell_split(query
)
167 return self
.revisions(all
=True, *paths
, **args
)
170 class MessageSearch(SearchEngine
):
172 query
, kwargs
= self
.common_args()
173 return self
.revisions(all
=True, grep
=query
, **kwargs
)
176 class AuthorSearch(SearchEngine
):
178 query
, kwargs
= self
.common_args()
179 return self
.revisions(all
=True, author
=query
, **kwargs
)
182 class CommitterSearch(SearchEngine
):
184 query
, kwargs
= self
.common_args()
185 return self
.revisions(all
=True, committer
=query
, **kwargs
)
188 class DiffSearch(SearchEngine
):
190 query
, kwargs
= self
.common_args()
191 return gitcmds
.parse_rev_list(
192 git
.log('-S'+query
, all
=True, **kwargs
))
195 class DateRangeSearch(SearchEngine
):
197 return self
.model
.start_date
< self
.model
.end_date
200 kwargs
= self
.rev_args()
201 start_date
= self
.model
.start_date
202 end_date
= self
.model
.end_date
203 return self
.revisions(date
='iso',
210 class Search(SearchWidget
):
211 EXPR
= 'Search by Expression'
212 PATH
= 'Search by Path'
213 MESSAGE
= 'Search Commit Messages'
214 DIFF
= 'Search Diffs'
215 AUTHOR
= 'Search Authors'
216 COMMITTER
= 'Search Committers'
217 DATE_RANGE
= 'Search Date Range'
219 def __init__(self
, model
, parent
):
220 super(Search
, self
).__init
__(parent
)
223 # Each search type is handled by a distinct SearchEngine subclass
225 self
.EXPR
: RevisionSearch
,
226 self
.PATH
: PathSearch
,
227 self
.MESSAGE
: MessageSearch
,
228 self
.DIFF
: DiffSearch
,
229 self
.AUTHOR
: AuthorSearch
,
230 self
.COMMITTER
: CommitterSearch
,
231 self
.DATE_RANGE
: DateRangeSearch
,
234 self
.modes
= (self
.EXPR
, self
.PATH
, self
.DATE_RANGE
,
235 self
.DIFF
, self
.MESSAGE
, self
.AUTHOR
, self
.COMMITTER
)
236 self
.mode_combo
.addItems(self
.modes
)
238 connect_button(self
.search_button
, self
.search_callback
)
239 connect_button(self
.browse_button
, self
.browse_callback
)
240 connect_button(self
.button_export
, self
.export_patch
)
241 connect_button(self
.button_cherrypick
, self
.cherry_pick
)
242 connect_button(self
.button_close
, self
.accept
)
244 self
.connect(self
.mode_combo
, SIGNAL('currentIndexChanged(int)'),
245 self
.mode_index_changed
)
247 self
.connect(self
.commit_list
,
248 SIGNAL('itemSelectionChanged()'),
251 self
.set_start_date(mkdate(time
.time()-(87640*31)))
252 self
.set_end_date(mkdate(time
.time()+87640))
253 self
.set_mode(self
.EXPR
)
255 self
.query
.setFocus()
257 def mode_index_changed(self
, idx
):
259 self
.update_shown_widgets(mode
)
260 if mode
== self
.PATH
:
261 self
.browse_callback()
263 def set_commit_list(self
, commits
):
264 widget
= self
.commit_list
266 widget
.addItems(commits
)
268 def set_start_date(self
, datestr
):
269 self
.set_date(self
.start_date
, datestr
)
271 def set_end_date(self
, datestr
):
272 self
.set_date(self
.end_date
, datestr
)
274 def set_date(self
, widget
, datestr
):
275 fmt
= QtCore
.Qt
.ISODate
276 date
= QtCore
.QDate
.fromString(datestr
, fmt
)
280 def set_mode(self
, mode
):
281 idx
= self
.modes
.index(mode
)
282 self
.mode_combo
.setCurrentIndex(idx
)
283 self
.update_shown_widgets(mode
)
285 def update_shown_widgets(self
, mode
):
286 date_shown
= mode
== self
.DATE_RANGE
287 browse_shown
= mode
== self
.PATH
288 self
.query
.setVisible(not date_shown
)
289 self
.browse_button
.setVisible(browse_shown
)
290 self
.start_date
.setVisible(date_shown
)
291 self
.end_date
.setVisible(date_shown
)
294 return str(self
.mode_combo
.currentText())
296 def search_callback(self
, *args
):
297 engineclass
= self
.engines
[self
.mode()]
298 self
.model
.query
= unicode(self
.query
.text())
299 self
.model
.max_count
= self
.max_count
.value()
301 fmt
= QtCore
.Qt
.ISODate
302 self
.model
.start_date
= str(self
.start_date
.date().toString(fmt
))
303 self
.model
.end_date
= str(self
.end_date
.date().toString(fmt
))
305 self
.results
= engineclass(self
.model
).search()
307 self
.display_results()
309 self
.commit_list
.clear()
310 self
.commit_text
.setText('')
312 def browse_callback(self
):
313 paths
= QtGui
.QFileDialog
.getOpenFileNames(self
,
314 self
.tr("Choose Path(s)"))
318 lenprefix
= len(os
.getcwd()) + 1
319 for path
in map(lambda x
: unicode(x
), paths
):
320 if not path
.startswith(os
.getcwd()):
322 filepaths
.append(path
[lenprefix
:])
323 query
= subprocess
.list2cmdline(filepaths
)
324 self
.query
.setText(query
)
326 self
.search_callback()
328 def display_results(self
):
329 commit_list
= map(lambda x
: x
[1], self
.results
)
330 self
.set_commit_list(commit_list
)
332 def display(self
, *args
):
333 widget
= self
.commit_list
334 row
, selected
= qtutils
.selected_row(widget
)
335 if not selected
or len(self
.results
) < row
:
336 self
.commit_text
.setText('')
338 revision
= self
.results
[row
][0]
339 qtutils
.set_clipboard(revision
)
340 diff
= gitcmds
.commit_diff(revision
)
341 self
.commit_text
.setText(diff
)
343 def export_patch(self
):
344 widget
= self
.commit_list
345 row
, selected
= qtutils
.selected_row(widget
)
346 if not selected
or len(self
.results
) < row
:
348 revision
= self
.results
[row
][0]
349 qtutils
.log(*self
.model
.export_patchset(revision
, revision
))
351 def cherry_pick(self
):
352 widget
= self
.commit_list
353 row
, selected
= qtutils
.selected_row(widget
)
354 if not selected
or len(self
.results
) < row
:
356 revision
= self
.results
[row
][0]
357 qtutils
.log(*git
.cherry_pick(revision
,
361 def search_commits(parent
):
362 opts
= SearchOptions()
363 widget
= Search(opts
, parent
)
368 if __name__
== "__main__":
370 app
= QtGui
.QApplication(sys
.argv
)
373 sys
.exit(app
.exec_())