Welcome, Guest! Log In | Create Account

Plugin Development

From notepad-plus

Jump to: navigation, search
How to develop a plugin or a lexer


Contents

How to install a plugin

The plugin (in the DLL form) should be placed in the \plugins subfolder of the Notepad++ Install Folder. Configuration files should go in \Plugins\Config. Documentation files should go to \Plugins\Doc\ directory.

These are generic instructions. If the plugin comes with some different procedure, that procedure should be followed instead.

Once you installed the plugin, you can use (and you may configure) it via the menu "Plugins".

How to develop a plugin

If you want to do your (very simple) plugin without going through the document, just check Plugin Development Quick Start Guide.


A plugin is a dll which is expected to provide some basic functionality required by Notepad++, in addition of the functionality (enhancements) it provides. External lexers for languages the User Defined Language framework cannot handle are plugins which expose additional interface to Notepad++.

The Windows Message System is used to avoid the overhead. It means the plugins system makes available some Notepad++ handles in order to give more flexibility and possibility to the plugin developers. There're 3 window handles available for the moment: The main Notepad++ handle and 2 Scintilla Window handles.

With these 3 window handles, we can do almost everything, just send the appropriate message to the right handle to get what you want.

Check the newest header file Notepad_plus_msgs.h that you may need to use the new Notepad++ message. It is mirrored on Messages And Notifications.

The interface (the header file) of plugins is written in C for both C/C++ programmer. However, if you translate it in order to develop in another language, please let it be known so that it can be included for the other language development. There are currently plugins written in Visual Basic, Delphi and Oberon. Any language able to interface with C code will do.

There are some plugin templates available from the npp-plugins project:

You can check the source code of plugins written in other languages: Web Edit (Oberon), Math Plugin (PureBasic).

Converting plugins

A side branch of developing plugins is convertins ANSI ones to Unicode. The following note may help:

All the plugins which are compatible with v5.0.3 (which is an ANSI application) will be compatible with ANSI release, but not with Unicode release. As well, all the Unicode plugins (which come with Unicode Notepad++) are not compatible with Notepad++ ANSI mode.

