MarkdownViewer is a Windows project for viewing Markdown files with two primary targets:
- a standalone WPF desktop application:
MdViewer - a Total Commander Lister plugin:
MdViewerWlxfor*.mdand related formats
Rendering is based on Markdig for Markdown parsing and WebView2 for HTML display. The WLX plugin uses a native C bootstrapper that loads the managed .NET components through hostfxr.
- Platform: Windows x64
- UI technology: WPF
- Runtime: .NET 10 Windows Desktop
- Markdown parser:
Markdig - HTML renderer:
Microsoft.Web.WebView2 - Total Commander plugin type:
WLX(Lister plugin)
- Renders Markdown as HTML inside a WPF/WebView2 UI
- Uses advanced Markdig extensions via
UseAdvancedExtensions() - Supports emoji and smiley syntax
- Uses GitHub-inspired CSS with light/dark adaptation via
prefers-color-scheme - Sets a
basepath for relative content from Markdown files - WLX plugin with Unicode entry points for Total Commander x64
- Fallback-first WLX behavior with immediate text preview
- Automatic switch to HTML/WebView2 once initialization completes
MarkdownViewer/
|- MarkdownViewer/ Desktop app (MdViewer)
|- MdViewer.Shared/ Shared rendering and logging components
|- MdViewerWlx/ Managed WLX layer for Total Commander
|- tools/
| |- NativeBootstrapper/
| | |- wlx_bootstrapper.c Native WLX bootstrapper in C
| | |- build-wlx.ps1 Build, deploy, and packaging script
| | |- pluginst.inf Total Commander installer descriptor
|- deploy/
| |- wlx/ Assembled WLX deploy directory
| |- MdViewerWlx-<version>-tc.zip Total Commander installation ZIP
|- README.md
The repository root contains MdViewer.slnx.
Standalone WPF desktop application with the following profile:
- Project file:
MarkdownViewer\MarkdownViewer.csproj - Output type:
WinExe - Target framework:
net10.0-windows - WPF enabled
- App icon:
MarkdownViewer\Resources\app.ico
The application uses MdViewer.Shared for rendering and logging.
Shared library used by both the desktop application and the WLX plugin.
Important files:
MdViewer.Shared\MarkdownRenderService.csMdViewer.Shared\FileLogger.cs
Responsibilities:
- render Markdown into a complete HTML document
- provide GitHub-inspired CSS
- write error and status logs to files
Managed part of the Total Commander plugin.
- Project file:
MdViewerWlx\MdViewerWlx.csproj - Output type:
Exe - Target framework:
net10.0-windows - Platform:
x64 - WPF enabled
Why Exe instead of Dll:
- the native bootstrapper initializes the CLR in app mode through
hostfxr_initialize_for_dotnet_command_line - this requires
MdViewerWlx.exeas the app host - this is necessary so WPF and WebView2 dependencies are resolved in the correct app context
Important files:
MdViewerWlx\WlxExports.csMdViewerWlx\WlxHost.csMdViewerWlx\WlxContentView.csMdViewerWlx\Program.cs
The Total Commander plugin is split into two layers.
File: tools\NativeBootstrapper\wlx_bootstrapper.c
Responsibilities:
- loaded directly by Total Commander as
.wlxor.wlx64 - exports the WLX functions
- handles
ListGetDetectStringnatively - loads
hostfxr.dll - initializes the .NET runtime
- loads managed export methods from
MdViewerWlx.dll
Important implementation details:
- uses
hdt=5forload_assembly_and_get_function_pointer - uses
hostfxr_initialize_for_dotnet_command_line - passes
MdViewerWlx.exeasargv[0] - supports both ANSI and Unicode WLX API variants
Exported WLX functions:
ListGetDetectStringListGetDetectStringWListLoadListLoadWListLoadNextListLoadNextWListCloseWindow
Current detect string:
ext="MD" | ext="MARKDOWN" | ext="MDOWN" | ext="RST"
Files:
MdViewerWlx\WlxExports.csMdViewerWlx\WlxHost.csMdViewerWlx\WlxContentView.cs
Responsibilities:
- receives WLX calls forwarded from the native layer
- creates a WPF child window inside the Total Commander parent window
- initializes WebView2 in a delayed and robust way
- shows a text preview until WebView2 is ready
Hosting details:
HwndSourceis created directly on the calling Total Commander UI thread- this avoids child-window creation deadlocks
- actual WebView2 initialization is delayed slightly via
DispatcherTimer - focus and z-order are corrected explicitly so the first Lister instance does not remain behind Total Commander
Rendering is implemented in MdViewer.Shared\MarkdownRenderService.cs.
Enabled Markdig pipeline features:
UseAdvancedExtensions()UseEmojiAndSmiley()UseSoftlineBreakAsHardlineBreak()
Additional behavior:
- relative paths are resolved using a
file:///basetag - empty files and file-read errors return a full HTML document instead of throwing exceptions
- light and dark CSS is embedded directly into the HTML output
File: MdViewer.Shared\FileLogger.cs
The logger writes log files into a Log subdirectory next to the executable:
[ProgramFolder]\Log\log-YYYY-MM-DD.log
Examples:
- desktop app: next to
MdViewer.exe - WLX plugin: inside the plugin folder next to
MdViewerWlx.wlxandMdViewerWlx.exe
The remaining logging is intended for production use and focuses on errors and warnings.
- Windows x64
- installed .NET desktop runtime or SDK compatible with
net10.0-windows - installed WebView2 Runtime
- for Total Commander: x64 Total Commander is recommended
- .NET 10 SDK
- One of the following native toolchains:
an initialized MSVC developer environment with cl.exe on PATH
or:
C:\Program Files\Microsoft Visual Studio\18\Insiders\SDK\ScopeCppSDK\vc15
The WLX build script first uses cl.exe from PATH. If none is available, it falls back to the ScopeCppSDK path above.
Example:
dotnet build "D:\Projekte\MarkdownViewer\MarkdownViewer\MarkdownViewer.csproj" -c ReleaseThe recommended path is the build script:
& "D:\Projekte\MarkdownViewer\tools\NativeBootstrapper\build-wlx.ps1"The script performs the following steps:
- runs
dotnet publishforMdViewerWlx - compiles
wlx_bootstrapper.cintoMdViewerWlx.wlx - duplicates the binary to
MdViewerWlx.wlx64 - assembles all required files into
deploy\wlx - creates an installable Total Commander ZIP package
Important build outputs:
- Deploy directory:
D:\Projekte\MarkdownViewer\deploy\wlx
- Installation ZIP:
D:\Projekte\MarkdownViewer\deploy\MdViewerWlx-<version>-tc.zip
The version number is read automatically from tools\NativeBootstrapper\pluginst.inf.
GitHub Releases are built automatically from version tags.
Workflow file:
.github\workflows\release-wlx.yml
Release flow:
- update
tools\NativeBootstrapper\pluginst.inf - set
version=to the release version, for example1.0 - create and push a matching Git tag, for example
v1.0 - GitHub Actions builds
deploy\MdViewerWlx-<version>-tc.zip - the workflow publishes that ZIP as a GitHub Release asset
Important rule:
- the pushed Git tag must match the plugin version from
pluginst.inf - example:
version=1.0requires tagv1.0
Typical contents of deploy\wlx:
MdViewerWlx.wlxMdViewerWlx.wlx64MdViewerWlx.dllMdViewerWlx.exeMdViewerWlx.deps.jsonMdViewerWlx.runtimeconfig.jsonMdViewer.Shared.dllMarkdig.dllMicrosoft.Web.WebView2.Core.dllMicrosoft.Web.WebView2.Wpf.dllWebView2Loader.dllpluginst.infruntimes\...
- Open Total Commander
- Go to
Configuration -> Plugins -> Install plugin - Select
D:\Projekte\MarkdownViewer\deploy\MdViewerWlx-<version>-tc.zip - Total Commander reads
pluginst.inffrom the ZIP and installs the plugin automatically
- Copy the contents of
deploy\wlxinto a plugin folder - Register the plugin in Total Commander or install it through
pluginst.inf
Active plugin folder in the current development environment:
C:\wincmd\plugins\wlx\wlx_mdviewer
Active Total Commander configuration file in the current development environment:
C:\Users\ThomasLeclaire\AppData\Roaming\GHISLER\WINCMD.INI
The relevant entry is stored in the [ListerPlugins] section.
When opening a Markdown file with F3:
- Total Commander loads
MdViewerWlx.wlxorMdViewerWlx.wlx64 - the native bootstrapper initializes the .NET runtime
- the managed layer creates a WPF child window
- an immediate text preview is shown first
- the view then switches to the rendered HTML/WebView2 display
This behavior is intentional so the first load stays usable even when WebView2 startup is slow.
- the project is Windows-only by design
- the WLX plugin is x64-focused
- the first WebView2 startup can be slower than later launches
- the native WLX build needs either an MSVC developer environment on
PATHor the configured ScopeCppSDK fallback path - the installation ZIP is intended for Total Commander and contains
pluginst.infat the ZIP root
README.mdMarkdownViewer\MarkdownViewer.csprojMdViewer.Shared\MarkdownRenderService.csMdViewer.Shared\FileLogger.csMdViewerWlx\MdViewerWlx.csprojMdViewerWlx\WlxExports.csMdViewerWlx\WlxHost.csMdViewerWlx\WlxContentView.cstools\NativeBootstrapper\wlx_bootstrapper.ctools\NativeBootstrapper\build-wlx.ps1tools\NativeBootstrapper\pluginst.inf
- for rendering changes,
MdViewer.Sharedis usually the main entry point - for WLX loading issues, start with the native bootstrapper and
WlxExports - for display issues inside Total Commander Lister, inspect
WlxHostandWlxContentView - for testing production artifacts, use
deploy\wlxor the versioned Total Commander ZIP rather than arbitrary files from intermediatebin\folders