Community
    • Login

    How to iterate over the NPP file tabs (open files)?

    Scheduled Pinned Locked Moved Notepad++ & Plugin Development
    14 Posts 3 Posters 1.1k Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • Mark OlsonM
      Mark Olson @Paul Baker
      last edited by Mark Olson

      @Paul-Baker

      Just eyeballing the public API, I think what you do (in pseudocode) is this

      MAIN_VIEW = 0
      SECONDARY_VIEW = 1
      
      def getBuffersInView(int whichView) -> list of buffer IDs:
          '''
          whichView = 0 for main view, 1 for secondary view
          returns the buffer IDs in that view
          '''
          int numBuffers = NPPM_GETNBOPENFILES(whichView)
          buffers = new empty bufferID list
          for (int ii = 0; ii < numBuffers; ii++)
          {
              bufferID buf = NPPM_GETBUFFERIDFROMPOS(ii, whichView)
              add buf to buffers
          }
          return buffers
          
      def getAllOpenBuffers() -> list of buffer IDs:
          buffersInMainView = getBuffersInView(MAIN_VIEW)
          buffersInSecondaryView = getBuffersInView(SECONDARY_VIEW)
          return buffersInMainView concatenated with buffersInSecondaryView
      
      1 Reply Last reply Reply Quote 2
      • CoisesC
        Coises @Paul Baker
        last edited by Coises

        @Paul-Baker said in How to iterate over the NPP file tabs (open files)?:

        I want to set the language for the just opened files
        […]
        I could not find any functions to help

        NPPN_FILEOPENED - a notification that will be sent for each file as it is opened and give you the corresponding buffer ID

        NPPM_SETBUFFERLANGTYPE - message you can send to Notepad++ to set the language type for each buffer ID after it is created

        That’s if you want to do this automatically as files are opened (as your text suggests). If, as your title suggests, you want to change all already open tabs, then @Mark-Olson’s approach is what you need. Depending on your circumstances, one or the other might make more sense.

        Paul BakerP 1 Reply Last reply Reply Quote 3
        • Paul BakerP
          Paul Baker @Coises
          last edited by

          @Coises @Mark-Olson
          Thanks to both of you! I have not tried the notification approach before. I may need some guidance. I’ll check the C# templates and sample plugins for examples. Thanks so much. After I get it working, I’ll post the solution. Thanks again!!! Paul

          Mark OlsonM 1 Reply Last reply Reply Quote 0
          • Mark OlsonM
            Mark Olson @Paul Baker
            last edited by

            @Paul-Baker
            Are you using NppCSharpPluginPack as a template? I recommend using that. If you see something wrong, PRs and issues are appreciated.

            Paul BakerP 1 Reply Last reply Reply Quote 0
            • Paul BakerP
              Paul Baker @Mark Olson
              last edited by

              @Mark-Olson @Coises
              This little snippet did the job:

                      /**
                       * Listener for Notifications
                       */
                      public static void OnNotification(ScNotification notification) {
                          // File Opened Notification - idFrom header field contains the BufferID
                          if (notification.Header.Code == (uint)NppMsg.NPPN_FILEOPENED) {
                              // Change buffer language type to COBOL - agr1 is BufferID arg2 is LangType
                              Win32.SendMessage(PluginBase.nppData._nppHandle, (uint)NppMsg.NPPM_SETBUFFERLANGTYPE, notification.Header.IdFrom, (int) LangType.L_COBOL);
                          }
                      }
              

              This works great but I would like to read a few lines from the buffer to verify the language type. Is that possible and if so, will you point me in a direction (example, messages)?

              Thanks. This is a great forum, so helpful.
              Paul

              CoisesC Mark OlsonM 2 Replies Last reply Reply Quote 0
              • CoisesC
                Coises @Paul Baker
                last edited by

                @Paul-Baker said in How to iterate over the NPP file tabs (open files)?:

                I would like to read a few lines from the buffer to verify the language type.

                That is a bit tricky. Normally you would use the Scintilla API to read (or modify) data. However, at the point the NPPN_FILEOPENED message is sent, the file isn’t necessarily loaded into a Scintilla control. If you open several files, buffers and tabs are created for all of them, but only one is loaded into the current Scintilla.

                One option would be to use NPPM_GETFULLPATHFROMBUFFERID to get the file name, including the path, and just read it like you would read any other file.

                A second option would be to delay processing until the buffer is activated. That would mean you would watch for NPPN_BUFFERACTIVATED; when that message comes, the buffer in question is loaded into the active Scintilla control. (In C++, you get the active Scintilla handle this way:

                int currentEdit = 0;
                SendMessage(nppData._nppHandle, NPPM_GETCURRENTSCINTILLA, 0, reinterpret_cast<LPARAM>(&currentEdit));
                activeScintilla = currentEdit ? nppData._scintillaSecondHandle : nppData._scintillaMainHandle;
                

                but @Mark-Olson can tell you better than I how to do that in C#.) Once you have that, you would use the Scintilla API to access the file content. Since buffers are activated often (including whenever you switch to the corresponding tab), you would probably want to make a list (std::vector, std::unordered_set, or whatever is used in C#) of buffer IDs when you encounter them in NPPN_FILEOPENED, then in NPPN_BUFFERACTIVATED check the list, and if the ID is there, remove it from the list and do your processing to see if it’s COBOL and set the type if it is.

                1 Reply Last reply Reply Quote 2
                • Mark OlsonM
                  Mark Olson @Paul Baker
                  last edited by

                  @Paul-Baker

                  I would like to read a few lines from the buffer to verify the language type

                  In NppCSharpPluginPack, you can get all the text of the buffer using Npp.editor.GetText(), and then you can split up the text of the buffer using C# standard library methods.

                  You could also call Scintilla a bunch of times with some method (IDK which, find it for yourself) that retrieves the i^th line of the file, but I recommend using C# standard library methods rather than Scintilla API calls if speed is important because Scintilla API calls are rather slow.

                  Paul BakerP 1 Reply Last reply Reply Quote 1
                  • Paul BakerP
                    Paul Baker @Mark Olson
                    last edited by

                    @Mark-Olson @Coises
                    Thanks again. I will chew on this for awhile.
                    I’ll post my solution when I get it.
                    Paul

                    Paul BakerP 1 Reply Last reply Reply Quote 1
                    • Paul BakerP
                      Paul Baker @Paul Baker
                      last edited by

                      @Coises @Mark-Olson

                      Thanks guys, this is the solution I’m going with right now. I appreciate the helpful comments.

                              static List<IntPtr> openedFiles = new List<IntPtr>();
                              ...
                              ...
                              /**
                               * Listener for Notifications
                               */
                              public static void OnNotification(ScNotification notification) {
                                  uint code = notification.Header.Code;
                      
                                  switch (code) {
                                      case (uint) NppMsg.NPPN_FILEOPENED: {
                                              openedFiles.Add(notification.Header.IdFrom);
                                              break;
                                          }
                                      case (uint) NppMsg.NPPN_BUFFERACTIVATED: {
                                              if (openedFiles.Contains(notification.Header.IdFrom)) {
                                                  openedFiles.Remove(notification.Header.IdFrom);
                                                  IScintillaGateway editor = new ScintillaGateway(PluginBase.GetCurrentScintilla());
                                                  if (editor.GetText(1600).Contains(ITSConstants.IDENTIFICATION_DIVISION)) {
                                                      Win32.SendMessage(PluginBase.nppData._nppHandle, (uint)NppMsg.NPPM_SETBUFFERLANGTYPE, notification.Header.IdFrom, (int)LangType.L_COBOL);
                                                  }
                                              }
                                              break;
                                          }
                                  }
                                  return;
                              }
                      
                      Mark OlsonM 1 Reply Last reply Reply Quote 1
                      • Mark OlsonM
                        Mark Olson @Paul Baker
                        last edited by Mark Olson

                        @Paul-Baker

                        Some thoughts:

                        1. I think it’s easier to have a public static IScintillaGateway Editor property of the Main class that is refreshed every time NPPN_BUFFERACTIVATED is received by calling Editor = new ScintillaGateway(PluginBase.GetCurrentScintilla()). The alternative is the more labor-intensive and annoying process of creating a new editor every time you need one.
                        2. Likewise, you should probably have a public static INotepadPPGateway Notepad = new NotepadPPGateway(), which you can use to call convenience methods, rather than having to do ugly Win32.SendMessage nonsense every time you want to tell Notepad++ to do something. This global singleton only needs to be assigned once, and never needs to be reassigned.
                        3. This is partially just personal preference, but I have a strong distaste for creating a list that you need to iterate every time you want to do a lookup. I personally would use a Dictionary<IntPtr, V>, where V is some indicator that determines whether the current document is being lexed as COBOL. Dictionaries have amortized constant time lookup, which could matter if you have hundreds of buffers open.
                        4. I would recommend clearing buffer IDs from your list/dictionary when they are closed. I do this when I receive the NPPN_FILEBEFORECLOSE notification, but it’s probably more correct to do this when receiving the NPPN_FILECLOSED notification.

                        Putting it all together, I would change what you’ve got to the following.

                                /// <summary>
                                /// connector to Scintilla
                                /// </summary>
                                public static IScintillaGateway Editor = new ScintillaGateway(PluginBase.GetCurrentScintilla());
                                /// <summary>
                                /// connector to Notepad++
                                /// </summary>
                                public static INotepadPPGateway Notepad = new NotepadPPGateway();
                                private static Dictionary<IntPtr, bool> _isBufferCOBOL = new Dictionary<IntPtr, bool>();
                                ...
                                ...
                                /**
                                 * Listener for Notifications
                                 */
                                public static void OnNotification(ScNotification notification) {
                                    uint code = notification.Header.Code;
                        
                                    switch (code) {
                                    // when switching buffers, if the lexer isn't COBOL,
                                    // read the first bit of the file to determine if we should lex it as COBOL.
                                    case (uint) NppMsg.NPPN_BUFFERACTIVATED:
                                        // When a buffer is activated, we need to reset the connector to the Scintilla editing component.
                                        // This is usually unnecessary, but if there are multiple instances or multiple views,
                                        // we need to track which of the currently visible buffers are actually being edited.
                                        Editor = new ScintillaGateway(PluginBase.GetCurrentScintilla());
                                        IntPtr bufID = notification.Header.IdFrom;
                                        // TODO: note that you should probably use NPPM_GETBUFFERLANGTYPE here to see if Notepad++ *already set the lexer language to COBOL*.
                                        //     You would need to write an unsafe method to do that correctly
                                        //     (see https://github.com/notepad-plus-plus/notepad-plus-plus/blob/6204f00e881f7fc9459aff7134c12badbe6794bd/PowerEditor/src/MISC/PluginsManager/Notepad_plus_msgs.h#L62)
                                        //     and I can't be bothered to implement it myself at the moment.
                                        if (!_isBufferCOBOL.TryGetValue(idFrom, out bool isCOBOL)
                                            || (!isCOBOL // if buffer is already COBOL, don't bother checking
                                            && Editor.GetText(1600).Contains(ITSConstants.IDENTIFICATION_DIVISION)))
                                        {
                                            _isBufferCOBOL[bufID] = true;    
                                            Notepad.SetCurrentLanguage(LangType.COBOL);
                                        }
                                        else
                                            _isBufferCOBOL[bufID] = false; // not COBOL
                                        break;
                                    case (uint) NppMsg.NPPN_FILEBEFORECLOSE:
                                        _isBufferCOBOL.Remove(notification.Header.IdFrom);
                                        break;
                                    }
                                    return;
                                }
                        

                        Note: I’m trying to get in the habit of using good naming conventions that help the user figure out whether a class member is public or private, so after my initial post, I renamed all the public static members to be Capitalized and the private static member to be _lowerCase. I did not change the semantics of my code in any way.

                        Paul BakerP 2 Replies Last reply Reply Quote 3
                        • Paul BakerP
                          Paul Baker @Mark Olson
                          last edited by

                          @Mark-Olson Thanks Mark. Your first two points were no brainers. I do use NotepadPPGateway for my main operations. I had looked at Dictionary. Now I can’t remember why I went with List (was I just lazy?). Anyway, your point was valid and I ended up using HashSet. I’ll have to chew on your last point for awhile. When I switch tabs NPPN_BUFFERACTIVATED is activated each time this is why I am removing the BufferID from the List (Set) immediately. I’ll have to experiment with NPPN_FILEBEFORECLOSE.

                          Thank again Mark for taking the time to break all that down.

                          Paul

                          1 Reply Last reply Reply Quote 1
                          • Paul BakerP
                            Paul Baker @Mark Olson
                            last edited by

                            @Mark-Olson I went through your comments and code again. It’s not clear why you are recommending that the buffer id be removed on file close. Is there a technical reason I’m not aware of? Thanks again for your detail comments and code!

                            Mark OlsonM 1 Reply Last reply Reply Quote 0
                            • Mark OlsonM
                              Mark Olson @Paul Baker
                              last edited by

                              @Paul-Baker said in How to iterate over the NPP file tabs (open files)?:

                              It’s not clear why you are recommending that the buffer id be removed on file close

                              I think it’s good housekeeping to delete all data regarding a file that no longer exists. In this particular case it doesn’t really matter whether you delete data on file close, but I’m sure you can imagine cases where it matters quite a lot.

                              1 Reply Last reply Reply Quote 0
                              • First post
                                Last post
                              The Community of users of the Notepad++ text editor.
                              Powered by NodeBB | Contributors