diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 4e58970..aa8d717 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -121,6 +121,14 @@ jobs: --project tests/SQLBI.Whiteboard.Core.SmokeTests/SQLBI.Whiteboard.Core.SmokeTests.csproj --configuration Release + # The highlighting adapter runs on AvalonEdit, so its tests need WPF. + - name: Application smoke tests + if: needs.changes.outputs.code == 'true' + run: >- + dotnet run + --project tests/SQLBI.Whiteboard.SmokeTests/SQLBI.Whiteboard.SmokeTests.csproj + --configuration Release + installer: name: Build installers needs: changes diff --git a/CHANGELOG.md b/CHANGELOG.md index 483bf64..811f5ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,19 @@ broke. The heading is parsed by `scripts/release-notes.ps1`, so keep its shape; under it is ordinary Markdown, and the renderer handles paragraphs, lists, links, `code` and **bold**. +## 1.4.0 - 8 September 2026 + +### Eleven more languages in a text container +The title-bar chip now also offers Python, C, C++, Java, C#, JavaScript, TypeScript, +Visual Basic .NET, R, Rust, and PHP. Choose one and the text is highlighted, on the board +and in an export. These languages are not formatted and are not recognized on paste: pick +them by hand. + +### F6 asks for a vote on a language it cannot format +On one of the new languages, **F6** opens a short note with a link to that language's issue +on GitHub. A thumbs-up there is how the next formatter is chosen. DAX, SQL, and KQL still +format, and Plain text is still quiet. + ## 1.3.1 - 5 September 2026 ### KQL joins DAX and SQL in text containers diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b8f422..17441b0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,8 +22,12 @@ typing the code themselves or directing a coding agent. It is deliberately tool- ```powershell dotnet build Whiteboard.sln -c Release dotnet run --project .\tests\SQLBI.Whiteboard.Core.SmokeTests\SQLBI.Whiteboard.Core.SmokeTests.csproj + dotnet run --project .\tests\SQLBI.Whiteboard.SmokeTests\SQLBI.Whiteboard.SmokeTests.csproj ``` + The first harness is framework-neutral. The second needs WPF, and covers what only + the application can answer, such as syntax highlighting. + `TreatWarningsAsErrors` is on for every project, so a warning fails the build. 4. Open the pull request against `main`: diff --git a/Directory.Build.props b/Directory.Build.props index c6b72f4..3516f05 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,7 +5,7 @@ scripts/build-installer.ps1 both read it from here, so releasing is a reviewed change to this line rather than an edit in a pipeline variable group. --> - 1.3.1 + 1.4.0 latest enable enable diff --git a/README.md b/README.md index a0c4a47..2cad342 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ How the project is developed and shipped is documented separately: - SVG stays vector: it is stored as its markup and redrawn at every zoom and resize rather than rasterized on arrival. Pasting SVG markup that was copied as text — the output of a DAX SVG measure, for instance — creates a picture, and copying an SVG container puts both the markup and a bitmap on the clipboard - Image selection, movement, resizing, and deletion - Text containers created by pasting plain text, with display and in-place edit modes -- Plain-text, DAX, and SQL Server language modes, with live syntax highlighting and local F6 formatting +- Fifteen text-container languages with live syntax highlighting, and local F6 formatting for DAX, SQL Server, and KQL - Containers automatically carry strokes that touch only that container when moved or resized - LiveView containers for GPU-backed capture of an application window or display, with freeze/resume and saved last-frame previews - Double-click a container to center it and fit it to the canvas @@ -198,8 +198,8 @@ Use **Copy settings** after finding a useful combination so the exact values can | Ctrl+V | Paste prefers an image (including a file on the clipboard) over text. Otherwise create a text container from plain text | | F2 | Edit the selected text container, or rename the selected frame | | View > Frame | Add a frame the size of the screen: a slide drawn on the board, selected by its edge or its tab, which Export takes as it is | -| Language chip | Choose Plain text, DAX, or SQL Server on a selected text container | -| F6 | Format DAX, SQL, or KQL on the selected text container; DAX wraps to the container's columns. In F2, formats in place | +| Language chip | Choose one of fifteen languages on a selected text container | +| F6 | Format DAX, SQL, or KQL on the selected text container; DAX wraps to the container's columns. In F2, formats in place. On a language that is only highlighted, opens that language's issue on GitHub | | Drag right edge, or Shift + drag handle | Change a text container's width in columns and reflow it; the handle shows the count. A plain drag of the corner scales it | | Ctrl+Enter | Commit the F2 edit, including an F6 format done in that session, and return to display mode | | Escape | Cancel the active text edit, close the command strip, or leave full screen or canvas only | @@ -222,7 +222,7 @@ With the mouse, selection is automatic: click a container to move it, or drag th Imported images, LiveViews, and text objects act as containers. A completed stroke is linked when it touches exactly one container, including crossing its edge; a stroke touching multiple containers remains independent. Moving or resizing a container transforms its linked strokes with it. **View → Bring to front** and **View → Send to back** reorder the selected container and those linked strokes. Deleting a container also deletes all of its linked strokes. Undo/redo treats each complete container operation as one action. -Paste plain text to create a selected text container in display mode. **Help → Preferences** has Snippet format order: paste tries those languages from top to bottom and uses the first that accepts the text. Plain text always accepts and comes last by default, so DAX, SQL, and KQL are recognized on paste; a language claims a snippet only when it carries an operator, function, or keyword, so a bare word stays a note. Put Plain text first to keep every paste plain. A language added by an update joins in front of Plain text unless Plain text is first. Recognized extensions (`.dax`, `.sql`, `.kql`, `.txt`) keep their language; other dropped text files use the same order. Choose **Plain text**, **DAX**, **SQL Server**, or **KQL** from the title-bar chip afterward. Press **F6** to format DAX, SQL, or KQL on the selected container without entering edit. Press **F2** to edit the body; the same list is in the title bar while editing. In F2, **F6** formats in place; **Ctrl+Enter** commits that edit (including the format) and returns to display. **Escape** restores the previous text, language, and dimensions. Text reflows while its edit-mode resize grip changes the width, and the height grows automatically when necessary. Double-click still centers and fits the container. Syntax highlighting applies in both edit and display modes. A language-aware title identifies a defined DAX, SQL, or KQL object when possible. SQL Server mode targets SQL Server 2025 T-SQL, preserves `GO` batch separators, and leaves invalid scripts unchanged. KQL mode reads Kusto through Microsoft's own parser and likewise leaves an invalid query unchanged. In display mode, resizing preserves the aspect ratio and scales the complete text visual without reflowing it. +Paste plain text to create a selected text container in display mode. **Help → Preferences** has Snippet format order: paste tries those languages from top to bottom and uses the first that accepts the text. Plain text always accepts and comes last by default, so DAX, SQL, and KQL are recognized on paste; a language claims a snippet only when it carries an operator, function, or keyword, so a bare word stays a note. Put Plain text first to keep every paste plain. A language added by an update joins in front of Plain text unless Plain text is first. Recognized extensions (`.dax`, `.sql`, `.kql`, `.txt`) keep their language; other dropped text files use the same order. Choose **Plain text**, **DAX**, **SQL Server**, or **KQL** from the title-bar chip afterward, or one of **Python**, **C**, **C++**, **Java**, **C#**, **JavaScript**, **TypeScript**, **Visual Basic .NET**, **R**, **Rust**, and **PHP**, which are highlighted but not formatted and are never chosen by paste; **F6** on one of them opens that language's issue on GitHub. Press **F6** to format DAX, SQL, or KQL on the selected container without entering edit. Press **F2** to edit the body; the same list is in the title bar while editing. In F2, **F6** formats in place; **Ctrl+Enter** commits that edit (including the format) and returns to display. **Escape** restores the previous text, language, and dimensions. Text reflows while its edit-mode resize grip changes the width, and the height grows automatically when necessary. Double-click still centers and fits the container. Syntax highlighting applies in both edit and display modes. A language-aware title identifies a defined DAX, SQL, or KQL object when possible. SQL Server mode targets SQL Server 2025 T-SQL, preserves `GO` batch separators, and leaves invalid scripts unchanged. KQL mode reads Kusto through Microsoft's own parser and likewise leaves an invalid query unchanged. In display mode, resizing preserves the aspect ratio and scales the complete text visual without reflowing it. Image files, and `.txt`, `.dax`, `.sql`, and `.kql` files, can be dropped directly from File Explorer. Their initial center is the board position at which they were dropped. DAX, SQL, and KQL files open in the matching language mode. Other dropped text files use Snippet format order. @@ -257,9 +257,10 @@ is installed. The source is `vscode/sqlbi-whiteboard`. - `SQLBI.Whiteboard.SqlServer` contains the framework-neutral SQL Server 2025 adapter over Microsoft's ScriptDOM parser and script generator. - `SQLBI.Whiteboard.Kql` contains the framework-neutral Kusto Query Language adapter over Microsoft's own Kusto parser, classifier, and formatter. - `vscode/sqlbi-whiteboard` is a VS Code custom editor that shows `preview.png` from a `.wboard` ZIP. It is not part of the desktop installer. -- `SQLBI.Whiteboard` is the WPF shell. `TouchInkCanvas` supplies system-managed wet ink for the finger, while `BoardSurface` renders completed ink, images, text, and selection on a white canvas in camera space, plus the pen's own wet stroke. A transient AvalonEdit surface is overlaid only while a text container is being edited; language services translate parser classifications into WPF text styles. +- `SQLBI.Whiteboard` is the WPF shell. `TouchInkCanvas` supplies system-managed wet ink for the finger, while `BoardSurface` renders completed ink, images, text, and selection on a white canvas in camera space, plus the pen's own wet stroke. A transient AvalonEdit surface is overlaid only while a text container is being edited; language services translate parser classifications into WPF text styles. `Highlighting/` holds the syntax definitions for the languages read lexically rather than parsed, and the adapter that flattens them into the same styled spans. - `SQLBI.Whiteboard/LiveView` owns Windows Graphics Capture and the Direct3D-to-WPF bridge. Capture retains one GPU frame per active LiveView; CPU bitmap conversion occurs only when copying or saving a snapshot. - `SQLBI.Whiteboard.Core.SmokeTests` is a package-free executable test harness for camera anchoring, commands, hit testing, and archive round trips. +- `SQLBI.Whiteboard.SmokeTests` is the same kind of harness for what needs WPF: syntax highlighting, the language registry, and the runs an editable export carries. It is never published. The current document query is deliberately linear. A spatial index can be introduced behind `BoardDocument.Query` when profiling demonstrates a need, without changing input, tools, persistence, or rendering call sites. diff --git a/Whiteboard.sln b/Whiteboard.sln index e2e7b80..b958152 100644 --- a/Whiteboard.sln +++ b/Whiteboard.sln @@ -23,6 +23,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLBI.Whiteboard.Export", "src\SQLBI.Whiteboard.Export\SQLBI.Whiteboard.Export.csproj", "{EE24F5C6-7C34-4613-8C82-DF34418E2B03}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLBI.Whiteboard.SmokeTests", "tests\SQLBI.Whiteboard.SmokeTests\SQLBI.Whiteboard.SmokeTests.csproj", "{39C47EAF-F18D-411F-AA1C-9F83D294725A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -141,11 +145,24 @@ Global {EE24F5C6-7C34-4613-8C82-DF34418E2B03}.Release|Any CPU.Build.0 = Release|Any CPU {EE24F5C6-7C34-4613-8C82-DF34418E2B03}.Release|x86.ActiveCfg = Release|Any CPU {EE24F5C6-7C34-4613-8C82-DF34418E2B03}.Release|x86.Build.0 = Release|Any CPU + {39C47EAF-F18D-411F-AA1C-9F83D294725A}.Debug|x64.ActiveCfg = Debug|Any CPU + {39C47EAF-F18D-411F-AA1C-9F83D294725A}.Debug|x64.Build.0 = Debug|Any CPU + {39C47EAF-F18D-411F-AA1C-9F83D294725A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39C47EAF-F18D-411F-AA1C-9F83D294725A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39C47EAF-F18D-411F-AA1C-9F83D294725A}.Debug|x86.ActiveCfg = Debug|Any CPU + {39C47EAF-F18D-411F-AA1C-9F83D294725A}.Debug|x86.Build.0 = Debug|Any CPU + {39C47EAF-F18D-411F-AA1C-9F83D294725A}.Release|x64.ActiveCfg = Release|Any CPU + {39C47EAF-F18D-411F-AA1C-9F83D294725A}.Release|x64.Build.0 = Release|Any CPU + {39C47EAF-F18D-411F-AA1C-9F83D294725A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39C47EAF-F18D-411F-AA1C-9F83D294725A}.Release|Any CPU.Build.0 = Release|Any CPU + {39C47EAF-F18D-411F-AA1C-9F83D294725A}.Release|x86.ActiveCfg = Release|Any CPU + {39C47EAF-F18D-411F-AA1C-9F83D294725A}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {EE24F5C6-7C34-4613-8C82-DF34418E2B03} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {39C47EAF-F18D-411F-AA1C-9F83D294725A} = {0AB3BF05-4346-4AA6-1389-037BE0695223} EndGlobalSection EndGlobal diff --git a/docs/additional-language-plan.md b/docs/additional-language-plan.md new file mode 100644 index 0000000..0973ea6 --- /dev/null +++ b/docs/additional-language-plan.md @@ -0,0 +1,285 @@ +# Additional languages: highlighting and manual selection + +Status: implemented, and shipping as 1.4.0. Step 1 built the capability split, its +persistence tests, the highlighting spike recorded below, and the F6 voting prompt. Step 2 +added the highlighting adapter, the definitions for C, C++, C#, Java and Visual Basic +.NET, and a WPF test host for them. Step 3 added JavaScript, TypeScript, Python, R, Rust +and PHP. Step 4 completed the regression and export matrix and the text a reader sees. The +eleven formatting/detection voting issues below were created on 8 September 2026, and are +what decides which language is formatted next. + +## Outcome and scope + +Add Python, C, C++, Java, C#, JavaScript, TypeScript, Visual Basic .NET, R, Rust, +and PHP to both text-container language selectors. Selecting a language colors the +existing text in edit mode, on the board, and in exports. It does not rewrite the text. +The chosen language survives saving and reopening the board. + +These languages initially provide highlighting only. F6 shows a language-specific +message inviting the user to vote for formatting and automatic detection on GitHub. +This plan interprets the request's "SQLBI Formatter" as Whiteboard's existing F6 +command, both while editing and on a selected text container. + +Visual Basic means VB.NET. VBA and VB6 are separate future scopes. JavaScript and +TypeScript initially cover ordinary source snippets; JSX and TSX are excluded. PHP +initially covers PHP code with or without opening tags, not HTML syntax highlighting +in mixed templates. Coloring is lexical: keywords, comments, literals, operators, +punctuation, and recognizable language constructs, without project-aware symbol +resolution, compilation, diagnostics, or completion. + +No new content detection, extension inference, or `.wimport` fence aliases are added +in this phase. Existing import and paste behavior remains available; the user chooses +one of the new languages after creating a text container. An explicitly saved language +in a `.wboard` is restored. Existing Plain text, DAX, SQL Server, and KQL behavior stays +unchanged. + +## Language inventory and voting destinations + +Use stable language IDs, independent of menu captions. Each issue collects votes for +both formatting and detection for that language. + +| Caption | ID | Highlighting cases to cover | Voting issue | +| --- | --- | --- | --- | +| Python | `python` | Decorators, comments, numeric literals, triple-quoted/raw strings, f-strings | [#108](https://github.com/sql-bi/SQLBI-Whiteboard/issues/108) | +| C | `c` | Comments, character/string literals, escapes, preprocessor directives, numeric suffixes | [#109](https://github.com/sql-bi/SQLBI-Whiteboard/issues/109) | +| C++ | `cpp` | C constructs plus templates, modern keywords, raw strings and their delimiters | [#110](https://github.com/sql-bi/SQLBI-Whiteboard/issues/110) | +| Java | `java` | Annotations, comments, strings, text blocks, modern keywords | [#111](https://github.com/sql-bi/SQLBI-Whiteboard/issues/111) | +| C# | `csharp` | Attributes, directives, verbatim/interpolated/raw strings, contextual keywords | [#112](https://github.com/sql-bi/SQLBI-Whiteboard/issues/112) | +| JavaScript | `javascript` | Comments, strings, template interpolation, regex literals versus division | [#113](https://github.com/sql-bi/SQLBI-Whiteboard/issues/113) | +| TypeScript | `typescript` | JavaScript constructs plus type keywords, annotations and generic syntax | [#114](https://github.com/sql-bi/SQLBI-Whiteboard/issues/114) | +| Visual Basic .NET | `vbnet` | Case-insensitive keywords, apostrophe/REM comments, escaped/interpolated strings, continuations, XML literals | [#115](https://github.com/sql-bi/SQLBI-Whiteboard/issues/115) | +| R | `r` | Comments, backtick names, strings/raw strings, formulas, pipes and custom infix operators | [#116](https://github.com/sql-bi/SQLBI-Whiteboard/issues/116) | +| Rust | `rust` | Nested comments, raw strings, lifetimes versus character literals, macro invocations | [#117](https://github.com/sql-bi/SQLBI-Whiteboard/issues/117) | +| PHP | `php` | Variables, comments, strings, heredoc/nowdoc and optional PHP tags | [#118](https://github.com/sql-bi/SQLBI-Whiteboard/issues/118) | + +Do not promise exact IDE coloring for ambiguous contextual constructs. Recognize string +and comment boundaries correctly and favor conservative coloring for ambiguous names. + +## 1. Separate selection from detection + +The current `TextLanguageIds.All` serves both language inventory and default detection +order. Simply appending these eleven IDs would put them in Snippet format order and +settings migration. Split those responsibilities before adding services. + +- In `src/SQLBI.Whiteboard.Core/Model/BoardObjects.cs`, register all fifteen IDs for + persistence/selection, and introduce a distinct detection-capable list containing + only DAX, SQL Server, KQL, and Plain text in their current default order. +- Make `Normalize` recognize the eleven new IDs. Keep the existing unknown-language + fallback for genuinely unknown IDs. +- Make `NormalizeOrder` validate against the detection-capable list, ignoring + manual-only IDs rather than converting them into Plain text. Preserve the existing + treatment of unknown values unless a separate migration is needed. +- Update defaults and normalization in `Core/Settings/AppSettings.cs`; preserve chosen + detection order, Plain-text-first behavior, and historical default migration. +- Keep `PreferencesWindow.xaml.cs` limited to detection-capable languages. Do not show + unavailable features as reorderable detection options. +- `TextLanguageRegistry.All` exposes all fifteen services to `LanguageChipCombo` and + `TextEditorLanguageCombo`. Retain the existing four entries in their current order; + append the eleven entries in the inventory order above. Ensure the menu scrolls. +- Add an explicit detection capability to language metadata, separate from + `CanFormat`. New services have detection disabled, `CanFormat = false`, and + `TryAccept` always returns false. Their `TryFormat` returns false and unchanged text. + Filter detection before calling `TryAccept`, so no manual-only analyzer runs on paste. + +Decision 28's automatic insertion rule continues to apply to detection-capable +languages only. Record that qualification in `docs/decisions.md` when implemented. + +## 2. Implement one reusable highlighting adapter + +Use the existing AvalonEdit dependency as the first implementation candidate: inspect +the pinned package's highlighting definitions and highlighter API, then run a small +spike converting line highlighting into Whiteboard `StyledTextSpan` values. Do not +assume the bundled grammars cover all eleven languages or their modern syntax. + +The spike must show identical spans in edit and display paths, multiline state across +lines, safe handling of incomplete code, and reuse by export. Use full-document +analysis rather than viewport-only coloring. Evaluate C# raw strings, JavaScript +template/regex syntax, and Rust nested comments early, because they expose grammar +limitations. Approve the engine approach against these cases before authoring all +eleven definitions. If XSHD cannot represent a construct reliably, use a small stateful +scanner for that language behind the same adapter rather than a growing single regex. + +Implement the adapter and definitions inside `src/SQLBI.Whiteboard`; avoid introducing +eleven parser projects or external runtimes for highlighting. Reuse compatible bundled +definitions where they pass the corpus, and ship missing definitions as embedded +resources under a dedicated `Highlighting/` folder. Record source/version/license for +any imported definitions and include required notices. No runtime downloads. + +The adapter must: + +- Return ordered, nonoverlapping, in-bounds spans using UTF-16 offsets, matching the + existing `TextClassificationColorizer` and export consumers. Resolve nested grammar + styles into flat runs before returning them. +- Map token categories to a shared readable palette consistent with the existing + languages, and use Consolas. Retain current wrapping, scaling and line-number policy. +- Preserve every source character, including whitespace and line endings. Highlight + incomplete snippets without rejecting them; recover after unterminated constructs + according to the language's lexical rules. +- Use generic titles such as "Python Code"; semantic definition-name discovery is + outside this phase. Follow existing title-override behavior for imported containers. +- Reuse `TextLanguageAnalysis` for `TextContainerVisual`, the editor colorizer and + `Export/EditableSlide.cs`, rather than adding an editor-only coloring path. Verify + PDF/vector and preview rendering through their existing consumers too. +- Keep mutable document/highlighter state local to an analysis operation. Freeze WPF + brushes before sharing across threads and bound caches. Enable background edit + analysis where useful, respecting cancellation/stale-result handling already present. +- Check long lines and incomplete strings for pathological runtime. A failed analyzer + must fall back to uncolored text without losing source or crashing display/export. + +### Spike result: adopt the engine, author the definitions + +A throwaway spike ran the pinned AvalonEdit 6.3.1.120 over fixtures for the constructs +named above, converting `DocumentHighlighter` line output into flat spans by painting each +line's sections in order and grouping the result. The engine is approved; the bundled +grammars are not. + +What the adapter shape delivers, measured rather than assumed: + +- Spans came back ordered, nonoverlapping and in bounds on every fixture, including + unterminated strings and comments. +- Offsets are document UTF-16 offsets and stayed correct across a surrogate pair, which is + what `TextClassificationColorizer` and the export consumers need. +- Span state carries across lines, so a block comment, a heredoc and a triple-quoted string + color to their real end. CRLF and LF both work; no span ever covered a line delimiter. +- Empty input produces no spans. An unterminated construct colors to the end and the next + construct recovers, which is the language's own lexical rule. +- 200 concurrent analyses sharing one definition returned identical spans, for a bundled + definition and a hand-written one, so a definition can be shared and the document and + highlighter kept local to the operation. +- Runtime is linear in the ordinary cases — 380 KB over 20,000 lines with an unterminated + block comment took 79 ms — but one 429 KB line of 80,000 tokens took 837 ms. The adapter + needs a size guard rather than trust. + +What the bundled definitions do not cover. Seven of the eleven languages have one (Python, +C++, Java, C#, JavaScript, VB, PHP); C has to borrow C++, and R, Rust and TypeScript have +none at all. The seven that exist predate the syntax people write now: C# raw strings and +Java text blocks are read as an empty string followed by loose code, JavaScript template +literals are not recognized at all, Python decorators and Java annotations are uncolored, +and `1_000` colors as `1`. They are not a shortcut worth taking. + +What our own XSHD can and cannot express, tested by writing three definitions: + +- A span whose rule set contains itself gives true nested comments: `/* outer /* inner */ + still comment */` came back as one comment run, 2,000 levels deep took 59 ms, and an + unterminated nested comment swallows the rest as Rust says it should. +- Ordering rules buys the ambiguous cases: a lifetime rule with a negative lookahead before + the character rule separates `&'static` from `'a'`. Nested rule sets buy string escapes, + and a span that re-enters the code rule set buys interpolation — `` `sum ${a + b} end` `` + and a template nested inside its own hole both came out right. +- XSHD cannot count. Bounded rules cover C# raw strings at three, four and five quotes and + Rust `r#"` and `r##"`, which is most real code, but a longer delimiter is beyond it. +- A rule regex cannot see far enough left to settle regex versus division: `/ab+c/gi` and + `foo(/lit/, 1 / 2)` are right, while `let x = a / b, re2 = /x\/y/` misses the second + literal, because the lookbehind window starts where the previous rule stopped. + +So: reuse the AvalonEdit engine and the flattening adapter, and ship our own definitions +under `Highlighting/` rather than the bundled ones. + +Writing the eleven definitions retired the stateful scanner the spike had expected to +need. C# raw strings take three rules, longest delimiter first, covering three, four and +five quotes; a snippet with six is not one anyone writes, and the same bounded trick +serves C++ raw string delimiters and Rust hashes. Regular expression versus division in +JavaScript was a bad rule rather than a limit of the engine: it is decided by the token +before the slash, and a lookbehind that names the operators, keywords and line starts a +value cannot follow settles every case in the corpus. A regular expression somewhere else +is left as operators and names, which is the harmless half of being wrong. + +Three lexical hazards showed up that the spike had not, all recorded in the definitions +themselves: an apostrophe is a digit separator in C and C++, so the character literal has +to be a rule that runs after the number rather than a span that runs before it; an angle +bracket opens a Visual Basic XML literal or a comparison, so the literal is recognized +only where a value belongs; and a hash opens a PHP comment or an attribute, so the +attribute is matched first. + +## 3. Add the F6 voting prompt + +Associate an optional immutable `FormattingRequestUri` with each service (or its +descriptor), using the exact issue links above. Plain text has no voting URI. Keep +`CanFormat` false: showing a request dialog is not formatting support. + +In `MainWindow.xaml.cs`, route both `FormatTextEdit` and `FormatSelectedText` through +one shared unsupported-format check before their current `!language.CanFormat` early +returns. A manual-only language opens the dialog; a supported formatter follows its +existing path. Invalid DAX/SQL/KQL input must not trigger a voting prompt. + +Proposed copy, substituting the selected language: + +> **Python formatting is not available yet** +> +> Vote for Python formatting and automatic detection on GitHub. Add a thumbs-up +> reaction to the issue's opening post to help us prioritize this language. + +Show a clickable **Vote on GitHub** link and a **Close** button. The link opens the +language's fixed HTTPS issue URL in the default browser only after the user activates +it, following the application's existing shell-link pattern. It does not submit a vote; +GitHub handles sign-in and reactions. Never append the snippet or local paths to the URL. + +The dialog should be owned by the main window, keyboard accessible, dismissible with +Escape, and avoid stacking on repeated F6. Closing it returns focus to the editor or +board without changing source, language, caret, selection, dimensions, dirty state, or +undo history. Handle browser-launch failures with a selectable/copyable issue URL. +Show no network request or dialog merely from selecting a language. Plain text and +F6 with no selected text remain quiet. Reuse this check for any additional format +entry points found during implementation. + +## 4. Tests and release verification + +Extend the framework-neutral smoke tests for language-ID persistence and detection-order +normalization. The existing harness targets `net10.0` and cannot directly host AvalonEdit +WPF highlighting; add focused Windows-targeted highlighting tests if the adapter remains +in WPF. If a new first-party test assembly is published, follow the signing requirements +in CONTRIBUTING.md; do not package test-only artifacts with the application. + +Automated acceptance coverage: + +- All eleven IDs round-trip through `.wboard`; older boards and unknown IDs keep their + existing behavior. Document that older app versions may display the new IDs as plain + text; this feature does not require changing the archive schema. +- Default and custom detection orders still contain only the original four languages. + Manual-only IDs injected into settings are ignored. Existing paste fixtures produce + the same result; none of the eleven new services accepts source automatically. +- Every language has fixtures for ordinary tokens, multiline constructs from the table, + incomplete input, CRLF/LF, Unicode before tokens, and empty input. Assert meaningful + token categories and valid spans, not just successful execution. +- Check string/comment containment, absence of text mutation, stale analysis after a + language switch, and bounded behavior on representative large snippets. +- Each manual-only service resolves to its exact issue URI; Plain/DAX/SQL/KQL have no + unsupported-feature prompt. Verify both format entry points use the shared behavior. + +Manual acceptance matrix, for each language: + +1. Paste text, choose the language in display mode, then edit and switch language again. + Confirm the selector works with all fifteen entries and editing remains responsive. +2. Check multiline coloring, resizing, wrapping and zoom; save and reopen the board. +3. Export representative content to editable PowerPoint and vector PDF, and check the + saved preview for matching text and highlighting. +4. Press F6 while editing and again in display mode. Confirm the correct language in + the prompt and the exact GitHub destination; check keyboard dismissal, unchanged + caret/selection/source/history, and browser failure fallback. +5. Confirm DAX, SQL and KQL still format, and Plain text remains quiet. + +Run `dotnet build Whiteboard.sln -c Release`, the existing Core smoke-test executable, +and the focused Windows tests. Review new resource inclusion in a published build. +Update README, relevant guide/shortcut pages and release notes when the behavior ships; +do not describe the plan as an already available feature. Follow the repository's +branch, PR and version/release-note workflow. + +## Delivery sequence and prioritization + +1. Shared capability split, persistence tests, adapter spike and voting dialog. +2. C and C# definitions to establish the first end-to-end examples; C++, Java and VB.NET. +3. JavaScript and TypeScript together; Python and R; Rust and PHP. +4. Complete the eleven-language regression/export matrix and ship the complete set. + +Use reviewable implementation PRs, keeping incomplete services out of the public +registry until their highlighting and voting links are ready. All eleven languages +are in the committed scope; voting does not gate their highlighting work. + +For subsequent formatting/detection work, compare the number of thumbs-up reactions on +the opening post of these eleven issues when planning the next feature. Higher-voted +languages receive priority, with implementation dependencies and shared engines noted +when they affect delivery order. Comments can distinguish demand for formatting versus +detection; reaction totals deliberately measure the combined per-language request. +No automatic vote polling, telemetry, scheduling, or delivery dates are part of this phase. +When a capability ships, update its issue and service metadata; if only formatting +ships, remove the F6 prompt while retaining the issue for remaining detection work. diff --git a/docs/decisions.md b/docs/decisions.md index 9a55e3f..0b27ca6 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -682,6 +682,15 @@ Two things were changed together, and the second is what makes the first safe: KQL a pipe, query operator, keyword, command, or function. The parser still has the last word after the signal. +Both rules are about the languages that can read a snippet. Choosing a language by hand +and recognizing one automatically are now two separate lists: a text container can be set +to any of fifteen languages, while only DAX, T-SQL, KQL, and plain text take part in the +snippet format order. A language that only colors never claims a paste, never joins a +saved order, and is ignored if one is written into settings by hand — reading it as plain +text instead would move plain text up an order it was never part of. The archive format is +unchanged by this: a board saved with one of the new languages opens in an older release +as plain text, with its source intact. + --- ## 29. A text container's width in columns is the line width of its snippet diff --git a/site/compare.html b/site/compare.html index 4e1d636..da647c2 100644 --- a/site/compare.html +++ b/site/compare.html @@ -66,7 +66,7 @@

