~ubuntu-branches/ubuntu/saucy/pida/saucy

« back to all changes in this revision

Viewing changes to pida/services/grepper.py

  • Committer: Bazaar Package Importer
  • Author(s): Barry deFreese
  • Date: 2006-08-01 13:08:56 UTC
  • mfrom: (0.1.2 etch) (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20060801130856-v92ktopgdxc8rv7q
Tags: 0.3.1-2ubuntu1
* Re-sync with Debian
* Remove bashisms from debian/rules

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*- 
 
2
 
 
3
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:
 
4
#Copyright (c) 2005 Ali Afshar aafshar@gmail.com
 
5
 
 
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:
 
12
 
 
13
#The above copyright notice and this permission notice shall be included in
 
14
#all copies or substantial portions of the Software.
 
15
 
 
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
 
22
#SOFTWARE.
 
23
 
 
24
 
 
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
 
31
 
 
32
#import pida.utils.pygrep as pygrep
 
33
 
 
34
import os
 
35
import re
 
36
import time
 
37
import gobject
 
38
import threading
 
39
import linecache
 
40
import cgi
 
41
import string
 
42
import gtk
 
43
 
 
44
import pida.core.actions as actions
 
45
 
 
46
types = service.types
 
47
defs = service.definitions
 
48
 
 
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)), "")
 
53
 
 
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>'
 
58
 
 
59
RESULT_MU = ('<span>'
 
60
                '<span color="#0000c0">%s</span>:'
 
61
                '<span weight="bold">%s</span>\n'
 
62
                '<tt>%s</tt>'
 
63
             '</span>')
 
64
 
 
65
RESULT_CONTEXT_MU = ('<span><tt>%s\n'
 
66
                     '<span weight="bold">%s</span>\n%s</tt></span>')
 
67
 
 
68
SMALL_MU = ('<span size="small" weight="bold">%s</span>')
 
69
 
 
70
class ResultsTreeItem(tree.TreeItem):
 
71
    
 
72
    def get_markup(self):
 
73
        return RESULT_MU % (self.value.linenumber,
 
74
                            cgi.escape(self.value.filename),
 
75
                            self.value.muline)
 
76
    markup = property(get_markup)
 
77
 
 
78
class ResultsTree(tree.Tree):
 
79
    pass
 
80
 
 
81
class GrepView(contentview.content_view):
 
82
 
 
83
    ICON_NAME = 'find'
 
84
    SHORT_TITLE = 'Find '
 
85
 
 
86
    LONG_TITLE = 'Find text in files.'
 
87
 
 
88
    def init(self):
 
89
        # Die RAD!
 
90
        hb = gtk.HBox()
 
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)
 
96
        l = gtk.Label()
 
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')
 
114
        hb = gtk.HBox()
 
115
        l = gtk.Label()
 
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)
 
128
        resbox = gtk.HBox()
 
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)
 
136
        hb = gtk.HBox()
 
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)
 
143
 
 
144
 
 
145
    def show_status(self, status):
 
146
        code, message = status
 
147
        if message is None:
 
148
            self.start()
 
149
            self.__status_bar.set_sensitive(True)
 
150
            self.__status_bar.pulse()
 
151
        else:
 
152
            self.stop()
 
153
            self.__status_bar.set_sensitive(False)
 
154
            #self.set_title(message)
 
155
 
 
156
    def set_details_expanded(self, expanded):
 
157
        self.__details_expander.set_expanded(expanded)
 
158
 
 
159
    def cb_result_selected(self, tree, result):
 
160
        #lines = self.boss.option('grepper', 'context-lines')
 
161
        lines = 4
 
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))
 
165
 
 
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)
 
170
 
 
171
    def cb_pattern_changed(self, entry):
 
172
        self.__start_but.set_sensitive(len(entry.get_text()) > 0)
 
173
 
 
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
 
181
        return options
 
182
 
 
183
    def from_options(self, options):
 
184
        self.__pattern_entry.set_text(options.pattern)
 
185
 
 
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
 
192
 
 
193
    def clear_results(self):
 
194
        self.__results_tree.clear()
 
195
 
 
196
    def add_result(self, result):
 
197
        self.__results_tree.add_item(ResultsTreeItem('', result))
 
198
 
 
199
    def start(self):
 
200
        self.__status_bar.show()
 
201
        self.__start_but.set_label(gtk.STOCK_STOP)
 
