Skip to content

Add .jsonl syntax highlighting support to TextViewer#1906

Merged
emako merged 5 commits intomasterfrom
copilot/add-jsonl-syntax-highlighting
Apr 1, 2026
Merged

Add .jsonl syntax highlighting support to TextViewer#1906
emako merged 5 commits intomasterfrom
copilot/add-jsonl-syntax-highlighting

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 1, 2026

.jsonl (JSON Lines) files had no syntax highlighting — they fell through to plain text rendering. JSONL differs from JSON in that each line is an independent JSON object, requiring dedicated per-line highlighting that resets at each line boundary.

Changes

  • Themes/HighlightingDefinitions/JsonLineHighlighter.cs — shared DocumentColorizingTransformer that tokenizes each line independently as JSON; uses a context stack to correctly distinguish object keys from string values, and colors strings, numbers, booleans, null, {} braces, and [] brackets
  • Themes/HighlightingDefinitions/Dark/JSONLHighlightingDefinition.csICustomHighlightingDefinition for dark mode registering .jsonl with VSCode-default dark colors (#569CD6 bool/null, #B5CEA8 numbers, #CE9178 strings, #9CDCF0 field names, #DA66BE braces, #FFD710 brackets)
  • Themes/HighlightingDefinitions/Light/JSONLHighlightingDefinition.csICustomHighlightingDefinition for light mode registering .jsonl with VSCode-default light colors (#0000FF bool/null, #098658 numbers, #A31515 strings, #0451A5 field names)
  • SUPPORTED_FORMATS.md — updated .json entry to include .jsonl

The implementation follows the same ICustomHighlightingDefinition + DocumentColorizingTransformer pattern used by YAML and Properties highlighting in this codebase, ensuring each JSONL line is treated as a self-contained JSON object.

Summary by Sourcery

Add JSON Lines (.jsonl) syntax highlighting support to the text viewer and document this as a supported format.

New Features:

  • Introduce light and dark theme highlighting definitions for .jsonl files using a dedicated per-line JSON tokenizer.

Enhancements:

  • Share a JsonLineHighlighter transformer to colorize JSONL tokens consistently across themes.
  • Simplify flow-direction toggling in the text viewer key handler by using the unqualified FlowDirection enum.

Documentation:

  • Update supported formats documentation to list .jsonl alongside .json.

Copilot AI linked an issue Apr 1, 2026 that may be closed by this pull request
Copilot AI changed the title [WIP] Add support for .jsonl text highlighting syntax Add .jsonl syntax highlighting support to TextViewer Apr 1, 2026
Copilot finished work on behalf of emako April 1, 2026 07:29
Copilot AI requested a review from emako April 1, 2026 07:29
Copy link
Copy Markdown
Member

@emako emako left a comment

Choose a reason for hiding this comment

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

由于 jsonl 格式和 json 不太一样,jsonl是每行都是json的日志形式,请单独设计.xshd实现,并且适配这种多行都是JSON的高亮渲染

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 1, 2026

由于 jsonl 格式和 json 不太一样,jsonl是每行都是json的日志形式,请单独设计.xshd实现,并且适配这种多行都是JSON的高亮渲染

Done in c10aaed. Created dedicated Syntax/Dark/JSONL.xshd and Syntax/Light/JSONL.xshd files separate from JSON.xshd.

The key design difference: JSONL's object {} and array [] spans do not use multiline="true", so the highlighting engine treats each line independently — when a line ends, all open spans reset. This correctly models the JSONL format where every line is a self-contained JSON object.

Copilot finished work on behalf of emako April 1, 2026 08:52
Copilot AI requested a review from emako April 1, 2026 08:52
Copy link
Copy Markdown
Member

@emako emako left a comment

Choose a reason for hiding this comment

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

没报错,但是渲染没有任何高亮效果

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 1, 2026

没报错,但是渲染没有任何高亮效果

Fixed in a5ba825. The xshd-based approach was replaced with a C# ICustomHighlightingDefinition + DocumentColorizingTransformer, which is the same pattern used by YAML/Properties in this codebase.

The new JsonLineHighlighter processes each line independently using a context stack to correctly distinguish JSON keys from values — so field names like "name" get one color and string values like "Alice" get another. Numbers, booleans, null, {}, and [] are each colored distinctly in both dark and light themes.

Copilot finished work on behalf of emako April 1, 2026 09:58
Copilot AI requested a review from emako April 1, 2026 09:58
@emako emako marked this pull request as ready for review April 1, 2026 11:09
Copilot AI review requested due to automatic review settings April 1, 2026 11:09
@emako emako merged commit 75e770d into master Apr 1, 2026
4 checks passed
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Apr 1, 2026

Reviewer's Guide

.jsonl (JSON Lines) syntax highlighting is added to the TextViewer by introducing a reusable per-line JSON tokenizer (JsonLineHighlighter) and wiring it into new light/dark JSONL highlighting definitions, along with a small FlowDirection cleanup and supported-formats doc update.

Sequence diagram for JSONL line highlighting via JsonLineHighlighter

sequenceDiagram
    actor User
    participant TextViewerPanel
    participant AvalonEditTextEditor as AvalonEditTextEditor
    participant JSONLHighlightingDefinition as JSONLHighlightingDefinition
    participant JsonLineHighlighter as JsonLineHighlighter

    User->>TextViewerPanel: Open .jsonl file
    TextViewerPanel->>AvalonEditTextEditor: LoadDocument(fileContent)
    AvalonEditTextEditor->>JSONLHighlightingDefinition: Get LineTransformers for .jsonl
    JSONLHighlightingDefinition-->>AvalonEditTextEditor: [ JsonLineHighlighter ]

    loop For each visible line
        AvalonEditTextEditor->>JsonLineHighlighter: ColorizeLine(line)
        activate JsonLineHighlighter
        JsonLineHighlighter->>JsonLineHighlighter: ColorizeJsonLine(line, text)
        JsonLineHighlighter->>JsonLineHighlighter: TokenizeString / TokenizeNumber / TokenizeKeyword
        JsonLineHighlighter->>AvalonEditTextEditor: ChangeLinePart with colors
        deactivate JsonLineHighlighter
    end

    AvalonEditTextEditor-->>User: Rendered JSONL with syntax highlighting
Loading

Class diagram for JSONLHighlightingDefinition and JsonLineHighlighter

classDiagram
    direction LR

    class ICustomHighlightingDefinition {
    }

    class HighlightingRuleSet {
    }

    class HighlightingColor {
    }

    class DocumentColorizingTransformer {
        <<abstract>>
        +ColorizeLine(line)
    }

    class LightHighlightingDefinition {
        <<abstract>>
        +string Name
        +string Extension
        +HighlightingRuleSet MainRuleSet
        +IEnumerable~HighlightingColor~ NamedHighlightingColors
        +HighlightingColor GetNamedColor(name)
        +DocumentColorizingTransformer[] LineTransformers
    }

    class DarkHighlightingDefinition {
        <<abstract>>
        +string Name
        +string Extension
        +HighlightingRuleSet MainRuleSet
        +IEnumerable~HighlightingColor~ NamedHighlightingColors
        +HighlightingColor GetNamedColor(name)
        +DocumentColorizingTransformer[] LineTransformers
    }

    class JSONLHighlightingDefinition_Light {
        +string Name
        +string Extension
        +HighlightingRuleSet MainRuleSet
        +IEnumerable~HighlightingColor~ NamedHighlightingColors
        +HighlightingColor GetNamedColor(name)
        +DocumentColorizingTransformer[] LineTransformers
    }

    class JSONLHighlightingDefinition_Dark {
        +string Name
        +string Extension
        +HighlightingRuleSet MainRuleSet
        +IEnumerable~HighlightingColor~ NamedHighlightingColors
        +HighlightingColor GetNamedColor(name)
        +DocumentColorizingTransformer[] LineTransformers
    }

    class JsonLineHighlighter {
        -string keyColor
        -string stringColor
        -string numberColor
        -string boolNullColor
        -string braceColor
        -string bracketColor
        +JsonLineHighlighter(keyColor, stringColor, numberColor, boolNullColor, braceColor, bracketColor)
        +ColorizeLine(line)
        -ColorizeJsonLine(line, text)
        -TokenizeString(line, text, start, quote, isKey)
        -TokenizeNumber(line, text, start)
        -TokenizeKeyword(line, text, start)
        -Colorize(line, from, to, hexColor)
    }

    ICustomHighlightingDefinition <|.. LightHighlightingDefinition
    ICustomHighlightingDefinition <|.. DarkHighlightingDefinition

    LightHighlightingDefinition <|-- JSONLHighlightingDefinition_Light
    DarkHighlightingDefinition <|-- JSONLHighlightingDefinition_Dark

    DocumentColorizingTransformer <|-- JsonLineHighlighter

    JSONLHighlightingDefinition_Light o--> JsonLineHighlighter : uses
    JSONLHighlightingDefinition_Dark o--> JsonLineHighlighter : uses
Loading

File-Level Changes

Change Details Files
Add a reusable per-line JSON tokenizer for JSONL that classifies keys, values, numbers, booleans, null, braces, and brackets and applies colorization via AvalonEdit.
  • Introduce JsonLineHighlighter as a DocumentColorizingTransformer that processes each line independently, using a context stack to distinguish object vs array contexts and to determine when strings represent keys vs values.
  • Implement token scanners for strings (with escape handling), numbers (including decimals and exponents), and keywords (true/false/null), applying different colors based on token type and key/value role.
  • Wire up a helper Colorize method that maps character ranges in a line to foreground brushes created from hex colors via ToBrush().
QuickLook.Plugin/QuickLook.Plugin.TextViewer/Themes/HighlightingDefinitions/Light/JSONLHighlightingDefinition.cs
Define light and dark JSONL highlighting definitions that register the .jsonl extension and plug in the shared JsonLineHighlighter with theme-appropriate colors.
  • Create Light JSONLHighlightingDefinition implementing LightHighlightingDefinition, with Name/Extension/MainRuleSet overrides and a LineTransformers array configured with VSCode-like light theme colors for keys, strings, numbers, booleans/null, braces, and brackets.
  • Create Dark JSONLHighlightingDefinition implementing DarkHighlightingDefinition, referencing the shared JsonLineHighlighter type from the Light namespace and configuring VSCode-like dark theme colors for the same token categories.
  • Ensure both definitions return empty NamedHighlightingColors and null from GetNamedColor since all coloring is driven by the line transformer rather than a rule set.
QuickLook.Plugin/QuickLook.Plugin.TextViewer/Themes/HighlightingDefinitions/Light/JSONLHighlightingDefinition.cs
QuickLook.Plugin/QuickLook.Plugin.TextViewer/Themes/HighlightingDefinitions/Dark/JSONLHighlightingDefinition.cs
Clean up FlowDirection switching in the TextViewer key handler and document JSONL support.
  • Simplify FlowDirection assignment when handling Ctrl+Shift shortcuts by dropping the redundant System.Windows qualifier.
  • Update SUPPORTED_FORMATS.md to indicate that both .json and .jsonl are supported for JSON/JSON Lines files.
QuickLook.Plugin/QuickLook.Plugin.TextViewer/TextViewerPanel.cs
SUPPORTED_FORMATS.md

Assessment against linked issues

Issue Objective Addressed Explanation
#1905 Add syntax highlighting support for .jsonl files in the TextViewer, including registering the .jsonl extension with the highlighting system.
#1905 Ensure .jsonl syntax highlighting works in both light and dark themes.
#1905 Update documentation to list .jsonl as a supported, syntax-highlighted format.

Possibly linked issues

  • #(unknown): Yes. The PR implements JSONL syntax highlighting and registers .jsonl support, directly fulfilling the issue request.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • Consider moving JsonLineHighlighter into a shared, neutral namespace/file (rather than the Light theme file) so the Dark theme doesn’t depend on a Light-specific namespace and future reuse is clearer.
  • JsonLineHighlighter could be made sealed and/or have its visibility reduced (e.g., internal) if it’s not intended for consumption outside the text viewer theming infrastructure, which will make the public surface area clearer.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider moving `JsonLineHighlighter` into a shared, neutral namespace/file (rather than the Light theme file) so the Dark theme doesn’t depend on a Light-specific namespace and future reuse is clearer.
- `JsonLineHighlighter` could be made `sealed` and/or have its visibility reduced (e.g., `internal`) if it’s not intended for consumption outside the text viewer theming infrastructure, which will make the public surface area clearer.

## Individual Comments

### Comment 1
<location path="QuickLook.Plugin/QuickLook.Plugin.TextViewer/Themes/HighlightingDefinitions/Light/JSONLHighlightingDefinition.cs" line_range="194-202" />
<code_context>
+    {
+        int i = start;
+        while (i < text.Length && char.IsLetter(text[i])) i++;
+        string keyword = text.Substring(start, i - start);
+        if (keyword is "true" or "false" or "null")
+            Colorize(line, start, i, boolNullColor);
</code_context>
<issue_to_address>
**suggestion (performance):** Avoid allocating a substring for keyword detection to reduce per-line allocations.

`TokenizeKeyword` currently calls `text.Substring(start, i - start)` for every keyword-like token, allocating a new string each time. Since you only need to recognize `true`, `false`, and `null`, you can compare length and characters directly on `text` (or use spans) to avoid these allocations and reduce GC pressure on large JSONL inputs.

```suggestion
    private int TokenizeKeyword(DocumentLine line, string text, int start)
    {
        int i = start;
        while (i < text.Length && char.IsLetter(text[i])) i++;

        int length = i - start;

        // Avoid substring allocations by checking expected keywords directly on the source text
        bool isBoolOrNull =
            (length == 4 && (
                // "true"
                (text[start] == 't' && text[start + 1] == 'r' && text[start + 2] == 'u' && text[start + 3] == 'e') ||
                // "null"
                (text[start] == 'n' && text[start + 1] == 'u' && text[start + 2] == 'l' && text[start + 3] == 'l')
            ))
            ||
            (length == 5 &&
                // "false"
                text[start] == 'f' && text[start + 1] == 'a' && text[start + 2] == 'l' && text[start + 3] == 's' && text[start + 4] == 'e');

        if (isBoolOrNull)
            Colorize(line, start, i, boolNullColor);

        return i;
    }
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +194 to +202
private int TokenizeKeyword(DocumentLine line, string text, int start)
{
int i = start;
while (i < text.Length && char.IsLetter(text[i])) i++;
string keyword = text.Substring(start, i - start);
if (keyword is "true" or "false" or "null")
Colorize(line, start, i, boolNullColor);
return i;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (performance): Avoid allocating a substring for keyword detection to reduce per-line allocations.

TokenizeKeyword currently calls text.Substring(start, i - start) for every keyword-like token, allocating a new string each time. Since you only need to recognize true, false, and null, you can compare length and characters directly on text (or use spans) to avoid these allocations and reduce GC pressure on large JSONL inputs.

Suggested change
private int TokenizeKeyword(DocumentLine line, string text, int start)
{
int i = start;
while (i < text.Length && char.IsLetter(text[i])) i++;
string keyword = text.Substring(start, i - start);
if (keyword is "true" or "false" or "null")
Colorize(line, start, i, boolNullColor);
return i;
}
private int TokenizeKeyword(DocumentLine line, string text, int start)
{
int i = start;
while (i < text.Length && char.IsLetter(text[i])) i++;
int length = i - start;
// Avoid substring allocations by checking expected keywords directly on the source text
bool isBoolOrNull =
(length == 4 && (
// "true"
(text[start] == 't' && text[start + 1] == 'r' && text[start + 2] == 'u' && text[start + 3] == 'e') ||
// "null"
(text[start] == 'n' && text[start + 1] == 'u' && text[start + 2] == 'l' && text[start + 3] == 'l')
))
||
(length == 5 &&
// "false"
text[start] == 'f' && text[start + 1] == 'a' && text[start + 2] == 'l' && text[start + 3] == 's' && text[start + 4] == 'e');
if (isBoolOrNull)
Colorize(line, start, i, boolNullColor);
return i;
}

Copy link
Copy Markdown
Contributor

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 custom syntax highlighting support for .jsonl (JSON Lines) files in the TextViewer plugin by introducing dedicated light/dark highlighting definitions and a per-line JSON tokenizer so each line is colorized independently.

Changes:

  • Added light and dark JSONLHighlightingDefinition implementations registering the .jsonl extension and supplying a line-based colorizer.
  • Implemented a DocumentColorizingTransformer-based per-line tokenizer to colorize JSONL tokens (keys/strings/numbers/bool/null/braces/brackets).
  • Updated SUPPORTED_FORMATS.md to list .jsonl alongside .json.

Reviewed changes

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

File Description
SUPPORTED_FORMATS.md Documents .jsonl as a supported text format.
QuickLook.Plugin/QuickLook.Plugin.TextViewer/Themes/HighlightingDefinitions/Light/JSONLHighlightingDefinition.cs Adds light-theme JSONL highlighting and the per-line tokenizer implementation.
QuickLook.Plugin/QuickLook.Plugin.TextViewer/Themes/HighlightingDefinitions/Dark/JSONLHighlightingDefinition.cs Adds dark-theme JSONL highlighting (currently reusing the tokenizer via Light namespace).
QuickLook.Plugin/QuickLook.Plugin.TextViewer/TextViewerPanel.cs Small change to FlowDirection assignment style in key handling.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +155 to +171
int i = start + 1; // skip opening quote
while (i < text.Length)
{
if (text[i] == '\\')
{
i += 2; // skip escape sequence
continue;
}
if (text[i] == quote)
{
i++; // include closing quote
break;
}
i++;
}
Colorize(line, start, i, isKey ? keyColor : stringColor);
return i;
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

TokenizeString can advance i past text.Length when a line ends with a trailing backslash (e.g., an incomplete escape sequence). That makes Colorize call ChangeLinePart with an end offset beyond the line end, which can throw. Clamp i to text.Length (or handle \ at end explicitly) before calling Colorize.

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +60
/// <summary>
/// Per-line JSON tokenizer for JSONL (JSON Lines) files.
/// Each call to ColorizeLine processes one independent JSON object/array.
/// </summary>
public class JsonLineHighlighter(
string keyColor,
string stringColor,
string numberColor,
string boolNullColor,
string braceColor,
string bracketColor) : DocumentColorizingTransformer
{
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

JsonLineHighlighter is declared in the Light theme namespace/file but is also used by the Dark JSONL definition (via a using ...Light). This creates an odd cross-theme dependency and makes reuse harder. Consider moving JsonLineHighlighter into a theme-agnostic namespace/file (e.g., Themes/HighlightingDefinitions/JsonLineHighlighter.cs) and make it internal if it’s not part of the public API.

Copilot uses AI. Check for mistakes.

using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Rendering;
using QuickLook.Plugin.TextViewer.Themes.HighlightingDefinitions.Light;
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

The Dark JSONL highlighting definition depends on the Light theme namespace solely to access JsonLineHighlighter (using ...HighlightingDefinitions.Light). This couples theme implementations together and complicates future refactors. Prefer placing shared transformers in a common HighlightingDefinitions namespace and referencing that from both themes.

Suggested change
using QuickLook.Plugin.TextViewer.Themes.HighlightingDefinitions.Light;
using QuickLook.Plugin.TextViewer.Themes.HighlightingDefinitions;

Copilot uses AI. Check for mistakes.
Comment on lines 165 to +168
if (Keyboard.IsKeyDown(Key.RightShift))
FlowDirection = System.Windows.FlowDirection.RightToLeft;
FlowDirection = FlowDirection.RightToLeft;
else if (Keyboard.IsKeyDown(Key.LeftShift))
FlowDirection = System.Windows.FlowDirection.LeftToRight;
FlowDirection = FlowDirection.LeftToRight;
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

This block now mixes unqualified FlowDirection.RightToLeft/LeftToRight with a fully-qualified System.Windows.FlowDirection.RightToLeft later in the same file (see the RTL auto-detect logic). Consider using one style consistently (either fully qualify here too, or rely on the using System.Windows; everywhere) to avoid confusion around the FlowDirection property vs enum type.

Copilot uses AI. Check for mistakes.
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.

Support text highlighting syntax for .jsonl

3 participants