Notepad3/.github/copilot-instructions.md
2026-03-06 18:11:52 +01:00

12 KiB

Copilot Instructions for Notepad3

Build

Notepad3 is a Windows-only C/C++ application built with Visual Studio 2026 (toolset v145) and MSBuild. The solution file is Notepad3.sln.

Build commands

# Full build (all platforms: Win32, x64, x64_AVX2, ARM64)
Build\BuildAll.cmd [Release|Debug]

# Single platform
Build\Build_x64.cmd [Release|Debug]
Build\Build_Win32.cmd [Release|Debug]
Build\Build_ARM64.cmd [Release|Debug]
Build\Build_x64_AVX2.cmd [Release|Debug]

# MSBuild directly (used by CI)
msbuild Notepad3.sln /m /p:Configuration=Release /p:Platform=x64

# Clean
Build\Clean.cmd

# NuGet restore (required before first build)
nuget restore

Default configuration is Release. The build scripts delegate to PowerShell scripts in Build\scripts\.

Versioning

Run Version.ps1 before building to generate src\VersionEx.h from templates in Versions\. Version format is Major.YY.Mdd.Build where the build number is persisted in Versions\build.txt.

Tests

Tests live in test\ and run via GitHub Actions CI (Win32 and x64 jobs):

cd test
TestFileVersion.cmd       # Verifies built binary version info
TestAhkNotepad3.cmd       # AutoHotkey-based GUI tests (requires AutoHotkey)

CI

GitHub Actions workflow at .github/workflows/build.yml builds all four platforms (Win32, x64, x64_AVX2, ARM64) in Release on windows-2022 runners, triggered on push/PR to master.

Architecture

Notepad3 is a Win32 desktop text editor built on the Scintilla editing component with Lexilla for syntax highlighting. It ships with two companion tools: MiniPath (file browser) and grepWinNP3 (file search/grep).

Core modules (in src\)

  • Notepad3.c/h — Application entry point (wWinMain), window procedure, global state structs (GLOBALS_T, SETTINGS_T, FLAGS_T, PATHS_T)
  • Edit.c/h — Text manipulation: find/replace (PCRE2 regex), encoding conversion, clipboard, indentation, sorting, bookmarks, folding, auto-complete
  • Styles.c/h — Scintilla styling, lexer selection, theme management
  • Dialogs.c/h — All dialog boxes and UI interactions
  • Encoding.c/h — Character encoding detection and conversion
  • SciCall.h — Type-safe inline wrappers for Scintilla direct function calls (avoids SendMessage overhead)
  • DynStrg.c/h — Custom dynamic wide-string type (HSTRINGW) with automatic buffer management
  • PathLib.c/h — Path manipulation via opaque HPATHL handle
  • TypeDefs.h — Core type definitions (DocPos, DocLn, cpi_enc_t), Windows version targeting, compiler macros
  • MuiLanguage.c/h — Multi-language UI support, language DLL loading

Vendored dependencies

  • scintilla\ — Scintilla editor component with Notepad3-specific patches in np3_patches\
  • scintilla\doc\ - Scintilla documentation offline
  • lexilla\ — Lexilla syntax highlighting engine with custom patches
  • lexilla\doc\ — Lexilla documentation offline
  • scintilla\pcre2\ — PCRE2 10.47 regex engine for find/replace (replaced archived Oniguruma)
  • src\uchardet\ — Character encoding detector
  • src\tinyexpr\ / src\tinyexprcpp\ — Expression evaluator for statusbar
  • src\uthash\ — Hash table library (C macros)
  • src\crypto\ — Rijndael/SHA-256 for AES-256 encryption
  • Boost Regex & IOStreams — Managed via vcpkg.json

Syntax highlighting lexers (src\StyleLexers\)

Each language has its own styleLexXXX.c file (~50+ languages). All lexers follow the EDITLEXER struct pattern defined in EditLexer.h:

EDITLEXER lexXXX = {
    SCLEX_XXX,          // Scintilla lexer ID
    "lexerName",        // Lexilla lexer name (case-sensitive)
    IDS_LEX_XXX_STR,    // Resource string ID
    L"Config Name",     // INI section name
    L"ext1; ext2",      // Default file extensions
    L"",                // Extension buffer (runtime)
    &KeyWords_XXX,      // Keyword lists
    { /* EDITSTYLE array — must be last member */ }
};

