Highlight JSON, TSCN and scenario fields across editor tools
This commit is contained in:
1 parent
84894b1e98
commit
cbc9472738
7 files changed
+70
-29
No files matched your search
@@ -119,12 +119,13 @@ namespace Cthangover.Tools.Common
|
|||||||
/// <summary>Monospace font for IDs, numbers and code (style guide <c>--ct-mono</c>).</summary>
|
/// <summary>Monospace font for IDs, numbers and code (style guide <c>--ct-mono</c>).</summary>
|
||||||
public static Font Mono() => ToolWindow.GetMonospaceFont();
|
public static Font Mono() => ToolWindow.GetMonospaceFont();
|
||||||
|
|
||||||
/// <summary>Code editor preset: monospace, line numbers, current-line highlight, no wrap.</summary>
|
/// <summary>Code editor preset: monospace, line numbers, JSON highlighting and no wrap; scenario/scene callers select their syntax.</summary>
|
||||||
public static CodeEdit Code(bool editable = true)
|
public static CodeEdit Code(bool editable = true)
|
||||||
{
|
{
|
||||||
var edit = new CodeEdit
|
var edit = new CodeEdit
|
||||||
{
|
{
|
||||||
Editable = editable,
|
Editable = editable,
|
||||||
|
SyntaxHighlighter = SourceSyntax.CreateJsonHighlighter(),
|
||||||
GuttersDrawLineNumbers = true,
|
GuttersDrawLineNumbers = true,
|
||||||
HighlightCurrentLine = true,
|
HighlightCurrentLine = true,
|
||||||
WrapMode = TextEdit.LineWrappingMode.None,
|
WrapMode = TextEdit.LineWrappingMode.None,
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ namespace Cthangover.Core.UI.Tool.LightEditor
|
|||||||
canvasPanel.AddChild(_canvas);
|
canvasPanel.AddChild(_canvas);
|
||||||
|
|
||||||
_lineEdit = ToolUi.Code(editable: false);
|
_lineEdit = ToolUi.Code(editable: false);
|
||||||
|
_lineEdit.SyntaxHighlighter = ScenarioSyntaxService.CreateHighlighter();
|
||||||
_lineEdit.CustomMinimumSize = new Vector2(0, 70);
|
_lineEdit.CustomMinimumSize = new Vector2(0, 70);
|
||||||
_lineEdit.WrapMode = TextEdit.LineWrappingMode.Boundary;
|
_lineEdit.WrapMode = TextEdit.LineWrappingMode.Boundary;
|
||||||
_lineEdit.Visible = false;
|
_lineEdit.Visible = false;
|
||||||
|
|||||||
@@ -249,6 +249,7 @@ namespace Cthangover.Core.UI.Tool.SceneBuilder
|
|||||||
_sideTabs.SetTabTitle(1, ToolUi.Tr("tools/scene_builder/tab_tscn"));
|
_sideTabs.SetTabTitle(1, ToolUi.Tr("tools/scene_builder/tab_tscn"));
|
||||||
|
|
||||||
_tscnEditor = ToolUi.Code();
|
_tscnEditor = ToolUi.Code();
|
||||||
|
_tscnEditor.SyntaxHighlighter = SourceSyntax.CreateTscnHighlighter();
|
||||||
_tscnEditor.TextChanged += OnTscnTextChanged;
|
_tscnEditor.TextChanged += OnTscnTextChanged;
|
||||||
tscnTab.AddChild(_tscnEditor);
|
tscnTab.AddChild(_tscnEditor);
|
||||||
tscnTab.AddChild(ToolUi.Hint(ToolUi.Tr("tools/scene_builder/tscn_hint")));
|
tscnTab.AddChild(ToolUi.Hint(ToolUi.Tr("tools/scene_builder/tscn_hint")));
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ namespace Cthangover.Tools.Schemas
|
|||||||
/// the kind (by name), of player/party/characters;</item>
|
/// the kind (by name), of player/party/characters;</item>
|
||||||
/// <item><c>ref_list</c> — a comma list with a drop-down that appends an entity;</item>
|
/// <item><c>ref_list</c> — a comma list with a drop-down that appends an entity;</item>
|
||||||
/// <item><c>picture</c> — a path with the picture grid of the editors;</item>
|
/// <item><c>picture</c> — a path with the picture grid of the editors;</item>
|
||||||
/// <item>anything else — a text box (monospace for formulas, conditions and JSON).</item>
|
/// <item><c>json</c> — a multiline source editor with syntax highlighting;</item>
|
||||||
|
/// <item>anything else — a text box (monospace for formulas and conditions).</item>
|
||||||
/// </list>
|
/// </list>
|
||||||
/// A current value the options do not know is kept and shown first, so opening and
|
/// A current value the options do not know is kept and shown first, so opening and
|
||||||
/// saving never loses data the schema did not foresee.
|
/// saving never loses data the schema did not foresee.
|
||||||
@@ -28,6 +29,7 @@ namespace Cthangover.Tools.Schemas
|
|||||||
{
|
{
|
||||||
private readonly Action changed;
|
private readonly Action changed;
|
||||||
private readonly LineEdit text;
|
private readonly LineEdit text;
|
||||||
|
private readonly CodeEdit json;
|
||||||
private readonly OptionButton picker;
|
private readonly OptionButton picker;
|
||||||
private readonly CheckBox check;
|
private readonly CheckBox check;
|
||||||
private readonly List<string> pickerValues = new();
|
private readonly List<string> pickerValues = new();
|
||||||
@@ -74,10 +76,19 @@ namespace Cthangover.Tools.Schemas
|
|||||||
}
|
}
|
||||||
Editor = ToolUi.Field(parent, label, row, tip);
|
Editor = ToolUi.Field(parent, label, row, tip);
|
||||||
break;
|
break;
|
||||||
|
case "json":
|
||||||
|
json = ToolUi.Code();
|
||||||
|
json.Name = "Text_" + field.Key;
|
||||||
|
json.Text = field.Default ?? string.Empty;
|
||||||
|
json.CustomMinimumSize = new Vector2(0, 90);
|
||||||
|
json.SizeFlagsVertical = Control.SizeFlags.Fill;
|
||||||
|
json.TextChanged += OnJsonChanged;
|
||||||
|
Editor = ToolUi.Field(parent, label, json, tip);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
text = ToolUi.Text(field.Default ?? string.Empty, OnTyped);
|
text = ToolUi.Text(field.Default ?? string.Empty, OnTyped);
|
||||||
text.Name = "Text_" + field.Key;
|
text.Name = "Text_" + field.Key;
|
||||||
if (Type is "formula" or "condition" or "json")
|
if (Type is "formula" or "condition")
|
||||||
text.AddThemeFontOverride("font", ToolUi.Mono());
|
text.AddThemeFontOverride("font", ToolUi.Mono());
|
||||||
Editor = ToolUi.Field(parent, label, text, tip);
|
Editor = ToolUi.Field(parent, label, text, tip);
|
||||||
break;
|
break;
|
||||||
@@ -103,6 +114,8 @@ namespace Cthangover.Tools.Schemas
|
|||||||
return check.ButtonPressed ? "1" : string.Empty;
|
return check.ButtonPressed ? "1" : string.Empty;
|
||||||
if (text != null)
|
if (text != null)
|
||||||
return text.Text.Trim();
|
return text.Text.Trim();
|
||||||
|
if (json != null)
|
||||||
|
return json.Text.Trim();
|
||||||
return picker != null && picker.Selected >= 0 && picker.Selected < pickerValues.Count ? pickerValues[picker.Selected] : string.Empty;
|
return picker != null && picker.Selected >= 0 && picker.Selected < pickerValues.Count ? pickerValues[picker.Selected] : string.Empty;
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
@@ -112,6 +125,8 @@ namespace Cthangover.Tools.Schemas
|
|||||||
check.ButtonPressed = value is "1" or "true" or "True" or "yes";
|
check.ButtonPressed = value is "1" or "true" or "True" or "yes";
|
||||||
else if (text != null)
|
else if (text != null)
|
||||||
text.Text = value ?? string.Empty;
|
text.Text = value ?? string.Empty;
|
||||||
|
else if (json != null)
|
||||||
|
json.Text = value ?? string.Empty;
|
||||||
else
|
else
|
||||||
FillPicker(value ?? string.Empty);
|
FillPicker(value ?? string.Empty);
|
||||||
suppress = false;
|
suppress = false;
|
||||||
@@ -155,6 +170,8 @@ namespace Cthangover.Tools.Schemas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnJsonChanged() => OnTyped(json.Text);
|
||||||
|
|
||||||
private void OnTyped(string value)
|
private void OnTyped(string value)
|
||||||
{
|
{
|
||||||
if (!suppress)
|
if (!suppress)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Cthangover.Mods.Dialog.Scenarios;
|
|||||||
using Cthangover.Core.Scenarios;
|
using Cthangover.Core.Scenarios;
|
||||||
using Cthangover.Core.Factories.Impls;
|
using Cthangover.Core.Factories.Impls;
|
||||||
using Cthangover.Core.UI.Theming;
|
using Cthangover.Core.UI.Theming;
|
||||||
|
using Cthangover.Mods.Interface.Theming;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace Cthangover.Tools.Services
|
namespace Cthangover.Tools.Services
|
||||||
@@ -20,36 +21,12 @@ namespace Cthangover.Tools.Services
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Builds a <see cref="CodeHighlighter"/> for scenario DSL syntax.
|
/// Builds a <see cref="CodeHighlighter"/> for scenario DSL syntax.
|
||||||
/// Comments (lines starting with <c>#</c>) are green, strings are bright
|
/// Comments (lines starting with <c>#</c>) are faint, strings are bright
|
||||||
/// gold, command names are blue, and argument keywords (e.g. <c>key</c>,
|
/// gold, command names are blue, and argument keywords (e.g. <c>key</c>,
|
||||||
/// <c>wait</c>, <c>quest_id</c>) are amber — all from <see cref="UiTokens"/>.
|
/// <c>wait</c>, <c>quest_id</c>) are amber — all from <see cref="UiTokens"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static CodeHighlighter CreateHighlighter()
|
public static CodeHighlighter CreateHighlighter()
|
||||||
{
|
=> SourceSyntax.CreateScenarioHighlighter(ScenarioCommandStrategyFactory.GetAllCommandNames());
|
||||||
var hl = new CodeHighlighter();
|
|
||||||
hl.AddColorRegion("#", null, UiTokens.Success, true);
|
|
||||||
hl.AddColorRegion("\"", "\"", UiTokens.AccentBright, false);
|
|
||||||
hl.NumberColor = UiTokens.Info;
|
|
||||||
hl.SymbolColor = UiTokens.Muted;
|
|
||||||
|
|
||||||
var cmdColor = UiTokens.Info;
|
|
||||||
hl.KeywordColors = new Godot.Collections.Dictionary();
|
|
||||||
|
|
||||||
foreach (var cmd in ScenarioCommandStrategyFactory.GetAllCommandNames())
|
|
||||||
hl.KeywordColors[cmd] = cmdColor;
|
|
||||||
|
|
||||||
foreach (var cmd in new[] {
|
|
||||||
"option", "select"
|
|
||||||
}) hl.KeywordColors[cmd] = cmdColor;
|
|
||||||
|
|
||||||
var argColor = UiTokens.Accent;
|
|
||||||
foreach (var kw in new[] {
|
|
||||||
"key", "first", "second", "hide_color", "wait", "speed", "hidden",
|
|
||||||
"sprites", "loop", "target", "quest_id", "show", "hide", "duration"
|
|
||||||
}) hl.KeywordColors[kw] = argColor;
|
|
||||||
|
|
||||||
return hl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Splits a line into tokens, handling quoted strings (with backslash
|
/// Splits a line into tokens, handling quoted strings (with backslash
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ namespace Cthangover.Tools.Tests.Windows
|
|||||||
/// <summary>Runs the checks for <paramref name="toolId"/>; <paramref name="check"/> records each result.</summary>
|
/// <summary>Runs the checks for <paramref name="toolId"/>; <paramref name="check"/> records each result.</summary>
|
||||||
public static void Run(string toolId, Window window, Action<bool, string> check)
|
public static void Run(string toolId, Window window, Action<bool, string> check)
|
||||||
{
|
{
|
||||||
|
foreach (var editor in window.FindChildren("*", "", true, false).OfType<CodeEdit>())
|
||||||
|
check(editor.SyntaxHighlighter != null, toolId + ": source field has syntax highlighting");
|
||||||
switch (toolId)
|
switch (toolId)
|
||||||
{
|
{
|
||||||
case "map_editor":
|
case "map_editor":
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
namespace Cthangover.Tools.Tests
|
||||||
|
{
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using Cthangover.Core.Mods.Testing;
|
||||||
|
using Cthangover.Mods.Schemas;
|
||||||
|
using Cthangover.Tools.Schemas;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
/// <summary>JSON schema fields preserve structured values through their highlighted multiline editor.</summary>
|
||||||
|
public class SchemaFieldControlTests
|
||||||
|
{
|
||||||
|
[ModTest("schema/json_field_roundtrip")]
|
||||||
|
static void JsonFieldRoundtrip(ModTestContext ctx)
|
||||||
|
{
|
||||||
|
var parent = new VBoxContainer();
|
||||||
|
((SceneTree)Engine.GetMainLoop()).Root.AddChild(parent);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var field = new SchemaFieldControl(parent, new FieldSchema { Key = "Extra", Type = "json" }, null);
|
||||||
|
ModAssert.True(field.Editor is CodeEdit, "JSON field has a multiline code editor");
|
||||||
|
var code = (CodeEdit)field.Editor;
|
||||||
|
ModAssert.True(code.SyntaxHighlighter != null, "JSON field has syntax highlighting");
|
||||||
|
var original = new JsonObject
|
||||||
|
{
|
||||||
|
["text"] = "Unicode text: \u041f\u0440\u0438\u0432\u0435\u0442",
|
||||||
|
["active"] = true,
|
||||||
|
["nested"] = new JsonArray(1, null, 3),
|
||||||
|
};
|
||||||
|
field.Value = JsonSerializer.Serialize(original, new JsonSerializerOptions { WriteIndented = true });
|
||||||
|
var restored = JsonNode.Parse(field.Value);
|
||||||
|
ModAssert.Equal(original["text"].GetValue<string>(), restored["text"].GetValue<string>(), "text survives the field");
|
||||||
|
ModAssert.True(restored["active"].GetValue<bool>(), "boolean survives the field");
|
||||||
|
ModAssert.Equal(3, restored["nested"].AsArray().Count, "array survives the field");
|
||||||
|
ModAssert.True(restored["nested"][1] == null, "null survives the field");
|
||||||
|
code.Text = "{\n \"number\": 42\n}";
|
||||||
|
ModAssert.Equal(42, JsonNode.Parse(field.Value)["number"].GetValue<int>(), "edited JSON is returned by the form");
|
||||||
|
}
|
||||||
|
finally { parent.Free(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user