Skip to content

VisualEditor pt BR

Edgar Mesquita edited this page Aug 14, 2026 · 14 revisions

Editor visual (VS Code)

🌐 Esta página em: English · Português

Uma extensão de primeira parte para o VS Code que transforma o SDK num ambiente de desenvolvimento visual: uma tela renderiza ao lado do arquivo que a produz, a partir do buffer que você está digitando, cada elemento renderizado sabe qual expressão C# o construiu, e selecionar um mostra — e edita — essa expressão.

O preview é o realizador web real rodando o módulo compilado real. Não é um sósia, e é esse o ponto: um preview que renderiza é prova de que a página renderiza. Nada novo desenha pixels.

Estado. O preview, o mecanismo de identidade, a seleção, o inspetor de propriedades e os três gestos estruturais (inserir, mover, remover) estão prontos e cobertos — por testes de unidade contra uma compilação de projeto real, e por uma suíte de integração que roda dentro de um VS Code de verdade. A extensão ainda é construída a partir do código, não instalada pelo Marketplace. O Track E no Roadmap tem os marcos.

O host de design

Desde 0.2.0-preview.30

eqdesign é um processo de vida longa que segura a compilação Roslyn do projeto e compila o buffer do editor — não o arquivo em disco. Essa distinção é a razão inteira de ele existir: o eqc lê arquivos e o serviço de hot reload observa o sistema de arquivos, então nenhum dos dois consegue te mostrar o texto que você está olhando agora, só o que você salvou por último.

Ele fala JSON delimitado por linha sobre stdio. Nenhuma porta para escolher, nada para autorizar, nada sobrando escutando se a janela morrer, e funciona igual sobre Remote-SSH porque o host da extensão roda na máquina remota de qualquer forma.

Medido no samples/DefaultUIDashboard (16 arquivos de código, 316 referências):

Passo Tempo
Montar a compilação, uma vez na ativação 271 ms
Compilar uma edição (PaymentsPage, 662 linhas, quente) p50 293 ms
Só diagnósticos (bind, sem transpilar) 36 ms

Daí as duas cadências: os erros são reconferidos depois de 150 ms de pausa na digitação, o preview recompila depois de 400 ms. Um erro de C# interrompe a compilação antes de qualquer emissão — o Roslyn analisa de forma tolerante, então uma chave faltando ainda produz uma árvore que o transpilador percorreria, gerando um módulo que monta e lança, o que chega na tela como um quadro em branco sem nada que explique.

O host se recusa a iniciar se a lista de referências estiver vazia, em vez de compilar contra um modelo incompleto. Essa falha é invisível de outro jeito: sem o modelo semântico, argumentos nomeados são emitidos na ordem sintática, e a tela renderiza com os valores nos lugares errados e nenhum erro em canto nenhum.

Uma tela raramente é um arquivo só. Um shell, uma linha, um helper de dados — então a extensão manda todo buffer C# que o editor tem aberto, não só o ativo, e o host sobrepõe todos eles na compilação antes de compilar. Do contrário o preview te mostraria a última versão salva de tudo, exceto do arquivo em que você por acaso está digitando.

Modo de design: um nó lembra do C# que o construiu

Desde 0.2.0-preview.30

VisualNode carrega um Origin — o span de código da expressão que o construiu, ou null, que é o que todo build publicado é. Uma compilação em modo de design preenche isso:

var compiler = new ComponentCompiler { DesignMode = true };
// Toda construção de nó é emitida envolvida, então o nó construído lembra de onde veio:
//   $eq.origin(UI.text(…), "…/PaymentsPage.cs|28:12|28:73")

Os dois realizadores levam isso para o DOM como data-eq-origin, anexado no despacho único de cada um:

<div data-eq-origin="/src/Screens/PaymentsPage.cs|27:8|34:10">
  <span data-eq-origin="/src/Screens/PaymentsPage.cs|28:12|28:73">Count: 0</span>
</div>

O formato é caminho|linhaInicial:colunaInicial|linhaFinal:colunaFinal, base zero — as coordenadas do próprio editor.

Por que não source maps. A resposta óbvia não funciona. O corpo inteiro do Build é convertido numa única string achatada e emitido por uma só chamada, então a posição mais fina que um mapa V3 consegue nomear é o início do método: um módulo publicado tem uma linha de 942 caracteres. Um origin é exato — e por ser uma string simples no nó abstrato, o track nativo (Photon) herda o mesmo mecanismo no dia em que quiser.

construções são carimbadas, nunca referências. Carimbar uma variável onde ela é apenas mencionada sobrescreveria o origin da construção com o span de um uso, e selecionaria a linha errada. Um nó construído dentro de um foreach carrega o span da única expressão que o constrói — que é a única coisa editável ali, de qualquer forma.

O modo de design fica desligado por padrão e nunca ligado num build do SDK: o envoltório é código emitido de verdade, e publicá-lo colocaria a preocupação de uma ferramenta de design no bundle de todo mundo. Um render de produção não contém nenhum data-eq-origin, e o teste de fingerprint de SSR/hidratação garante isso.