Localization (language\)

Resource-based MUI system with 27+ locales. Each locale has a directory np3_LANG_COUNTRY\ containing resource .rc files. Language DLLs are built as separate projects in the solution.

Adding String Resources

  1. Add #define IDS_MUI_XXX <id> to language\common_res.h (use next available ID in the appropriate range: 13xxx for errors/warnings, 14xxx for info/prompts)
  2. Add the English string to language\np3_en_us\strings_en_us.rc in the matching STRINGTABLE block
  3. Add the same English text as placeholder to all other 25 locale strings_*.rc files (translators update later)
  4. Use InfoBoxLng() / MessageBoxLng() with MB_YESNO, MB_ICONWARNING, etc. to display; check result with IsYesOkay()
  5. Settings.MuteMessageBeep controls whether to use InfoBoxLng (silent) or MessageBoxLng (with sound) — always provide both paths

File I/O Flow

  • FileSave() (src\Notepad3.c) — Main save dispatcher. Handles Save, Save As, Save Copy. Calls FileIO()EditSaveFile() (src\Edit.c)
  • FileLoad() (src\Notepad3.c) — Main load dispatcher. Calls FileIO()EditLoadFile() (src\Edit.c)
  • EditSaveFile() supports atomic save (temp file + ReplaceFileW) controlled by Settings2.AtomicFileSave
  • Error handling: Globals.dwLastError holds the Win32 error code after failed I/O. FileSave() checks specific codes (ERROR_ACCESS_DENIED, ERROR_PATH_NOT_FOUND) before falling back to generic error.
  • File watching: InstallFileWatching() uses FindFirstChangeNotificationW on the parent directory. Must be stopped before save (InstallFileWatching(false)) and restarted after (InstallFileWatching(true)).

PCRE2 Regex Engine (scintilla\pcre2\)

PCRE2 10.47 replaced the archived Oniguruma library. The Scintilla integration lives in scintilla\pcre2\scintilla\PCRE2RegExEngine.cxx, compiled with SCI_OWNREGEX to override Scintilla's built-in regex.

Key components:

  • PCRE2RegExEngine::FindText — Scintilla regex search (pattern matching via pcre2_match)
  • PCRE2RegExEngine::SubstituteByPosition — Regex replacement with group references
  • PCRE2RegExEngine::convertReplExpr — Normalizes replacement strings: converts \1-\9 to $1-$9, processes escape sequences (\n, \t, \xHH, \uHHHH)
  • PCRE2RegExEngine::translateRegExpr — Translates Scintilla regex extensions: \</\> word boundaries → lookarounds, \uHHHH\x{HHHH}
  • RegExFind (exported C function) — Standalone regex find used by EditURLDecode in Edit.c; wraps SimplePCRE2Engine

Replacement string backreference syntax (both flavors supported for backward compatibility):

  • $0-$99 and \0-\9 — numbered group references
  • ${name} / ${+name} — named group references
  • Escape sequences: \n, \t, \r, \\, \xHH, \uHHHH

URL hotspot regex is defined at src\Edit.c:108 (HYPLNK_REGEX_FULL macro). It matches https?://, ftp://, file:///, file://, mailto:, www., ftp. schemes. The trailing group excludes punctuation (.,:?!) so URLs don't absorb sentence-ending characters.

DarkMode (src\DarkMode\)

Windows 10/11 dark mode via IAT (Import Address Table) hooks. Includes stub DLLs for uxtheme and user32.

Conventions

Code style

  • Formatting: LLVM-based .clang-format in src\ — 4-space indentation, Stroustrup brace style, left-aligned pointers, no column limit, no include sorting
  • Editor config: .editorconfig enforces UTF-8/CRLF for source files, 4-space indentation for C/C++; Lexilla code uses tabs (preserved from upstream)
  • String safety: Uses strsafe.h throughout; deprecated string functions are disabled

