Zum Inhalt

Zeilenmarkierungen und der Randklick

Eine Zeilenmarkierung ist ein Symbol in der Lesezeichenspalte des Editors und/oder eine Tönung über die ganze Zeile, die an einer Zeile eines Dokuments hängt und beim Bearbeiten mitwandert. Haltepunkte (gefüllt, hohl, bedingt), ein Pfeil für die aktuelle Anweisung und Änderungsstreifen der Versionsverwaltung sind allesamt Zeilenmarkierungen. Den Dienst erreichen Sie über IPluginContext::lineMarkers() (PluginApi/ILineMarkerService.h).

Das Plugin beschreibt, wie eine Art von Markierung aussieht und wo die Markierungen dieser Art sitzen; das Zeichnen, der Rand und die Markierungs-Slots der Bearbeitungs-Engine gehören dem Editor. Keine Pixelgröße, keine Engine-Nummer und kein Qt-Typ überquert die Grenze.

Arten

Registrieren Sie jede Art von Markierung einmal, in initialize(), und behalten Sie das Token:

enum class LineMarkerGlyph : std::int32_t {
    None, Circle, CircleHollow, CircleBar, Arrow, Strip
};

struct LineMarkerKind {
    LineMarkerGlyph glyph = LineMarkerGlyph::Circle;
    std::string     glyphColor;  // a theme category you registered
    std::string     lineTint;    // a theme category; empty = no tint
    std::int32_t    zOrder = 0;  // higher draws above lower
};

m_breakpoint = ctx.lineMarkers().registerKind(kind);   // LineMarkerKindToken
if (!m_breakpoint.valid()) { /* no slot left: see below */ }
  • Farben sind IDs von Theme-Kategorien, die Sie selbst über ctx.themePalette() registriert haben; Markierungen folgen so dem aktiven Theme, und der Benutzer kann sie im Theme-Editor umfärben; der Editor wendet sie bei jedem Theme-Wechsel neu an.
  • Symbole sind ein kleiner, fester Satz, den jeder Host in einem schmalen Rand bei jedem Zoom zeichnen kann: Circle (ein Haltepunkt), CircleHollow (ein deaktivierter oder nicht bestätigter Haltepunkt), CircleBar (ein bedingter Haltepunkt), Arrow (die aktuelle Anweisung), Strip (eine geänderte Zeile). None ergibt eine Art, die nur tönt.
  • zOrder entscheidet, welche Markierung oben gezeichnet wird, wenn sich mehrere eine Zeile teilen (der Pfeil der aktuellen Anweisung über einem Haltepunkt, ein Haltepunkt über einem Änderungsstreifen); bei gleichen Werten liegt die später registrierte Art oben. Die Reihenfolge gilt über Plugins hinweg.
  • Die Registrierung kann fehlschlagen. Die Bearbeitungs-Engine hat eine kleine, feste Anzahl von Markierungs-Slots, die sich alle Plugins teilen — neun; eine Art mit Symbol und Tönung belegt zwei. Sind sie aufgebraucht, kommt das Token leer zurück, und das Log sagt, warum. Registrieren Sie die benötigten Arten einmal und behalten Sie sie; registrieren Sie nie eine Art pro Dokument oder pro Markierung.
  • Das Zerstören des Tokens entfernt alle Markierungen dieser Art aus allen Dokumenten und zieht die Art zurück.

Markierungen

using LineMarkerId = std::uint64_t;                 // 0 is never valid

LineMarkerId id = ctx.lineMarkers().addMarker(m_breakpoint.id(), doc, line);
std::int64_t now = ctx.lineMarkers().lineOf(id);     // -1 = gone
auto all = ctx.lineMarkers().markers(m_breakpoint.id(), doc); // {id, line} by line
ctx.lineMarkers().removeMarker(id);
ctx.lineMarkers().clearMarkers(m_breakpoint.id(), 0);           // 0 = every document