202
 
 
203
    def stop(self):
 
204
        self.__status_bar.hide()
 
205
        self.__start_but.set_label(gtk.STOCK_FIND)
 
206
 
 
207
    def cb_start_clicked(self, button):
 
208
        if button.get_label() == gtk.STOCK_STOP:
 
209
            self.service.grep_stop()
 
210
        else:
 
211
            self.service.grep_start()
 
212
 
 
213
    def cb_activated(self, entry):
 
214
        self.service.grep_start()
 
215
 
 
216
class Grepper(service.service):
 
217
 
 
218
    single_view_type = GrepView
 
219
    single_view_book = 'view'
 
220
 
 
221
    display_name = 'Grep Search'
 
222
 
 
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
 
228
            default = False
 
229
        class recursive_search(defs.option):
 
230
            """Whether the search will be recursive by default."""
 
231
            rtype = types.boolean
 
232
            default = True
 
233
        class ignore_version_control_directories(defs.option):
 
234
            """Whether version control directories will be ignored by default."""
 
235
            rtype = types.boolean
 
236
            default = True
 
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)
 
242
            default = 500
 
243
 
 
244
    def grep_start(self):
 
245
        opts = self.single_view.get_options()
 
246
        if not opts.pattern:
 
247
            return
 
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)
 
253
        self.__grep.run()
 
254
 
 
255
    def grep_stop(self):
 
256
        self.__grep.stop()
 
257
        self.single_view.stop()
 
258
 
 
259
    def cb_results_found(self, grep, result):
 
260
        self.single_view.add_result(result)
 
261
 
 
262
    def cb_results_status(self, grep, status):
 
263
        self.single_view.show_status(status)
 
264
            
 
265
 
 
266
    def cmd_find_interactive(self, directories=None, ignorevcs=None,
 
267
                             recursive=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')
 
273
            if proj is not None:
 
274
                options.directories = [proj.source_directory]
 
275
            else:
 
276
                options.directories = [os.getcwd()]
 
277
        else:
 
278
            options.directories = directories
 
279
        if ignorevcs is None:
 
280
            options.ignorevcs = self.opt(
 
281
                'default_options', 'ignore_version_control_directories')
 
282
        else:
 
283
            options.ignorevcs = ignorevcs
 
284
        if recursive is None:
 
285
            options.recursive = self.opt(
 
286
                'default_options', 'recursive_search')
 
287
        else:
 
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'))
 
293
 
 
294
    def cmd_find(self, path, pattern):
 
295
        pass
 
296
 
 
297
 
 
298
    def cb_search_clicked(self, button):
 
299
        self.cmd_find_interactive()
 
300
 
 
301
    def cb_view_action(self, view, name):
 
302
        if name == 'apply':
 
303
            self.grep()
 
304
        if name == 'stop':
 
305
            self.__grep.stop()
 
306
 
 
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')
 
313
 
 
314
    def get_menu_definition(self):
 
315
        return """
 
316
            <menubar>
 
317
            <menu name="base_edit" action="base_edit_menu">
 
318
                <placeholder name="SubEditSearchMenu">
 
319
                    <menuitem name="grepper" action="grepper+find" />
 
320
                </placeholder>
 
321
            </menu>
 
322
            </menubar>
 
323
                <toolbar>
 
324
                <placeholder name="OpenFileToolbar">
 
325
                </placeholder>
 
326
                <placeholder name="SaveFileToolbar">
 
327
                </placeholder>
 
328
                <placeholder name="EditToolbar">
 
329
                </placeholder>
 
330
                <placeholder name="ProjectToolbar">
 
331
                </placeholder>
 
332
                <placeholder name="VcToolbar">
 
333
                </placeholder>
 
334
                <placeholder name="ToolsToolbar">
 
335
            <separator />
 
336
            <toolitem  name="grepper" action="grepper+find" />
 
337
            <separator />
 
338
                </placeholder>
 
339
                </toolbar>
 
340
            """
 
341
    
 
342
    
 
343
BINARY_RE = re.compile(r'[\000-\010\013\014\016-\037\200-\377]')
 
344
class GrepOptions(object):
 
345
    files = []
 
346
    directories = []
 
347
    recursive = True
 
348
    pattern = ''
 
349
    ignorevcs = True
 
350
    ignoreglob = None
 
351
 
 
352
MATCH_MU = '<span color="#c00000">%s</span>'
 
353
 
 
354
class GrepResult(object):
 
355
 
 
356
    def __init__(self, linenumber, filename, line, matches):
 
357
        self.linenumber = linenumber
 
358
        self.filename = filename
 
359
        self.line = line = line.rstrip()
 
360
        muline = ''
 
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))
 
