threading_colorizer.py :  » Development » Leo » Leo-4.7.1-final » leo » plugins » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Development » Leo 
Leo » Leo 4.7.1 final » leo » plugins » threading_colorizer.py
#@+leo-ver=4-thin
#@+node:ekr.20071010193720:@thin threading_colorizer.py  
'''A threading colorizer using jEdit language description files.

See: http://webpages.charter.net/edreamleo/coloring.html for documentation.
'''

#@@language python
#@@tabwidth -4
#@@pagewidth 80

__version__ = '1.5'

trace_all_matches = False
trace_leo_matches = False

#@<< imports >>
#@+node:ekr.20071010193720.1:<< imports >>
import leo.core.leoGlobals as g
import leo.core.leoPlugins as leoPlugins

# import os
import re
import string
import threading
import traceback

# import xml.sax
# import xml.sax.saxutils

import Tkinter as Tk

# php_re = re.compile("<?(\s|=|[pP][hH][pP])")
php_re = re.compile("<?(\s[pP][hH][pP])")
#@nonl
#@-node:ekr.20071010193720.1:<< imports >>
#@nl
#@<< version history >>
#@+node:ekr.20071010193720.2:<< version history >>
#@@nocolor
#@+at
# 
# 1.0 EKR: A complete rewrite:
#     - No incremental coloring **at all**.
#     - The helper thread runs to completion before *any* tagging is done.
#     - No locks are needed because the globalTagList is never simultaneously 
# accessed.
# 1.1: EKR: support non-interruptable coloring.  Required when there are 
# multiple body editors.
# 1.2: EKR: Fixed off-by-one bug in 'end' hack in putNewTags.
# 1.3: EKR: Fixed off-by-one bug in match_doc_part.
# 1.4: EKR:
# - Better recovery of matcher errors.
# - Fixed bug in match_doc_part.
# - Don't try to import non-existent language files.
#   This suppresses errors for 'p', 'pe', and 'per' when typing @langauge 
# perl.
# ** Important: regexp matching can hang for complex regexp's.
#    The fix for perl was to disable two perl rules.
# 1.5 EKR: Changes suggested by pylint.
#@-at
#@nonl
#@-node:ekr.20071010193720.2:<< version history >>
#@nl
#@<< define leoKeywordsDict >>
#@+node:ekr.20071010193720.3:<< define leoKeywordsDict >>
leoKeywordsDict = {}

for key in g.globalDirectiveList:
    leoKeywordsDict [key] = 'leoKeyword'
#@nonl
#@-node:ekr.20071010193720.3:<< define leoKeywordsDict >>
#@nl
#@<< define default_colors_dict >>
#@+node:ekr.20071010193720.4:<< define default_colors_dict >>
# These defaults are sure to exist.

default_colors_dict = {
    # tag name       :(     option name,           default color),
    'comment'        :('comment_color',               'red'),
    'cwebName'       :('cweb_section_name_color',     'red'),
    'pp'             :('directive_color',             'blue'),
    'docPart'        :('doc_part_color',              'red'),
    'keyword'        :('keyword_color',               'blue'),
    'leoKeyword'     :('leo_keyword_color',           'blue'),
    'link'           :('section_name_color',          'red'),
    'nameBrackets'   :('section_name_brackets_color', 'blue'),
    'string'         :('string_color',                '#00aa00'), # Used by IDLE.
    'name'           :('undefined_section_name_color','red'),
    'latexBackground':('latex_background_color',      'white'),

    # Tags used by forth.
    'keyword5'       :('keyword5_color',              'blue'),
    'bracketRange'   :('bracket_range_color',         'orange'),
    # jEdit tags.

    'comment1'       :('comment1_color', 'red'),
    'comment2'       :('comment2_color', 'red'),
    'comment3'       :('comment3_color', 'red'),
    'comment4'       :('comment4_color', 'red'),
    'function'       :('function_color', 'black'),
    'keyword1'       :('keyword1_color', 'blue'),
    'keyword2'       :('keyword2_color', 'blue'),
    'keyword3'       :('keyword3_color', 'blue'),
    'keyword4'       :('keyword4_color', 'blue'),
    'label'          :('label_color',    'black'),
    'literal1'       :('literal1_color', '#00aa00'),
    'literal2'       :('literal2_color', '#00aa00'),
    'literal3'       :('literal3_color', '#00aa00'),
    'literal4'       :('literal4_color', '#00aa00'),
    'markup'         :('markup_color',   'red'),
    'null'           :('null_color',     'black'),
    'operator'       :('operator_color', 'black'),
    }
#@-node:ekr.20071010193720.4:<< define default_colors_dict >>
#@nl
#@<< define default_font_dict >>
#@+node:ekr.20071010193720.5:<< define default_font_dict >>
default_font_dict = {
    # tag name      : option name
    'comment'       :'comment_font',
    'cwebName'      :'cweb_section_name_font',
    'pp'            :'directive_font',
    'docPart'       :'doc_part_font',
    'keyword'       :'keyword_font',
    'leoKeyword'    :'leo_keyword_font',
    'link'          :'section_name_font',
    'nameBrackets'  :'section_name_brackets_font',
    'string'        :'string_font',
    'name'          :'undefined_section_name_font',
    'latexBackground':'latex_background_font',

    # Tags used by forth.
    'bracketRange'   :'bracketRange_font',
    'keyword5'       :'keyword5_font',

     # jEdit tags.
    'comment1'      :'comment1_font',
    'comment2'      :'comment2_font',
    'comment3'      :'comment3_font',
    'comment4'      :'comment4_font',
    'function'      :'function_font',
    'keyword1'      :'keyword1_font',
    'keyword2'      :'keyword2_font',
    'keyword3'      :'keyword3_font',
    'keyword4'      :'keyword4_font',
    'keyword5'      :'keyword5_font',
    'label'         :'label_font',
    'literal1'      :'literal1_font',
    'literal2'      :'literal2_font',
    'literal3'      :'literal3_font',
    'literal4'      :'literal4_font',
    'markup'        :'markup_font',
    # 'nocolor' This tag is used, but never generates code.
    'null'          :'null_font',
    'operator'      :'operator_font',
    }
#@-node:ekr.20071010193720.5:<< define default_font_dict >>
#@nl

#@+others
#@+node:ekr.20071010193720.6:module-level
#@+node:ekr.20071010193720.7:init
def init ():

    # The qt gui plugin now does colorizing.
    ok = g.app.gui.guiName() in ('tkinter',)

    if ok:

        leoPlugins.registerHandler('start1',onStart1)
        g.plugin_signon(__name__)

    return ok
#@-node:ekr.20071010193720.7:init
#@+node:ekr.20071010193720.8:onStart1
def onStart1 (tag, keywords):

    '''Override Leo's core colorizer classes.'''

    import leo.core.leoColor as leoColor
    # g.pr('threading_colorizer overriding core classes')
    leoColor.colorizer = colorizer
    leoColor.nullColorizer = nullColorizer
#@-node:ekr.20071010193720.8:onStart1
#@+node:ekr.20071010193720.9:Leo rule functions (in helper thread)
#@+at
# These rule functions recognize noweb syntactic constructions. These are 
# treated
# just like rule functions, so they are module-level objects whose first 
# argument
# is 'self'.
#@-at
#@@c

#@+node:ekr.20071010193720.10:match_at_color
def match_at_color (self,s,i):

    if trace_leo_matches: g.trace()

    seq = '@color'

    # Only matches at start of line.
    if i != 0 and s[i-1] != '\n': return 0

    if g.match_word(s,i,seq):
        self.flag = True # Enable coloring.
        j = i + len(seq)
        self.colorRangeWithTag(s,i,j,'leoKeyword')
        return j - i
    else:
        return 0
#@nonl
#@-node:ekr.20071010193720.10:match_at_color
#@+node:ekr.20071010193720.11:match_at_nocolor
def match_at_nocolor (self,s,i):

    if trace_leo_matches: g.trace()

    # Only matches at start of line.
    if i != 0 and s[i-1] != '\n':
        return 0
    if not g.match_word(s,i,'@nocolor'):
        return 0

    j = i + len('@nocolor')
    k = s.find('\n@color',j)
    if k == -1:
        # No later @color: don't color the @nocolor directive.
        self.flag = False # Disable coloring.
        return len(s) - j
    else:
        # A later @color: do color the @nocolor directive.
        self.colorRangeWithTag(s,i,j,'leoKeyword')
        self.flag = False # Disable coloring.
        return k+1-j

#@-node:ekr.20071010193720.11:match_at_nocolor
#@+node:ekr.20071010193720.12:match_doc_part
def match_doc_part (self,s,i):

    # New in Leo 4.5: only matches at start of line.
    if i != 0 and s[i-1] != '\n':
        return 0

    if g.match_word(s,i,'@doc'):
        j = i+4
        self.colorRangeWithTag(s,i,j,'leoKeyword')
    elif g.match(s,i,'@') and (i+1 >= len(s) or s[i+1] in (' ','\t','\n')):
        j = i + 1
        self.colorRangeWithTag(s,i,j,'leoKeyword')
    else: return 0

    i = j ; n = len(s)
    while j < n:
        k = s.find('@c',j)
        if k == -1:
            # g.trace('i,len(s)',i,len(s))
            j = n+1 # Bug fix: 2007/12/14
            self.colorRangeWithTag(s,i,j,'docPart')
            return j - i
        if s[k-1] == '\n' and (g.match_word(s,k,'@c') or g.match_word(s,k,'@code')):
            j = k
            self.colorRangeWithTag(s,i,j,'docPart')
            return j - i
        else:
            j = k + 2
    j = n - 1
    return max(0,j - i) # Bug fix: 2008/2/10
