Musings from Bruderstein

Alien in Berlin.

Top 10 Hints for Writing a Notepad++ Plugin, and Getting It Listed

This is a list of the things that many people forget or don’t do properly when writing a Notepad++ plugin. None of them stop things working, but they all make either the user experience a bit less friendly, or cause stability problems, or make hosting the plugin in the Plugin Manager just that little bit more tricky.

1. Version number.

I know, this is a stupid thing, but you’d be amazed how many plugins don’t advertise their version number, or advertise it wrong. At the time of writing, 54 from 115 listed, do or have had a plugin DLL that either doesn’t report a version number or reports the wrong number. This means plugin manager needs to keep an MD5 sum of the DLL to identify the version, and the user can’t see the version from explorer, which makes manual upgrades that bit trickier.

Plugins can be written in any number of languages, but for C/C++ it’s very easy to add a version number. Sadly, this isn’t quite so easy in the Express versions of Visual Studio, however, you can add a resource text file (a .rc file), and copy the code in from below, changing the obvious details.

Project Resource File myproject.rc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
VS_VERSION_INFO VERSIONINFO
 FILEVERSION 1,2,34,5
 PRODUCTVERSION 1,2,34,5
 FILEFLAGSMASK 0x37L
#ifdef _DEBUG
 FILEFLAGS 0x21L
#else
 FILEFLAGS 0x20L
#endif
 FILEOS 0x4L
 FILETYPE 0x0L
 FILESUBTYPE 0x0L
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "080904b0"
        BEGIN
            VALUE "Comments", "Source code at git://github.com/davegb3/nppPluginManager.git"
            VALUE "CompanyName", "Dave Brotherstone.  davegb@pobox.com"
            VALUE "FileDescription", "My Great Plugin for Notepad++.  A free (GPL) Plugin for doing great things"
            VALUE "FileVersion", "1, 2, 34, 5"
            VALUE "InternalName", "MyGreatPlugin"
            VALUE "LegalCopyright", "Copyright (C) 2009-11 Dave Brotherstone"
            VALUE "OriginalFilename", "MyGreatPlugin.dll"
            VALUE "ProductName", "My Great Plugin"
            VALUE "ProductVersion", "1, 2, 34, 5"
#ifdef UNICODE
            VALUE "SpecialBuild", "UNICODE"
#else
          VALUE "SpecialBuild", "ANSI"
#endif
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x809, 1200
    END
END

The key piece of information is on the second line - the FILEVERSION. This is what plugin manager uses to assign the version number. The other version numbers (PRODUCTVERSION and the "ProductVersion" and "FileVersion" numbers in the BLOCK) don’t have to be the same, but it usually makes sense to have them the same number.

Adding the (same!) version number to the about box is also a nice way for your users to know they’ve got the new version. Take a look at the Plugin Manager Source, specifically version.rc and PluginManagerVersion.h (under pluginManager/src) to see how to put the version number in basically one place and have it always consistent.

2. Use the config directory.

Depending on the installation, where the plugins should save their configuration can change. Use the NPPM_GETPLUGINSCONFIGDIR message to get the currently configured plugin directory. Attempting to write to the Program Files\Notepad++\plugins\config directory rarely results in a happy afternoon.

It’s also worth considering what happens when you upgrade your plugin. Adding a config version number into your config can help you port the config later, if you need to, depending on how complex your config is. If you need to install a config file at install time, it can be worth it to deploy a standard /default/ config with your plugin, and have your plugin copy that to the plugin config directory if it doesn’t find a config file. This means that future versions don’t overwrite the users configuration, but you’ve got a standard one for if you do. Remember your plugin might start with a new version and an old config file, which could be where the version number in the config file helps.

3. DLL_PROCESS_ATTACH and NPPN_SHUTDOWN.

Sadly, there was an error in an old C/C++ template, that meant that some people built a potentially buggy plugin before they’d even started.

So, here’s the thing: DllMain gets called for every process/thread that loads it. For every DLL_PROCESS_ATTACH call there’s a DLL_PROCESS_DETACH. The same with DLL_THREAD_ATTACH and DLL_THREAD_DETACH. Plugin Manager will effectively call these when it performs the LoadLibrary() to call getPluginName().

