"""
This is a test version of an alternate find in files ui.
"""

# Initial version for PyPE
# Based on work from Josiah Carlson and others.
# License GPL
# Further work by parameter@sourceforge (Dave Schuyler).

from wx import ImageFromStream, BitmapFromImage, Yield
from wx.stc import StyledTextCtrl
import cStringIO

import wx
import wx.lib.mixins.listctrl as listmix
import wx.lib.scrolledpanel as scrolled
import os
import re
import fnmatch
import posixpath
import time
import tokenize
import cStringIO
import compiler

newlines = re.compile(r'((\r\n)|(\r)|(\n))')
quotedPattern=re.compile(
    r'''((?:"(?:[^\\"]*(?:\\.[^\\"]*)*)["])|(?:'(?:[^\\']*(?:\\.[^\\']*)*)[']))''')
commentedPattern=re.compile(r"[^#]*#(.*)$")

def fixnewlines(string):
    return newlines.sub('\n', string)

def namematches(name, include, exclude):
    for extn in include:
        if fnmatch.fnmatch(name, extn):
            for i in exclude:
                if fnmatch.fnmatch(name, i):
                    return 0
            else:
                return 1
    return 0

columns = (
    (0, "", 0, 0),
    (1, "Path", 200, 0),
    (2, "Filename", 100, 0),
    (3, "L#", 40, wx.LIST_FORMAT_RIGHT),
    (4, "Item", 15, 0))

OP = tokenize.OP
NAME = tokenize.NAME
NTOK = tokenize.N_TOKENS
NEWL = tokenize.NEWLINE
include_re = re.compile('^#include\s+"([^"\\n\\r ]+)".*?$', re.MULTILINE)
PY = 0
C = 1

enable_options = ['Directories', 'Tags in Directories']

def find_imports(txt):
    if 'import' in txt:
        #probably a python file
        #this part runs around 450k/second on my 2.8 ghz P4, fast enough.
        imports = []
        parname = ''
        curname = ''
        ok = 0
        x = 0
        for typ, token, st, en, lin in tokenize.generate_tokens(cStringIO.StringIO(txt).readline):
            x += 1
            if x >= 100:
                x = 0
                Yield()
            if typ == NAME:
                if token == 'from':
                    parname = ''
                    curname = ''
                    ok = 1
                elif token == 'import':
                    parname = curname
                    curname = ''
                    ok = 1
                elif ok:
                    curname += token
            elif typ == OP:
                if token == '.':
                    curname += '.'
                elif token in '*,':
                    if parname and curname:
                        imports.append(parname + '.' + curname)
                    elif token == '*' and parname:
                        imports.append(parname)
                    elif curname:
                        imports.append(curname)
                    curname = ''
            elif typ == NEWL or typ >= NTOK:
                if parname and curname:
                    imports.append(parname + '.' + curname)
                elif curname:
                    imports.append(curname)
                parname = ''
                curname = ''
                ok = 0
        return PY, [i for i in imports if len(i) != i.count('.')]
    elif '#include' in txt:
        #probably a c/c++ file
        return C, [i for i in include_re.findall(txt)]
    return -1, []

#-----------------------------------------------------------------------------