#@-node:ekr.20071010193720.12:match_doc_part
#@+node:ekr.20071010193720.13:match_leo_keywords
def match_leo_keywords(self,s,i):

    '''Succeed if s[i:] is a Leo keyword.'''

    # g.trace(i,g.get_line(s,i))

    # We must be at the start of a word.
    if i > 0 and s[i-1] in self.word_chars:
        return 0

    if s[i] != '@':
        return 0

    # Get the word as quickly as possible.
    j = i+1
    while j < len(s) and s[j] in self.word_chars:
        j += 1
    word = s[i+1:j] # Bug fix: 10/17/07: entries in leoKeywordsDict do not start with '@'

    if leoKeywordsDict.get(word):
        kind = 'leoKeyword'
        self.colorRangeWithTag(s,i,j,kind)
        self.prev = (i,j,kind)
        result = j-i
        self.trace_match(kind,s,i,j)
        return result
    else:
        return 0
#@-node:ekr.20071010193720.13:match_leo_keywords
#@+node:ekr.20071010193720.14:match_section_ref
def match_section_ref (self,s,i):

    if trace_leo_matches: g.trace()
    c = self.c ; w = self.w

    if not g.match(s,i,'<<'):
        return 0
    k = g.find_on_line(s,i+2,'>>')
    if k is not None:
        j = k + 2
        self.colorRangeWithTag(s,i,i+2,'nameBrackets')
        ref = g.findReference(c,s[i:j],self.p)
        if ref:
            if self.use_hyperlinks:
                #@                << set the hyperlink >>
                #@+node:ekr.20071010193720.15:<< set the hyperlink >>
                # Set the bindings to vnode callbacks.
                # Create the tag.
                # Create the tag name.
                tagName = "hyper" + str(self.hyperCount)
                self.hyperCount += 1
                w.tag_delete(tagName)
                self.tag_add(tagName,i+2,j)

                ref.tagName = tagName
                c.tag_bind(w,tagName,"<Control-1>",ref.OnHyperLinkControlClick)
                c.tag_bind(w,tagName,"<Any-Enter>",ref.OnHyperLinkEnter)
                c.tag_bind(w,tagName,"<Any-Leave>",ref.OnHyperLinkLeave)
                #@nonl
                #@-node:ekr.20071010193720.15:<< set the hyperlink >>
                #@nl
            else:
                self.colorRangeWithTag(s,i+2,k,'link')
        else:
            self.colorRangeWithTag(s,i+2,k,'name')
        self.colorRangeWithTag(s,k,j,'nameBrackets')
        return j - i
    else:
        return 0
#@nonl
#@-node:ekr.20071010193720.14:match_section_ref
#@+node:ekr.20071010193720.16:match_blanks
def match_blanks (self,s,i):

    # if trace_leo_matches: g.trace()

    j = i ; n = len(s)

    while j < n and s[j] == ' ':
        j += 1

    if j > i:
        # g.trace(i,j)
        if self.showInvisibles:
            self.colorRangeWithTag(s,i,j,'blank')
        return j - i
    else:
        return 0
#@nonl
#@-node:ekr.20071010193720.16:match_blanks
#@+node:ekr.20071010193720.17:match_tabs
def match_tabs (self,s,i):

    if trace_leo_matches: g.trace()

    j = i ; n = len(s)

    while j < n and s[j] == '\t':
        j += 1

    if j > i:
        # g.trace(i,j)
        self.colorRangeWithTag(s,i,j,'tab')
        return j - i
    else:
        return 0
#@nonl
#@-node:ekr.20071010193720.17:match_tabs
#@+node:ekr.20071010193720.18:match_incomplete_strings
# def match_incomplete_strings (self,s,i):

    # if trace_leo_matches: g.trace()

    # if not g.match(s,i,'"') and not g.match(s,i,"'"):
        # return 0

    # if self.language == 'python' and (g.match(s,i-2,'"""') or g.match(s,i-2,"'''")):
        # return 0 # Do not interfere with docstrings.

    # delim = s[i]
    # j = g.skip_line(s,i)

    # if s.find(delim,i+1,j) == -1:
        # g.trace(repr(s[i:j]))
        # self.colorRangeWithTag(s,i,j,'literal1')
        # return j-i
    # else:
        # return 0
