Skip to content

VisualEditor pt BR

Edgar Mesquita edited this page Aug 17, 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 gestos estruturais (inserir, mover, duplicar, remover e arrastar entre contêineres) 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. Ela empacota num .vsix instalável; ainda não está no 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. Numa chamada de factory a resposta honesta é "daqui não, e eis o porquê": a superfície de factories existe justamente para ninguém escrever new Button("Salvar") { … }, e virar uma na outra pelas costas de quem escreveu não é uma edição que alguém pediu. Já uma construção escrita com new escolheu essa forma, então as chaves dela são simplesmente acrescentadas quando algo precisa entrar.

Um conjunto fechado é oferecido como lista, e o tipo não é a única coisa que decide o que é fechado. Metade dos tokens do framework não são enums: Space é uma classe estática de const float, então um float gap se lê como número. O que está escrito ali diz outra coisa — Space.S3 nomeia um membro de uma classe estática, e todo irmão do mesmo tipo cabe naquele lugar, então a linha abre como a escala de onde veio. Um gap: 7 puro continua número, porque ali nada nomeia uma escala.

Um valor não é uma string. Um BoxStyle chegaria como uma linha comprida de C# numa célula só — a coisa que um autor mais quer mudar, no único lugar em que não consegue mudá-la. Um valor escrito como new T { … } reporta os membros dele como propriedades comuns, e o painel os desenha como uma planilha pequena embaixo da linha que os carrega:

style      BoxStyle    2 of 9 set
  │ Padding      EdgeInsets    EdgeInsets.All(Space.S4)   ×
  │ Background   ColorToken    context.Theme.Surface      ×
  │ [ BorderWidth ▾ ]  float                              + set

Chaves à esquerda, valores no input do próprio editor, e uma última linha cuja chave é um select de tudo o que ainda não está definido. Tirar um membro de volta é o × da linha: uma planilha em que só dá para acrescentar linhas vaza. Tudo aqui é a mesma máquina de propriedades um nível abaixo — as mesmas listas de enum, o mesmo portão de compilação, a mesma recusa quando o valor está escrito sem um inicializador em que pôr o membro.

Toda lista, não só children

Os columns de um Grid são uma collection expression com spans de verdade, igualzinho a um children — e os items de um menu e as actions de um diálogo também. Todos ganham o que os children ganharam: o painel os lista, e inserir ou remover recebe o nome da lista.

O que muda é o que vai dentro deles. Um GridTrack é dado: nunca renderiza, então nada na tela carrega o span dele e não há o que clicar. Essas listas são endereçadas por posição a partir do painel, e a paleta delas são as formas do próprio tipo de ser escrito — GridTrack.Flex(), GridTrack.Auto e new(…) com tipo pelo alvo, já que o framework não dá factory a value record de propósito. As duas coisas são lidas da assinatura, não escritas como regra sobre grids.

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.

  • Duplicar copia o texto do nó e põe a cópia logo depois dele — e é a cópia que fica selecionada, porque é a coisa que você acabou de fazer.

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

Pela lista

A tela inteira também é uma árvore, no Explorer ao lado do Outline. Cada linha diz onde ela está escrita — PaymentsPage.cs:28 — que é a única coisa que uma lista diz e uma imagem não, e alcança o que a tela não alcança: um contêiner preenchido de ponta a ponta pelos próprios filhos não tem um pixel seu.

Escolher uma linha seleciona na tela, pelo mesmo caminho que um clique faz, então o contorno, o painel e o cursor do editor seguem juntos. Selecionar na tela marca a linha.

Pelo teclado

Todo gesto acima também é uma tecla, e são o mesmo gesto e não uma segunda implementação dele:

Setas caminham na árvore — para cima o pai, para baixo o primeiro filho, esquerda e direita os irmãos
Alt+Setas movem o nó em vez da seleção
Delete remove
Cmd/Ctrl+D duplica
Esc solta — primeiro a seleção, depois o modo de inspeção

Elas respeitam as mesmas guardas que o ponteiro: nada estrutural enquanto a árvore está assentando, e nada de nada enquanto o ponteiro está sobre o painel, onde essas teclas são alguém digitando.

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.

