Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,29 @@ Resource-based MUI system with 27+ locales. Each locale has a directory `np3_LAN
Always use `SciCall.h` wrappers (e.g., `SciCall_GetTextLength()`) instead of raw `SendMessage(hwnd, SCI_XXX, ...)`. The wrappers use Scintilla's direct function pointer for performance.
Add missing wrapper calls to `SciCall.h` if needed.

#### SciCall.h wrapper macros

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

- **V** = void return, **R** = has return value
- **0** = no parameters, **1** = one parameter (wParam), **2** = two parameters (wParam + lParam)
- **01** = optional second parameter only (wParam=0, lParam=var) — used when the SCI message takes lParam but not wParam

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

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

### Scintilla / Lexilla versions

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

### Adding a new syntax lexer

1. Create `src\StyleLexers\styleLexNEW.c` following existing lexer patterns
Expand All @@ -124,3 +147,12 @@ Add missing wrapper calls to `SciCall.h` if needed.
### 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.

### INI file / portable-app design

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

- **No auto-creation on first run**: If no INI file is found, the application runs with defaults. The path is stored in `Paths.IniFileDefault` (not `Paths.IniFile`) so the user can explicitly create it via "Save Settings Now".
- **Admin redirect**: An administrator can place `Notepad3.ini=<path>` in `[Notepad3]` section of the app-directory INI to redirect to a per-user path. Up to 2 levels of redirect are supported. Redirect targets **are** auto-created (the admin intended them to exist).
- **Key paths**: `Paths.IniFile` = active writable INI (empty if none exists), `Paths.IniFileDefault` = fallback path for "Save Settings Now" recovery.
- **Configuration code**: All INI init logic lives in `src\Config\Config.cpp` — `FindIniFile()` → `TestIniFile()` → `CreateIniFile()` → `LoadSettings()`.
26 changes: 23 additions & 3 deletions src/Config/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1000,10 +1000,23 @@ static bool _HandleIniFileRedirect(LPCWSTR lpszSecName, LPCWSTR lpszKeyName, HPA
Path_Sanitize(hredirect);
Path_FreeExtra(hredirect, 0);
if (_CheckAndSetIniFile(hredirect)) {
// Redirect target exists — use it
Path_Swap(hpth_in_out, hredirect);
}
else {
Path_CanonicalizeEx(hpth_in_out, Paths.ModuleDirectory);
// Redirect target doesn't exist — try to create it
// (admin explicitly configured this redirect)
Path_ExpandEnvStrings(hredirect);
if (Path_IsRelative(hredirect)) {
Path_RelativeToApp(hredirect, false, false, true);
}
if (CreateIniFile(hredirect, NULL)) {
Path_Swap(hpth_in_out, hredirect);
} else {
// Creation failed — remember target for "Save Settings Now"
Path_Reset(Paths.IniFileDefault, Path_Get(hredirect));
Path_CanonicalizeEx(hpth_in_out, Paths.ModuleDirectory);
}
}
result = true;
}
Expand Down Expand Up @@ -1093,7 +1106,12 @@ extern "C" bool CreateIniFile(const HPATHL hini_pth, DWORD* pdwFileSize_out)
Path_RemoveFileSpec(hdir_path);
if (Path_IsNotEmpty(hdir_path)) {
// Use SHCreateDirectoryExW to create all intermediate directories - fixes #5075
SHCreateDirectoryExW(NULL, Path_Get(hdir_path), NULL);
HRESULT const hr = SHCreateDirectoryExW(NULL, Path_Get(hdir_path), NULL);
if (FAILED(hr) && (hr != HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS))) {
Path_Release(hdir_path);
if (pdwFileSize_out) { *pdwFileSize_out = 0UL; }
return false;
}
}
Path_Release(hdir_path);

Expand All @@ -1113,6 +1131,8 @@ extern "C" bool CreateIniFile(const HPATHL hini_pth, DWORD* pdwFileSize_out)
StrgFormat(msg, L"CreateIniFile(%s): FAILED TO CREATE INITIAL INI FILE!", fileName);
MsgBoxLastError(StrgGet(msg), 0);
StrgDestroy(msg);
if (pdwFileSize_out) { *pdwFileSize_out = 0UL; }
return false;
}
} else {
HANDLE hFile = CreateFileW(Path_Get(hini_pth),
Expand All @@ -1137,7 +1157,7 @@ extern "C" bool CreateIniFile(const HPATHL hini_pth, DWORD* pdwFileSize_out)
*pdwFileSize_out = dwFileSize;
}

return CanAccessPath(Paths.IniFile, GENERIC_WRITE);
return CanAccessPath(hini_pth, GENERIC_WRITE);
}
return false;
}
Expand Down
12 changes: 10 additions & 2 deletions src/Edit.c
Original file line number Diff line number Diff line change
Expand Up @@ -8300,13 +8300,21 @@ static void _UpdateIndicators(const int indicator, const int indicator2nd,
break; // wrong match
}

// URL-specific: if match ends with single-quote and is preceded by one, strip trailing quote
DocPos mlen_adj = mlen;
if ((indicator == INDIC_NP3_HYPERLINK) && (mlen_adj > 4)) {
if ((SciCall_GetCharAt(end - 1) == '\'') && (start > 0) && (SciCall_GetCharAt(start - 1) == '\'')) {
--mlen_adj;
}
}

_ClearIndicatorInRange(indicator, indicator2nd, start_m, end);

SciCall_SetIndicatorCurrent(indicator);
SciCall_IndicatorFillRange(start, mlen);
SciCall_IndicatorFillRange(start, mlen_adj);
if (indicator2nd >= 0) {
SciCall_SetIndicatorCurrent(indicator2nd);
SciCall_IndicatorFillRange(start, mlen);
SciCall_IndicatorFillRange(start, mlen_adj);
}

// next occurrence
Expand Down
31 changes: 31 additions & 0 deletions src/Helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include <shlobj.h>
#include <shellapi.h>
#include <propkey.h>
#include <ctype.h>
#include <wchar.h>

Expand Down Expand Up @@ -295,6 +296,36 @@ HRESULT PrivateSetCurrentProcessExplicitAppUserModelID(PCWSTR AppID)
}


//=============================================================================
//
// SetWindowAppUserModelID()
//
HRESULT SetWindowAppUserModelID(HWND hwnd, PCWSTR AppID)
{
if (!hwnd || StrIsEmpty(AppID)) {
return S_OK;
}
if (StringCchCompareXI(AppID, L"(default)") == 0) {
return S_OK;
}

IPropertyStore* pps = NULL;
HRESULT hr = SHGetPropertyStoreForWindow(hwnd, &IID_IPropertyStore, (void**)&pps);
if (SUCCEEDED(hr)) {
PROPVARIANT pv;
PropVariantInit(&pv);
pv.vt = VT_LPWSTR;
hr = SHStrDupW(AppID, &pv.pwszVal);
if (SUCCEEDED(hr)) {
hr = pps->lpVtbl->SetValue(pps, &PKEY_AppUserModel_ID, &pv);
PropVariantClear(&pv);
}
pps->lpVtbl->Release(pps);
}
return hr;
}


//=============================================================================
//
// IsProcessElevated()
Expand Down
1 change: 1 addition & 0 deletions src/Helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ static inline int IsFullHD(HWND hwnd, int resX, int resY)
// ----------------------------------------------------------------------------

HRESULT PrivateSetCurrentProcessExplicitAppUserModelID(PCWSTR AppID);
HRESULT SetWindowAppUserModelID(HWND hwnd, PCWSTR AppID);

bool IsProcessElevated();
//bool IsUserAdmin();
Expand Down
1 change: 1 addition & 0 deletions src/Notepad3.c
Original file line number Diff line number Diff line change
Expand Up @@ -1773,6 +1773,7 @@ HWND InitInstance(const HINSTANCE hInstance, int nCmdShow)
DrawMenuBar(hwndMain);

Globals.hwndMain = hwndMain; // make main window globaly available
SetWindowAppUserModelID(hwndMain, Settings2.AppUserModelID);

HPATHL hfile_pth = Path_Copy(s_pthArgFilePath);
FileLoadFlags fLoadFlags = FLF_None;
Expand Down