366
 
 
367
    def get_context(self, lines=2):
 
368
        pre = []
 
369
        post = []
 
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))
 
376
        pre = '\n'.join(pre)
 
377
        post = '\n'.join(post)
 
378
        return pre, '%s\t%s' % (self.linenumber, self.line), post
 
379
 
 
380
    def get_markup(self):
 
381
        return RESULT_MU % (self.linenumber,
 
382
                            cgi.escape(self.filename),
 
383
                            self.muline)
 
384
    markup = property(get_markup)
 
385
 
 
386
class PidaGrep(gobject.GObject):
 
387
    
 
388
    __gsignals__ = {'found' : (
 
389
                        gobject.SIGNAL_RUN_LAST,
 
390
                        gobject.TYPE_NONE,
 
391
                        (gobject.TYPE_PYOBJECT,)),
 
392
                    'status' : (
 
393
                        gobject.SIGNAL_RUN_LAST,
 
394
                        gobject.TYPE_NONE,
 
395
                        (gobject.TYPE_PYOBJECT,))}
 
396
 
 
397
    def __init__(self, options):
 
398
        gobject.GObject.__init__(self)
 
399
        self.__options = options
 
400
        self.__pattern = re.compile(options.pattern)
 
401
        self.__t = None
 
402
        self.__running = False
 
403
 
 
404
    def run(self):
 
405
        self.__t = threading.Thread(target = self.__run)
 
406
        self.__t.start()
 
407
        self.__running = True
 
408
 
 
409
    def get_files(self):
 
410
        for directory in self.__options.directories:
 
411
            for dirname, dirnames, filenames in os.walk(directory):
 
412
                if not self.__options.recursive:
 
413
                    dirnames[0:] = []
 
414
                else:
 
415
                    if self.__options.ignorevcs:
 
416
                        for d in dirnames:
 
417
                            if d in ['.svn', 'CVS', '_darcs']:
 
418
                                dirnames.remove(d)
 
419
                for filename in filenames:
 
420
                    filepath = os.path.join(dirname, filename)
 
421
                    yield filepath
 
422
        for filepath in self.__options.files:
 
423
                yield filepath
 
424
 
 
425
    def __run(self):
 
426
        try:
 
427
            self.__find()
 
428
        except StopIteration:
 
429
            return
 
430
 
 
431
    def __find(self):
 
432
        self.status(1, "searching")
 
433
        self.__nfound = 0
 
434
        for i, filename in enumerate(self.get_files()):
 
435
            if i % 16 == 0:
 
436
                self.status(self.__nfound)
 
437
            try:
 
438
                f = open(filename, 'r')
 
439
            except IOError:
 
440
                continue
 
441
            for linenumber, line in enumerate(f):
 
442
                if not self.__running:
 
443
                    self.__finished()
 
444
                if BINARY_RE.match(line):
 
445
                    break
 
446
                line = string.translate(line, all_chars, hi_bit_chars)
 
447
                line = string.translate(line, hi_lo_table)
 
448
                matches = self.__pattern.findall(line)
 
449
                if len(matches):
 
450
                    self.__nfound = self.__nfound + len(matches)
 
451
                    if self.__nfound >= self.__options.maxresults:
 
452
                        self.__finished()
 
453
                    result = GrepResult(linenumber, filename, line, matches)
 
454
                    gtk.threads_enter()
 
455
                    self.emit('found', result)
 
456
                    gtk.threads_leave()
 
457
            f.close()
 
458
        self.__finished()
 
459
 
 
460
    def status(self, code, message=None):
 
461
        gtk.threads_enter()
 
462
        self.emit('status', (code, message))
 
463
        gtk.threads_leave()
 
464
 
 
465
    def stop(self):
 
466
        self.__running = False
 
467
 
 
468
    def __finished(self):
 
469
        self.status(1, '%s found' % self.__nfound)
 
470
        raise StopIteration
 
471
 
 
472
gobject.type_register(PidaGrep)
 
473
 
 
474
Service = Grepper