Persistent highlight of characters (e.g. no-break space)



  • Is there a way to highlight/underscore a set of user-defined symbols in a persistent global manner like a spell-checker does it? For example, I’d like to to show red wavy/squiggly underscore lines for a No-Break Space (U+00A0) (FYI here is a Sublime plugin that does it), is it currently possible via a plugin/script or some kind of language file modification?

    I’ve found this post that shows how to show a different symbol via e.g.

    editor.setRepresentation(u'\u200B', "ZWS")
    

    but this breaks alignment and also looks ugly.

    I’ve also found this post that has a regex to catch all zero-width chars and has a great suggestion to mark them all (there is also a nice option in the mark menu to bookmark the lines for extra visibility)
    However, this highlighting is not dynamic, e.g. if I open another file or paste another zero-width char, I’d have to re-run the command.
    Also, I don’t know how to make these steps from within a Python script so I could add a menu button to find all + mark all + bookmark all for me.

    This EnhanceUDLLexer script solves the persistency issue as it applies highlighting in the visible buffer on-the-fly, however, it only applies a different color as a styling method, which is unfortunately useless for spaces.

    So, I’m ideally looking for a way to add the following commands to the last script:

    • Add a specific mark style to the matched characters (these would show as vertical color lines in place of zero-width chars and colored boxes for non zero-width)
    • Add wiggly underscore highlighting style to the matched characters (these would be useful for non zero-width chars, e.g. no-break spaces)
    • Bookmark line for the same matches

    Or maybe there is another/better way to achieve the ability to highlight an arbitrary group of characters (including whitespace)
    Thanks for any suggestions!



  • I guess we can make a script which might do what you want.
    So am I right you want to have

    • dynamic marking on every document
    • bookmarking the line with the same marks as npp uses to be able to use F2 to jump to it

    To be honest, I never tried to use two indicators on the same character,
    so I’m not 100% sure we can use the INDIC_ROUNDBOX and the INDIC_SQUIGGLE together.



  • @eugenesvk

    just a test to see if two indicators can be used together.
    YES we can.

    038af67a-24d9-42fd-8da3-169c280dc2f2-image.png



  • Thank you for a prompt response!

    @Ekopalypse said in Persistent highlight of characters (e.g. no-break space):

    • dynamic marking on every document

    Just to clarify: not every document I have opened, but on any document in the current tab. I mean that this highlighting would be independent of any syntax/user-defined language etc. And ideally also only for the visible text buffer just like the script cited above does as I’ve noticed in Sublime that this king of highlighting could be a performance-killing feature, which isn’t great

    • bookmarking the line with the same marks as npp uses to be able to use F2 to jump to it

    Yes, these marks (a very nice feature for navigation)

    To be honest, I never tried to use two [indicators]

    Just to clarify, I don’t need BOTH at the same time, I wanted to be able to use one for one group and another for another group (e.g. I’d prefer an underline for non zero-width chars so it’s not as visible as a box).

    I just wanted to differentiate between whatever the mark style is (I actually don’t know what exactly it is, is it always INDIC_ROUNDBOX that turns into a vertical line for zero-width chars?) and a user-defined style that you mention (INDIC_SQUIGGLE) as I thought these could be two different implementations:

    1. the mark style could be invoked via a “open search → mark menu → insert search string → select mark all” type of command and the other style
    2. the other style would be applied “directly” to the regex strings found by the script


  • @eugenesvk said in Persistent highlight of characters (e.g. no-break space):

    Just to clarify: not every document I have opened, but on any document in the current tab.

    Yes, I meant the same. Regardless of the file type and the assigned lexer,
    when you run the script it should color what it finds.

    Only this visual area - ok

    By mark style I guess you refer to the find dialog mark tab and its marking function, correct?
    I assume it is using INDIC_FULLBOX.

    the mark style could be invoked via a “open search → mark menu → insert search string → select mark all” type of command and the other style

    I don’t know if I understand this correctly.
    I can change the general behavior of the builtin marking style,
    to be a squiggle line for example but I cannot add
    another marking feature to the find dialog.

    What a script can do, from ui point of view is,

    • it can be executed via context menu (right click within a document, not the tab)
    • it can be executed from within the pythonscript plugin menu
    • it can be executed via keyboard short cut.

    From functional point of view a script can be written to either

    • ask for the search string and maybe indicator and do the job
    • do multiple searches within the script with different indicators per regex for example

    As it is already ~midnight here I will follow up on this tomorrow.



  • Only this visual area - ok

    Yep, if it’s dynamic, I don’t need to highlight the (invisible) chars in the invisible area :)

    @Ekopalypse said in Persistent highlight of characters (e.g. no-break space):

    By mark style I guess you refer to the find dialog mark tab and its marking function, correct?

    Yes

    to be a squiggle line for example but I cannot add
    another marking feature to the find dialog

    That’s fine, I’m not looking for such a UI change. I was just describing a potential way to achieve the highligting, just maybe the only way to automate the search&mark operation would’ve been to record a macro with the steps mentioned above. I just don’t know enough about what’s possible via scripting, that’s why I mentioned the mark style separately

    What you describe next about the script is perfectly fine as the only implementation, and I’m not even looking for the extra dynamic user-input functionality like this

    ask for the search string and maybe indicator and do the job

    (though you maybe didn’t mean it as you’ve put it in functionality, not UI)
    I’d be perfectly fine with defining regex groups of characters and their respective indicator type+color in the script itself (similarly to how EnhanceUDLLexer script defines regex groups and colors, but also adding an indicator type), as I’d only set it once

    (By the way, I didn’t know you could execute script also from a context menu, thanks for mentioning it, would I need to add some type of reference to this script in the contextMenu.xml config file?)

    As it is already ~midnight here I will follow up on this tomorrow

    Thanks a bunch!



  • @eugenesvk

    Ok lunch break gave me some time to already play with it.
    What about this

    # -*- coding: utf-8 -*-
    
    from Npp import editor, editor1, editor2, notepad, SCINTILLANOTIFICATION, INDICATORSTYLE
    import ctypes
    from ctypes import wintypes
    from collections import OrderedDict
    regexes = OrderedDict()
    
    # ------------------------------------------------- configuration area ---------------------------------------------------
    #
    # List of defined/used indicators
    #   Npp uses ids 21-31, lexer should use 0-7 which leaves plugins to use 8-20
    #   As other plugins might make usage of indicators as well,
    #   it needs to be tested which are free to use.
    #   Each indicator can be defined with the following attributes
    #       - an uique id starting from 8 to 20 (required)
    #       - an indicator style (required) (see https://www.scintilla.org/ScintillaDoc.html#Indicators which are available)
    #       - a foreground color (optional) (color are tuples containing the red, green and blue value like (255,0,0) )
    #       - an alpha transparency value (optional) (range 0-255)
    #       - an outline alpha transparency value (optional) (range 0-255)
    #       - a boolean value which makes indicator drawn under text or over(default) (optional)
    #   example:
    #      indicators = [(8, INDICATORSTYLE.ROUNDBOX),
    #                    (9, INDICATORSTYLE.SQUIGGLE, (100,215,100)),
    #                    (10, INDICATORSTYLE.GRADIENT, None, 55, None, False)
    #                      
    # Definition of colors and regular expressions
    #   Note, the order in which regular expressions will be processed
    #   is determined by its creation, that is, the first definition is processed first, then the 2nd, and so on
    #
    #   The basic structure always looks like this
    #
    #   regexes[a] = (b, c)
    #
    #   regexes = an ordered dictionary which ensures that the regular expressions are always processed in the same order
    #   a = an indicator id
    #   b = raw byte string, describes the regular expression. Example r'\w+'
    #   c = number of the match group to be used
    
    
    # Examples:
    #   All occurances of word editor, except editor1 and editor2
    #   should be indicated by a roundbox by using
    #   the indicator id 8 and the results from match group 1
    regexes[8] = (r'editor1|editor2|(editor)', 1)
    
    #   All numbers should be indicated by an squiggled lined,
    #   using indicator id 9  and the results from matchgroup 0.
    regexes[9] = (r'\d', 0)
    
    indicator_list = [(8, INDICATORSTYLE.ROUNDBOX),
                      (9, INDICATORSTYLE.SQUIGGLE, (255,0,0)),]
    
    # ------------------------------------------------ /configuration area ---------------------------------------------------
    
    try:
        AutoMarker().main()
    except NameError:
        user32 = ctypes.WinDLL('user32')
    
        class SingletonAutoMarker(type):
            '''
                Ensures, more or less, that only one
                instance of the main class can be instantiated
            '''
            _instance = None
            def __call__(cls, *args, **kwargs):
                if cls._instance is None:
                    cls._instance = super(SingletonAutoMarker, cls).__call__(*args, **kwargs)
                return cls._instance
    
    
        class AutoMarker(object):
            ''' Colors within visual area based on found matches. '''
            __metaclass__ = SingletonAutoMarker
    
            def __init__(self):
                '''
                    Instantiated the class,
                    because of __metaclass__ = ... usage, is called once only.
                '''
                editor.callbackSync(self.on_updateui, [SCINTILLANOTIFICATION.UPDATEUI])
                self.npp_hwnd = user32.FindWindowW(u'Notepad++', None)
                self.editor1_hwnd = None
                self.editor2_hwnd = None
                self.__enum_scintilla_hwnds()
                self.configure()
    
    
            def __enum_scintilla_hwnds(self):
                '''
                    Helper function
                    Enumerates the window handles for editor1 and editor2
    
                    Args:
                        None
                    Returns:
                        None
                '''
                EnumWindowsProc = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
    
                def foreach_window(hwnd, lParam):
                    curr_class = ctypes.create_unicode_buffer(256)
                    user32.GetClassNameW(hwnd, curr_class, 256)
    
                    if (curr_class.value == u'Scintilla') and (user32.GetParent(hwnd) == self.npp_hwnd):
                        if self.editor1_hwnd is None:
                            self.editor1_hwnd = hwnd
                        elif self.editor2_hwnd is None:
                            self.editor2_hwnd = hwnd
                            return False
                    return True
    
                user32.EnumChildWindows(self.npp_hwnd, EnumWindowsProc(foreach_window), 0)
    
    
            @staticmethod
            def paint_it(indicator_id, pos, length):
                '''
                    This is where the actual coloring takes place.
                    Color, the position of the first character and
                    the length of the text to be colored must be provided.
                    Coloring occurs only if the position is not within the excluded range.
    
                    Args:
                        indicator_id = integer, expected in range of 0-16777215
                        pos = integer,  denotes the start position
                        length = integer, denotes how many chars need to be colored.
                    Returns:
                        None
                '''
                editor.setIndicatorCurrent(indicator_id)
                editor.indicatorFillRange(pos, length)
                editor.markerAdd(editor.lineFromPosition(pos), 24)
    
    
            def style(self):
                '''
                    Calculates the text area to be searched for in the current document.
                    Calls up the regexes to find the position and
                    calculates the length of the text to be colored.
                    Deletes the old indicators before setting new ones.
    
                    Args:
                        None
                    Returns:
                        None
                '''
                start_line = editor.docLineFromVisible(editor.getFirstVisibleLine())
                end_line = editor.docLineFromVisible(start_line + editor.linesOnScreen())
                start_position = editor.positionFromLine(start_line)
                end_position = editor.getLineEndPosition(end_line)
                
                for indicator in indicator_list:
                    editor.setIndicatorCurrent(indicator[0])
                    editor.indicatorClearRange(indicator[0], editor.getTextLength())
                
                for id, _regex in regexes.items():
                    editor.research(_regex[0],
                                    lambda m: self.paint_it(id,
                                                            m.span(_regex[1])[0],
                                                            m.span(_regex[1])[1] - m.span(_regex[1])[0]),
                                    0,
                                    start_position,
                                    end_position)
    
    
            def configure(self):
                '''
                    Define basic indicator settings and the needed regexes.
    
                    Args:
                        None
                    Returns:
                        None
                '''
                def set_indicator_attributes(indicator_number, 
                                             indicator_style, 
                                             fore=None, 
                                             alpha=None,
                                             outline_alpha=None, 
                                             under=None):
                    ''' helper function to set indicators '''
                    editor1.indicSetStyle(indicator_number, indicator_style)
                    editor2.indicSetStyle(indicator_number, indicator_style)
                    editor1.indicSetAlpha(indicator_number, alpha or 55)
                    editor2.indicSetAlpha(indicator_number, alpha or 55)
                    editor1.indicSetFore(indicator_number, fore or (100,215,100))
                    editor2.indicSetFore(indicator_number, fore or (100,215,100))
                    editor1.indicSetOutlineAlpha(indicator_number, outline_alpha or 255)
                    editor2.indicSetOutlineAlpha(indicator_number, outline_alpha or 255)
                    editor1.indicSetUnder(indicator_number, under or True)
                    editor2.indicSetUnder(indicator_number, under or True)
    
                for indicator in indicator_list:
                    set_indicator_attributes(*indicator)
    
    
            def on_updateui(self, args):
                '''
                    Callback which gets called every time scintilla
                    (aka the editor) changed something within the document.
    
                    Triggers the styling function if the document is of interest.
    
                    Args:
                        provided by scintilla but none are of interest
                    Returns:
                        None
                '''
                self.style()
    
    
            def main(self):
                '''
                    Main function entry point.
                    Simulates updateui event to enforce
                    checking the document for the first time uasge.
    
                    Args:
                        None
                    Returns:
                        None
                '''
                self.on_updateui(None)
    
        AutoMarker().main()
    

    Concerning the contect menu, yes, you have to add something like

    <Item FolderName="SUBFOLDERNAME" PluginEntryName="Python Script" PluginCommandItemName="SCRIPTNAME_WITHOUT_EXTENSION" ItemNameAs="NAME_HOW_IT_SHOULD_APPEAR"/>



  • @Ekopalypse
    Thanks a lot for the script, it’s working great!

    Just a few questions:

    1. Is it possible to make the script toggle itself? This way I’d can click a menu button to turn it on/off
    2. Also, is there a chance to add the bookmark as an option for a regex group as I don’t need them for all the characters, but only for some?
    3. During performance testing I’ve noticed that ~100 of characters would make it choke and slow down caret movement refresh rate, i.e. if I hold the Right key (without any edits and without moving the screen buffer), the caret would disappear and appear only when I release it (the position would change). I was wondering whether this is due to it auto-updating too fast or someting and if the script could maybe poll for changes slower? Granted, in real documents I never have so many of characters for this script to work with, so it’s not a big issue


  • @eugenesvk

    # -*- coding: utf-8 -*-
    
    from Npp import editor, editor1, editor2, notepad, SCINTILLANOTIFICATION, INDICATORSTYLE
    import ctypes
    from ctypes import wintypes
    from collections import OrderedDict
    regexes = OrderedDict()
    
    # ------------------------------------------------- configuration area ---------------------------------------------------
    #
    # List of defined/used indicators
    #   Npp uses ids 21-31, lexer should use 0-7 which leaves plugins to use 8-20
    #   As other plugins might make usage of indicators as well,
    #   it needs to be tested which are free to use.
    #   Each indicator can be defined with the following attributes
    #       - an uique id starting from 8 to 20 (required)
    #       - an indicator style (required) (see https://www.scintilla.org/ScintillaDoc.html#Indicators which are available)
    #       - a foreground color (optional) (color are tuples containing the red, green and blue value like (255,0,0) )
    #       - an alpha transparency value (optional) (range 0-255)
    #       - an outline alpha transparency value (optional) (range 0-255)
    #       - a boolean value which makes indicator drawn under text or over(default) (optional)
    #   example:
    #      indicators = [(8, INDICATORSTYLE.ROUNDBOX),
    #                    (9, INDICATORSTYLE.SQUIGGLE, (100,215,100)),
    #                    (10, INDICATORSTYLE.GRADIENT, None, 55, None, False)
    #                      
    # Definition of colors and regular expressions
    #   Note, the order in which regular expressions will be processed
    #   is determined by its creation, that is, the first definition is processed first, then the 2nd, and so on
    #
    #   The basic structure always looks like this
    #
    #   regexes[a] = (b, c, d)
    #
    #   regexes = an ordered dictionary which ensures that the regular expressions are always processed in the same order
    #   a = an indicator id
    #   b = raw byte string, describes the regular expression. Example r'\w+'
    #   c = number of the match group to be used
    #   d = boolean value which indicates whether line should be bookmarked
    
    # Examples:
    #   All occurances of word editor, except editor1 and editor2
    #   should be indicated by a roundbox by using
    #   the indicator id 8 and the results from match group 1
    regexes[8] = (r'editor1|editor2|(editor)', 1, False)
    
    #   All numbers should be indicated by an squiggled lined,
    #   using indicator id 9  and the results from matchgroup 0.
    regexes[9] = (r'\d', 0, True)
    
    indicator_list = [(8, INDICATORSTYLE.ROUNDBOX),
                      (9, INDICATORSTYLE.SQUIGGLE, (255,0,0)),]
    
    # ------------------------------------------------ /configuration area ---------------------------------------------------
    
    try:
        AutoMarker().main()
    except NameError:
        user32 = ctypes.WinDLL('user32')
    
        class SingletonAutoMarker(type):
            '''
                Ensures, more or less, that only one
                instance of the main class can be instantiated
            '''
            _instance = None
            def __call__(cls, *args, **kwargs):
                if cls._instance is None:
                    cls._instance = super(SingletonAutoMarker, cls).__call__(*args, **kwargs)
                return cls._instance
    
    
        class AutoMarker(object):
            ''' Colors within visual area based on found matches. '''
            __metaclass__ = SingletonAutoMarker
    
            def __init__(self):
                '''
                    Instantiated the class,
                    because of __metaclass__ = ... usage, is called once only.
                '''
                editor.callbackSync(self.on_updateui, [SCINTILLANOTIFICATION.UPDATEUI])
                self.npp_hwnd = user32.FindWindowW(u'Notepad++', None)
                self.editor1_hwnd = None
                self.editor2_hwnd = None
                self.do_mark = False
                self.__enum_scintilla_hwnds()
                self.configure()
    
    
            def __enum_scintilla_hwnds(self):
                '''
                    Helper function
                    Enumerates the window handles for editor1 and editor2
    
                    Args:
                        None
                    Returns:
                        None
                '''
                EnumWindowsProc = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
    
                def foreach_window(hwnd, lParam):
                    curr_class = ctypes.create_unicode_buffer(256)
                    user32.GetClassNameW(hwnd, curr_class, 256)
    
                    if (curr_class.value == u'Scintilla') and (user32.GetParent(hwnd) == self.npp_hwnd):
                        if self.editor1_hwnd is None:
                            self.editor1_hwnd = hwnd
                        elif self.editor2_hwnd is None:
                            self.editor2_hwnd = hwnd
                            return False
                    return True
    
                user32.EnumChildWindows(self.npp_hwnd, EnumWindowsProc(foreach_window), 0)
    
    
            @staticmethod
            def paint_it(indicator_id, pos, length, bookmark_it):
                '''
                    This is where the actual coloring takes place.
                    Color, the position of the first character and
                    the length of the text to be colored must be provided.
                    Coloring occurs only if the position is not within the excluded range.
    
                    Args:
                        indicator_id = integer, expected in range of 0-16777215
                        pos = integer,  denotes the start position
                        length = integer, denotes how many chars need to be colored.
                    Returns:
                        None
                '''
                editor.setIndicatorCurrent(indicator_id)
                editor.indicatorFillRange(pos, length)
                if bookmark_it:
                    editor.markerAdd(editor.lineFromPosition(pos), 24)
    
    
            def style(self):
                '''
                    Calculates the text area to be searched for in the current document.
                    Calls up the regexes to find the position and
                    calculates the length of the text to be colored.
                    Deletes the old indicators before setting new ones.
    
                    Args:
                        None
                    Returns:
                        None
                '''
                start_line = editor.docLineFromVisible(editor.getFirstVisibleLine())
                end_line = editor.docLineFromVisible(start_line + editor.linesOnScreen())
                start_position = editor.positionFromLine(start_line)
                end_position = editor.getLineEndPosition(end_line)
                
                for indicator in indicator_list:
                    editor.setIndicatorCurrent(indicator[0])
                    editor.indicatorClearRange(indicator[0], editor.getTextLength())
                
                for id, _regex in regexes.items():
                    editor.research(_regex[0],
                                    lambda m: self.paint_it(id,
                                                            m.span(_regex[1])[0],
                                                            m.span(_regex[1])[1] - m.span(_regex[1])[0],
                                                            _regex[2]),
                                    0,
                                    start_position,
                                    end_position)
    
    
            def configure(self):
                '''
                    Define basic indicator settings and the needed regexes.
    
                    Args:
                        None
                    Returns:
                        None
                '''
                def set_indicator_attributes(indicator_number, 
                                             indicator_style, 
                                             fore=None, 
                                             alpha=None,
                                             outline_alpha=None, 
                                             under=None):
                    ''' helper function to set indicators '''
                    editor1.indicSetStyle(indicator_number, indicator_style)
                    editor2.indicSetStyle(indicator_number, indicator_style)
                    editor1.indicSetAlpha(indicator_number, alpha or 55)
                    editor2.indicSetAlpha(indicator_number, alpha or 55)
                    editor1.indicSetFore(indicator_number, fore or (100,215,100))
                    editor2.indicSetFore(indicator_number, fore or (100,215,100))
                    editor1.indicSetOutlineAlpha(indicator_number, outline_alpha or 255)
                    editor2.indicSetOutlineAlpha(indicator_number, outline_alpha or 255)
                    editor1.indicSetUnder(indicator_number, under or True)
                    editor2.indicSetUnder(indicator_number, under or True)
    
                for indicator in indicator_list:
                    set_indicator_attributes(*indicator)
    
    
            def on_updateui(self, args):
                '''
                    Callback which gets called every time scintilla
                    (aka the editor) changed something within the document.
    
                    Triggers the styling function if the document is of interest.
    
                    Args:
                        provided by scintilla but none are of interest
                    Returns:
                        None
                '''
                if self.do_mark:
                    self.style()
    
    
            def main(self):
                '''
                    Main function entry point.
                    Simulates updateui event to enforce
                    checking the document for the first time uasge.
    
                    Args:
                        None
                    Returns:
                        None
                '''
                self.do_mark = not self.do_mark
                self.on_updateui(None)
    
        AutoMarker().main()
    

    Point 1 + 2 should be implemented now,

    Concerning point 3, this might have several causes.
    One, from what I recall is a change within scintilla.
    If scintilla receives too many keyboard inputs it won’t repaint until a key release has been received BUT it could be python as well.
    Second, if you have really long lines this is an internal known scintilla issue.

    What I did was changing one regex to search for \w+ and using the script itself and it seem to be reasonable fast if I stayed on up/down arrow key.

    One other thing, the bookmarking is somewhat slow and I don’t know why.
    Currently, well some month already, I’m developing a new pythonscript plugin,
    one which uses python3 and if I do bookmarking with this plugin,
    it instantly does it.



  • @Ekopalypse
    You’re right, emoving the bookmarks and shortening the lines did indeed solve all of the performance issues even in a file with 100k lines of 25 highlighted characters each! Not only does the caret not disappear, but even holding PageDown has only a minor negative effect.
    That’s awesome!!!
    Thanks again for rewriting your script to add this functionality

    Will just paste my config that I used in the script in case someone might find it useful in their whitespacing quests ;)

    #'ur' not supported in Python 3, but the scripting engine is Python2
    regexes[ 8] = (ur'\u00A0+|\u202F+'	, 0, False)	#¦ ¦no-break; ¦ ¦ no-break narrow
    regexes[ 9] = (ur'\u2003+'        	, 0, False)	#¦ ¦ Em
    regexes[10] = (ur'\u2002+'        	, 0, False)	#¦ ¦ En
    regexes[11] = (ur'\u2007+'        	, 0, False)	#¦ ¦ Figure
    regexes[12] = (ur'\u2008+'        	, 0, False)	#¦ ¦ Punctuation
    regexes[13] = (ur'\u2009+'        	, 0, False)	#¦ ¦ Thin
    regexes[14] = (ur'\u200A+'        	, 0, False)	#¦ ¦ Hair
    regexes[15] = (ur'\u1680+|\u180E+|\u2000+|\u2001+|\u2004+|\u2005+|\u2006+|\u205F+|\u3000+', 0, False) #Others non zero-width: ¦ ¦Ogham Mark, ¦᠎¦Mongolian Vowel Separator, ¦ ¦En Quad, ¦ ¦Em Quad, ¦ ¦3-per-em (thick), ¦ ¦4-per-em (mid), ¦ ¦6-per-em, ¦ ¦Medium Mathematical, ¦ ¦Ideographic
    #15¦ ¦Ogh ¦᠎¦Mong ¦ ¦En ¦ ¦Em Quad ¦ ¦3-per-em ¦ ¦4 ¦ ¦6 ¦ ¦Math ¦ ¦Ideographic
    ZeroWidth = [
    	ur'\u00AD+',                                	#Zero1: >­< Soft Hyphen
    	ur'\u200B+|\u200C+|\u200D+|\u200E+|\u200F+',	#Zero2: >​< ZWS >‌< ZWNJ >‍< ZWJoiner >‎< LTR Mark, >‏< RTL Mark
    	ur'\u202A+|\u202B+|\u202C+|\u202D+|\u202E+',	#Zero3: >‪< LTR Embedding >‫< RTL Embedding, >‬< Pop Directional, >‭< LTR Override, >‮< RTL Override
    	ur'\u2060+|\u2061+|\u2062+|\u2063+|\u2064+|\u2066+|\u2067+|\u2068+|\u2069+|\u206A+|\u206B+|\u206C+|\u206D+|\u206E+|\u206F+',
    	#Zero4: >⁠< Word J >⁡< FunctionApp >⁢< Invisible Times >⁣< I Sep >⁤< I+ >⁦< LTR Isolate >⁧< RTL I >⁨< First Strong I >⁩< Pop Directional I >< Inhibit Symmetric Swapping, >< Activate Symmetric Swapping, >< Inhibit Arabic Form Shaping, >< Activate Arabic Form Shaping, >< National Digit, >< Nominal Digit
    	ur'\uFEFF+',                	#Zero5: ¦¦ ZW No-break
    	ur'\uFFF9+|\uFFFA+|\uFFFB+']	#Zero6: >< Interlinear Annotation Anchor >< IA Separator >< IA Terminator
    ZWCombo = '|'.join([str for str in (ZeroWidth)])
    regexes[16] = (ZWCombo	, 0, False)
    
    indicator_list = [
    	( 8, INDICATORSTYLE.PLAIN         	, (  0, 255,   0)),
    	( 9, INDICATORSTYLE.DASH          	, (255,  83, 112)),
    	(10, INDICATORSTYLE.SQUIGGLE      	, (255,  83, 112)),
    	(11, INDICATORSTYLE.PLAIN         	, (255, 130,  41)),
    	(12, INDICATORSTYLE.POINTCHARACTER	, (  0, 200, 255)),
    	(13, INDICATORSTYLE.POINTCHARACTER	, (  0, 255,   0)),
    	(14, INDICATORSTYLE.POINTCHARACTER	, (255,  83, 112)),
    	(15, INDICATORSTYLE.PLAIN         	, (  0,   0,   0)),
    	(16, INDICATORSTYLE.BOX           	, (255,  83, 112))]
    


  • @Ekopalypse said:

    the bookmarking is somewhat slow and I don’t know why

    A discussion of boomark processing with Pythonscript being slow can be found here:
    https://community.notepad-plus-plus.org/topic/15159/bookmark-multiple-lines



  • @Alan-Kilborn

    I remember we already had that discussion once, right?
    I never tracked down what the issue is but ok, PS is using SendMessage
    instead of Direct_Function and this might slow down a little bit but
    why only with markerAdd method? I mean, shouldn’t the indicator calls
    be affected as well? I my example I’ve done multiple times more indicator
    calls than markerAdd calls.
    I just recorded a screecast, can be seen here.

    The code in question is this

    cdef void mark_spaces():
        cdef size_t _ptr, i, j, linenumber
        _ptr = SendMessageW(nppData._scintillaMainHandle, SCI_GETCHARACTERPOINTER, 0, 0)
        SendMessageW(nppData._scintillaMainHandle, SCI_INDICSETSTYLE, 8, 7)
        SendMessageW(nppData._scintillaMainHandle, SCI_INDICSETFORE, 8, 0x64d764)
        SendMessageW(nppData._scintillaMainHandle, SCI_INDICSETALPHA, 8, 55)
        SendMessageW(nppData._scintillaMainHandle, SCI_INDICSETOUTLINEALPHA, 8, 255)
    
        i = 0
        j = -1
        for c in <char*>_ptr:
            j += 1
            if c == b'e':
                i += 1
                linenumber = SendMessageW(nppData._scintillaMainHandle,SCI_LINEFROMPOSITION, j, 0)
                SendMessageW(nppData._scintillaMainHandle, SCI_SETINDICATORCURRENT, 8, 0)
                SendMessageW(nppData._scintillaMainHandle, SCI_INDICATORFILLRANGE, j, 1)
                SendMessageW(nppData._scintillaMainHandle,SCI_MARKERADD, linenumber, 24)
                
        text = f'{i} matches found!'
        SendMessageW(nppData._nppHandle, NPPM_SETSTATUSBAR, STATUSBAR_DOC_TYPE, <LPARAM><LPCWSTR>text)
        
    cdef void beNotified(SCNotification *notifyCode):
        global notification
        if notifyCode.nmhdr.code == SCN_UPDATEUI:
            mark_spaces()
    

    with each updateui notification the whole document is scanned
    and I’m using SendMessage exclusively. But no GIL and thread.



  • @Ekopalypse said in Persistent highlight of characters (e.g. no-break space):

    I remember we already had that discussion once, right?

    Probably. :-)

    I’m not sure what the code is you’re showing. Maybe it is just out of my swim lane. :-)



  • @Alan-Kilborn

    I’m not sure what the code is you’re showing.

    It’s cythonized python code :)
    Basically this
    editor.callbackSync(self.on_updateui, [SCINTILLANOTIFICATION.UPDATEUI])
    is equivalent to

    cdef void beNotified(SCNotification *notifyCode):
        global notification
        if notifyCode.nmhdr.code == SCN_UPDATEUI:
            mark_spaces()
    

    With pythonscript on_updateui would handle the notifcation and
    here it is mark_spaces.

    mark_spces uses SendMessage method to do the communication with
    scintilla to show and proof that this message can be used to some
    certain of degree, so this can’t be the only reason. I assume it has to do
    with locking/releasing GIL and threads but I’m not going to dig deeper
    as I’m working on a replacement for PS - so from my point of view it doesn’t make sense to spent time on it. :)


Log in to reply