Pular para conteúdo

Marcas de linha e clique na margem

Uma marca de linha é um glifo na coluna de marcadores do editor e/ou um tom sobre a linha inteira, preso a uma linha de um documento e levado junto com ela enquanto o usuário edita. Pontos de interrupção (cheios, vazados, condicionais), uma seta da instrução atual e as faixas de alterações do controle de versão são todos marcas de linha. Acesse o serviço por IPluginContext::lineMarkers() (PluginApi/ILineMarkerService.h).

O plugin descreve como é um tipo de marca e onde ficam as marcas desse tipo; o desenho, a margem e os espaços de marcas do mecanismo de edição pertencem ao editor. Nenhum tamanho em pixels, número do mecanismo ou tipo do Qt atravessa a fronteira.

Tipos

Registre cada tipo de marca uma única vez, em initialize(), e guarde o 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 */ }
  • As cores são ids de categorias de tema que você mesmo registrou com ctx.themePalette(), então as marcas acompanham o tema ativo e o usuário pode mudar a cor delas no Editor de temas; o editor as reaplica a cada troca de tema.
  • Os glifos são um conjunto pequeno e fixo que qualquer host consegue desenhar numa margem estreita com qualquer zoom: Circle (um ponto de interrupção), CircleHollow (um ponto de interrupção desativado ou não verificado), CircleBar (um ponto de interrupção condicional), Arrow (a instrução atual), Strip (uma linha alterada). None cria um tipo que só aplica o tom.
  • zOrder decide qual marca é desenhada por cima quando várias dividem uma linha (a seta da instrução atual sobre um ponto de interrupção, um ponto de interrupção sobre uma faixa de alterações); com valores iguais, o tipo registrado depois fica por cima. A ordem vale também entre plugins.
  • O registro pode falhar. O mecanismo de edição tem um número pequeno e fixo de espaços de marcas compartilhados por todos os plugins — nove; um tipo com glifo e tom usa dois. Quando eles acabam, o token volta vazio e o log diz por quê. Registre uma vez os tipos de que precisa e guarde-os; nunca registre um tipo por documento ou por marca.
  • Destruir o token remove todas as marcas desse tipo de todos os documentos e aposenta o 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 devolve 0 — e não posiciona nada — quando o tipo não está vivo, o documento não está aberto ou a linha está fora do documento. A linha não é limitada: um ponto de interrupção na linha errada é pior do que nenhum. Um identificador é um valor simples, não um token; guarde quantos precisar.

O que acontece depois com uma marca:

  • As edições a movem junto com a linha: linhas inseridas ou excluídas acima a deslocam, digitar na linha a mantém — é assim que um ponto de interrupção acompanha o código.
  • A linha dela é excluída (faz parte de um intervalo removido): ela sobrevive e passa para a linha onde começava o intervalo excluído. Duas marcas podem acabar na mesma linha.
  • O buffer inteiro é substituído (um recarregamento após uma mudança externa, IEditorService::setText): cada marca fica no mesmo número de linha; a que ficar sem linha é removida. O editor não compara o texto antigo com o novo.
  • O documento é fechado: as marcas dele são removidas e os identificadores deixam de valer. O editor nunca guarda marcas — um plugin que mantém marcas depois de um fechamento (pontos de interrupção) as guarda no próprio modelo e as acrescenta de novo em DocumentOpened (um arquivo reaberto é um DocumentId novo).

As marcas pertencem ao documento: posicionadas uma vez, aparecem em todos os painéis de um documento dividido. O minimapa, a pré-visualização dele ao passar o mouse e a visão de comparação nunca as mostram. Os marcadores não são marcas deste serviço; eles mantêm as próprias chamadas de IEditorService.

O clique na margem

Todo clique esquerdo na coluna de marcadores de um editor é publicado 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
};

Os nomes dos modificadores seguem as cadeias portáteis de atalhos: kControl é Ctrl no Windows e no Linux e Command (⌘) no macOS, kAlt é Alt / Option (⌥), kMeta é a tecla Windows / Control (⌃) no macOS. Cliques na coluna de dobra dobram e cliques nos números de linha selecionam; nenhum deles é publicado.

Por padrão, o editor também alterna um marcador na linha clicada, como sempre fez. Um plugin que dá ao clique um significado próprio — um depurador que alterna um ponto de interrupção com um clique simples e edita a condição dele com um clique com modificador — o reivindica:

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

Enquanto houver pelo menos uma reivindicação viva — de qualquer plugin —, o editor deixa o clique para os plugins e só publica o evento; destruir o último token traz de volta a alternância de marcadores. O evento é publicado haja ou não uma reivindicação, então um plugin que só observa cliques não precisa de nenhuma. Os comandos de marcadores e os atalhos deles não são afetados. As reivindicações são contadas, não enfileiradas: dois plugins que reivindicam o clique o recebem ambos e não devem ambos reagir a ele.

Python

O mesmo serviço é ctx.line_markers para plugins Python, e o clique é @ctx.events.on_margin_clicked.