Beside Microsoft Whiteboard

PlatformsWindows 10 version 2004 or later, 64-bitWindows, web, and Microsoft 365 clients CollaborationOne person at the boardReal-time, multi-user Ink and objectsStrokes can travel with a containerInk and objects on a shared canvas - CodeDAX, SQL, and KQL containers, F6 formats locallyNot a language surface + CodeFifteen languages highlighted, F6 formats DAX, SQL, and KQL locallyNot a language surface Live applicationLiveView of a window or a displayNot a capture surface diff --git a/site/faq.html b/site/faq.html index 2af5b82..40b1927 100644 --- a/site/faq.html +++ b/site/faq.html @@ -75,7 +75,7 @@

What is LiveView, and why Reconnect after open?

LiveView captures a window or a display onto the board so you can draw over it. Windows cannot save the capture permission. After you reload a board the last frame appears immediately; Reconnect restores the live feed. The guide has the session story.

Can it format DAX, SQL, and KQL?

-

Yes. Paste or drop a .dax, .sql, or .kql file, choose the language, press F6. Help → Preferences has Snippet format order for clipboard paste and unrecognized dropped files. Formatting is local. SQL Server mode targets SQL Server 2025 T-SQL and leaves an invalid script unchanged; KQL reads Kusto through Microsoft's own parser and does the same.

