12 KiB
Copilot Instructions for Notepad3
Win32 C/C++ text editor on Scintilla/Lexilla. Built with Visual Studio 2026 (toolset v145) via MSBuild. Solution: Notepad3.sln. Windows-only.
Build
Scripts under Build\ (PowerShell under Build\scripts\):
Build\Build_x64.cmd [Release|Debug]— single platform (also_Win32,_ARM64,_x64_AVX2)Build\BuildAll.cmd— all four platformsmsbuild Notepad3.sln /m /p:Configuration=Release /p:Platform=x64— CI equivalentBuild\Clean.cmd— clean outputsnuget restoreonce before first buildVersion.ps1regeneratessrc\VersionEx.h(Major.YY.Mdd.Build, build number inVersions\build.txt)- Tests:
test\TestFileVersion.cmd,test\TestAhkNotepad3.cmd(AutoHotkey). CI in.github/workflows/build.yml(windows-2022, Release × all four platforms).
Default config is Release.
Core Modules (src\)
Notepad3.c/h—wWinMain,MainWndProc, global state structs (Globals,Settings,Settings2,Flags,Paths),MsgCommand()dispatcher with static sub-handlers (_HandleFileCommands,_HandleEditBasicCommands,_HandleViewAndSettingsCommands, etc.).Notepad3Util.c/h— bitmap/toolbar loaders (NP3Util_LoadBitmapFile,NP3Util_CreateScaledImageListFromBitmap), wrap config (NP3Util_SetWrapIndentMode,NP3Util_SetWrapVisualFlags), middle-click auto-scroll (NP3Util_AutoScrollStart/Stop).Edit.c/h— find/replace (PCRE2), encoding, clipboard, indent, sort, bookmarks, folding, autocomplete.Styles.c/h— Scintilla styling, lexer selection, themes.Dialogs.c/h— dialogs, DPI-aware UI.Encoding.c/h— character encoding detection/conversion.SciCall.h— type-safe wrappers for Scintilla direct calls.DynStrg.c/h—HSTRINGWdynamic wide-string handle.PathLib.c/h—HPATHLpath handle + long-path-aware Win32 wrappers.TypeDefs.h—DocPos,DocLn,cpi_enc_t, OS targeting, compiler macros.MuiLanguage.c/h— MUI language DLL loading.
Vendored Libraries
scintilla\ (5.5.8 + np3_patches\, docs in scintilla\doc\), lexilla\ (5.4.6 + np3_patches\, docs in lexilla\doc\), scintilla\pcre2\ (10.47, replaced archived Oniguruma), src\uchardet\, src\tinyexpr\ / src\tinyexprcpp\, src\uthash\, src\crypto\ (Rijndael/SHA-256 for AES-256).
grepWin (grepWin\) — external tool, not built from source
Pre-built exes under grepWin\portables\; .lang files under grepWin\translations\. Runtime lookup in src\Dialogs.c: Settings2.GrepWinPath → <ModuleDir>\grepWin\grepWin-x{64,86}_portable.exe → %APPDATA%\Rizonesoft\Notepad3\grepWin\. grepWinLangResName[] in MuiLanguage.c maps Notepad3 locales to .lang filenames (written into grepwin.ini before launch). Portable build scripts (Build\make_portable_*.cmd) package both portable exes + LICENSE + all .lang files. ARM64 uses the x64 exe via emulation (#if defined(_M_ARM64) in Notepad3.c).
Lexers (src\StyleLexers\)
Each language is one styleLexXXX.c defining an EDITLEXER struct (see existing files for template). To add: create the file, register in Styles.c lexer array, add localization string IDs.
Localization (language\)
27+ locales under np3_LANG_COUNTRY\. Language packs build as separate DLLs.
Adding a string resource
#define IDS_MUI_XXX <id>inlanguage\common_res.h(13xxx errors/warnings, 14xxx info/prompts).- English in
language\np3_en_us\strings_en_us.rc. - Same English as placeholder in the 25 other
strings_*.rcfiles (translators update later). - Display via
InfoBoxLng()/MessageBoxLng(); checkIsYesOkay().Settings.MuteMessageBeepcontrols silent vs. sound — provide both paths.
Clipboard Monitoring (Pasteboard Mode)
Runtime toggle IDM_EDIT_TOGGLE_PASTEBOARD; /B enables at startup. Not persisted. IsPasteBoardActive() in Notepad3.h is the cross-TU query.
- Mutex with Tail (
FileWatching.MonitoringLog): each greys the other inMsgInitMenu, the tail toolbar button, and the "Monitoring Log" checkbox inChangeNotifyDlgProc./B+ persistedMonitoringLog=true→/Bwins the session, INI preserved. /Binitial auto-paste vs runtime toggle:/Bpastes the current clipboard once — only on an empty untitled buffer (no file arg, no/c, no auto-loaded MRU). Runtime toggle never auto-pastes. Preserve this asymmetry.Settings2.PasteBoardSeparator:\x01= one document EOL;\0= none; else verbatim. Also suppressed on first paste after enable and when caret is at line start.
File I/O
FileSave()/FileLoad()(Notepad3.c) →FileIO()→EditSaveFile()/EditLoadFile()(Edit.c).- Atomic save (temp +
ReplaceFileW) controlled bySettings2.AtomicFileSave. Globals.dwLastErrorholds Win32 error.FileSave()checksERROR_ACCESS_DENIED,ERROR_PATH_NOT_FOUNDbefore generic fallback.InstallFileWatching()usesFindFirstChangeNotificationWon the parent dir.InstallFileWatching(false)before save,(true)after.
PCRE2 (scintilla\pcre2\)
scintilla\pcre2\scintilla\PCRE2RegExEngine.cxx compiled with SCI_OWNREGEX overrides Scintilla's built-in regex. Key points: FindText, SubstituteByPosition, convertReplExpr (normalizes \1-\9→$1-$9, processes \n\t\xHH\uHHHH), translateRegExpr (\</\> → lookarounds, \uHHHH → \x{HHHH}). Standalone RegExFind used by EditURLDecode.
Replacement backref syntax: $0-$99, \0-\9, ${name}, ${+name}.
URL hotspot regex: HYPLNK_REGEX_FULL in src\Edit.c — matches https?://, ftp://, file:///, file://, mailto:, www., ftp.. Trailing group excludes .,:?!.
DarkMode (src\DarkMode\)
Windows 10/11 dark mode via IAT hooks on uxtheme/user32 (stub DLLs included).
ARM64
Supported: Win32 (x86), x64, x64_AVX2, ARM64. ARM 32-bit is not supported — Release|ARM maps to Win32.
- Both ARM64 and x64 define
_WIN64. Use_M_ARM64(orNP3_BUILD_ARM64inTypeDefs.h) to distinguish. - Rendering: defaults to
SC_TECHNOLOGY_DIRECTWRITERETAIN(2) — preserves Direct2D back buffer, avoids flicker on Qualcomm Adreno + Win11 25H2 DWM. Main window getsWS_EX_COMPOSITED. User can override viaRenderingTechnology/ View menu. - Build:
CETCompat=false(CET is x86/x64 only),TargetMachine=MachineARM64,_WIN64defined. Fix scripts:Build\scripts\FixARM64{CETCompat,CrossCompile,OutDir}.ps1. - grepWin: uses x64 exe via emulation.
MsgThemeChanged()wraps bar recreate / lexer reset / restyle inWM_SETREDRAW FALSE/TRUE+ singleRedrawWindow()at end. DarkModeRedrawWindow()inListViewUtil.hppomitsRDW_ERASE.
Conventions
Formatting & encoding
.clang-formatinsrc\— 4-space indent, Stroustrup braces, left-aligned pointers, no column limit, no include sorting..editorconfig— UTF-8/CRLF for source, 4-space indent C/C++. Lexilla uses tabs (upstream preserved).language\*\*.rc= UTF-8 WITHOUT BOM, CRLF. Never write with BOM. UseBuild\rc_to_utf8.cmdto strip. PowerShell:[System.Text.UTF8Encoding]::new($false). Python: write\r\nexplicitly.Build\Notepad3.ini,Build\minipath.ini= UTF-8 WITH BOM (EF BB BF). Preserve it.strsafe.hthroughout; deprecated string functions disabled.
Types
DocPos/DocPosU/DocLnfor document positions/lines (not rawint).cpi_enc_tfor encodings.HSTRINGW/HPATHLinstead of rawWCHAR*buffers.NOMINMAXis global — usemin()/max()or typed equivalents.
Scintilla interaction
Always use SciCall.h wrappers (direct function pointer for performance). Add missing wrappers there. Naming: DeclareSciCall{V|R}{0|01|1|2} — V=void, R=return; 0/1/2 = param count; 01 = lParam-only. The msg arg is the suffix after SCI_.
Example wrappers:
DeclareSciCallV0(Undo, UNDO); // SciCall_Undo()
DeclareSciCallV1(SetTechnology, SETTECHNOLOGY, int, technology); // SciCall_SetTechnology(int)
DeclareSciCallR0(GetTextLength, GETTEXTLENGTH, DocPos); // DocPos SciCall_GetTextLength()
DeclareSciCallR1(SupportsFeature, SUPPORTSFEATURE, bool, int, feature); // bool SciCall_SupportsFeature(int)
Scintilla/Lexilla versions
Vendored 5.5.8 / 5.4.6 with NP3 patches under np3_patches\. Versions in scintilla\version.txt / lexilla\version.txt. Check offline docs under scintilla\doc\ and lexilla\doc\ for new APIs.
Global state
Use existing structs (Globals, Settings, Settings2, Flags, Paths). Don't add new globals.
Portable INI design
- INI beside the exe. No registry. Runs on defaults if none exists (user creates via "Save Settings Now").
- Admin redirect:
Notepad3.ini=<path>in[Notepad3]redirects to per-user path (up to 2 levels). Redirect targets auto-created viaCreateIniFileEx(). Paths.IniFile= active writable INI;Paths.IniFileDefault= recovery fallback.- Init flow (in
src\Config\Config.cpp):FindIniFile()→TestIniFile()→CreateIniFile()→LoadSettings(). SettingsVersiondefaults toCFG_VER_CURRENTwhen missing — empty/new INI gets current defaults.bIniFileFromScratchis set when INI is 0 bytes, cleared afterSaveAllSettings(). While set,MuiLanguage.csuppresses writingPreferredLanguageLocaleName.- Empty lexer sections are pruned in
Style_ToIniSection()viaIniSectionGetKeyCount()— only sections with non-default styles persist. - MiniPath uses the same pattern (
minipath\src\Config.cpp). - New
Settings2(or other INI) params must be documented as commented entries inBuild\Notepad3.ini.
Save/Load macros (src\Config\Config.cpp)
Encoding_MapIniSettingis asymmetric. CPI constants (CPI_UTF8=6,CPI_OEM=1, …) don't equal INI values (3,5, …).Encoding_MapIniSetting(true, val)= INI→CPI (load);(false, val)= CPI→INI (save). CPI withbLoad=trueproduces wrong results. Reference:MRU_Save()usesfalse.- Defaults depending on
DefaultEncoding(e.g.SkipANSICodePageDetection,LoadASCIIasUTF8) must be recalculated in_SaveSettings()beforeSAVE_VALUE_IF_NOT_EQ_DEFAULTfires. See thebCurrentEncUTF8block.
Creating directories
Path_CreateDirectoryEx(hpth) wraps SHCreateDirectoryExW. Success: SUCCEEDED(hr) || (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)). Reference: CreateIniFile() in Config.cpp.
Long-path / PathLib wrappers
Never call Win32 file APIs directly with Path_Get(hpth). Use PathLib wrappers — they apply \\?\ prefix conditionally (only when RtlAreLongPathsEnabled() is false AND path ≥ 260 chars):
| Wrapper | Win32 |
|---|---|
Path_CreateFile |
CreateFileW |
Path_DeleteFile |
DeleteFileW |
Path_GetFileAttributes / Path_GetFileAttributesEx |
GetFileAttributes[Ex]W |
Path_SetFileAttributes |
SetFileAttributesW |
Path_ReplaceFile |
ReplaceFileW |
Path_MoveFileEx |
MoveFileExW |
Path_FindFirstFile |
FindFirstFileW |
Path_CreateDirectoryEx |
SHCreateDirectoryExW |
Path_IsExistingFile / Path_IsExistingDirectory |
GetFileAttributesW + check |
Path comparison
Use Path_StrgComparePath() (normalization + CompareStringOrdinal, locale-independent, case-insensitive). For raw wide strings: CompareStringOrdinal(s1, -1, s2, -1, TRUE). Never _wcsicmp / _wcsnicmp (locale-dependent).
Undo/Redo transactions
Use _BEGIN_UNDO_ACTION_ / _END_UNDO_ACTION_ macros (Notepad3.h) for grouping; they also throttle notifications during bulk edits.
WriteAccessBuf — dangling pointer anti-pattern
NEVER use a pointer from Path_WriteAccessBuf() / StrgWriteAccessBuf() after any operation that may reallocate/swap the same handle's buffer: Path_CanonicalizeEx, Path_Swap / StrgSwap, Path_ExpandEnvStrings / ExpandEnvironmentStrgs, Path_Append / Path_Reset, StrgCat / StrgInsert / StrgFormat / StrgReset, Path_NormalizeEx / Path_AbsoluteFromApp / Path_RelativeToApp.
After any of these:
- Read-only:
Path_Get(h)/StrgGet(h). - Read-write: re-obtain via
Path_WriteAccessBuf(h, 0)/StrgWriteAccessBuf(h, 0)(size 0 = no resize).