Merge pull request #5885 from RaiKoHoff/dev_master

feat: open file/folder from selection + bare-path hyperlink hotspots
This commit is contained in:
Pairi Daiza 2026-05-17 00:05:58 +02:00 committed by GitHub
commit 2b546fca90
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 989 additions and 299 deletions

View File

@ -162,6 +162,12 @@ User-facing description of every entry in Notepad3's menus and context menus, st
📋 **Menu reference:** [readme/MenuEntriesAndCmds.md](readme/MenuEntriesAndCmds.md)
## File-Path Handling
How Notepad3 turns clickable links, selected text, and path-shaped tokens into real files — including bare paths with line specs (`file.c:42`, `file.c(42)`), `file:///` URLs, relative-path anchor rules (current document's directory → working directory), environment-variable expansion, and the two new selection commands **Open Containing Folder of Selection** / **Open File from Selection**.
🗂️ **File-path reference:** [readme/paths/FilePathHandling.md](readme/paths/FilePathHandling.md)
## Keyboard Shortcuts
Every key combination Notepad3 understands — file, editing, view, search, customization — grouped by menu, with Notepad2 → Notepad3 reassignments flagged.

View File

@ -1,226 +0,0 @@
# Plan — Follow-up enhancements: file/path reference handling
Companion to the now-shipped change that added `IDM_EDIT_OPEN_CONTAINING_FOLDER_SEL` / `IDM_EDIT_OPEN_FILE_FROM_SEL` and fixed the `Paths.ModuleDirectory` anchor bug in the hyperlink-click handler.
These items were intentionally deferred from that change to keep the scope tight. They are independent of each other; pick any subset.
## Status (2026-05-16)
Already shipped on `dev_master`:
- New selection-based commands `IDM_EDIT_OPEN_CONTAINING_FOLDER_SEL` (40395) and `IDM_EDIT_OPEN_FILE_FROM_SEL` (40396), wired to Edit > Miscellaneous and the editor right-click popup. No accelerator — users can bind one via custom keybindings if desired.
- Shared helpers in `src\Helpers.c`: `ExtractSelectionOrTokenAtCaret`, `SplitFilePathLineColNum`, `ResolveSelectionToPath`.
- Bug fix at `src\Notepad3.c:8891` (or thereabouts after later edits) — relative `file:///` URLs now anchor to the current document's directory, fallback `Paths.WorkingDirectory`, never `Paths.ModuleDirectory`.
- English placeholder strings replicated across all 25 sibling locale `menu_*.rc` files. Translators still need to translate them.
Function bodies for the two new commands live in `src\Notepad3.c` (not Edit.c) because `<shlobj.h>` defines `SORT_ASCENDING` / `SORT_DESCENDING` enum values that conflict with Edit.c's `SortOrderMask` enum.
## Item 1 — Bare-path hotspotting (`HYPLNK_REGEX_FULL` extension)
**Goal**: make `C:\Users\me\foo.txt`, `\\server\share\file`, `.\rel\path.c`, `../include/foo.h`, and similar bare paths clickable hotspots in the editor, just like `https://...` URLs already are.
### Why this is deferred and load-bearing
The existing hyperlink hotspot system (`src\Edit.c:111-116`) uses one big PCRE2 regex that requires an explicit scheme prefix:
```c
#define HYPLNK_REGEX_FULL "\\b(?:(?:https?|ftp)://|file:///|file://|mailto:|www\\.|ftp\\.)" ...
```
Adding bare-path matching is high-risk because:
1. **False positives**`C:` could match in non-path text (e.g. a code snippet, a colon-time `12:34:56`).
2. **Indicator-marking pass cost** — the URL hotspotter runs on every restyle (`MarkAllOccurrences` flow). A broader regex adds CPU per keystroke in large documents.
3. **Lexer interference** — many lexers already style strings/paths in domain-specific ways. Adding `INDIC_NP3_HYPERLINK` on top of an existing lexer style can cause visual conflicts (double-underlining, color clash).
4. **`SCN_INDICATORRELEASE` activation** — currently `HandleHotSpotURLClicked` at `src\Notepad3.c:8824` is dispatched on indicator-release. It calls `UrlIsFileUrl(szTextW)` to decide between OPEN_IN_NOTEPAD3 and OPEN_WITH_BROWSER. Bare paths aren't file:// URLs, so the routing logic needs a new branch: "if it looks like a Windows path, treat it as OPEN_IN_NOTEPAD3 directly."
### Implementation sketch
Two viable approaches:
**A. Single bigger regex.** Add an alternation arm for paths. E.g.:
```
\\b(?:[A-Za-z]:[\\\\/]|\\\\\\\\[^\\s]+\\\\|\\.{1,2}[\\\\/])[^\\s'`"<>|*,;]+
```
- Drive-letter: `C:\...`, `c:/...`
- UNC: `\\server\share\...`
- Relative: `.\foo`, `..\foo`, `./foo`, `../foo`
Pros: minimal code change. Cons: regex becomes harder to maintain; false-positive risk is concentrated.
**B. Second-pass detection.** Keep `HYPLNK_REGEX_FULL` for scheme URLs. Add a separate, more restrictive pass for bare paths only when the line context suggests "this is a path" — e.g. preceded by `at`, `file:`, `> `, in compiler-error format `file.c:42:`, etc. More conservative; harder to author.
**Recommendation**: try (A) but gate behind a `Settings2.HyperlinkBarePaths` opt-in toggle defaulting to `false`. Document the toggle in `Build\Notepad3.ini` `[Settings2]` and `readme/config/Configuration.md` (CLAUDE.md invariant).
### Required touchpoints
| File | Change |
|---|---|
| `src\Edit.c:111-116` | Extend `HYPLNK_REGEX_FULL` (or add `HYPLNK_REGEX_BAREPATH` and run both) |
| `src\Notepad3.c:8824` (`HandleHotSpotURLClicked`) | New branch when `!UrlIsFileUrl(szTextW)` AND text looks like a Windows path → call same OPEN_IN_NOTEPAD3 logic but skip `PathCreateFromUrlW` (use the text as-is, then `ResolveSelectionToPath`-style anchoring) |
| `src\TypeDefs.h` (`Settings2`) | Add `HyperlinkBarePaths` bool with default `false` |
| `src\Config\Config.cpp` | Persist `HyperlinkBarePaths` |
| `Build\Notepad3.ini`, `readme\config\Configuration.md` | Document new INI key |
### Verification
- Open a fresh `.txt` buffer with toggle ON. Paste:
```
C:\Windows\notepad.exe
..\src\Helpers.c
\\?\C:\Windows
src/Edit.c:111
See line C: this should NOT highlight
Time 12:34:56 must NOT highlight
```
Expect: lines 1-4 hotspotted, lines 5-6 not.
- With toggle OFF: no change vs. current behavior.
- Test in C/C++ buffer (SCLEX_CPP) to confirm no lexer-style clash on strings containing paths.
## Item 2 — Markdown `[label](url)` extraction in `ExtractSelectionOrTokenAtCaret`
**Goal**: when the caret is inside the URL portion of a Markdown link `[label](url)`, the selection extractor should grab `url` rather than expanding outward through the closing `)` and stopping at the next whitespace.
### Why this is deferred
The current `ExtractSelectionOrTokenAtCaret` in `src\Helpers.c` uses a flat boundary set `[\s'`"<>|*,;]` and never considers paired-delimiter context. Notepad4's `EditGetStringAroundCaret` (`notepad4\src\Edit.cpp:7015-7093`) has a dedicated subroutine to recognize `(...)` after a Markdown link target and clamp the extraction.
It's deferred because: (a) Notepad3 already hotspots Markdown URLs via the lexer-driven indicator path, so most Markdown click flows already work; (b) the extractor is used by the new selection commands, where the user can always select the URL by hand if the auto-expand is wrong.
### Sketch
Inside `ExtractSelectionOrTokenAtCaret` in `src\Helpers.c`, when the selection is empty, BEFORE the regular boundary scan:
```c
// Markdown link target: if caret sits inside `(...)` and the char before '(' is ']',
// clamp the extraction to the contents of the parens.
DocPos const posCur = SciCall_GetCurrentPos();
// Walk back to find an unmatched '(' on the same line.
// If found AND the char immediately before is ']', use [openParen+1, matching ')') as the span.
```
Use Scintilla's bracket-matching (`SciCall_BraceMatch`) — but only on `(`; Scintilla supports `()` `[]` `{}` `<>`. Caveat: caret-on-`(` semantics vary; you may need to do a manual scan.
### Touchpoints
- `src\Helpers.c``ExtractSelectionOrTokenAtCaret` (introduced in the prior change).
### Verification
```markdown
See [the doc](D:\projects\foo\readme.md) for details.
^ caret here
```
Invoke "Open File from Selection" → opens `D:\projects\foo\readme.md`. Currently expands to `D:\projects\foo\readme.md)` and the trailing `)` may or may not be tolerated by `ResolveSelectionToPath`.
## Item 3 — Enable/disable gating in `MsgInitMenu` for the new commands
**Goal**: grey out `IDM_EDIT_OPEN_CONTAINING_FOLDER_SEL` / `IDM_EDIT_OPEN_FILE_FROM_SEL` when there's no selection AND no token at the caret position.
### Current behavior
The commands are always enabled. Invoking them with an empty selection on whitespace-only content silently produces a `MessageBeep(MB_ICONHAND)`. This matches Notepad4's UX and is acceptable, but a power user might prefer a greyed-out menu.
### Sketch
`MsgInitMenu()` in `src\Notepad3.c` should add (near the existing `IDM_EDIT_INSERT_GUID` enable check around line 4963):
```c
bool const bHasToken = !SciCall_IsSelectionEmpty();
// optionally also check word boundaries at SciCall_GetCurrentPos()
EnableCmd(hmenu, IDM_EDIT_OPEN_CONTAINING_FOLDER_SEL, bHasToken /* OR token-at-caret */);
EnableCmd(hmenu, IDM_EDIT_OPEN_FILE_FROM_SEL, bHasToken);
```
Right-click context menu builder at `src\Notepad3.c:4549` (`imenu == MNU_EDIT` branch) already has a similar pattern for the case-conversion commands using `s_iCtxMenuClickPos`. Add the same predicate there.
### Verification
- Empty selection, caret on whitespace → menu items greyed.
- Empty selection, caret in a word → enabled.
- Any non-empty selection → enabled.
## Item 4 — `\\?\` long-path prefix handling in `ResolveSelectionToPath`
**Goal**: gracefully handle paths with the Windows long-path prefix when passed to `SHParseDisplayName`.
### Why
`SHParseDisplayName` does not always accept `\\?\C:\...`. For shell-API consumption the prefix should be stripped. PathLib wrappers already handle this internally for filesystem ops, but `EditOpenContainingFolderFromSelection` calls `SHParseDisplayName` directly with `Path_Get(hpth)` which may include the prefix.
### Sketch
Inside `EditOpenContainingFolderFromSelection` in `src\Notepad3.c`, before `SHParseDisplayName`:
```c
LPCWSTR pth = Path_Get(hpth);
if (StrCmpNW(pth, L"\\\\?\\", 4) == 0) {
// Skip the prefix for shell-API use. If \\?\UNC\..., strip differently.
if (StrCmpNW(pth + 4, L"UNC\\", 4) == 0) {
// \\?\UNC\server\share → \\server\share
WCHAR shellPath[MAX_PATH_EXPLICIT];
StringCchPrintf(shellPath, COUNTOF(shellPath), L"\\\\%s", pth + 8);
SHParseDisplayName(shellPath, ...);
} else {
SHParseDisplayName(pth + 4, ...);
}
}
```
### Verification
- `\\?\C:\Windows` → opens `C:\Windows` in Explorer.
- `\\?\UNC\server\share\file.txt` → opens `\\server\share\` with `file.txt` selected.
## Item 5 — `#fragment` parsing for `file://` URLs
**Goal**: a `file:///D:/docs/readme.md#section-2` URL clicked in Notepad3 should at minimum open the file (currently works) and ideally jump to the line with `#section-2` as a heading anchor.
### Why deferred
Markdown anchor resolution is out of scope for a text editor — the heading-anchor mapping is a Markdown-specific convention. The minimum useful behavior is to strip `#...` before resolving the path. Currently `PathCreateFromUrlW` may or may not strip it depending on Windows version.
### Sketch
In `HandleHotSpotURLClicked` at `src\Notepad3.c:8824`, after `PathCreateFromUrlW`:
```c
LPWSTR const hash = StrChr(szUnEscW, L'#');
if (hash) {
*hash = L'\0'; // discard fragment
}
```
A future Markdown-aware extension could parse `#anchor-name`, scan the buffer for `## Anchor Name` (case-insensitive, dashes→spaces), and `SCI_GOTOLINE` there. Probably not worth the complexity.
### Verification
Click `file:///D:/test/foo.md#whatever` — opens `foo.md`. Don't error or attempt to interpret `#whatever`.
## Item 6 — Localize the new menu strings (translator task, not Claude's)
The 25 sibling locales currently have **English placeholder** strings for the two new menu items. Translators need to update:
- `language\np3_*\menu_*.rc` — replace `"Open &Containing Folder of Selection\tCtrl+Shift+,"` and `"Open &File from Selection"` (both in Edit > Miscellaneous AND in `IDR_MUI_POPUPMENU`).
This is normal translator workflow — not blocked, just pending.
### What Claude can do
Nothing without authoritative translations. If asked to "translate", refuse and ask the user to source translations from the locale maintainers listed in `language\np3_*\strings_*.rc` (`IDS_MUI_TRANSL_AUTHOR`).
## Item 7 — Tinyexpr behavior in `SplitFilePathLineColNum` `:N` fallback
`SplitFilePathLineColNum` falls back to `SplitFilePathLineNum` (which uses `te_interp`) for the single-colon case `foo.c:42`. This means `foo.c:42+1` would parse as line 43 — a side effect of using tinyexpr. Two existing callers depend on this:
- `src\Notepad3.c:8666``SplitFilePathLineNum(wchUrl, NULL)` — only cuts the suffix, discards the number.
- `src\Notepad3.c:8879``SplitFilePathLineNum(szTextW, &lineNum)` — uses the parsed number.
If we ever want to drop the tinyexpr quirk: replace the wrapper with a plain `_wtoi` parse. Verify the two existing callers don't pass arithmetic.
## Touch-stones for any future session
- **CLAUDE.md** in repo root has all the build/encoding/localization invariants. Read it before touching `.rc` files.
- **`.venv\Scripts\python.exe`** is required for bulk locale edits. Do NOT use sed/perl.
- **`.rc` files** are UTF-8 *without* BOM, CRLF. Use `Build\rc_to_utf8.cmd` if a BOM sneaks in.
- **Locale es_la** has an empty directory — script-based bulk edits should skip it gracefully.
- **Reference impl**: Notepad4 at `D:\DEV\GitHub\notepad4\` — inspect `src\Edit.cpp` (`EditOpenSelection`, `EditGetStringAroundCaret`) and `src\Helpers.cpp` (`OpenContainingFolder`).
- **Build**: `Build\Build_x64.cmd Release` for quick verification. The Build.ps1 wrapper has quoting quirks under some PowerShell versions — invoke MSBuild directly if it fails: `& "C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe" "D:\DEV\GitHub\Notepad3\Notepad3.sln" /m /p:Configuration=Release /p:Platform=x64 /v:minimal`.
## Out of scope for these follow-ups
- IDS_MUI_* / Notepad3DLL string resources — none of the deferred items add user-facing strings beyond menu labels (which are inline in `.rc`).
- ARM64-specific concerns — none of these touch rendering or CET paths.
- grepWin integration — orthogonal.

View File

@ -8,6 +8,7 @@ A user-facing reference for every command exposed by Notepad3's menus and contex
> - **`Notepad3.ini` reference** (every `[Settings2]` key referenced below): [`config/Configuration.md`](config/Configuration.md).
> - **Focused View** in depth: [`focusedview/FocusedView.md`](focusedview/FocusedView.md).
> - **Encryption** in depth: [`encryption/Encryption.md`](encryption/Encryption.md).
> - **File-path handling** — hyperlinks, selection commands, line/column suffixes, anchor rules: [`paths/FilePathHandling.md`](paths/FilePathHandling.md).
> - **MiniPath** (the bundled file browser launched with `Ctrl+M`): [`minipath/`](minipath/).
> - **FAQ**, including the Notepad2 migration table: [`faq/FAQ.md`](faq/FAQ.md).
@ -321,6 +322,8 @@ Less-frequently-used utilities and selection commands.
| **Select To Previous** | `Ctrl+Alt+Shift+F2` | Extend the selection backward to the previous match. |
| **Select Word or Lines** | `Ctrl+Spc` | Select the word at the caret; press again to extend to the whole line. |
| **Multi-Select All Matches** | `Ctrl+Shift+Spc` | Place multi-carets at every occurrence of the word at the caret (true multi-selection, edits affect all). |
| **Open Containing Folder of Selection** | — | Resolve the selection (or token at the caret) to a path and reveal it in Windows Explorer. For relative paths the anchor is the current document's directory, falling back to the working directory. Greyed out when there is no selection and no word at the caret. See [`paths/FilePathHandling.md`](paths/FilePathHandling.md). |
| **Open File from Selection** | — | Same resolution as above; loads the file into Notepad3 (jumping to a parsed `:line` if present) or opens the file-open dialog rooted at the resolved directory. See [`paths/FilePathHandling.md`](paths/FilePathHandling.md). |
| **Split Undo Transaction at Line-Breaks** | — | Toggle: when on, every newline boundary becomes an undo checkpoint, so `Ctrl+Z` rolls back line-by-line instead of by typing run. |
### Bookmarks

View File

@ -2,6 +2,8 @@
Notepad3 stores its settings in a portable INI file (`Notepad3.ini`). Press **Ctrl+F7** to open it directly in the editor. Most `[Settings2]` changes require restarting Notepad3.
> Path-handling behaviour controlled by keys below (`AtomicFileSave`, `ResolveUNCPaths`, `DefaultDirectory`, `OpenWithDir`, `FavoritesDir`, `HyperlinkShellExURLWithApp`, `HyperlinkFileProtocolVerb`, `ShowHypLnkToolTip`, …) is summarised in [`../paths/FilePathHandling.md`](../paths/FilePathHandling.md).
---
## `[Notepad3]`

View File

@ -0,0 +1,326 @@
# File-path handling in Notepad3
Everywhere Notepad3 turns a piece of text into a *file* — a clickable link in
the editor, a path you typed into a dialog, a reference you selected from a
build log — it goes through the same few rules. This page collects them in
one place.
> Related references
> - **Menu entries** for the commands below: [`../MenuEntriesAndCmds.md`](../MenuEntriesAndCmds.md).
> - **INI keys** that influence path handling: [`../config/Configuration.md`](../config/Configuration.md).
> - **Command-line switches** for opening files: [`../cmdln/CmdLnOptions.md`](../cmdln/CmdLnOptions.md).
> - **MiniPath** (the bundled file browser): [`../minipath/`](../minipath/).
## Quick reference
| Want to … | Do this |
|---|---|
| Open a `http://…` or `https://…` URL from the document | Ctrl+Click the highlighted link → opens in your default browser. |
| Open a `file:///…` URL in Notepad3 | Alt+Click the highlighted link → loads the target into the editor. |
| Open a bare path like `C:\foo\bar.c:42` in Notepad3 | Alt+Click — bare paths are highlighted only when they end in `:42`, `:42:7`, or `(42)`. |
| Reveal the file under the caret in Explorer | Place the caret on the path and choose **Edit → Miscellaneous → Open Containing Folder of Selection**, or right-click → same. |
| Open a file referenced by selection / token at caret | **Edit → Miscellaneous → Open File from Selection** (right-click also). |
| Reveal the *current* document in Explorer | **File → Open File Explorer**. |
| Copy the current path | **File → Path to Clipboard → Copy Filename / Directory / Full-Path**. |
---
## 1. Clickable hyperlinks in your document
When **View → Hyperlink Hotspots** is enabled (default), Notepad3 detects
links and paths in your text and marks them with an underline. Click
modifiers control what happens:
| Gesture | Action |
|---|---|
| **Ctrl+Click** | Open in the associated external app (browser for URLs, default opener for files). |
| **Ctrl+Shift+Click** | Open in the associated app *in the background* (Notepad3 stays in front). |
| **Alt+Click** | Open in Notepad3 itself — works for `file:///…` URLs and bare file paths. |
| **Alt+Shift+Click** | Open in a *new* Notepad3 instance. |
| **Middle-mouse click** | Same as Ctrl+Click. |
| **Right-click on a hotspot** | Context menu with explicit "Open Hyperlink" / "Copy Hyperlink URL" entries. |
| **Hover** (with `ShowHypLnkToolTip`) | Tooltip showing the link target. |
### URL forms Notepad3 recognises
Detected as a clickable URL with a scheme:
| Form | Example |
|---|---|
| HTTP(S) | `https://example.com/foo` |
| FTP | `ftp://example.com/foo` |
| `www.…` (no scheme) | `www.example.com` |
| `ftp.…` (no scheme) | `ftp.example.com` |
| `mailto:` | `mailto:nobody@example.com` |
| `file://` | `file://server/share/file.txt` |
| `file:///` | `file:///D:/projects/foo/readme.md` |
The matcher is a single PCRE2 regular expression — it stops at common
sentence punctuation (`. , ! ? :`) so a URL followed by punctuation in prose
doesn't absorb the period.
### File URLs (`file://` and `file:///`)
After the scheme is stripped, the rest is treated as a filesystem path.
**Relative `file:///` URLs** are resolved against:
1. The directory of the *current document*, if it has been saved.
2. Otherwise, the working directory (the directory Notepad3 was launched
from, or set via `/y` on the command line).
It is *never* resolved against Notepad3's install directory — that was a bug
in older versions and is now fixed.
A trailing `#fragment` (e.g. `file:///foo.md#section-2`) is **stripped before
the file is opened**. Notepad3 doesn't resolve Markdown anchor names; the
file is opened cleanly without trying to interpret `#section-2`.
### Bare file paths (with mandatory line spec)
Plain filesystem paths are clickable **when they're followed by a line-number
indicator** — that's a strong signal the text is a build-error reference,
log entry, or stack-trace line rather than prose. Three prefix shapes are
recognised:
| Form | Example |
|---|---|
| Drive letter + separator | `C:\foo\bar.c:42` · `c:/foo/bar.c(42)` |
| UNC | `\\server\share\foo\bar.c:42` |
| Relative | `.\src\Helpers.c:42` · `../include/foo.h:42:7` |
A bare path without a line spec — for example `C:\Windows` typed in prose —
is **not** highlighted. That's intentional, to keep false positives down.
If you need to act on such a path, select it and use **Open Containing
Folder of Selection** or **Open File from Selection** (see §2).
### Line / column suffix patterns
Notepad3 recognises four trailing patterns and jumps to the parsed line
when opening:
| Pattern | Example | Meaning |
|---|---|---|
| `:N` | `Helpers.c:42` | Line 42 |
| `:N:M` | `Helpers.c:42:7` | Line 42, column 7 (column currently ignored) |
| `(N)` | `Helpers.c(42)` | Line 42 |
| `,N` | `Helpers.c,42` | Line 42 |
The parser is digit-only — `Helpers.c:42+1` is **not** interpreted as
"line 43"; it's treated as a non-match and the path is opened without a
jump.
A colon that follows a drive letter (`C:`) is never treated as a line
separator.
---
## 2. Selection-based commands
For paths that *aren't* highlighted as hotspots — or when you just want to
act on the path under the caret without targeting an indicator precisely —
use the two **Edit → Miscellaneous** entries (also in the editor right-click
menu when there's a selection or a token at the click point):
### Open Containing Folder of Selection
Resolves the selected text (or token at caret) to a filesystem path and
opens Windows Explorer with that file highlighted. If the target is a
folder, opens the folder itself.
### Open File from Selection
Resolves the same way, then loads the file into Notepad3 (jumps to the
parsed line if a line spec was present). If the target is a folder, opens
the file-open dialog rooted in it.
### How the token is extracted
If there's a non-empty selection:
- Use the selected text.
- Truncate at the first newline or tab character.
If the selection is empty:
- Expand from the caret outward on the same line.
- Stop at any of: whitespace, single quote, backtick, double quote, `<`,
`>`, `|`, `*`, `,`, `;`.
- **Markdown link special-case**: if the caret sits inside `(…)` parens
immediately preceded by `]`, the inner contents of the parens are used
instead — so caret inside the URL of `[label](D:\projects\foo\readme.md)`
picks up `D:\projects\foo\readme.md`, not the surrounding text.
Surrounding whitespace, `"`, `'`, `` ` ``, `<`, `>` are stripped from the
result.
Any trailing line/column suffix (`:N`, `:N:M`, `(N)`, `,N`) is split off
before path resolution. For *Open File from Selection*, the parsed line
number controls the caret jump after the file loads.
### Menu state
Both commands are greyed out when there's no selection *and* no word at the
caret / click position. They have no default keyboard shortcut; users can
bind one via custom keybindings if desired.
---
## 3. Path resolution rules
The same rules apply to file-URL clicks, bare-path clicks, and both
selection-based commands.
### Anchor priority for relative paths
A *relative* path (one without a drive letter, UNC `\\`, or leading slash)
is resolved against:
1. The directory of the **current document** (`Paths.CurrentFile`), if it
has been saved.
2. Otherwise, the **working directory** (`Paths.WorkingDirectory`).
Notepad3's *install directory* (`Paths.ModuleDirectory`) is **never** used
as an anchor for in-document references.
### Environment-variable expansion
Tokens containing `%VAR%` are expanded against the current process
environment before resolution. So `%WINDIR%\notepad.exe` and
`%USERPROFILE%\Documents\foo.txt` resolve correctly.
### `file://` → Windows path conversion
`file://`-prefixed URLs go through the Windows shell's `PathCreateFromUrlW`
to convert URL-encoded characters (`%20` → space, etc.) into a real
filesystem path. If `PathCreateFromUrlW` fails on a malformed URL, the
prefix is stripped manually and the resolver tries the rest as a best
effort.
### Long paths (`\\?\` prefix)
When **Open Containing Folder of Selection** hands a path to the Windows
shell, the long-path prefix `\\?\` is stripped first so Explorer can accept
the path:
- `\\?\C:\Windows``C:\Windows`
- `\\?\UNC\server\share\file.txt``\\server\share\file.txt`
Internally Notepad3 uses long-path-aware Win32 wrappers throughout. If
Windows' long-path support (`LongPathsEnabled` group policy) is off and a
path is ≥ 260 characters, Notepad3 applies the `\\?\` prefix transparently
where necessary.
---
## 4. Path-related menu commands
### File menu
| Command | What it does |
|---|---|
| **Open…** (Ctrl+O) | Standard file-open dialog. |
| **Open with…** (Alt+L) | Hand the current file to an external editor — choose via `OpenWithDir` setting. |
| **Recent Files** (sub-menu) | Most-recently-used list. Paths are stored relative to Notepad3.exe by default — flip via the `Notepad3.ini` redirect mechanism. |
| **Save** / **Save As…** / **Save Copy…** | Standard write paths; respects `AtomicFileSave` (see §5). |
| **Open File Explorer** | Reveal the **current document** in Windows Explorer (or, if untitled, the working directory). Distinct from **Open Containing Folder of Selection**, which operates on text *inside* the document. |
| **Path to Clipboard → Copy Filename only / Copy Directory-Path only / Copy Full-Path** | Self-explanatory; for filling into other tools. |
| **Add to Favorites** / **Favorites…** | Pin a path to a quick-pick list, stored under `FavoritesDir`. |
| **Launch Default Application** (Ctrl+L) | ShellExecute the current document with its registered "open" verb. |
| **Run…** | ShellExecute an arbitrary command line, prefilled with the document's full path. |
### Edit menu
| Command | What it does |
|---|---|
| **Edit → Miscellaneous → Open Containing Folder of Selection** | See §2. |
| **Edit → Miscellaneous → Open File from Selection** | See §2. |
| **Edit → Insert → Filename / Directory / Path** | Insert the current document's name / parent dir / full path into the buffer at the caret. |
---
## 5. Background behavior
These mechanisms aren't user-triggered but are worth knowing about because
they affect what you'll see on disk.
### File watching
Notepad3 asks Windows to notify it of changes in the **parent directory**
of the open file. When the current file changes externally, the **File
Change Notification** dialog offers to reload (or you can configure silent
reload). The watch is released before *Save* and re-armed afterwards so
Notepad3 doesn't observe its own writes.
### Atomic save
With `AtomicFileSave=true` (the default), *Save* writes to a temporary file
alongside the target and then atomically replaces the original via
`ReplaceFileW`. ACLs, alternate data streams, and attributes are preserved
on failure — nothing is lost if the write is interrupted. See
[`../config/Configuration.md#atomicfilesave-true`](../config/Configuration.md#atomicfilesavetrue)
to disable on very large files where in-place writing is faster.
### MRU (Most-Recently-Used) list
The Recent Files list in the File menu is persisted in `Notepad3.ini` under
`[Recent Files]`. Disable persistence with the `NoSaveRecent` setting.
Entries can be removed individually from the MRU dialog.
### Portable & redirected INI
By default `Notepad3.ini` lives next to `Notepad3.exe` (portable mode). The
file `Notepad3.ini` itself can specify a `Notepad3.ini=<path>` redirect
under `[Notepad3]` (up to two levels) that points at a per-user location —
useful for shared installs. The redirected file is auto-created on first
write.
---
## 6. Configuration keys that affect path handling
All in `Notepad3.ini` under `[Settings2]` unless noted — see
[`../config/Configuration.md`](../config/Configuration.md) for full
descriptions.
| Key | Effect |
|---|---|
| `AtomicFileSave` | Two-phase Save (temp file + ReplaceFileW). |
| `ResolveUNCPaths` | When opening a UNC path that's actually a mapped drive's share, prefer the drive-letter form for display. |
| `DefaultDirectory` | Initial directory of the **Open…** dialog. |
| `OpenWithDir` | Directory used by **Open with…**. |
| `FavoritesDir` | Where the Favorites list lives. |
| `GrepWinPath` | Override the grepWin executable lookup. |
| `HyperlinkShellExURLWithApp` | Open URLs with a specific external app instead of the OS default. |
| `HyperlinkShellExURLCmdLnArgs` | Argument template for the above. |
| `HyperlinkFileProtocolVerb` | Override the verb used to ShellExecute `file:` URLs. |
| `ShowHypLnkToolTip` | Hover tooltip showing the link target. |
| `MonitoringLog` | `FileWatching.MonitoringLog`; tails the file by default. |
---
## 7. Troubleshooting
**Q. My path has spaces and the resolver only picked up part of it.**
Wrap it in double quotes or single quotes in the source text, or select the
full path manually before invoking the command.
**Q. `..\..\foo.c:42` opened the wrong file.**
Save your document first — relative paths anchor to the document's
directory only after it has been saved. Until then, they anchor to the
working directory.
**Q. A path is highlighted but Alt+Click does nothing.**
Notepad3 silently does nothing when the resolved path doesn't exist on
disk. Check the path with **Open Containing Folder of Selection** — it will
beep if the resolution fails.
**Q. `C:\Windows` isn't highlighted as a hotspot.**
By design — bare paths are only hotspotted when they end in a line spec
(`:42`, `:42:7`, or `(42)`). For paths without a line number, use the
selection-based commands instead.
**Q. The line jump for `foo.c:42:7` ignores `7`.**
The column is parsed and accepted but not currently acted on — only the
line is set. Tracked for a future enhancement.
**Q. `file:///D:/docs/readme.md#section-2` doesn't jump to the anchor.**
Heading-anchor resolution is a Markdown-specific convention and isn't
implemented — the fragment is stripped and the file opens at the top.

View File

@ -112,8 +112,24 @@ static LPCWSTR const s_pUnicodeRegEx = L"(\\\\[uU|xX]([0-9a-fA-F]){4}|\\\\[xX]([
"(?:\\([-" HYPLNK_REGEX_VALID_CDPT "?!:,.]*+\\)|[-" HYPLNK_REGEX_VALID_CDPT "?!:,.])*"\
"(?:\\([-" HYPLNK_REGEX_VALID_CDPT "?!:,.]*+\\)|[-" HYPLNK_REGEX_VALID_CDPT "])"
// Bare-path-with-line-spec detection. Conservative (Approach B): only paths
// followed by a digit suffix `:N`, `:N:M`, or `(N)` are detected — that's a
// strong signal the token is a file reference (compiler error, log entry,
// build output) rather than a noun in prose. Avoids the false-positive
// risk of hotspotting any drive-letter substring.
//
// Prefix arms: drive letter, UNC `\\host\share\`, or relative `.\`/`..\`.
// Leading lookbehind prevents accidental matches inside identifiers (e.g. a
// stray `xC:\foo:42` wouldn't start at the `C`). Body chars exclude the
// token-boundary set [\s'`"<>|*,;].
#define HYPLNK_REGEX_FILEREF "(?<![A-Za-z0-9_])"\
"(?:[A-Za-z]:[\\\\/]|\\\\\\\\[^\\s\\\\/]+\\\\[^\\s\\\\/]+\\\\|\\.{1,2}[\\\\/])"\
"[^\\s'`\"<>|*,;]+"\
"(?::\\d+(?::\\d+)?|\\(\\d+\\))"
static LPCSTR const s_pUrlRegExA = HYPLNK_REGEX_FULL;
static LPCWSTR const s_pUrlRegEx = _W(HYPLNK_REGEX_FULL);
static LPCWSTR const s_pFileRefRegEx = _W(HYPLNK_REGEX_FILEREF);
// ----------------------------------------------------------------------------
@ -8829,8 +8845,15 @@ static void _ClearIndicatorInRange(const int indicator, const int indicator2nd,
}
}
// Scan [startPos, endPos) for regex matches and stamp the indicator(s) onto
// each match. When bClearGaps is true, the regions BETWEEN matches (and the
// trailing region after the last match) are cleared first — used for the
// authoritative URL pass. When false, the function is purely additive —
// used to LAYER a second regex onto the same indicator without undoing the
// first pass's matches.
static void _UpdateIndicators(const int indicator, const int indicator2nd,
LPCWSTR regExpr, DocPos startPos, DocPos endPos)
LPCWSTR regExpr, DocPos startPos, DocPos endPos,
bool bClearGaps)
{
if (endPos < 0) {
endPos = Sci_GetDocEndPosition();
@ -8845,26 +8868,25 @@ static void _UpdateIndicators(const int indicator, const int indicator2nd,
return;
}
// --------------------------------------------------------------------------
DocPos start = startPos;
DocPos end = endPos;
do {
DocPos const start_m = start;
DocPos const end_m = end;
DocPos const iPos = _FindInTarget(regExpr, SCFIND_REGEXP, &start, &end, true, FRMOD_IGNORE);
if (iPos < 0) {
// not found
_ClearIndicatorInRange(indicator, indicator2nd, start_m, end_m);
if (bClearGaps) {
_ClearIndicatorInRange(indicator, indicator2nd, start_m, end_m);
}
break;
}
DocPos const mlen = end - start;
if ((mlen <= 0) || (end > endPos)) {
// wrong match
_ClearIndicatorInRange(indicator, indicator2nd, start_m, end_m);
break; // wrong match
if (bClearGaps) {
_ClearIndicatorInRange(indicator, indicator2nd, start_m, end_m);
}
break;
}
// URL-specific: if match ends with single-quote and is preceded by one, strip trailing quote
@ -8875,7 +8897,9 @@ static void _UpdateIndicators(const int indicator, const int indicator2nd,
}
}
_ClearIndicatorInRange(indicator, indicator2nd, start_m, end);
if (bClearGaps) {
_ClearIndicatorInRange(indicator, indicator2nd, start_m, end);
}
SciCall_SetIndicatorCurrent(indicator);
SciCall_IndicatorFillRange(start, mlen_adj);
@ -8884,12 +8908,9 @@ static void _UpdateIndicators(const int indicator, const int indicator2nd,
SciCall_IndicatorFillRange(start, mlen_adj);
}
// next occurrence
start = end;
end = endPos;
} while (start < end);
}
//=============================================================================
@ -8907,23 +8928,26 @@ void EditUpdateIndicators(DocPos startPos, DocPos endPos, bool bClearOnly)
return;
}
if (Settings.HyperlinkHotspot) {
_UpdateIndicators(INDIC_NP3_HYPERLINK, INDIC_NP3_HYPERLINK_U, s_pUrlRegEx, startPos, endPos);
_UpdateIndicators(INDIC_NP3_HYPERLINK, INDIC_NP3_HYPERLINK_U, s_pUrlRegEx, startPos, endPos, true);
// Layer bare-path-with-line-spec matches onto the same indicator —
// additive (bClearGaps=false) so the URL matches above survive.
_UpdateIndicators(INDIC_NP3_HYPERLINK, INDIC_NP3_HYPERLINK_U, s_pFileRefRegEx, startPos, endPos, false);
} else {
_ClearIndicatorInRange(INDIC_NP3_HYPERLINK, INDIC_NP3_HYPERLINK_U, startPos, endPos);
}
if (IsColorDefHotspotEnabled()) {
if (Settings.ColorDefHotspot < 3) {
_UpdateIndicators(INDIC_NP3_COLOR_DEF, -1, s_pColorRegEx, startPos, endPos);
_UpdateIndicators(INDIC_NP3_COLOR_DEF, -1, s_pColorRegEx, startPos, endPos, true);
} else {
_UpdateIndicators(INDIC_NP3_COLOR_DEF, -1, s_pColorRegEx_Tr, startPos, endPos);
_UpdateIndicators(INDIC_NP3_COLOR_DEF, -1, s_pColorRegEx_Tr, startPos, endPos, true);
}
} else {
_ClearIndicatorInRange(INDIC_NP3_COLOR_DEF, INDIC_NP3_COLOR_DEF_T, startPos, endPos);
}
if (Settings.HighlightUnicodePoints) {
_UpdateIndicators(INDIC_NP3_UNICODE_POINT, -1, s_pUnicodeRegEx, startPos, endPos);
_UpdateIndicators(INDIC_NP3_UNICODE_POINT, -1, s_pUnicodeRegEx, startPos, endPos, true);
} else {
_ClearIndicatorInRange(INDIC_NP3_UNICODE_POINT, -1, startPos, endPos);
}

