Saltar a contenido

Marcas de línea y clic en el margen

Una marca de línea es un glifo en la columna de marcadores del editor y/o un tinte sobre toda la línea, unido a una línea de un documento y que se desplaza con ella mientras el usuario edita. Los puntos de interrupción (rellenos, huecos, condicionales), una flecha de instrucción actual y las franjas de cambios del control de versiones son todos marcas de línea. Accede al servicio mediante IPluginContext::lineMarkers() (PluginApi/ILineMarkerService.h).

El plugin describe cómo es un tipo de marca y dónde están las marcas de ese tipo; el dibujo, el margen y las ranuras de marcas del motor de edición son del editor. Ningún tamaño en píxeles, número del motor ni tipo de Qt cruza la frontera.

Tipos

Registra cada tipo de marca una sola vez, en initialize(), y conserva el 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 */ }
  • Los colores son identificadores de categorías del tema que registraste tú mismo con ctx.themePalette(), así que las marcas siguen al tema activo y el usuario puede cambiarles el color en el Editor de temas; el editor los vuelve a aplicar en cada cambio de tema.
  • Los glifos son un conjunto pequeño y fijo que cualquier anfitrión puede dibujar en un margen estrecho con cualquier zoom: Circle (un punto de interrupción), CircleHollow (un punto de interrupción desactivado o sin verificar), CircleBar (un punto de interrupción condicional), Arrow (la instrucción actual), Strip (una línea cambiada). None crea un tipo que solo tiñe.
  • zOrder decide qué marca se dibuja encima cuando varias comparten una línea (la flecha de la instrucción actual sobre un punto de interrupción, un punto de interrupción sobre una franja de cambios); con valores iguales queda encima el tipo registrado más tarde. El orden se mantiene entre plugins.
  • El registro puede fallar. El motor de edición tiene un número pequeño y fijo de ranuras de marcas compartidas por todos los plugins: nueve; un tipo con glifo y tinte usa dos. Cuando se agotan, el token vuelve vacío y el registro explica por qué. Registra una sola vez los tipos que necesitas y consérvalos; nunca registres un tipo por documento o por marca.
  • Destruir el token quita todas las marcas de ese tipo de todos los documentos y retira el tipo.

Marcas

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 devuelve 0, y no coloca nada, cuando el tipo no está vivo, el documento no está abierto o la línea está fuera del documento. La línea no se ajusta: un punto de interrupción en la línea equivocada es peor que ninguno. Un identificador es un simple valor, no un token; guarda tantos como necesites.

Qué le pasa después a una marca:

  • Las ediciones la mueven con su línea: las líneas insertadas o eliminadas por encima la desplazan, escribir en la línea la mantiene; así es como un punto de interrupción sigue al código.
  • Se elimina su línea (forma parte de un rango eliminado): sobrevive y se une a la línea donde empezaba el rango eliminado. Dos marcas pueden acabar en una misma línea.
  • Se sustituye todo el búfer (una recarga tras un cambio externo, IEditorService::setText): cada marca se queda en el mismo número de línea; la que se queda sin línea se elimina. El editor no compara el texto antiguo con el nuevo.
  • Se cierra el documento: sus marcas se eliminan y sus identificadores dejan de valer. El editor nunca guarda las marcas: un plugin que conserva marcas tras un cierre (puntos de interrupción) las mantiene en su propio modelo y las vuelve a añadir en DocumentOpened (un archivo reabierto es un DocumentId nuevo).

Las marcas pertenecen al documento: colocadas una vez, se muestran en cada panel de un documento dividido. El minimapa, su vista previa al pasar el puntero y la vista de comparación nunca las muestran. Los marcadores no son marcas de este servicio; conservan sus propias llamadas de IEditorService.

El clic en el margen

Cada clic izquierdo en la columna de marcadores de un editor se publica como 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
};

Los nombres de los modificadores siguen las cadenas portátiles de los atajos: kControl es Ctrl en Windows y Linux y Comando (⌘) en macOS, kAlt es Alt / Opción (⌥), kMeta es la tecla Windows / Control (⌃) en macOS. Los clics en la columna de plegado pliegan y los clics en los números de línea seleccionan; ninguno de ellos se publica.

De forma predeterminada, el editor además alterna un marcador en la línea en la que se hizo clic, como siempre. Un plugin que da al clic un significado propio —un depurador que alterna un punto de interrupción con un clic simple y edita su condición con un clic con modificador— lo reclama:

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

Mientras haya al menos una reclamación viva, de cualquier plugin, el editor deja el clic a los plugins y solo publica el evento; destruir el último token devuelve el alternado de marcadores. El evento se publica tanto si alguien tiene una reclamación como si no, así que un plugin que solo observa los clics no necesita ninguna. Los comandos de los marcadores y sus atajos no se ven afectados. Las reclamaciones se cuentan, no se ponen en cola: dos plugins que reclaman el clic lo reciben ambos y no deben reaccionar ambos a él.

Python

El mismo servicio es ctx.line_markers para los plugins Python, y el clic es @ctx.events.on_margin_clicked.