+

Yes. Paste or drop a .dax, .sql, or .kql file, choose the language, press F6. Help → Preferences has Snippet format order for clipboard paste and unrecognized dropped files. Formatting is local. SQL Server mode targets SQL Server 2025 T-SQL and leaves an invalid script unchanged; KQL reads Kusto through Microsoft's own parser and does the same. Eleven more languages are highlighted but not formatted; F6 on one links to its issue on GitHub, where a thumbs-up decides the next formatter.

How do I bring a workshop in?

A .wimport file is Markdown that builds image and text containers. Drop it on an open board, or use Import. The import format is the contract. Save always writes a .wboard.

diff --git a/site/guide.html b/site/guide.html index ef52d74..8d7fd2d 100644 --- a/site/guide.html +++ b/site/guide.html @@ -252,7 +252,7 @@

Keep ink on the slide you are discussing

Explain code

-

Paste DAX, SQL, or KQL, or drop a .dax, .sql, or .kql file. Use the title-bar chip to set the language, press F6 to format it locally, then draw on the container. Formatting no longer needs edit mode: F6 works on the container as soon as it is selected.

+

Paste DAX, SQL, or KQL, or drop a .dax, .sql, or .kql file. Use the title-bar chip to set the language — fifteen are highlighted — press F6 to format DAX, SQL, or KQL locally, then draw on the container. Formatting no longer needs edit mode: F6 works on the container as soon as it is selected.