Type conventions

  • Use DocPos / DocPosU / DocLn for Scintilla document positions and line numbers (not raw int)
  • Use cpi_enc_t for encoding identifiers
  • Use HSTRINGW and HPATHL (opaque handle types) instead of raw WCHAR* buffers for dynamic strings and paths
  • NOMINMAX is defined globally — use min()/max() macros or typed equivalents

Scintilla interaction

Always use SciCall.h wrappers (e.g., SciCall_GetTextLength()) instead of raw SendMessage(hwnd, SCI_XXX, ...). The wrappers use Scintilla's direct function pointer for performance. Add missing wrapper calls to SciCall.h if needed.

SciCall.h wrapper macros

Wrappers are declared using macros. The naming convention is DeclareSciCall{V|R}{0|01|1|2}:

  • V = void return, R = has return value
  • 0 = no parameters, 1 = one parameter (wParam), 2 = two parameters (wParam + lParam)
  • 01 = optional second parameter only (wParam=0, lParam=var) — used when the SCI message takes lParam but not wParam
// Examples:
DeclareSciCallV0(Undo, UNDO);                                          // SciCall_Undo()
DeclareSciCallV1(SetTechnology, SETTECHNOLOGY, int, technology);       // SciCall_SetTechnology(int)
DeclareSciCallV2(ScrollVertical, SCROLLVERTICAL, DocLn, docLn, int, subLn); // SciCall_ScrollVertical(DocLn, int)
DeclareSciCallR0(GetTextLength, GETTEXTLENGTH, DocPos);                // DocPos SciCall_GetTextLength()
DeclareSciCallR1(SupportsFeature, SUPPORTSFEATURE, bool, int, feature); // bool SciCall_SupportsFeature(int)

The msg argument is the suffix after SCI_ (e.g., UNDO for SCI_UNDO).

Scintilla / Lexilla versions

The vendored Scintilla (5.5.8) and Lexilla (5.4.6) have Notepad3-specific patches in scintilla\np3_patches\ and lexilla\np3_patches\. Version numbers are in scintilla\version.txt and lexilla\version.txt. When evaluating new Scintilla APIs, check the offline docs in scintilla\doc\ and lexilla\doc\.

Adding a new syntax lexer

  1. Create src\StyleLexers\styleLexNEW.c following existing lexer patterns
  2. Define EDITLEXER struct with lexer ID, name, extensions, keywords, and styles
  3. Register it in Styles.c lexer array
  4. Add localization string IDs to resource files

Global state

Application state is centralized in global structs in Notepad3.cGlobals, Settings, Settings2, Flags, Paths. Prefer accessing these through their defined interfaces rather than adding new globals.

INI file / portable-app design

Notepad3 follows a portable-app design for its configuration file (Notepad3.ini):

  • No auto-creation on first run: If no INI file is found, the application runs with defaults. The path is stored in Paths.IniFileDefault (not Paths.IniFile) so the user can explicitly create it via "Save Settings Now".
  • Admin redirect: An administrator can place Notepad3.ini=<path> in [Notepad3] section of the app-directory INI to redirect to a per-user path. Up to 2 levels of redirect are supported. Redirect targets are auto-created (the admin intended them to exist).
  • Key paths: Paths.IniFile = active writable INI (empty if none exists), Paths.IniFileDefault = fallback path for "Save Settings Now" recovery.
  • Configuration code: All INI init logic lives in src\Config\Config.cppFindIniFile()TestIniFile()CreateIniFile()LoadSettings().
  • MiniPath follows the same portable INI and admin-redirect pattern (minipath\src\Config.cpp). Redirect targets are auto-created via CreateIniFileEx().
  • New parameters: When adding new Settings2 (or other INI) parameters, always document them as commented entries in Build\Notepad3.ini

Creating Directories

Use SHCreateDirectoryExW(NULL, path, NULL) to recursively create directory trees (requires <shlobj.h>, already included in core modules). Check result: SUCCEEDED(hr) || (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)). See CreateIniFile() in src\Config\Config.cpp for the reference pattern.

Undo/Redo transactions

Use _BEGIN_UNDO_ACTION_ / _END_UNDO_ACTION_ macros (defined in Notepad3.h) to group Scintilla operations into single undo steps. These also handle notification limiting during bulk edits.