Turn unreadable FIX wire captures into scannable, one-message-per-line text — and inspect any message's fields without leaving Notepad++.
Pretty-print is the headline feature. A 50 MB single-line capture becomes a scrollable, line-addressable log in one command, regardless of which delimiter convention the log uses.
- Pretty-print FIX log — normalizes any common delimiter convention and puts one FIX message per line in a single undoable edit.
- Multi-delimiter input — accepts literal SOH (
0x01), pipe (|), caret-A (^A), and the\u0001escape, with automatic detection and an alert when detection is ambiguous. - Hover quickview — hover a FIX line to see a parsed summary in a calltip.
- Dock + history — double-click a FIX line to open a dockable panel with the full field breakdown and a lookback history of inspected messages.
- QuickFIX dictionaries — tag names, types, and enum descriptions resolved per message with automatic version detection (FIX.4.0–FIX.5.0 SP2 / FIXT.1.1).
- Open a FIX log (any delimiter convention).
- Plugins → FixParser → Pretty-print FIX log.
- Each message is reformatted to
tag=value|tag=value|…, one per line.Ctrl+Zrestores the original buffer.
src/core/ Parser library — pure C++17, no Win32 / Notepad++ deps
src/plugin/ Plugin shell (menu, lifecycle, pretty-print command)
src/ui/ Dock panel, hover hooks, field renderer
test/unit/ Catch2 unit tests for the core library
cmake/ FetchDictionaries.cmake, FetchNppSdk.cmake (pinned downloads)
resources/dictionaries/ NOTICE for the dictionaries
third_party/quickfix-spec/ Vendored dictionary fallback (used when offline)
test/data/ Real-world FIX corpora by source (fixsim, quickfix, onixs, fxcm)
The src/core/ library has no Windows dependencies and is fully unit-tested on
its own. The Notepad++ integration layers (src/plugin/, src/ui/) wrap it.
- Windows 10/11
- Visual Studio 2022 (or Build Tools) with the C++ workload (MSVC + Windows SDK)
- CMake ≥ 3.21
- vcpkg — dependencies are declared in
vcpkg.json(manifest mode) and installed automatically at configure time.
VCPKG_ROOTmust be set, and how you set it matters. The vcpkg toolchain inCMakePresets.jsonresolves$env{VCPKG_ROOT}. If that variable is empty at configure time it silently collapses to an invalid toolchain path (/scripts/buildsystems/vcpkg.cmake), vcpkg never runs,find_packagefails, and you get a broken/empty build tree. A session-only$env:VCPKG_ROOT = "…"(below) is fine for command-line builds in that same terminal, but VS Code and Visual Studio launched from Explorer only inherit persistent User/System variables. For the IDE flows, set it persistently once and restart the IDE:setx VCPKG_ROOT "C:\path\to\vcpkg" # persistent; takes effect in new processes
$env:VCPKG_ROOT = "C:\path\to\vcpkg" # this terminal only — see note above
cmake --preset vs2022
cmake --build --preset vs2022-debug
ctest --preset vs2022-debugDependencies (pugixml, catch2) are resolved by vcpkg manifest mode. The
manifest deliberately does not pin a builtin-baseline, so it resolves
against whatever commit your active vcpkg checkout ($VCPKG_ROOT) is at. This is
what lets CI "just work" on GitHub's preinstalled vcpkg (whose commit GitHub
controls and changes over time) without a baseline the runner might not contain.
If you ever need reproducibility, re-pin by adding "builtin-baseline": "<commit>"
to vcpkg.json (that becomes the override for both local and CI).
Because neither side pins a baseline, your local vcpkg and the CI runner's vcpkg
can drift to different commits. For pugixml + catch2 (very stable ports) this
rarely matters, but if you want your local builds to match CI exactly:
-
Find the commit CI used — every CI run prints "Runner vcpkg commit:
<sha>" in its job summary (and build log). -
Check your local vcpkg out to that commit:
git -C $env:VCPKG_ROOT fetch origin git -C $env:VCPKG_ROOT checkout <sha>
(Re-run
bootstrap-vcpkg.batif vcpkg asks you to.) Deletebuild/<preset>/and re-configure so the new ports take effect.
To go the other way — make CI match a commit you choose — pin builtin-baseline
as described above; both sides then use exactly that commit.
cmake/FetchDictionaries.cmake provides the QuickFIX dictionaries at configure
time, writing them to build/<preset>/dictionaries/:
- Default: download from a pinned
quickfix/quickfixcommit, verified by SHA-512. - Fallback: if the download fails (offline / air-gapped) the build copies the
vendored copies committed under
third_party/quickfix-spec/. - Pass
-DFIXPARSER_FETCH_DICTIONARIES=OFFto skip the network entirely and always use the vendored copies.
Real-world FIX corpora live under test/data/, organized by source with a
SOURCE.md (origin, license, retrieval date) in each folder. See
test/data/README.md.
The parser core is held to a high bar; the checks below run in CI (see
.github/workflows/) and can all be run locally via CMake presets. Each is
scoped to first-party code (src/core) — pugixml, Catch2, and the Notepad++ SDK
are excluded.
| Tool | Preset / how | What it catches |
|---|---|---|
| Strict warnings | built into ci (FIXPARSER_WARNINGS_AS_ERRORS=ON) |
/W4 /permissive- /WX (MSVC); -Wconversion -Wshadow -Wformat=2 -Werror (GCC/Clang) on the core |
| AddressSanitizer | cmake --preset sanitize then build + ctest |
buffer overruns, use-after-free, double-free — e.g. an over-read when a BodyLength/NumInGroup count lies |
| clang-tidy | cmake --preset tidy + build |
bugprone-*, clang-analyzer-*, cert-* (strict, build-breaking) plus cppcoreguidelines/performance/portability/readability (advisory); config in .clang-tidy |
| libFuzzer | cmake --preset fuzz + build, then run build/fuzz/fixparser_fuzzer.exe |
coverage-guided fuzzing of the full parse pipeline under ASan; seed from test/fuzz/corpus + test/data/ |
| CodeQL | .github/workflows/codeql.yml |
semantic security analysis (security-extended) of the C++ |
| Secret scanning | .github/workflows/security.yml (gitleaks) |
committed credentials/keys across history |
| Dependabot | .github/dependabot.yml |
outdated GitHub Actions (weekly, 21-day cooldown) |
Run the fuzzer for a few hours to shake out real bugs:
cmake --preset fuzz
cmake --build --preset fuzz
# First dir collects new finds; the rest are read-only seeds.
build\fuzz\fixparser_fuzzer.exe -max_len=65536 build\fuzz-corpus test\fuzz\seeds test\data\fixsimUBSan note: UndefinedBehaviorSanitizer is not available with the MSVC driver (
cl), so thesanitizepreset is ASan-only. The libFuzzer build uses clang-cl, which does support UBSan — wiring it into the fuzz target is a reasonable next step.
CMake finds a toolchain but does not install one. To make a local box match the
CI runner (LLVM/clang-cl for the sanitize/tidy/fuzz presets, plus Ninja and
CMake), run once:
pwsh scripts/setup-dev-env.ps1 # add -CheckOnly to only reportIt installs the prerequisites via winget and verifies Visual Studio (MSVC) and
VCPKG_ROOT. The sanitize/tidy/fuzz presets need clang/clang-tidy/clang-cl
on PATH, so run them from a Visual Studio Developer prompt with LLVM's bin
directory on PATH.
A one-command loop sets up a real portable Notepad++ with the plugin deployed and a sample log open, so you can build → F5 → step into plugin code.
Prerequisites: the VS Code C/C++ extension (ms-vscode.cpptools, provides the
cppvsdbg debugger) and a persistent VCPKG_ROOT (see the setx note under
Building — a terminal-only $env:VCPKG_ROOT is not seen by VS Code,
including the CMake Tools extension and the F5 build task). When you open this repo
in VS Code it will prompt to install the recommended extensions (from
.vscode/extensions.json) — accept it, or install them from the Extensions view's
"Recommended" section.
If configure already failed once with an unset
VCPKG_ROOT, thebuild/<preset>/tree holds a poisoned cache with the bad toolchain path baked in; re-configuring won't repair it. Delete the build directory (Remove-Item -Recurse -Force build\vs2022) and configure again after settingVCPKG_ROOTpersistently.
Use it: pick Debug FixParser in Notepad++ (moderate log) (or the 50 MB
single-line variant) from the Run and Debug panel and press F5. The
preLaunchTask builds the plugin, downloads portable Notepad++, generates the
sample logs, and deploys — then launches N++ on the sample so your breakpoints in
src/plugin / src/ui hit.
What the local-dev CMake targets do (all excluded from ALL/CI):
| Target | Action |
|---|---|
npp_fetch |
Download + extract pinned portable Notepad++ to tools/npp/ |
gen_sample_log |
Generate tools/sample/sample-multiline.fix (~25k msgs) and sample-singleline.fix (~50 MB) |
deploy_local |
Copy FixParser.dll + .pdb + dictionaries into tools/npp/plugins/FixParser/ |
local_env |
All of the above (the VS Code preLaunchTask) |
The plugin is built with the static triplet (x64-windows-static), so the
deployed DLL is self-contained — it loads in a stock portable Notepad++ with no
extra runtime DLLs, including Debug builds. Everything under tools/ is generated
and git-ignored.
Files must be open in UTF-8 or ANSI encoding in Notepad++. FIX's structural characters are all ASCII, so parsing is encoding-agnostic; non-ASCII bytes in free-text fields are passed through and rendered using the buffer's code page. UTF-16 files should be converted via the Encoding menu first.
Releases are automated with release-please (run as a GitHub App) and gated by two approvals:
- Merge feature PRs to
main. release-please opens/updates a "release PR" (chore(main): release X.Y.Z) with the version bump + changelog. Versioning isalways-bump-patch. - Merge the release PR (approval #1). This creates a draft GitHub release — mutable, and with no git tag yet (the tag is created when it's published).
- Approve the
releaseenvironment (approval #2). Thepublishjob builds x64/Win32, verifies each DLL's embedded version matches the release, attaches the four zips to the draft, then publishes it — which creates the tag and locks the release with its assets. (Assets must be attached before publish: immutable releases reject changes to an already-published release.)
To force a specific version (e.g. skipping a number), put a Release-As: X.Y.Z
footer in a commit that lands on main.
Never reuse a version number. This repo has GitHub immutable releases enabled: once a release is published its tag name is reserved permanently, even if you later delete the release in the UI. Re-publishing that version fails with "tag_name was used by an immutable release" and a blocked tag
creationrule — bump to the next version instead.
Implementation note: because a draft release has no tag, a single release-please invocation would re-propose the just-released version as a duplicate PR. The workflow therefore runs release-please in two phases — create-release (skipping PR management) and manage-PR (skipped on the run that just cut a release) — so no duplicate is ever opened.
GPL-2.0. See LICENSE. Third-party components and their licenses are
listed in THIRD_PARTY_NOTICES.md; the bundled FIX
dictionaries are covered by the QuickFIX Software License (see
resources/dictionaries/NOTICE.txt).