Notepad3/.github/copilot-instructions.md

12 KiB
Raw Blame History

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 platforms
  • msbuild Notepad3.sln /m /p:Configuration=Release /p:Platform=x64 — CI equivalent
  • Build\Clean.cmd — clean outputs
  • nuget restore once before first build
  • Version.ps1 regenerates src\VersionEx.h (Major.YY.Mdd.Build, build number in Versions\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/hwWinMain, 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/hHSTRINGW dynamic wide-string handle.
  • PathLib.c/hHPATHL path handle + long-path-aware Win32 wrappers.
  • TypeDefs.hDocPos, 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

  1. #define IDS_MUI_XXX <id> in language\common_res.h (13xxx errors/warnings, 14xxx info/prompts).
  2. English in language\np3_en_us\strings_en_us.rc.
  3. Same English as placeholder in the 25 other strings_*.rc files (translators update later).
  4. Display via InfoBoxLng() / MessageBoxLng(); check IsYesOkay(). Settings.MuteMessageBeep controls 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 in MsgInitMenu, the tail toolbar button, and the "Monitoring Log" checkbox in ChangeNotifyDlgProc. /B + persisted MonitoringLog=true/B wins the session, INI preserved.
  • /B initial auto-paste vs runtime toggle: /B pastes 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 by Settings2.AtomicFileSave.
  • Globals.dwLastError holds Win32 error. FileSave() checks ERROR_ACCESS_DENIED, ERROR_PATH_NOT_FOUND before generic fallback.
  • InstallFileWatching() uses FindFirstChangeNotificationW on 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 supportedRelease|ARM maps to Win32.

  • Both ARM64 and x64 define _WIN64. Use _M_ARM64 (or NP3_BUILD_ARM64 in TypeDefs.h) to distinguish.
  • Rendering: defaults to SC_TECHNOLOGY_DIRECTWRITERETAIN (2) — preserves Direct2D back buffer, avoids flicker on Qualcomm Adreno + Win11 25H2 DWM. Main window gets WS_EX_COMPOSITED. User can override via RenderingTechnology / View menu.
  • Build: CETCompat=false (CET is x86/x64 only), TargetMachine=MachineARM64, _WIN64 defined. Fix scripts: Build\scripts\FixARM64{CETCompat,CrossCompile,OutDir}.ps1.
  • grepWin: uses x64 exe via emulation.
  • MsgThemeChanged() wraps bar recreate / lexer reset / restyle in WM_SETREDRAW FALSE/TRUE + single RedrawWindow() at end. DarkMode RedrawWindow() in ListViewUtil.hpp omits RDW_ERASE.

Conventions

Formatting & encoding

  • .clang-format in src\ — 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. Use Build\rc_to_utf8.cmd to strip. PowerShell: [System.Text.UTF8Encoding]::new($false). Python: write \r\n explicitly.
  • Build\Notepad3.ini, Build\minipath.ini = UTF-8 WITH BOM (EF BB BF). Preserve it.
  • strsafe.h throughout; deprecated string functions disabled.

Types

  • DocPos / DocPosU / DocLn for document positions/lines (not raw int).
  • cpi_enc_t for encodings.
  • HSTRINGW / HPATHL instead of raw WCHAR* buffers.
  • NOMINMAX is global — use min()/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 via CreateIniFileEx().
  • Paths.IniFile = active writable INI; Paths.IniFileDefault = recovery fallback.
  • Init flow (in src\Config\Config.cpp): FindIniFile()TestIniFile()CreateIniFile()LoadSettings().
  • SettingsVersion defaults to CFG_VER_CURRENT when missing — empty/new INI gets current defaults.
  • bIniFileFromScratch is set when INI is 0 bytes, cleared after SaveAllSettings(). While set, MuiLanguage.c suppresses writing PreferredLanguageLocaleName.
  • Empty lexer sections are pruned in Style_ToIniSection() via IniSectionGetKeyCount() — 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 in Build\Notepad3.ini.

Save/Load macros (src\Config\Config.cpp)

  • Encoding_MapIniSetting is 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 with bLoad=true produces wrong results. Reference: MRU_Save() uses false.
  • Defaults depending on DefaultEncoding (e.g. SkipANSICodePageDetection, LoadASCIIasUTF8) must be recalculated in _SaveSettings() before SAVE_VALUE_IF_NOT_EQ_DEFAULT fires. See the bCurrentEncUTF8 block.

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).