O que um nó é, antes do que se pode fazer com ele

Desde 0.2.0-preview.30

Um origin diz de onde o nó veio. Ele não diz se aquele lugar pode ser editado, e tratar as duas coisas como uma pergunta só é como um editor visual corrompe um arquivo. Uma linha construída quinhentas vezes dentro de um foreach tem um único ponto no código e nenhuma existência separada; um nó devolvido por um método auxiliar está escrito em outro lugar inteiramente. "Apagar esta linha" está errado para os dois.

Então todo nó selecionado é classificado, subindo os ancestrais do Roslyn a partir da posição do origin:

Nível Onde está escrito O que a tela oferece
literal Incondicionalmente, no Build que está no preview tudo, inclusive edições estruturais
derived Dentro de um laço, um condicional ou um callback selecionar, inspecionar, editar propriedades
foreign Outro membro, uma função local, ou outro arquivo selecionar, inspecionar, e pular para onde está escrito

Uma função local conta como membro próprio aqui, porque um nó construído nela é escrito uma vez e alcançado por uma chamada — exatamente como um método auxiliar.

Toda recusa se explica antes de o controle ser desenhado, nunca depois de ser clicado:

Construído dentro de um laço — cada repetição vem desta mesma expressão, então não existe uma linha única para mover ou apagar.

Construído por PaymentRow(), não pelo Build().

A distinção que importa na prática: entender não é cercado, escrever é. O editor abre código que já existe, e código real se divide em métodos auxiliares e em arquivos. Selecionar, inspecionar e editar uma propriedade funcionam onde quer que o nó tenha sido escrito, inclusive em outro arquivo — e é por isso que o host recebe todos os buffers abertos. A inserção estrutural é o que fica dentro da forma declarativa.

O inspetor

Desde 0.2.0-preview.30

Selecionar um elemento abre um painel encaixado, com a cara do próprio editor, listando o que o componente aceita: o tipo de cada propriedade, o comentário de documentação lido do símbolo de origem, e como ela está escrita agora — como argumento, num inicializador de objeto, ou de jeito nenhum.

As edições voltam como código C#, porque o arquivo é C#. Um enum oferece seus membros numa lista em vez de uma caixa de texto; o resto é um valor que você digita, e 12, "Salvar" e Variant.Secondary são todos só expressões.

Três coisas mantêm isso honesto:

  • É compilado antes de ser oferecido. A edição candidata é analisada e vinculada, e a recusa cita o compilador: That would not compile: CS0117 ….
  • Passa por WorkspaceEdit, nunca por escrita de arquivo. A mudança cai na pilha de undo do próprio documento, então um Ctrl+Z reverte o gesto inteiro e um buffer não salvo continua não salvo.
  • Nunca reescreve a forma em que o arquivo está escrito. Os membros init-only de um componente — o Loading do Button, o Width do FlexNode — só são alcançáveis por um inicializador de objeto, e a superfície de factories existe justamente para ninguém escrever um. Numa chamada de factory a resposta honesta é "daqui não, e eis o porquê", o que é melhor do que virar Button("Salvar") em new Button("Salvar") { … } pelas costas de quem escreveu.

O painel também desce um nível, para dentro de um valor que um argumento carrega — style.Padding, onde style é o argumento e Padding um membro do BoxStyle escrito nele. Sem isso ele ofereceria o gap de um Box e nada do que alguém veio buscar: padding, fundo e raio de canto não estão no nó.

Arrumar: inserir, mover, remover

Desde 0.2.0-preview.30

Os gestos estruturais são cercados à forma declarativa — uma collection expression children: [ … ]:

Column(gap: Space.S4, children: [
    Text("Total", TypeRole.LabelM, context.Theme.TextSecondary),
    Text(total, TypeRole.TitleM, context.Theme.TextPrimary),
])

Essa lista é a única forma em que uma inserção pode ser emendada com segurança: os elementos dela têm spans de verdade, então um novo entra entre dois deles sem tocar em mais nada. Um contêiner construído com instruções (var column = new Column(); column.Add(…);) é fluxo de dados, não lista, e reorganizá-lo é outro problema. O painel pergunta antes e diz isso, em vez de oferecer um controle que recusa.

  • Inserir vem de uma paleta derivada das próprias superfícies de factories, então um componente novo aparece nela sem ninguém manter lista. Isso inclui os componentes do próprio app: o gerador escreve para eles uma superfície AppUI igualzinha à do framework, e eles vêm primeiro, sob um título só deles. Toda entrada é provada compilar onde cai — um teste insere cada uma numa lista real e falha em qualquer uma que não compilasse.
  • Mover troca dois elementos e deixa tudo o que está entre eles exatamente onde está: a vírgula, a quebra de linha, a indentação. Reescrever a lista a partir dos textos dos elementos seria mais simples e descartaria em silêncio um comentário que alguém deixou entre dois filhos. Sobra uma imperfeição, e ela é dita em vez de escondida: um comentário escrito acima de um filho fica com a posição, não com o filho.
  • Remover leva o elemento e exatamente um separador. Só o elemento deixa uma vírgula órfã; os dois deixam um buraco.