#@-node:ekr.20071010193720.18:match_incomplete_strings
#@-node:ekr.20071010193720.9:Leo rule functions (in helper thread)
#@-node:ekr.20071010193720.6:module-level
#@+node:ekr.20071010193720.19:class colorizer
class colorizer:

    #@    @+others
    #@+node:ekr.20071010193720.20:Birth and init
    #@+node:ekr.20071010193720.21:__init__ (threading colorizer)
    def __init__(self,c,w=None):

        # g.trace('threading_colorizer',self)
        # Basic data...
        self.c = c
        self.p = None
        self.s = None # The string being colorized.

        self.isQt = g.app.gui.guiName() == 'qt'
        self.fake = bool(w)

        if w is None:
            self.w = c.frame.body.bodyCtrl
            # Use hasattr/getattr to keep pylint happy.
            if hasattr(g.app.gui,'Tk_Text'):
                self.Tk_Text = getattr(g.app.gui,'Tk_Text')
                self.fake = True
            else:
                self.Tk_Text = Tk.Text
        else:
            self.w = w
            self.Tk_Text = w

        # Attributes dict ivars: defaults are as shown...
        self.default = 'null'
        self.digit_re = ''
        self.escape = ''
        self.highlight_digits = True
        self.ignore_case = True
        self.no_word_sep = ''
        # Config settings...
        self.comment_string = None # Set by scanColorDirectives on @comment
        self.showInvisibles = False # True: show "invisible" characters.
        self.underline_undefined = c.config.getBool("underline_undefined_section_names")
        self.use_hyperlinks = c.config.getBool("use_hyperlinks")
        self.enabled = c.config.getBool('use_syntax_coloring')
        # Debugging...
        self.count = 0 # For unit testing.
        self.allow_mark_prev = True # The new colorizer tolerates this nonsense :-)
        self.trace = False or c.config.getBool('trace_colorizer')
        self.trace_match_flag = False # (Useful) True: trace all matching methods.
        self.trace_tags = False
        self.verbose = False
        # Mode data...
        self.comment_string = None # Can be set by @comment directive.
        self.defaultRulesList = []
        self.flag = True # True unless in range of @nocolor
        self.importedRulesets = {}
        self.language = 'python' # set by scanColorDirectives.
        self.prev = None # The previous token.
        self.fonts = {} # Keys are config names.  Values are actual fonts.
        self.keywords = {} # Keys are keywords, values are 0..5.
        self.modes = {} # Keys are languages, values are modes.
        self.mode = None # The mode object for the present language.
        self.modeBunch = None # A bunch fully describing a mode.
        self.modeStack = []
        # self.defineAndExtendForthWords()
        self.word_chars = [] # Inited by init_keywords().
        self.setFontFromConfig()
        self.tags = [
            "blank","comment","cwebName","docPart","keyword","leoKeyword",
            "latexModeBackground","latexModeKeyword",
            "latexBackground","latexKeyword",
            "link","name","nameBrackets","pp","string",
            "elide","bold","bolditalic","italic", # new for wiki styling.
            "tab",
            # Leo jEdit tags...
            '@color', '@nocolor', 'doc_part', 'section_ref',
            # jEdit tags.
            'bracketRange',
            'comment1','comment2','comment3','comment4',
            'function',
            'keyword1','keyword2','keyword3','keyword4',
            'label','literal1','literal2','literal3','literal4',
            'markup','operator',
        ]
        # Threading info...
        self.threadCount = 0
        self.helperThread = None # A singleton helper thread.
        self.interruptable = True
        self.killFlag = False
        # Tagging...
        self.oldTags = [] # Sorted list of all old tags.
        self.oldTagsDict = {} # Keys are tag names, values are (i,j)
        self.globalAddList = [] # The tags (i,j,tagName) remaining to be colored.
        self.newTagsDict = {} # Keys are tag names, values are lists of tuples (i,j)
            # The helper thread adds to this dict.  idleHandler in the main thread uses these dicts.
        self.oldTagsDict = {}
        self.postPassStarted = False

        # New in Leo 4.6: configure tags only once here.
        # Some changes will be needed for multiple body editors.
        self.configure_tags() # Must do this every time to support multiple editors.
    #@-node:ekr.20071010193720.21:__init__ (threading colorizer)
    #@+node:ekr.20071010193720.22:addImportedRules
    def addImportedRules (self,mode,rulesDict,rulesetName):

        '''Append any imported rules at the end of the rulesets specified in mode.importDict'''

        if self.importedRulesets.get(rulesetName):
            return
        else:
            self.importedRulesets [rulesetName] = True

        names = hasattr(mode,'importDict') and mode.importDict.get(rulesetName,[]) or []

        for name in names:
            savedBunch = self.modeBunch
            ok = self.init_mode(name)
            if ok:
                rulesDict2 = self.rulesDict
                for key in rulesDict2.keys():
                    aList = rulesDict.get(key,[])
                    aList2 = rulesDict2.get(key)
                    if aList2:
                        # Don't add the standard rules again.
                        rules = [z for z in aList2 if z not in aList]
                        if rules:
                            # g.trace([z.__name__ for z in rules])
                            aList.extend(rules)
                            rulesDict [key] = aList
            # g.trace('***** added rules for %s from %s' % (name,rulesetName))
            self.initModeFromBunch(savedBunch)
    #@nonl
    #@-node:ekr.20071010193720.22:addImportedRules
    #@+node:ekr.20071010193720.23:addLeoRules
    def addLeoRules (self,theDict):

        '''Put Leo-specific rules to theList.'''

        table = (
            # Rules added at front are added in **reverse** order.
            ('@',  match_leo_keywords,True), # Called after all other Leo matchers.
                # Debatable: Leo keywords override langauge keywords.
            ('@',  match_at_color,    True),
            ('@',  match_at_nocolor,  True),
            ('@',  match_doc_part,    True), 
            ('<',  match_section_ref, True), # Called **first**.
            # Rules added at back are added in normal order.
            (' ',  match_blanks,      False),
            ('\t', match_tabs,        False),
            # Python rule 3 appears to work well enough.
            #('"',  match_incomplete_strings, False),
            #("'",  match_incomplete_strings, False),
        )

        for ch, rule, atFront, in table:

            theList = theDict.get(ch,[])
            if atFront:
                theList.insert(0,rule)
            else:
                theList.append(rule)
            theDict [ch] = theList

        # g.trace(g.listToString(theDict.get('@')))
    #@-node:ekr.20071010193720.23:addLeoRules
    #@+node:ekr.20071010193720.24:configure_tags
    def configure_tags (self):

        c = self.c ; w = self.w ; trace = False

        if w and hasattr(w,'start_tag_configure'):
            w.start_tag_configure()

        # Get the default body font.
        defaultBodyfont = self.fonts.get('default_body_font')
        if not defaultBodyfont:
            defaultBodyfont = c.config.getFontFromParams(
                "body_text_font_family", "body_text_font_size",
                "body_text_font_slant",  "body_text_font_weight",
                c.config.defaultBodyFontSize)
            self.fonts['default_body_font'] = defaultBodyfont

        # Configure fonts.
        keys = default_font_dict.keys() ; keys.sort()
        for key in keys:
            option_name = default_font_dict[key]
            # First, look for the language-specific setting, then the general setting.
            for name in ('%s_%s' % (self.language,option_name),(option_name)):
                font = self.fonts.get(name)
                if font:
                    if trace: g.trace('found',name,id(font))
                    w.tag_config(key,font=font)
                    break
                else:
                    family = c.config.get(name + '_family','family')
                    size   = c.config.get(name + '_size',  'size')   
                    slant  = c.config.get(name + '_slant', 'slant')
                    weight = c.config.get(name + '_weight','weight')
                    if family or slant or weight or size:
                        family = family or g.app.config.defaultFontFamily
                        size   = size or c.config.defaultBodyFontSize
                        slant  = slant or 'roman'
                        weight = weight or 'normal'
                        font = g.app.gui.getFontFromParams(family,size,slant,weight)
                        # Save a reference to the font so it 'sticks'.
                        self.fonts[name] = font 
                        if trace: g.trace(key,name,family,size,slant,weight,id(font))
                        w.tag_config(key,font=font)
                        break
            else: # Neither the general setting nor the language-specific setting exists.
                if self.fonts.keys(): # Restore the default font.
                    if trace: g.trace('default',key)
                    w.tag_config(key,font=defaultBodyfont)

        keys = default_colors_dict.keys() ; keys.sort()
        for name in keys:
            option_name,default_color = default_colors_dict[name]
            color = (
                c.config.getColor('%s_%s' % (self.language,option_name)) or
                c.config.getColor(option_name) or
                default_color
            )
            if trace: g.trace(option_name,color)

            # Must use foreground, not fg.
            try:
                w.tag_configure(name, foreground=color)
            except: # Recover after a user error.
                g.es_exception()
                w.tag_configure(name, foreground=default_color)

        # underline=var doesn't seem to work.
        if 0: # self.use_hyperlinks: # Use the same coloring, even when hyperlinks are in effect.
            w.tag_configure("link",underline=1) # defined
            w.tag_configure("name",underline=0) # undefined
        else:
            w.tag_configure("link",underline=0)
            if self.underline_undefined:
                w.tag_configure("name",underline=1)
            else:
                w.tag_configure("name",underline=0)

        self.configure_variable_tags()

        # Colors for latex characters.  Should be user options...

        if 1: # Alas, the selection doesn't show if a background color is specified.
            w.tag_configure("latexModeBackground",foreground="black")
            w.tag_configure("latexModeKeyword",foreground="blue")
            w.tag_configure("latexBackground",foreground="black")
            w.tag_configure("latexKeyword",foreground="blue")
        else: # Looks cool, and good for debugging.
            w.tag_configure("latexModeBackground",foreground="black",background="seashell1")
            w.tag_configure("latexModeKeyword",foreground="blue",background="seashell1")
            w.tag_configure("latexBackground",foreground="black",background="white")
            w.tag_configure("latexKeyword",foreground="blue",background="white")

        # Tags for wiki coloring.
        w.tag_configure("bold",font=self.bold_font)
        w.tag_configure("italic",font=self.italic_font)
        w.tag_configure("bolditalic",font=self.bolditalic_font)
        for name in self.color_tags_list:
            w.tag_configure(name,foreground=name)

        try:
            w.end_tag_configure()
        except AttributeError:
            pass
    #@nonl
    #@-node:ekr.20071010193720.24:configure_tags
    #@+node:ekr.20071010193720.25:configure_variable_tags
    def configure_variable_tags (self):

        c = self.c ; w = self.w

        # g.trace()

        for name,option_name,default_color in (
            ("blank","show_invisibles_space_background_color","Gray90"),
            ("tab",  "show_invisibles_tab_background_color",  "Gray80"),
            ("elide", None,                                   "yellow"),
        ):
            if self.showInvisibles:
                color = option_name and c.config.getColor(option_name) or default_color
            else:
                option_name,default_color = default_colors_dict.get(name,(None,None),)
                color = option_name and c.config.getColor(option_name) or ''
            try:
                w.tag_configure(name,background=color)
            except: # A user error.
                w.tag_configure(name,background=default_color)

        # Special case:
        if not self.showInvisibles:
            w.tag_configure("elide",elide="1")
    #@-node:ekr.20071010193720.25:configure_variable_tags
    #@+node:ekr.20071010193720.26:init_mode & helpers
    def init_mode (self,name):

        '''Name may be a language name or a delegate name.'''

        if not name: return False
        language,rulesetName = self.nameToRulesetName(name)
        bunch = self.modes.get(rulesetName)
        if bunch:
            # g.trace('found',language,rulesetName)
            self.initModeFromBunch(bunch)
            return True
        else:
            # g.trace('****',language,rulesetName)
            path = g.os_path_join(g.app.loadDir,'..','modes')
            # Bug fix: 2008/2/10: Don't try to import a non-existent language.
            fileName = g.os_path_join(path,'%s.py' % (language))
            if g.os_path_exists(fileName):
                mode = g.importFromPath (language,path)
            else: mode = None

            if mode:
                # A hack to give modes/forth.py access to c.
                if hasattr(mode,'pre_init_mode'):
                    mode.pre_init_mode(self.c)
            else:
                # Create a dummy bunch to limit recursion.
                self.modes [rulesetName] = self.modeBunch = g.Bunch(
                    attributesDict  = {},
                    defaultColor    = None,
                    keywordsDict    = {},
                    language        = language,
                    mode            = mode,
                    properties      = {},
                    rulesDict       = {},
                    rulesetName     = rulesetName)
                # g.trace('No colorizer file: %s.py' % language)
                return False
            self.language = language
            self.rulesetName = rulesetName
            self.properties = hasattr(mode,'properties') and mode.properties or {}
            self.keywordsDict = hasattr(mode,'keywordsDictDict') and mode.keywordsDictDict.get(rulesetName,{}) or {}
            self.setKeywords()
            self.attributesDict = hasattr(mode,'attributesDictDict') and mode.attributesDictDict.get(rulesetName) or {}
            self.setModeAttributes()
            self.rulesDict = hasattr(mode,'rulesDictDict') and mode.rulesDictDict.get(rulesetName) or {}
            self.addLeoRules(self.rulesDict)

            self.defaultColor = 'null'
            self.mode = mode
            self.modes [rulesetName] = self.modeBunch = g.Bunch(
                attributesDict  = self.attributesDict,
                defaultColor    = self.defaultColor,
                keywordsDict    = self.keywordsDict,
                language        = self.language,
                mode            = self.mode,
                properties      = self.properties,
                rulesDict       = self.rulesDict,
                rulesetName     = self.rulesetName)
            # Do this after 'officially' initing the mode, to limit recursion.
            self.addImportedRules(mode,self.rulesDict,rulesetName)
            self.updateDelimsTables()

            initialDelegate = self.properties.get('initialModeDelegate')
            if initialDelegate:
                # g.trace('initialDelegate',initialDelegate)
                # Replace the original mode by the delegate mode.
                self.init_mode(initialDelegate)
                language2,rulesetName2 = self.nameToRulesetName(initialDelegate)
                self.modes[rulesetName] = self.modes.get(rulesetName2)
            return True
    #@+node:ekr.20071010193720.27:nameToRulesetName
    def nameToRulesetName (self,name):

        '''Compute language and rulesetName from name, which is either a language or a delegate name.'''

        if not name: return ''

        i = name.find('::')
        if i == -1:
            language = name
            rulesetName = '%s_main' % (language)
        else:
            language = name[:i]
            delegate = name[i+2:]
            rulesetName = self.munge('%s_%s' % (language,delegate))

        # g.trace(name,language,rulesetName)
        return language,rulesetName
    #@nonl
    #@-node:ekr.20071010193720.27:nameToRulesetName
    #@+node:ekr.20071010193720.28:setKeywords
    def setKeywords (self):

        '''Initialize the keywords for the present language.

         Set self.word_chars ivar to string.letters + string.digits
         plus any other character appearing in any keyword.'''

        # Add any new user keywords to leoKeywordsDict.
        d = self.keywordsDict
        keys = d.keys()
        for s in g.globalDirectiveList:
            key = '@' + s
            if key not in keys:
                d [key] = 'leoKeyword'

        # Create the word_chars list. 
        self.word_chars = [g.toUnicode(ch) for ch in (string.letters + string.digits)]

        for key in d.keys():
            for ch in key:
                # if ch == ' ': g.trace('blank in key: %s' % repr (key))
                if ch not in self.word_chars:
                    self.word_chars.append(g.toUnicode(ch))

        # jEdit2Py now does this check, so this isn't really needed.
        # But it is needed for forth.py.
        for ch in (' ', '\t'):
            if ch in self.word_chars:
                # g.es_print('removing %s from word_chars' % (repr(ch)))
                self.word_chars.remove(ch)

        # g.trace(self.language,[str(z) for z in self.word_chars])
    #@nonl
    #@-node:ekr.20071010193720.28:setKeywords
    #@+node:ekr.20071010193720.29:setModeAttributes
    def setModeAttributes (self):

        '''Set the ivars from self.attributesDict import 
        converting 'true'/'false' to True and False.'''

        d = self.attributesDict
        aList = (
            ('default',         'null'),
          ('digit_re',        ''),
            ('escape',          ''), # New in Leo 4.4.2.
          ('highlight_digits',True),
          ('ignore_case',     True),
          ('no_word_sep',     ''),
        )

        for key, default in aList:
            val = d.get(key,default)
            if val in ('true','True'): val = True
            if val in ('false','False'): val = False
            setattr(self,key,val)
            # g.trace(key,val)
    #@nonl
    #@-node:ekr.20071010193720.29:setModeAttributes
    #@+node:ekr.20071010193720.30:initModeFromBunch
    def initModeFromBunch (self,bunch):

        self.modeBunch = bunch
        self.attributesDict = bunch.attributesDict
        self.setModeAttributes()
        self.defaultColor   = bunch.defaultColor
        self.keywordsDict   = bunch.keywordsDict
        self.language       = bunch.language
        self.mode           = bunch.mode
        self.properties     = bunch.properties
        self.rulesDict      = bunch.rulesDict
        self.rulesetName    = bunch.rulesetName

        # g.trace(self.rulesetName)
    #@nonl
    #@-node:ekr.20071010193720.30:initModeFromBunch
    #@+node:ekr.20071010193720.31:updateDelimsTables
    def updateDelimsTables (self):

        '''Update g.app.language_delims_dict if no entry for the language exists.'''

        d = self.properties
        lineComment = d.get('lineComment')
        startComment = d.get('commentStart')
        endComment = d.get('commentEnd')

        if lineComment and startComment and endComment:
            delims = '%s %s %s' % (lineComment,startComment,endComment)
        elif startComment and endComment:
            delims = '%s %s' % (startComment,endComment)
        elif lineComment:
            delims = '%s' % lineComment
        else:
            delims = None

        if delims:
            d = g.app.language_delims_dict
            if not d.get(self.language):
                d [self.language] = delims
                # g.trace(self.language,'delims:',repr(delims))
    #@-node:ekr.20071010193720.31:updateDelimsTables
    #@-node:ekr.20071010193720.26:init_mode & helpers
    #@-node:ekr.20071010193720.20:Birth and init
    #@+node:ekr.20071010193720.32:Entry points
    #@+node:ekr.20071010193720.33:colorize
    def colorize(self,p,incremental=False,interruptable=True):

        '''The main colorizer entry point.'''

        self.count += 1 # For unit testing.

        self.interruptable = interruptable

        c = self.c

        if self.enabled:
            self.updateSyntaxColorer(p) # Sets self.flag.
            self.threadColorizer(p)
        else:
            self.removeAllTags()

        return "ok" # For unit testing.
    #@-node:ekr.20071010193720.33:colorize
    #@+node:ekr.20071010193720.34:enable & disable
    def disable (self):

        g.pr("disabling all syntax coloring")
        self.enabled=False

    def enable (self):
        self.enabled=True
    #@nonl
    #@-node:ekr.20071010193720.34:enable & disable
    #@+node:ekr.20071010193720.35:isSameColorState
    def isSameColorState (self):

        return False
    #@nonl
    #@-node:ekr.20071010193720.35:isSameColorState
    #@+node:ekr.20071010193720.36:interrupt (does nothing)
    interrupt_count = 0

    def interrupt(self):

        '''Interrupt colorOneChunk'''

        if self.trace and self.verbose: g.trace('thread',self.threadCount)
    #@-node:ekr.20071010193720.36:interrupt (does nothing)
    #@+node:ekr.20080308151956.4:kill
    def kill (self):

        '''Kill all future coloring.'''

        self.killFlag = True
    #@-node:ekr.20080308151956.4:kill
    #@+node:ekr.20071010193720.37:useSyntaxColoring
    def useSyntaxColoring (self,p):

        """Return True unless p is unambiguously under the control of @nocolor."""

        p = p.copy() ; first = p.copy()
        val = True ; self.killcolorFlag = False

        # New in Leo 4.6: @nocolor-node disables one node only.
        theDict = g.get_directives_dict(p)
        if 'nocolor-node' in theDict:
            # g.trace('nocolor-node',p.h)
            return False

        for p in p.self_and_parents():
            theDict = g.get_directives_dict(p)
            no_color = 'nocolor' in theDict
            color = 'color' in theDict
            kill_color = 'killcolor' in theDict
            # A killcolor anywhere disables coloring.
            if kill_color:
                val = False ; self.killcolorFlag = True ; break
            # A color anywhere in the target enables coloring.
            if color and p == first:
                val = True ; break
            # Otherwise, the @nocolor specification must be unambiguous.
            elif no_color and not color:
                val = False ; break
            elif color and not no_color:
                val = True ; break

        # g.trace(first.h,val)
        return val
    #@-node:ekr.20071010193720.37:useSyntaxColoring
    #@+node:ekr.20071010193720.38:updateSyntaxColorer
    def updateSyntaxColorer (self,p):

        p = p.copy()

        # self.flag is True unless an unambiguous @nocolor is seen.
        self.flag = self.useSyntaxColoring(p)
        self.scanColorDirectives(p)
    #@nonl
    #@-node:ekr.20071010193720.38:updateSyntaxColorer
    #@-node:ekr.20071010193720.32:Entry points
    #@+node:ekr.20071010193720.39:Colorers & helpers
    #@+node:ekr.20071025133020:completeColoring
    def completeColoring (self):

        trace = False ; verbose = False

        if not self.postPassStarted:
            # if trace: g.trace('****** post processing')
            self.postPassStarted = True
            self.oldTags = self.getOldTags() # Must be in main thread!
            newList = self.newMergeTags()
            addList,deleteList = self.computeNewTags(self.oldTags,newList)
            self.globalAddList = addList
            self.removeOldTags(deleteList)
        else:
            addList = self.globalAddList ; deleteList = []

        if verbose or (trace and (addList or deleteList)):
            g.trace('-%-3d +%-3d' % (len(deleteList),len(addList)))

        if self.interruptable:
            self.putNewTags()
        else:
            if trace: g.trace('**** non-interruptable')
            while self.globalAddList:
                self.putNewTags()
    #@-node:ekr.20071025133020:completeColoring
    #@+node:ekr.20071013072543:computeNewTags
    def computeNewTags (self,oldList,newList):

        '''Return (addList,deleteList)
        deleteList: a list of old tags to be deleted.
        addList: a list of new tags to be added.

        '''
        trace = False or self.trace and self.trace_tags
        verbose = self.verbose
        old_len = len(oldList) ; new_len = len(newList)
        addList = [] ; deleteList = []

        def report(kind,tag):
            i,j,name = tag
            g.pr('computeNewTags: *** %-5s %10s %3d %3d' % (kind,name,i,j),repr(self.s[i:j]))

        # Compare while both lists have remaining elements.
        old_n = 0 ; new_n = 0
        while old_n < old_len and new_n < new_len:
            progress = old_n + new_n
            old_i, old_j, old_name = oldTag = oldList[old_n]
            new_i, new_j, new_name = newTag = newList[new_n]
            if oldTag == newTag:
                if trace and verbose: report('match',oldTag)
                old_n += 1 ; new_n += 1
            elif old_i <= new_i:
                if trace: report('del',oldTag)
                deleteList.append(oldTag)
                old_n += 1
            else:
                if trace: report('add',newTag)
                addList.append(newTag)
                new_n += 1
            assert old_n + new_n > progress

        # Add all remaining entries of the newList to delete list.
        while old_n < old_len:
            oldTag = oldList[old_n]
            if trace: report('del',oldTag)
            deleteList.append(oldTag)
            old_n += 1

        # Add all remaining entries of the newList to the add List.
        while new_n < new_len:
            newTag = newList[new_n]
            if trace: report('add',newTag)
            addList.append(newTag)
            new_n += 1

        return addList,deleteList
    #@-node:ekr.20071013072543:computeNewTags
    #@+node:ekr.20071011080442:getOldTags
    #@+at
    # tag_names(). Return a tuple containing all tags used in the widget. This 
    # includes the SEL selection tag.
    # 
    # tag_names(index). Return a tuple containing all tags used by the 
    # character at the given position.
    #   tag_nextrange
    # 
    # tag_ranges(tag). Return a tuple with start- and stop-indexes for each 
    # occurence of the given tag. If the tag doesn't exist, this method 
    # returns an empty tuple. Note that the tuple contains two items for each 
    # range.
    #@-at
    #@@c

    def getOldTags (self):

        w = self.w ; names = w.tag_names()
        trace = self.trace and self.trace_tags ; verbose = True
        if trace: g.trace('len(s)',len(self.s))
        lines = g.splitLines(self.s)
        lineIndices = [0]
        for i in range(1,len(lines)+1): # Add one more line
            lineIndices.append(lineIndices[i-1] + len(lines[i-1]))
        def quickConvertRowColToPyhthonIndex(row,col):
            return lineIndices[min(len(lineIndices)-1,row)] + col
        aList = [] ; self.oldTagsDict = {}
        for name in names:
            if name in self.tags:
                # Call Tk.tag_ranges to avoid overhead in toPython index (called from w.tag_ranges)
                tk_tags = self.Tk_Text.tag_ranges(w,name)
                # if tk_tags: g.trace(name,tk_tags)
                tags = []
                for tag in tk_tags:
                    row,col = tag.split('.')
                    row,col = int(row),int(col)
                    # This was a huge bottleneck. Adding the lines keyword helped, but not enough.
                    # tags.append(g.convertRowColToPythonIndex(self.s,row-1,col,lines=lines))
                    i = quickConvertRowColToPyhthonIndex(row-1,col)
                    # g.trace('row',row,'col',col,'i',i)
                    tags.append(i)
                if tags:
                    # g.trace(name,len(tags))
                    if (len(tags) % 2) == 0:
                        aList2 = []
                        i = 0
                        while i < len(tags):
                            a,b = tags[i],tags[i+1]
                            if a > b: a,b = b,a
                            # g.trace(name,a,b)
                            aList.append((a,b,name),)
                            aList2.append((a,b),)
                            i += 2
                        self.oldTagsDict[name] = aList2
                    else:
                        g.trace('*** can not happen: odd length of tag_ranges tuple')

        aList.sort()

        if trace and (verbose or len(aList) < 100):
            # g.trace(len(aList))
            dumpList = []
            for z in aList:
                a,b,name = z
                dumpList.append((a,b,name,self.s[a:b]),)
            g.trace('\n',g.listToString(dumpList,sort=False))
        return aList
    #@-node:ekr.20071011080442:getOldTags
    #@+node:ekr.20071010193720.42:idleHandler
    def idleHandler (self,n):

        # This is called at idle time, so there are shutdown issues.
        if not hasattr(self,'c') or not self.c.exists:
            return
        if not self.c.frame in g.app.windowList:
            # g.pr('threading_colorizer.idleHandler: window killed %d' % n)
            return

        # Do this after we know the ivars exist.
        after_time = 5 # minimum time (in msec.) before this method gets called again.
        trace = (False or self.trace) and not g.unitTesting
        verbose = (False or (self.trace and self.verbose)) and not g.unitTesting

        if self.threadCount > n:
            if verbose: g.trace('*** old thread %d' % n)
            return
        if self.killFlag:
            if verbose: g.trace('*** helper killed %d' % n)
            return
        t = self.helperThread
        if t and t.isAlive():
            if verbose: g.trace('*** helper working %d' % n)
            self.w.after(200,self.idleHandler,n)
            return

        self.completeColoring()

        if self.globalAddList:
            self.w.after(after_time,self.idleHandler,n)
        else:
            if verbose: g.trace('****** thread done %d' % n)
            # Call update_idletasks only at the very end.
            self.w.update_idletasks()
    #@-node:ekr.20071010193720.42:idleHandler
    #@+node:ekr.20071010193720.43:init
    def init (self,p):

        if not self.fake:
            self.w = self.c.frame.body.bodyCtrl

        w = self.w

        self.killFlag = False
        # self.language is set by self.updateSyntaxColorer.
        self.p = p.copy()
        self.s = w.getAllText()
        self.oldTags = []
        self.globalAddList = []
        self.newTagsDict = {}
        self.oldTagsDict = {}
        self.postPassStarted = False
        self.prev = None
        self.tagsRemoved = False
        self.init_mode(self.language)
        # self.configure_tags() # Must do this every time to support multiple editors.

        try:
            w.init_colorizer(self)
        except:
            pass
    #@-node:ekr.20071010193720.43:init
    #@+node:ekr.20071013090135:newMergeTags & helper
    def newMergeTags (self):

        result = []

        for tagName in self.tags:
            aList = self.newTagsDict.get(tagName,[])
            aList2 = self.mergeOneTag(tagName,aList)
            result.extend(aList2)

        result.sort()
        return result
    #@+node:ekr.20071013065508:mergeOneTag
    def mergeOneTag(self,tagName,aList):

        '''Return a copy of aList with all adjacent and overlapping entries merged.'''

        trace = self.trace and self.trace_tags
        if not aList: return []
        aList.sort()
        # g.trace('entry',tagName,aList)
        n = 1
        while n < len(aList):
            i,j = aList[n]
            i2,j2 = aList[n-1]
            if (i2,j2) == (i,j):
                if trace: g.trace('*** duplicate',tagName,i,j,self.s[i:j])
                aList[n-1] = None
            elif j2 == i:
                if trace: g.trace('*** new extends old',tagName,i,j,self.s[i:j])
                aList[n-1] = None
                aList[n] = i2,j
            elif i2 <= i and j <= j2:
                if trace: g.trace('*** old covers new',tagName,i,j,self.s[i:j])
                aList[n-1] = None
                aList[n] = i2,j2
            elif i <= i2 and j2 <= j:
                if trace: g.trace('*** new covers old',tagName,i,j,self.s[i:j])
                aList[n-1] = None
            n += 1

        result = [tuple((z[0],z[1],tagName),) for z in aList if z is not None]
        if trace: g.trace('%s returns' % (tagName), result)
        return result
    #@-node:ekr.20071013065508:mergeOneTag
    #@-node:ekr.20071013090135:newMergeTags & helper
    #@+node:ekr.20071013085626:putNewTags
    def putNewTags (self):

        s = self.s ; len_s = len(s); w = self.w ; limit = 500
        trace = self.trace ; verbose = self.trace and self.trace_tags

        addList = self.globalAddList[:limit]
        self.globalAddList = self.globalAddList[limit:]

        # if verbose and addList:g.trace(len(addList))

        try:
            flag = w.putNewTags(self, addList, trace, verbose)
        except AttributeError:
            flag = False

        if flag:
            return

        if self.isQt:
            for i,j,tag in addList:
                if trace and verbose: g.trace('add',tag,i,j,self.s[i:j])
                w.tag_add(tag,i,j)
        else:
            last_i = last_row = last_col = last_i = 0
            for i,j,tag in addList:
                if trace and verbose: g.trace('add',tag,i,j,self.s[i:j])
                ### x1,x2 = w.toGuiIndex(i,s=s), w.toGuiIndex(j,s=s)
                row_i,col_i = self.quickConvertPythonIndexToRowCol(i,last_row,last_col,last_i)
                last_row = row_i ; last_col = col_i ; last_i = i
                x1 = '%d.%d' % (row_i+1,col_i)
                # An important hack to extend the coloring at the very end of the text.
                if j >= len_s:
                    # g.trace('end hack:',j,s[j:])
                    x2 = 'end'
                else:
                    row_j,col_j = self.quickConvertPythonIndexToRowCol(j,last_row,last_col,last_i)
                    last_row = row_j ; last_col = col_j ; last_i = j
                    x2 = '%d.%d' % (row_j+1,col_j)
                # A crucial optimization for large body text.
                # Even so, the color_markup plugin slows down coloring considerably.
                if tag == 'docPart' or tag.startswith('comment'):
                    if not g.doHook("color-optional-markup",
                        colorer=self,p=self.p,v=self.p,s=s,i=i,j=j,colortag=tag):
                        if self.isQt:
                            w.tag_add(tag,x1,x2)
                        else:
                            self.Tk_Text.tag_add(w,tag,x1,x2)
                else:
                    # g.trace(tag,x1,x2,i,j)
                    self.Tk_Text.tag_add(w,tag,x1,x2)
    #@-node:ekr.20071013085626:putNewTags
    #@+node:ekr.20071010193720.44:removeAllImages
    def removeAllImages (self):

        # for photo,image,i in self.image_references:
            # try:
                # w = self.w
                # w.setAllText(w.getAllText())

                # # i = self.index(i)
                # # w.deleteCharacter(image)
                # # s = self.allBodyText ; w = self.w
                # # w.delete(s,i)
                # # self.allBodyText = w.getAllText()
            # except:
                # g.es_exception()
                # pass # The image may have been deleted earlier.

        self.image_references = []
    #@-node:ekr.20071010193720.44:removeAllImages
    #@+node:ekr.20071010193720.45:removeAllTags
    def removeAllTags (self):

        w = self.w

        if hasattr(w,'removeAllTags'):
            # A hook for qt plugin.
            w.removeAllTags()
        else:
            names = w.tag_names()
            i,j = w.toGuiIndex(0), w.toGuiIndex('end')
            for name in names:
                if name not in ('sel','insert'):
                    w.tag_remove(name,i,j)
    #@-node:ekr.20071010193720.45:removeAllTags
    #@+node:ekr.20071013084305:removeOldTags
    def removeOldTags (self,deleteList):

        '''Call Tk to delete all tags on deleteList.'''

        s = self.s ; len_s = len(s); w = self.w
        verbose = self.trace and self.verbose

        if hasattr(w,'removeAllTags'):
            # A hook for qt plugin.
            w.removeAllTags()
            return

        # Delete all tags on the delete list.
        last_i = last_row = last_col = last_i = 0
        for aTuple in deleteList:
            i,j,tagName = aTuple
            if i > j: i,j = j,i
            if last_i > i:
                # Restart the scan. 
                last_i = last_row = last_col = last_i = 0
                # g.trace('******* last_i > i')
            if verbose: g.trace('del',tagName,i,j,self.s[i:j])
            # w.tag_remove calls g.convertPythonIndexToRowCol,
            # so it is **much** slower than the following code.
            # w.tag_remove(tag,i,j)
            row_i,col_i = self.quickConvertPythonIndexToRowCol(i,last_row,last_col,last_i)
            last_row = row_i ; last_col = col_i ; last_i = i
            x_i = '%d.%d' % (row_i+1,col_i)
            # An important hack to extend the coloring at the very end of the text.
            if j >= len_s:
                x_j = 'end'
            else:
                row_j,col_j = self.quickConvertPythonIndexToRowCol(j,last_row,last_col,last_i)
                last_row = row_j ; last_col = col_j ; last_i = j
                x_j = '%d.%d' % (row_j+1,col_j)
            # if trace: g.trace('i',i,'x_i',x_i,'j',j,'x_j',x_j)
            self.Tk_Text.tag_remove(w,tagName,x_i,x_j)
    #@-node:ekr.20071013084305:removeOldTags
    #@+node:ekr.20071011042037:removeTagsFromRange
    def removeTagsFromRange(self,i,j):

        s = self.s ; w = self.w

        if self.trace: g.trace('i',i,'j',j)

        names = [z for z in w.tag_names() if z not in ('sel','insert')]

        x1,x2 = w.toGuiIndex(i,s=s), w.toGuiIndex(j,s=s)
        for tag in names:
            # g.trace(tag,x1,x2)
            w.tag_remove(tag,x1,x2)
    #@-node:ekr.20071011042037:removeTagsFromRange
    #@+node:ekr.20071010193720.47:tag & index (threadingColorizer)
    def index (self,i):

        w = self.w
        x1 = w.toGuiIndex(i)
        # g.trace(i,x1)
        return x1

    def tag (self,name,i,j):

        s = self.s ; w = self.w
        # g.trace(name,i,j,repr(s[i:j])) # ,g.callers())
        x1,x2 = w.toGuiIndex(i,s=s), w.toGuiIndex(j,s=s)
        w.tag_add(name,x1,x2)
    #@-node:ekr.20071010193720.47:tag & index (threadingColorizer)
    #@+node:ekr.20071010193720.49:threadColorizer
    thread_count = 0

    def threadColorizer (self,p):

        trace = self.trace and self.verbose
        if trace: g.trace(g.callers())

        # Kill the previous thread for this widget.
        t = self.helperThread
        if t and t.isAlive():
            self.killFlag = True
            if trace: g.trace('*** before join',self.threadCount)
            t.join()
            if trace: g.trace('*** after join',self.threadCount)
            self.killFlag = False
        self.helperThread = None

        # Init the ivars *after* ending the previous helper thread.
        self.init(p) # Sets 'p','s','w' and other ivars.
        g.doHook("init-color-markup",colorer=self,p=self.p,v=self.p)

        if self.killcolorFlag or not self.mode:
            self.removeAllTags()
        elif self.interruptable:
            self.threadCount += 1
            t = threading.Thread(target=self.target,kwargs={'s':self.s})
            self.helperThread = t
            t.start()
            self.w.after_idle(self.idleHandler,self.threadCount)
        else:
            self.fullColor(self.s)
            self.completeColoring()
    #@-node:ekr.20071010193720.49:threadColorizer
    #@-node:ekr.20071010193720.39:Colorers & helpers
    #@+node:ekr.20071010193720.50:In helper thread
    # Important: g.pdb, g.es or g.es_exception will crash if called from within helper thread.
    #@nonl
    #@+node:ekr.20071010193720.52:colorRangeWithTag (in helper thread)
    def colorRangeWithTag (self,s,i,j,tag,delegate='',exclude_match=False):

        '''Add an item to the globalAddList if colorizing is enabled.'''

        trace = False and not g.unitTesting

        if self.killFlag:
            if self.trace and self.verbose: g.trace('*** killed',self.threadCount)
            return

        if not self.flag: return

        if delegate:
            if trace and tag != 'match_blanks':
                g.trace('delegate %s %3s %3s %s %s' % (
                    delegate,i,j,tag,s[i:j]),g.callers(3))
            self.modeStack.append(self.modeBunch)
            self.init_mode(delegate)
            # Color everything at once, using the same indices as the caller.
            while i < j:
                progress = i
                assert j >= 0, 'colorRangeWithTag: negative j'
                for f in self.rulesDict.get(s[i],[]):
                    n = f(self,s,i)
                    if n is None:
                        g.trace('Can not happen: delegate matcher returns None')
                    elif n > 0:
                        if trace and f.__name__ != 'match_blanks':
                            g.trace('--- %3s %3s %s %s' % (
                                i,n,f.__name__,s[i:i+n]))
                        i += n ; break
                else:
                    # New in Leo 4.6: Use the default chars for everything else.
                    aList = self.newTagsDict.get(tag,[])
                    aList.append((i,i+1),)
                    self.newTagsDict[tag] = aList
                    i += 1
                assert i > progress
            bunch = self.modeStack.pop()
            self.initModeFromBunch(bunch)
        elif not exclude_match:
            # g.trace(tag,i,j)
            aList = self.newTagsDict.get(tag,[])
            aList.append((i,j),)
            self.newTagsDict[tag] = aList
    #@-node:ekr.20071010193720.52:colorRangeWithTag (in helper thread)
    #@+node:ekr.20071010193720.54:fullColor (in helper thread)
    def fullColor (self,s):

        '''Fully recolor s.'''

        trace = False and not g.unitTesting
        if trace: g.trace(self.language,'thread',self.threadCount,'len(s)',len(s))
        i = 0
        while i < len(s):
            progress = i
            if self.c.frame not in g.app.windowList:
                # g.pr('threading_colorizer.fullColor: window killed')
                return
            if self.killFlag:
                if trace: g.trace('*** killed %d' % self.threadCount)
                return
            functions = self.rulesDict.get(s[i],[])
            for f in functions:
                if trace and f.__name__ != 'match_blanks':
                    g.trace(i,f.__name__)
                n = f(self,s,i)
                if n is None or n < 0:
                    g.trace('Can not happen: matcher returns: %s f = %s' % (repr(n),repr(f)))
                    break
                elif n > 0:
                    i += n ; break
            else:
                i += 1
            assert i > progress

        if trace: g.trace('*** done',self.threadCount)
    #@nonl
    #@-node:ekr.20071010193720.54:fullColor (in helper thread)
    #@+node:ekr.20071010193720.58:jEdit matchers (in helper thread)
    #@@nocolor
    #@+at
    # 
    # The following jEdit matcher methods return the length of the matched 
    # text if the
    # match succeeds, and zero otherwise.  In most cases, these methods 
    # colorize all the matched text.
    # 
    # The following arguments affect matching:
    # 
    # - at_line_start         True: sequence must start the line.
    # - at_whitespace_end     True: sequence must be first non-whitespace text 
    # of the line.
    # - at_word_start         True: sequence must start a word.
    # - hash_char             The first character that must match in a regular 
    # expression.
    # - no_escape:            True: ignore an 'end' string if it is preceded 
    # by the ruleset's escape character.
    # - no_line_break         True: the match will not succeed across line 
    # breaks.
    # - no_word_break:        True: the match will not cross word breaks.
    # 
    # The following arguments affect coloring when a match succeeds:
    # 
    # - delegate              A ruleset name. The matched text will be colored 
    # recursively by the indicated ruleset.
    # - exclude_match         If True, the actual text that matched will not 
    # be colored.
    # - kind                  The color tag to be applied to colored text.
    #@-at
    #@@c
    #@@color
    #@+node:ekr.20071010193720.59:match_eol_span
    def match_eol_span (self,s,i,
        kind=None,seq='',
        at_line_start=False,at_whitespace_end=False,at_word_start=False,
        delegate='',exclude_match=False):

        '''Succeed if seq matches s[i:]'''

        if self.verbose: g.trace(g.callers(1),i,repr(s[i:i+20]))

        if at_line_start and i != 0 and s[i-1] != '\n': return 0
        if at_whitespace_end and i != g.skip_ws(s,0): return 0
        if at_word_start and i > 0 and s[i-1] in self.word_chars: return 0 # 7/5/2008
        if at_word_start and i + len(seq) + 1 < len(s) and s[i+len(seq)] in self.word_chars:
            return 0 # 7/5/2008

        if g.match(s,i,seq):
            #j = g.skip_line(s,i) # Include the newline so we don't get a flash at the end of the line.
            j = self.skip_line(s,i)
            self.colorRangeWithTag(s,i,j,kind,delegate=delegate,exclude_match=exclude_match)
            self.prev = (i,j,kind)
            self.trace_match(kind,s,i,j)
            return j - i
        else:
            return 0
    #@-node:ekr.20071010193720.59:match_eol_span
    #@+node:ekr.20071010193720.60:match_eol_span_regexp
    def match_eol_span_regexp (self,s,i,
        kind='',regexp='',
        at_line_start=False,at_whitespace_end=False,at_word_start=False,
        delegate='',exclude_match=False):

        '''Succeed if the regular expression regex matches s[i:].'''

        if self.verbose: g.trace(g.callers(1),i,repr(s[i:i+20]))

        if at_line_start and i != 0 and s[i-1] != '\n': return 0
        if at_whitespace_end and i != g.skip_ws(s,0): return 0
        if at_word_start and i > 0 and s[i-1] in self.word_chars: return 0 # 7/5/2008

        n = self.match_regexp_helper(s,i,regexp)
        if n > 0:
            # j = g.skip_line(s,i) # Include the newline so we don't get a flash at the end of the line.
            j = self.skip_line(s,i)
            self.colorRangeWithTag(s,i,j,kind,delegate=delegate,exclude_match=exclude_match)
            self.prev = (i,j,kind)
            self.trace_match(kind,s,i,j)
            return j - i
        else:
            return 0
    #@nonl
    #@-node:ekr.20071010193720.60:match_eol_span_regexp
    #@+node:ekr.20081014064755.1:match_everything
    # def match_everything (self,s,i,kind,delegate):

        # '''A hack for phpsection mode: match the entire text and color with delegate.'''

        # j = len(s)

        # self.colorRangeWithTag(s,i,j,kind,delegate=delegate)

        # return j-i
    #@-node:ekr.20081014064755.1:match_everything
    #@+node:ekr.20071010193720.61:match_keywords
    # This is a time-critical method.
    def match_keywords (self,s,i):

        '''Succeed if s[i:] is a keyword.'''

        # We must be at the start of a word.
        if i > 0 and s[i-1] in self.word_chars:
            return 0

        # Get the word as quickly as possible.
        j = i ; n = len(s) ; chars = self.word_chars
        while j < n and s[j] in chars:
            j += 1

        word = s[i:j]
        if self.ignore_case: word = word.lower()
        kind = self.keywordsDict.get(word)
        if kind:
            self.colorRangeWithTag(s,i,j,kind)
            self.prev = (i,j,kind)
            result = j - i
            # g.trace('success',word,kind,j-i)
            # g.trace('word in self.keywordsDict.keys()',word in self.keywordsDict.keys())
            self.trace_match(kind,s,i,j)
            return result
        else:
            # g.trace('fail',word,kind)
            # g.trace('word in self.keywordsDict.keys()',word in self.keywordsDict.keys())
            return 0
    #@-node:ekr.20071010193720.61:match_keywords
    #@+node:ekr.20071010193720.62:match_mark_following & getNextToken
    def match_mark_following (self,s,i,
        kind='',pattern='',
        at_line_start=False,at_whitespace_end=False,at_word_start=False,
        exclude_match=False):

        '''Succeed if s[i:] matches pattern.'''

        if not self.allow_mark_prev: return 0

        if self.verbose: g.trace(g.callers(1),i,repr(s[i:i+20]))

        if at_line_start and i != 0 and s[i-1] != '\n': return 0
        if at_whitespace_end and i != g.skip_ws(s,0): return 0
        if at_word_start and i > 0 and s[i-1] in self.word_chars: return 0 # 7/5/2008
        if at_word_start and i + len(pattern) + 1 < len(s) and s[i+len(pattern)] in self.word_chars:
            return 0 # 7/5/2008

        if g.match(s,i,pattern):
            j = i + len(pattern)
            self.colorRangeWithTag(s,i,j,kind,exclude_match=exclude_match)
            k = self.getNextToken(s,j)
            if k > j:
                self.colorRangeWithTag(s,j,k,kind,exclude_match=False)
                j = k
            self.prev = (i,j,kind)
            self.trace_match(kind,s,i,j)
            return j - i
        else:
            return 0
    #@+node:ekr.20071010193720.63:getNextToken
    def getNextToken (self,s,i):

        '''Return the index of the end of the next token for match_mark_following.

        The jEdit docs are not clear about what a 'token' is, but experiments with jEdit
        show that token means a word, as defined by word_chars.'''

        while i < len(s) and s[i] in self.word_chars:
            i += 1

        return min(len(s),i+1)
    #@nonl
    #@-node:ekr.20071010193720.63:getNextToken
    #@-node:ekr.20071010193720.62:match_mark_following & getNextToken
    #@+node:ekr.20071010193720.64:match_mark_previous
    def match_mark_previous (self,s,i,
        kind='',pattern='',
        at_line_start=False,at_whitespace_end=False,at_word_start=False,
        exclude_match=False):

        '''Return the length of a matched SEQ or 0 if no match.

        'at_line_start':    True: sequence must start the line.
        'at_whitespace_end':True: sequence must be first non-whitespace text of the line.
        'at_word_start':    True: sequence must start a word.'''

        if not self.allow_mark_prev: return 0

        if self.verbose: g.trace(g.callers(1),i,repr(s[i:i+20]))

        if at_line_start and i != 0 and s[i-1] != '\n': return 0
        if at_whitespace_end and i != g.skip_ws(s,0): return 0
        if at_word_start and i > 0 and s[i-1] in self.word_chars: return 0 # 7/5/2008
        if at_word_start and i + len(pattern) + 1 < len(s) and s[i+len(pattern)] in self.word_chars:
            return 0 # 7/5/2008

        if g.match(s,i,pattern):
            j = i + len(pattern)
            # Color the previous token.
            if self.prev:
                i2,j2,kind2 = self.prev
                # g.trace(i2,j2,kind2)
                self.colorRangeWithTag(s,i2,j2,kind2,exclude_match=False)
            if not exclude_match:
                self.colorRangeWithTag(s,i,j,kind)
            self.prev = (i,j,kind)
            self.trace_match(kind,s,i,j)
            return j - i
        else:
            return 0
    #@-node:ekr.20071010193720.64:match_mark_previous
    #@+node:ekr.20071010193720.65:match_regexp_helper
    def match_regexp_helper (self,s,i,pattern):

        '''Return the length of the matching text if seq (a regular expression) matches the present position.'''

        if self.verbose: g.trace(g.callers(1),i,repr(s[i:i+20]),'pattern',pattern)
        trace = False

        try:
            flags = re.MULTILINE
            if self.ignore_case: flags|= re.IGNORECASE
            re_obj = re.compile(pattern,flags)
        except Exception:
            # Bug fix: 2007/11/07: do not call g.es here!
            g.trace('Invalid regular expression: %s' % (pattern))
            return 0

        # Match succeeds or fails more quickly than search.
        # g.trace('before')
        self.match_obj = mo = re_obj.match(s,i) # re_obj.search(s,i) 
        # g.trace('after')

        if mo is None:
            return 0
        else:
            start, end = mo.start(), mo.end()
            if start != i: # Bug fix 2007-12-18: no match at i
                return 0
            if trace:
                g.trace('pattern',pattern)
                g.trace('match: %d, %d, %s' % (start,end,repr(s[start: end])))
                g.trace('groups',mo.groups())
            return end - start
    #@-node:ekr.20071010193720.65:match_regexp_helper
    #@+node:ekr.20071010193720.66:match_seq
    def match_seq (self,s,i,
        kind='',seq='',
        at_line_start=False,at_whitespace_end=False,at_word_start=False,
        delegate=''):

        '''Succeed if s[:] mathces seq.'''

        if at_line_start and i != 0 and s[i-1] != '\n':
            j = i
        elif at_whitespace_end and i != g.skip_ws(s,0):
            j = i
        elif at_word_start and i > 0 and s[i-1] in self.word_chars:  # 7/5/2008
            j = i
        if at_word_start and i + len(seq) + 1 < len(s) and s[i+len(seq)] in self.word_chars:
            j = i # 7/5/2008
        elif g.match(s,i,seq):
            j = i + len(seq)
            self.colorRangeWithTag(s,i,j,kind,delegate=delegate)
            self.prev = (i,j,kind)
            self.trace_match(kind,s,i,j)
        else:
            j = i
        return j - i
    #@nonl
    #@-node:ekr.20071010193720.66:match_seq
    #@+node:ekr.20071010193720.67:match_seq_regexp
    def match_seq_regexp (self,s,i,
        kind='',regexp='',
        at_line_start=False,at_whitespace_end=False,at_word_start=False,
        delegate=''):

        '''Succeed if the regular expression regexp matches at s[i:].'''

        if self.verbose: g.trace(g.callers(1),i,repr(s[i:i+20]),'regexp',regexp)

        if at_line_start and i != 0 and s[i-1] != '\n': return 0
        if at_whitespace_end and i != g.skip_ws(s,0): return 0
        if at_word_start and i > 0 and s[i-1] in self.word_chars: return 0 # 7/5/2008

        # g.trace('before')
        n = self.match_regexp_helper(s,i,regexp)
        # g.trace('after')
        j = i + n # Bug fix: 2007-12-18
        assert (j-i == n)
        self.colorRangeWithTag(s,i,j,kind,delegate=delegate)
        self.prev = (i,j,kind)
        self.trace_match(kind,s,i,j)
        return j - i
    #@nonl
    #@-node:ekr.20071010193720.67:match_seq_regexp
    #@+node:ekr.20071010193720.68:match_span & helper
    def match_span (self,s,i,
        kind='',begin='',end='',
        at_line_start=False,at_whitespace_end=False,at_word_start=False,
        delegate='',exclude_match=False,
        no_escape=False,no_line_break=False,no_word_break=False):

        '''Succeed if s[i:] starts with 'begin' and contains a following 'end'.'''

        if at_line_start and i != 0 and s[i-1] != '\n':
            j = i
        elif at_whitespace_end and i != g.skip_ws(s,0):
            j = i
        elif at_word_start and i > 0 and s[i-1] in self.word_chars: # 7/5/2008
            j = i
        elif at_word_start and i + len(begin) + 1 < len(s) and s[i+len(begin)] in self.word_chars:
            j = i # 7/5/2008
        elif not g.match(s,i,begin):
            j = i
        else:
            j = self.match_span_helper(s,i+len(begin),end,no_escape,no_line_break,no_word_break=no_word_break)
            if j == -1:
                j = i
            else:
                i2 = i + len(begin) ; j2 = j + len(end)
                # g.trace(i,j,s[i:j2],kind)
                if delegate:
                    self.colorRangeWithTag(s,i,i2,kind,delegate=None,    exclude_match=exclude_match)
                    self.colorRangeWithTag(s,i2,j,kind,delegate=delegate,exclude_match=exclude_match)
                    self.colorRangeWithTag(s,j,j2,kind,delegate=None,    exclude_match=exclude_match)
                else: # avoid having to merge ranges in addTagsToList.
                    self.colorRangeWithTag(s,i,j2,kind,delegate=None,exclude_match=exclude_match)
                j = j2
                self.prev = (i,j,kind)

        self.trace_match(kind,s,i,j)
        return j - i
    #@+node:ekr.20071010193720.69:match_span_helper
    def match_span_helper (self,s,i,pattern,no_escape,no_line_break,no_word_break=False):

        '''Return n >= 0 if s[i] ends with a non-escaped 'end' string.'''

        esc = self.escape

        while 1:
            j = s.find(pattern,i)
            if j == -1:
                # Match to end of text if not found and no_line_break is False
                if no_line_break:
                    return -1
                else:
                    return len(s)
            elif no_word_break and j > 0 and s[j-1] in self.word_chars:
                return -1 # New in Leo 4.5.
            elif no_line_break and '\n' in s[i:j]:
                return -1
            elif esc and not no_escape:
                # Only an odd number of escapes is a 'real' escape.
                escapes = 0 ; k = 1
                while j-k >=0 and s[j-k] == esc:
                    escapes += 1 ; k += 1
                if (escapes % 2) == 1:
                    # Continue searching past the escaped pattern string.
                    i = j + len(pattern) # Bug fix: 7/25/07.
                    # g.trace('escapes',escapes,repr(s[i:]))
                else:
                    return j
            else:
                return j
    #@nonl
    #@-node:ekr.20071010193720.69:match_span_helper
    #@-node:ekr.20071010193720.68:match_span & helper
    #@+node:ekr.20071010193720.70:match_span_regexp
    def match_span_regexp (self,s,i,
        kind='',begin='',end='',
        at_line_start=False,at_whitespace_end=False,at_word_start=False,
        delegate='',exclude_match=False,
        no_escape=False,no_line_break=False, no_word_break=False,
    ):

        '''Succeed if s[i:] starts with 'begin' (a regular expression) and contains a following 'end'.'''

        if self.verbose: g.trace('begin',repr(begin),'end',repr(end),self.dump(s[i:]))

        if at_line_start and i != 0 and s[i-1] != '\n': return 0
        if at_whitespace_end and i != g.skip_ws(s,0): return 0
        if at_word_start and i > 0 and s[i-1] in self.word_chars: return 0 # 7/5/2008
        if at_word_start and i + len(begin) + 1 < len(s) and s[i+len(begin)] in self.word_chars:
            return 0 # 7/5/2008

        n = self.match_regexp_helper(s,i,begin)
        # We may have to allow $n here, in which case we must use a regex object?
        if n > 0:
            j = i + n
            j2 = s.find(end,j)
            if j2 == -1: return 0
            if self.escape and not no_escape:
                # Only an odd number of escapes is a 'real' escape.
                escapes = 0 ; k = 1
                while j-k >=0 and s[j-k] == self.escape:
                    escapes += 1 ; k += 1
                if (escapes % 2) == 1:
                    # An escaped end **aborts the entire match**:
                    # there is no way to 'restart' the regex.
                    return 0
            i2 = j2 - len(end)
            if delegate:
                self.colorRangeWithTag(s,i,j,kind, delegate=None,     exclude_match=exclude_match)
                self.colorRangeWithTag(s,j,i2,kind, delegate=delegate,exclude_match=False)
                self.colorRangeWithTag(s,i2,j2,kind,delegate=None,    exclude_match=exclude_match)
            else: # avoid having to merge ranges in addTagsToList.
                self.colorRangeWithTag(s,i,j2,kind,delegate=None,exclude_match=exclude_match)
            self.prev = (i,j,kind)
            self.trace_match(kind,s,i,j2)
            return j2 - i
        else: return 0
    #@-node:ekr.20071010193720.70:match_span_regexp
    #@+node:ekr.20080703111151.19:match_word_and_regexp
    def match_word_and_regexp (self,s,i,
        kind1='',word='',
        kind2='',pattern='',
        at_line_start=False,at_whitespace_end=False,at_word_start=False,
        exclude_match=False):

        '''Succeed if s[i:] matches pattern.'''

        if not self.allow_mark_prev: return 0

        if (False or self.verbose): g.trace(i,repr(s[i:i+20]))

        if at_line_start and i != 0 and s[i-1] != '\n': return 0
        if at_whitespace_end and i != g.skip_ws(s,0): return 0
        if at_word_start and i > 0 and s[i-1] in self.word_chars: return 0 # 7/5/2008
        if at_word_start and i + len(word) + 1 < len(s) and s[i+len(word)] in self.word_chars:
            j = i # 7/5/2008

        if not g.match(s,i,word):
            return 0

        j = i + len(word)
        n = self.match_regexp_helper(s,j,pattern)
        # g.trace(j,pattern,n)
        if n == 0:
            return 0
        self.colorRangeWithTag(s,i,j,kind1,exclude_match=exclude_match)
        k = j + n
        self.colorRangeWithTag(s,j,k,kind2,exclude_match=False)    
        self.prev = (j,k,kind2)
        self.trace_match(kind1,s,i,j)
        self.trace_match(kind2,s,j,k)
        return k - i
    #@-node:ekr.20080703111151.19:match_word_and_regexp
    #@+node:ekr.20080929035109.1:skip_line
    def skip_line (self,s,i):

        if self.escape:
            escape = self.escape + '\n'
            n = len(escape)
            while i < len(s):
                j = g.skip_line(s,i)
                if not g.match(s,j-n,escape):
                    return j
                # g.trace('escape',s[i:j])
                i = j
            return i
        else:
            return g.skip_line(s,i)
                # Include the newline so we don't get a flash at the end of the line.
    #@nonl
    #@-node:ekr.20080929035109.1:skip_line
    #@-node:ekr.20071010193720.58:jEdit matchers (in helper thread)
    #@+node:ekr.20071010193720.57:target (in helper thread)
    def target(self,*args,**keys):

        s = keys.get('s')
        # if self.trace: g.trace(self.threadCount)
        try:
            self.fullColor(s)
            return "ok" # for testing.
        except:
            # We can not use g.es_exception: it calls Tk methods.
            traceback.print_exc()
            return "error" # for unit testing.
    #@-node:ekr.20071010193720.57:target (in helper thread)
    #@-node:ekr.20071010193720.50:In helper thread
    #@+node:ekr.20071010193720.71:Utils
    #@+at 
    #@nonl
    # These methods are like the corresponding functions in leoGlobals.py 
    # except they issue no error messages.
    #@-at
    #@+node:ekr.20071010193720.72:dump
    def dump (self,s):

        if s.find('\n') == -1:
            return s
        else:
            return '\n' + s + '\n'
    #@nonl
    #@-node:ekr.20071010193720.72:dump
    #@+node:ekr.20071010193720.73:munge
    def munge(self,s):

        '''Munge a mode name so that it is a valid python id.'''

        valid = string.ascii_letters + string.digits + '_'

        return ''.join([g.choose(ch in valid,ch.lower(),'_') for ch in s])
    #@nonl
    #@-node:ekr.20071010193720.73:munge
    #@+node:ekr.20071011201952:quickConvertPythonIndexToRowCol
    def quickConvertPythonIndexToRowCol(self,i,last_row,last_col,last_i):

        # trace = False and self.trace
        # if trace: g.trace('i',i,'last_row',last_row,'last_col',last_col,'last_i',last_i)
        s = self.s
        row = s.count('\n',last_i,i) # Don't include i
        # if trace: g.trace('row',row)
        if row == 0:
            # if trace: g.trace('returns',last_row,last_col+i-last_i)
            return last_row,last_col+i-last_i
        else:
            prevNL = s.rfind('\n',last_i,i) # Don't include i
            # if trace: g.trace('prevNL',prevNL,'returns',last_row+row,i-prevNL-1)
            return last_row+row,i-prevNL-1
    #@-node:ekr.20071011201952:quickConvertPythonIndexToRowCol
    #@+node:ekr.20071010193720.74:scanColorDirectives
    def scanColorDirectives(self,p):

        """Scan position p and p's ancestors looking for @comment, @language and @root directives,
        setting corresponding colorizer ivars.
        """

        p = p.copy() ; c = self.c
        if c == None: return # self.c may be None for testing.

        self.language = language = c.target_language
        self.comment_string = None
        self.rootMode = None # None, "code" or "doc"

        for p in p.self_and_parents():
            theDict = g.get_directives_dict(p)
            #@        << Test for @comment or @language >>
            #@+node:ekr.20071010193720.75:<< Test for @comment or @language >>
            # @comment and @language may coexist in the same node.

            if 'comment' in theDict:
                self.comment_string = theDict["comment"]

            if 'language' in theDict:
                s = theDict["language"]
                i = g.skip_ws(s,0)
                j = g.skip_c_id(s,i)
                self.language = s[i:j].lower()

            if 'comment' in theDict or 'language' in theDict:
                break
            #@nonl
            #@-node:ekr.20071010193720.75:<< Test for @comment or @language >>
            #@nl
            #@        << Test for @root, @root-doc or @root-code >>
            #@+node:ekr.20071010193720.76:<< Test for @root, @root-doc or @root-code >>
            if 'root' in theDict and not self.rootMode:

                s = theDict["root"]
                if g.match_word(s,0,"@root-code"):
                    self.rootMode = "code"
                elif g.match_word(s,0,"@root-doc"):
                    self.rootMode = "doc"
                else:
                    doc = c.config.at_root_bodies_start_in_doc_mode
                    self.rootMode = g.choose(doc,"doc","code")
            #@nonl
            #@-node:ekr.20071010193720.76:<< Test for @root, @root-doc or @root-code >>
            #@nl

        # g.trace('new colorizer',self.language)

        return self.language # For use by external routines.
    #@nonl
    #@-node:ekr.20071010193720.74:scanColorDirectives
    #@+node:ekr.20071010193720.77:setFontFromConfig
    def setFontFromConfig (self):

        c = self.c
        isQt = g.app.gui.guiName() == 'qt'

        self.bold_font = c.config.getFontFromParams(
            "body_text_font_family", "body_text_font_size",
            "body_text_font_slant",  "body_text_font_weight",
            c.config.defaultBodyFontSize) # , tag = "colorer bold")

        if self.bold_font and not isQt:
            self.bold_font.configure(weight="bold")

        self.italic_font = c.config.getFontFromParams(
            "body_text_font_family", "body_text_font_size",
            "body_text_font_slant",  "body_text_font_weight",
            c.config.defaultBodyFontSize) # , tag = "colorer italic")

        if self.italic_font and not isQt:
            self.italic_font.configure(slant="italic",weight="normal")

        self.bolditalic_font = c.config.getFontFromParams(
            "body_text_font_family", "body_text_font_size",
            "body_text_font_slant",  "body_text_font_weight",
            c.config.defaultBodyFontSize) # , tag = "colorer bold italic")

        if self.bolditalic_font and not isQt:
            self.bolditalic_font.configure(weight="bold",slant="italic")

        self.color_tags_list = []
        self.image_references = []
    #@nonl
    #@-node:ekr.20071010193720.77:setFontFromConfig
    #@+node:ekr.20071010193720.78:trace_match
    def trace_match(self,kind,s,i,j):

        if j != i and self.trace_match_flag:
            g.trace(kind,i,j,g.callers(2),self.dump(s[i:j]))
    #@nonl
    #@-node:ekr.20071010193720.78:trace_match
    #@-node:ekr.20071010193720.71:Utils
    #@-others
#@-node:ekr.20071010193720.19:class colorizer
#@-others

#@<< class nullColorizer (colorizer) >>
#@+node:ekr.20071010193720.79:<< class nullColorizer (colorizer) >>
class nullColorizer (colorizer):

    """A do-nothing colorer class"""

    #@    @+others
    #@+node:ekr.20071010193720.80:__init__
    def __init__ (self,c):

        colorizer.__init__(self,c) # init the base class.

        self.c = c
        self.enabled = False
    #@-node:ekr.20071010193720.80:__init__
    #@+node:ekr.20071010193720.81:entry points
    def colorize(self,p,incremental=False,interruptable=True): return 'ok' # used by unit tests.

    def disable(self): pass

    def enable(self): pass

    def recolor_range(self,p,leading,trailing): pass

    def scanColorDirectives(self,p): pass

    def schedule(self,p,incremental=0): pass

    def updateSyntaxColorer (self,p): pass
    #@nonl
    #@-node:ekr.20071010193720.81:entry points
    #@-others
#@nonl
#@-node:ekr.20071010193720.79:<< class nullColorizer (colorizer) >>
#@nl
#@nonl
#@-node:ekr.20071010193720:@thin threading_colorizer.py  
#@-leo
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.