class FoundTable(wx.ListCtrl, listmix.ColumnSorterMixin, listmix.ListCtrlAutoWidthMixin):
    def __init__(self, parent, columns = columns):
        wx.ListCtrl.__init__(
            self, parent, -1,
            style=wx.LC_REPORT|wx.LC_VIRTUAL|wx.LC_HRULES|wx.LC_VRULES)
        self.parent = parent
        self.c = columns
        
        self.imageList = wx.ImageList(16, 16)
        self.sm_up = self.imageList.Add(getSmallUpArrowBitmap())
        self.sm_dn = self.imageList.Add(getSmallDnArrowBitmap())
        self.SetImageList(self.imageList, wx.IMAGE_LIST_SMALL)

        for i in columns:
            if 0:
                info = wx.ListItem()
                info.SetMask(
                    wx.LIST_MASK_TEXT|wx.LIST_MASK_IMAGE|wx.LIST_MASK_FORMAT)
                info.SetImage(-1)
                info.SetText(i[1])
                info.SetAlign(i[3])
                self.InsertColumnInfo(i[0], info)
            else:
                self.InsertColumn(i[0], i[1], i[3])
            self.SetColumnWidth(i[0], i[2])

        self.data=[]
        self.cache=[]
        listmix.ColumnSorterMixin.__init__(self, len(columns))
        listmix.ListCtrlAutoWidthMixin.__init__(self)
        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected)
        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated)
        #self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected)
        self.pattern = None

    def setData(self, arrayOfTuples, copy=1):
        if copy:
            self.data=arrayOfTuples[:]
        else:
            self.data=arrayOfTuples
        if self.c is columns:
            self.cache = [os.path.split(i[0]) for i in self.data]
        self.SetItemCount(len(arrayOfTuples))
        self.Refresh()

    def Clear(self):
        self.data=[]
        self.cache=[]
        self.SetItemCount(0)
        self.DeleteAllItems()
        self.Refresh()
    
    def AppendEntry(self, tuple):
        """
        ((filename, line, line contents, extra), *)
        """
        self.data.append(tuple)
        if self.c is columns:
            self.cache.append(os.path.split(tuple[0]))
        self.SetItemCount(len(self.data))
    
    def ExtendEntries(self, array):
        self.data.extend(array)
        if self.c is columns:
            self.cache.extend([os.path.split(i[0]) for i in array])
        self.SetItemCount(len(self.data))

    # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py
    def GetListCtrl(self):
        return self
    
    # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py
    def GetSortImages(self):
        return (self.sm_dn, self.sm_up)
    
    def GetColumnSorter(self):
        # Override listctrl mixin default to do nothing.
        pass
    
    def SortItems(self, *args, **kwargs):
        # Override listctrl mixin
        col=self._col
        ascending = self._colSortFlag[col]
        col -= 1
        
        if col in (0,1) and self.c is columns:
            ops = os.path.split
            fcn = lambda a:ops(a[0])[col]
        else:
            fcn = lambda a:a[col]
        
        def cmpf(a,b):
            return cmp(fcn(a), fcn(b))
            
        if ascending:
            self.data.sort(cmpf)
            if self.c is columns:
                self.cache = [os.path.split(i[0]) for i in self.data]
        else:
            self.data.reverse()
            self.cache.reverse()
        
        self.Refresh()

    def OnItemSelected(self, event):
        self.currentItem = event.m_itemIndex

    def OnItemActivated(self, event):
        self.currentItem = event.m_itemIndex
        path = self.GetItem(self.currentItem, 1).GetText()
        file = self.GetItem(self.currentItem, 2).GetText()
        line = self.GetItem(self.currentItem, 3).GetText()
        data = self.GetItem(self.currentItem, 4).GetText()
        self.parent.OpenFound(os.path.join(path, file), int(line), self.pattern, ('\\n' in data) and len(data.encode('utf-8')))

    def OnGetItemText(self, item, col):
        if col == 0:
            return ''
        if col in (1,2):
            return self.cache[item][col-1]
        return "%s" % (self.data[item][col-2],)

#-----------------------------------------------------------------------------

class FoundText(wx.ListBox):
    def __init__(self, parent):
        wx.ListBox.__init__(
            self, parent, -1,
            style=wx.LB_SINGLE|wx.LB_NEEDED_SB)
        self.parent = parent
        wx.EVT_LISTBOX_DCLICK(parent, self.GetId(), self.OnItemActivated)
        self.last = ''
        self.pattern = None

    def setData(self, arrayOfTuples):
        self.Clear()
        self.ExtendEntries(arrayOfTuples)
    
    def AppendEntry(self, tuple):
        #tuple is (filename, line, line contents, extra)
        if tuple[0] != self.last:
            #set text color
            self.Append(tuple[0])
            self.last = tuple[0]
        self.Append("  %s: %s"%tuple[1:3])
    
    def ExtendEntries(self, array):
        a = []
        for tuple in array:
            if tuple[0] != self.last:
                #set text color
                a.append(tuple[0])
                self.last = tuple[0]
            a.append("  %s: %s"%tuple[1:3])
        
        self.InsertItems(a, self.GetCount())

    def OnItemActivated(self, event):
        selected = self.GetSelection()
        if selected < 0:
            return
        cur = selected
        while (cur > 0) and (self.GetString(cur)[0] == ' '):
            cur -= 1
        file = self.GetString(cur)
        line = 1
        a = self.GetString(selected) 
        if a[0] == ' ':
            line = int(a.split(':', 1)[0])
        self.parent.OpenFound(file, int(line), self.pattern, ('\\n' in a) and len(a.encode('utf-8')))

#---------------------------------------------------------------------------

