Community
    • Login

    Using the PythonScript plugin to automate N++

    Scheduled Pinned Locked Moved Help wanted · · · – – – · · ·
    pythonscriptautomation
    53 Posts 6 Posters 5.2k 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.
    • Alan KilbornA
      Alan Kilborn @TBugReporter
      last edited by Alan Kilborn

      @TBugReporter said in Using the PythonScript plugin to automate N++:

      Your “proper” method doesn’t work for me when the reason to end the script is detected in a sub-(sub-sub-…)routine - it just ends the subroutine.

      Not my method; Python’s.

      Here’s some more pure Python (that is on-topic for this forum only because we’re talking about exiting PythonScripts) that answers the question; simply run it to see the effect, varying the if in func3() between if 1 and if 0 for a couple of runs.

      # -*- coding: utf-8 -*-
      from __future__ import print_function
      
      class Exit_exception(Exception): pass
      
      def func3():
          if 0:
              print('func3 determined we should end and do it QUICKLY')
              raise Exit_exception()
          else:
              print('func3 determined we should proceed in an orderly fashion')
      
      def func2():
          print('entered func2, about to call func3')
          func3()
          print('back in func2, after calling func3')
      
      def func1():
          print('entered func1, about to call func2')
          func2()
          print('back in func1, after calling func2')
      
      def main():
          print('entering main, about to call func1')
          try:
              func1()
          except Exit_exception:
              print('back in main and making an exceptional exit!')
              return
          print('back in main, after calling func1')
      
      main()
      

      This is a bit more “advanced” than what was discussed before; I didn’t share it then because it is more complicated and didn’t seem necessary at the time.

      So for the if 0 case, all proceeds normally and in-sequence:

      entering main, about to call func1
      entered func1, about to call func2
      entered func2, about to call func3
      func3 determined we should proceed in an orderly fashion
      back in func2, after calling func3
      back in func1, after calling func2
      back in main, after calling func1
      

      If the code is changed to if 1, then we obtain:

      entering main, about to call func1
      entered func1, about to call func2
      entered func2, about to call func3
      func3 determined we should end and do it QUICKLY
      back in main and making an exceptional exit!
      

      BTW, this is just a “nice” way of achieving the goal. If you want to end abruptly and rudely, just insert this line of code where you want to achieve it:

      1/0 <-- yep, just a 3-character line of code

      Side note: The exception technique can also be used to get out of deeply nested loops, example new func2:

      def func2():
          print('entered func2, about to call func3')
          try:
              while True:
                  while True:
                      while True:
                          while True:
                              while True:
                                  while True:
                                      while True:
                                          while True:
                                              func3()
                                              raise Exit_exception()
          except Exit_exception: pass
          print('back in func2, after calling func3')
      
      TBugReporterT 1 Reply Last reply Reply Quote 2
      • PeterJonesP
        PeterJones @TBugReporter
        last edited by PeterJones

        @TBugReporter ,

        Just as an aside from @Alan-Kilborn’s excellent example of using an exception to bomb out: he handled the exception in main() – and that’s probably Python best-practice, and the right way to show it in an example that others follow

        But just so that you know: if you ever forget to handle an exception, the PythonScript plugin will be forgiving, and won’t exit Notepad++ for you. It will show the traceback for the exception, and will relinquish control back to Notepad++. You’ll have a nice red message

        raise Exception()
        8826f69e-ee18-4372-b2bd-4163fdeea8a6-image.png

        And if you want additional information in your exception printout, you can add it as a text argument when you raise the exception

        raise Exception("extra text")

        6472ce88-bdf4-4e28-99c1-aa1234189ccb-image.png

        Or, using Alan’s Exit_exception() sub-class, the name of the class becomes an indicator of what kind of exception it was:

        97a350ea-ad16-4a8f-9bdf-d81e891d03ef-image.png

        281df0f5-a230-4b78-b33f-cc6d9cdccffd-image.png

        But however you call the exception: after seeing such a red error, you will then want to fix your script so that it won’t happen again.

        And if you want a clean exit – it’s not an “error”, just a way to leave a deep subroutine early – then handling that clean-exit-exception in main() is the Right Thing To Do™

        TL;DR summary

        Catching exceptions at some point is Python best-practice, but if you miss catching one, the PythonScript plugin will not exit Notepad++. Thus, if you want to exit a script early or promptly, raising an exception is a much better choice than using notepad.runPluginCommand('Python Script', 'Stop Script') to push the menu button for you

        Alan KilbornA 1 Reply Last reply Reply Quote 2
        • Alan KilbornA
          Alan Kilborn @PeterJones
          last edited by

          I just noticed that we dealt with the topic of “early return” from a PythonScript before, HERE. That time, the simple return-from-main() technique satisfied the petitioner. :-)

          TBugReporterT 1 Reply Last reply Reply Quote 1
          • TBugReporterT
            TBugReporter @Alan Kilborn
            last edited by

            This post is deleted!
            1 Reply Last reply Reply Quote 0
            • TBugReporterT
              TBugReporter
              last edited by

              Is there any way that the PythonScript plugin can determine the size and position of the N++ window?

              EkopalypseE 1 Reply Last reply Reply Quote 0
              • EkopalypseE
                Ekopalypse @TBugReporter
                last edited by Ekopalypse

                @TBugReporter

                as an example

                import ctypes
                from ctypes import wintypes
                
                rect = wintypes.RECT()
                
                user32 = ctypes.WinDLL("user32")
                hwnd = user32.FindWindowW(u"Notepad++", None)
                user32.GetWindowRect(hwnd, ctypes.byref(rect))
                print("position = ({},{})".format(rect.top, rect.left))
                print("width = {}".format(rect.right - rect.left))
                print("height = {}".format(rect.bottom - rect.top))
                

                Be careful though, these are the outputs for my main and secondary monitor for example

                position = (0,459)
                width = 1079
                height = 1087
                
                >>> 
                position = (-440,1912)
                width = 1096
                height = 1936
                

                and when you start making more and more C interop calls, do yourself a favor and create your own startup.py script (you create it like any other script, but just call it startup.py. It should be stored alongside your other scripts so that it doesn’t conflict with the startup.py that comes with PS by default)

                and do something like this

                from win_api import FindWindow, FindWindowEx
                notepad.hwnd = FindWindow(u'Notepad++', None)
                editor1.hwnd = FindWindowEx(notepad.hwnd, None, u"Scintilla", None)
                editor2.hwnd = FindWindowEx(notepad.hwnd, editor1.hwnd, u"Scintilla", None)
                

                win_api is another file that contains all your C type declarations.
                Just a suggestion.

                Alan KilbornA 1 Reply Last reply Reply Quote 1
                • Alan KilbornA
                  Alan Kilborn @Ekopalypse
                  last edited by

                  @Ekopalypse said in Using the PythonScript plugin to automate N++:

                  FindWindow(u’Notepad++', None)

                  If you have multiple instances of Notepad++ running, is this guaranteed to find the one that the script code is executing within?

                  EkopalypseE 1 Reply Last reply Reply Quote 0
                  • EkopalypseE
                    Ekopalypse @Alan Kilborn
                    last edited by

                    @Alan-Kilborn

                    normaly yes - but 100% guaranteed - I assume no.

                    Alan KilbornA 1 Reply Last reply Reply Quote 0
                    • Alan KilbornA
                      Alan Kilborn @Ekopalypse
                      last edited by

                      @Ekopalypse said in Using the PythonScript plugin to automate N++:

                      normaly yes - but 100% guaranteed - I assume no.

                      I asked because I have some more complicated code to find the correct N++, and I was wondering if it was necessary. I’ll keep it. :-)

                      EkopalypseE TBugReporterT 2 Replies Last reply Reply Quote 1
                      • EkopalypseE
                        Ekopalypse @Alan Kilborn
                        last edited by Ekopalypse

                        @Alan-Kilborn

                        It would be nice if PS would provide this, I know there is an open issue …

                        Alan KilbornA 1 Reply Last reply Reply Quote 0
                        • Alan KilbornA
                          Alan Kilborn @Ekopalypse
                          last edited by

                          @Ekopalypse said in Using the PythonScript plugin to automate N++:

                          would be nice if PS would provide this, I know there is an open issue

                          Yes, HERE, but from the comment HERE the primary person maintaining PS seemed to have no clue as to why it would be valuable. :-(

                          dinkumoilD 1 Reply Last reply Reply Quote 0
                          • TBugReporterT
                            TBugReporter @Alan Kilborn
                            last edited by

                            @Alan-Kilborn said in Using the PythonScript plugin to automate N++:

                            I have some more complicated code to find the correct N++

                            Would you mind sharing?

                            Alan KilbornA 1 Reply Last reply Reply Quote 0
                            • dinkumoilD
                              dinkumoil @Alan Kilborn
                              last edited by

                              @Alan-Kilborn said in Using the PythonScript plugin to automate N++:

                              the primary person maintaining PS seemed to have no clue as to why it would be valuable

                              PythonScript plugin v3.0.15 should contain that feature. See change log 3.0.14 to 3.0.15, commit 2c178d8 from 2022-11-21.

                              EkopalypseE 1 Reply Last reply Reply Quote 0
                              • EkopalypseE
                                Ekopalypse @dinkumoil
                                last edited by

                                @dinkumoil

                                As far as I understand, this was only implemented for the console and is probably why the issue is still open.

                                dinkumoilD 1 Reply Last reply Reply Quote 1
                                • dinkumoilD
                                  dinkumoil @Ekopalypse
                                  last edited by dinkumoil

                                  @Ekopalypse

                                  this was only implemented for the console

                                  Seems like you are right. At least the names of the files changed in the commit I mentioned above indicate that. I’ve missed that, sorry.

                                  1 Reply Last reply Reply Quote 0
                                  • Alan KilbornA
                                    Alan Kilborn @TBugReporter
                                    last edited by Alan Kilborn

                                    @TBugReporter said in Using the PythonScript plugin to automate N++:

                                    Would you mind sharing?

                                    Here’s NppHwnd.py:

                                    # -*- coding: utf-8 -*-
                                    from __future__ import print_function
                                    
                                    from ctypes import (WinDLL, WINFUNCTYPE, create_unicode_buffer, byref)
                                    from ctypes.wintypes import (BOOL, HWND, LPARAM, DWORD)
                                    
                                    def find_npp_hwnd():
                                    
                                        user32 = WinDLL('user32')
                                        kernel32 = WinDLL('kernel32')
                                    
                                        WNDENUMPROC = WINFUNCTYPE(BOOL, HWND, LPARAM)
                                    
                                        our_pid = kernel32.GetCurrentProcessId()
                                        dw_process_id = DWORD()
                                    
                                        ubuff_size = 1024
                                        ubuffer = create_unicode_buffer(ubuff_size)
                                    
                                        notepad.hwnd = 0
                                    
                                        def foreach_window_to_find_npp(hwnd, __):
                                            if user32.IsWindowVisible(hwnd):  # maybe the check for being visible is not necessary?
                                                text_length = user32.GetWindowTextLengthW(hwnd)
                                                if 0 < text_length < ubuff_size:
                                                    user32.GetWindowTextW(hwnd, ubuffer, text_length + 1)
                                                    if u'- Notepad++' in ubuffer.value:
                                                        user32.GetWindowThreadProcessId(hwnd, byref(dw_process_id))
                                                        if dw_process_id.value == our_pid:
                                                            notepad.hwnd = hwnd
                                                            return False  # stop enumerating
                                            return True  # continue enumerating
                                    
                                        user32.EnumWindows(WNDENUMPROC(foreach_window_to_find_npp), 0)  # enumerate Desktop windows
                                    
                                        print('notepad.hwnd:', notepad.hwnd)
                                    
                                    find_npp_hwnd()
                                    

                                    Note that this script, like the original line of @Ekopalypse code ( notepad.hwnd = FindWindow(u'Notepad++', None) ), adds the hwnd member to the pre-existing notepad object.

                                    Perhaps some explanation is in order: This script finds desktop windows with - Notepad++ in their titlebar. Since N++ always has this string of characters in its titlebar, it can be located in this manner. If there happens to be multiple instances of Notepad++ running, multiple windows will be located because they will all have the string in the titlebar – how to tell them apart? This script compares the process id of the located window to see if it is the same id as that under which the script is running; if so then we know we’ve located the desired N++ window.

                                    1 Reply Last reply Reply Quote 2
                                    • TBugReporterT
                                      TBugReporter @Alan Kilborn
                                      last edited by TBugReporter

                                      @Alan-Kilborn
                                      I’m sorry, but I’m still having trouble with exception handling; when Tk is involved, it seems to eat the exception itself instead of passing it up to Python. Some sample code:

                                      # -*- coding: utf-8 -*-
                                      from Npp import MESSAGEBOXFLAGS
                                      
                                      def main():
                                      
                                          print("Program started")
                                          tk_ok = False
                                          try:                                                    # see if we can do pretty dialogs
                                              import Tkinter as tk
                                              import qq                                           # DEBUG:  bogus name to force error triggering
                                              tk_ok = True
                                              print("Found tk")                                   # DEBUG
                                          except ImportError as e:                                # if not, tell user
                                              user_response = notepad.messageBox(
                                                  ("Unable to import Tcl/Tk libraries.\n\n" + e.message), 
                                                  "Missing Library",
                                                  MESSAGEBOXFLAGS.OKCANCEL | MESSAGEBOXFLAGS.ICONWARNING)
                                              if user_response == MESSAGEBOXFLAGS.RESULTCANCEL:
                                                  print("RESULTCANCEL loading Tkinter")           # DEBUG
                                                  raise KeyboardInterrupt                         # closest exception to this condition
                                                  while True:                                     # DEBUG:  wait for stop to happen
                                                      print("Should have stopped!")
                                              elif user_response == MESSAGEBOXFLAGS.RESULTOK:
                                                  print("RESULTOK loading Tkinter")               # DEBUG
                                      
                                          if tk_ok:
                                              my_dlg_bx = tk.Tk()
                                      
                                              def btn_cncl_action():
                                                  print("Program CANCELLED")
                                                  my_dlg_bx.destroy()
                                                  raise KeyboardInterrupt                         # BUG:  doesn't work like above
                                                  while True:                                     # DEBUG:  wait for stop to happen
                                                      print("Should have stopped!")
                                              btn_cncl     = tk.Button     (
                                                  my_dlg_bx,
                                                  command     = btn_cncl_action,
                                                  text        = "Cancel",
                                                  width       = 10,
                                                                           )
                                              btn_cncl.pack    (padx   = 10,
                                                                pady   = 10,
                                                                side   = tk.RIGHT
                                                               )
                                      
                                              my_dlg_bx.attributes("-toolwindow", True)
                                              my_dlg_bx.attributes("-topmost", True)
                                              my_dlg_bx.resizable(width = False, height = False)
                                              my_dlg_bx.title("My Custom Dialog Box")
                                              my_dlg_bx.mainloop()
                                          # end "if tk_ok"
                                      
                                          print("Program should NOT get here if user clicks Cancel button")
                                          # do main program stuff here
                                      
                                          print("Program ended")                                  # DEBUG
                                      
                                      main()
                                      

                                      This code does as I expect - so long as the exception is in the testing for Tk. Comment out import qq, let Tk create and display “My Custom Dialog Box”, and click on its “Cancel” button, and a similar traceback appears in the console - but preceded by Exception in Tkinter callback. What can I do to get these exceptions both treated the same way? (And yes, I do realize that this sample code does nothing to actually catch the exception; I wanted to be sure it wasn’t my code that was eating it.)


                                      And on a (probably) unrelated note, why is the “Plugins Admin” version of PythonScript so old? There are probably lots of people using it that don’t realize how outdated it is. Plus, I imagine it makes it more difficult to assist users when they’re likely not using the same version that you are.

                                      Alan KilbornA 1 Reply Last reply Reply Quote 0
                                      • Alan KilbornA
                                        Alan Kilborn @TBugReporter
                                        last edited by

                                        @TBugReporter said in Using the PythonScript plugin to automate N++:


                                        I’m still having trouble with exception handling

                                        General Python exception handling questions are off-topic for this forum.


                                        why is the “Plugins Admin” version of PythonScript so old?

                                        If I look at it, it shows the current version. Maybe you could be more specific in this question, like exactly what you see and why you think it is old?

                                        TBugReporterT 1 Reply Last reply Reply Quote 0
                                        • TBugReporterT
                                          TBugReporter @Alan Kilborn
                                          last edited by

                                          @Alan-Kilborn said in Using the PythonScript plugin to automate N++:

                                          General Python exception handling questions are off-topic for this forum.

                                          Yeah, but every time I try asking somewhere else they say “Old Python versions are off topic here”.
                                          Head Bang Emoji

                                          Maybe you could be more specific in this question, like exactly what you see and why you think it is old?

                                          old Python.png

                                          PeterJonesP 1 Reply Last reply Reply Quote 0
                                          • PeterJonesP
                                            PeterJones @TBugReporter
                                            last edited by PeterJones

                                            @TBugReporter,.

                                            Yeah, but every time I try asking somewhere else they say “Old Python versions are off topic here”.

                                            The overflowing stack of code writers don’t have such a restriction that I know of.

                                            old Python.png

                                            You complained to us about an old version of PythonScript in Plugins Admin, but the screenshot highlights that you’re actually wondering about the old version of the Python interpreter that’s part of PythonScript Plugin.

                                            PythonScript is currently developing a Python 3 version of PythonScript Plugin, which is available for download in the PythonScript repository… But since it’s a huge project, it’s still considered alpha/beta and thus not in Plugins Admin yet. But that doesn’t stop you from installing it manually.

                                            Michael VincentM TBugReporterT 2 Replies Last reply Reply Quote 0
                                            • First post
                                              Last post
                                            The Community of users of the Notepad++ text editor.
                                            Powered by NodeBB | Contributors