4.6.13. Working with the Notepad++ 'Replace' dialog

Let's imagine you want to invoke the Notepad++ 'Replace' dialog, populate its 'Find what' and 'Replace with' fields with specific strings, check or uncheck the search-related checkboxes, and finally press the 'Find Next' button. And you want to do all of this programmatically.

The first tools that come to my mind to do such things are AutoIt ( https://www.autoitscript.com/ ) and AutoHotKey ( https://www.autohotkey.com/ ).

But let's consider something simpler. For example, NirCmd from NirSoft ( https://www.nirsoft.net/ ).

First, let's create the following NppExec script:

env_set local PATH = $(SYS.PATH);C:\tools\NirSoft  // TODO: specify path to the folder with nircmd.exe here!
npp_sendmsg WM_COMMAND IDM_SEARCH_REPLACE  // showing the Replace dialog
nircmd script "$(NPP_DIRECTORY)\replace.ncl"  // TODO: you may specify other folder than $(NPP_DIRECTORY)

Then, as the NppExec script above mentions "$(NPP_DIRECTORY)\replace.ncl", let's create a file named "replace.ncl" under your Notepad++ folder. It will be a simple non-Unicode text file with the following lines:

// set 'Find what' text:
dlg "notepad++.exe" "Replace" settext 1601 "Find what"
// set 'Replace with' text:
dlg "notepad++.exe" "Replace" settext 1602 "Replace with"
// uncheck 'Whole word' checkbox:
win child title "Replace" sendmsg id 1603 0x00F1 0x0000 0
// uncheck 'Match case' checkbox:
win child title "Replace" sendmsg id 1604 0x00F1 0x0000 0
// check 'Wrap around' checkbox:
win child title "Replace" sendmsg id 1606 0x00F1 0x0001 0
// uncheck 'Backward direction' checkbox:
win child title "Replace" sendmsg id 1722 0x00F1 0x0000 0
// click 'Regular expression' button:
win child title "Replace" sendmsg id 1605 0x00F5 0 0
// uncheck '. matches newline' checkbox:
win child title "Replace" sendmsg id 1703 0x00F1 0x0000 0
// click 'Find Next' button:
win child title "Replace" sendmsg id 1 0x00F5 0 0

This file uses a hard-coded string "Replace" as the title of the Notepad++ 'Replace' dialog. If you are using a non-English localization, you will need to replace the word "Replace" with the localization-dependent title (such as "Remplacer" for French).

Also, this file uses several magic numbers such as 1601 and 1602, 0x00F1 and 0x00F5, 0x0000 and 0x0001. Let me explain what they mean.

To deal with controls (elements) of a dialog, we need to know the corresponding control identifiers (control IDs). Here is what we can see inside the Notepad++ source file https://github.com/notepad-plus-plus/notepad-plus-plus/blob/master/PowerEditor/src/ScintillaComponent/FindReplaceDlg_rc.h :

#define	IDFINDWHAT						1601
#define	IDREPLACEWITH					1602
#define	IDWHOLEWORD						1603
#define	IDMATCHCASE						1604
#define IDREGEXP						1605
#define	IDWRAP							1606

I believe this explains where the magic numbers of 1601, 1602, and so on, came from. These are the control IDs. Another way to get these IDs is to use a specific program such as Property Edit ( https://mh-nexus.de/en/downloads.php?product=Property%20Edit ).

Now, knowing the control IDs, we want to send some specific messages to these controls. The BM_SETCHECK message is responsible for checking/unchecking a checkbox control; the BM_CLICK message is responsible for clicking a button. When we look inside a Windows header file, "winuser.h", we can see that BM_SETCHECK is defined as 0x00F1 and BM_CLICK is defined as 0x00F5.

Finally, the values of 0x0000 and 0x0001 in this context are parameters of the BM_SETCHECK (0x00F1) message. Their meanings with respect to the BM_SETCHECK message are: 0x0000 is BST_UNCHECKED and 0x0001 is BST_CHECKED. So, "0x00F1 0x0001 0" checks a checkbox control, whereas "0x00F1 0x0000 0" unchecks it. You may read more here: https://docs.microsoft.com/windows/win32/controls/bm-setcheck

Important note: as NppExec's scripts are executed in a separate thread, the script continues its execution when Notepad++'s dialog is shown. It allows to use NirCmd (as well as AutoHotKey or any other tool) to interact with the Notepad++'s dialog by pressing its buttons and so on. Correspondingly, it's also possible to close the dialog and then execute other commands from the same NppExec's script.

 

Now, after warming up, let's create a similar AutoHotKey script.

AutoHotKey provides more abilities and more control over what we are doing, so it is recommended. We will use AutoHotKey 2.0 (refer to https://www.autohotkey.com/docs/v2/v2-changes.htm for details).

The new NppExec script to invoke AutoHotKey will be quite similar to the one that invokes NirCmd. We will additionally pass a handle to the Notepad++ main window as an input parameter to the .ahk script. This is to ensure that we will be dealing with the current Notepad++ window (as there may be other running instances of Notepad++):

set local AutoHotKeyDir = C:\tools\AutoHotKey  // TODO: specify path to the AutoHotKey folder here!
env_set local PATH = $(SYS.PATH);$(AutoHotKeyDir)  // adding to %PATH%
npp_sendmsg WM_COMMAND IDM_SEARCH_REPLACE  // showing the Replace dialog
AutoHotkey32.exe "$(AutoHotKeyDir)\NppReplace.ahk" $(NPP_HWND)  // TODO: you may place your .ahk script under a different folder

Now, let's write the .ahk script that will do all the magic. Let's create a file named "NppReplace.ahk" under the folder specified in the NppExec script above (in this example, it is "$(AutoHotKeyDir)\NppReplace.ahk"). This file uses the AutoHotKey 2.0 syntax:

if A_Args.Length < 1
{
  MsgBox("Usage: " A_ScriptName " <NPP_HWND>",, 0x30) ; 0x30 - exclamation
  Exit
}

ReplaceDlgTitle := "Replace"
idNppWnd := Integer(A_Args[1])
idReplaceDlg := 0

idDlgList := WinGetList(ReplaceDlgTitle " ahk_exe notepad++.exe")
for idDlg in idDlgList
{
  idParent := DllCall("user32\GetParent", "Ptr", idDlg, "Ptr")
  if idParent = idNppWnd
  {
    idReplaceDlg := idDlg
    break
  }
}

if idReplaceDlg = 0
{
  MsgBox("Could not find a dialog with a title `"" ReplaceDlgTitle "`" for NPP_HWND=" Format("0x{:X}", idNppWnd),, 0x10) ; 0x10 - error
  Exit
}

GetDlgItem(idDlg, idItem)
{
  return DllCall("user32\GetDlgItem", "Ptr", idDlg, "Int", idItem, "Ptr")
}

ClickButton(idButton)
{
  SendMessage(0x00F5, 0, 0, idButton)
}

idFindWhat := GetDlgItem(idReplaceDlg, 1601)
idReplaceWith := GetDlgItem(idReplaceDlg, 1602)
idWholeWord := GetDlgItem(idReplaceDlg, 1603)
idMatchCase := GetDlgItem(idReplaceDlg, 1604)
idWrapAround := GetDlgItem(idReplaceDlg, 1606)
idBackwardDirection := GetDlgItem(idReplaceDlg, 1722)
idRegularExpression := GetDlgItem(idReplaceDlg, 1605)
idDotMatchesNewline := GetDlgItem(idReplaceDlg, 1703)
idFindNext := GetDlgItem(idReplaceDlg, 1)

ControlSetText("Find What", idFindWhat)
ControlSetText("Replace With", idReplaceWith)
ControlSetChecked(0, idWholeWord)
ControlSetChecked(0, idMatchCase)
ControlSetChecked(1, idWrapAround)
ControlSetChecked(0, idBackwardDirection)
ClickButton(idRegularExpression)
ControlSetChecked(0, idDotMatchesNewline)
ClickButton(idFindNext)

Exit
 

Note: If you want to find or replace some string programmatically without showing the Replace dialog, NppExec proposes the following commands for this: SCI_FIND and SCI_REPLACE. The actual behavior of these commands completely depend on the <flags> parameter specified. The description and examples are available by typing any of the following:

help sci_find
help sci_replace
help all
 

See also: Clipboard, keystrokes and much more [4.6.8].