Notepad++ official documentation is online now





  • blocked…

    Powered By Fortinet
    FortiGuard Web Filtering
    blocked
    Web Page Blocked!
    You have tried to access a web page which is in violation of your internet usage policy.
    URL: https://npp-user-manual.org/
    Category: Newly Observed Domain
    User name:
    Group name:

    To have the rating of this web page re-evaluated please click here.



  • @Allen-Bai said:

    To have the rating of this web page re-evaluated please click here.

    it’s a newly-created domain, which is why it matches that “Newly Observed Domain” category. Not much we can do about that. You, however, apparently have a link you can click (you didn’t share a clickable link with us), so you should ask them to re-evaluate and determine whether this newly-observed-domain is safe. (Seems to me, blocking newly created sites by default is a bit rude.)



  • Please add a chapter about context menu customization.



  • @dinkumoil said:

    Please add a chapter about context menu customization.

    The old documentation for this is (sort of) here. Seems like it is not bad; could be used as a basis for something new.

    Reading it now, I see:

    The Tab Bar Context Menu
    For completeness’ sake, there is another menu that would qualify as a context menu. It is the one that pops up when right clicking a tab in any view. However, there isn’t much to say about it here, as it is not configurable. This is not related with its being practical. Plugins can manipulate it.

    The part I bolded got me thinking “I wonder if @Ekopalypse has some nice Pythonscript code for showing how to do this, for adding/removing items?”. Eko was certainly a master Pythonscripter for getting the Shortcut Mapper to cough up its mappings, so… :)



  • @Ekopalypse Does the upvote mean you’re going to share something soon? :)



  • for adding/removing items

    Do you mean adding/removing on the fly aka at runtime?
    If so, never thought about it.
    I assume hooking the window procedure is the way to go. Hmm, interesting.

    Eko was certainly a master Pythonscripter

    Lately I learned that my knowledge about python isn’t that good :-(



  • @Ekopalypse said in Notepad++ official documentation is online now:

    Do you mean adding/removing on the fly aka at runtime?

    I suppose I mean somehow achieving with P.S. a way to customize this (tab bar rclick context) menu, much like the regular (editor window rclick) context menu can be customized. The method would be very different, but the results would be similar.

    Lately I learned that my knowledge about python isn’t that good

    I’m having a hard time believing that.



  • for testing my notepad wrapper methods I’ve modified the test from the pythonscript plugin

            from threading import Thread
            import queue
            item_count = -1
            def start_monitor(_q):
                MN_GETHMENU = 0x1E1
                i = 10
                tabbar_context_menu_hwnd = 0
                while i:
                    tabbar_context_menu_hwnd = user32.FindWindowExW(None, None, '#32768', None)
                    log(tabbar_context_menu_hwnd)
                    if tabbar_context_menu_hwnd:
                        break
                    else:
                        time.sleep(.1)
                        i -= 1
    
                if tabbar_context_menu_hwnd:
                    menu_handle = user32.SendMessageW(tabbar_context_menu_hwnd, MN_GETHMENU, 0, 0)
                    global item_count
                    item_count = user32.GetMenuItemCount(menu_handle)
                    _q.put(item_count)
                    user32.SendMessageW(tabbar_context_menu_hwnd, WM_CLOSE, 0, 0)
    
            q = queue.Queue()
            t = Thread(target=start_monitor, args=(q,))
            t.start()
            self.assertIsNone(notepad.triggerTabbarContextMenu(0,0))
            t.join()
            item_count = q.get()
            self.assertNotEqual(item_count, -1, msg="Can't find popup menu")
            self.assertEqual(item_count, 28, msg=f'Expected 28 menu items but received:{item_count}')
    

    so yes, it is possible to use it as an starting point, I guess.
    Let me think about it.

    I’m having a hard time believing that.

    Then you better don’t check the official python mailing list :-(



  • Just a word of warning - don’t run the current code unmodified as it might
    hang npp.



  • @Alan-Kilborn

    basically you run this once to register your menu items
    and the hook. Then every time you right click the tabbar menu you
    should see your newly created menu items.
    Not sure whether this is the most optimized version but I guess
    a good starting point.

    The critical parts is the notepad.allocateCmdID and passing this value
    to the menuitem wID field.

    MENU_MAPPER holds this id and the function which should get executed.

    from threading import Thread
    import queue
    import time
    
    import ctypes
    from ctypes import wintypes  
    import platform
    
    user32 = ctypes.WinDLL('user32', use_last_error=True)
    MENU_MAPPER = {}
    
    LRESULT = wintypes.LPARAM
    ULONG_PTR = wintypes.LPVOID
    
    WndProcType = ctypes.WINFUNCTYPE(LRESULT, wintypes.HWND, wintypes.UINT, wintypes.WPARAM, wintypes.LPARAM)
    
    CallWindowProc = user32.CallWindowProcW
    CallWindowProc.restype = LRESULT
    CallWindowProc.argtypes = [WndProcType, wintypes.HWND, wintypes.UINT, wintypes.WPARAM, wintypes.LPARAM]
    
    x86 = platform.architecture()[0] == '32bit'
    SetWindowLong = user32.SetWindowLongW if x86 else user32.SetWindowLongPtrW
    SetWindowLong.restype = WndProcType
    SetWindowLong.argtypes = [wintypes.HWND, wintypes.INT, WndProcType]
    
    CONFIGURATION = None
    
    MFS_ENABLED = 0x0
    MFS_DISABLED = 0x3
    MIIM_STATE = 1
    MIIM_ID = 2
    MIIM_SUBMENU = 4
    MIIM_STRING = 64
    MFT_STRING = 0
    MFS_ENABLED = 0
    
    class MENUITEMINFO(ctypes.Structure):
        ''' Implements the winapi MENUITEMINFO structure'''
        _fields_ = [('cbSize', wintypes.UINT),
                    ('fMask',  wintypes.UINT),
                    ('fType',  wintypes.UINT),
                    ('fState',  wintypes.UINT),
                    ('wID',  wintypes.UINT),
                    ('hSubMenu',  wintypes.HMENU),
                    ('hbmpChecked',  wintypes.HBITMAP),
                    ('hbmpUnchecked', wintypes.HBITMAP),
                    ('dwItemData',  ULONG_PTR),
                    ('dwTypeData',  wintypes.LPWSTR),
                    ('cch',  wintypes.UINT),
                    ('hbmpItem',  wintypes.HBITMAP),]
    
    
    class Hook():
    
        def __init__(self):
            self.nppHandle = user32.FindWindowW(u'Notepad++', None)
    
        def register(self):
            self.newWndProc = WndProcType(self.MyWndProc)
            self.prevWndProc = SetWindowLong(self.nppHandle, -4, self.newWndProc)
    
        def MyWndProc(self, hWnd, msg, wParam, lParam):
            if msg == 273 and wParam in MENU_MAPPER:
                MENU_MAPPER[wParam]()
    
            # hand control back to npp
            return CallWindowProc(self.prevWndProc, hWnd, msg, wParam, lParam)
    
    def modify_context_menu():
        i = 10
        tabbar_context_menu_hwnd = 0
        while i:
            tabbar_context_menu_hwnd = user32.FindWindowExW(None, None, u'#32768', None)
            if tabbar_context_menu_hwnd:
                menu_handle = user32.SendMessageW(tabbar_context_menu_hwnd, MN_GETHMENU, 0, 0)
    
                mii = MENUITEMINFO()
                mii.cbSize = ctypes.sizeof(mii)
                mii.fMask = MIIM_ID | MIIM_STRING
                mii.fType = MFT_STRING
                mii.fState = MFS_ENABLED
    
                startid = notepad.allocateCmdID(2)
    
                mii.wID = startid
                mii.dwTypeData = "TEST1"
                user32.InsertMenuItemW(menu_handle, 0, True, ctypes.byref(mii))
                MENU_MAPPER[startid] = lambda: console.write('TEST1 clicked\n')
    
                mii.wID = startid+1
                mii.dwTypeData = "TEST2"
                user32.InsertMenuItemW(menu_handle, 0, True, ctypes.byref(mii))
                MENU_MAPPER[startid+1] = lambda: console.write('TEST2 clicked\n')
    
                break
    
            else:
                time.sleep(.1)
                i -= 1
    
    MN_GETHMENU = 0x1E1
    t = Thread(target=modify_context_menu)
    t.start()
    notepad.triggerTabbarContextMenu(0,6)
    t.join()
    hook = Hook()
    hook.register()
    print('done')
    


  • @Ekopalypse

    So, it does work to achieve the goal, but 2 immediate things were noticed.

    1. import queue fails because there is no module queue in the Python that ships with Pythonscript. However, this isn’t a real problem, because the code doesn’t even use queue. Easy enough to remove the line. Done.

    2. A bigger problem; when exiting Notepad++ after this “new feature” has been added, I get this, every time:

    8e787af3-3493-427e-8d7a-ac34248faafa-image.png



  • @Alan-Kilborn

    Do you already have another HOOK registered?
    Which Npp version and which PS version are you currently running?

    Right, in python3 it is called queue and in python2 it is called Queue.
    Removed the code but forgot to remove the import.

    This part is critical if it is already used but maybe slightly different defined.

    LRESULT = wintypes.LPARAM
    ULONG_PTR = wintypes.LPVOID
    
    WndProcType = ctypes.WINFUNCTYPE(LRESULT, wintypes.HWND, wintypes.UINT, wintypes.WPARAM, wintypes.LPARAM)
    
    CallWindowProc = user32.CallWindowProcW
    CallWindowProc.restype = LRESULT
    CallWindowProc.argtypes = [WndProcType, wintypes.HWND, wintypes.UINT, wintypes.WPARAM, wintypes.LPARAM]
    
    x86 = platform.architecture()[0] == '32bit'
    SetWindowLong = user32.SetWindowLongW if x86 else user32.SetWindowLongPtrW
    SetWindowLong.restype = WndProcType
    SetWindowLong.argtypes = [wintypes.HWND, wintypes.INT, WndProcType]
    


  • @Ekopalypse said in Notepad++ official documentation is online now:

    Do you already have another HOOK registered?

    Very likely. Is this a problem?

    Which Npp version and which PS version are you currently running?

    7.7.1 32-bit PS1.3.0.0

    PS1.4.0.0 has a trivial amount of difference from 1.3.0.0 so that’s why.

    This part is critical if it is already used but maybe slightly different defined.

    This I don’t understand.

    Are you glad I got you involved in this? :)



  • @Alan-Kilborn

    Are you glad I got you involved in this? :)

    Yes of course :-D, and actually I do something similar with python3script while handling the
    menu items (in PS when you create a script it appears in the script menu - I do the same) so it is important for me as well.

    If you hook the message queue you need to ensure that data passed and provided do not get mangled in between
    because at then end npp needs to handle the messages as well.

    I actually never tested whether this codes work with 32bit and because of the following

    LRESULT = wintypes.LPARAM
    CallWindowProc.restype = LRESULT
    
    
    x86 = platform.architecture()[0] == '32bit'
    SetWindowLong = user32.SetWindowLongW if x86 else user32.SetWindowLongPtrW
    

    it might be that I’m doing the wrong thing.
    Let me check with a npp 32bit version and msdn.



  • @Alan-Kilborn

    seems my code is correct and works on npp and PS 32bit version.
    According to MSDN SetWindowLongPtrW is for x64 and SetWindowLongW for 32bit.
    WindowProc callback function returns LRESULT which is defined as typedef LONG_PTR LRESULT;
    the same as LPARAM (wich is also typedef LONG_PTR LPARAM;)
    LONG_PTR is defined to be int64 on 64bit and long on 32bit

    #if defined(_WIN64)
     typedef __int64 LONG_PTR; 
    #else
     typedef long LONG_PTR;
    #endif
    

    So I don’t see a problem with my code what means that one of your other hook(s),
    defines something slightly different and corrupts the message handling, I guess.



  • @Ekopalypse said in Notepad++ official documentation is online now:

    one of your other hook(s),
    defines something slightly different and corrupts the message handling

    Okay, I will go examine those others and use your way of doing it as the definitive way it should be done.


Log in to reply