1
# -*- coding: utf-8 -*-
3
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:
4
#Copyright (c) 2005 Ali Afshar aafshar@gmail.com
6
#Permission is hereby granted, free of charge, to any person obtaining a copy
7
#of this software and associated documentation files (the "Software"), to deal
8
#in the Software without restriction, including without limitation the rights
9
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
#copies of the Software, and to permit persons to whom the Software is
11
#furnished to do so, subject to the following conditions:
13
#The above copyright notice and this permission notice shall be included in
14
#all copies or substantial portions of the Software.
16
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
import pida.core.service as service
26
import pida.core.registry as registry
27
import pida.pidagtk.contentbook as contentbook
28
import pida.pidagtk.contentview as contentview
29
import pida.pidagtk.filedialogs as filedialogs
30
import pida.pidagtk.tree as tree
32
#import pida.utils.pygrep as pygrep
44
import pida.core.actions as actions
47
defs = service.definitions
49
table = range(0,128) * 2
50
hi_bit_chars = string.join(map(chr, range(0200,0400)),"")
51
hi_lo_table = string.join(map(chr, table), "")
52
all_chars = string.join(map(chr, range(0, 256)), "")
54
EXPANDER_LABEL_MU = '<span size="small">Details</span>'
55
RESULTS_LABEL_MU = '<span size="small">Results</span>'
56
DIR_ENTRY_MU = '<span>Search Path </span>'
57
CURRENT_ONLY_MU = '<span> Search only current buffer</span>'
60
'<span color="#0000c0">%s</span>:'
61
'<span weight="bold">%s</span>\n'
65
RESULT_CONTEXT_MU = ('<span><tt>%s\n'
66
'<span weight="bold">%s</span>\n%s</tt></span>')
68
SMALL_MU = ('<span size="small" weight="bold">%s</span>')
70
class ResultsTreeItem(tree.TreeItem):
73
return RESULT_MU % (self.value.linenumber,
74
cgi.escape(self.value.filename),
76
markup = property(get_markup)
78
class ResultsTree(tree.Tree):
81
class GrepView(contentview.content_view):
86
LONG_TITLE = 'Find text in files.'
91
self.widget.pack_start(hb, expand=False, padding=2)
92
self.__pattern_entry = gtk.Entry()
93
hb.pack_start(self.__pattern_entry)
94
self.__pattern_entry.connect('activate', self.cb_activated)
95
self.__pattern_entry.connect('changed', self.cb_pattern_changed)
97
l.set_markup(SMALL_MU % 'in')
98
hb.pack_start(l, expand=False)
99
self.__path_entry = filedialogs.FolderButton()
100
hb.pack_start(self.__path_entry)
101
self.__recursive = gtk.CheckButton('-R')
102
hb.pack_start(self.__recursive, expand=False)
103
#self.__stop_but = gtk.Button(stock=gtk.STOCK_STOP)
104
#hb.pack_start(self.__stop_but, expand=False)
105
#self.__stop_but.set_sensitive(False)
106
#self.__stop_but.connect('clicked', self.cb_stop_clicked)
107
self.__start_but = gtk.Button(label='gtk-find')
108
self.__start_but.set_use_stock(True)
109
hb.pack_start(self.__start_but, expand=False)
110
self.__start_but.connect('clicked', self.cb_start_clicked)
111
self.__start_but.set_sensitive(False)
112
#self.add_button('apply', 'apply', 'Start the search')
113
#self.add_button('stop', 'stop', 'Stop the search')
116
hb.pack_start(l, expand=True)
117
l.set_markup(EXPANDER_LABEL_MU)
118
self.__details_expander = gtk.Expander()
119
self.__details_expander.set_label_widget(hb)
120
self.widget.pack_start(self.__details_expander, expand=False)
121
details_box = gtk.Table(4, 3)
122
self.__details_expander.add(details_box)
123
details_box.set_col_spacings(4)
124
self.__dir_box = gtk.HBox()
125
details_box.attach(self.__dir_box, 1, 3, 0, 1)
126
self.__ignore_vcs = gtk.CheckButton("Ignore Version Control Directories")
127
details_box.attach(self.__ignore_vcs, 0, 1, 1, 2)
129
self.widget.pack_start(resbox)
130
butbox = gtk.VButtonBox()
131
resbox.pack_start(butbox, expand=False)
132
self.__results_tree = tree.Tree()
133
self.__results_tree.set_property('markup-format-string', '%(markup)s')
134
resbox.pack_start(self.__results_tree)
135
self.__results_tree.connect('clicked', self.cb_result_activated)
137
self.widget.pack_start(hb, expand=False)
138
self.__status_bar = gtk.ProgressBar()
139
self.__status_bar.set_size_request(-1, 6)
140
self.__status_bar.set_pulse_step(0.01)
141
hb.pack_start(self.__status_bar, padding=4)
142
self.__status_bar.set_no_show_all(True)
145
def show_status(self, status):
146
code, message = status
149
self.__status_bar.set_sensitive(True)
150
self.__status_bar.pulse()
153
self.__status_bar.set_sensitive(False)
154
#self.set_title(message)
156
def set_details_expanded(self, expanded):
157
self.__details_expander.set_expanded(expanded)
159
def cb_result_selected(self, tree, result):
160
#lines = self.boss.option('grepper', 'context-lines')
162
pre, match, post = [cgi.escape(s) for s in
163
result.value.get_context(lines)]
164
self.__context_label.set_markup(RESULT_CONTEXT_MU % (pre, match, post))
166
def cb_result_activated(self, tree, result):
167
self.service.boss.call_command('buffermanager', 'open_file_line',
168
filename=result.value.value.filename,
169
linenumber=result.value.value.linenumber + 1)
171
def cb_pattern_changed(self, entry):
172
self.__start_but.set_sensitive(len(entry.get_text()) > 0)
174
def get_options(self):
175
options = GrepOptions()
176
options.pattern = self.__pattern_entry.get_text()
177
options.recursive = self.__recursive.get_active()
178
options.directories = [self.__path_entry.get_filename()]
179
options.ignorevcs = self.__ignore_vcs.get_active()
180
options.maxresults = self.__maxresults
183
def from_options(self, options):
184
self.__pattern_entry.set_text(options.pattern)
186
if options.directories is not None and len(options.directories):
187
self.__path_entry.set_filename(options.directories[0])
188
self.__recursive.set_active(options.recursive)
189
self.__ignore_vcs.set_active(options.ignorevcs)
190
self.__pattern_entry.grab_focus()
191
self.__maxresults = options.maxresults
193
def clear_results(self):
194
self.__results_tree.clear()
196
def add_result(self, result):
197
self.__results_tree.add_item(ResultsTreeItem('', result))
200
self.__status_bar.show()
201
self.__start_but.set_label(gtk.STOCK_STOP)
204
self.__status_bar.hide()
205
self.__start_but.set_label(gtk.STOCK_FIND)
207
def cb_start_clicked(self, button):
208
if button.get_label() == gtk.STOCK_STOP:
209
self.service.grep_stop()
211
self.service.grep_start()
213
def cb_activated(self, entry):
214
self.service.grep_start()
216
class Grepper(service.service):
218
single_view_type = GrepView
219
single_view_book = 'view'
221
display_name = 'Grep Search'
223
class default_options(defs.optiongroup):
224
"""Options that the search will start with by default."""
225
class start_detailed(defs.option):
226
"""Whether the detailed search options will start expanded."""
227
rtype = types.boolean
229
class recursive_search(defs.option):
230
"""Whether the search will be recursive by default."""
231
rtype = types.boolean
233
class ignore_version_control_directories(defs.option):
234
"""Whether version control directories will be ignored by default."""
235
rtype = types.boolean
237
class results(defs.optiongroup):
238
"""Options relating to search results."""
239
class maximum_results(defs.option):
240
"""The maximum number of search results."""
241
rtype = types.intrange(5, 5000, 5)
244
def grep_start(self):
245
opts = self.single_view.get_options()
248
self.single_view.clear_results()
249
self.single_view.start()
250
self.__grep = PidaGrep(opts)
251
self.__grep.connect('found', self.cb_results_found)
252
self.__grep.connect('status', self.cb_results_status)
257
self.single_view.stop()
259
def cb_results_found(self, grep, result):
260
self.single_view.add_result(result)
262
def cb_results_status(self, grep, status):
263
self.single_view.show_status(status)
266
def cmd_find_interactive(self, directories=None, ignorevcs=None,
268
self.create_single_view()
269
options = GrepOptions()
270
if directories is None:
271
proj = self.boss.call_command('projectmanager',
272
'get_current_project')
274
options.directories = [proj.source_directory]
276
options.directories = [os.getcwd()]
278
options.directories = directories
279
if ignorevcs is None:
280
options.ignorevcs = self.opt(
281
'default_options', 'ignore_version_control_directories')
283
options.ignorevcs = ignorevcs
284
if recursive is None:
285
options.recursive = self.opt(
286
'default_options', 'recursive_search')
288
options.recursive = recursive
289
options.maxresults = self.opt('results', 'maximum_results')
290
self.single_view.from_options(options)
291
self.single_view.set_details_expanded(self.opt(
292
'default_options', 'start_detailed'))
294
def cmd_find(self, path, pattern):
298
def cb_search_clicked(self, button):
299
self.cmd_find_interactive()
301
def cb_view_action(self, view, name):
307
@actions.action(stock_id='gtk-searchtool',
308
label='Find in directory',
309
default_accel='<Control>slash')
310
def act_find(self, action):
311
"""Find text on a document or in a directory"""
312
self.call('find_interactive')
314
def get_menu_definition(self):
317
<menu name="base_edit" action="base_edit_menu">
318
<placeholder name="SubEditSearchMenu">
319
<menuitem name="grepper" action="grepper+find" />
324
<placeholder name="OpenFileToolbar">
326
<placeholder name="SaveFileToolbar">
328
<placeholder name="EditToolbar">
330
<placeholder name="ProjectToolbar">
332
<placeholder name="VcToolbar">
334
<placeholder name="ToolsToolbar">
336
<toolitem name="grepper" action="grepper+find" />
343
BINARY_RE = re.compile(r'[\000-\010\013\014\016-\037\200-\377]')
344
class GrepOptions(object):
352
MATCH_MU = '<span color="#c00000">%s</span>'
354
class GrepResult(object):
356
def __init__(self, linenumber, filename, line, matches):
357
self.linenumber = linenumber
358
self.filename = filename
359
self.line = line = line.rstrip()
361
for match in matches:
362
prematch, line = line.split(match, 1)
363
muline = '%s%s' % (muline, cgi.escape(prematch))
364
muline = '%s%s' % (muline, MATCH_MU % cgi.escape(match))
365
self.muline = '%s%s' % (muline, cgi.escape(line))
367
def get_context(self, lines=2):
370
for i in xrange(self.linenumber - lines, self.linenumber):
371
readline = linecache.getline(self.filename, i).rstrip()
372
pre.append('%s\t%s' % (i, readline))
373
for i in xrange(self.linenumber + 1, self.linenumber + lines + 1):
374
readline = linecache.getline(self.filename, i + 1).rstrip()
375
post.append('%s\t%s' % (i, readline))
377
post = '\n'.join(post)
378
return pre, '%s\t%s' % (self.linenumber, self.line), post
380
def get_markup(self):
381
return RESULT_MU % (self.linenumber,
382
cgi.escape(self.filename),
384
markup = property(get_markup)
386
class PidaGrep(gobject.GObject):
388
__gsignals__ = {'found' : (
389
gobject.SIGNAL_RUN_LAST,
391
(gobject.TYPE_PYOBJECT,)),
393
gobject.SIGNAL_RUN_LAST,
395
(gobject.TYPE_PYOBJECT,))}
397
def __init__(self, options):
398
gobject.GObject.__init__(self)
399
self.__options = options
400
self.__pattern = re.compile(options.pattern)
402
self.__running = False
405
self.__t = threading.Thread(target = self.__run)
407
self.__running = True
410
for directory in self.__options.directories:
411
for dirname, dirnames, filenames in os.walk(directory):
412
if not self.__options.recursive:
415
if self.__options.ignorevcs:
417
if d in ['.svn', 'CVS', '_darcs']:
419
for filename in filenames:
420
filepath = os.path.join(dirname, filename)
422
for filepath in self.__options.files:
428
except StopIteration:
432
self.status(1, "searching")
434
for i, filename in enumerate(self.get_files()):
436
self.status(self.__nfound)
438
f = open(filename, 'r')
441
for linenumber, line in enumerate(f):
442
if not self.__running:
444
if BINARY_RE.match(line):
446
line = string.translate(line, all_chars, hi_bit_chars)
447
line = string.translate(line, hi_lo_table)
448
matches = self.__pattern.findall(line)
450
self.__nfound = self.__nfound + len(matches)
451
if self.__nfound >= self.__options.maxresults:
453
result = GrepResult(linenumber, filename, line, matches)
455
self.emit('found', result)
460
def status(self, code, message=None):
462
self.emit('status', (code, message))
466
self.__running = False
468
def __finished(self):
469
self.status(1, '%s found' % self.__nfound)
472
gobject.type_register(PidaGrep)