diff --git a/QuickLook.Plugin/QuickLook.Plugin.TextViewer/TextViewerPanel.cs b/QuickLook.Plugin/QuickLook.Plugin.TextViewer/TextViewerPanel.cs index 98606ab27..4657eb1e5 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.TextViewer/TextViewerPanel.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.TextViewer/TextViewerPanel.cs @@ -163,9 +163,9 @@ private void Viewer_KeyDown(object sender, KeyEventArgs e) // RTL: Ctrl + RShift // LTR: Ctrl + LShift 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; } else if (Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)) { diff --git a/QuickLook.Plugin/QuickLook.Plugin.TextViewer/Themes/HighlightingDefinitions/Dark/JSONLHighlightingDefinition.cs b/QuickLook.Plugin/QuickLook.Plugin.TextViewer/Themes/HighlightingDefinitions/Dark/JSONLHighlightingDefinition.cs new file mode 100644 index 000000000..352def1f7 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.TextViewer/Themes/HighlightingDefinitions/Dark/JSONLHighlightingDefinition.cs @@ -0,0 +1,47 @@ +// Copyright © 2017-2026 QL-Win Contributors +// +// This file is part of QuickLook program. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using ICSharpCode.AvalonEdit.Highlighting; +using ICSharpCode.AvalonEdit.Rendering; +using QuickLook.Plugin.TextViewer.Themes.HighlightingDefinitions.Light; +using System.Collections.Generic; + +namespace QuickLook.Plugin.TextViewer.Themes.HighlightingDefinitions.Dark; + +public class JSONLHighlightingDefinition : DarkHighlightingDefinition +{ + public override string Name => "JSONL"; + + public override string Extension => ".jsonl"; + + public override HighlightingRuleSet MainRuleSet => new(); + + public override HighlightingColor GetNamedColor(string name) => null; + + public override IEnumerable NamedHighlightingColors => []; + + public override DocumentColorizingTransformer[] LineTransformers { get; } = + [ + new JsonLineHighlighter( + keyColor: "#9CDCF0", // field names (VSCode default) + stringColor: "#CE9178", // string values (VSCode default) + numberColor: "#B5CEA8", // numbers (VSCode default) + boolNullColor:"#569CD6", // true/false/null (VSCode default) + braceColor: "#DA66BE", // { } + bracketColor: "#FFD710") // [ ] + ]; +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.TextViewer/Themes/HighlightingDefinitions/Light/JSONLHighlightingDefinition.cs b/QuickLook.Plugin/QuickLook.Plugin.TextViewer/Themes/HighlightingDefinitions/Light/JSONLHighlightingDefinition.cs new file mode 100644 index 000000000..4568154f8 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.TextViewer/Themes/HighlightingDefinitions/Light/JSONLHighlightingDefinition.cs @@ -0,0 +1,211 @@ +// Copyright © 2017-2026 QL-Win Contributors +// +// This file is part of QuickLook program. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using ICSharpCode.AvalonEdit.Document; +using ICSharpCode.AvalonEdit.Highlighting; +using ICSharpCode.AvalonEdit.Rendering; +using System.Collections.Generic; + +namespace QuickLook.Plugin.TextViewer.Themes.HighlightingDefinitions.Light; + +public class JSONLHighlightingDefinition : LightHighlightingDefinition +{ + public override string Name => "JSONL"; + + public override string Extension => ".jsonl"; + + public override HighlightingRuleSet MainRuleSet => new(); + + public override HighlightingColor GetNamedColor(string name) => null; + + public override IEnumerable NamedHighlightingColors => []; + + public override DocumentColorizingTransformer[] LineTransformers { get; } = + [ + new JsonLineHighlighter( + keyColor: "#0451A5", // field names (VSCode light default) + stringColor: "#A31515", // string values (VSCode light default) + numberColor: "#098658", // numbers (VSCode light default) + boolNullColor:"#0000FF", // true/false/null (VSCode light default) + braceColor: "#800000", // { } + bracketColor: "#0451A5") // [ ] + ]; +} + +/// +/// Per-line JSON tokenizer for JSONL (JSON Lines) files. +/// Each call to ColorizeLine processes one independent JSON object/array. +/// +public class JsonLineHighlighter( + string keyColor, + string stringColor, + string numberColor, + string boolNullColor, + string braceColor, + string bracketColor) : DocumentColorizingTransformer +{ + protected override void ColorizeLine(DocumentLine line) + { + var text = CurrentContext.Document.GetText(line); + if (string.IsNullOrWhiteSpace(text)) + return; + + ColorizeJsonLine(line, text); + } + + private void ColorizeJsonLine(DocumentLine line, string text) + { + // Stack: true = currently inside an object (next unquoted string is a key), + // false = currently inside an array (all strings are values). + var contextStack = new Stack(); + bool expectKey = false; + + int i = 0; + while (i < text.Length) + { + char c = text[i]; + + if (char.IsWhiteSpace(c)) { i++; continue; } + + switch (c) + { + case '{': + Colorize(line, i, i + 1, braceColor); + contextStack.Push(true); + expectKey = true; + i++; + break; + + case '}': + Colorize(line, i, i + 1, braceColor); + if (contextStack.Count > 0) contextStack.Pop(); + expectKey = false; + i++; + break; + + case '[': + Colorize(line, i, i + 1, bracketColor); + contextStack.Push(false); + expectKey = false; + i++; + break; + + case ']': + Colorize(line, i, i + 1, bracketColor); + if (contextStack.Count > 0) contextStack.Pop(); + expectKey = false; + i++; + break; + + case ':': + // Colon separates key from value; next token is a value. + expectKey = false; + i++; + break; + + case ',': + // In object context the next string is a key; in array it is a value. + expectKey = contextStack.Count > 0 && contextStack.Peek(); + i++; + break; + + case '"': + case '\'': + i = TokenizeString(line, text, i, c, expectKey); + if (expectKey) + expectKey = false; // key consumed, colon follows next + break; + + default: + if (c == '-' || char.IsDigit(c)) + { + i = TokenizeNumber(line, text, i); + expectKey = false; + } + else if (char.IsLetter(c)) + { + i = TokenizeKeyword(line, text, i); + expectKey = false; + } + else + { + i++; + } + break; + } + } + } + + private int TokenizeString(DocumentLine line, string text, int start, char quote, bool isKey) + { + 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; + } + + private int TokenizeNumber(DocumentLine line, string text, int start) + { + int i = start; + if (i < text.Length && text[i] == '-') i++; // optional leading minus + while (i < text.Length && char.IsDigit(text[i])) i++; + if (i < text.Length && text[i] == '.') + { + i++; + while (i < text.Length && char.IsDigit(text[i])) i++; + } + if (i < text.Length && (text[i] == 'e' || text[i] == 'E')) + { + i++; + if (i < text.Length && (text[i] == '+' || text[i] == '-')) i++; + while (i < text.Length && char.IsDigit(text[i])) i++; + } + Colorize(line, start, i, numberColor); + return i; + } + + 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 void Colorize(DocumentLine line, int from, int to, string hexColor) + { + ChangeLinePart(line.Offset + from, line.Offset + to, el => + { + el.TextRunProperties.SetForegroundBrush(hexColor.ToBrush()); + }); + } +} diff --git a/SUPPORTED_FORMATS.md b/SUPPORTED_FORMATS.md index b41df9393..6d103bd96 100644 --- a/SUPPORTED_FORMATS.md +++ b/SUPPORTED_FORMATS.md @@ -7,7 +7,7 @@ Update not completed yet... - `.rtf` (Rich Text Format) - `.log` (Log file) - `.ini` (Initialization/config file) -- `.json` (JavaScript Object Notation) +- `.json`, `.jsonl` (JavaScript Object Notation / JSON Lines) - `.xml` (Extensible Markup Language) - `.yaml`, `.yml` (YAML Ain't Markup Language) - `.md`, `.markdown`, `.mdx`, `.mmd`, `.mkd`, `.mdwn`, `.mdown`, `.mdc`, `.qmd`, `.rmd`, `.rmarkdown`, `.apib`, `.mdtxt`, `.mdtext` (Markdown and variants)