-
Notifications
You must be signed in to change notification settings - Fork 1
VisualEditor pt BR
🌐 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.
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.
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.
Só 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.
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 peloBuild().
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.
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
LoadingdoButton, oWidthdoFlexNode— 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 virarButton("Salvar")emnew 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ó.
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
AppUIigualzinha à 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.
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: umaRowé flex, umGridé grid e umStacké 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.
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 compileDepois 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.
- 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.
-
GrideStackprecisam 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 (
LayoutNodecarrega 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.
- 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.
🌐 English · Português
🏁 Comece aqui
📱 Write-once
- Componentes write-once
- Superfície declarativa
- Motor Photon
- Design System
- Capacidades
- Armazenamento
- Formulários
- Editor de código
- Markdown
- Mermaid
- Renderização de Email
🏗️ Arquitetura
⚙️ Compilação
- Compilador
- Avaliação em tempo de compilação
- Recursos C# suportados
- Resolução de tipos externos
- Fluxo de build
- Diagnósticos
⚡ Runtime
🔌 Servidor
🎨 Ecossistema
🚀 Desenvolvimento