mirror of
https://github.com/rizonesoft/Notepad3.git
synced 2026-06-14 21:09:05 +08:00
Merge branch 'master' into Mui_Dev
This commit is contained in:
commit
1a2ae2cf64
366
.github/copilot-instructions.md
vendored
366
.github/copilot-instructions.md
vendored
@ -1,314 +1,186 @@
|
||||
# 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
|
||||
|
||||
Notepad3 is a Windows-only C/C++ application built with Visual Studio 2026 (toolset v145) and MSBuild. The solution file is `Notepad3.sln`.
|
||||
Scripts under `Build\` (PowerShell under `Build\scripts\`):
|
||||
|
||||
### Build commands
|
||||
- `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).
|
||||
|
||||
```powershell
|
||||
# Full build (all platforms: Win32, x64, x64_AVX2, ARM64)
|
||||
Build\BuildAll.cmd [Release|Debug]
|
||||
Default config is Release.
|
||||
|
||||
# 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]
|
||||
## Core Modules (`src\`)
|
||||
|
||||
# MSBuild directly (used by CI)
|
||||
msbuild Notepad3.sln /m /p:Configuration=Release /p:Platform=x64
|
||||
- `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` — `HSTRINGW` dynamic wide-string handle.
|
||||
- `PathLib.c/h` — `HPATHL` path handle + long-path-aware Win32 wrappers.
|
||||
- `TypeDefs.h` — `DocPos`, `DocLn`, `cpi_enc_t`, OS targeting, compiler macros.
|
||||
- `MuiLanguage.c/h` — MUI language DLL loading.
|
||||
|
||||
# Clean
|
||||
Build\Clean.cmd
|
||||
## Vendored Libraries
|
||||
|
||||
# NuGet restore (required before first build)
|
||||
nuget restore
|
||||
```
|
||||
`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).
|
||||
|
||||
Default configuration is Release. The build scripts delegate to PowerShell scripts in `Build\scripts\`.
|
||||
## grepWin (`grepWin\`) — external tool, not built from source
|
||||
|
||||
### Versioning
|
||||
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`).
|
||||
|
||||
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`.
|
||||
## Lexers (`src\StyleLexers\`)
|
||||
|
||||
### Tests
|
||||
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.
|
||||
|
||||
Tests live in `test\` and run via GitHub Actions CI (Win32 and x64 jobs):
|
||||
## Localization (`language\`)
|
||||
|
||||
```cmd
|
||||
cd test
|
||||
TestFileVersion.cmd # Verifies built binary version info
|
||||
TestAhkNotepad3.cmd # AutoHotkey-based GUI tests (requires AutoHotkey)
|
||||
```
|
||||
27+ locales under `np3_LANG_COUNTRY\`. Language packs build as separate DLLs.
|
||||
|
||||
### CI
|
||||
### Adding a string resource
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## Architecture
|
||||
## Clipboard Monitoring (Pasteboard Mode)
|
||||
|
||||
Notepad3 is a Win32 desktop text editor built on the **Scintilla** editing component with **Lexilla** for syntax highlighting. It ships with the companion tool **MiniPath** (file browser) and integrates with the external **grepWin** tool (file search/grep) via pre-built portable executables.
|
||||
Runtime-toggleable; external clipboard changes are pasted at the caret.
|
||||
|
||||
### Core modules (in `src\`)
|
||||
- Menu: `IDM_EDIT_TOGGLE_PASTEBOARD` — "Toggle Clipboard Monitoring". Check mark reflects state.
|
||||
- Helpers `PasteBoard_Start(HWND)` / `PasteBoard_Stop(HWND)` wrap `AddClipboardFormatListener` + `ID_PASTEBOARDTIMER`. Used for `/B` startup and from the toggle handler.
|
||||
- **Not persisted**; always OFF at startup unless `/B`.
|
||||
- **Mutex with Tail** (`IDM_VIEW_CHASING_DOCTAIL` / `FileWatching.MonitoringLog`): each mode greys the other in `MsgInitMenu`; tail toolbar button (`IDT_VIEW_CHASING_DOCTAIL`) greyed; "Monitoring Log" checkbox in `ChangeNotifyDlgProc` greyed. `IsPasteBoardActive()` in `Notepad3.h` for cross-TU use.
|
||||
- Startup conflict (`/B` + persisted `MonitoringLog=true`): `/B` wins this session; `FileWatching.MonitoringLog` cleared in memory, `Settings.MonitoringLog` (INI) preserved.
|
||||
- `PasteBoardTimerProc` pastes at caret. `Settings2.PasteBoardSeparator` pre-pended; suppressed on (1) first paste after enable and (2) caret at line start. `\x01` = one document EOL; `\0` = no separator.
|
||||
- Status bar `STATUS_OVRMODE` shows `CBS` while active (passive indicator).
|
||||
|
||||
- **Notepad3.c/h** — Application entry point (`wWinMain`), window procedure, global state structs (`GLOBALS_T`, `SETTINGS_T`, `FLAGS_T`, `PATHS_T`), `MsgCommand()` dispatcher with sub-handlers
|
||||
- **Notepad3Util.c/h** — Utility functions extracted from Notepad3.c: bitmap/toolbar image loading (`NP3Util_LoadBitmapFile`, `NP3Util_CreateScaledImageListFromBitmap`), word-wrap configuration (`NP3Util_SetWrapIndentMode`, `NP3Util_SetWrapVisualFlags`), auto-scroll (`NP3Util_AutoScrollStart/Stop`)
|
||||
- **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
|
||||
## File I/O
|
||||
|
||||
### Vendored dependencies
|
||||
- `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.
|
||||
|
||||
- **`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
|
||||
## PCRE2 (`scintilla\pcre2\`)
|
||||
|
||||
### grepWin Integration (`grepWin\`)
|
||||
`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`.
|
||||
|
||||
grepWin is an **external** file search/grep tool — it is **not** built from source as part of Notepad3. Pre-built portable executables and translation files are stored in the repository:
|
||||
Replacement backref syntax: `$0`-`$99`, `\0`-`\9`, `${name}`, `${+name}`.
|
||||
|
||||
- **`grepWin\portables\`** — `grepWin-x86_portable.exe`, `grepWin-x64_portable.exe`, `LICENSE.txt`
|
||||
- **`grepWin\translations\`** — `*.lang` translation files (e.g. `German.lang`, `French.lang`)
|
||||
URL hotspot regex: `HYPLNK_REGEX_FULL` in `src\Edit.c` — matches `https?://`, `ftp://`, `file:///`, `file://`, `mailto:`, `www.`, `ftp.`. Trailing group excludes `.,:?!`.
|
||||
|
||||
At runtime (`src\Dialogs.c`), Notepad3 searches for grepWin in this order:
|
||||
1. `Settings2.GrepWinPath` (user-configured INI setting)
|
||||
2. `<ModuleDirectory>\grepWin\grepWin-x64_portable.exe` (or x86) — portable layout
|
||||
3. `%APPDATA%\Rizonesoft\Notepad3\grepWin\` — installed layout
|
||||
## DarkMode (`src\DarkMode\`)
|
||||
|
||||
Language mapping (`src\MuiLanguage.c`): `grepWinLangResName[]` maps Notepad3 locale names (e.g. `de-DE`) to grepWin `.lang` filenames (e.g. `German.lang`). The language file path is written to `grepwin.ini` before launching.
|
||||
Windows 10/11 dark mode via IAT hooks on uxtheme/user32 (stub DLLs included).
|
||||
|
||||
Portable build scripts (`Build\make_portable_*.cmd`) package grepWin into a `grepWin\` subdirectory in the archive containing both portable executables, the license, and all `*.lang` translations.
|
||||
## ARM64
|
||||
|
||||
### Syntax highlighting lexers (`src\StyleLexers\`)
|
||||
Supported: Win32 (x86), x64, x64_AVX2, ARM64. **ARM 32-bit is not supported** — `Release|ARM` maps to Win32.
|
||||
|
||||
Each language has its own `styleLexXXX.c` file (~50+ languages). All lexers follow the `EDITLEXER` struct pattern defined in `EditLexer.h`:
|
||||
|
||||
```c
|
||||
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.
|
||||
|
||||
### ARM64 Platform Considerations
|
||||
|
||||
**Supported platforms**: Win32 (x86), x64, x64_AVX2, ARM64. ARM 32-bit is **not** supported (the `Release|ARM` solution config maps to Win32).
|
||||
|
||||
#### Architecture detection
|
||||
|
||||
Use `#if defined(_M_ARM64)` or the helper macro `NP3_BUILD_ARM64` (defined in `src\TypeDefs.h`) for ARM64-specific code paths. **Important**: both ARM64 and x64 define `_WIN64`, so use `_M_ARM64` when you need to distinguish ARM64 from x64.
|
||||
|
||||
#### ARM64 rendering defaults
|
||||
|
||||
ARM64 defaults to `SC_TECHNOLOGY_DIRECTWRITERETAIN` (value 2) instead of `SC_TECHNOLOGY_DIRECTWRITE` (value 1) to preserve the Direct2D back buffer between frames. This avoids flickering on Qualcomm Adreno GPUs and the Win11 25H2 DWM compositor. The main window also uses `WS_EX_COMPOSITED` on ARM64 for system-level double-buffering. Users can override via `RenderingTechnology` in the INI file or the View menu.
|
||||
|
||||
#### ARM64 build configuration
|
||||
|
||||
- `CETCompat` must be `false` for ARM64 (CET is x86/x64 only)
|
||||
- `TargetMachine` must be `MachineARM64` in all ARM64 linker sections
|
||||
- `_WIN64` must be defined in preprocessor definitions for all ARM64 configurations
|
||||
- Build fix scripts in `Build\scripts\`: `FixARM64CETCompat.ps1`, `FixARM64CrossCompile.ps1`, `FixARM64OutDir.ps1`
|
||||
|
||||
#### GrepWin on ARM64
|
||||
|
||||
No native ARM64 grepWin build exists. The ARM64 build uses `grepWin-x64_portable.exe` which runs via x64 emulation on Windows ARM64. The binary selection in `src\Notepad3.c` uses `#if defined(_M_ARM64)` to handle this explicitly.
|
||||
|
||||
#### Theme change flickering prevention
|
||||
|
||||
`MsgThemeChanged()` in `src\Notepad3.c` wraps all heavy operations (bar recreation, lexer reset, restyling) in `WM_SETREDRAW FALSE/TRUE` to suppress intermediate repaints and performs a single `RedrawWindow()` at the end. DarkMode `RedrawWindow()` calls in `ListViewUtil.hpp` omit `RDW_ERASE` to avoid background erase flashes during theme transitions.
|
||||
- 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
|
||||
|
||||
### Code style
|
||||
### Formatting & encoding
|
||||
|
||||
- **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)
|
||||
- **File encoding rules** (must be respected when creating or editing these files):
|
||||
- `language\*\*.rc` — **UTF-8 without BOM**. Never write or save these files with a UTF-8 BOM. Use `Build\rc_to_utf8.cmd` to strip accidental BOMs.
|
||||
- **PowerShell pitfall**: `[System.Text.Encoding]::UTF8` writes **with** BOM. Always use `[System.Text.UTF8Encoding]::new($false)` when writing `.rc` files from PowerShell scripts.
|
||||
- **Python/script pitfall**: `.rc` files use **CRLF** line endings. When writing or inserting lines from Python or other scripts, use `\r\n` — not bare `\n`. After bulk edits, normalize with: `content = content.replace('\r\n', '\n').replace('\n', '\r\n')` before writing.
|
||||
- `Build\Notepad3.ini`, `Build\minipath.ini` — **UTF-8 with BOM** (BOM = `EF BB BF`). These INI reference files must retain the BOM.
|
||||
- **String safety**: Uses `strsafe.h` throughout; deprecated string functions are disabled
|
||||
- `.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.
|
||||
|
||||
### Type conventions
|
||||
### Types
|
||||
|
||||
- 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
|
||||
- `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 (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.
|
||||
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_`.
|
||||
|
||||
#### 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
|
||||
Example wrappers:
|
||||
|
||||
```c
|
||||
// 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)
|
||||
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)
|
||||
```
|
||||
|
||||
The `msg` argument is the suffix after `SCI_` (e.g., `UNDO` for `SCI_UNDO`).
|
||||
### Scintilla/Lexilla versions
|
||||
|
||||
### 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
|
||||
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
|
||||
|
||||
Application state is centralized in global structs in `Notepad3.c` — `Globals`, `Settings`, `Settings2`, `Flags`, `Paths`. Prefer accessing these through their defined interfaces rather than adding new globals.
|
||||
Use existing structs (`Globals`, `Settings`, `Settings2`, `Flags`, `Paths`). Don't add new globals.
|
||||
|
||||
### INI file / portable-app design
|
||||
### Portable INI design
|
||||
|
||||
Notepad3 follows a **portable-app** design for its configuration file (`Notepad3.ini`):
|
||||
- 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`.**
|
||||
|
||||
- **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.cpp` — `FindIniFile()` → `TestIniFile()` → `CreateIniFile()` → `LoadSettings()`.
|
||||
- **Empty INI = no INI**: An empty (0-byte) INI file must produce identical saved output to having no INI file at all. `SettingsVersion` defaults to `CFG_VER_CURRENT` when missing — a missing key does NOT imply a legacy config. Empty lexer sections are removed after style saving (`IniSectionGetKeyCount()`).
|
||||
- **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`
|
||||
### Save/Load macros (`src\Config\Config.cpp`)
|
||||
|
||||
### Settings 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.
|
||||
|
||||
**`Encoding_MapIniSetting` direction** — CPI constants (`CPI_UTF8=6`, `CPI_OEM=1`, etc.) do NOT equal their INI storage values (`3`, `5`, etc.). The mapping is asymmetric:
|
||||
- `Encoding_MapIniSetting(true, val)` = INI integer → CPI constant (**loading**)
|
||||
- `Encoding_MapIniSetting(false, val)` = CPI constant → INI integer (**saving**)
|
||||
### Creating directories
|
||||
|
||||
Passing a CPI value with `bLoad=true` produces wrong results. Reference: `MRU_Save()` correctly uses `false` for saving.
|
||||
`Path_CreateDirectoryEx(hpth)` wraps `SHCreateDirectoryExW`. Success: `SUCCEEDED(hr) || (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS))`. Reference: `CreateIniFile()` in `Config.cpp`.
|
||||
|
||||
**Conditional defaults and `SAVE_VALUE_IF_NOT_EQ_DEFAULT`** — Some settings have defaults that depend on `DefaultEncoding` (e.g., `SkipANSICodePageDetection`, `LoadASCIIasUTF8`). If the encoding can change at runtime (via the Encoding dialog), the dependent `Defaults.*` fields must be recalculated in `_SaveSettings()` before the comparison macros fire — otherwise stale defaults cause incorrect save/delete decisions. See the `bCurrentEncUTF8` block in `_SaveSettings()`.
|
||||
### Long-path / PathLib wrappers
|
||||
|
||||
### Creating Directories
|
||||
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):
|
||||
|
||||
Use `Path_CreateDirectoryEx(hpth)` (PathLib wrapper around `SHCreateDirectoryExW`) to recursively create directory trees. Check result: `SUCCEEDED(hr) || (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS))`. See `CreateIniFile()` in `src\Config\Config.cpp` for the reference pattern.
|
||||
| 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 |
|
||||
|
||||
### Long-Path Support / Win32 File API Wrappers
|
||||
### Path comparison
|
||||
|
||||
Notepad3 supports paths exceeding `MAX_PATH` (260 chars) on systems without the Windows 10 long-path registry opt-in by conditionally prepending the `\\?\` prefix. The static function `PrependLongPathPrefix()` in PathLib.c handles this.
|
||||
|
||||
**Rule: Never call Win32 file APIs directly with `Path_Get(hpth)`.** Use the PathLib wrapper functions instead — they internally apply the copy-prefix-call-release pattern:
|
||||
|
||||
| Wrapper | Win32 API |
|
||||
|---------|-----------|
|
||||
| `Path_CreateFile(hpth, ...)` | `CreateFileW` |
|
||||
| `Path_DeleteFile(hpth)` | `DeleteFileW` |
|
||||
| `Path_GetFileAttributes(hpth)` | `GetFileAttributesW` |
|
||||
| `Path_GetFileAttributesEx(hpth, ...)` | `GetFileAttributesExW` |
|
||||
| `Path_SetFileAttributes(hpth, ...)` | `SetFileAttributesW` |
|
||||
| `Path_ReplaceFile(hpth_dest, src)` | `ReplaceFileW` |
|
||||
| `Path_MoveFileEx(src, hpth_dest, ...)` | `MoveFileExW` |
|
||||
| `Path_FindFirstFile(hpth, ...)` | `FindFirstFileW` |
|
||||
| `Path_CreateDirectoryEx(hpth)` | `SHCreateDirectoryExW` |
|
||||
| `Path_IsExistingFile(hpth)` | `GetFileAttributesW` + check |
|
||||
| `Path_IsExistingDirectory(hpth)` | `GetFileAttributesW` + check |
|
||||
|
||||
The prefix is only added when `RtlAreLongPathsEnabled()` returns false AND the path is ≥ 260 chars. On modern Windows 10+ with the opt-in, these wrappers are effectively pass-through.
|
||||
|
||||
### Path Comparison
|
||||
|
||||
Use `Path_StrgComparePath()` for comparing file paths — it supports optional normalization and uses `CompareStringOrdinal` (locale-independent, case-insensitive). For raw wide-string path comparison, use `CompareStringOrdinal(s1, -1, s2, -1, TRUE)` instead of `_wcsicmp` or `_wcsnicmp` which are locale-dependent.
|
||||
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 (defined in `Notepad3.h`) to group Scintilla operations into single undo steps. These also handle notification limiting during bulk edits.
|
||||
Use `_BEGIN_UNDO_ACTION_` / `_END_UNDO_ACTION_` macros (`Notepad3.h`) for grouping; they also throttle notifications during bulk edits.
|
||||
|
||||
### WriteAccessBuf — Dangling Pointer Anti-Pattern
|
||||
### WriteAccessBuf — dangling pointer anti-pattern
|
||||
|
||||
**NEVER use a pointer obtained from `Path_WriteAccessBuf()` / `StrgWriteAccessBuf()` after ANY operation that may reallocate or swap the underlying buffer of the SAME handle.** The pointer becomes dangling (use-after-free).
|
||||
**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`.
|
||||
|
||||
Buffer-invalidating operations:
|
||||
- `Path_CanonicalizeEx(h, ...)` — calls `Path_Swap` internally
|
||||
- `Path_Swap(h, ...)` / `StrgSwap(h, ...)`
|
||||
- `Path_ExpandEnvStrings(h)` / `ExpandEnvironmentStrgs(h, ...)` — may realloc
|
||||
- `Path_Append(h, ...)` / `Path_Reset(h, ...)` — may realloc
|
||||
- `StrgCat(h, ...)` / `StrgInsert(h, ...)` / `StrgFormat(h, ...)` / `StrgReset(h, ...)` — may realloc
|
||||
- `Path_NormalizeEx(h, ...)` / `Path_AbsoluteFromApp(h, ...)` / `Path_RelativeToApp(h, ...)` — may realloc/swap
|
||||
|
||||
Safe patterns after invalidation:
|
||||
- **Read-only**: use `Path_Get(h)` or `StrgGet(h)` — always returns current buffer
|
||||
- **Read-write**: re-obtain via `ptr = Path_WriteAccessBuf(h, 0)` / `ptr = StrgWriteAccessBuf(h, 0)` (size 0 = no resize, just returns current pointer)
|
||||
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).
|
||||
|
||||
@ -28,7 +28,7 @@ SettingsVersion=5
|
||||
;NoCopyLineOnEmptySelection=0
|
||||
;NoCutLineOnEmptySelection=0
|
||||
;CopyMultiSelectionSeparator= ;(-> <use current EOL>) {separator between multi-selection copies; empty=no separator; supports escape sequences: \r\n=CRLF, \n=LF, \t=tab, \xHH=hex}
|
||||
;PasteBoardSeparator= ;(-> <use current EOL>) {separator inserted verbatim between entries in pasteboard mode; empty=no separator; supports \r\n, \n, \t, \xHH; include newlines explicitly e.g. "\r\n---\r\n" for a dashed separator line}
|
||||
;PasteBoardSeparator= ;(-> <use current EOL>) {separator pre-pended before each new clipboard entry (pasted at caret) in pasteboard mode; suppressed on first paste after enable and when caret is at a line start; empty=no separator; supports \r\n, \n, \t, \xHH; include newlines explicitly e.g. "\r\n---\r\n" for a dashed separator line}
|
||||
;PasteBoardDebounceMs=200 ;(min: 0, max: 5000[msec]) {debounce interval for clipboard monitoring}
|
||||
;PasteBoardAddTimestamp=0 ;(0/1) {prepend [HH:MM:SS] timestamp to each pasted entry in pasteboard mode}
|
||||
;NoFadeHidden=0
|
||||
|
||||
414
CLAUDE.md
414
CLAUDE.md
@ -1,354 +1,192 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
Guidance for Claude Code working in this repo. Read once per session; covers non-obvious mechanics and gotchas. Self-evident build/layout information is not duplicated here.
|
||||
|
||||
## Project Overview
|
||||
## Project
|
||||
|
||||
Notepad3 is a Windows-only Win32 desktop text editor (C/C++) built on **Scintilla** (editing component) and **Lexilla** (syntax highlighting). It ships with the companion tool **MiniPath** (file browser, Ctrl+M) and integrates with the external **grepWin** tool (file search/grep, Ctrl+Shift+F) via pre-built portable executables. Licensed under BSD 3-Clause.
|
||||
Notepad3 — Win32 C/C++ text editor on Scintilla/Lexilla. Ships with MiniPath (`Ctrl+M`) and integrates external grepWin (`Ctrl+Shift+F`) via pre-built portable exes. BSD-3. Windows-only.
|
||||
|
||||
## Build Commands
|
||||
## Build
|
||||
|
||||
```powershell
|
||||
# NuGet restore (required before first build)
|
||||
nuget restore Notepad3.sln
|
||||
Build scripts live in `Build\` (PowerShell under `Build\scripts\`):
|
||||
|
||||
# Single platform builds
|
||||
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]
|
||||
- `Build\Build_x64.cmd [Release|Debug]` — single platform (also `_Win32`, `_ARM64`, `_x64_AVX2`)
|
||||
- `Build\BuildAll.cmd` — all platforms
|
||||
- `msbuild Notepad3.sln /m /p:Configuration=Release /p:Platform=x64` — CI equivalent
|
||||
- `Build\Clean.cmd` — clean outputs
|
||||
- Run `nuget restore Notepad3.sln` once before first build.
|
||||
- Run `Version.ps1` before building to regenerate `src\VersionEx.h` (format `Major.YY.Mdd.Build`; build number in `Versions\build.txt`).
|
||||
- Tests: `test\TestFileVersion.cmd`, `test\TestAhkNotepad3.cmd` (needs AutoHotkey). CI matrix in `.github/workflows/build.yml` (windows-2022).
|
||||
|
||||
# All platforms at once
|
||||
Build\BuildAll.cmd [Release|Debug]
|
||||
Default configuration is Release.
|
||||
|
||||
# MSBuild directly (used by CI)
|
||||
msbuild Notepad3.sln /m /p:Configuration=Release /p:Platform=x64
|
||||
|
||||
# Clean all outputs
|
||||
Build\Clean.cmd
|
||||
```
|
||||
|
||||
Default configuration is Release. Build scripts delegate to PowerShell in `Build\scripts\`.
|
||||
|
||||
### Versioning
|
||||
|
||||
Run `Version.ps1` before building to generate `src\VersionEx.h` from templates in `Versions\`. Format: `Major.YY.Mdd.Build` (build number persisted in `Versions\build.txt`).
|
||||
|
||||
### Tests
|
||||
|
||||
```cmd
|
||||
cd test
|
||||
TestFileVersion.cmd # Verifies built binary version info
|
||||
TestAhkNotepad3.cmd # AutoHotkey-based GUI tests (requires AutoHotkey)
|
||||
```
|
||||
|
||||
### CI
|
||||
|
||||
GitHub Actions (`.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
|
||||
|
||||
### Core Modules (`src\`)
|
||||
## Code Map (`src\`)
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| **Notepad3.c/h** | Entry point (`wWinMain`), window procedure (`MainWndProc`), global state structs (`Globals`, `Settings`, `Settings2`, `Flags`, `Paths`), `MsgCommand()` dispatcher |
|
||||
| **Notepad3Util.c/h** | Utility functions extracted from Notepad3.c: bitmap/toolbar image loading, word-wrap configuration, auto-scroll (middle-click continuous scroll) |
|
||||
| **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, margin configuration |
|
||||
| **Dialogs.c/h** | All dialog boxes, DPI-aware UI interactions, window placement |
|
||||
| **Config/Config.cpp/h** | INI file management, settings loading/saving, MRU list |
|
||||
| **Encoding.c/h** | Encoding detection and conversion (integrates uchardet) |
|
||||
| **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 |
|
||||
| `Notepad3.c/h` | `wWinMain`, `MainWndProc`, global state structs (`Globals`, `Settings`, `Settings2`, `Flags`, `Paths`), `MsgCommand()` dispatcher |
|
||||
| `Notepad3Util.c/h` | Image/toolbar helpers, word-wrap config, middle-click auto-scroll |
|
||||
| `Edit.c/h` | Find/replace (PCRE2), encoding, clipboard, indent, sort, bookmarks, folding, autocomplete |
|
||||
| `Styles.c/h` | Scintilla styling, lexer selection, themes, margins |
|
||||
| `Dialogs.c/h` | Dialogs, DPI-aware UI, window placement |
|
||||
| `Config/Config.cpp/h` | INI load/save, MRU |
|
||||
| `Encoding.c/h` | Encoding detection/conversion (wraps uchardet) |
|
||||
| `SciCall.h` | Type-safe wrappers for Scintilla direct calls (avoid `SendMessage`) |
|
||||
| `DynStrg.c/h` | `HSTRINGW` dynamic wide-string handle |
|
||||
| `PathLib.c/h` | `HPATHL` path handle + long-path-aware Win32 wrappers |
|
||||
| `TypeDefs.h` | `DocPos`, `DocLn`, `cpi_enc_t`, OS targeting, compiler macros |
|
||||
| `MuiLanguage.c/h` | MUI language DLL loading |
|
||||
| `StyleLexers\styleLexXXX.c` | Per-language `EDITLEXER` definitions (~50 files) |
|
||||
|
||||
### Window Hierarchy
|
||||
### Menu / Command Dispatch
|
||||
|
||||
```
|
||||
MainWndProc (Notepad3.c)
|
||||
+-- Scintilla Edit Control (hwndEdit / IDC_EDIT)
|
||||
+-- Toolbar (via Rebar control)
|
||||
+-- Status Bar (16 configurable fields)
|
||||
```
|
||||
|
||||
### Menu / Command Architecture
|
||||
|
||||
Menu items are defined in resource files (`language/np3_*/menu_*.rc`). Command handling in `Notepad3.c` is structured as:
|
||||
|
||||
- **`MsgInitMenu()`** — `WM_INITMENU` handler; enables/disables/checks menu items based on current state
|
||||
- **`MsgCommand()`** — `WM_COMMAND` thin dispatcher; handles timer/notification cases inline, then delegates to static sub-handlers:
|
||||
- `MsgInitMenu()` (`WM_INITMENU`) — enable/check state.
|
||||
- `MsgCommand()` (`WM_COMMAND`) — thin dispatcher delegating to static sub-handlers, each returning `true` if it handled:
|
||||
|
||||
| Handler | Scope |
|
||||
|---------|-------|
|
||||
| `_HandleFileCommands` | File open/save/print/favorites (`IDM_FILE_*`) |
|
||||
| `_HandleEncodingCommands` | Encoding & line endings (`IDM_ENCODING_*`, `IDM_LINEENDINGS_*`) |
|
||||
| `_HandleEditBasicCommands` | Undo/redo/cut/copy/paste/indent (`IDM_EDIT_UNDO`..`CMD_VK_INSERT`) |
|
||||
| `_HandleEditLineManipulation` | Line modify/sort/join/case (`IDM_EDIT_ENCLOSESELECTION`..`IDM_EDIT_INSERT_GUID`) |
|
||||
| `_HandleEditTextTransform` | Comments/URL encode/escape/hex (`IDM_EDIT_LINECOMMENT`..`IDM_EDIT_HEX2CHAR`) |
|
||||
| `_HandleEditFind` | Find/replace/bookmarks/goto (`IDM_EDIT_FINDMATCHINGBRACE`..`IDM_EDIT_GOTOLINE`) |
|
||||
| `_HandleViewAndSettingsCommands` | View/settings/rendering (`IDM_VIEW_*`, `IDM_SET_*`) |
|
||||
| `_HandleHelpCommands` | Help/about (`IDM_HELP_*`) |
|
||||
| `_HandleCmdCommands` | Keyboard shortcuts/navigation/window positioning (`CMD_*`) |
|
||||
| `_HandleToolbarCommands` | Toolbar button dispatch via `s_ToolbarDispatch[]` table (`IDT_*`) |
|
||||
| `_HandleFileCommands` | `IDM_FILE_*` |
|
||||
| `_HandleEncodingCommands` | `IDM_ENCODING_*`, `IDM_LINEENDINGS_*` |
|
||||
| `_HandleEditBasicCommands` | `IDM_EDIT_UNDO`..`CMD_VK_INSERT` |
|
||||
| `_HandleEditLineManipulation` | `IDM_EDIT_ENCLOSESELECTION`..`IDM_EDIT_INSERT_GUID` |
|
||||
| `_HandleEditTextTransform` | `IDM_EDIT_LINECOMMENT`..`IDM_EDIT_HEX2CHAR` |
|
||||
| `_HandleEditFind` | `IDM_EDIT_FINDMATCHINGBRACE`..`IDM_EDIT_GOTOLINE` |
|
||||
| `_HandleViewAndSettingsCommands` | `IDM_VIEW_*`, `IDM_SET_*` |
|
||||
| `_HandleHelpCommands` | `IDM_HELP_*` |
|
||||
| `_HandleCmdCommands` | `CMD_*` |
|
||||
| `_HandleToolbarCommands` | `IDT_*` via `s_ToolbarDispatch[]` |
|
||||
|
||||
Each handler returns `true` if it handled the command, `false` to try the next. All handlers are `static` in `Notepad3.c`.
|
||||
### Clipboard Monitoring (Pasteboard Mode)
|
||||
|
||||
### Vendored Dependencies
|
||||
Runtime-toggleable; external clipboard changes are pasted at the caret.
|
||||
|
||||
| Directory | Library | Purpose |
|
||||
|-----------|---------|---------|
|
||||
| `scintilla\` | Scintilla 5.5.8 | Editor component (NP3 patches in `np3_patches\`, docs in `doc\`) |
|
||||
| `lexilla\` | Lexilla 5.4.6 | Syntax highlighting (NP3 patches in `np3_patches\`, docs in `doc\`) |
|
||||
| `scintilla\pcre2\` | PCRE2 10.47 | Regex engine for find/replace (replaced archived Oniguruma) |
|
||||
| `src\uchardet\` | uchardet | Mozilla encoding detection |
|
||||
| `src\tinyexpr\` / `src\tinyexprcpp\` | TinyExpr | Expression evaluator (statusbar) |
|
||||
| `src\uthash\` | uthash | Hash table / dynamic array macros |
|
||||
| `src\crypto\` | Rijndael/SHA-256 | AES-256 encryption |
|
||||
- Menu: `IDM_EDIT_TOGGLE_PASTEBOARD` — "Toggle Clipboard Monitoring". Check mark reflects state.
|
||||
- Helpers `PasteBoard_Start(HWND)` / `PasteBoard_Stop(HWND)` wrap `AddClipboardFormatListener` + `ID_PASTEBOARDTIMER`. Used at startup for `/B` and from the toggle handler.
|
||||
- **Not persisted** — always OFF at startup unless `/B`.
|
||||
- **Mutex with Tail** (`IDM_VIEW_CHASING_DOCTAIL` / `FileWatching.MonitoringLog`): each mode greys the other in `MsgInitMenu`; tail toolbar button (`IDT_VIEW_CHASING_DOCTAIL`) also greyed; "Monitoring Log" checkbox in `ChangeNotifyDlgProc` greyed while pasteboard active. `IsPasteBoardActive()` exposed in `Notepad3.h` for cross-TU use.
|
||||
- Startup conflict (`/B` + persisted `MonitoringLog=true`): `/B` wins this session; `FileWatching.MonitoringLog` cleared in memory, `Settings.MonitoringLog` (INI) preserved.
|
||||
- `PasteBoardTimerProc` pastes at current caret. `Settings2.PasteBoardSeparator` pre-pended before each new entry; suppressed on (1) first paste after enable and (2) caret at line start. `\x01` = one document EOL; `\0` = no separator.
|
||||
- Status bar `STATUS_OVRMODE` shows `CBS` while active (passive indicator).
|
||||
|
||||
### grepWin Integration (`grepWin\`)
|
||||
## Vendored Libraries
|
||||
|
||||
grepWin is an **external** file search/grep tool — it is **not** built from source as part of Notepad3. Pre-built portable executables and translation files are stored in the repository:
|
||||
`scintilla\` (5.5.8), `lexilla\` (5.4.6), `scintilla\pcre2\` (PCRE2 10.47), `src\uchardet\`, `src\tinyexpr\` / `src\tinyexprcpp\`, `src\uthash\`, `src\crypto\` (Rijndael/SHA-256). NP3 patches under each `np3_patches\`; offline docs under `scintilla\doc\` and `lexilla\doc\`.
|
||||
|
||||
- **`grepWin\portables\`** — `grepWin-x86_portable.exe`, `grepWin-x64_portable.exe`, `LICENSE.txt`
|
||||
- **`grepWin\translations\`** — `*.lang` translation files (e.g. `German.lang`, `French.lang`)
|
||||
## grepWin Integration (`grepWin\`)
|
||||
|
||||
At runtime (`src\Dialogs.c`), Notepad3 searches for grepWin in this order:
|
||||
1. `Settings2.GrepWinPath` (user-configured INI setting)
|
||||
2. `<ModuleDirectory>\grepWin\grepWin-x64_portable.exe` (or x86) — portable layout
|
||||
3. `%APPDATA%\Rizonesoft\Notepad3\grepWin\` — installed layout
|
||||
External tool, **not built from source**. Pre-built exes under `grepWin\portables\`; `.lang` files under `grepWin\translations\`. Runtime lookup order (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 → `.lang` filenames. ARM64 uses the x64 exe via emulation.
|
||||
|
||||
Language mapping (`src\MuiLanguage.c`): `grepWinLangResName[]` maps Notepad3 locale names (e.g. `de-DE`) to grepWin `.lang` filenames (e.g. `German.lang`). The language file path is written to `grepwin.ini` before launching.
|
||||
## Adding a Lexer
|
||||
|
||||
Portable build scripts (`Build\make_portable_*.cmd`) package grepWin into a `grepWin\` subdirectory in the archive containing both portable executables, the license, and all `*.lang` translations.
|
||||
`styleLexXXX.c` defines an `EDITLEXER` (see any existing file for the struct), register in the `Styles.c` lexer array, add localization string IDs.
|
||||
|
||||
### Syntax Lexers (`src\StyleLexers\`)
|
||||
## Localization
|
||||
|
||||
50+ languages, each in a `styleLexXXX.c` file. All follow the `EDITLEXER` struct pattern from `EditLexer.h`:
|
||||
- 26 locales under `language\np3_LANG_COUNTRY\`. Language DLLs are separate projects in the solution.
|
||||
- **`.rc` files are UTF-8 WITHOUT BOM, CRLF line endings.** Never write with BOM. Use `Build\rc_to_utf8.cmd` to strip accidental BOMs. In PowerShell use `[System.Text.UTF8Encoding]::new($false)`; in Python write `\r\n` explicitly.
|
||||
- **`Build\Notepad3.ini`, `Build\minipath.ini` are UTF-8 WITH BOM** (`EF BB BF`). Preserve it.
|
||||
|
||||
```c
|
||||
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 */ }
|
||||
};
|
||||
```
|
||||
### Adding a string resource
|
||||
|
||||
To add a new lexer: create `styleLexNEW.c`, define the `EDITLEXER` struct, register in `Styles.c` lexer array, add localization string IDs to resource files.
|
||||
1. `#define IDS_MUI_XXX <id>` in `language\common_res.h` (13xxx errors/warnings, 14xxx info/prompts).
|
||||
2. Add the English string to `language\np3_en_us\strings_en_us.rc`.
|
||||
3. Add the same English text as placeholder to all other 25 locale files (translators update later).
|
||||
4. Display via `InfoBoxLng()` / `MessageBoxLng()`; check with `IsYesOkay()`. `Settings.MuteMessageBeep` controls silent vs. sound — provide both paths.
|
||||
|
||||
### Localization (`language\`)
|
||||
## Portable INI Design
|
||||
|
||||
Resource-based MUI system with 27+ locales. Each locale has a `np3_LANG_COUNTRY\` directory with `.rc` files. Language packs are built as separate DLLs (separate projects in the solution).
|
||||
- INI sits beside the exe. No registry. Runs on defaults if no INI exists (user creates via "Save Settings Now").
|
||||
- **Admin redirect**: `Notepad3.ini=<path>` in `[Notepad3]` redirects to a per-user path (up to 2 levels). Redirect targets ARE auto-created via `CreateIniFileEx()`.
|
||||
- `Paths.IniFile` = active writable INI; `Paths.IniFileDefault` = recovery fallback.
|
||||
- Init flow: `FindIniFile()` → `TestIniFile()` → `CreateIniFile()` → `LoadSettings()`.
|
||||
- **`SettingsVersion` defaults to `CFG_VER_CURRENT`** when missing — empty/new INI gets current defaults, not legacy treatment.
|
||||
- **`bIniFileFromScratch`** is set when INI is 0 bytes, cleared after `SaveAllSettings()`. While set, `MuiLanguage.c` suppresses writing `PreferredLanguageLocaleName` to keep fresh INIs clean.
|
||||
- **Empty lexer sections are pruned** in `Style_ToIniSection()` via `IniSectionGetKeyCount()` after `Style_CanonicalSectionToIniCache()` establishes order — only sections with non-default styles persist.
|
||||
- MiniPath follows the same pattern (`minipath\src\Config.cpp`).
|
||||
- **New `Settings2` (or other INI) params must be documented as commented entries in `Build\Notepad3.ini`.**
|
||||
|
||||
### Adding String Resources
|
||||
### Save/Load macros (`src\Config\Config.cpp`)
|
||||
|
||||
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
|
||||
- **`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). Passing CPI with `bLoad=true` produces wrong results. Reference: `MRU_Save()` uses `false`.
|
||||
- **Defaults that depend on `DefaultEncoding`** (e.g. `SkipANSICodePageDetection`, `LoadASCIIasUTF8`) must be recalculated in `_SaveSettings()` before `SAVE_VALUE_IF_NOT_EQ_DEFAULT` fires, since encoding can change at runtime. See the `bCurrentEncUTF8` block.
|
||||
|
||||
### Configuration / Portable Design
|
||||
## File I/O
|
||||
|
||||
- INI file alongside executable (`Notepad3.ini`), no registry usage
|
||||
- **No auto-creation**: runs with defaults if no INI exists; user explicitly creates via "Save Settings Now"
|
||||
- **Admin redirect**: `Notepad3.ini=<path>` in `[Notepad3]` section redirects to per-user path (up to 2 levels)
|
||||
- Key paths: `Paths.IniFile` (active writable INI), `Paths.IniFileDefault` (fallback for recovery)
|
||||
- INI init flow: `FindIniFile()` -> `TestIniFile()` -> `CreateIniFile()` -> `LoadSettings()`
|
||||
- **SettingsVersion default**: `SettingsVersion` defaults to `CFG_VER_CURRENT` when missing from INI — an INI file without `SettingsVersion` is NOT treated as a legacy (pre-versioning) file. This ensures empty INI files and newly created INI files both get current defaults.
|
||||
- **`bIniFileFromScratch`**: Set when INI file size is 0 bytes. Cleared after `SaveAllSettings()` completes. While true, `MuiLanguage.c` suppresses writing `PreferredLanguageLocaleName` to avoid polluting fresh INI files.
|
||||
- **Style section saving**: `Style_ToIniSection()` removes empty lexer sections after processing via `IniSectionGetKeyCount()`. `Style_CanonicalSectionToIniCache()` establishes canonical section order but empty sections are cleaned up — only sections with non-default style values appear in the saved INI.
|
||||
- **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`
|
||||
- `FileSave()` / `FileLoad()` (in `Notepad3.c`) → `FileIO()` → `EditSaveFile()` / `EditLoadFile()` (in `Edit.c`).
|
||||
- Atomic save via temp file + `ReplaceFileW` controlled by `Settings2.AtomicFileSave`.
|
||||
- Error codes land in `Globals.dwLastError`; check `ERROR_ACCESS_DENIED`, `ERROR_PATH_NOT_FOUND` before falling back to generic error.
|
||||
- **File watching** (`InstallFileWatching()`) uses `FindFirstChangeNotificationW` on the parent directory. Must `InstallFileWatching(false)` before save and `InstallFileWatching(true)` after.
|
||||
|
||||
### Settings Save/Load Macros (`src\Config\Config.cpp`)
|
||||
## Long-Path / PathLib wrappers
|
||||
|
||||
**`Encoding_MapIniSetting` direction** — CPI constants (`CPI_UTF8=6`, `CPI_OEM=1`, etc.) do NOT equal their INI storage values (`3`, `5`, etc.). The mapping is asymmetric:
|
||||
- `Encoding_MapIniSetting(true, val)` = INI integer → CPI constant (**loading**)
|
||||
- `Encoding_MapIniSetting(false, val)` = CPI constant → INI integer (**saving**)
|
||||
Never call Win32 file APIs directly with `Path_Get(hpth)`. Use the PathLib wrappers — they apply the `\\?\` prefix conditionally (only when `RtlAreLongPathsEnabled()` is false AND path ≥ 260 chars):
|
||||
|
||||
Passing a CPI value with `bLoad=true` produces wrong results. Reference: `MRU_Save()` correctly uses `false` for saving.
|
||||
| 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 |
|
||||
|
||||
**Conditional defaults and `SAVE_VALUE_IF_NOT_EQ_DEFAULT`** — Some settings have defaults that depend on `DefaultEncoding` (e.g., `SkipANSICodePageDetection`, `LoadASCIIasUTF8`). If the encoding can change at runtime (via the Encoding dialog), the dependent `Defaults.*` fields must be recalculated in `_SaveSettings()` before the comparison macros fire — otherwise stale defaults cause incorrect save/delete decisions. See the `bCurrentEncUTF8` block in `_SaveSettings()`.
|
||||
### Path comparison
|
||||
|
||||
### Creating Directories
|
||||
Use `Path_StrgComparePath()` (supports normalization; `CompareStringOrdinal` under the hood — locale-independent, case-insensitive). For raw wide strings use `CompareStringOrdinal(s1, -1, s2, -1, TRUE)`; **never** `_wcsicmp` / `_wcsnicmp` (locale-dependent).
|
||||
|
||||
Use `Path_CreateDirectoryEx(hpth)` (PathLib wrapper around `SHCreateDirectoryExW`) to recursively create directory trees. Check result: `SUCCEEDED(hr) || (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS))`. See `CreateIniFile()` in `src\Config\Config.cpp` for the reference pattern.
|
||||
### Creating directories
|
||||
|
||||
### Long-Path Support / Win32 File API Wrappers
|
||||
Use `Path_CreateDirectoryEx(hpth)`. Success: `SUCCEEDED(hr) || (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS))`. Reference pattern in `CreateIniFile()` (`Config.cpp`).
|
||||
|
||||
Notepad3 supports paths exceeding `MAX_PATH` (260 chars) on systems without the Windows 10 long-path registry opt-in by conditionally prepending the `\\?\` prefix. The static function `PrependLongPathPrefix()` in PathLib.c handles this.
|
||||
## PCRE2
|
||||
|
||||
**Rule: Never call Win32 file APIs directly with `Path_Get(hpth)`.** Use the PathLib wrapper functions instead — they internally apply the copy-prefix-call-release pattern:
|
||||
`scintilla\pcre2\scintilla\PCRE2RegExEngine.cxx` compiled with `SCI_OWNREGEX` overrides Scintilla's built-in regex. Entry points: `FindText`, `SubstituteByPosition`, `convertReplExpr` (normalizes `\1`-`\9`→`$1`-`$9`, processes `\n\t\xHH\uHHHH`), `translateRegExpr` (`\<`/`\>` → lookarounds, `\uHHHH` → `\x{HHHH}`). Standalone `RegExFind` (exported C) used by `EditURLDecode`.
|
||||
|
||||
| Wrapper | Win32 API |
|
||||
|---------|-----------|
|
||||
| `Path_CreateFile(hpth, ...)` | `CreateFileW` |
|
||||
| `Path_DeleteFile(hpth)` | `DeleteFileW` |
|
||||
| `Path_GetFileAttributes(hpth)` | `GetFileAttributesW` |
|
||||
| `Path_GetFileAttributesEx(hpth, ...)` | `GetFileAttributesExW` |
|
||||
| `Path_SetFileAttributes(hpth, ...)` | `SetFileAttributesW` |
|
||||
| `Path_ReplaceFile(hpth_dest, src)` | `ReplaceFileW` |
|
||||
| `Path_MoveFileEx(src, hpth_dest, ...)` | `MoveFileExW` |
|
||||
| `Path_FindFirstFile(hpth, ...)` | `FindFirstFileW` |
|
||||
| `Path_CreateDirectoryEx(hpth)` | `SHCreateDirectoryExW` |
|
||||
| `Path_IsExistingFile(hpth)` | `GetFileAttributesW` + check |
|
||||
| `Path_IsExistingDirectory(hpth)` | `GetFileAttributesW` + check |
|
||||
Replacement backref syntax: `$0`-`$99`, `\0`-`\9`, `${name}`, `${+name}`.
|
||||
|
||||
The prefix is only added when `RtlAreLongPathsEnabled()` returns false AND the path is ≥ 260 chars. On modern Windows 10+ with the opt-in, these wrappers are effectively pass-through.
|
||||
URL hotspot regex: `HYPLNK_REGEX_FULL` macro in `src\Edit.c` — matches `https?://`, `ftp://`, `file:///`, `file://`, `mailto:`, `www.`, `ftp.`. Trailing group excludes `.,:?!` so URLs don't absorb sentence punctuation.
|
||||
|
||||
### Path Comparison
|
||||
## DarkMode
|
||||
|
||||
Use `Path_StrgComparePath()` for comparing file paths — it supports optional normalization and uses `CompareStringOrdinal` (locale-independent, case-insensitive). For raw wide-string path comparison, use `CompareStringOrdinal(s1, -1, s2, -1, TRUE)` instead of `_wcsicmp` or `_wcsnicmp` which are locale-dependent.
|
||||
`src\DarkMode\` — Windows 10/11 dark mode via IAT hooks on uxtheme/user32 (stub DLLs included).
|
||||
|
||||
### File I/O Flow
|
||||
## ARM64
|
||||
|
||||
- **`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)`).
|
||||
Supported: Win32 (x86), x64, x64_AVX2, ARM64. **ARM 32-bit is not supported** — the `Release|ARM` solution config maps to Win32.
|
||||
|
||||
### PCRE2 Regex Engine (`scintilla\pcre2\`)
|
||||
- Both ARM64 and x64 define `_WIN64`. Use `_M_ARM64` (or helper `NP3_BUILD_ARM64` in `TypeDefs.h`) to distinguish.
|
||||
- Rendering default: `SC_TECHNOLOGY_DIRECTWRITERETAIN` (2) instead of `SC_TECHNOLOGY_DIRECTWRITE` (1) — preserves Direct2D back buffer, avoids flicker on Qualcomm Adreno + Win11 25H2 DWM. Main window also gets `WS_EX_COMPOSITED`. User can override via `RenderingTechnology` / View menu.
|
||||
- Build config: `CETCompat=false` (CET is x86/x64 only), `TargetMachine=MachineARM64`, `_WIN64` defined. Fix scripts: `Build\scripts\FixARM64{CETCompat,CrossCompile,OutDir}.ps1`.
|
||||
- grepWin: no native ARM64 — uses x64 exe via emulation (`#if defined(_M_ARM64)` in `Notepad3.c`).
|
||||
- `MsgThemeChanged()` wraps bar recreate / lexer reset / restyle in `WM_SETREDRAW FALSE/TRUE` and does a single `RedrawWindow()` at end. DarkMode `RedrawWindow()` in `ListViewUtil.hpp` omits `RDW_ERASE` to avoid background flash.
|
||||
|
||||
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.
|
||||
## Conventions
|
||||
|
||||
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`
|
||||
- **Formatting**: LLVM-based `.clang-format` in `src\` — 4-space indent, Stroustrup braces, left-aligned pointers, no column limit, no include sorting. `.editorconfig` enforces UTF-8/CRLF; Lexilla code uses tabs (upstream preserved).
|
||||
- **Strings**: `strsafe.h` throughout; deprecated string functions disabled.
|
||||
- **Types**: `DocPos` / `DocPosU` / `DocLn` (not raw `int`). `cpi_enc_t` for encodings. `HSTRINGW` / `HPATHL` (opaque) instead of raw `WCHAR*`. `NOMINMAX` is global — use `min()`/`max()` or typed equivalents.
|
||||
- **Scintilla**: always use `SciCall.h` wrappers. 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_`.
|
||||
- **Global state**: use existing structs (`Globals`, `Settings`, `Settings2`, `Flags`, `Paths`); don't add new globals.
|
||||
- **Undo/redo**: use `_BEGIN_UNDO_ACTION_` / `_END_UNDO_ACTION_` macros (`Notepad3.h`) for grouping; they also throttle notifications during bulk edits.
|
||||
|
||||
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`
|
||||
### WriteAccessBuf — dangling pointer anti-pattern
|
||||
|
||||
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.
|
||||
**NEVER** use a pointer from `Path_WriteAccessBuf()` / `StrgWriteAccessBuf()` after any operation that may reallocate/swap the underlying buffer of the SAME handle. Buffer-invalidating ops include: `Path_CanonicalizeEx`, `Path_Swap` / `StrgSwap`, `Path_ExpandEnvStrings` / `ExpandEnvironmentStrgs`, `Path_Append` / `Path_Reset`, `StrgCat` / `StrgInsert` / `StrgFormat` / `StrgReset`, `Path_NormalizeEx` / `Path_AbsoluteFromApp` / `Path_RelativeToApp`.
|
||||
|
||||
### DarkMode (`src\DarkMode\`)
|
||||
|
||||
Windows 10/11 dark mode via IAT (Import Address Table) hooks. Includes stub DLLs for uxtheme and user32.
|
||||
|
||||
### ARM64 Platform Considerations
|
||||
|
||||
**Supported platforms**: Win32 (x86), x64, x64_AVX2, ARM64. ARM 32-bit is **not** supported (the `Release|ARM` solution config maps to Win32).
|
||||
|
||||
#### Architecture detection
|
||||
|
||||
Use `#if defined(_M_ARM64)` or the helper macro `NP3_BUILD_ARM64` (defined in `src\TypeDefs.h`) for ARM64-specific code paths. **Important**: both ARM64 and x64 define `_WIN64`, so use `_M_ARM64` when you need to distinguish ARM64 from x64.
|
||||
|
||||
#### ARM64 rendering defaults
|
||||
|
||||
ARM64 defaults to `SC_TECHNOLOGY_DIRECTWRITERETAIN` (value 2) instead of `SC_TECHNOLOGY_DIRECTWRITE` (value 1) to preserve the Direct2D back buffer between frames. This avoids flickering on Qualcomm Adreno GPUs and the Win11 25H2 DWM compositor. The main window also uses `WS_EX_COMPOSITED` on ARM64 for system-level double-buffering. Users can override via `RenderingTechnology` in the INI file or the View menu.
|
||||
|
||||
#### ARM64 build configuration
|
||||
|
||||
- `CETCompat` must be `false` for ARM64 (CET is x86/x64 only)
|
||||
- `TargetMachine` must be `MachineARM64` in all ARM64 linker sections
|
||||
- `_WIN64` must be defined in preprocessor definitions for all ARM64 configurations
|
||||
- Build fix scripts in `Build\scripts\`: `FixARM64CETCompat.ps1`, `FixARM64CrossCompile.ps1`, `FixARM64OutDir.ps1`
|
||||
|
||||
#### GrepWin on ARM64
|
||||
|
||||
No native ARM64 grepWin build exists. The ARM64 build uses `grepWin-x64_portable.exe` which runs via x64 emulation on Windows ARM64. The binary selection in `src\Notepad3.c` uses `#if defined(_M_ARM64)` to handle this explicitly.
|
||||
|
||||
#### Theme change flickering prevention
|
||||
|
||||
`MsgThemeChanged()` in `src\Notepad3.c` wraps all heavy operations (bar recreation, lexer reset, restyling) in `WM_SETREDRAW FALSE/TRUE` to suppress intermediate repaints and performs a single `RedrawWindow()` at the end. DarkMode `RedrawWindow()` calls in `ListViewUtil.hpp` omit `RDW_ERASE` to avoid background erase flashes during theme transitions.
|
||||
|
||||
### Formatting
|
||||
|
||||
- LLVM-based `.clang-format` in `src\` — 4-space indentation, Stroustrup brace style, left-aligned pointers, no column limit, no include sorting
|
||||
- `.editorconfig` enforces UTF-8/CRLF for source, 4-space indent for C/C++; Lexilla code uses tabs (preserved from upstream)
|
||||
- **File encoding rules** (must be respected when creating or editing these files):
|
||||
- `language\*\*.rc` — **UTF-8 without BOM**. Never write or save these files with a UTF-8 BOM. Use `Build\rc_to_utf8.cmd` to strip accidental BOMs.
|
||||
- **PowerShell pitfall**: `[System.Text.Encoding]::UTF8` writes **with** BOM. Always use `[System.Text.UTF8Encoding]::new($false)` when writing `.rc` files from PowerShell scripts.
|
||||
- **Python/script pitfall**: `.rc` files use **CRLF** line endings. When writing or inserting lines from Python or other scripts, use `\r\n` — not bare `\n`. After bulk edits, normalize with: `content = content.replace('\r\n', '\n').replace('\n', '\r\n')` before writing.
|
||||
- `Build\Notepad3.ini`, `Build\minipath.ini` — **UTF-8 with BOM** (BOM = `EF BB BF`). These INI reference files must retain the BOM.
|
||||
- String safety via `strsafe.h` throughout; deprecated string functions are disabled
|
||||
|
||||
### Type Conventions
|
||||
|
||||
- `DocPos` / `DocPosU` / `DocLn` for Scintilla document positions and line numbers (not raw `int`)
|
||||
- `cpi_enc_t` for encoding identifiers
|
||||
- `HSTRINGW` and `HPATHL` (opaque handle types) instead of raw `WCHAR*` buffers
|
||||
- `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, ...)`. Add missing wrappers to `SciCall.h` if needed.
|
||||
|
||||
Wrapper macros follow the naming `DeclareSciCall{V|R}{0|01|1|2}`:
|
||||
- **V** = void return, **R** = has return value
|
||||
- **0** = no params, **1** = one param (wParam), **2** = two params, **01** = lParam only (wParam=0)
|
||||
- The `msg` argument is the suffix after `SCI_` (e.g. `UNDO` for `SCI_UNDO`)
|
||||
|
||||
```c
|
||||
DeclareSciCallV0(Undo, UNDO); // SciCall_Undo()
|
||||
DeclareSciCallR0(GetTextLength, GETTEXTLENGTH, DocPos); // DocPos SciCall_GetTextLength()
|
||||
DeclareSciCallV1(SetTechnology, SETTECHNOLOGY, int, technology); // SciCall_SetTechnology(int)
|
||||
DeclareSciCallR1(SupportsFeature, SUPPORTSFEATURE, bool, int, feature); // bool SciCall_SupportsFeature(int)
|
||||
```
|
||||
|
||||
### Global State
|
||||
|
||||
Application state is centralized in global structs (`Globals`, `Settings`, `Settings2`, `Flags`, `Paths`) defined in `Notepad3.c`. Access these through their defined interfaces rather than adding new globals.
|
||||
|
||||
### 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.
|
||||
|
||||
### WriteAccessBuf — Dangling Pointer Anti-Pattern
|
||||
|
||||
**NEVER use a pointer obtained from `Path_WriteAccessBuf()` / `StrgWriteAccessBuf()` after ANY operation that may reallocate or swap the underlying buffer of the SAME handle.** The pointer becomes dangling (use-after-free).
|
||||
|
||||
Buffer-invalidating operations:
|
||||
- `Path_CanonicalizeEx(h, ...)` — calls `Path_Swap` internally
|
||||
- `Path_Swap(h, ...)` / `StrgSwap(h, ...)`
|
||||
- `Path_ExpandEnvStrings(h)` / `ExpandEnvironmentStrgs(h, ...)` — may realloc
|
||||
- `Path_Append(h, ...)` / `Path_Reset(h, ...)` — may realloc
|
||||
- `StrgCat(h, ...)` / `StrgInsert(h, ...)` / `StrgFormat(h, ...)` / `StrgReset(h, ...)` — may realloc
|
||||
- `Path_NormalizeEx(h, ...)` / `Path_AbsoluteFromApp(h, ...)` / `Path_RelativeToApp(h, ...)` — may realloc/swap
|
||||
|
||||
Safe patterns after invalidation:
|
||||
- **Read-only**: use `Path_Get(h)` or `StrgGet(h)` — always returns current buffer
|
||||
- **Read-write**: re-obtain via `ptr = Path_WriteAccessBuf(h, 0)` / `ptr = StrgWriteAccessBuf(h, 0)` (size 0 = no resize, just returns current pointer)
|
||||
After any of these:
|
||||
- Read-only: use `Path_Get(h)` / `StrgGet(h)` (always current).
|
||||
- Read-write: re-obtain via `Path_WriteAccessBuf(h, 0)` / `StrgWriteAccessBuf(h, 0)` (size 0 = no resize, returns current pointer).
|
||||
|
||||
## Python Environment
|
||||
|
||||
A Python 3.14 virtual environment is available at `.venv\` for scripting tasks (batch file manipulation, locale file updates, code generation, etc.).
|
||||
`.venv\` (Python 3.14) for scripting tasks — bulk locale edits, code generation, etc.
|
||||
|
||||
```bash
|
||||
# Run a script (from project root, in bash/Cygwin)
|
||||
.venv/Scripts/python.exe <script.py>
|
||||
|
||||
# Install packages
|
||||
.venv/Scripts/pip.exe install <package>
|
||||
```
|
||||
|
||||
Use this venv instead of system Python (which may not be installed). Useful for bulk operations across the 26 locale `strings_*.rc` files.
|
||||
|
||||
## Key File Paths (Quick Reference)
|
||||
|
||||
| Pattern | Purpose |
|
||||
|---------|---------|
|
||||
| `language/common_res.h` | String resource ID definitions |
|
||||
| `language/np3_*/strings_*.rc` | Locale string tables (26 files) |
|
||||
| `Build/Notepad3.ini` | Reference INI with documented settings |
|
||||
| `src/Notepad3.c:FileSave()` | Save flow entry point |
|
||||
| `src/Edit.c:EditSaveFile()` | Actual file write logic |
|
||||
| `src/Config/Config.cpp` | INI management, settings load/save |
|
||||
| `src/Dialogs.c:SaveFileDlg()` | Save As dialog |
|
||||
System Python is not installed; `python3` fails. Python beats sed/perl under Cygwin for literal string insertions (reliable `\r\n`).
|
||||
|
||||
@ -873,7 +873,7 @@
|
||||
#define IDM_TRAY_EXIT 43701
|
||||
#define IDM_SETPASS 43702
|
||||
#define IDM_EDIT_INSERT_GUID 43705
|
||||
#define IDM_EDIT_STOP_PASTEBOARD 43706
|
||||
#define IDM_EDIT_TOGGLE_PASTEBOARD 43706
|
||||
|
||||
|
||||
#define IDS_ENC_ANSI 61000
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "&Verwyder\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "Maak &Klipbord Skoon", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "Stop Clipboard &Monitoring", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "Toggle Clipboard &Monitoring", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "W&oorde"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "В&ыдаліць\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "А&чысціць буфер абмену", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "Stop Clipboard &Monitoring", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "Toggle Clipboard &Monitoring", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "Словы"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "&Löschen\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "&Lösche Zwischenablage", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "Zwischenablage &Überwachung abbrechen", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "Zwischenablage &Überwachung umschalten", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "&Wörter"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "&Διαγραφή\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "&Απαλοιφή πρόχειρου", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "Stop Clipboard &Monitoring", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "Toggle Clipboard &Monitoring", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "&Λέξεις"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "&Delete\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "Cl&ear Clipboard", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "Stop Clipboard &Monitoring", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "Toggle Clipboard &Monitoring", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "W&ords"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "&Delete\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "Cl&ear Clipboard", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "Stop Clipboard &Monitoring", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "Toggle Clipboard &Monitoring", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "W&ords"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "E&liminar\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "&Vaciar el portapapeles", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "Stop Clipboard &Monitoring", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "Toggle Clipboard &Monitoring", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "P&alabras"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "&Poista\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "Tyhj&ennä leikepöytä", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "Stop Clipboard &Monitoring", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "Toggle Clipboard &Monitoring", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "S&anat"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "&Supprimer\tSuppr", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "Vi&der le presse-papiers", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "Stop Clipboard &Monitoring", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "Toggle Clipboard &Monitoring", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "M&ots"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "हटाएं (&D)\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "क्लिपबोर्ड साफ़ करें (&E)", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "Stop Clipboard &Monitoring", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "Toggle Clipboard &Monitoring", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "शब्द (&O)"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "&Törlés\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "Vágólap t&örlése", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "&Vágólapfigyelés leállítása", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "&Vágólapfigyelés leállítása", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "Sza&vak"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "Hap&us\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "Bersi&hkan Papan Klip", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "Stop Clipboard &Monitoring", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "Toggle Clipboard &Monitoring", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "Ka&ta"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "&Elimina\tCanc", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "Svuota appunti", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "Stop &monitoraggio appunti", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "Stop &monitoraggio appunti", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "&Parole"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "削除(&D)\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "クリップボードをクリア(&E)", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "Stop Clipboard &Monitoring", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "Toggle Clipboard &Monitoring", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "単語(&O)"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "삭제(&D)\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "클립보드 지우기(&E)", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "클립보드 모니터링 중지(&M)", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "클립보드 모니터링 중지(&M)", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "단어(&W)"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "&Wissen\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "&Klembord wissen", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "&Bewaking van klembord stoppen", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "&Bewaking van klembord omschakelen", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "W&oorden"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "&Usuń\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "Wyczyść schowek", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "Stop Clipboard &Monitoring", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "Toggle Clipboard &Monitoring", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "W&yrazy"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "&Apagar\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "L&impar Área de Transferência", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "Parar de &Monitorar Área de Transferência", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "Parar de &Monitorar Área de Transferência", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "P&alavras"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "&Eliminar\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "Limpar ár&ea de transferência", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "Stop Clipboard &Monitoring", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "Toggle Clipboard &Monitoring", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "Pala&vras"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "&Удалить\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "О&чистить буфер обмена", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "Stop Clipboard &Monitoring", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "Toggle Clipboard &Monitoring", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "Слова"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "Ta bort\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "Rensa urklipp", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "Stop Clipboard &Monitoring", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "Toggle Clipboard &Monitoring", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "Ord"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "&Sil\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "&Panoyu temizle", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "Stop Clipboard &Monitoring", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "Toggle Clipboard &Monitoring", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "&Sözcükler"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "Xóa(&D)\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "Xóa bộ nhớ tạm(&E)", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "Stop Clipboard &Monitoring", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "Toggle Clipboard &Monitoring", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "Từ(&W)"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "删除(&D)\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "清空剪贴板(&E)", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "停止监视剪贴板(&M)", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "停止监视剪贴板(&M)", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "单词(&O)"
|
||||
BEGIN
|
||||
|
||||
@ -169,7 +169,7 @@ BEGIN
|
||||
MENUITEM "刪除(&D)\tDel", IDM_EDIT_CLEAR
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "清空剪貼簿(&E)", IDM_EDIT_CLEARCLIPBOARD
|
||||
MENUITEM "停止監視剪貼簿(&M)", IDM_EDIT_STOP_PASTEBOARD
|
||||
MENUITEM "停止監視剪貼簿(&M)", IDM_EDIT_TOGGLE_PASTEBOARD
|
||||
MENUITEM SEPARATOR
|
||||
POPUP "單詞(&O)"
|
||||
BEGIN
|
||||
|
||||
@ -2661,6 +2661,12 @@ static INT_PTR CALLBACK ChangeNotifyDlgProc(HWND hwnd, UINT umsg, WPARAM wParam,
|
||||
CheckDlgButton(hwnd, IDC_CHECK_BOX_A, SetBtn(Settings.ResetFileWatching));
|
||||
CheckDlgButton(hwnd, IDC_CHECK_BOX_B, SetBtn(FileWatching.MonitoringLog));
|
||||
|
||||
// Mutually exclusive with Clipboard Monitoring — disable the
|
||||
// "Monitoring Log" checkbox while the pasteboard is active.
|
||||
if (IsPasteBoardActive()) {
|
||||
EnableItem(hwnd, IDC_CHECK_BOX_B, FALSE);
|
||||
}
|
||||
|
||||
if (FileWatching.MonitoringLog) {
|
||||
CheckRadioButton(hwnd, IDC_RADIO_BTN_A, IDC_RADIO_BTN_E, IDC_RADIO_BTN_C);
|
||||
EnableItem(hwnd, IDC_RADIO_BTN_A, FALSE);
|
||||
|
||||
116
src/Notepad3.c
116
src/Notepad3.c
@ -626,6 +626,7 @@ static bool s_flagStartAsTrayIcon = false;
|
||||
static bool s_flagKeepTitleExcerpt = false;
|
||||
static bool s_flagNewFromClipboard = false;
|
||||
static bool s_flagPasteBoard = false;
|
||||
static bool s_bPasteBoardFirstPaste = true;
|
||||
static bool s_flagJumpTo = false;
|
||||
static FILE_WATCHING_MODE s_flagChangeNotify = FWM_NO_INIT;
|
||||
static bool s_flagQuietCreate = false;
|
||||
@ -796,6 +797,47 @@ static void _InitGlobals()
|
||||
}
|
||||
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// PasteBoard_Start() / PasteBoard_Stop()
|
||||
//
|
||||
// Install / remove the clipboard-format listener and the polling timer used
|
||||
// by Clipboard Monitoring (a.k.a. "pasteboard mode"). State is process-local
|
||||
// only; it is never persisted to the INI. Mutually exclusive with Tail.
|
||||
//
|
||||
static void PasteBoard_Start(HWND hwnd)
|
||||
{
|
||||
if (s_flagPasteBoard) { return; }
|
||||
s_bPasteBoardListening = AddClipboardFormatListener(hwnd);
|
||||
if (s_bPasteBoardListening) {
|
||||
s_iLastCopyTime = 0;
|
||||
s_bLastPasteSeqNoValid = false;
|
||||
SetTimer(hwnd, ID_PASTEBOARDTIMER, 100, PasteBoardTimerProc);
|
||||
}
|
||||
s_flagPasteBoard = true;
|
||||
s_bPasteBoardFirstPaste = true;
|
||||
}
|
||||
|
||||
bool IsPasteBoardActive(void)
|
||||
{
|
||||
return s_flagPasteBoard;
|
||||
}
|
||||
|
||||
static void PasteBoard_Stop(HWND hwnd)
|
||||
{
|
||||
KillTimer(hwnd, ID_PASTEBOARDTIMER);
|
||||
if (s_bPasteBoardListening) {
|
||||
RemoveClipboardFormatListener(hwnd);
|
||||
s_bPasteBoardListening = false;
|
||||
}
|
||||
s_flagPasteBoard = false;
|
||||
s_iLastCopyTime = 0;
|
||||
s_bLastCopyFromMe = false;
|
||||
s_bLastPasteSeqNoValid = false;
|
||||
s_bPasteBoardFirstPaste = true;
|
||||
}
|
||||
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// _CleanUpResources()
|
||||
@ -1914,6 +1956,13 @@ HWND InitInstance(const HINSTANCE hInstance, int nCmdShow)
|
||||
// Restore saved Monitoring Log setting - fixes #5037
|
||||
FileWatching.MonitoringLog = Settings.MonitoringLog;
|
||||
|
||||
// /B (Clipboard Monitoring) and Tail are mutually exclusive. If both would
|
||||
// be active at startup, the command-line flag wins for this session only;
|
||||
// Settings.MonitoringLog stays untouched so the persisted value survives.
|
||||
if (s_flagPasteBoard && FileWatching.MonitoringLog) {
|
||||
FileWatching.MonitoringLog = false;
|
||||
}
|
||||
|
||||
// initial set text in front of ShowWindow()
|
||||
EditSetNewText(Globals.hwndEdit, "", 0, false, false);
|
||||
|
||||
@ -2097,12 +2146,11 @@ HWND InitInstance(const HINSTANCE hInstance, int nCmdShow)
|
||||
}
|
||||
|
||||
// Check for Paste Board option -- after loading files
|
||||
// /B command-line flag pre-set s_flagPasteBoard = true; the helper expects it false
|
||||
// and flips it to true, so clear it first to get a clean activation.
|
||||
if (s_flagPasteBoard) {
|
||||
s_bPasteBoardListening = AddClipboardFormatListener(Globals.hwndMain);
|
||||
if (s_bPasteBoardListening) {
|
||||
s_iLastCopyTime = 0;
|
||||
SetTimer(Globals.hwndMain, ID_PASTEBOARDTIMER, 100, PasteBoardTimerProc);
|
||||
}
|
||||
s_flagPasteBoard = false;
|
||||
PasteBoard_Start(Globals.hwndMain);
|
||||
}
|
||||
|
||||
// check if a lexer was specified from the command line
|
||||
@ -3254,13 +3302,7 @@ LRESULT MsgEndSession(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam)
|
||||
|
||||
// Terminate clipboard watching
|
||||
if (s_flagPasteBoard) {
|
||||
s_flagPasteBoard = false;
|
||||
s_iLastCopyTime = 0;
|
||||
KillTimer(hwnd, ID_PASTEBOARDTIMER);
|
||||
if (s_bPasteBoardListening) {
|
||||
RemoveClipboardFormatListener(hwnd);
|
||||
s_bPasteBoardListening = false;
|
||||
}
|
||||
PasteBoard_Stop(hwnd);
|
||||
}
|
||||
|
||||
// close Find/Replace and CustomizeSchemes
|
||||
@ -4161,7 +4203,10 @@ LRESULT MsgInitMenu(HWND hwnd, WPARAM wParam, LPARAM lParam)
|
||||
OpenClipboard(hwnd);
|
||||
EnableCmd(hmenu, IDM_EDIT_CLEARCLIPBOARD, CountClipboardFormats());
|
||||
CloseClipboard();
|
||||
EnableCmd(hmenu, IDM_EDIT_STOP_PASTEBOARD, s_flagPasteBoard);
|
||||
// Clipboard Monitoring is a toggle; grey it out while Tail is active
|
||||
// (the two modes are mutually exclusive).
|
||||
CheckCmd(hmenu, IDM_EDIT_TOGGLE_PASTEBOARD, s_flagPasteBoard);
|
||||
EnableCmd(hmenu, IDM_EDIT_TOGGLE_PASTEBOARD, !FileWatching.MonitoringLog);
|
||||
|
||||
EnableCmd(hmenu, IDM_EDIT_MOVELINEUP, !ro);
|
||||
EnableCmd(hmenu, IDM_EDIT_MOVELINEDOWN, !ro);
|
||||
@ -4287,6 +4332,7 @@ LRESULT MsgInitMenu(HWND hwnd, WPARAM wParam, LPARAM lParam)
|
||||
CheckCmd(hmenu, IDM_VIEW_BOOKMARK_MARGIN, SciCall_GetMarginWidthN(MARGIN_SCI_BOOKMRK) > 0);
|
||||
CheckCmd(hmenu, IDM_VIEW_CHGHIST_TOGGLE_MARGIN, SciCall_GetMarginWidthN(MARGIN_SCI_CHGHIST) > 0);
|
||||
CheckCmd(hmenu, IDM_VIEW_CHASING_DOCTAIL, FileWatching.MonitoringLog);
|
||||
EnableCmd(hmenu, IDM_VIEW_CHASING_DOCTAIL, !s_flagPasteBoard);
|
||||
|
||||
CheckCmd(hmenu, IDM_VIEW_MARKOCCUR_ONOFF, moe);
|
||||
CheckCmd(hmenu, IDM_VIEW_MARKOCCUR_BOOKMARKS, Settings.MarkOccurrencesBookmark);
|
||||
@ -5134,16 +5180,17 @@ static bool _HandleEditBasicCommands(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM
|
||||
break;
|
||||
|
||||
|
||||
case IDM_EDIT_STOP_PASTEBOARD:
|
||||
KillTimer(Globals.hwndMain, ID_PASTEBOARDTIMER);
|
||||
if (s_bPasteBoardListening) {
|
||||
RemoveClipboardFormatListener(Globals.hwndMain);
|
||||
s_bPasteBoardListening = false;
|
||||
case IDM_EDIT_TOGGLE_PASTEBOARD:
|
||||
// Toggle Clipboard Monitoring. Mutually exclusive with Tail/Log-File
|
||||
// Monitoring: refuse if Tail is active (the menu item is also greyed).
|
||||
if (FileWatching.MonitoringLog) {
|
||||
break;
|
||||
}
|
||||
if (s_flagPasteBoard) {
|
||||
PasteBoard_Stop(Globals.hwndMain);
|
||||
} else {
|
||||
PasteBoard_Start(Globals.hwndMain);
|
||||
}
|
||||
s_flagPasteBoard = false;
|
||||
s_iLastCopyTime = 0;
|
||||
s_bLastCopyFromMe = false;
|
||||
s_bLastPasteSeqNoValid = false;
|
||||
UpdateToolbar_Now(Globals.hwndMain);
|
||||
UpdateStatusbar(true);
|
||||
break;
|
||||
@ -6407,6 +6454,14 @@ static bool _HandleViewAndSettingsCommands(HWND hwnd, UINT umsg, WPARAM wParam,
|
||||
|
||||
case IDM_VIEW_CHASING_DOCTAIL: {
|
||||
|
||||
// Mutually exclusive with Clipboard Monitoring: refuse to enable Tail
|
||||
// while the pasteboard is active. Disabling Tail is still allowed
|
||||
// (although with the mutex in place, Tail can never be ON in that
|
||||
// state — belt-and-braces).
|
||||
if (s_flagPasteBoard && !FileWatching.MonitoringLog) {
|
||||
break;
|
||||
}
|
||||
|
||||
InstallFileWatching(false);
|
||||
|
||||
static DocPos _lastCaretPos = -1;
|
||||
@ -9250,7 +9305,7 @@ LRESULT MsgNotify(HWND hwnd, WPARAM wParam, LPARAM lParam)
|
||||
|
||||
case STATUS_OVRMODE:
|
||||
if (s_flagPasteBoard) {
|
||||
PostWMCommand(hwnd, IDM_EDIT_STOP_PASTEBOARD);
|
||||
PostWMCommand(hwnd, IDM_EDIT_TOGGLE_PASTEBOARD);
|
||||
} else {
|
||||
PostWMCommand(hwnd, CMD_VK_INSERT);
|
||||
}
|
||||
@ -9978,6 +10033,7 @@ static void _UpdateToolbarDelayed()
|
||||
|
||||
CheckTool(Globals.hwndToolbar, IDT_VIEW_WORDWRAP, Globals.fvCurFile.bWordWrap);
|
||||
CheckTool(Globals.hwndToolbar, IDT_VIEW_CHASING_DOCTAIL, FileWatching.MonitoringLog);
|
||||
EnableTool(Globals.hwndToolbar, IDT_VIEW_CHASING_DOCTAIL, !s_flagPasteBoard);
|
||||
CheckTool(Globals.hwndToolbar, IDT_VIEW_PIN_ON_TOP, Settings.AlwaysOnTop);
|
||||
|
||||
bool const b1 = SciCall_IsSelectionEmpty();
|
||||
@ -12329,11 +12385,18 @@ void CALLBACK PasteBoardTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD
|
||||
if (SciCall_CanPaste()) {
|
||||
bool bAutoIndent2 = Settings.AutoIndent;
|
||||
Settings.AutoIndent = 0;
|
||||
EditJumpTo(-1, 0);
|
||||
UndoTransActionBegin();
|
||||
if (!Sci_IsDocEmpty()) {
|
||||
|
||||
// Paste at the current caret position. Pre-pend the configured
|
||||
// separator unless this is the first paste of the session OR the
|
||||
// caret is already at a line start.
|
||||
DocPos const curPos = SciCall_GetCurrentPos();
|
||||
bool const bAtLineStart = (curPos == Sci_GetLineStartPosition(curPos));
|
||||
bool const bSkipSep = s_bPasteBoardFirstPaste || bAtLineStart;
|
||||
|
||||
if (!bSkipSep) {
|
||||
if (Settings2.PasteBoardSeparator[0] == L'\x01') {
|
||||
// default: one EOL between entries
|
||||
// configured default: one document EOL between entries
|
||||
SciCall_NewLine();
|
||||
}
|
||||
else if (Settings2.PasteBoardSeparator[0] != L'\0') {
|
||||
@ -12360,6 +12423,7 @@ void CALLBACK PasteBoardTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD
|
||||
Settings.AutoIndent = bAutoIndent2;
|
||||
s_dwLastPasteSeqNo = dwCurrentSeqNo;
|
||||
s_bLastPasteSeqNoValid = true;
|
||||
s_bPasteBoardFirstPaste = false;
|
||||
}
|
||||
s_iLastCopyTime = 0;
|
||||
}
|
||||
|
||||
@ -140,6 +140,7 @@ bool FileIO(bool fLoad, const HPATHL hfile_pth, EditFileIOStatus* status,
|
||||
FileLoadFlags fLoadFlags, FileSaveFlags fSaveFlags, bool bSetSavePoint);
|
||||
|
||||
void CALLBACK PasteBoardTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
|
||||
bool IsPasteBoardActive(void);
|
||||
void InstallFileWatching(const bool bInstall);
|
||||
|
||||
void AutoSaveStart(bool bReset);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user