View File

@ -34,11 +34,6 @@
#include "DarkMode/DarkMode.h"
#include "Version.h"
#pragma warning(push)
#pragma warning(disable : 4201) // union/struct w/o name
#include "tinyexprcpp/tinyexpr_cif.h"
#pragma warning(pop)
#include "Scintilla.h"
@ -1136,34 +1131,6 @@ void PathFixBackslashes(LPWSTR lpsz)
}
//=============================================================================
//
// SplitFilePathLineNum()
//
bool SplitFilePathLineNum(LPWSTR lpszPath, int* lineNum)
{
LPWSTR const lpszSplit = StrRChr(lpszPath, NULL, L':');
bool res = false;
if (lpszSplit) {
char chLnNumber[128];
char const defchar = (char)0x24;
WideCharToMultiByte(CP_ACP, (WC_COMPOSITECHECK | WC_DISCARDNS), &lpszSplit[1], -1, chLnNumber, COUNTOF(chLnNumber), &defchar, NULL);
te_int_t iExprError = true;
int const ln = (int)te_interp(chLnNumber, &iExprError);
if (!iExprError) {
res = true;
lpszSplit[0] = L'\0'; // split
if (lineNum) {
*lineNum = ln;
}
}
}
return res;
}
//=============================================================================
//
// _IsAllDigitsW() - non-empty wide string consisting solely of ASCII digits
@ -1199,6 +1166,29 @@ static inline void _StripCommonQuoting(HSTRINGW hstr)
}
//=============================================================================
//
// SplitFilePathLineNum()
//
// Strips a trailing ":N" digit-only line-number suffix from lpszPath in
// place and writes N into *lineNum (if non-NULL). Returns true on a strip.
// No expression evaluation — `foo:42` works; `foo:42+1` does NOT.
//
bool SplitFilePathLineNum(LPWSTR lpszPath, int* lineNum)
{
LPWSTR const lpszSplit = StrRChr(lpszPath, NULL, L':');
if (!lpszSplit || !_IsAllDigitsW(lpszSplit + 1)) {
return false;
}
int const ln = _wtoi(lpszSplit + 1);
lpszSplit[0] = L'\0'; // split in place
if (lineNum) {
*lineNum = ln;
}
return true;
}
//=============================================================================
//
// SplitFilePathLineColNum()
@ -1208,7 +1198,7 @@ static inline void _StripCommonQuoting(HSTRINGW hstr)
// foo.c(42) -> line 42
// foo.c,42 -> line 42
// foo.c:42:7 -> line 42, column 7
// foo.c:<expr> -> falls through to SplitFilePathLineNum (te_interp)
// foo.c:42 -> falls through to SplitFilePathLineNum (single colon)
//
// Drive-letter colons ("C:\...") are never treated as line separators.
// Returns true if a suffix was stripped.
@ -1292,7 +1282,7 @@ bool SplitFilePathLineColNum(LPWSTR lpszPath, int* lineNum, int* colNum)
*lastColon = saved;
}
// Single-colon fallback (preserves te_interp expression support)
// Single-colon fallback: digit-only :N parse.
return SplitFilePathLineNum(lpszPath, lineNum);
}
@ -1316,6 +1306,54 @@ static inline bool _IsTokenBoundary(char c)
}
//=============================================================================
//
// _TryMarkdownLinkSpan() - if caret sits inside `(...)` preceded by `]` on
// the same line, return the inner (url) span.
// Handles nested parens via depth counting.
//
static bool _TryMarkdownLinkSpan(DocPos pos, DocPos lineStart, DocPos lineEnd,
DocPos* outStart, DocPos* outEnd)
{
DocPos openP = -1;
int depth = 0;
for (DocPos p = pos; p > lineStart; --p) {
char const c = SciCall_GetCharAt(p - 1);
if (c == ')') {
++depth;
}
else if (c == '(') {
if (depth == 0) {
if (p - 1 > lineStart && SciCall_GetCharAt(p - 2) == ']') {
openP = p - 1;
}
break;
}
--depth;
}
}
if (openP < 0) {
return false;
}
int d2 = 0;
for (DocPos p = pos; p < lineEnd; ++p) {
char const c = SciCall_GetCharAt(p);
if (c == '(') {
++d2;
}
else if (c == ')') {
if (d2 == 0) {
*outStart = openP + 1;
*outEnd = p;
return true;
}
--d2;
}
}
return false;
}
//=============================================================================
//
// ExtractSelectionOrTokenAtCaret()
@ -1324,6 +1362,8 @@ static inline bool _IsTokenBoundary(char c)
// the current selection, or - if the selection is empty - the span around
// the caret bounded by [\s'`"<>|*,;]. Newline-truncates selections.
// Strips surrounding whitespace and quote-like characters.
// Special-cases Markdown link targets: caret inside `[label](url)` parens
// grabs `url` rather than expanding outward.
//
HSTRINGW ExtractSelectionOrTokenAtCaret(void)
{
@ -1349,13 +1389,16 @@ HSTRINGW ExtractSelectionOrTokenAtCaret(void)
DocLn const ln = SciCall_LineFromPosition(pos);
DocPos const lineStart = SciCall_PositionFromLine(ln);
DocPos const lineEnd = SciCall_GetLineEndPosition(ln);
byteStart = pos;
while (byteStart > lineStart && !_IsTokenBoundary(SciCall_GetCharAt(byteStart - 1))) {
--byteStart;
}
byteEnd = pos;
while (byteEnd < lineEnd && !_IsTokenBoundary(SciCall_GetCharAt(byteEnd))) {
++byteEnd;
if (!_TryMarkdownLinkSpan(pos, lineStart, lineEnd, &byteStart, &byteEnd)) {
byteStart = pos;
while (byteStart > lineStart && !_IsTokenBoundary(SciCall_GetCharAt(byteStart - 1))) {
--byteStart;
}
byteEnd = pos;
while (byteEnd < lineEnd && !_IsTokenBoundary(SciCall_GetCharAt(byteEnd))) {
++byteEnd;
}
}
}

View File

@ -4488,6 +4488,22 @@ LRESULT MsgCopyData(HWND hwnd, WPARAM wParam, LPARAM lParam)
//
static DocPos s_iCtxMenuClickPos = -1;
// Returns true if there is something to act on at this position — either a
// non-empty selection (then pos is ignored), or a non-empty word span at
// pos. Pass pos = -1 to test selection-only.
static bool _HasSelectionOrWordAt(DocPos pos)
{
if (!SciCall_IsSelectionEmpty()) {
return true;
}
if (pos < 0) {
return false;
}
DocPos const ws = SciCall_WordStartPosition(pos, true);
DocPos const we = SciCall_WordEndPosition(pos, true);
return (ws != we);
}
LRESULT MsgContextMenu(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam)
{
bool const bMargin = (SCN_MARGINRIGHTCLICK == umsg);
@ -4556,21 +4572,18 @@ LRESULT MsgContextMenu(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam)
ModifyMenu(hStdCtxMenu, CMD_WEBACTION2, MF_BYCOMMAND | MF_STRING, CMD_WEBACTION2, Settings2.WebTmpl2MenuName);
}
// case conversion submenu: enable based on selection or word at click position
// Enable selection-acting commands based on selection-or-word-at-click-position.
s_iCtxMenuClickPos = SciCall_CharPositionFromPointClose(pt.x, pt.y);
bool const ro = SciCall_GetReadOnly();
bool const se = SciCall_IsSelectionEmpty();
bool bHasTarget = !se;
if (se && (s_iCtxMenuClickPos >= 0)) {
DocPos const ws = SciCall_WordStartPosition(s_iCtxMenuClickPos, true);
DocPos const we = SciCall_WordEndPosition(s_iCtxMenuClickPos, true);
bHasTarget = (ws != we);
}
bool const bHasTarget = _HasSelectionOrWordAt(s_iCtxMenuClickPos);
EnableCmd(hStdCtxMenu, CMD_CTX_UPPERCASE, bHasTarget && !ro);
EnableCmd(hStdCtxMenu, CMD_CTX_LOWERCASE, bHasTarget && !ro);
EnableCmd(hStdCtxMenu, CMD_CTX_INVERTCASE, bHasTarget && !ro);
EnableCmd(hStdCtxMenu, CMD_CTX_TITLECASE, bHasTarget && !ro);
EnableCmd(hStdCtxMenu, CMD_CTX_SENTENCECASE, bHasTarget && !ro);
EnableCmd(hStdCtxMenu, IDM_EDIT_OPEN_CONTAINING_FOLDER_SEL, bHasTarget);
EnableCmd(hStdCtxMenu, IDM_EDIT_OPEN_FILE_FROM_SEL, bHasTarget);
}
// back to screen coordinates for menu display
@ -4962,6 +4975,14 @@ LRESULT MsgInitMenu(HWND hwnd, WPARAM wParam, LPARAM lParam)
EnableCmd(hmenu, IDM_EDIT_INSERT_GUID, !ro);
// The selection-based "Open …" commands need either a non-empty selection
// or a token at the caret. Read-only is irrelevant — neither modifies.
{
bool const bHasTarget = _HasSelectionOrWordAt(SciCall_GetCurrentPos());
EnableCmd(hmenu, IDM_EDIT_OPEN_CONTAINING_FOLDER_SEL, bHasTarget);
EnableCmd(hmenu, IDM_EDIT_OPEN_FILE_FROM_SEL, bHasTarget);
}
EnableCmd(hmenu, IDM_EDIT_FIND, !te);
EnableCmd(hmenu, IDM_EDIT_SAVEFIND, !te);
EnableCmd(hmenu, IDM_EDIT_FINDNEXT, !te);
@ -6071,8 +6092,21 @@ void EditOpenContainingFolderFromSelection(void)
return;
}
// SHParseDisplayName doesn't reliably accept the \\?\ long-path prefix.
// Strip it for shell-API use: \\?\C:\… -> C:\…, \\?\UNC\server\share -> \\server\share.
LPCWSTR pszForShell = Path_Get(hpth);
WCHAR shellPath[PATHLONG_MAX_CCH];
if (pszForShell && StrCmpNW(pszForShell, L"\\\\?\\", 4) == 0) {
if (StrCmpNIW(pszForShell + 4, L"UNC\\", 4) == 0) {
StringCchPrintf(shellPath, COUNTOF(shellPath), L"\\\\%s", pszForShell + 8);
} else {
StringCchCopy(shellPath, COUNTOF(shellPath), pszForShell + 4);
}
pszForShell = shellPath;
}
PIDLIST_ABSOLUTE pidl = NULL;
SHParseDisplayName(Path_Get(hpth), NULL, &pidl, SFGAO_BROWSABLE | SFGAO_FILESYSTEM, NULL);
SHParseDisplayName(pszForShell, NULL, &pidl, SFGAO_BROWSABLE | SFGAO_FILESYSTEM, NULL);
if (pidl) {
SHOpenFolderAndSelectItems(pidl, 0, NULL, 0);
ILFree(pidl);
@ -8951,6 +8985,40 @@ void HandleDWellStartEnd(const DocPos position, const UINT uid)
}
}
//=============================================================================
//
// _IsLikelyBarePath()
//
// Pure prefix-shape check mirroring HYPLNK_REGEX_FILEREF's three arms:
// drive letter, UNC `\\…`, or relative `.\` / `..\` (forward slashes too).
// Does NOT verify the path exists or follows the full token grammar — used
// only to decide hyperlink-click routing.
//
static bool _IsLikelyBarePath(LPCWSTR s)
{
if (!s) {
return false;
}
// Drive-letter: <letter>:[\/]
if (((s[0] >= L'A' && s[0] <= L'Z') || (s[0] >= L'a' && s[0] <= L'z'))
&& s[1] == L':' && (s[2] == L'\\' || s[2] == L'/')) {
return true;
}
// UNC: \\<host>
if (s[0] == L'\\' && s[1] == L'\\') {
return true;
}
// Relative: .\, ./, ..\, ../
if (s[0] == L'.' && (s[1] == L'\\' || s[1] == L'/')) {
return true;
}
if (s[0] == L'.' && s[1] == L'.' && (s[2] == L'\\' || s[2] == L'/')) {
return true;
}
return false;
}
//=============================================================================
//
// HandleHotSpotURLClicked()
@ -9011,16 +9079,38 @@ bool HandleHotSpotURLClicked(const DocPos position, const HYPERLINK_OPS operatio
DWORD dCch = COUNTOF(szUnEscW);
int lineNum = -1;
SplitFilePathLineNum(szTextW, &lineNum);
// Use Col-aware splitter so (N), :N:M, ,N suffixes are all stripped
// — bare-path indicators include these forms.
SplitFilePathLineColNum(szTextW, &lineNum, NULL);
lineNum = clampi(lineNum, 0, INT_MAX);
if (((operation & OPEN_IN_NOTEPAD3) || (operation & OPEN_NEW_NOTEPAD3)) && UrlIsFileUrl(szTextW)) {
// A bare Windows path that didn't go through PathCreateFromUrlW.
// Detected by the file-ref indicator regex (HYPLNK_REGEX_FILEREF).
bool const isFileScheme = UrlIsFileUrl(szTextW);
bool const isBarePath = !isFileScheme && _IsLikelyBarePath(szTextW);
if (((operation & OPEN_IN_NOTEPAD3) || (operation & OPEN_NEW_NOTEPAD3)) && (isFileScheme || isBarePath)) {
bool const bReuseWindow = Flags.bReuseWindow && !(operation & OPEN_NEW_NOTEPAD3);
PathCreateFromUrlW(szTextW, szUnEscW, &dCch, 0);
szUnEscW[INTERNET_MAX_URL_LENGTH] = L'\0'; // limit length
StrTrim(szUnEscW, L"/");
if (isFileScheme) {
PathCreateFromUrlW(szTextW, szUnEscW, &dCch, 0);
szUnEscW[INTERNET_MAX_URL_LENGTH] = L'\0'; // limit length
StrTrim(szUnEscW, L"/");
// Discard any trailing #fragment — anchor names are a
// markup concept the editor doesn't resolve, but the file
// half of the URL should still open cleanly.
LPWSTR const hash = StrChrW(szUnEscW, L'#');
if (hash) {
*hash = L'\0';
}
}
else {
// Bare path: szTextW is already the filesystem path text
// (line suffix already stripped above).
StringCchCopy(szUnEscW, COUNTOF(szUnEscW), szTextW);
}
HPATHL hfile_pth = Path_Allocate(szUnEscW);
// Anchor relative file:// URLs to the directory of the current

View File

@ -0,0 +1,422 @@
; Notepad3 lexer test fixtures - 'Open File from Selection' targets.
;
; Each entry appears twice: (1) file:// URL with absolute path,
; (2) the same target as a relative path resolved via the
; document-anchor (this file's directory).
;
; Usage: place the caret on a line (or select the line text) and
; invoke Edit > Open File from Selection. Files whose names
; contain spaces must be selected explicitly - whitespace is a
; token boundary when no selection is active.
; --- styleLexABAQUS ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexABAQUS/example.inp
styleLexABAQUS/example.inp
; --- styleLexAHK2 ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexAHK2/lexer_ahk_v2_smoke.ahk2
styleLexAHK2/lexer_ahk_v2_smoke.ahk2
; --- styleLexAHKL ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexAHKL/AHKL_Test.ahk
styleLexAHKL/AHKL_Test.ahk
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexAHKL/lexer_ahk_v1_smoke.ahk
styleLexAHKL/lexer_ahk_v1_smoke.ahk
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexAHKL/NP3_Test.ahk
styleLexAHKL/NP3_Test.ahk
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexAHKL/NP3_Test_Gui.ahk
styleLexAHKL/NP3_Test_Gui.ahk
; --- styleLexANSI ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexANSI/acme.nfo
styleLexANSI/acme.nfo
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexANSI/DEADLINE.nfo
styleLexANSI/DEADLINE.nfo
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexANSI/file01_id.diz
styleLexANSI/file01_id.diz
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexANSI/file02_id.diz
styleLexANSI/file02_id.diz
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexANSI/file03_id.diz
styleLexANSI/file03_id.diz
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexANSI/FILE_ID.DIZ
styleLexANSI/FILE_ID.DIZ
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexANSI/jerry.nfo
styleLexANSI/jerry.nfo
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexANSI/NFO_01%20-%20BOX.nfo
styleLexANSI/NFO_01 - BOX.nfo
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexANSI/NFO_02%20-%20PENTIUM.nfo
styleLexANSI/NFO_02 - PENTIUM.nfo
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexANSI/wwc94.nfo
styleLexANSI/wwc94.nfo
; --- styleLexASM ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexASM/lowhelpr.asm
styleLexASM/lowhelpr.asm
; --- styleLexAU3 ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexAU3/clipboard_logger.au3
styleLexAU3/clipboard_logger.au3
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexAU3/Configure_NP3%20%28Craigo-%29.au3
styleLexAU3/Configure_NP3 (Craigo-).au3
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexAU3/environment.au3
styleLexAU3/environment.au3
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexAU3/evernote_details.au3
styleLexAU3/evernote_details.au3
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexAU3/free_diskspace.au3
styleLexAU3/free_diskspace.au3
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexAU3/get_putty_sessions.au3
styleLexAU3/get_putty_sessions.au3
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexAU3/logoff.au3
styleLexAU3/logoff.au3
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexAU3/security_eventlog.au3
styleLexAU3/security_eventlog.au3
; --- styleLexAVS ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexAVS/Sample_AVS.avs
styleLexAVS/Sample_AVS.avs
; --- styleLexAwk ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexAwk/mve.awk
styleLexAwk/mve.awk
; --- styleLexBASH ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexBASH/autogen.sh
styleLexBASH/autogen.sh
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexBASH/backup.sh
styleLexBASH/backup.sh
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexBASH/gawk.csh
styleLexBASH/gawk.csh
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexBASH/gettext.sh
styleLexBASH/gettext.sh
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexBASH/perlbin.csh
styleLexBASH/perlbin.csh
; --- styleLexBAT ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexBAT/BAT_01.bat
styleLexBAT/BAT_01.bat
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexBAT/CMD_01.cmd
styleLexBAT/CMD_01.cmd
; --- styleLexCmake ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCmake/minizip-exports.cmake
styleLexCmake/minizip-exports.cmake
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCmake/symbols.cmake
styleLexCmake/symbols.cmake
; --- styleLexCOFFEESCRIPT ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCOFFEESCRIPT/coffeelint.coffee
styleLexCOFFEESCRIPT/coffeelint.coffee
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCOFFEESCRIPT/commandline.coffee
styleLexCOFFEESCRIPT/commandline.coffee
; --- styleLexCONF ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCONF/Example1%20of%20httpd.conf
styleLexCONF/Example1 of httpd.conf
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCONF/Example2%20of%20httpd.conf
styleLexCONF/Example2 of httpd.conf
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCONF/fonts.conf
styleLexCONF/fonts.conf
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCONF/httpd%20%28issue%20%231662%29.conf
styleLexCONF/httpd (issue #1662).conf
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCONF/jsl.node.conf
styleLexCONF/jsl.node.conf
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCONF/mscormmc.cfg
styleLexCONF/mscormmc.cfg
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCONF/WildCard.0809.cfg
styleLexCONF/WildCard.0809.cfg
; --- styleLexCPP ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCPP/Config.cpp
styleLexCPP/Config.cpp
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCPP/DBCS.cxx
styleLexCPP/DBCS.cxx
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCPP/DefaultLexer.cxx
styleLexCPP/DefaultLexer.cxx
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCPP/Empty.cpp
styleLexCPP/Empty.cpp
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCPP/Notepad3.c
styleLexCPP/Notepad3.c
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCPP/Test_Replace%20%28issue%20%231901%29.cxx
styleLexCPP/Test_Replace (issue #1901).cxx
; --- styleLexCS ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCS/SecurityPage.cs
styleLexCS/SecurityPage.cs
; --- styleLexCSS ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCSS/markdown.css
styleLexCSS/markdown.css
; --- styleLexCSV ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCSV/Sample3.csv
styleLexCSV/Sample3.csv
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCSV/SampleCSVFile_1kb.csv
styleLexCSV/SampleCSVFile_1kb.csv
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCSV/SampleCSVFile_2kb.csv
styleLexCSV/SampleCSVFile_2kb.csv
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCSV/SampleCSVFile_4kb.csv
styleLexCSV/SampleCSVFile_4kb.csv
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexCSV/SampleCSVFile_inconsistent.csv
styleLexCSV/SampleCSVFile_inconsistent.csv
; --- styleLexD ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexD/Sample_D.d
styleLexD/Sample_D.d
; --- styleLexDart ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexDart/ExampleCode.dart
styleLexDart/ExampleCode.dart
; --- styleLexDIFF ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexDIFF/Fix%20broken%20Style%20Scheme%20Export%20%28%231409%29.diff
styleLexDIFF/Fix broken Style Scheme Export (#1409).diff
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexDIFF/Fix%20broken%20Style%20Scheme%20Export%20%28%231409%29.patch
styleLexDIFF/Fix broken Style Scheme Export (#1409).patch
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexDIFF/float.patch
styleLexDIFF/float.patch
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexDIFF/No%20pre-defined%20font%20for%20Text%20Files%20%28%231445%29.diff
styleLexDIFF/No pre-defined font for Text Files (#1445).diff
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexDIFF/No%20pre-defined%20font%20for%20Text%20Files%20%28%231445%29.patch
styleLexDIFF/No pre-defined font for Text Files (#1445).patch
; --- styleLexFortran ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexFortran/cylinder.f90
styleLexFortran/cylinder.f90
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexFortran/Fortran_Control.f90
styleLexFortran/Fortran_Control.f90
; --- styleLexGo ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexGo/Go_goroutine_channel.go
styleLexGo/Go_goroutine_channel.go
; --- styleLexHTML ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexHTML/Design.html
styleLexHTML/Design.html
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexHTML/MultiLang.html
styleLexHTML/MultiLang.html
; --- styleLexINNO ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexINNO/AllPagesExample.iss
styleLexINNO/AllPagesExample.iss
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexINNO/Example3.iss
styleLexINNO/Example3.iss
; --- styleLexJAVA ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexJAVA/DecryptNotepad3.java
styleLexJAVA/DecryptNotepad3.java
; --- styleLexJS ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexJS/codepen.js
styleLexJS/codepen.js
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexJS/environment.js
styleLexJS/environment.js
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexJS/pythonMain.js
styleLexJS/pythonMain.js
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexJS/test_script.js
styleLexJS/test_script.js
; --- styleLexJSON ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexJSON/Sample_JSON%20%28issue%20%233070%29.json
styleLexJSON/Sample_JSON (issue #3070).json
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexJSON/Sample_JSON5_extended.json
styleLexJSON/Sample_JSON5_extended.json
; --- styleLexJulia ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexJulia/x.jl
styleLexJulia/x.jl
; --- styleLexKiX ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexKiX/demo.kix
styleLexKiX/demo.kix
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexKiX/kick.kix
styleLexKiX/kick.kix
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexKiX/kixtart.kix
styleLexKiX/kixtart.kix
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexKiX/plt.kix
styleLexKiX/plt.kix
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexKiX/test.kix
styleLexKiX/test.kix
; --- styleLexKotlin ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexKotlin/CommandLine%20%28issue%20%233343%29.kt
styleLexKotlin/CommandLine (issue #3343).kt
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexKotlin/ExampleCode.kt
styleLexKotlin/ExampleCode.kt
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexKotlin/NetworkConnectionMonitor.kts
styleLexKotlin/NetworkConnectionMonitor.kts
; --- styleLexLATEX ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexLATEX/Sample_LATEX.tex
styleLexLATEX/Sample_LATEX.tex
; --- styleLexLUA ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexLUA/Sample_LUA.lua
styleLexLUA/Sample_LUA.lua
; --- styleLexMAK ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexMAK/scintilla.mak
styleLexMAK/scintilla.mak
; --- styleLexMARKDOWN ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexMARKDOWN/README.md
styleLexMARKDOWN/README.md
; --- styleLexMATLAB ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexMATLAB/bresenham.m
styleLexMATLAB/bresenham.m
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexMATLAB/evidence.m
styleLexMATLAB/evidence.m
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexMATLAB/example.m
styleLexMATLAB/example.m
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexMATLAB/ThinICAnew.m
styleLexMATLAB/ThinICAnew.m
; --- styleLexNim ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexNim/aliases.nim
styleLexNim/aliases.nim
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexNim/sugar.nim
styleLexNim/sugar.nim
; --- styleLexNSIS ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexNSIS/GeneratorWizard.nsi
styleLexNSIS/GeneratorWizard.nsi
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexNSIS/PortableApps.comLauncher.nsi
styleLexNSIS/PortableApps.comLauncher.nsi
; --- styleLexPAS ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexPAS/nsis.pas
styleLexPAS/nsis.pas
; --- styleLexPL ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexPL/CA.pl
styleLexPL/CA.pl
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexPL/svn.pl
styleLexPL/svn.pl
; --- styleLexPROPS ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexPROPS/html.properties
styleLexPROPS/html.properties
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexPROPS/INI_Notepad3_01.ini
styleLexPROPS/INI_Notepad3_01.ini
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexPROPS/INI_Old_Reg_01.ini
styleLexPROPS/INI_Old_Reg_01.ini
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexPROPS/usbxhci.inf
styleLexPROPS/usbxhci.inf
; --- styleLexPS ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexPS/PS1_Version_NP3.ps1
styleLexPS/PS1_Version_NP3.ps1
; --- styleLexPY ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexPY/hello_world.py
styleLexPY/hello_world.py
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexPY/python.py
styleLexPY/python.py
; --- styleLexR ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexR/Sample_R.r
styleLexR/Sample_R.r
; --- styleLexRC ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexRC/np3_en_us.rc
styleLexRC/np3_en_us.rc
; --- styleLexRegistry ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexRegistry/REG_01.reg
styleLexRegistry/REG_01.reg
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexRegistry/REG_02.reg
styleLexRegistry/REG_02.reg
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexRegistry/Registry.reg
styleLexRegistry/Registry.reg
; --- styleLexRUBY ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexRUBY/test.rb
styleLexRUBY/test.rb
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexRUBY/test_erb.rb
styleLexRUBY/test_erb.rb
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexRUBY/test_logger.rb
styleLexRUBY/test_logger.rb
; --- styleLexRust ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexRust/Rust.rs
styleLexRust/Rust.rs
; --- styleLexSQL ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexSQL/SQL-File-10-Rows.sql
styleLexSQL/SQL-File-10-Rows.sql
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexSQL/SQL-TestData.sql
styleLexSQL/SQL-TestData.sql
; --- styleLexTCL ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexTCL/branch_create.tcl
styleLexTCL/branch_create.tcl
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexTCL/commit.tcl
styleLexTCL/commit.tcl
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexTCL/option.tcl
styleLexTCL/option.tcl
; --- styleLexTEXT ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexTEXT/Find.since.certain_current.line.not.1st.line%20%28issue%20%233107%29.txt
styleLexTEXT/Find.since.certain_current.line.not.1st.line (issue #3107).txt
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexTEXT/License.txt
styleLexTEXT/License.txt
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexTEXT/Malayan_Problem.txt
styleLexTEXT/Malayan_Problem.txt
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexTEXT/regex_find.txt
styleLexTEXT/regex_find.txt
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexTEXT/strange_chacter_visual_artefacts.txt
styleLexTEXT/strange_chacter_visual_artefacts.txt
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexTEXT/TXT_Readme_01.txt
styleLexTEXT/TXT_Readme_01.txt
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexTEXT/TXT_Test_20_Long_Lines.txt
styleLexTEXT/TXT_Test_20_Long_Lines.txt
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexTEXT/TXT_The_quick_brown_fox.txt
styleLexTEXT/TXT_The_quick_brown_fox.txt
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexTEXT/TXT_vertical-selection-bug.txt
styleLexTEXT/TXT_vertical-selection-bug.txt
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexTEXT/TXT_Word-Wrapped_1_Lines.txt
styleLexTEXT/TXT_Word-Wrapped_1_Lines.txt
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexTEXT/TXT_Word-Wrapped_many_Lines.txt
styleLexTEXT/TXT_Word-Wrapped_many_Lines.txt
; --- styleLexTOML ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexTOML/TOML.toml
styleLexTOML/TOML.toml
; --- styleLexVB ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexVB/Sample_VB.vb
styleLexVB/Sample_VB.vb
; --- styleLexVBS ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexVBS/base.vbs
styleLexVBS/base.vbs
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexVBS/VBS_01.vbs
styleLexVBS/VBS_01.vbs
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexVBS/VBS_02.vbs
styleLexVBS/VBS_02.vbs
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexVBS/wisubstg.vbs
styleLexVBS/wisubstg.vbs
; --- styleLexVerilog ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexVerilog/SystemVerilog_example.sv
styleLexVerilog/SystemVerilog_example.sv
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexVerilog/Verilog_example.v
styleLexVerilog/Verilog_example.v
; --- styleLexVHDL ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexVHDL/Sample_VHDL.vhdl
styleLexVHDL/Sample_VHDL.vhdl
; --- styleLexXML ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexXML/MiniPath.exe.manifest
styleLexXML/MiniPath.exe.manifest
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexXML/Sample%20of%20FFS_GUI.ffs_gui
styleLexXML/Sample of FFS_GUI.ffs_gui
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexXML/Sample%20of%20NZB.nzb
styleLexXML/Sample of NZB.nzb
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexXML/XML_01.xml
styleLexXML/XML_01.xml
; --- styleLexYAML ---
file:///D:/DEV/GitHub/Notepad3/test/test_files/StyleLexers/styleLexYAML/appveyor.yml
styleLexYAML/appveyor.yml