How to change word separators in Notpad++ ?

Asger-P
2014-05-23
2014-07-10
1 2 > >> (Page 1 of 2)
  • 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

     
1 2 > >> (Page 1 of 2)

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

Sign up for the SourceForge newsletter:

JavaScript is required for this form.





No, thanks