setInfo() gets called only when Notepad++ successfully loads the DLL (with LoadLibrary()). What this means, is that any allocation performed in DLL_PROCESS_ATTACH must be deallocated in DLL_PROCESS_DETACH. And the same with the THREAD equivalents. And, more importantly, anything that gets allocated in setInfo() or similar calls (getPluginName() etc), can only be deallocated from a Notepad++ call. Probably the best one to use for this is beNotified(), using notification NPPN_SHUTDOWN, which gets called (surprisingly) when Notepad++ shuts down.

As a hint, if your plugin doesn’t work when Plugin Manager has been opened, then it’s likely you’ve got an issue with where things are allocated and deallocated.

Allocate in Deallocate in
DllMain: DLL_PROCESS_ATTACHDllMain: DLL_PROCESS_DETACH
DllMain: DLL_THREAD_ATTACHDllMain: DLL_THREAD_DETACH
setInfo() - ideally don’t use this for allocation, instead consider beNotified() with message NPPN_READYbeNotified() message NPPN_SHUTDOWN
getName()Don’t. getName() gets called by Plugin Manager and shouldn’t do any allocation
beNotified() message NPPN_READYbeNotified() message NPPN_SHUTDOWN

4. /MT everything.

For C/C++ projects, this says “statically link the C++ runtime code with the DLL”. This has two effects, one is that it removes the requirement for the Visual C++ Runtime library to be installed, which is just kinda nice for the users. If you use /MD and they don’t have it installed, they’ll get a message that LoadLibrary() failed, and that the plugin is not compatible. The second effect is that it makes everything work. Ok, it’s not actually that dramatic, but Notepad++ itself is compiled with this switch mixing /MT with /MD is a bit like mixing potassium and water - you might get some interesting effects.

Also, make sure you do a release build, not just a debug version.

5. Zipped download.

Just a little point, but if your plugin consists of a single DLL, still go to the trouble of making a ZIP file available. If you’ve nowhere to host it, and you don’t want to setup a sourceforge or other OSS hosting service project for it, send your stuff to me and I’ll add it somewhere sensible. Although Plugin Manager since 1.0.0 can download any file for copying, it’s happiest with ZIP files. 7zip makes good, small ZIP files.

6. Release the code.

My first Notepad++ plugin was my first outing to opensourceville, and I can highly recommend releasing the source to your plugin. Even if you believe the code to be awful, and you don’t want anyone to see it, I know I still feel like that about anything I release. Opening the code means people can take a glance over it, and maybe suggest improvements or even bug fixes. I learnt a mountain of stuff from very helpful developers in the Notepad++ project. Also, IANAL, but if your plugin is C/C++, and you use the headers and various support C++ files from the Notepad++ project, it’s against the terms of the Notepad++ license not to.

Keep your source code in a separate download. Less than 1% of people want the source. They’re your most valuable 1%, they’ll help you, support you, provide suggestions and fixes, even new features, so look after them, but don’t ignore the other 99% that just want the plugin. By look after them, I mean respect the feedback they give you, even if you’re more experienced than them, and make sure your source download is a reasonable size. See the Plugin Manager ignore list for a reasonable list of files not to include in your zip. Obviously providing a link to a github, bitbucket or other source hosting provider is either just as good, or even better, depending on how you see the world.

7. Don’t worry about Unicode and ANSI releases.

Windows 95 just will not die. We’ve tried everything, drowning it, soaking it in non-pH balanced oils next to industrial magnets, flame throwers, everything, but it’s still around. However, ANSI users account for, as far as we can tell, less than 1% of the total users of Notepad++. We know from the forums that a few still use it due to a plugin or two that only works for the ANSI version, and some due to some sadistic need to still use a nearly-twenty-year-old operating system. Notepad++ itself has discontinued the ANSI build, so don’t feel compelled to make one too.

8. Release announcement.

Announce your plugin on the Plugin Development forum. Do this for each and every new version. Ideally, create a new post for each new version, rather than adding your announcement to an old thread. This keeps the forum clean, and means people get to see easier that a new version is out.

9. List it.