addMarker liefert 0 — und setzt nichts —, wenn die Art nicht lebt, das Dokument nicht geöffnet ist oder die Zeile außerhalb des Dokuments liegt. Eine Zeile wird nicht begrenzt: Ein Haltepunkt in der falschen Zeile ist schlimmer als keiner. Ein Handle ist ein einfacher Wert, kein Token; halten Sie so viele, wie Sie brauchen.

Was danach mit einer Markierung geschieht:

  • Bearbeitungen verschieben sie mit ihrer Zeile: Darüber eingefügte oder gelöschte Zeilen verschieben sie, Tippen in der Zeile behält sie bei — so folgt ein Haltepunkt dem Code.
  • Ihre Zeile wird gelöscht (Teil eines entfernten Bereichs): Sie bleibt erhalten und wandert in die Zeile, in der der gelöschte Bereich begann. Zwei Markierungen können so in einer Zeile landen.
  • Der ganze Puffer wird ersetzt (ein Neuladen nach einer Änderung von außen, IEditorService::setText): Jede Markierung bleibt auf derselben Zeilennummer; eine, deren Zeile nicht mehr existiert, wird entfernt. Der Editor vergleicht alten und neuen Text nicht.
  • Das Dokument wird geschlossen: Seine Markierungen werden entfernt und ihre Handles werden ungültig. Der Editor speichert Markierungen nie — ein Plugin, das Markierungen über ein Schließen hinweg behält (Haltepunkte), hält sie in seinem eigenen Modell und fügt sie bei DocumentOpened erneut hinzu (eine wieder geöffnete Datei ist eine neue DocumentId).

Markierungen gehören zum Dokument: Einmal gesetzt, erscheinen sie in jedem Bereich eines geteilten Dokuments. Die Minimap, ihre Hover-Vorschau und die Vergleichsansicht zeigen sie nie. Lesezeichen sind keine Zeilenmarkierungen dieses Dienstes; sie behalten ihre eigenen IEditorService-Aufrufe.

Der Randklick

Jeder Linksklick in die Lesezeichenspalte eines Editors wird als events::MarginClicked veröffentlicht:

struct MarginClicked {
    DocumentId   doc;
    std::int64_t line;       // 0-based, level with the click
    EditorMargin margin;     // EditorMargin::Symbol
    KeyModifiers modifiers;  // key_modifier::kShift | kControl | kAlt | kMeta
};

Die Namen der Modifikatoren folgen den portablen Kürzel-Strings: kControl ist Strg unter Windows und Linux und Befehl (⌘) unter macOS, kAlt ist Alt / Option (⌥), kMeta ist die Windows-Taste / Control (⌃) unter macOS. Klicks in die Faltspalte falten und Klicks auf die Zeilennummern wählen aus; keines von beiden wird veröffentlicht.

Standardmäßig schaltet der Editor außerdem ein Lesezeichen in der angeklickten Zeile um, wie schon immer. Ein Plugin, das dem Klick eine eigene Bedeutung gibt — ein Debugger, der mit einem einfachen Klick einen Haltepunkt umschaltet und mit einem Klick mit Modifikator dessen Bedingung bearbeitet —, beansprucht ihn:

m_marginClaim = ctx.lineMarkers().claimMarginClick();   // MarginClickToken
m_clicks = ctx.events().subscribe<events::MarginClicked>(
    [this](const events::MarginClicked& e) { onMarginClick(e); });

Solange mindestens ein Anspruch lebt — von welchem Plugin auch immer —, überlässt der Editor den Klick den Plugins und veröffentlicht nur das Event; das Zerstören des letzten Tokens bringt das Umschalten der Lesezeichen zurück. Das Event wird veröffentlicht, ob jemand einen Anspruch hält oder nicht; ein Plugin, das Klicks nur beobachtet, braucht also keinen. Die Lesezeichen-Befehle und ihre Tastenkürzel sind nicht betroffen. Ansprüche werden gezählt, nicht eingereiht: Zwei Plugins, die beide den Klick beanspruchen, erhalten ihn beide und dürfen nicht beide darauf reagieren.

Python

Derselbe Dienst ist ctx.line_markers für Python-Plugins, und der Klick ist @ctx.events.on_margin_clicked.