F2 moves the container into edit mode. Ctrl+Enter goes back to display mode and keeps the changes. Escape also goes back to display mode, but cancels the changes and restores the text, language, and size the container had before.

The two modes resize differently. In edit mode the text reflows as you change the width, so a long line wraps onto more lines at the same size. In display mode a resize scales the whole visual, like a picture of the code, so the room still sees the layout you just formatted.

@@ -693,12 +693,13 @@

Containers

View → Bring to front and View → Send to back reorder the selected container and its linked strokes. Strokes cannot be reordered on their own.

View → Frame adds a frame the size of the screen: a slide drawn on the board. Select it by its dashed edge or its title tab, drag to move or resize it, and press F2 to rename it. Export uses frames as they are, first, and cuts the rest of the board automatically. Frames never appear in an export.

-

Text, DAX, SQL, and KQL

+

Text and code

Paste prefers an image when the clipboard has one, including a file SnagIt or another capture tool left on the clipboard. Paste plain text to create a selected text container.

SVG counts as an image, whether the source application published it as SVG or the markup was simply copied as text — the output of a DAX SVG measure pastes as a picture, not as a snippet. Copying an SVG container puts the markup and a bitmap on the clipboard together, so an editor receives the source and a slide receives the picture.

Help → Preferences sets Snippet format order: paste tries those languages from top to bottom, and Plain text comes last, so DAX, SQL, and KQL are recognized while a bare word stays a note. Put Plain text first to keep every paste plain. Use the title-bar chip to change the language afterward.