Uma pilha não é uma linha. Os filhos de um Stack se sobrepõem, e a ordem de pintura é a ordem dos filhos — então a lista dele significa algo e a geometria dele não. A tela não tem como saber (dois filhos sobrepostos são idênticos a dois que ninguém posicionou), então o host diz quais listas são em camadas. Ali a marca do drop vira uma cobertura sobre o filho na frente do qual o nó vai parar, o rótulo diz in front of Card em vez de 3 of 5, e as setas do painel mantêm o gesto e largam o nome errado dele: Send backward e Bring forward.

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.

Projeto nativo também tem preview

O preview não se importa com qual SDK o projeto embarca. Um projeto nativo (Photon) — que não tem wwwroot e nunca roda o eqc — tem preview do mesmo jeito: o SDK nativo escreve a lista de referências para as ferramentas, a extensão carrega o runtime de navegador que o projeto legitimamente não tem, e a mesma árvore abstrata de componentes renderiza pelo realizador web. A toolbar diz exatamente isso — native project · web realizer — e abre na casca de telefone, porque essa é a forma em que o app é entregue. Um padrão, não uma jaula: tocou no seletor de formato, o projeto para de dar opinião.

O estado sobrevive à recompilação

Um contador clicado até 7 continua em 7 na próxima tecla. Antes de cada remontagem os campos da própria página são capturados — separados dos do framework sondando a própria classe-base da página, não por uma lista escrita à mão — e devolvidos pela mesma porta __INITIAL_STATE__ que a entrega do SSR usa. Protegido pelo nome da classe: uma página renomeada começa do zero em vez de herdar os campos de uma estranha.

Um quadro Photon, do próprio engine

O botão de cubo na toolbar renderiza o buffer atual pelo engine nativo e mostra o quadro — glifos reais, tema real, no formato que o seletor escolheu. Esses são os pixels contra os quais os backends de GPU estão pinados: o quadro vem do rasterizador Reference, a fonte normativa de pixels do engine, e um teste de paridade permanente prova que o Metal desenha a mesma display list nos mesmos pixels.

Ele roda num processo filho descartável, porque o Build() de uma página é código do autor: um que nunca retorna é morto em dez segundos e volta como uma frase, com o preview vivo atrás. Serviços que o construtor da página pede são satisfeitos como o container de DI faria, menos o container — uma implementação do próprio assembly do app quando existe, um stub benigno caso contrário — e uma página que ainda assim lança renderiza a superfície de diagnóstico do próprio framework, nomeando o componente e a exceção.

Um still, de propósito — mas clicável. Antes de o quadro ser emitido, toda expressão que constrói um nó é reescrita para carimbar o span de C# que o construiu (um passe C#-para-C# sobre a compilação de design, nunca sobre nada que um usuário publica), e o renderizador devolve um mapa de hit junto do PNG: o origin de cada nó e seus bounds calculados, em ordem de pintura. Um clique na imagem encontra o componente mais ao topo sob o cursor — a última entrada contendo o ponto — marca-o no quadro, abre o inspetor e revela a linha exata. É a mesma string de origin com que o canvas web seleciona, então a imagem e a página viva respondem a um clique de forma idêntica; helper methods são carimbados no call site, e o span da própria construção sempre vence a chamada que apenas a retornou.

Interações além da seleção ficam no preview vivo, e uma recompilação dispensa o quadro da página que ela substituiu.

Rodando hoje

Empacote uma vez e instale o .vsix:

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

Isso compila a extensão, publica o host de design dentro dela e escreve o equantic-ui.vsix (10,7 MB) — o host viaja junto, então instalar não exige checkout nenhum. Ele é dependente do framework: um publish self-contained seria ~70 MB por plataforma, e três deles, e a única dependência que isto assume é o runtime .NET contra o qual você já está construindo.

Trabalhando NA extensão, o npm run compile basta — em desenvolvimento o build do host que está no repositório ganha do empacotado, então um .vsix de ontem não responde caladamente pelo código de hoje.

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