Add .jsonl syntax highlighting support to TextViewer#1906
Conversation
Agent-Logs-Url: https://github.com/QL-Win/QuickLook/sessions/09cbe11d-0505-4679-860c-e3f9cf30da4b Co-authored-by: emako <24737061+emako@users.noreply.github.com>
emako
left a comment
There was a problem hiding this comment.
由于 jsonl 格式和 json 不太一样,jsonl是每行都是json的日志形式,请单独设计.xshd实现,并且适配这种多行都是JSON的高亮渲染
…highlighting Agent-Logs-Url: https://github.com/QL-Win/QuickLook/sessions/e0b27a9b-d9a3-4ec2-bec3-1f7c0ed26203 Co-authored-by: emako <24737061+emako@users.noreply.github.com>
Done in c10aaed. Created dedicated The key design difference: JSONL's object |
… tokenizer Agent-Logs-Url: https://github.com/QL-Win/QuickLook/sessions/9856efe6-ce09-448b-87a1-a02481ee99fe Co-authored-by: emako <24737061+emako@users.noreply.github.com>
Fixed in a5ba825. The xshd-based approach was replaced with a C# The new |
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 JsonLineHighlightersequenceDiagram
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
Class diagram for JSONLHighlightingDefinition and JsonLineHighlighterclassDiagram
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
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- Consider moving
JsonLineHighlighterinto 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. JsonLineHighlightercould be madesealedand/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>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| 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; | ||
| } |
There was a problem hiding this comment.
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.
| 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; | |
| } |
There was a problem hiding this comment.
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
JSONLHighlightingDefinitionimplementations registering the.jsonlextension 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.mdto list.jsonlalongside.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.
| 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; |
There was a problem hiding this comment.
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.
| /// <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 | ||
| { |
There was a problem hiding this comment.
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.
|
|
||
| using ICSharpCode.AvalonEdit.Highlighting; | ||
| using ICSharpCode.AvalonEdit.Rendering; | ||
| using QuickLook.Plugin.TextViewer.Themes.HighlightingDefinitions.Light; |
There was a problem hiding this comment.
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.
| using QuickLook.Plugin.TextViewer.Themes.HighlightingDefinitions.Light; | |
| using QuickLook.Plugin.TextViewer.Themes.HighlightingDefinitions; |
| 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; |
There was a problem hiding this comment.
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.
.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— sharedDocumentColorizingTransformerthat 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[]bracketsThemes/HighlightingDefinitions/Dark/JSONLHighlightingDefinition.cs—ICustomHighlightingDefinitionfor dark mode registering.jsonlwith VSCode-default dark colors (#569CD6bool/null,#B5CEA8numbers,#CE9178strings,#9CDCF0field names,#DA66BEbraces,#FFD710brackets)Themes/HighlightingDefinitions/Light/JSONLHighlightingDefinition.cs—ICustomHighlightingDefinitionfor light mode registering.jsonlwith VSCode-default light colors (#0000FFbool/null,#098658numbers,#A31515strings,#0451A5field names)SUPPORTED_FORMATS.md— updated.jsonentry to include.jsonlThe implementation follows the same
ICustomHighlightingDefinition+DocumentColorizingTransformerpattern 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:
Enhancements:
Documentation: