How to change word separators in Notpad++ ?

Asger-P
2014-05-23
2014-07-10
  • Asger-P

    Asger-P - 2014-05-23

    How to change word separators in Notpad++

    I'm writing some php code at the moment and I find it very annoying, that Notepad++ don't select the $ when I double-click on a variable.

    I have been through the setting a couple of times, but I cant seem to find where I can change the word separators.

    Anybody know how ?

    Thanks in advance
    Asger-P

     
  • Loreia2

    Loreia2 - 2014-05-23

    Hi Asger-P,

    you can't set it up directly. You must install PythonScript plugin and create new scipt with this code in it:

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    editor.setWordChars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$")
    ~~~~~~~~~~~~~~~~~~~~~~~

    Then just configure PythonScipt to execute this code automatically at start up.

    Regards,
    Loreia

     
    • Asger-P

      Asger-P - 2014-05-23

      Hi Loreia

      Thanks for your answer, but that can not work, unless of course you live in USA or another country with the English alphabet, the world is Unicode now which means I would have to write a looooot more then some 60 chars.
      Php use English but there is words in between the code in strings or just body text in the html parts. As it is now Notepad++ works correctly with all chars it just consider $ to be a separator.
      Isn't there somewhere in a language file or something where separators are defined ?

      Thanks in advance
      Asger-P

       
      Last edit: Asger-P 2014-05-23
  • cchris

    cchris - 2014-05-24

    Sco,tolla consides everything above 0x80 as a word character by default.

    Perhaps the following variant of Loreia's Python cod will work for you, but I didn't test it:

    editor.setWhitespaceChars(" your_unicode_dhars")
    

    The initial space has to be here, since it probably remains a whitespace character!

    CChris

     
  • Loreia2

    Loreia2 - 2014-05-24

    Now you made me check the code :-)

    Here is the relevant part from CharClassify.cxx file:

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    void CharClassify::SetDefaultCharClasses(bool includeWordClass) {
    // Initialize all char classes to default values
    for (int ch = 0; ch < 256; ch++) {
    if (ch == '\r' || ch == '\n')
    charClass[ch] = ccNewLine;
    else if (ch < 0x20 || ch == ' ')
    charClass[ch] = ccSpace;
    else if (includeWordClass && (ch >= 0x80 || isalnum(ch) || ch == '_'))
    charClass[ch] = ccWord;
    else
    charClass[ch] = ccPunctuation;
    }
    }

    void CharClassify::SetCharClasses(const unsigned char chars, cc newCharClass) {
    // Apply the newCharClass to the specifed chars
    if (chars) {
    while (
    chars) {
    charClass[*chars] = static_cast<unsigned char="">(newCharClass);
    chars++;
    }
    }
    }

    first method is triggered for SCI_SETCHARSDEFAULT message, argument is hardcoded to "true".
    Second method is triggered for SCI_SETWORDCHARS message, with arguments set as
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    (reinterpret_cast<unsigned char *>(lParam), CharClassify::ccSpace)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Now it is clear what you need to do:
    

    word_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
    for i in range(129,257):
    word_chars += chr(i)
    editor.setWordChars(word_chars)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    Code should be pretty clear, ASCII stuff (<128) goes into first line, this is what you should edit to set desired result. High values (>128) are simply appended in the string. You can get better performance in Python if you use list.append and string.join methods instead of string concatenation (better yet execute it only once and copy result into hardcoded string, therefore eliminating loop entirely), but I consider this to be trivial gain so lets keep it like this.

    Regards,
    Loreia

     
    • Asger-P

      Asger-P - 2014-05-24

      Hi Loreia

      Woooou!, you know a lot about Notepad++.
      I just found that Notepad++ actually only works in chars and not in widechars as I expected, so you were right the first time, if it's true that everything above 0x80 is a word-char by default as CChris say.

      I did a search on 'setWhitespaceChars' and they say this about it:
      "Set the set of characters making up whitespace for when moving or selecting by word. Should be called after SetWordChars"

      I also found that there is a getWhitespaceChars() as well so in order not to mess with anything, but the $, wouldn't it be better to do something like this:

      white_chars = Editor.getWhitespaceChars()
      remove $ from white_chars
      Editor.setWhitespaceChars(white_chars)

      I just don't have a clue about how to write it, so that Notepad++ understands it. Or where to put it so that Notepad++ will act on it. I am quite new to Notepad++ and normally I use C++Builder, I only use Notepad++ for php and xml.

      P.s. How do you get those grey code boxes ??

      Thanks to both CChris and Loreia
      Best regards
      Asger-P

       
      Last edit: Asger-P 2014-05-24
  • Loreia2

    Loreia2 - 2014-05-25

    Hi Asger-P,

    here is the deal. Think of Notepad++ as a wrapper around Scintilla editing component. That is a simplification, but good enough for this conversation. Scintilla works with UTF-8 strings. So, Notepad++ doesn't set "WordChars" directly, it simply sends SCI_SETCHARSDEFAULT to Scintilla.dll.

    This is the code that gets executed on Scintilla side:

    ~~~~~~~~~~~~~~~~~
    case SCI_SETCHARSDEFAULT:
    pdoc->SetDefaultCharClasses(true);
    break;
    ~~~~~~~~~~~~~~~~~~

    which in fact leads to this:

    void CharClassify::SetDefaultCharClasses(bool includeWordClass) {
        // Initialize all char classes to default values
        for (int ch = 0; ch < 256; ch++) {
            if (ch == '\r' || ch == '\n')
                charClass[ch] = ccNewLine;
            else if (ch < 0x20 || ch == ' ')
                charClass[ch] = ccSpace;
            else if (includeWordClass && (ch >= 0x80 || isalnum(ch) || ch == '_'))
                charClass[ch] = ccWord;
            else
                charClass[ch] = ccPunctuation;
        }
    }
    ~~~~~~~~~~~~~~~~~~~~
    
    Lets read this code out loud:
    - Newline chars are set to ccNewLine enumeration, there are just two of them, as expected
    - Whitespase chars are all characters lower than space character, that's first 32 chars of ASCII table
    - if argument is **true**, "WordChars" become: underscore, english alphanumerics, and everything above 128 in ASCII table
    - if argument is **false**, no chars are treated as "WordChars". There is simply ccNewLine, ccSpace and everything else is ccPunctuation
    - ccPunctuation is simply anything that is not in first three groups
    
    Notepad++ by default sends SCI_SETCHARSDEFAULT to Scintilla.dll. That's why it sets "WordChars" as **underscore, english alphanumerics and everything above 128 in ASCII table**.
    
    If you wish to extend a list of "WordChars", you must send SCI_SETWORDCHARS message to Scintilla.dll, but Notepad++ gives you no GUI interface to do this. Preferred way to do it is by using PythonScript plugin. But first lets check the code path:
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~
    case SCI_SETWORDCHARS: {
        pdoc->SetDefaultCharClasses(false);
        if (lParam == 0)
            return 0;
        pdoc->SetCharClasses(reinterpret_cast<unsigned char *>(lParam), CharClassify::ccWord);
    }
    break;
    ~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Again, short analysis is necessary, just to make sure we understand each other.
    We already explained **SetDefaultCharClasses** method, but notice that argument is set to **false** this time around. Effectively, that removes default values for "WordChars", and user is responsible to provide a string with new "WordChars" characters.
    **SetCharClasses** is a simple loop:
    

    void CharClassify::SetCharClasses(const unsigned char chars, cc newCharClass) {
    // Apply the newCharClass to the specifed chars
    if (chars) {
    while (
    chars) {
    charClass[*chars] = static_cast<unsigned char="">(newCharClass);
    chars++;
    }
    }
    }
    ~~~~~~~~~~~~~~~~~~~~~~

    Before we switch to Python, here is what SCI_SETWHITESPACECHARS does:

    case SCI_SETWHITESPACECHARS: {
        if (lParam == 0)
            return 0;
        pdoc->SetCharClasses(reinterpret_cast<unsigned char *>(lParam), CharClassify::ccSpace);
    }
    break;
    

    So, editor.setWhitespaceChars(white_chars) is useless for you, because it can only set definition of "WhiteSpace" chars, you need to extend definition of "WordChars". Basically, there is no way to cheat, you must define absolute value for "WordChars". You can't add or remove stuff from default definition.

    And we go back to the same Python code:

    word_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$"
    for i in range(129,257):
        word_chars += chr(i)
    editor.setWordChars(word_chars)
    

    This time I added dollar sign to the list. Copy this, execute it, and it will work (I tested something very similar, it does work :-))

    Regards,
    Loreia

     
    • Asger-P

      Asger-P - 2014-05-26

      Hi Loreia
      Thank you very much for explaining, I think I get it now, it is just the total opposite of what I'm used to in My other editor MED and also in my own program MacroMenu, I guess that's why I had a hard time accepting it could be done in an other way.;)
      One last question though.
      How do I go about executing your fine code ?
      I have been looking through the menus in Notepad++ and the only place I found is in Setting->Import->Import Plugin, but that seem to want a DLL, so I thought Id better ask before doing something wrong.

      Thanks in advance
      Asger-P
      http://Asger-P.dk/Software

       
  • Loreia2

    Loreia2 - 2014-05-27

    Hi Asger-P ,

    yes, you need to install PythonScript plugin (start Plugin manager, and tick appropriate check box). It will download one dll (Plugin itself) and Python to run the code.

    After restart you will have another entry in Plugins menu, go there, create a new script (name it anything you like), copy/paste my code, and in plugin configuration select to load plugin ATSTARTUP. To execute this script automatically just call it from startup.py. I am writing this from memory, but it should be enough to successfully complete the task.

    If something breaks, ask here again.

    Regards,
    Loreia

     
    • Asger-P

      Asger-P - 2014-05-28

      Hi Loreia

      PythonScript plugin is installed, your script is added as a new script and it appear in the menu.
      Clicking the script has no effect though.:(
      I have set python to load ATSTARTUP, but I don't know how to call the new script from startup.py, what is the syntax for doing that ?
      My script is named: $inPhp.py

      and it contains these lines of code:

      word_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$"
      for i in range(129,257):
          word_chars += chr(i)
      editor.setWordChars(word_chars)
      

      I guess I must be doing something wrong.

      Thanks for your help
      Best regards
      Asger

       
  • CFrank

    CFrank - 2014-05-29

    Hi Asger,

    some time ago I had the similar issue and I didn't get it solved by setting the
    word chars during startup only. It was weird, it worked if the php script was
    already open (saying that it was known as a file which was opened after npp starts automatically) but when I opened a new php script, it didn't work.

    My solution is similar to Loreias except I register a callback when
    npp is ready and buffer gets activated. By the way, there is a little error
    in the script, it needs to be

    for i in range(129,256)
    

    instead of

    for i in range(129,257)
    

    so here is the code of my startup.py (user)

    def extendWordChar(args):
        word_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$"
        for i in range(129,256):
            word_chars += chr(i)
        editor.setWordChars(word_chars)
    
    def callback_BUFFERACTIVATED(args):
        extendWordChar(args)
    
    def callback_READY(args):
        extendWordChar(args)
    
    notepad.callback(callback_BUFFERACTIVATED, [NOTIFICATION.BUFFERACTIVATED ])
    notepad.callback(callback_READY, [NOTIFICATION.READY ])
    

    So create a new script called startup.py, copy the code, set the
    initialition to atstartup, as already explained by loreia, and restarted npp.
    Since then - I don't have problem anymore. Btw. the issue/problem with this solution is,
    that every switch of a document triggers a buffer activated event but as already said,
    this was the only way I could got around this.

    @Loreia, if there is another way by doing the setting only once, then I would
    appreciate letting me know. I'm still using npp 6.5.3 maybe this is the issue.

    Cheers
    Claudia

     
  • Loreia2

    Loreia2 - 2014-05-29

    Hi Claudia,

    thanks for helping out.
    You are right, indexing starts with zero, so range should be:

    for i in range(128,256)
    

    Using callbacks is not necessary, I believe your problem is here:

    create a new script called startup.py
    

    Such script already exists and it is, by default, placed here (on my Win 7 machine):

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    C:\Program Files (x86)\Notepad++\plugins\PythonScript\scripts
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    At the end of the script I placed your code, only the code inside the function, not the function itself! Note that I fixed start of range margin. Also I had to start editor with Admin rights (in order to write within Program Files folder).

    word_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$"
    for i in range(128,256):
        word_chars += chr(i)
    editor.setWordChars(word_chars)
    

    And after restart, double clicking includes dollar sign.
    Can you repeat the same steps and verify that it works for you?

    Regards,
    Loreia

     
    • Asger-P

      Asger-P - 2014-05-29

      Hi Loreia
      Thanks for taking the time to help.

      I have put the code inside my startup.py so it now looks like this:

      # The lines up to and including sys.stderr should always come first
      # Then any errors that occur later get reported to the console
      # If you'd prefer to report errors to a file, you can do that instead here.
      import sys
      from Npp import *
      
      # Set the stderr to the normal console as early as possible, in case of early errors
      sys.stderr = console
      
      # Define a class for writing to the console in red
      class ConsoleError:
          def __init__(self):
              global console
              self._console = console;
      
          def write(self, text):
              self._console.writeError(text);
      
          def flush(self):
              pass
      # Set the stderr to write errors in red
      sys.stderr = ConsoleError()
      
      # This imports the "normal" functions, including "help"
      import site
      
      # This sets the stdout to be the currently active document, so print "hello world", 
      # will insert "hello world" at the current cursor position of the current document
      sys.stdout = editor
      
      word_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$"
      for i in range(128,256):
          word_chars += chr(i)
      editor.setWordChars(word_chars)
      

      In: Plugin -> Python Scripts -> Configuration I have chosen ATSTARTUP instead of LAZY.

      I still don't get the $ selected in $variable only variable when I double click on the word. ( I'm using NPP version 6.6.3 ).

      Thanks again
      Best regards
      Asger

       
  • CFrank

    CFrank - 2014-05-29

    Hi Loreia,

    you are right, there is already a startup.py. From what I understand, Dave introduced
    a "machine-specific" startup.py script, which is the one you refer to and gave the
    users the possibility to create their own startup.py. This "user-specific" script, once
    created, is located under

     ...\Notepad++\plugins\Config\PythonScript\scripts
    

    I use this way because I did not want to mess around with Daves script and in addition
    I cannot be sure that it changes it content with, let's say, the next update.

    By using your way, it only works if the php script gets automatically opened during
    npp statup. Whenever I open a new php script, it is broken again.

    @Asger, can you reproduce my behaviour?
    Open a php script and close npp, without closing the php script first, restart
    npp and your php script should be opened automatically. Now double clicking the var
    should include the $ sign. For newly opened/created scripts it shouldn't work, at least
    it does for me.

    Cheers
    Claudia

     
  • Loreia2

    Loreia2 - 2014-05-29

    Hi Asger,

    that is strange, I just tested on Linux (under Wine) and even there everything works fine. I copied code directly from your e-mail, selected ATSTARTUP initialization and restarted. Double-click worked like a charm.

    Can you add one more line to your startup.py? Just something that prints "I'm alive message", say something like this:

    console.write("Hello world from startup.py")
    

    Then after restart check if you can see that message in PythonScript console. I just want to determine that proper script gets executed.

    If it still doesn't work, can you download ZIP version and try from there? In this way you will eliminate possible problems in your current configuration settings, both in plugin and main application.

    Regards,
    Loreia

     
    • Asger-P

      Asger-P - 2014-05-29

      Hi Loreia

      I see this in my console window:

      Hello world from startup.py
      Python 2.7.1 (release27-maint-npp, Feb  6 2011, 16:58:20) [MSC v.1500 32 bit (Intel)]
      Initialisation took 31ms
      Ready.
      

      But as the first line ? It could be that Claudia have a point.

      Aaaaah, just did another check, I tried putting in a $word in the startup.py and in that file the $ gets selected with the word, but not in my php files.

      Does it work in php files on your setup ?

      Is there a way around this ?

      Thanks for helping
      Best regards
      Asger-P

       
  • CFrank

    CFrank - 2014-05-29

    @Loreia, I did some further test using your technique and I found another circumstance
    which breaks it. When the last active document was NOT the php script then it doesn't
    work either.
    To confirm that both startup.py script get executed I added a console.write
    statements in each script. By opening the console we see that both get executed.

    running machine startup.py and extending word chars
    running user startup.py
    Python 2.7.1 (release27-maint-npp, Feb 6 2011, 16:58:20) [MSC v.1500 32 bit (Intel)]
    Initialisation took 47ms
    Ready.

    So, from my point it looks like scintilla/npp is creating the word chars everytime
    a buffer needs to be activated. Unfortunattely, the brilliant python script interface
    doesn't have the SCI_GETWORDCHARS implemented otherwise we could easily proove my assumption.

    Cheers
    Claudia

     
  • Loreia2

    Loreia2 - 2014-05-29

    Hi Claudia,

    you are right, this code needs to be executed once per buffer. It is not application level applicable, it affects active buffer (opened file) only.

    So, callbacks are the only way to do it, we just need to find notifications that cover all possibilities: open new files, open existing files and files opened by main application after restart.

    Then we can simplify your code like this:

    ~~~~~~~~~~~~~~~~~~
    def extendWordChar(args):
    word_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$"
    for i in range(128,256):
    word_chars += chr(i)
    editor.setWordChars(word_chars)

    notepad.callback(extendWordChar, [NOTIFICATION.NOTIFICATION1, NOTIFICATION.NOTIFICATION3, NOTIFICATION.NOTIFICATION3])
    ~~~~~~~~~~~~~~~~

    I'll try to scan documentation to find appropriate notification tonight or tomorrow evening.
    http://npppythonscript.sourceforge.net/docs/latest/enums.html

    Regards,
    Loreia

     
  • CFrank

    CFrank - 2014-05-29

    Hi Loreia,

    I set only NOTIFICATION.BUFFERACTIVATED and NOTIFICATION.READY and it seems that
    this did it. I use this now for maybe 3 month and never had the problem anymore.
    Not saying that this is 100% sure, it works for me and how I use npp. I guess it is
    a good starting point.
    You are right, the code can be simplified. The reason why I use different callbacks
    is that I'm doing other tweaks as well.

    Btw. Thank you for UDL2 - very nice job.

    Cheers
    Claudia

     
  • Loreia2

    Loreia2 - 2014-05-29

    Hi Claudia,

    It looks like BUFFERACTIVATE is the only solution for this problem. FILEOPENED works for files that are opened after application starts, but it looks like Notepad++ loads old files before it initializes PythonScript.

    That leaves BUFFERACTIVATE as the only safe way to send this message to Scintilla for every buffer.

    Long story short, here is the code that works:

    ~~~~~~~~~~~~~~~~~~~~~~~
    def extendWordChar(args):
    word_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$"
    for i in range(128,256):
    word_chars += chr(i)
    editor.setWordChars(word_chars)

    notepad.callback(extendWordChar, [NOTIFICATION.BUFFERACTIVATED, NOTIFICATION.READY])
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~

    I am not sure why you included NOTIFICATION.READY. According to documentation this notification doesn't even include "BufferID". I think this one might be redundant. Can one of you two test above code with just [NOTIFICATION.BUFFERACTIVATED] ?
    I think that should be the final version.
    .

    Btw. Thank you for UDL2 - very nice job.

    .
    You are welcome. Keep an eye on next version, it should be ready latter this year. UDL 3.0 will be much improved. In fact the only reason why I noticed this thread was because I was working on adding this same functionality in new UDL. With UDL 3.0 this thread will become obsolete :-)

    Regards,
    Loreia

     
  • CFrank

    CFrank - 2014-05-29

    Hi Loreia,

    we need the READY notification for just one case and this is when the php script was the last active document before npp close. At next startup the php document is the active one
    but we only get a READY event and not a BUFFERACTIVATED event.

    UDL 3.0 - I'm eager to get it ;-)

    Cheers
    Claudia

     
  • Asger-P

    Asger-P - 2014-05-29

    Hi Loreia

    This thread might become obsolete, but right now it's a thread of gold.

    Now it works in my php files and it works without NOTIFICATION.READY, I am so grateful.

    The next thing would be to make it per file type.;)

    Thanks again for all your help
    Best regards
    Asger-P

     
  • Asger-P

    Asger-P - 2014-05-29

    Hi Loreia

    I had a little peak at the UDL help, that look serious.

    Is it you who makes the languages highlighting file: stylers.xml ?
    Isn't it hard to keep up the speed ? I mean there is no "rubber" in the scrollbar not even when you scroll fast, that's great work.

    p.s. if it is you who make the syntax highlighting I believe there is missing a couple of groups in the css section:

    <WordsStyle name="STRING" styleID="13" ...../>
    <WordsStyle name="SIMPLESTRING" styleID="14" ...../>
    

    I added the above to my own and it seem to work.;-)

    Thanks again
    Best regards
    Asger

     
  • CFrank

    CFrank - 2014-05-29

    Hi Asger,

    may I ask what excatly you did? The reason why I 'm asking is that even after installing
    the actual npp it still doesn't send a BUFFERACTIVATED event at startup. So, either
    you use a manual call and registered the callback or I'm doing something totally wrong.

    Regarding your per file type comment - if you registered a callback it is done for
    every file already, or do I not get the point?

    Cheers
    Claudia

     
  • Loreia2

    Loreia2 - 2014-05-30

    Hi Claudia,

    we need the READY notification for just one case and this is when the php script was the last active document before npp close. At next startup the php document is the active one
    but we only get a READY event and not a BUFFERACTIVATED event.

    .
    I get it now, thanks.

    Hi Asger,

    This thread might become obsolete, but right now it's a thread of gold.

    .
    I agree actually, it helped me learn few things. I appreciate that.

    The next thing would be to make it per file type.;)

    .
    File type checking in Python is really easy. To be honest everything is easy in Python :-)

    ~~~~~~~~~~~~~~~~~~~~~~~
    import os

    def extendWordChar(args):
    if "bufferID" in args:
    filename, extension = os.path.splitext(notepad.getBufferFilename(args["bufferID"]))
    if extension not in ("php", "txt", "xyz"):
    return
    word_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$"
    for i in range(128,256):
    word_chars += chr(i)
    editor.setWordChars(word_chars)

    notepad.callback(extendWordChar, [NOTIFICATION.BUFFERACTIVATED, NOTIFICATION.READY])
    ~~~~~~~~~~~~~~~~~~~~~~~~

    .
    Is that what you had in mind?

    Regards,
    Loreia

     
  • Loreia2

    Loreia2 - 2014-05-30

    Hi Asger,

    I had a little peak at the UDL help, that look serious.

    Had I known how hard it would be, I would never have gotten into this mess :-) But now, I just want to finish the damn thing.

    Is it you who makes the languages highlighting file: stylers.xml ?

    No, that one is used by main application, UDL uses its own userDefinedLang.xml file.

    Isn't it hard to keep up the speed ? I mean there is no "rubber" in the scrollbar not even when you scroll fast, that's great work.

    That is what Scintilla provides, so don't thank me, thank Neil Hodgson.

    p.s. if it is you who make the syntax highlighting I believe there is missing a couple of groups in the css section:

    Actually, a whole bunch of languages are missing some keyword groups. styles.xml has not been kept in synch with Scintilla. Some six months ago I tried to update entire stylers.xml, but it was taking too much of my time so I gave up.
    I'd like to finish that task when I finish next version of UDL.

    Regards,
    Loreia

     
  • Loreia2

    Loreia2 - 2014-05-30

    Hi Claudia,

    Regarding your per file type comment - if you registered a callback it is done for
    every file already, or do I not get the point?

    he would like to restrict it to say PHP files only. In this way he can add $ to php files, and # to some other file type, some other char to some other file type, and so on.

    I guess his extendWordChar will be one big switch statement (if .. elif in Python).

    Regards,
    Loreia

     
    • CFrank

      CFrank - 2014-05-31

      Thx - for clarification ;-)

      Cheers
      Claudia

       
  • THEVENOT Guy

    THEVENOT Guy - 2014-05-31

    Hello, Asger-P, CFrank and Loreia2,

    This thread was really interesting and I'm quite pleased that your collaboration give, to all of us, a solution to set a user list of Word characters and, especially, for only few extensions ( Thanks Loreia2 for your last script ! )

    However, you can, also, get this behaviour, with a very simple NppExec script !

    Follow the method below :

    • Download the NppExec plugin, with the menu option Plugins - Plugin Manager - Show Plugin Manager, if necessary

    • Hit the F6 key or chose the menu option "Plugins - NppExec - Execute..."

    • Select, if necessary, <temporary script>

    • Type in the two lines below :

    npp_console 0
    sci_sendmsg 2077 0 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789$"

    • Click on the button Save... and give it, let say, the name Word_Chars

    • Open the menu option "Plugins - NppExec - Advanced Options..."

    • In the Menu item zone, chose, from the Associated Script list, this new script Word_Chars

    • Click on the button Add/Modify and, then, on the button OK

    • Re-start Notepad++

    => From now on, the script Word_Chars is placed at the end of the Macros sub-menu

    • Create a shortcut, for that script, in Menu option "Settings - Shortcut Mapper... - Plugin Commands"

    To go back to the Scintilla DEFAULT Word chars ( [0-9A-Za-z_\x80-\xFF] ), use the script below :

    npp_console 0
    sci_sendmsg 2444


    So:

    • Each time you run the Word_Chars script or execute the shortcut, the $ symbol will be considered as a word character, for the CURRENT file.

    • This behaviour is kept, for this file, till you exit Notepad++ or you select the DEFAULT word chars script

    • This new set of Scintilla Word Chars can be set for any opened file, in Notepad++

    • However, be aware that the set of Word characters, in a search, with the simple regex \w will always give you the same standard set of 139 characters below, for the Windows 1252 code-page :

    [0-9A-Za-z_ƒŠŒŽšœžŸª²³µ¹ºÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòôõöøùúûüýþÿ]

    Cheers,

    guy038

    P.S. :

    In older ANSI versions of Notepad++, it was possible to directly, add, in the macros section of the Shortcuts.xml file, the two macros below ( ALT + W and ALT + SHIFT + W ) :

    <Macro name="Word Characters =  DEFAULT and '$'" Ctrl="no" Alt="yes" Shift="no" Key="87">
        <Action type="1" message="2077" wParam="0" lParam="0" sParam="$0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" />
    </Macro>
    <Macro name="DEFAULT Word Characters =  A-Z  a-z  0-9  _  and IF &gt; \x7F" Ctrl="no" Alt="yes" Shift="yes" Key="87">
        <Action type="0" message="2444" wParam="0" lParam="0" sParam="" />
    </Macro>
    

    So, you didn't even need any plugin to create a new set of words characters. Unfortunately, only the DEFAULT behaviour seems to work, with recent Unicode versions :-(

     
    Last edit: THEVENOT Guy 2014-06-01
    • CFrank

      CFrank - 2014-05-31

      You are kidding aren't you?? ;-)) npp_exec can send sci_messages??
      I need to check my plugins functionality more often.

      Seriously - this is why I love npp and its plugins because mostly there are
      more ways to solve a problem and everyone can choose his/her favourite way.

      But there is one major issue with your solution - I need to click a button but
      I'm soooo lazy ;-D

      Today I thought I can extend my npp python ide with a
      Gimme_a_builtin_function_attribute_user_list_thingy.

      What I did is I extended my startup.py with the following

      def formatlist(list_or_iterator):
          formatted_list = []
          for x in list_or_iterator:
              if (x.find('_',1) < 0):
                  formatted_list.append(x)
          return ' '.join(formatted_list)
      
      def getcurrentWord():
          current_position = editor.getCurrentPos()
          word_start = int(editor.wordStartPosition(current_position -1, True))
          word_end = int(editor.wordEndPosition(current_position-1, True))
          return editor.getTextRange(word_start, word_end)
      
      def callback_CHARADDED(args):
          if(args.has_key('ch')):
              if args['ch'] == 46:
                  current_word = getcurrentWord()
                  if editor.getLexerLanguage() == 'python':
                      try:
                          attribute_list = formatlist(dir(eval(current_word)))
                      except:
                          return
                      else:
                          editor.userListShow(1, attribute_list)
      
      def callback_USERLISTSELECTION(args):
          if(args.has_key('text') and args.has_key('position')):
              editor.insertText(args['position'],args['text'])
      
      editor.callback(callback_CHARADDED, [SCINTILLANOTIFICATION.CHARADDED])
      editor.callback(callback_USERLISTSELECTION, [SCINTILLANOTIFICATION.USERLISTSELECTION])
      

      So, I have a callback when a char is added and I check if this is a dot.
      If so, I get the currentword, the word before the dot and if the language
      is python, I try to create a attribute list of the "current_word". If this
      succeeds a userlist will be shown and if the user chooses an entry the second
      callback will write it to the actual position.

      Is it useful? - Well, depends - maybe - but it will be more useful
      if I can do this for imported modules and newly created classes.
      Let's see if I can figure out how to do this.

      Cheers
      Claudia

       
  • THEVENOT Guy

    THEVENOT Guy - 2014-06-01

    Hi, Claudia,

    With the NppExec plugin, it quite possible to execute a script when Notepad++ starts or exits !

    You just have to choose the script to run in the proposed list :-)

    • Open the menu option "Plugins - NppExec - Advanced Options..."

    • On the right top of the NppExec Advanced Options dialog, choose, from the list, the right script to run

    Of course, the $ symbol will be assumed as a word character, for the current file only, after N++ starts !

    So, if you switch to an other opened file and that you need the dollar as word character, for that file, you'll have to run the script Macro - Words_Char or use the shortcut.

    Good coding for extending your "npp Python ide" !

    Cheers,

    Guy

     
    Last edit: THEVENOT Guy 2014-06-01
  • Cyrillev

    Cyrillev - 2014-06-06

    hi,

    How to change word separators in Notpad++ for "highlight selected text" ?
    See attachment for example

    Cyrillev

     
  • Loreia2

    Loreia2 - 2014-06-06

    Hi Cyrillev,

    I see the problem, but I don't know how highlighting works. I would have to check the code to see the details.

    Regards,
    Loreia

     
  • Cyrillev

    Cyrillev - 2014-07-06

    Hi Loreia2

    For "highlight selected text" I think is in this source file : .\PowerEditor\src\ScitillaComponent\SmartHighlighter.cpp
    but I don't know if a PythonScript can change this behavior.

    Cyrillev

    // SmartHighlighter.cpp

    void SmartHighlighter::highlightView(ScintillaEditView * pHighlightView)
    {
    //Get selection
    CharacterRange range = pHighlightView->getSelection();

    //Clear marks
    pHighlightView->clearIndicator(SCE_UNIVERSAL_FOUND_STYLE_SMART);
    
    //If nothing selected, dont mark anything
    if (range.cpMin == range.cpMax)
    {
        return;
    }
    
    int textlen = range.cpMax - range.cpMin + 1;
    
    char * text2Find = new char[textlen];
    pHighlightView->getSelectedText(text2Find, textlen, false); //do not expand selection (false)
    
    bool valid = true;
    //The word has to consist if wordChars only, and the characters before and after something else
    if (!isQualifiedWord(text2Find))
        valid = false;
    else
    {
        UCHAR c = (UCHAR)pHighlightView->execute(SCI_GETCHARAT, range.cpMax);
        if (c)
        {
            if (isWordChar(char(c)))
                valid = false;
        }
        c = (UCHAR)pHighlightView->execute(SCI_GETCHARAT, range.cpMin-1);
        if (c)
        {
            if (isWordChar(char(c)))
                valid = false;
        }
    }
    if (!valid) {
        delete [] text2Find;
        return;
    }
    
    // save target locations for other search functions
    int originalStartPos = (int)pHighlightView->execute(SCI_GETTARGETSTART);
    int originalEndPos = (int)pHighlightView->execute(SCI_GETTARGETEND);
    
    // Get the range of text visible and highlight everything in it
    int firstLine =     (int)pHighlightView->execute(SCI_GETFIRSTVISIBLELINE);
    int nrLines =   min((int)pHighlightView->execute(SCI_LINESONSCREEN), MAXLINEHIGHLIGHT ) + 1;
    int lastLine =      firstLine+nrLines;
    int startPos =      0;
    int endPos =        0;
    int currentLine = firstLine;
    int prevDocLineChecked = -1;    //invalid start
    
    const NppGUI & nppGUI = NppParameters::getInstance()->getNppGUI();
    
    FindOption fo;
    fo._isMatchCase = nppGUI._smartHiliteCaseSensitive;
    fo._isWholeWord = true;
    
    const TCHAR * searchText = NULL;
    
    WcharMbcsConvertor *wmc = WcharMbcsConvertor::getInstance();
    unsigned int cp = pHighlightView->execute(SCI_GETCODEPAGE); 
    const TCHAR * text2FindW = wmc->char2wchar(text2Find, cp);
    searchText = text2FindW;
    
    for(; currentLine < lastLine; ++currentLine)
    {
        int docLine = (int)pHighlightView->execute(SCI_DOCLINEFROMVISIBLE, currentLine);
        if (docLine == prevDocLineChecked)
            continue;   //still on same line (wordwrap)
        prevDocLineChecked = docLine;
        startPos = (int)pHighlightView->execute(SCI_POSITIONFROMLINE, docLine);
        endPos = (int)pHighlightView->execute(SCI_POSITIONFROMLINE, docLine+1);
        if (endPos == -1) { //past EOF
            endPos = (int)pHighlightView->getCurrentDocLen() - 1;
            _pFRDlg->processRange(ProcessMarkAll_2, searchText, NULL, startPos, endPos, NULL, &fo);
            break;
        } else {
            _pFRDlg->processRange(ProcessMarkAll_2, searchText, NULL, startPos, endPos, NULL, &fo);
        }
    }
    
    // restore the original targets to avoid conflicts with the search/replace functions
    pHighlightView->execute(SCI_SETTARGETSTART, originalStartPos);
    pHighlightView->execute(SCI_SETTARGETEND, originalEndPos);
    

    }

    and this :
    bool SmartHighlighter::isQualifiedWord(const char *str) const
    {
    for (size_t i = 0, len = strlen(str) ; i < len ; ++i)
    {
    if (!isWordChar(str[i]))
    return false;
    }
    return true;
    };

    bool SmartHighlighter::isWordChar(char ch) const
    {
    if ((UCHAR)ch < 0x20)
    return false;

    switch(ch)
    {
        case ' ':
        case '  ':
        case '\n':
        case '\r':
        case '.':
        case ',':
        case '?':
        case ';':
        case ':':
        case '!':
        case '(':
        case ')':
        case '[':
        case ']':
        case '+':
        case '-':
        case '*':
        case '/':
        case '#':
        case '@':
        case '^':
        case '%':
        case '$':
        case '"':
        case '\'':
        case '~':
        case '&':
        case '{':
        case '}':
        case '|':
        case '=':
        case '<':
        case '>':
        case '\\':
            return false;
    }
    return true;
    

    };

     
    Last edit: Cyrillev 2014-07-06
  • Loreia2

    Loreia2 - 2014-07-07

    Hi Cyrillev,

    but I don't know if a PythonScript can change this behavior.

    Unfortunately not. isWordChar hardcodes definition of a word. This should be reimplemented to query Scintilla about the "word chars".

    Basically, Npp isWordChar should send SCI_GETWORDCHARS to Scintilla to get a c-string of "word chars" and check if current char is part of the string.

    Obviously, sending SCI_GETWORDCHARS to Scintilla should be done in isQualifiedWord, not in isWordChar.

    If you know some basic C++ you can write a small patch and send it to Don.

    Regards,
    Loreia

     
  • Cyrillev

    Cyrillev - 2014-07-09

    Hi Loreia,

    thank you for your explanations, here is the modified code.

    see attachments:
    - SmartHighlighter.cpp and SmartHighlighter.h : with modified functions (sending SCI_GETWORDCHARS): highlightView() isWordChar() and isQualifiedWord() functions;
    - smartHighlight.jpg: to compare new and old code, with or without add '-' char with PythonScript: editor.setWordChars(word_chars);
    - essai_wordSeparator.py: the python script;
    - notepadPlus_Debug.exe for test.

    Cyrillev

     
  • Loreia2

    Loreia2 - 2014-07-10

    Hi Cyrillev,

    I checked the code quickly, and it looks OK.
    The only thing that you might consider is calling SCI_GETWORDCHARS just once. It will always need a 256 byte buffer so instead of:

    char *listChar = new char[listCharSize+1];
    

    you may just allocate

    ~~~~~~~~~~~~~~~~~~~
    char listChar[256];
    ~~~~~~~~~~~~~~~~~~~~

    In which case you don't need explicit delete statements:

    ~~~~~~~~~~~~~~~~~
    delete [] listChar;
    ~~~~~~~~~~~~~~~~

    Also, if SmartHighlighter::isWordChar is not used anywhere else, I would suggest to merge it with SmartHighlighter::isQualifiedWord as two nested for loops.

    You may send Don both versions (the one you have posted here and the one with my suggestions) so he can pick the version he likes more.

    His e-mail:

    don_DOT_h_AT_free_DOT_fr

    Regards,
    Loreia

     
  • Cyrillev

    Cyrillev - 2014-07-10

    Hi,
    Thank you for your suggestion.
    I sent the code to Don.
    Cyrillev

     

Get latest updates about Open Source Projects, Conferences and News.

Sign up for the SourceForge newsletter:

JavaScript is required for this form.





No, thanks