class FoundTree(wx.TreeCtrl):
    def __init__(self, parent):
        wx.TreeCtrl.__init__(self, parent,
                             style=wx.TR_HAS_BUTTONS | wx.TR_HIDE_ROOT | wx.TR_NO_LINES)
        self.parent = parent

        isz = (16,16)
        il = wx.ImageList(isz[0], isz[1])
        self.img0 = fldridx     = il.Add(wx.ArtProvider_GetBitmap(wx.ART_FOLDER,      wx.ART_OTHER, isz))
        self.img1 = fldropenidx = il.Add(wx.ArtProvider_GetBitmap(wx.ART_FILE_OPEN,   wx.ART_OTHER, isz))
        i = wx.EmptyImage(*isz)
        i.SetData(isz[0]*isz[1]*3*'\xff')
        self.img2 = fileidx     = il.Add(wx.BitmapFromImage(i))

        self.SetImageList(il)
        self.il = il

        self.root = self.AddRoot("The hidden root item")
        self.SetItemImage(self.root, fldridx, wx.TreeItemIcon_Normal)
        self.SetItemImage(self.root, fldropenidx, wx.TreeItemIcon_Expanded)

        self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnActivate, self)
        
        self.last = ''
        self.lasti = None
        self.pattern = None
    
    def setData(self, arrayOfTuples):
        self.Clear()
        self.ExtendEntries(arrayOfTuples)
    
    def Clear(self):
        self.DeleteAllItems()
    
    def AppendEntry(self, tuple):
        if tuple[0] != self.last:
            self.last = tuple[0]
            self.lasti = self.AppendItem(self.root, self.last)
            self.SetItemImage(self.lasti, self.img0, wx.TreeItemIcon_Normal)
            self.SetItemImage(self.lasti, self.img1, wx.TreeItemIcon_Expanded)
            self.Expand(self.lasti)
            #set color of item
        it = self.AppendItem(self.lasti, " %s: %s"%(tuple[1:3]))
        self.SetItemImage(it, self.img2, wx.TreeItemIcon_Normal)
    
    def ExtendEntries(self, arrayOfTuples):
        for i in arrayOfTuples:
            self.AppendEntry(i)

    def OnActivate(self, event):
        item = event.GetItem()
        parent = self.GetItemParent(item)
        
        if parent == self.root:
            event.Skip()
            return
        file = self.GetItemText(parent)
        data = self.GetItemText(item)
        line = int(data.split(':')[0].strip())
        self.parent.OpenFound(file, line, self.pattern, ('\\n' in data) and len(data.encode('utf-8')))

#-----------------------------------------------------------------------------

viewoptions = {'table': FoundTable,
               'tree' : FoundTree,
               'text' : FoundText}

