Render GFM tables in markdown viewer plugin - #114
Merged
Conversation
… sort out but we'll do those on different commits
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The hand-rolled markdown parser had no table support, so pipe-delimited rows fell through to paragraph accumulation and rendered as one wrapped block of text. Add GFM table detection (header + delimiter row), HTML emission with column alignment from `:---:` style delimiters, inline markdown inside cells, padding/truncation of mismatched rows, escaped `\|` handling, and matching light/dark CSS for <table>/<th>/<td>. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds table rendering support to the macOS MarkdownViewer++ plugin and brings supporting plugin/docking scaffolding into the PaperWasp macOS build so the Markdown preview can be packaged and shown as a docked native panel.
Changes:
- Adds GFM-style table parsing/emission and table styling to the macOS Markdown viewer.
- Adds macOS plugin docking support and routes NPP docking messages.
- Imports upstream MarkdownViewer++ reference files and packaging/license metadata.
Reviewed changes
Copilot reviewed 62 out of 92 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
plugins/markdownViewerPlusPlus/src/macOS/MarkdownViewerPlusPlus.mm |
Adds table parsing, HTML emission, and table CSS for the native macOS viewer. |
macos/platform/plugin_docking.h |
Declares plugin docking panel helpers. |
macos/platform/plugin_docking.mm |
Implements native macOS docking container behavior for plugin panels. |
macos/platform/panel_layout.mm |
Integrates plugin docking visibility into right-side panel layout. |
macos/platform/nppm_handler.mm |
Handles NPP docking messages for plugins. |
macos/CMakeLists.txt |
Builds and packages MarkdownViewerPlusPlus and plugin docking support. |
PowerEditor/src/MISC/sha2/sha-256.h |
Adds standard integer/size includes needed by the header API. |
plugins/markdownViewerPlusPlus/IMPORT.txt |
Documents upstream import source and macOS port location. |
plugins/markdownViewerPlusPlus/README.md |
Adds upstream MarkdownViewer++ README reference. |
plugins/markdownViewerPlusPlus/LICENSE.md |
Adds upstream MIT license. |
plugins/markdownViewerPlusPlus/.gitignore |
Adds upstream Visual Studio ignore rules. |
plugins/markdownViewerPlusPlus/.gitattributes |
Adds upstream Git attributes. |
plugins/markdownViewerPlusPlus/_config.yml |
Adds upstream GitHub Pages config. |
plugins/markdownViewerPlusPlus/appveyor.yml |
Adds upstream CI config. |
plugins/markdownViewerPlusPlus/license/* |
Adds third-party license notices. |
plugins/markdownViewerPlusPlus/MarkdownViewerPlusPlus/** |
Adds upstream C#/WinForms reference implementation and resources. |
Files not reviewed (7)
- plugins/markdownViewerPlusPlus/MarkdownViewerPlusPlus/Forms/AboutDialog.Designer.cs: Language not supported
- plugins/markdownViewerPlusPlus/MarkdownViewerPlusPlus/Forms/AbstractRenderer.Designer.cs: Language not supported
- plugins/markdownViewerPlusPlus/MarkdownViewerPlusPlus/Forms/MarkdownViewerOptions.Designer.cs: Language not supported
- plugins/markdownViewerPlusPlus/MarkdownViewerPlusPlus/Forms/OptionsPanelGeneral.Designer.cs: Language not supported
- plugins/markdownViewerPlusPlus/MarkdownViewerPlusPlus/Forms/OptionsPanelHTML.Designer.cs: Language not supported
- plugins/markdownViewerPlusPlus/MarkdownViewerPlusPlus/Forms/OptionsPanelPDF.Designer.cs: Language not supported
- plugins/markdownViewerPlusPlus/MarkdownViewerPlusPlus/Properties/Resources.Designer.cs: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+370
to
+377
| if (parseTableDelimiter(lines[i + 1], aligns)) | ||
| { | ||
| auto headerCells = splitTableCells(stripped); | ||
| if (!headerCells.empty()) | ||
| { | ||
| flushParagraph(); | ||
| closeList(); | ||
| i = emitTable(html, lines, i, aligns); |
Per GFM spec the header row must match the delimiter row in cell count, otherwise the block is not a table. Previously a mismatch was padded / truncated like body rows, which could swallow plain text that happens to contain pipes when followed by a `---` line. Tighten the gate to `headerCells.size() == aligns.size()` and drop the now-redundant header normalization inside emitTable; body rows still pad/truncate as before. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hybridmachine
added a commit
that referenced
this pull request
Jun 13, 2026
…alEffectView dealloc crash (#116) * Render GFM tables in the markdown viewer plugin The hand-rolled markdown parser had no table support, so pipe-delimited rows fell through to paragraph accumulation and rendered as one wrapped block of text. Add GFM table detection (header + delimiter row), HTML emission with column alignment from `:---:` style delimiters, inline markdown inside cells, padding/truncation of mismatched rows, escaped `\|` handling, and matching light/dark CSS for <table>/<th>/<td>. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Address PR #114 review: require header column count to match delimiter Per GFM spec the header row must match the delimiter row in cell count, otherwise the block is not a table. Previously a mismatch was padded / truncated like body rows, which could swallow plain text that happens to contain pipes when followed by a `---` line. Tighten the gate to `headerCells.size() == aligns.size()` and drop the now-redundant header normalization inside emitTable; body rows still pad/truncate as before. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix SC_AUTOMATICFOLD_CLICK value and guard shim command macros Follow-ups from the ComparePlus navigation verification pass: - SC_AUTOMATICFOLD_CLICK was 0x0004 (the value of SC_AUTOMATICFOLD_CHANGE); Scintilla defines it as 0x0002 - Guard GET_WM_COMMAND_* in windowsx.h against redefinition with winuser.h - Silence -Wdeprecated-this-capture (Scintilla) and pragma-message noise (ComparePlus) in the plugin builds Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Bump version to 1.0.8 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Function list: fix Java parsing and add C# support The function browser missed most Java methods — on a representative file it found only 2 of 7. The Java regex required ')' immediately followed by '{', so it failed on `throws` clauses, same-line annotations (`@Override public ... {`), and Allman-style braces (signature on one line, '{' on the next, which the Java path never handled). It also listed control statements like `if`/`while`/`try` as methods. C# was not a language in the port at all: no LANG_CSHARP, no .cs detection, no table entry — hence no highlighting and no function list. Changes: - function_list_parser.mm: rework the Java path into a shared Java/C# branch handling annotations, throws clauses, Allman pending-signatures, C# expression-bodied members (=>), ": base(...)" constructors, "where" constraints, and class|struct|interface|enum|record containers. Filter control keywords (separate set so "lock"/"using" stay valid C++ names). Regexes match modifiers/types token-wise with cheap string prefilters — the naive form took minutes on large files; this is ~5s on 115k+ lines. - language_defs.{h,mm}: add LANG_CSHARP=36 (appended at end of table since sessions persist languageIndex as a raw int), C# keyword/type lists on the cpp lexer, .cs/.csx detection, and C-style indentation. - npp_constants.h: move IDM_LANG_BASE 44000 -> 51000. 44000+ is upstream's plugin-visible view-command range, and the language range check runs first in MainWndProc, so 44035 (IDM_VIEW_SYNSCROLLV, sent by ComparePlus) was being misrouted to "set language to x86 Assembly". The move also makes room for the 37th language. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Address PR #115 review: record struct/class containers, ID comment accuracy - javaCsClassRe now matches "record class X" / "record struct X" (C# 10) as a unit; previously the second keyword was captured as the container name, so "record struct Point(...)" produced a container named "struct". - Correct the IDM_LANG_BASE comment: upstream menuCmdID.h IDs top out at IDM_EDIT_FUNCCALLTIP_NEXT = 50011 (autocomplete block at 50000+), not 50000. The 51000 base itself is unaffected — nothing upstream sits in 51000-51100. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Bump version to 1.0.9; add C# document type to Info.plist Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: remove stale notification observer object filters causing NSVisualEffectView dealloc crash - Changed three NSNotificationCenter registration object: parameters from dangling-capable pointers to nil in ScintillaView.mm (initWithFrame:) - NSWindowWillMoveNotification: self.window -> nil - NSSystemColorsDidChangeNotification: self.window -> nil - NSViewBoundsDidChangeNotification: scrollView.contentView -> nil All three handlers ignore the notification object, so no behavioral change. This eliminates the stale pointer in the notification center's internal data structures that caused a SIGBUS crash when AppKit's internal NSVisualEffectView was deallocated during autorelease pool drain. - Added dealloc to NppAppDelegate to remove observer from NSDistributedNotificationCenter (AppleInterfaceThemeChangedNotification). - Added missing ScintillaBridge_destroyView call for scintillaView2 in applicationWillTerminate: to balance __bridge_retained from createView. Co-Authored-By: Claude <noreply@anthropic.com> * fix: require at least 3 dashes per GFM table delimiter column The GFM spec requires 3+ dashes in delimiter rows (e.g. ---, :---:, ---:). Previously a single dash per column would pass validation, causing false-positive table parsing for inputs that should not be tables. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Brian Tabone <brian.tabone@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
MarkdownViewerPlusPlus.mmhad no table support, so any GFM table rendered as one wrapped paragraph of text.:---:/---:/:---column alignment, inline markdown inside cells, pad-short / truncate-long handling for mismatched rows, and\|unescaping.<table>/<th>/<td>/thead/ zebra-stripedtbodystyles tobaseCss(), plus matching overrides in the existing dark-mode media query.Single file changed:
plugins/markdownViewerPlusPlus/src/macOS/MarkdownViewerPlusPlus.mm(+160 / -5).Known v1 limitation noted in source: a literal
|inside a backtick code span inside a cell is treated as a cell separator, since cells split before inline markdown runs. Not present in observed real-world docs; can be addressed later if needed.Test plan
cmake --build macos/build --config Debug --target MarkdownViewerPlusPlussucceeds.docs/superpowers/plans/2026-05-25-a-maze-ing-vertical-slice.md(or any GFM file with tables); confirm the "Phases" and "Spec coverage by task" tables render as real bordered HTML tables.`code`inside cells remains formatted as<code>.🤖 Generated with Claude Code