Nem mover nem remover são oferecidos onde não significam nada: um nó que não é elemento de uma lista não tem ordem em que ser movido, então os controles ficam escondidos em vez de desabilitados — não há nada a explicar sobre um arranjo que não existe.

Cada gesto é um único WorkspaceEdit, então cada um é um undo.

Pela tela

Os mesmos três gestos estão no ponteiro, que é onde a árvore é legível:

  • Arraste um filho para reordená-lo. Um caret marca o vão em que ele cairia, e o rótulo diz para onde ele vai — Text → 3 of 5. Por mais longe que ele viaje, é uma edição e um undo.
  • Passe o mouse num filho e um + aparece em cada ponta dele — antes deste, depois deste — no eixo do próprio contêiner. Qual é o eixo se lê do resultado renderizado, não do CSS: uma Row é flex, um Grid é grid e um Stack é absoluto, e na tela os três respondem "estes estão lado a lado?" do mesmo jeito. (Com menos de dois filhos não há resultado para ler, e só aí a tela pergunta ao layout.)
  • Passe o mouse no espaço do próprio contêiner, inclusive de um vazio, e o vão mais próximo do ponteiro é marcado com uma linha tracejada e um +. Essa marca é a resposta a onde caberia alguma coisa, uma pergunta que uma tela nunca deveria obrigar ninguém a adivinhar. O tracejado é de propósito: o caret marca um compromisso, o tracejado marca uma possibilidade.

O arrasto não fica preso à lista em que começou: o alvo do drop é recalculado do ponteiro a cada movimento, então o caret entra em qualquer contêiner por cima do qual ele passe. Sair da própria lista é um remover e um inserir, que o host escreve como uma substituição só cobrindo os dois — uma edição, um Ctrl+Z. O texto movido é reindentado à nova profundidade e no resto vai verbatim, e é isso que faz do compilador a cerca: um nó escrito contra um local do método de onde veio não compila na casa nova, e a recusa cita o CS0103 em vez de inventar uma regra sobre o que é portátil.

As recusas são desenhadas antes do gesto, não depois dele. A tela e a lista C# têm de bater filho a filho — um filho que renderiza como vários elementos, ou como nenhum, quebra essa correspondência, e aí um índice calculado a partir de pixels nomearia outro elemento no arquivo, então o controle é retirado em vez de chutado. Um nó não pode cair dentro da própria subárvore, nem num contêiner que não tem lista, nem numa lista de outro arquivo — isso editaria dois documentos de uma vez.

Mover invalida o span que o painel estava segurando, então o host responde com onde o nó caiu e a seleção o segue até lá. Sem isso, perguntar pelas coordenadas antigas descreve o irmão com quem ele acabou de trocar de lugar.

Rodando hoje

A extensão é construída a partir do código e aponta para o host de design no mesmo checkout:

cd extensions/vscode && npm install && npm run compile

Depois abra uma página ou componente C# e rode eQuantic UI: Open Preview. O projeto precisa ter sido construído uma vez — o preview se apoia na saída de um build comum (a lista de referências que o SDK escreve, e o wwwroot/_equantic/runtime.js), então "construa o projeto uma vez" é a instrução honesta quando falta alguma peça.

Os controles do preview ficam na barra de título do editor, contribuídos como comandos comuns, então herdam o tema e o conjunto de ícones como qualquer outra ação do editor: o ponteiro liga e desliga a inspeção, e um reinício está ali para quando o host precisar voltar.

O que ainda não faz

  • Não arrasta entre arquivos. Um nó pode ser arrastado para qualquer lista do arquivo em edição, mas um contêiner escrito em outro é recusado: essa edição tocaria dois documentos de uma vez.
  • Grid e Stack precisam de gesto próprio. As colunas de um Grid são trilhas cuja adição reflui todos os filhos, e a ordem da lista de um Stack é a ordem de pintura — nenhum dos dois é o gesto de "inserir um irmão" que os outros contêineres compartilham.
  • Sem preview nativo. A identidade já está resolvida no Photon (LayoutNode carrega o nó, os limites absolutos e um caminho estável); falta só levar os quadros para dentro de um webview.
  • Sem chamadas a [ServerAction]. Os ids das ações vêm de uma varredura de assembly na inicialização, então uma classe de preview improvisada nunca está no registro.
  • O tema baseline. Um app escolhe o tema dele na inicialização, e ler isso de volta significa rodar a raiz de composição do app. Formas e layout são exatos; uma paleta rebrandeada ainda não aparece.

Veja também

  • Depuração — o logger e o overlay de erro que o preview reaproveita.
  • Compilador — como o C# vira o módulo que o preview roda.
  • Roadmap — o Track E e o que vem antes dele.

Clone this wiki locally