Zum Inhalt
Kuonen.UI

Code-Editor

Ein Editor für Abfragen wie in SSMS: Hervorhebung je Dialekt, Zeilennummern, Faltung (Strg+M Strg+M), Vorschläge (Strg+Leertaste) und Parameterhilfe, Suchen und Ersetzen (Strg+F, Strg+H), Gehe zu Zeile (Strg+G), Kommentar (Strg+K Strg+C), Spaltenauswahl (Alt+Ziehen). „Prüfen“ setzt Beispieldiagnosen: Wellenlinien mit Kurzinfo, F8 springt zur nächsten.

Vorführung: Code-EditorVorführung: Code-Editor

KXAML

CodeEditorSeite.kxaml
<StackPanel xmlns="https://ui.kuonen.org/kxaml"
            xmlns:x="https://ui.kuonen.org/kxaml/x"
            x:Class="Kuonen.UI.Galerie.Seiten.CodeEditorVorfuehrung"
            Spacing="8">
  <StackPanel Orientation="Horizontal" Spacing="8">
    <ComboBox x:Name="Dialekt" MinWidth="130" />
    <CheckBox x:Name="Nummern" Content="Zeilennummern" />
    <Button x:Name="Pruefen" Content="Prüfen" />
    <TextBlock x:Name="Lage" Text="Zeile 1, Spalte 1" VerticalAlignment="Center" />
  </StackPanel>
  <CodeEditor x:Name="Editor" Height="320" Language="{x:Static SqlSprache.TSql}" />
</StackPanel>
CodeEditorSeite.kxaml.cs
using Kuonen.UI;

namespace Kuonen.UI.Galerie.Seiten;

public sealed partial class CodeEditorSeite
{
    protected override Func<UIElement>? KxamlFabrik => () => new CodeEditorVorfuehrung();
}

/// <summary>KXAML-Fassung der Seite Code-Editor: der Aufbau steht in CodeEditorSeite.kxaml, hier das Verhalten.</summary>
public sealed partial class CodeEditorVorfuehrung
{
    public CodeEditorVorfuehrung()
    {
        InitializeComponent();
        CodeEditorSeite.Verdrahte(Editor, Dialekt, Nummern, Pruefen, Lage);
    }
}

C#

CodeEditorSeite.cs
using Kuonen.UI;
using Kuonen.UI.Controls;
using Kuonen.UI.Layout;

namespace Kuonen.UI.Galerie.Seiten;

/// <summary>Galerie-Seite: der Code-Editor (<see cref="CodeEditor"/>) mit SQL in vier Dialekten.</summary>
public sealed partial class CodeEditorSeite : GalerieSeite
{
    public override string Titel => "Code-Editor";

    public override string Beschreibung =>
        "Ein Editor für Abfragen wie in SSMS: Hervorhebung je Dialekt, Zeilennummern, Faltung (Strg+M Strg+M), Vorschläge (Strg+Leertaste) und "
        + "Parameterhilfe, Suchen und Ersetzen (Strg+F, Strg+H), Gehe zu Zeile (Strg+G), Kommentar (Strg+K Strg+C), Spaltenauswahl (Alt+Ziehen). "
        + "„Prüfen“ setzt Beispieldiagnosen: Wellenlinien mit Kurzinfo, F8 springt zur nächsten.";

    public override string Stichworte =>
        "CodeEditor AvalonEdit Monaco Scintilla SQL T-SQL PostgreSQL MySQL SQLite Syntax Hervorhebung Faltung IntelliSense Vorschläge Suchen Ersetzen Diagnosen Wellenlinie Spaltenauswahl";

    public override string Datei => nameof(CodeEditorSeite);

    /// <summary>Das Beispielskript der Seite.</summary>
    public const string Beispiel = """
        -- Umsatz je Kunde im laufenden Jahr
        SELECT k.Name, SUM(r.Betrag) AS Umsatz
        FORM dbo.Kunden AS k
        JOIN dbo.Rechnungen AS r ON r.KundeId = k.Id
        WHERE r.Datum >= DATEFROMPARTS(YEAR(GETDATE()), 1, 1)
        GROUP BY k.Name
        ORDER BY Umsatz DESC;

        BEGIN
            SELECT * FROM dbo.Kunden WHERE Ort = N'Bern';
        END
        """;

    internal static readonly string[] Dialekte = ["T-SQL", "PostgreSQL", "MySQL", "SQLite"];

    /// <summary>Das Verhalten beider Fassungen: Dialekt wählen, Zeilennummern schalten, Beispieldiagnosen setzen, Caret-Lage melden.</summary>
    internal static void Verdrahte(CodeEditor editor, ComboBox dialekt, CheckBox nummern, Button pruefen, TextBlock lage)
    {
        editor.Text = Beispiel;
        editor.Language = SqlSprache.TSql;
        dialekt.ItemsSource = Dialekte;
        dialekt.SelectedIndex = 0;
        dialekt.SelectionChanged += (_, _) => editor.Language = dialekt.SelectedIndex switch
        {
            1 => SqlSprache.PostgreSql,
            2 => SqlSprache.MySql,
            3 => SqlSprache.Sqlite,
            _ => SqlSprache.TSql,
        };
        nummern.IsChecked = true;
        nummern.Checked += (_, _) => editor.ShowLineNumbers = true;
        nummern.Unchecked += (_, _) => editor.ShowLineNumbers = false;
        pruefen.Click += (_, _) =>
        {
            editor.Diagnostics = Pruefe(editor.Text);
            lage.Text = $"{editor.Diagnostics.Count} Diagnosen. F8: zur nächsten.";
        };
        editor.SelectionChanged += (_, _) => lage.Text = $"Zeile {editor.CaretLine + 1}, Spalte {editor.CaretColumn + 1}";
    }

    // Beispieldiagnosen: „FORM“ statt FROM ist ein Fehler, „SELECT *“ eine Warnung.
    internal static List<CodeDiagnose> Pruefe(string text)
    {
        var liste = new List<CodeDiagnose>();
        for (int i = text.IndexOf("FORM", StringComparison.Ordinal); i >= 0; i = text.IndexOf("FORM", i + 1, StringComparison.Ordinal))
            liste.Add(new CodeDiagnose(i, 4, CodeSchwere.Fehler, "Falsche Syntax bei „FORM“. Gemeint ist wohl FROM."));
        for (int i = text.IndexOf("SELECT *", StringComparison.Ordinal); i >= 0; i = text.IndexOf("SELECT *", i + 1, StringComparison.Ordinal))
            liste.Add(new CodeDiagnose(i + 7, 1, CodeSchwere.Warnung, "Spalten besser ausdrücklich nennen."));
        return liste;
    }

    public override UIElement Baue()
    {
        var editor = new CodeEditor { Height = 320 };
        var dialekt = new ComboBox { MinWidth = 130 };
        var nummern = new CheckBox { Content = "Zeilennummern" };
        var pruefen = new Button { Content = "Prüfen" };
        var lage = new TextBlock { Text = "Zeile 1, Spalte 1", VerticalAlignment = VerticalAlignment.Center };
        Verdrahte(editor, dialekt, nummern, pruefen, lage);
        var leiste = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8, Children = { dialekt, nummern, pruefen, lage } };
        return new StackPanel { Spacing = 8, Children = { leiste, editor } };
    }
}