In order to make your plugins compatible with Unicode Notepad++ (Unicode release will be the main stream on the future versions), you should include the newest PluginsInterface.h in your project, then compile your project by setting the symbol UNICODE (with #define UNICODE in your header file or set Character Set option from "Use Multi-Byte Character Set" to "Use Unicode Character Set" in Configuration Properties->General via project properties dialog in your VC studio).

Keep one thing in mind : Send all unicode messages to Notepad++ in unicode mode, but send ansi message to scintilla in both ANSI and UNICODE mode. Actually this is all it takes.

The following snippets summarise the adjustments required in HexEditor by J. Lorenz, an advanced plugin:

using namespace std;
anbsp;
#ifdef UNICODE
#define string wstring
#endif

You must indeed prepare for getting Unicode strings and paths from Notepad++. Don't forget BOMs when applicable.

#ifdef UNICODE
   WCHAR	wText[65];
   ListView_GetItemText(_hListCtrl, _pCurProp->cursorItem, _pCurProp->cursorSubItem, wText, SUBITEM_LENGTH);
   wText[_pCurProp->cursorPos] = (TCHAR)wParam;
   ::WideCharToMultiByte(CP_ACP, 0, wText, -1, text, SUBITEM_LENGTH, NULL, NULL);
#else
   ListView_GetItemText(_hListCtrl, _pCurProp->cursorItem, _pCurProp->cursorSubItem, text, SUBITEM_LENGTH);
#endif

Again, the trick is to use the right API variant and send/receive strings in the right format. Don't forget using the TCHAR Windows macro type for individual characters, it will help flatten differences out.

#ifdef UNICODE
  static WCHAR wText[129] = _T("\0");
  ::MultiByteToWideChar(CP_ACP, 0, text, -1, wText, 129);
  lvItem->pszText = wText;
#else
  lvItem->pszText = text;
#endif

Yet another conversion.

Remember that Scintilla always stores things in ANSI or UTF-8. Please refer to the Scintilla documentation on UTF-8 encoding messages if you wish to directly access Scintilla's buffers.

Here are some more quotes from XBracket Lite, by D. Vitaliy:

void CXBrackets::OnNppSetInfo(const NppData& nppd)
{
   m_PluginMenu.setNppData(nppd);
   isNppWndUnicode = ::IsWindowUnicode(nppd._nppHandle) ? true : false;
}
  • This one helps talking with Notepad++:
   if ( isNppWndUnicode )
   {
       nppOriginalWndProc = (WNDPROC) SetWindowLongPtrW( 
         m_nppMsgr.getNppWnd(), GWLP_WNDPROC, (LONG_PTR) nppNewWndProc );
   }
   else
   {
       nppOriginalWndProc = (WNDPROC) SetWindowLongPtrA( 
         m_nppMsgr.getNppWnd(), GWLP_WNDPROC, (LONG_PTR) nppNewWndProc );

Example plugins

Here's a plugin demo projects named NppInsertPlugin, which is a complete working project , and which may give you more idea how it works.

Plugin template for Delphi is now available, as well as a few others, from the Sourceforge NppPlugin homepage. Also check Plugin Development forum for more plugins, or Plugin Central.

You may wish to read Analysing Plugin Code for an example of how and why the code of an advanced, actual plugin is laid out the way it is.

What should a plugin do?

A plugin is expected to implement some functions. Their names and semantics are explained below.

Further, there are some duties plugins are expected to perform on startup for proper operation. What lexers need to do is significantly more complex, because they have to initialise all sorts of constants that the exer and folder functions will use.

The common plugin interface

bool isUnicode()
A plugin is designed to either work with an ANSI or Unicode build of Notepad++. ANSI plugins must not define this function. Unicode plugins must define it, and it must return true.
void setInfo(NppData)
This routine is called on loading the plugin, providing it with information on the current instance of Notepad++, namely an array of three handles for
  1. the main Notepad++ window;
  2. The primary Scintilla control;
  3. the secundary Scintilla control.

Any NPPM_ or WM_ message can be sent to the main Notepad window. Any WM_ and SCI_ message can be sent to the Scintilla controls. EM_ messages are recommended against by Scintilla documentation, though a subset is emulated.

const TCHAR* getName()
Returns name of the plugin, to appear in the Plugin menu.
FuncItem *getFuncArray(int *)
Retrieves a pointer to an array of structures that describe the exposed functions. The expected length of the array is the value pointed by the argument. There must be at least one such routine. Provide one that displays some sort of About dialog box if there is otherwise no need for a menu entry - a typical case for external lexers.
void beNotified(SciNotification *notif)
This procedure will be called by Notepad++ for a variety of reasons. The complete list of codes is to be found on the Messages And Notifications. It should handle these tasks using information passed in the notification header.
LRESULT messageProc(UINT message, WPARAM wParam, LPARAM lParam)
This is a message processor handling any message Notepad++ has to pass on.

The last two routines, if not needed, may have an empty body. But they must be defined.

All the above function must be defined with the following link information, couched in C terms:

extern "C" __declspec(dllexport)

If the plugin is not developed in C, then consul your interpreter or compiler documentation in order to generate a dll that conforms with those expectations.

The FuncItem structure

This structure describes items to add to the plugin's submenu. It is laid out as follows:

  1. char _itemName[64]: name to display
  2. *void() _pfunc : a poiner to the routine to execute, which has no arguments and returns nothing
  3. int _cmdId : a command index
  4. bool _init2check : set if the item is checked at startup
  5. Shortcut *_pShKey: a pointer to a structure describing the key bound to this item.

The Shortcut structure is as follows:

  1. bool _isCtrl
  2. bool _isAlt
  3. bool _isShift
  4. UCHAR _key

The value for _key is the desired Windows virtul key code. Note that you cannot use Win[-modifiers]-<some_key> style shortcuts.

Additional lexer library interface

int getLexerCount()
Returns how many lexers this dll hosts. If the plugin doesn't have any lexer capability, then that function should not be defined at all. This number should be no greater than 30, though there is no bound check.
void getLexerName(int i, TCHAR *name, int nameLen)
Retrieves the short name for the lexer language of index i in the plugin. Such names should be 12 character long or less. "Lua", C++" or "Verilog" are such names. This name will be used as foreign key to retrieve setings from langs.xml and stylers.xml. The function must fill the <t>name</tt> buffer, whose capacity is nameLen. Redefinitions of built-in languages are ignored.
void getLexerStatusText(int i, TCHAR *desc, int descLen)
Returns a string that will be displayed on the statusbar when Notepad++ highlights a document using this lexer. "Visual Basic module" could be such a string. Otherwise, arguments have the same meaning as for getLexerName().
void Lex(unsigned int ext_index, unsigned int startPos, int lengthDoc, int initStyle,
   char** words, unsigned int Window_ID, char* PropSet)
This function is called by the Scintila control whenever it needs to colourise some text. The startPosis guaranteed to start a document line.
Fold(unsigned int ext_index, unsigned int startPos, int lengthDoc, int initStyle,
   char** words, unsigned int Window_ID, char* PropSet)
This routine is called at the end of each styled line. It is meant to tell the styler what the level of the current line should be, with a couple flags or'ed in. Defining this routine is optional - folding will be supported only if it is defined.

There must exist a <module_name>.xml file in the plugins\config subflder of the Notepad++ install folder. This file contains information in the same format as the one found in langs.xml and stylers.xml, harbor with one Language and one LexerType node per language in the dll.

The menu initialisation and the dll entry point

A dll always has an BOOL APIENTRY DllMain(HANDLE hModule, DWORD reasonForCall, LPVOID lpReserved) entry point so as to manage attaching to and detaching from processes and threads.

Menu initialisation

Since all plugins must provide a FuncItem[] array of length at least one, this array must be initialised before Notepad++ has loaded the plugin. Since it does so by loading the plugin dll, the only place to do that is the DllMain() entry point.

Typical code will take action only on the ATTACH_PROCESS reason, and should initialise the array of function descriptions (and optional shortcut) then.

Note that as of 5.5.1 cascading menus are not supported for plugins.

Initialisation and cleanup

It is not safe to assume that your dll will be attached only once per instance of Notepad++.exe. Collaborative plugins may need to attach it so as to get information they need.

By definition, an initialisation is to be performed only on the first attachment of a given instance of Notepad++. It is only sloppy coding to perform it on each attach if it has no side effect. If it has, then this can cause errors of various kinds.

If your plugin needs to do some cleanup, like destroying an icon handle it owns, this should be done on the last process detach for any given instance of Notepad++.exe. The safest way is to keep a reference count of your dll inside its DllMain() entry point. Keeping track of the count will allow to perform initialisation only on the first attach and cleanup only on the last detach, the latter being usually more important.

Properties

If your lexer needs to set properties on the Scintilla compoent, you must be aware that the component may change because of switching views, or because of other plugins creating extra handles. You must be prepared to find a component without the properties you expect, in which case you should set them according to their current state out of consistency with other views.

Lexer specific tasks

Lexers must define states which say how a character is to be interpreted. The word categories and styles are read from langs.xml and stylers.xml, so that it is good practice to define litteral symbols that represent those styles.

Meaning and use of the arguments to Lex() and Fold()

  1. ext_index is the 0-based index of the lexer being called in the library.
  2. startPos is the position at which styling starts.
  3. lengthDoc is how long should the lexer proceed, in characters;
  4. initStyle is the state of the lexer before the first character of the first line to style;
  5. words is the address of an array of pointers to arrays of char, the keywords in the language arranged in categories. The list terminates with NULL;
  6. Window_ID is thehandle of the window viewing the document being styled.
  7. PropSet is a string holding all Scintilla-wide properties. It is a string like "key1=value1\nkey2=value2\n....\0. As usual, \n is ASCII 0Ah, and \0 is ASCII 0.

The styler interface

If you are using C or C++ todevelop the lexer, you may take advantage of the WindowAccessor document interface.You create such an object calling the WindowAccessor constructor with arguments Window_ID and PropSet.

Accessing the methods of the styler parameter
Returns Prototype Description
char SafeGetCharAt(int position, char chDefault=' '); Returns character at given position if valid, and default value if not valid. Attempts to fill buffer with valid values prior.
bool IsLeadByte(char ch) Returns <true if character is a lead byte in a DBCS script.
bool Match(int pos, const char *s); Returns <tttrue> if string s can be read from position pos
char StyleAt(int position); Returns the style byte for that position
int GetLine(int position); Returns the document line number
int LineStart(int line); Get the position at which a line starts.
int LevelAt(int line); Returns the current folding level of the line
int Length(); Returns document length.
void Flush(); Reset accessor and invalidate internal buffers
int GetLineState(int line); Returns the current lexer state
int SetLineState(int line, int state); Sets the lexer state for this line.
int GetPropertyInt(const char *key, int defaultValue=0) Returns a property, designated by ordinal.
char *GetProperties(); Returns a string of all properties for lexer
void StartAt(unsigned int start, char chMask=31); Defines the startong position for styling
void SetFlags(char chFlags_, char chWhile_); Sets internal flags ColourTo(pos, chAttr) uses for optimisation:

  • if chAttr is not chWhile, chFlags is cleared and chAttr left unchaged
  • otherwise, chAttr is or'ed ith chFlags
unsigned int GetStartSegment(); Returns starting point of segment. A segment is a range of positions with the same styling.
void StartSegment(unsigned int pos); Set start of current segment at pos.
void ColourTo(unsigned int pos, int chAttr); Request colouring a range of characters.
void SetLevel(int line, int level); Sets folding level for the line.
int IndentAmount(int line, int *flags, PFNIsCommentLeader pfnIsCommentLeader = 0); Determines the amount of indentation on a line, as well as a couple flags
void IndicatorFill(int start, int end, int indicator, int value); Fills a range using an indicator.

Obviously, not all methods are equally useful. The ones that you will always use are ColourTo(), SafeCharacterAt(), LevelAt() and SetLevel().

Lexer call initialisations

  1. Unpack the word lists, if you are using them. It is of course more efficient to do this when the lexer is loaded, if they don't change.
  2. You are going to send many Scintilla messages to Window_ID. You can do so by sending the messages to the Scintilla handle, which you know from the PluginInfo structure Notepad++ provided your plugin with on startup. But this has an overhead you can drastically cut. Instead,
    1. Quey SCI_GETDIRECTPOINTER to get the_pointer you'll be perusing
    2. Query SCI_GETDIRECTFUNCTION. You'll get a function pointer, the_function. Its prototype is as follows (see scintilla.h): it returns a long and takes as arguments:
      1. the_pointer
      2. message_ID, an integer
      3. wParam, an unsigned integer
      4. lParam, a pointer
    3. Now, to send a message, call (the_function)(the_pointer,message_id,wParam,lParam). Types are obvious.
  3. Since you must handle any DBCS in comments or literals, you must obtain the current code page by querying SCI_GETCODEPAGE.
  4. Set the initial styling position by calling SCI_STARTSTYLING with wParam=start position, lParam=0x1F(=31). This last value corresponds to a compatibility kludge between old and new indicators.
  5. In case initStyle is not set properly, check the style value at the end of the previous line. If you find a different value, override the initStyle you were passed.
Unpacking word lists

If using the C++ language, you may want to take advantage of the helper functions in the WordList class. To do this, count how many entries the words array has, define an array of WordLists of the same size, and initialise each using the corresponding char* pointer.

Build requirements

Using the C or C++ language

You will have to include some Scintilla code in order to call methods on theDocumentAccessor object the lexer and folder functions are passed.

The following list of include files seems to be more than enough. * Depending on your own requirements, some may be removed:

  • #include "Accessor.h"
  • #include "CharacterSet.h"
  • #include "ExternalLexer.h"
  • #include "KeyWords.h"
  • #include "Platform.h"
  • #include "PluginInterface.h"
  • #include "PropSet.h"
  • #include "SciLexer.h"
  • #include "WindowAccessor.h"
  • #include <algorithm>
  • #include <cstdlib>
  • #include <iterator>
  • #include <TCHAR.h>
  • #include <vector>

You can use the Plugin Interface Library by aathell in order to automate some initialisation chores and more.

Not using the C or C++ language

The following table loosely follows the interface listing for a document accessor, and shows alternate methods to obtain the same result. The table is made of alternating rows: even numbered ones hold comments. The leftmost part of odd numbered rows describes a task. The right part displays the message to send and its parameters, or some other indication when sending a single message wouldn't do.

Emulating the methods of a document accessor
Getting a character at some position
SCI_GETCHARAT
wParam: Document position
lParam: 0
Returns the character or 0. If the text can hold NULs, for instance because it has comments using a DBCS script, this is not safe enough, because 0 may be valid
Get some range of text
SCI_GETTEXTRANGE
wParam: 0
lParam: pointer to: start position, end position, address of buffer. The end position can be -1 to go all the way to end of document.
Returns the number of characters copied. The buffer should also accommodate an extra 0 terminator (not counted in return value). It is a good policy to fill the buffer at a position sligtly before the actual one, so as to perform any needed backtracking.
Decide whether a character is a DBCS lead byte
#Implementing IsLeadByte(char ch)
wParam: N/A
lParam: N/A
You must inquire the current code page using the SCI_GETCODEPAGE message.
Decide whther a string is a substring of buffer contents
Not available
wParam: N/A
lParam: N/A
Use the string methods of your language.
Get the style at some position
SCI_GETSTYLEAT
wParam: position
lParam: 0
Returns the style byte for that position, or 0 if position is invalid.
Retrieve line number for a given position
SCI_LINEFROMPOSITION
wParam: position
lParam: 0
Returns the document line number.
Retrieve start position for a line
SCI_POSITIONFROMLINE
wParam: line number
lParam: 0
Returns <p>
  • the document position at which line starts, if valid
  • the doucment position the line holding current selection starts, if line<0
  • -1 if line>document line count
  • end of document position if line = line count

Retrieve end position for a line
SCI_LINEENDPOSITION
wParam: line number
lParam: 0
Returns position of last non terminating character on the line. The line number must be valid.
Retrieve the length of a line
SCI_LINELENGTH
wParam: line number
lParam: 0
Returns the line length, including any terminating characters. Returns 0 if line is not valid.
Retrieve the folding level for a line
SCI_GETFOLDLEVEL
wParam: line number
lParam: 0
Returns the current folding level of the line
Retrieve document length
SCI_GETLENGTH
wParam: 0
lParam: 0
Returns document length.
etrieve the state for the current line
SCI_GETLINESTATE
wParam: line
lParam: 0
Returns the current lexer state. If the line doesn't have a state, this is undefined.
Determine the largest line number for which the state is known
SCI_GETMAXLINESTATE
wParam: 0
lParam: 0
Returns the largest line number for which a state is known.
Sets the state for the current line
SCI_SETLINESTATE
wParam: line
lParam: new state
States are integer values your lexer is the sole user of. They hold persistent information which mught be usedin a multiple pass parsing, for instance.
Read a property value as string
Not available
wParam: N/A
lParam: 0
Search for substring "<propname>"+'='. The \n-terminated char* that follows is the value.
Ask for a general property
#Retrieving a general property
wParam: N/A
lParam: 0
 
Ask for a property with integer value
SCI_GETPROPERTYINT
wParam: pointer to property name
lParam: Default value to return if property invalid
Returns the value of the property if found, or the supplied default value else.
Set the style for a range of characters
SCI_SETSTYLING
wParam: number of characters to style
lParam: style value to apply
Requires a styling position to have been set prior. Styles are defined in the xml companion file.
Set fold level for a line
SCI_SETFOLDLEVEL
wParam: line number
lParam: fold level
Please refer to the Scintilla docs: the level is made of an index - top level is at 1024 - and or'ed in flags:
  • SC_FOLDLEVELWHITEFLAG: set this for blank lines, so that they can be folded in a smarter way
  • SC_FOLDLEVELHEADERFLAG: there is a folder point on this line
Retrieve indentation of a line
SCI_GETINDENTATION
wParam: line number
lParam: 0
Returns the number of spaces a line is indented. You must supply any code that would check for indentation consistency or such. Use SCI_GETINDENT to get the indent step width. If this is 0, the actual width of the step is given by querying SCI_GETTABWIDTH.
Use indicators on a range of characters
#Implementing IndicatorFill(int start, int end, int indicator, int value)
wParam: N/A
lParam: 0
Fills a range using an indicator.

Note that it is up to your code to perform any segment management if you need it.

Implementing IsLeadByte(char ch)

If the current codepage is 0, return 0, else forward the call to the Win32 API IsDBCSLeadingByteEx(ch, codepage).

Retrieving a general property

This usually involves two calls to the SCI_GETPROPERTY message, with wParam the address of a buffer holding the 0 terminated property name.

Since it is safer not to assume anything on the property value length, if only to avoid a buffer overrun exploit, you need to het the length, allocate a buffer of that length and get it filled with the desired text. This is done as follows:

  1. Call with lParam=0 to get the length of the answer.
  2. Allocate a buffer of that length+2 (beware DBCS vlues of properties)
  3. Call again with the buffer address as lParam. The returned string length does not include the 0 terminator.

There is a variant SCI_GETPROERTYEXPANDED message. If a property value has a substring like "$(<prop-name>)", then that string is recursively replaced by the value of <prop-name>.

Implementing IndicatorFill(int start, int end, int indicator, int value)

You may wish to read the Scintilla documentation on indicators to decide whether you are going to use them.

This is a three stage proess:

  1. Call message SCI_SETINDICATORCURRENT with wParam=indicator, lParam=0 to set the indicator you are oing to use
  2. Call SCI_INDICATORVALUE with wParam=value, lParam=0 to set the value for that indicator, ie its appearance.
  3. Call SCI_INDICATORFILLRANGE with wParam=start position, lParam=end position to effectively perform the task.

Debugging

This is a proven fact: everyone else's plugins all have bugs.

Since a plugin interacts with Notepad+ to monitor both Notepad++ code and the plugin code - for simplicit, let's assume interaction between plugins isn't an issue. So, the somution is straightforward:

  • create a solution (in Visual Studio parlance) that includes both the Notepad++ project and all the plugin projects.
  • All projects must be in debug mode
  • Compile the solution
  • Now that all the code is under a single umbrella, debug just like any appliation.

If the plugin is not written in C++, it will have to play with two debuggers, one for each part. The plugin dll must still be included somehow on the C++ side. Who knows, it may even work.

References