If you’ve not got an account at the Plugin Manager Admin web site, then just click register on the site to get one. It may take a day or so to be approved, but once you get the email that it’s been approved, you can log in and add (or update) your plugin. I’ll try and do a post about adding your plugin and what steps to take really soon. Like really really soon. Promise.

And if you’ve not done step 8, do that first. No, really, do that first.

10. Enjoy it.

This should be number 1. Sounds a bit corny, but it’s true. Notepad++ has millions of users, and if even a tiny percentage of those download your plugin, you can still be looking at tens of thousands of downloads. One developer actually complained that something must be wrong, as his equivalent plugin for Visual Studio had received no where near the same number of downloads. Personally, I find it a really nice feeling that lots of people are using my code.

Nginx With Kohana in a Subdirectory

I found out if you actually read the docs, then it’s a lot easier to get working. None of the posts on the web about using Kohana under nginx were particularly helpful (due to the kohana site being located under a subdirectory), and as I took a while to get working I thought I’d post this as my findings in case it helps anyone else.

The critical part was understanding the phases that nginx uses as it processes a request.

The Notepad++ Plugin Admin System lives under /npp/pm/admin, which maps to a separate path /npp/pm/pluginadmin/ in the physical filesystem. Requests to /npp/pm/pluginadmin are all denied. The admin system uses the Kohana Framework, which has an app/ and a www/ directory. The www/ directory contains the main index.php file, which is the start point for every URL, and the app/ directory contains all the controllers, models and views for the site.

Configuration nginx-site.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
server {
  root /www/brotherstone

  location / {
      try_files $uri $uri/;
  }


  location ^~ /npp/pm/admin {

                rewrite /npp/pm/admin$ http://www.brotherstone.co.uk/npp/pm/admin/welcome permanent;

                rewrite /npp/pm/admin/(.*)$ /npp/pm/pluginadmin/www/$1;

        }



        location ^~ /npp/pm/pluginadmin/ {
                internal;


                if (-f $request_filename) {
                        break;
                }

                try_files $uri $uri/ /npp/pm/pluginadmin/www/index.php?kohana_uri=$uri;

                location ~ \.php$ {
                        fastcgi_pass    unix:/tmp/fcgi.sock;
                        fastcgi_index   index.php;
                        fastcgi_param   SCRIPT_FILENAME $document_root$fastcgi_script_name;
                        include         fastcgi_params;
                }
  }
}

Line 9 starts the main location for URLs starting with (~ is not a regular expression, but a /starts with/ marker) /npp/pm/admin. One thing I’m still not terribly happy with is that should really be ^~ /npp/pm/admin/ - i.e. with the trailing slash.

Line 11 deals with exactly this case, that user requested exactly /npp/pm/admin without the trailing slash, and sends a redirect back to the user. This is the only thing that worked for me, Kohana seems to have a problem if the REQUEST_URI is not in it’s root, even if if it it’s root without the slash. Huh.

Line 13 does the main internal redirection, to /npp/pm/pluginadmin/www/. nginx will then re-evaluate which location entry to use after performing the rewrite. If I’d have added a break; on the end, it would have ended the cycle of rewrite - location selection - rewrite. A last on the end would mean that further rewrite rules in this location should not be performed, but the cycle should start again. nginx has an automatic protection of 10 cycles to stop infinite loops. Adding rewrite_log on; and setting the error_log level to notice writes the rewritten URIs to the error log, and can be (and was) very useful for debugging.

Inside the new location block, line 20 states that this location is internal, and can only be used from internally rewritten requests.

Line 23 checks that if the (rewritten) requested URL exists as a file itself, such as a CSS, javascript or image file. If it does, I don’t want to perform any further processing, just give them the file.

Line 27 try_files checks the file existance in order. Theorically, this should mean the if() block isn’t necessary. However, it doesn’t seem to work without the if block. The last option is effectively a rewrite to the kohana index.php script, without doing a further rewrite. The try_files command is recommended for speed, and should be used whenever possible.

The last embedded location just sends all .php files to the FastCGI server (I’m using spawn-fcgi).

If anyone has any ideas about getting rid of the first rewrite, or any other improvements, shout.