Skip to content

Add JSON output highlighting and rainbow brackets (#79)#80

Merged
mkrueger merged 5 commits into
mainfrom
dev/mkrueger/issue79
May 12, 2026
Merged

Add JSON output highlighting and rainbow brackets (#79)#80
mkrueger merged 5 commits into
mainfrom
dev/mkrueger/issue79

Conversation

@mkrueger
Copy link
Copy Markdown
Contributor

@mkrueger mkrueger commented May 11, 2026

Implements JSON syntax highlighting for command output, plus related improvements to the input highlighter discovered while working on it.

Refs #79 (does not close it — the per-type color scheme requested by the issue's acceptance criteria lands in a follow-up PR).

What this PR does

JSON output highlighting

  • New JsonOutputHighlighter walks a JsonElement and renders Spectre.Console markup with the same indented layout as Utf8JsonWriter(Indented = true).
  • Theme gains dedicated helpers for each JSON value kind (FormatJsonString, FormatJsonNumber, FormatJsonBoolean, FormatJsonNull) so the color scheme can evolve per type without touching call sites.
  • ShellInterpreter.PrintState routes JSON results to the highlighter when the output goes to the terminal; file redirection and CSV/Table formats are unchanged, so MCP and existing scripts are unaffected.
  • All four primitive helpers currently render in violet. A follow-up PR will introduce the distinct color scheme requested by the issue (kept separate to keep this one focused).

Bracket pair colorization

  • New Theme.GetBracketColor(int depth) / Theme.FormatBracket(text, depth) cycle through gold1 / orchid / deepskyblue1.
  • The output formatter and the input HighlightingVisitor share a depth counter across {}, [], and (), so nested structures and ParensExpression contents pick up the rainbow coloring familiar from modern editors.

Input highlighter fixes

  • Visit(InterpolatedStringExpression) no longer smears characters from the start of the line into the rendered output when an interpolated string contains $(...) or $VAR — fixes a duplication bug where typing two interpolated strings back to back rendered an extra copy.
  • Root cause fix: Lexer now accepts a positionOffset, and interpolated string tokens carry a per-character source map so ExpressionParser.ParseInterpolatedStringExpression can build sub-lexers and synthetic identifier tokens whose positions are accurate against the outer source buffer. Operators, numbers, parens, and variable references inside $(...) and $name references in "..." are now colored as the corresponding expression elements.
  • The interpolated string source-map is keyed by reference equality so equivalent (record-equal) tokens cannot collide in the lookup map.
  • $(...) interiors are lexed from the raw outer-source slice (via the new Lexer.RawInput / PositionOffset accessors) instead of the cooked token content, so escape sequences inside the interpolation no longer drift token positions.

Tests

  • JsonOutputHighlighterTests (5 tests): primitive type colors, indentation, empty containers, JSON-and-markup escaping, depth-cycled bracket coloring.
  • New highlighter tests: TestInterpolatedExpressionDoesNotDuplicateText, TestInterpolatedExpressionContentsAreColoredAsExpression, TestInterpolatedVariableIsColoredSeparately, TestInterpolatedExpressionWithEscapesRoundTrips.
  • Full non-integration suite: 828/828 passing.

Out of scope (separate PR)

  • Distinct colors per JSON value type beyond the new helpers (requested by issue acceptance criteria).
  • Documentation updates for the new behavior.

mkrueger added 4 commits May 11, 2026 09:44
Adds Theme helpers for JSON strings, numbers, booleans, and null, and a JsonOutputHighlighter that renders a JsonElement as Spectre.Console markup matching the indented Utf8JsonWriter layout. PrintState now applies the highlighter when JSON output is written to the terminal (file redirection and non-JSON formats are unchanged). Closes #79.
Adds Theme.GetBracketColor(depth) and Theme.FormatBracket(text, depth) using a 3-color cycle (gold1/orchid/deepskyblue1). The JSON output highlighter and the input HighlightingVisitor now color matched braces, brackets, and parentheses by their shared nesting depth, so {[(...)]} pairs are visually distinguishable. Comma and colon continue to use the existing punctuation color.
Sub-expressions inside an interpolated string interpolation are produced by a
separate Lexer over the interior content, so their Start/Length positions are
relative to that inner buffer, not the outer line. Recursing into them from
HighlightingVisitor.Visit(InterpolatedStringExpression) caused AppendUpTo and
Substring to index into the wrong buffer and smear characters from the start
of the line into the rendered output (the reported case typed two interpolated
strings in a row and the highlighter duplicated them). Render the whole
interpolated string as a single string-literal token instead, and add a
regression test that asserts the highlighter round-trips the exact input text.
Sub-expressions inside an interpolated string interpolation were previously
parsed by a fresh Lexer over the cooked content with no awareness of the outer
source buffer, so the resulting AST nodes carried Start/Length values relative
to that sub-buffer. Downstream consumers (most visibly the syntax highlighter)
either had to fall back to rendering the entire string as one literal or risk
indexing into the wrong buffer.

Add a position-offset constructor to Lexer that shifts every emitted token by
a fixed amount, and record a per-character source-position map for every
TokenType.InterpolatedString token (covering both the explicit "$"..."" form
and the implicit interpolation found inside a regular double-quoted string).
ExpressionParser.ParseInterpolatedStringExpression then constructs the inner
sub-Lexer with the appropriate offset and uses the source map to build
synthetic identifier tokens for variable references whose ranges align with
the outer text. The highlighter visits these sub-expressions in place and
falls back to the literal coloring for raw text chunks.

Tests cover the round-trip case, expression coloring inside an interpolated
expression, and a separately colored variable reference inside a quoted
string.
@mkrueger
Copy link
Copy Markdown
Contributor Author

Looks like

image

@mkrueger
Copy link
Copy Markdown
Contributor Author

Need to work on highlighting/colors - the light/dark theme is a valid point but it's a bit out of scope for that PR.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds terminal-friendly JSON output syntax highlighting and rainbow bracket pair colorization, plus fixes to interpolated-string input highlighting by improving token position fidelity across sub-lexers.

Changes:

  • Introduce JsonOutputHighlighter and route terminal JSON output through it (without affecting redirection/CSV/table outputs).
  • Add bracket depth color cycling in Theme and apply it in the input highlighter for {}, [], and ().
  • Improve interpolated-string parsing/highlighting by tracking per-character source mappings and supporting lexing with position offsets.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
CosmosDBShell/Azure.Data.Cosmos.Shell.Parser/Lexer.cs Adds positionOffset support and per-character source maps for interpolated string tokens.
CosmosDBShell/Azure.Data.Cosmos.Shell.Parser/ExpressionParser.cs Uses interpolated-string source maps to build accurately positioned nested-expression/variable tokens.
CosmosDBShell/Azure.Data.Cosmos.Shell.Core/Theme.cs Adds depth-cycled bracket formatting and dedicated JSON primitive formatting helpers.
CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ShellInterpreter.Highlighter.cs Applies rainbow bracket coloring and fixes interpolated-string rendering to avoid duplication and enable nested highlighting.
CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ShellInterpreter.cs Prints JSON results via the new highlighter when output is to terminal and format is JSON.
CosmosDBShell/Azure.Data.Cosmos.Shell.Core/JsonOutputHighlighter.cs New renderer that formats JsonElement as indented Spectre.Console markup with escaping.
CosmosDBShell.Tests/UtilTest/JsonOutputHighlighterTests.cs Adds unit tests for JSON markup rendering, escaping, indentation, and bracket depth cycling.
CosmosDBShell.Tests/Shell/HighlighterTests.cs Adds regression/behavior tests for interpolated-string highlighting correctness and segmentation.

Comment thread CosmosDBShell/Azure.Data.Cosmos.Shell.Parser/Lexer.cs Outdated
Comment thread CosmosDBShell/Azure.Data.Cosmos.Shell.Parser/ExpressionParser.cs Outdated
Comment thread CosmosDBShell/Azure.Data.Cosmos.Shell.Core/Theme.cs
- Use ReferenceEqualityComparer for the interpolated string source-map so
  equivalent (record-equal) Token instances cannot collide with the actual
  lexer-produced tokens in the lookup map.
- Lex the contents of an interpolated string interpolation from the raw
  outer-source slice (via new Lexer.RawInput / Lexer.PositionOffset
  accessors) instead of the cooked token content. The cooked content
  collapses escape sequences (e.g. backslash-quote becomes quote) and would
  otherwise drift token positions inside interpolations that contain string
  literals with escapes such as $( "a\nb" ). The cooked-content path is
  retained as a fallback for synthetic tokens that have no source map.
- Add a regression test that round-trips an interpolated string containing
  an escape inside an interpolation.
@mkrueger mkrueger requested a review from sevoku May 11, 2026 11:22
@mkrueger mkrueger merged commit 221355d into main May 12, 2026
8 checks passed
@mkrueger mkrueger deleted the dev/mkrueger/issue79 branch May 12, 2026 08:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants