Vai al contenuto

Marcatori di riga e clic sul margine

Un marcatore di riga è un glifo nella colonna dei segnalibri dell'editor e/o una tinta sull'intera riga, agganciato a una riga di un documento e portato con essa mentre l'utente modifica il testo. I punti di interruzione (pieni, vuoti, condizionali), una freccia dell'istruzione corrente e le strisce delle modifiche del controllo di versione sono tutti marcatori di riga. Si raggiunge il servizio tramite IPluginContext::lineMarkers() (PluginApi/ILineMarkerService.h).

Il plugin descrive come appare un tipo di marcatore e dove si trovano i marcatori di quel tipo; il disegno, il margine e i posti per i marcatori del motore di modifica appartengono all'editor. Nessuna dimensione in pixel, nessun numero del motore e nessun tipo Qt attraversa il confine.

Tipi

Registra ogni tipo di marcatore una sola volta, in initialize(), e conserva il 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 */ }
  • I colori sono id di categorie del tema che hai registrato tu stesso con ctx.themePalette(), così i marcatori seguono il tema attivo e l'utente può ricolorarli nell'Editor dei temi; l'editor li riapplica a ogni cambio di tema.
  • I glifi sono un piccolo insieme fisso che qualsiasi host sa disegnare in un margine stretto a qualsiasi zoom: Circle (un punto di interruzione), CircleHollow (un punto di interruzione disattivato o non verificato), CircleBar (un punto di interruzione condizionale), Arrow (l'istruzione corrente), Strip (una riga modificata). None crea un tipo che applica solo la tinta.
  • zOrder decide quale marcatore viene disegnato sopra quando più marcatori condividono una riga (la freccia dell'istruzione corrente sopra un punto di interruzione, un punto di interruzione sopra una striscia delle modifiche); a parità di valore sta sopra il tipo registrato per ultimo. L'ordine vale anche tra plugin diversi.
  • La registrazione può fallire. Il motore di modifica ha un numero piccolo e fisso di posti per i marcatori condivisi da tutti i plugin: nove; un tipo con glifo e tinta ne usa due. Quando finiscono, il token torna vuoto e il log dice perché. Registra una volta sola i tipi che ti servono e conservali; non registrare mai un tipo per documento o per marcatore.
  • Distruggere il token rimuove tutti i marcatori di quel tipo da tutti i documenti e ritira il tipo.

Marcatori

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 restituisce 0, e non posiziona nulla, quando il tipo non è vivo, il documento non è aperto o la riga è fuori dal documento. Una riga non viene limitata: un punto di interruzione sulla riga sbagliata è peggio di nessuno. Un handle è un semplice valore, non un token; tienine quanti te ne servono.

Che cosa succede poi a un marcatore:

  • Le modifiche lo spostano con la sua riga: le righe inserite o eliminate sopra lo spostano, scrivere sulla riga lo lascia dov'è; è così che un punto di interruzione segue il codice.
  • La sua riga viene eliminata (fa parte di un intervallo rimosso): sopravvive e si unisce alla riga in cui iniziava l'intervallo eliminato. Due marcatori possono così finire sulla stessa riga.
  • L'intero buffer viene sostituito (un ricaricamento dopo una modifica esterna, IEditorService::setText): ogni marcatore resta sullo stesso numero di riga; quello la cui riga non esiste più viene rimosso. L'editor non confronta il testo vecchio con quello nuovo.
  • Il documento viene chiuso: i suoi marcatori vengono rimossi e i loro handle non valgono più. L'editor non salva mai i marcatori: un plugin che conserva dei segni oltre una chiusura (punti di interruzione) li tiene nel proprio modello e li aggiunge di nuovo su DocumentOpened (un file riaperto è un nuovo DocumentId).

I marcatori appartengono al documento: posizionati una volta, compaiono in ogni riquadro di un documento diviso. La minimappa, la sua anteprima al passaggio del mouse e la vista di confronto non li mostrano mai. I segnalibri non sono marcatori di questo servizio; mantengono le proprie chiamate di IEditorService.

Il clic sul margine

Ogni clic sinistro nella colonna dei segnalibri di un editor viene pubblicato come events::MarginClicked:

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
};

I nomi dei modificatori seguono le stringhe portabili delle scorciatoie: kControl è Ctrl su Windows e Linux e Comando (⌘) su macOS, kAlt è Alt / Opzione (⌥), kMeta è il tasto Windows / Control (⌃) su macOS. I clic nella colonna di piegatura piegano e i clic sui numeri di riga selezionano; nessuno dei due viene pubblicato.

Per impostazione predefinita l'editor attiva o disattiva anche un segnalibro sulla riga cliccata, come ha sempre fatto. Un plugin che dà al clic un significato proprio, come un debugger che attiva o disattiva un punto di interruzione con un clic semplice e ne modifica la condizione con un clic con modificatore, lo rivendica:

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

Finché è viva almeno una rivendicazione, di qualsiasi plugin, l'editor lascia il clic ai plugin e si limita a pubblicare l'evento; distruggere l'ultimo token ripristina l'attivazione dei segnalibri. L'evento viene pubblicato che qualcuno detenga una rivendicazione o no, quindi un plugin che si limita a osservare i clic non ne ha bisogno. I comandi dei segnalibri e le loro scorciatoie non sono interessati. Le rivendicazioni vengono contate, non messe in coda: due plugin che rivendicano entrambi il clic lo ricevono entrambi e non devono reagire entrambi.

Python

Lo stesso servizio è ctx.line_markers per i plugin Python, e il clic è @ctx.events.on_margin_clicked.