class FindInFiles(wx.Panel):
    def __init__(self, parent, root):
        wx.Panel.__init__(self, parent, -1)
        
        self.found = []
        self.parent = parent
        self.root = root

        self.running = 0
        self.stopping = 0
        self.starting = 0

        winids = []

        controlsWindow = wx.SashLayoutWindow(
            self, -1, wx.DefaultPosition, (200, 30),
            wx.NO_BORDER|wx.SW_3D)
        controlsWindow.SetOrientation(wx.LAYOUT_VERTICAL)
        controlsWindow.SetAlignment(wx.LAYOUT_LEFT)
        controlsWindow.SetSashVisible(wx.SASH_RIGHT, True)
        self.controlsWindow = controlsWindow
        winids.append(controlsWindow.GetId())

        fc = self.readPreferences()
        self.scope = fc['scope']

        self.viewResultsAs = fc['view']
        
        self.resultsWindow = viewoptions.get(fc['view'], FoundText)(self)
            
        winids.append(self.resultsWindow.GetId())

        self.Bind(
            wx.EVT_SASH_DRAGGED_RANGE, self.OnSashDrag,
            id=min(winids), id2=max(winids)
            )

        self.Bind(wx.EVT_SIZE, self.OnSize)

        self.scrolledPanel = scrolled.ScrolledPanel(controlsWindow, -1,
            style=wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
        SLF = self.scrolledPanel

        def static(text, style=wx.ALIGN_LEFT):
            return wx.StaticText(SLF, -1, text, style=style)

        def checkb(text, val=0, style=wx.NO_BORDER):
            a = wx.CheckBox(SLF, -1, text, style=style)
            a.SetValue(val)
            return a

        def combobox(ch, d=''):
            a = wx.ComboBox(SLF, -1, choices=ch, style=wx.CB_DROPDOWN,
                size=(100, 1))
            if ch: a.SetSelection(0)
            elif d: a.SetValue(d)
            return a

        def button(name, fcn):
            id = wx.NewId()
            a = wx.Button(SLF, id, name)
            wx.EVT_BUTTON(SLF, id, fcn)
            return a

        #------------------------------

        gbs = self.gbs = wx.GridBagSizer(5, 5)

        outsideBorder=5
        lastColumn=4
        editBoxSpan=(1, lastColumn-1)
        allSpan=(1, lastColumn+1)
        row=0

        gbs.Add(static("Search for:"), (row,0),
                flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.LEFT|wx.TOP,
                border=outsideBorder)
        self.search = combobox(fc['search'])
        gbs.Add(self.search, (row,1), editBoxSpan,
                flag=wx.EXPAND|wx.TOP,
                border=outsideBorder)
        self.b=button("Start Search", self.OnFindButtonClick)
        self.b.SetDefault()
        gbs.Add(self.b, (row,lastColumn),
            flag=wx.ALIGN_LEFT|wx.RIGHT|wx.TOP,
            border=outsideBorder)

        row+=1
        gbs.Add(static("Within:"), (row,0),
            flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.LEFT,
            border=outsideBorder)
        scopes = [
            'Selected Text',
            'Current File',
            'Current File with Includes',
            '',
            'Open Files',
            #'Project',   #perhaps if/when PyPE has projects
            #'Workspace', #perhaps for PyPE 2.1
            'Directories',
            'Tags in Directories',
            #'',
            #'Dictionary', #yikes, requires shipping a dictionary or the internet requirements.
            #'Internet',   #yikes, requires shipping urllib, a parser, etc.
            ]
        self.scopeChoice = wx.Choice(self.scrolledPanel, -1,
            choices = scopes)
        try:
            self.scopeChoice.SetSelection(scopes.index(fc['scope']))
        except:
            self.scopeChoice.SetSelection('Directories')
        self.Bind(wx.EVT_CHOICE, self.OnScopeChoice, self.scopeChoice)
        gbs.Add(self.scopeChoice, (row,1), editBoxSpan,
                flag=wx.EXPAND)

        opt_sizer = wx.BoxSizer(wx.VERTICAL)
        self.cs = checkb("Case sensitive", fc['case'])
        self.re = checkb("Regular Expression", fc['regex'])
        self.multiline = checkb("Multiline", fc['multiline'])
        opt_sizer.Add(self.cs, flag=wx.ALIGN_LEFT|wx.TOP, border=2)
        opt_sizer.Add(self.re, flag=wx.ALIGN_LEFT|wx.TOP, border=2)
        opt_sizer.Add(self.multiline, flag=wx.ALIGN_LEFT|wx.TOP, border=2)
        opt_sizer.Layout()
        row+=1
        gbs.Add(opt_sizer, (row,1), (1,2),
            wx.RIGHT, outsideBorder)

        opt_sizer2 = wx.BoxSizer(wx.VERTICAL)
        self.ww = checkb("Whole Word", fc['whole_word'])
        self.quoted = checkb("Quoted", fc['quoted'])
        self.commented = checkb("Commented", fc['commented'])
        opt_sizer2.Add(self.ww, flag=wx.ALIGN_LEFT|wx.TOP, border=2)
        opt_sizer2.Add(self.quoted, flag=wx.ALIGN_LEFT|wx.TOP, border=2)
        opt_sizer2.Add(self.commented, flag=wx.ALIGN_LEFT|wx.TOP, border=2)
        opt_sizer2.Layout()
        #row+=1
        gbs.Add(opt_sizer2, (row,3), (1,2),
            wx.RIGHT, outsideBorder)

        row+=1
        self.line = wx.StaticLine(
            self.scrolledPanel, -1, style=wx.LI_HORIZONTAL)
        gbs.Add(self.line, (row,0), allSpan,
            wx.EXPAND|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 10)

        row+=1
        gbs.Add(
            static("Separate multiple directories and criteria with ; (semicolons)"),
            (row,1), (1,lastColumn), flag=wx.ALIGN_LEFT)

        row+=1
        gbs.Add(static("Directories:"), (row,0),
            flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.LEFT,
            border=outsideBorder)
        self.sdirs = combobox(fc['dirs'])
        gbs.Add(self.sdirs, (row,1), editBoxSpan, flag=wx.EXPAND)
        self.add_path = button("Add Path", self.OnDirButtonClick)
        gbs.Add(self.add_path, (row,lastColumn),
            flag=wx.ALIGN_LEFT|wx.RIGHT,
            border=outsideBorder)

        row+=1
        gbs.Add(static("Include:"), (row,0),
                flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.LEFT,
                border=outsideBorder)
        self.include = combobox(fc['include'])
        gbs.Add(self.include, (row,1), editBoxSpan,
                flag=wx.EXPAND)

        row+=1
        gbs.Add(static("Exclude:"), (row,0),
                flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.LEFT,
                border=outsideBorder)
        self.exclude = combobox(fc['exclude'])
        gbs.Add(self.exclude, (row,1), editBoxSpan,
                flag=wx.EXPAND)

        row+=1
        self.ss = checkb("Search Subdirectories", fc['search_sub_dirs'])
        gbs.Add(self.ss, (row,1), (1,lastColumn),
            flag=wx.ALIGN_LEFT|wx.BOTTOM,
            border=outsideBorder)

        row+=1
        self.line = wx.StaticLine(
            self.scrolledPanel, -1, style=wx.LI_HORIZONTAL)
        gbs.Add(self.line, (row,0), allSpan,
            wx.EXPAND|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 10)

        row+=1
        gbs.Add(static("View Results As:"), (row,0),
            flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.LEFT,
            border=outsideBorder)
        #views=['table', 'text', 'tree']
        views=['table', 'text', 'tree']
        self.viewChoice = wx.Choice(self.scrolledPanel, -1,
            choices = views)
        self.viewChoice.SetSelection(views.index(fc['view']))
        self.Bind(wx.EVT_CHOICE, self.OnViewChoice, self.viewChoice)
        gbs.Add(self.viewChoice, (row,1), editBoxSpan,
                flag=wx.EXPAND|wx.TOP,
                border=outsideBorder)
        
        self.enable_controls = [self.sdirs, self.add_path, self.ss]
        self.checkscope()
        #------------------------------

        #gbs.AddGrowableRow(0)
        gbs.AddGrowableCol(1)

        self.scrolledPanel.SetSizerAndFit(gbs)
        self.scrolledPanel.SetAutoLayout(True)
        self.scrolledPanel.SetupScrolling()

        cwSizer = wx.BoxSizer(wx.HORIZONTAL)
        cwSizer.Add(self.scrolledPanel, 1, wx.EXPAND)
        self.controlsWindow.SetSizerAndFit(cwSizer)
        self.controlsWindow.SetAutoLayout(True)
        self.controlsWindow.Layout()
        #HACK:
        minSize=[i+40 for i in self.controlsWindow.GetMinSize()]
        self.controlsWindow.SetDefaultSize(minSize)
        parent.SetClientSize(self.GetSize())

        #------------------------------

        self.SetAutoLayout(True)
        tid = wx.NewId()
        self.timer = wx.Timer(self, tid)
        wx.EVT_TIMER(self, tid, self.OnFindButtonClick)

    #-------------------------------------------------------------------------

    def getfn(self, win):
        if win.dirname and win.filename:
            return os.path.join(win.dirname, win.filename)
        else:
            return '<untitled %i>'%win.NEWDOCUMENT
    
    def OnScopeChoice(self, event):
        scope=event.GetString()
        ## print 'scope', scope
        if self.scope != scope:
            self.scope=scope
            self.savePreferences()
        
        self.checkscope()
    
    def checkscope(self):
        enable = self.scopeChoice.GetStringSelection() in enable_options
        for i in self.enable_controls:
            i.Enable(enable)

    def OnViewChoice(self, event):
        view=event.GetString()
        if self.viewResultsAs != view:
            self.viewResultsAs=view
            self.savePreferences()
            #toss old results window
            x = self.resultsWindow.pattern 
            self.resultsWindow.Destroy()
            #create new results window
            self.resultsWindow = viewoptions.get(view, FoundText)(self)
            self.resultsWindow.pattern = x
            self.Bind(wx.EVT_SASH_DRAGGED_RANGE, self.OnSashDrag, self.resultsWindow)
            #populate new results window
            self.resultsWindow.setData(self.found)
            self.OnSize(event)

    def OnSashDrag(self, event):
        if event.GetDragStatus() == wx.SASH_STATUS_OUT_OF_RANGE:
            self.log.write('drag is out of range')
            return

        eobj = event.GetEventObject()

        if eobj is self.controlsWindow:
            self.controlsWindow.SetDefaultSize((event.GetDragRect().width, 1000))
        elif eobj is self.resultsWindow:
            self.resultsWindow.SetDefaultSize((event.GetDragRect().width, 1000))

        wx.LayoutAlgorithm().LayoutWindow(self, self.resultsWindow)
        #self.controlsWindow.Layout()
        self.resultsWindow.Refresh()

    def OnSize(self, event):
        wx.LayoutAlgorithm().LayoutWindow(self, self.resultsWindow)

    def OnClear(self, e):
        print "OnClear"
        self.results.Clear()

    def OnExit(self, e):
        print "OnExit"

    def OpenFound(self, file, line, pattern, multiline):
        if file[:1] == '<':
            #untitled files
            a = self.root.getPositionAbsolute(file, 1)
            if a == -1:
                return
            self.root.control.SetSelection(a)
        else:
            self.root.OnDrop([file], 1)
        a = self.root.control.GetSelection()
        if a > -1:
            line -= 1
            stc = self.root.control.GetPage(a).GetWindow1()
            start = 0
            end = stc.GetLineEndPosition(line)
            if line != 0:
                start = stc.GetLineEndPosition(line-1)+len(stc.format)
            
            #handle overflow
            eml = min(end+multiline, stc.GetLength())
            
            if multiline:
                t = fixnewlines(stc.GetTextRange(start, eml))
            else:
                t = stc.GetLine(line)
            grp = pattern(t)
            if grp != None:
                end =   grp.end()   + start
                start = grp.start() + start
                if stc.format == '\r\n':
                    #handle line ending fixing
                    end += grp.group().count('\n')
            stc.SetSelection(start, end)
            stc.SetFocus()

    def OnDirButtonClick(self, e):
        dlg = wx.DirDialog(self, "Choose a directory", style=wx.DD_DEFAULT_STYLE)
        a = dlg.ShowModal()
        if a == wx.ID_OK:
            a = self.sdirs.GetValue()
            if a:
                self.sdirs.SetValue(a+';'+dlg.GetPath())
            else:
                self.sdirs.SetValue(dlg.GetPath())
        dlg.Destroy()

        self.savePreferences()

    def OnFindButtonClick(self, e):
        global spaces_per_tab
        if self.stopping:
            #already stopping
            return
        elif self.running:
            #running, we want to try to stop
            self.stopping = 1
            self.b.SetLabel("Start Search")
            return

        if e.GetId() == self.b.GetId():
            if self.starting:
                #previously was waiting to start due to an
                #external yield, abort waiting and cancel
                #search
                self.starting = 0
                self.b.SetLabel("Start Search")
                return
        elif not self.starting:
            #I was a waiting timer event, but I don't
            #need to start anymore
            return

        spaces_per_tab = self.root.getglobal('spaces_per_tab')
        #try to start
        self.starting = 1
        self.b.SetLabel("Stop Search")

        try:
            wx.Yield()
        except:
            print "excepted", 1
            #would have tried to start while in another function's
            #wxYield() call.  Will wait 100ms and try again
            self.timer.Start(100, wx.TIMER_ONE_SHOT)
            return

        self.savePreferences()

        #am currently the topmost call, so will continue.
        self.starting = 0
        self.running = 1
        wx.Yield()

        pattern = self.search.GetValue()
        paths = self.sdirs.GetValue().split(';')
        include = self.include.GetValue().split(';')
        exclude = self.exclude.GetValue().split(';')
        caseSensitive = self.cs.IsChecked()
        wholeWord = self.ww.IsChecked()
        self.checkComment = self.commented.IsChecked()
        self.checkQuoted = self.quoted.IsChecked()
        multiline = self.multiline.IsChecked()
        subd = self.ss.IsChecked()

        #python strings
        if pattern and pattern[-1] in ['"', "'"]:
            try:
                pattern = [i for i in compiler.parse(str(pattern)).getChildren()[:1] if isinstance(i, basestring)][0]
            except Exception, e:
                pass
        
        multiline = multiline or '\n' in pattern
        
        if not self.re.IsChecked():
            pattern = re.escape(pattern)
        
        if wholeWord:
            pattern = "\\b%s\\b"%(pattern,)
        
        if caseSensitive:
            pattern = re.compile(pattern)
        else:
            pattern = re.compile(pattern, re.IGNORECASE)

        self.pattern = pattern
        self.resultsWindow.Clear()
        self.resultsWindow.pattern = pattern.search

        if multiline:
            searchFile = self.searchFileMultiline
        else:
            searchFile = self.searchFileEachLine
        self.found = found = []
        fileCount = 0
        hitFileCount = 0
        ss = "Found %i instances in %i files out of %i files %s%s."
        
        ch = self.scope.lower().replace(' ', '_')
        fcn = self.directories
        if hasattr(self, ch):
            fcn = getattr(self, ch)
        
        if ch not in ('tags_in_directories', 'directories'):
            paths = ('',)
        startTime=time.time()
        for path in paths:
            wx.Yield()
            if not self.running:
                break
            for filename, data in fcn(path, subd, include, exclude):
                self.root.SetStatusText(ss%(len(found), hitFileCount, fileCount, "so far ", '"'+filename+'"'), log=0)
                fileCount+=1
                wx.Yield()
                if self.stopping:
                    break
                r = searchFile(filename, data)
                if len(r):
                    hitFileCount+=1
                found += r
                self.resultsWindow.ExtendEntries(r)
        if self.stopping:
            self.stopping = 0
            self.root.SetStatusText('Find in files cancelled.')
            # stopped by a button press
        else:
            self.running = 0
            self.root.SetStatusText(ss%(len(found), hitFileCount, fileCount,
                "checked (in %.2f seconds)"%(time.time()-startTime,), ""))
        self.b.SetLabel("Start Search")
        self.stopping = 0
        self.running = 0
        self.starting = 0
        self.found = found

#--------------------------- scopes for searching ----------------------------

    def current_file(self, *args):
        try:
            _, win = self.root.getNumWin()
            yield self.getfn(win), fixnewlines(StyledTextCtrl.GetText(win))
        except cancelled:
            pass
    
    def current_file_with_includes(self, path, subdirs, include, exclude):
        try:
            _, win = self.root.getNumWin()
            t = fixnewlines(StyledTextCtrl.GetText(win))
            yield self.getfn(win), t
            
            typ, lst = find_imports(t)
            if typ == C:
                for pth in lst:
                    a = os.path.join(win.dirname, pth)
                    if os.path.isfile(a):
                        if namematches(a.split('/')[-1], include, exclude):
                            try:
                                x = self.root.GetPositionAbsolute(a)
                                if x != -1:
                                    t = StyledTextCtrl.GetText(self.root.GetPage(x).GetWindow1())
                                else:
                                    t = open(a, 'rb').read()
                                t = fixnewlines(t)
                            except:
                                continue
                    yield a, t
            elif typ == PY:
                tried = {}
                extns = ['.py', '.pyw', os.path.sep+'__init__.py', os.path.sep+'__init__.pyw']
                for pth in lst:
                    found = 0
                    pth = pth.split('.')
                    while not found and pth:
                        for extn in extns:
                            a = win.dirname + os.path.sep + os.path.sep.join(pth) + extn
                            if a in tried:
                                found = 1
                                break
                            tried[a] = None
                            if os.path.isfile(a):
                                if namematches(a.split('/')[-1], include, exclude):
                                    try:
                                        x = self.root.GetPositionAbsolute(a)
                                        if x != -1:
                                            t = StyledTextCtrl.GetText(self.root.GetPage(x).GetWindow1())
                                        else:
                                            t = open(a, 'rb').read()
                                        t = fixnewlines(t)
                                        found = 1
                                        tried[a] = 1
                                        break
                                    except Exception, why:
                                        continue
                        if not found and pth:
                            _ = pth.pop()
                    if found and pth and tried[a]:
                        yield a,t
                        tried[a] = None
        except cancelled:
            pass
    
    def selected_text(self, *args):
        try:
            _, win = self.root.getNumWin()
            #add some prefix lines so that the line offset is correct
            data = (win.LineFromPosition(win.GetSelection()[0])*'\n' + 
                    fixnewlines(win.GetTextRange(*win.GetSelection())))
            yield self.getfn(win), data
        except cancelled:
            pass
    
    def open_files(self, path, subdirs, include, exclude):
        for win in self.root.control:
            if namematches(win.filename, include, exclude):
                yield self.getfn(win), fixnewlines(StyledTextCtrl.GetText(win))
        
    def directories(self, path, subdirs, include, exclude):
        try:
            lst = os.listdir(path)
        except Exception, e:
            print e
            self.root.SetStatusText("Directory \"%s\" not found."%(path,))
            return
        d = []
        for filen in lst:
            a = os.path.join(path, filen)
            if os.path.isfile(a):
                if namematches(filen, include, exclude):
                    yield a, open(a, 'rU').read()
            elif subdirs and os.path.isdir(a):
                d.append(a)
        if subdirs:
            for p in d:
                for f in self.directories(p, subdirs, include, exclude):
                    yield f
    
    def tags_in_directories(self, *args):
        for fn, content in self.directories(*args):
            for line in content.split('\n'):
                if line and line.count('\t') != 2:
                    break
            else:
                yield fn, content

#----------------------------- searching methods -----------------------------
    def searchFileMultiline(self, fileName, data):
        found = []
        cp = 0
        lc = 0
        for match in self.pattern.finditer(data):
            lc += data.count('\n', cp, match.start())
            cp = match.start()
            found.append((fileName, lc + 1, match.group().replace('\n', '\\n'), ''))
        wx.Yield()
        return found

    def searchFileEachLine(self, fileName, data):
        lines = data.split('\n')
        found = []
        search=self.pattern.search
        pth = os.path.split(fileName)[0] #for tags support
        for number, line in enumerate(lines):
            itemOrig=line
            if self.checkComment:
                match=commentedPattern.search(line)
                if match is None:
                    continue
                line=match.group(1)
            if self.checkQuoted:
                matches=quotedPattern.finditer(line)
                if matches is None:
                    continue
                for match in matches:
                    line=match.group()[1:-1]
                    if search(line) is not None:
                        # (filename, line, line contents, extra)
                        found.append((fileName, int(number) + 1, itemOrig.strip(), ''))
            else:
                if search(line) is not None:
                    # (filename, line, line contents, extra)
                    if self.scope == 'Tags':
                        try:
                            tag, fn, xtra = line.split('\t')
                            lineno = int(xtra.split(';')[0])
                            found.append((os.path.join(pth, fn), lineno, itemOrig.strip(), ';'.join(xtra.split(';')[1:])))
                        except:
                            found.append((fileName, int(number) + 1, itemOrig.strip(), ''))
                    else:
                        found.append((fileName, int(number) + 1, itemOrig.strip(), ''))
            wx.Yield()
        return found

#---------------------------- preference handling ----------------------------

    def readPreferences(self):
        prefs = self.root.config.setdefault('FindInFilesPlugin', {})
        # Set the defaults individually so that adding options works
        # as expected.
        prefs.setdefault('prefs_version', 1)

        prefs.setdefault('search', [])
        prefs.setdefault('case', 0)
        prefs.setdefault('regex', 0)
        prefs.setdefault('multiline', 0)
        prefs.setdefault('whole_word', 0)
        prefs.setdefault('quoted', 0)
        prefs.setdefault('commented', 0)

        prefs.setdefault('scope', 'Current File')
        prefs.setdefault('dirs', [])
        prefs.setdefault('include', ['*.*'])
        prefs.setdefault('exclude', ['.*;*.bak;*.orig;~*;*.swp;CVS'])
        prefs.setdefault('search_sub_dirs', 1)

        prefs.setdefault('view', 'table')
        self.preferences = prefs
        return prefs

    def savePreferences(self):
        def getlist(c):
            cc = c.GetCount()
            e = [c.GetString(i) for i in xrange(cc)]
            a = c.GetValue()
            if a in e:
                e.remove(a)
            e = [a] + e
            e = e[:10]
            if len(e) > cc:
                c.Append(e[-1])
            for i in xrange(len(e)):
                c.SetString(i, e[i])
            c.SetSelection(0)
            return e

        prefs = {
            'prefs_version': 1,

            'scope': self.scopeChoice.GetStringSelection(),
            'search': getlist(self.search),
            'case': self.cs.IsChecked(),
            'regex': self.re.IsChecked(),
            'multiline': self.multiline.IsChecked(),
            'whole_word': self.ww.IsChecked(),
            'quoted': self.quoted.IsChecked(),
            'commented': self.commented.IsChecked(),

            'dirs': getlist(self.sdirs),
            'include': getlist(self.include),
            'exclude': getlist(self.exclude),
            'search_sub_dirs': self.ss.IsChecked(),

            'view': self.viewChoice.GetStringSelection(),
            }
        self.root.config['FindInFilesPlugin'] = prefs

    #-------------------------------------------------------------------------

#------------------------------ small up arrow -------------------------------

def getSmallUpArrowData():
    return \
'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\
\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\
\x00\x00<IDATx\x9ccddbf\xa0\x040Q\xa4{h\x18\xf0\xff\xdf\xdf\xffd\x1b\x00\xd3\
\x8c\xcf\x10\x9c\x06\xa0k\xc2e\x08m\xc2\x00\x97m\xd8\xc41\x0c \x14h\xe8\xf2\
\x8c\xa3)q\x10\x18\x00\x00R\xd8#\xec\x95{\xc4\x11\x00\x00\x00\x00IEND\xaeB`\
\x82' 

def getSmallUpArrowBitmap():
    return BitmapFromImage(getSmallUpArrowImage())

def getSmallUpArrowImage():
    stream = cStringIO.StringIO(getSmallUpArrowData())
    return ImageFromStream(stream)
#----------------------------- small down arrow ------------------------------

def getSmallDnArrowData():
    return \
"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\
\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\
\x00\x00HIDATx\x9ccddbf\xa0\x040Q\xa4{\xd4\x00\x06\x06\x06\x06\x06\x16t\x81\
\xff\xff\xfe\xfe'\xa4\x89\x91\x89\x99\x11\xa7\x0b\x90%\ti\xc6j\x00>C\xb0\x89\
\xd3.\x10\xd1m\xc3\xe5*\xbc.\x80i\xc2\x17.\x8c\xa3y\x81\x01\x00\xa1\x0e\x04e\
\x1d\xc4;\xb7\x00\x00\x00\x00IEND\xaeB`\x82" 

def getSmallDnArrowBitmap():
    return BitmapFromImage(getSmallDnArrowImage())

def getSmallDnArrowImage():
    stream = cStringIO.StringIO(getSmallDnArrowData())
    return ImageFromStream(stream)