A text container's width is its line width. Drag its right edge to change the width in columns and reflow the text; the handle shows the count. Shift on the corner handle does the same, and a plain drag of the corner scales the container like a picture. A new container is 65 columns wide, which is where the DAX formatter wraps.

F6 formats DAX, SQL, or KQL on the selected container, without entering edit mode, and wraps DAX to the container's columns. F2 edits the container, and double-click still centers it and fits it to the canvas; inside an edit F6 formats in place and Ctrl+Enter commits that format with the rest of the edit. SQL Server mode targets SQL Server 2025 T-SQL, keeps GO batch separators, and leaves an invalid script unchanged; KQL reads Kusto through Microsoft's own parser and likewise leaves an invalid query alone. Escape restores the previous text, language, and size.

+

The chip also offers Python, C, C++, Java, C#, JavaScript, TypeScript, Visual Basic .NET, R, Rust, and PHP. These are highlighted but not formatted, and paste never picks one for you. F6 on one of them opens a link to that language's issue on GitHub, where a thumbs-up decides which formatter comes next.

LiveView

View → LiveView captures a window or a display. The container behaves like an image: move, resize, frame, delete, undo.

diff --git a/site/index.html b/site/index.html index 144feac..9dfe1ac 100644 --- a/site/index.html +++ b/site/index.html @@ -116,8 +116,8 @@

Everything stays on one canvas

  • - DAX, SQL, and KQL containers - Text blocks with syntax highlighting, and F6 to format the code in place. + Code containers + Text blocks with syntax highlighting for fifteen languages, and F6 to format DAX, SQL, and KQL in place.
  • diff --git a/site/shortcuts.html b/site/shortcuts.html index c268508..0aae62a 100644 --- a/site/shortcuts.html +++ b/site/shortcuts.html @@ -122,7 +122,7 @@