New built-in Plugin Admin (Plugin Manager) is ready



  • @pnedev

    The installation path for plugins should be $Notepad++_Install_folder\plugins mainly because of the portable version of Notepad++.

    If Notepad++ is not installed in “Program Files” and doLocalConf.xml is present, Plugins Admin will install plugins into “$Notepad++_Install_folder\plugins” - is it not the case for you?

    Edit: I just checked in current official released packages, GUP.exe, which is needed by Plugins Admin, is not available in any zip packages (and it should not be, since GUP.exe will get installer EXE and execute it - that’s not the expected behaviour of portable version). I have no (yet) a perfect solution for it. So both GUP.exe and nppPluginList.dll will only be included in installer in the next release.

    The Notepad++ restart upon plugin installation is not very elegant since this is just a DLL being plugged into the editor and all it needs to work properly is the right sequence of notifications.

    I couldn’t agree more.

    AFAIK, what a plugin needs to initialize properly is loading its main DLL (LoadLibrary() - this will also trigger DLL_PROCESS_ATTACH) then call getName() and getFuncsArray() and setInfo() and last send NPPN_READY notification. That should make it fully functional (unless I’m missing something).
    In the case of updating a plugin, the plugin has to properly unregister itself with Notepad++ on DLL_PROCESS_DETACH so there are no conflicts in the consequent plugin reload.

    Yes, that’s my point. The process to load a just installed plugin is the same as the one which is loaded from the startup of Notepad++.
    Is there any other plugin doesn’t work after its installation by Plugins Admin?



  • @pnedev
    Thank you for your code snippet. I had this method not in mind because in Delphi the DLL entry function is hidden in the RTL. It is only possible to install a hook function with the following signature DllMain(DWORD reasonForCall). The DLL instance handle has to be obtained from a global variable called HInstance.

    @pnedev said:

    The Notepad++ restart upon plugin installation is not very elegant since this is just a DLL being plugged into the editor and all it needs to work properly is the right sequence of notifications.

    what a plugin needs to initialize properly is loading its main DLL (LoadLibrary() - this will also trigger DLL_PROCESS_ATTACH) then call getName() and getFuncsArray() and setInfo() and last send NPPN_READY notification.

    Yes, this should do the thing but NppFTP seems to be an example where this sequence isn’t sufficient. Maybe there are other plugins where it is the same (Wild guessing: There could be required some WM_XXX messages, in the worst case this may be plugin dependent).

    My point is the following:

    • Plugin installation happens only one times, also uninstallation. In contrast updating a plugin happens more frequently.
    • Performing plugin updates restart-free is (as far as I know) a complicated thing, at all it makes no sense (see below).
    • If it is not possible to update plugins restart-free why there should be invested effort to make installations restart-free?

    Why do I think restart-free plugin updates are complicated?

    Plugins can manipulate the UI of Npp in various ways:

    1. Inserting icons into the toolbar.
    2. Inserting icons into menus (Menu Icons plugin).
    3. Manipulating the whole toolbar (Customize Toolbar plugin).
    4. Adding an entry to the Plugins menu including a submenu.
    5. Adding other menu entries in arbitrary menus of Npp (ToolBucket plugin) or even adding a new menu in the menu bar (TextFX plugin).
    6. Opening (docked) windows and modeless dialogs.
    7. Inserting new windows in Npp’s UI (e.g. HEX editor’s address bar above the hex data).

    All these manipulations have to be reverted when updating a plugin before Npp unloads its DLL and this case should be distinguishable from simply terminating Npp, thus it can’t be done in DllMain as a reaction to a DLL_PROCESS_DETACH call - there has to be a new Npp notification to inform plugins that they are about to be uninstalled (updating is a sequence of uninstall - install).

    Furthermore, currently there is only a NPPM_ADDTOOLBARICON and no NPPM_REMOVETOOLBARICON (see 1.); there is only a method to add entries to the plugin’s submenu in the Plugins menu and no method to remove them (see 4.); the other points (2., 3., 5., 6. and 7.) are based solely on plugin code and require more or less effort to revert them.

    Why do I think restart-free plugin updates make no sense?

    There are two categories of plugins:

    • Stateless plugins
    • Stateful plugins

    Stateless plugins perform simple actions, e.g. my AutoCodepage plugin (it monitors user actions and switches the document encoding scheme if neccessary) or @donho 's PorkToSausage (works only on-demand). These plugins are not critical for restart-free plugin updates.

    Stateful plugins create a state by themself, e.g. NppFTP, @pnedev 's Compare plugin, scripting plugins with debugging capabilities (e.g. CS-Script) or the HEX editor plugin. These plugins are critical for restart-free plugin updates.

    A state includes:

    • open windows (all plugins mentioned above)
    • open network connections (NppFTP)
    • set path for FTP session (NppFTP)
    • processing data and displaying results (Compare)
    • state of a debug session (CS-Script)
    • maybe more that I’m not aware of at the moment

    To restore this state after a plugin update requires much effort, I guess that nobody wants to do that, maybe its even impossible. Thus restart-free updating makes no sense - after updating users would have to restore the desired state by themself, there would be no benefit compared to the current situation.



  • @donho ,

    If Notepad++ is not installed in “Program Files” and doLocalConf.xml is present, Plugins Admin will install plugins into “$Notepad++_Install_folder\plugins” - is it not the case for you?

    I haven’t tried with portable Notepad++ yet. I just have Notepad++ installed in Program Files (x86) and after installing NppGTags plugin via Plugins Admin it is put in C:\Users\<user-name>\AppData\Local\Notepad++\plugins.

    Is there any other plugin doesn’t work after its installation by Plugins Admin?

    Yes, NppGTags is also not functional after installation via Plugins Admin. After Notepad++ restart everything is working as expected.

    @dinkumoil ,

    Maybe there are other plugins where it is the same

    You are correct - NppGTags is also not working without Notepad++ restart at the moment.

    There could be required some WM_XXX messages, in the worst case this may be plugin dependent

    Hm, those messages should be a consequence of loading a plugin so they should be there.

    You have provided a very thorough description of the plugin update problem, good points there.

    Well, I agree that if updating a plugin is such a pain then it is not worth the work to make it restart-free so basically I agree with you.

    Just as a note about the Plugins menu - I suppose Notepad++ knows which menu entries are connected to which plugin (it has some internal ID) and will remove them on plugin’s behalf when unloading it.



  • Maybe an additional flag “RestartRequired” in the json could do the magic to distinguish between plugins updateable on-the-fly and the ones which are not.



  • @dinkumoil

    Good point.
    The installation will follow update behaviour - ie. quit Notepad++, install the plugins then restart Notepad++.
    All the binaries are updated in the following link:
    https://notepad-plus-plus.org/pluginListTestTools/



  • @donho

    I’ve tested the new binaries. Here are the results.

    1. If the SHA-256 hash of a plugin ZIP file is provided with uppercase letters plugin installation fails, saying that the hash value is wrong and the plugin is about to be deleted. Wouldn’t it be better to check hashes case insensitive?
    2. If the download URL for a plugin ZIP file is wrong plugin installation fails, saying that the hash value is wrong and the plugin is about to be deleted. There should be a better error message pointing to the actual cause of the error.
    3. Portable installations of Npp install plugins under <folder-of-portable-Npp>\plugins\<plugin-name> whereas local installations of Npp install them under <Userprofile>\AppData\Local\Notepad++\plugins\<plugin-name>. The plugins which are packaged with Npp installer are always stored under <Npp-folder>\plugins\<plugin-name>. This is confusing and additionally there is a pitfall when upgrading from Npp 32 bit to Npp 64 bit (local installations) - the plugins for both architectures are stored in the same location (<Userprofile>\AppData\Local\Notepad++\plugins\<plugin-name>) thus at first start of newly installed 64 bit Npp users will be confrontated with a bunch of alert boxes stating that plugin XYZ is not suitable for the current version of Npp.
    4. In these alert boxes users can choose to remove the plugin in question to avoid the same error message at next start of Npp. But the plugin gets not deleted.
    5. In the 32 bit version of the plugin list (pl.x86.json) currently is a syntax error. You left a semicolon instead of a comma before the block of the NppSaveAsAdmin plugin.

    Points 3. and 4. make me to ask again if it wouldn’t be better to install plugins in all cases under <Npp-folder>\plugins\<plugin-name>. Plugin installations are performed with a restart now. I guess GUP is actually doing the job to copy plugin files, it would be easy to start it with admin rights (ShellExecute with verb runas) so it is able to copy files even to a subfolder of C:\Program Files.

    To cope with the current situation I had to reimplement the function to get the plugin DLL path (which is now identical with the path for all companion files of the plugin, e.g documentation, help files), a code change which has to be done for many other plugins too (with all the problems caused by that fact I already mentioned).



  • @dinkumoil

    Thank you for your tests and pertinent suggestions.

    Wouldn’t it be better to check hashes case insensitive?

    Done. Both 32/64 bits of GUP.exe are updated and available to download now.

    In the 32 bit version of the plugin list (pl.x86.json) currently is a syntax error. You left a semicolon instead of a comma before the block of the NppSaveAsAdmin plugin.

    Fixed and committed.

    Points 3. and 4. make me to ask again if it wouldn’t be better to install plugins in all cases under <Npp-folder>\plugins<plugin-name>.

    The reason of installation to <Userprofile>\AppData\Local\Notepad++\plugins\<plugin-name> is Microsoft’s Universal Windows Platform. A UWP package is immutable so there’s no way to install plugins into Notepad++ installation directory. That says, in the next release while Notepad++ installation if the installation destination is in “Program files”, Notepad++ installer will install the default plugins into <Userprofile>\AppData\Local\Notepad++\plugins\<plugin-name> and Notepad++ loads the plugins only from <Userprofile>\AppData\Local\Notepad++\plugins\<plugin-name>.



  • @donho said:

    The reason of installation to <Userprofile>\AppData\Local\Notepad++\plugins\<plugin-name> is Microsoft’s Universal Windows Platform.

    Ahh, got it. Another suggestion: My current implementation to retrieve the path of the plugin DLL and its companion files is as follows (I ported it from Delphi to C because I’m not a C++ dev):

    LPTSTR GetPluginsDir(HMODULE hModule)
    {
      LPTSTR szBuf;
      DWORD  dwBufLen;
      DWORD  nCCh;
    
      dwBufLen = MAX_PATH;  // Initial buffer size, has to be greater than 0
      szBuf    = NULL;      // Buffer memory is allocated in loop
    
      do
      {
        // Resize buffer and try to get module path
        szBuf = (LPTSTR) realloc(szBuf, dwBufLen * sizeof(*szBuf));
        nCCh  = GetModuleFileName(hModule, szBuf, dwBufLen);
    
        // If nCCh is 0 there was an error
        //   => leave loop
        // if nCCh is less than dwBufLen the buffer size was sufficient
        //   => leave loop
        // If nCCh is equal to dwBufLen the buffer size was too small
        //   => loop and retry with double sized buffer
        // nCCh greater than dwBufLen is a non-existing case
        if (nCCh < dwBufLen) break;
        dwBufLen *= 2;
      } while (TRUE);
    
      if (nCCh != 0) // Retrieving path succeeded
      {
        // Resize buffer to desired size
        szBuf = (LPTSTR) realloc(szBuf, (nCCh + 1) * sizeof(*szBuf));
      }
      else // Retrieving path failed
      {
        // Free buffer and return NULL
        free(szBuf);
        szBuf = NULL;
      }
    
      return szBuf;
    }
    

    Seems complicated but starting with Windows 10 it is no longer sufficient to deal with MAX_PATH to allocate buffers for file paths since they can be longer now. When writing the code I had in mind to cover all possible installation scenarios for Npp. A portable installation can be located in a deeply nested path, thus the loop for buffer allocation.

    But for the most part, the bad design of the GetModuleFileName API is to blame for the complicated code. Would you please provide a Npp message which can be used to query the folder of a plugin DLL file? This would make plugin devs life more easy. Ideally it schould be designed like the NPPM_GETFULLPATHFROMBUFFERID call.



  • @dinkumoil

    Ideally it schould be designed like the NPPM_GETFULLPATHFROMBUFFERID call

    It’s not clear to me at all. Could you define the API you need by using this form:

    	#define NPPM_GETFULLPATHFROMBUFFERID (NPPMSG + 58)
    	// INT NPPM_GETFULLPATHFROMBUFFERID(UINT_PTR bufferID, TCHAR *fullFilePath)
    	// Get full path file name from a bufferID.
    	// Return -1 if the bufferID non existing, otherwise the number of TCHAR copied/to copy
    	// User should call it with fullFilePath be NULL to get the number of TCHAR (not including the nul character),
    	// allocate fullFilePath with the return values + 1, then call it again to get full path file name
    


  • It’s not clear to me at all

    ??

    And to think, all this pain was wrought by one little ad in the corner of a window…



  • Was getting ready to add some of my plugins to the list, however I’m not able to get any installation to work. A few minutes ago I grabbed the notepad++ and gup exes, and the new plugin list json file.

    1. Don’t have any plugins installed
    2. Start notepad++ as administrator
    3. Go to plugin admin, no plugins are listed under “Updates” Or “Installed”
    4. Select DSpellCheck
    5. Click install
    6. Notepad++ says it will restart
    7. Click Ok
    8. Nothing happens
    9. Wait a bit, start N++ manually, nothing is installed
    Notepad++ v7.5.9   (32-bit)
    Build time : Oct 31 2018 - 19:53:57
    Path : C:\Program Files (x86)\Notepad++\notepad++.exe
    Admin mode : ON
    Local Conf mode : OFF
    OS : Windows 7 (64-bit)
    Plugins : none
    


  • @dail
    Could you paste here your plugin entry (32-bits) to add in nppPluginList.cpp ?
    I will check it.



  • @donho

    I’m wanting to add these entries (for 32 bit) but have no way to test it currently:

    {
    	"folder-name": "BetterMultiSelection",
    	"display-name": "BetterMultiSelection",
    	"version": "1.3",
    	"id": "ef3f4e8ffac54fd7e6c9abcd785df09482cba2d63ab68c6bc9bffa71e4053de5",
    	"repository": "https://github.com/dail8859/BetterMultiSelection/releases/download/v1.3/BetterMultiSelection_v1.3.zip",
    	"description": "Provides better cursor movements when using multiple selections.",
    	"author": "Justin Dailey",
    	"homepage": "https://github.com/dail8859/BetterMultiSelection"
    },
    {
    	"folder-name": "DoxyIt",
    	"display-name": "DoxyIt",
    	"version": "0.4.3",
    	"id": "401bc3413e90f87baf6d31ceb800e6fe7d8df54fe063fe659c713eddeec8e2f3",
    	"repository": "https://github.com/dail8859/DoxyIt/releases/download/v0.4.3/DoxyIt.zip",
    	"description": "Support for creating Doxygen comments.",
    	"author": "Justin Dailey",
    	"homepage": "https://github.com/dail8859/DoxyIt"
    },
    {
    	"folder-name": "ElasticTabstops",
    	"display-name": "ElasticTabstops",
    	"version": "1.3",
    	"id": "6d2d8ba941d060e9caca6bb84e0b3864173b93c164de97b663f66d4998749d3a",
    	"repository": "https://github.com/dail8859/ElasticTabstops/releases/download/v1.3/ElasticTabstops.zip",
    	"description": "Support for Elastic Tabstops.",
    	"author": "Justin Dailey",
    	"homepage": "https://github.com/dail8859/ElasticTabstops"
    },
    {
    	"folder-name": "LuaScript",
    	"display-name": "LuaScript",
    	"version": "0.8",
    	"id": "b10c358552ab9d12990c3005e3a45ee0c736587e4025fc1cfd054f1325c9ef46",
    	"repository": "https://github.com/dail8859/LuaScript/releases/download/v0.8/LuaScript_v0.8.zip",
    	"description": "Adds Lua scripting capabilities. This provides control over all of Scintilla's features and options with a light-weight, fully-functional programming language.",
    	"author": "Justin Dailey",
    	"homepage": "https://github.com/dail8859/LuaScript"
    },
    {
    	"folder-name": "SurroundSelection",
    	"display-name": "SurroundSelection",
    	"version": "1.1",
    	"id": "632f7411f00aef29ecf565415df79cc4edb6a6518fa620073567d379583d81b8",
    	"repository": "https://github.com/dail8859/SurroundSelection/releases/download/v1.1/SurroundSelection.zip",
    	"description": "Automatically surround the selection in quotes/brackets/parenthesis/etc.",
    	"author": "Justin Dailey",
    	"homepage": "https://github.com/dail8859/SurroundSelection"
    }


  • @dail
    All passed.
    Will you pass me the 64-bits ones so I’ll include them in the nppPluginList for the next release ?



  • @donho

    64 bit plugin entries:

    {
    	"folder-name": "BetterMultiSelection",
    	"display-name": "BetterMultiSelection",
    	"version": "1.3",
    	"id": "515db8992abbd434da7f05192300283834e7d3ffb49068ab9db018aaf8ec00f0",
    	"repository": "https://github.com/dail8859/BetterMultiSelection/releases/download/v1.3/BetterMultiSelection_v1.3_x64.zip",
    	"description": "Provides better cursor movements when using multiple selections.",
    	"author": "Justin Dailey",
    	"homepage": "https://github.com/dail8859/BetterMultiSelection"
    },
    {
    	"folder-name": "DoxyIt",
    	"display-name": "DoxyIt",
    	"version": "0.4.3",
    	"id": "93802623dfd1e3db2cab6148f3158d6b3ca98a2f9556cc57bdc4228347acb62d",
    	"repository": "https://github.com/dail8859/DoxyIt/releases/download/v0.4.3/DoxyIt_64.zip",
    	"description": "Support for creating Doxygen comments.",
    	"author": "Justin Dailey",
    	"homepage": "https://github.com/dail8859/DoxyIt"
    },
    {
    	"folder-name": "ElasticTabstops",
    	"display-name": "ElasticTabstops",
    	"version": "1.3",
    	"id": "8cbb183c1fa059480739ce4be9b46b2c07ecf3360e5b1bfc373bee0cfde94d1d",
    	"repository": "https://github.com/dail8859/ElasticTabstops/releases/download/v1.3/ElasticTabstops_64.zip",
    	"description": "Support for Elastic Tabstops.",
    	"author": "Justin Dailey",
    	"homepage": "https://github.com/dail8859/ElasticTabstops"
    },
    {
    	"folder-name": "LuaScript",
    	"display-name": "LuaScript",
    	"version": "0.8",
    	"id": "15e355c7f299059d743950a6d53fe24cc7035ea3221736cb3d4d8a13ae6300f6",
    	"repository": "https://github.com/dail8859/LuaScript/releases/download/v0.8/LuaScript_v0.8_x64.zip",
    	"description": "Adds Lua scripting capabilities. This provides control over all of Scintilla's features and options with a light-weight, fully-functional programming language.",
    	"author": "Justin Dailey",
    	"homepage": "https://github.com/dail8859/LuaScript"
    },
    {
    	"folder-name": "SurroundSelection",
    	"display-name": "SurroundSelection",
    	"version": "1.1",
    	"id": "f6e02a1f4872995ba84e37a4c667598c970aa2ea46307a7b983c663f2ee46c8c",
    	"repository": "https://github.com/dail8859/SurroundSelection/releases/download/v1.1/SurroundSelection_64.zip",
    	"description": "Automatically surround the selection in quotes/brackets/parenthesis/etc.",
    	"author": "Justin Dailey",
    	"homepage": "https://github.com/dail8859/SurroundSelection"
    }


  • @Scott-Sumner

    And to think, all this pain was wrought by one little ad in the corner of a window…

    No Ads in the application even it’s a tiny one - it’s my taste, sorry if you’re offended by it. :)

    During the implementation of PA, not only the project itself is interesting, but also the plugin folder structure has been reviewed and modified to make it better and cleaner. Not mention to it’ll bring the new features in Notepad++ in the future (SHA-256) and in GUP project as well.
    You’re right that It’s painful, but the final product with iteration (thanks to the help of community) is not so bad.

    I could just remove Plugin Manager and ignore the request of community, but I didn’t and I’m quite happy to make implementation of PA done finally.



  • @dail said:

    64 bit plugin entries:
    All the 64-bits packages don’t work.

    Quick check in the SurroundSelection, found out the reason:
    The name of plugin in the package is SurroundSelection_64.dll instead of SurroundSelection.dll.
    I guess it’s the case for all other packages.

    Please fix the packages then I’ll test it again.



  • @donho said:

    Could you define the API you need by

    Back from garden work and too tired to do anything. Will do it tomorrow.



  • @donho said:

    Quick check in the SurroundSelection, found out the reason:
    The name of plugin in the package is SurroundSelection_64.dll instead of SurroundSelection.dll.

    Ah yes you are right. Unfortunately I don’t know when I’ll have time to repackage/release them with the correct naming convention.



  • @donho

    Query full plugin DLL file path by message sent to Npp

    As far as I know Npp is not able to know which plugin has sent a message to it. Thus the plugin has to send:

    • its HMODULE (or some other unique ID known by Npp)
    • a pointer to a buffer receiving the plugin DLL path
    • the length of this buffer.

    These are three parameters, that means it is one too much. In the following my proposal.

    An additional struct type has to be introduced which can hold the buffer pointer and its length. The DLL HMODULE should be send as WPARAM and a pointer to the aforementioned struct as LPARAM.

    #define NPPM_GETPLUGINDLLFILEPATH (NPPMSG + 97)
    // BOOL NPPM_GETPLUGINDLLFILEPATH(HMODULE hDllModuleHandle, bufferDesc *buffer)
    // Get full plugin DLL file path.
    // If the function succeeds the return value is TRUE and the variable bufLen
    // points to contains the number of TCHARs which have been copied to the buffer
    // (not including the terminating 0-TCHAR).
    // If the buffersize is too small the function fails. The return value is FALSE
    // and the variable bufLen points to contains the number of TCHARs needed to hold
    // the full plugin DLL file path (not including the terminating 0-TCHAR).
    // If the function fails for another reason the return value is FALSE and the
    // variable bufLen points to is set to 0.
    //
    // hDllModuleHandle can be obtained in DllMain when it is called with DLL_PROCESS_ATTACH.
    // It is possible to call the function with bufDllPath set to NULL and the variable
    // bufLen points to set to 0 to obtain the needed buffer size.
    
            struct bufferDesc {
              TCHAR *bufDllPath;
              DWORD *bufLen;
            };
    

    If you have any idea for another way to identify the calling plugin DLL feel free to change the API and its definition.


Log in to reply