Skip to content

VisualEditor es

Edgar Mesquita edited this page Oct 14, 2026 · 1 revision

Editor visual (VS Code)

🌐 Esta página en: English · Português · Español · Français · Deutsch

Una extensión de VS Code de primera mano que convierte el SDK en un entorno de desarrollo visual: una pantalla se renderiza al lado del archivo que la produce, desde el búfer en el que estás escribiendo, cada elemento renderizado sabe qué expresión de C# lo construyó, y seleccionar uno muestra, y edita, esa expresión.

La vista previa es el realizador web real ejecutando el módulo compilado real. No es un parecido, y ahí está el punto: una vista previa que se renderiza es evidencia de que la página se renderiza. Nada nuevo dibuja píxeles.

Estado. La vista previa, el mecanismo de identidad, la selección, el inspector de propiedades y los gestos estructurales (insertar, mover, duplicar, quitar y arrastrar entre contenedores) están en su lugar y cubiertos, por pruebas unitarias contra una compilación de proyecto real, y por una suite de integración que corre dentro de un VS Code real. Se empaqueta en un .vsix instalable; todavía no está en el Marketplace. La pista E de la Hoja de ruta tiene los hitos.

El host de diseño

Desde 0.2.0-preview.30

eqdesign es un proceso de larga vida que tiene la compilación de Roslyn del proyecto y compila el búfer del editor, no el archivo en disco. Esa distinción es toda la razón de que exista: eqc lee archivos y el servicio de recarga en caliente vigila el sistema de archivos, así que ninguno puede mostrarte el texto que estás mirando ahora, solo el texto que guardaste la última vez.

Habla JSON delimitado por saltos de línea sobre stdio. Ningún puerto que elegir, nada que autorizar, nada escuchando si la ventana muere, y funciona sin cambios sobre Remote-SSH porque el host de la extensión corre en la máquina remota de todos modos.

Medido en samples/DefaultUIDashboard (16 archivos fuente, 316 referencias):

Paso Tiempo
Construir la compilación, una vez al activar 271 ms
Compilar una edición (PaymentsPage de 662 líneas, en caliente) p50 293 ms
Solo diagnósticos (enlazar, sin transpilar) 36 ms

De ahí dos cadencias: los errores se revisan de nuevo tras una pausa de 150 ms al escribir, la vista previa recompila tras 400 ms. Un error de C# detiene la compilación antes de que se emita nada, Roslyn parsea con indulgencia, así que una llave que falta todavía produce un árbol que el transpilador recorrería, produciendo un módulo que se monta y lanza, que llega a la pantalla como un fotograma en blanco sin nada que lo explique.

El host se niega a arrancar si la lista de referencias está vacía, en vez de compilar contra un modelo incompleto. Ese fallo es invisible de otro modo: sin el modelo semántico, los argumentos nombrados se emiten en orden sintáctico, y la pantalla se renderiza con sus valores en las ranuras equivocadas y ningún error en ninguna parte.

Una pantalla rara vez es un archivo. Un shell, una fila, un ayudante de datos, así que la extensión envía todo búfer de C# que el editor tiene ahora, no solo el activo, y el host los superpone todos sobre la compilación antes de compilar. De lo contrario la vista previa te mostraría la última versión guardada de todo excepto del archivo en el que resulta que estás escribiendo.

Modo diseño: un nodo recuerda el C# que lo construyó

Desde 0.2.0-preview.30

VisualNode lleva un Origin, el tramo de fuente de la expresión que lo construyó, o null, que es lo que es toda compilación entregada. Una compilación en modo diseño lo rellena:

var compiler = new ComponentCompiler { DesignMode = true };
// Cada construcción de nodo se emite envuelta, para que el nodo construido recuerde de dónde vino:
//   $eq.origin(UI.text(…), "…/PaymentsPage.cs|28:12|28:73")

Los dos realizadores lo llevan al DOM como data-eq-origin, adjunto en su único despacho:

<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>

El formato es path|startLine:startColumn|endLine:endColumn, con base cero, las coordenadas propias del editor.

Por qué no mapas de fuente. La respuesta obvia no funciona. Todo el cuerpo de Build se convierte en una sola cadena plana y se emite por una sola llamada, así que la posición más fina que un mapa V3 puede nombrar es el inicio del método: un módulo entregado tiene una línea de 942 caracteres. Un origen es exacto en cambio, y como es una cadena simple en el nodo abstracto, la pista nativa (Photon) hereda el mismo mecanismo el día que lo quiera.

Solo las construcciones se sellan, nunca las referencias. Sellar una variable donde solo se menciona sobrescribiría el origen de la construcción con el tramo de un uso, y seleccionaría la línea equivocada. Un nodo construido dentro de un foreach lleva el tramo de la única expresión que lo construye, que de todos modos es lo único editable allí.

El modo diseño está apagado por defecto y nunca encendido en una compilación del SDK: el envoltorio es código emitido de verdad, y entregarlo pondría la preocupación de una herramienta de diseño en el bundle de cada usuario. Un render de producción no contiene ningún data-eq-origin, y la prueba de huella de SSR e hidratación lo fija.

Qué es un nodo, antes de qué se le puede hacer

Desde 0.2.0-preview.30

Un origen dice de dónde vino un nodo. No dice si ese lugar puede editarse, y tratar las dos como una sola pregunta es cómo un editor visual corrompe un archivo. Una fila construida quinientas veces dentro de un foreach tiene un sitio de fuente y ninguna existencia separada; un nodo devuelto por un método ayudante está escrito en otra parte por completo. «Borra esta fila» está mal para los dos.

Así que todo nodo seleccionado se clasifica, recorriendo los ancestros de Roslyn desde la posición del origen:

Nivel Dónde está escrito Qué ofrece el lienzo
literal Sin condición, en el Build del que se hace la vista previa todo, incluidas las ediciones estructurales
derived Dentro de un bucle, un condicional o un callback seleccionar, inspeccionar, editar propiedades
foreign Otro miembro, una función local, u otro archivo seleccionar, inspeccionar, y saltar a donde está escrito

Una función local cuenta aquí como su propio miembro, porque un nodo construido allí está escrito una vez y se alcanza por una llamada, exactamente como un método ayudante.

Todo rechazo se explica antes de que se dibuje la posibilidad, nunca después de que se pulse:

Construido dentro de un bucle: toda repetición viene de esta única expresión, así que no hay una sola fila que mover o borrar.

Construido por PaymentRow(), no por Build().

La distinción que importa en la práctica: el entendimiento no está cercado, solo la escritura. El editor abre código que ya existe, y el código real se reparte por métodos ayudantes y archivos. Seleccionar, inspeccionar y editar una propiedad funcionan donde sea que el nodo se haya escrito, incluso en otro archivo, que es por lo que al host se le envía todo búfer abierto. La inserción estructural es lo que se queda dentro de la forma declarativa.

El inspector

Desde 0.2.0-preview.30

Seleccionar un elemento abre un panel acoplado, en el cromo propio del editor, listando lo que el componente toma: el tipo de cada propiedad, el comentario de documentación leído del símbolo de fuente, y cómo está escrita ahora, como argumento, en un inicializador de objeto, o en absoluto.

Las ediciones se escriben de vuelta como fuente de C#, porque el archivo es C#. Un enum ofrece sus miembros como una lista en vez de un cuadro de texto; todo lo demás es un valor que escribes, y 12, "Save" y Variant.Secondary son todos simplemente expresiones.

Tres cosas mantienen eso honesto:

  • Se compila antes de ofrecerse. Una edición candidata se parsea y se enlaza, y un rechazo cita al compilador: Eso no compilaría: CS0117 ….
  • Pasa por WorkspaceEdit, nunca por una escritura de archivo. El cambio aterriza en la pila de deshacer propia del documento, así que un solo Ctrl+Z revierte el gesto entero y un búfer sin guardar sigue sin guardar.
  • Nunca reescribe la forma en la que el archivo está escrito. Los miembros init-only de un componente, el Loading de Button, el Width de FlexNode, se alcanzan solo por un inicializador de objeto. En una llamada de fábrica la respuesta honesta es «no desde aquí, y aquí está por qué»: la superficie de fábrica existe precisamente para que nadie escriba new Button("Save") { … }, y convertir una en la otra a espaldas del autor no es una edición que haya pedido. Una construcción ya escrita con new ha elegido esa forma, así que sus llaves simplemente se añaden cuando algo tiene que ir en ellas.

Un conjunto cerrado se ofrece como lista, y el tipo no es lo único que decide qué está cerrado. La mitad de los tokens del framework no son enums: Space es una clase estática de const float, así que un float gap se lee como un número. Lo que está escrito ahí dice otra cosa: Space.S3 nombra un miembro de una clase estática, y todo hermano del mismo tipo pertenece a esa ranura, así que la fila se abre como la escala de la que vino. Un gap: 7 simple sigue siendo un número, porque nada allí nombra una escala.

Un valor no es una cadena. Un BoxStyle llegaría de otro modo como una larga línea de C# en una sola celda, lo que un autor más quiere cambiar, en el único lugar donde no puede cambiarlo. Un valor escrito como new T { … } reporta sus miembros como propiedades ordinarias, y el panel los renderiza como una pequeña hoja bajo la fila que los lleva:

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

Claves a la izquierda, valores en la entrada propia del editor, y una última fila cuya clave es un select de todo lo que no está puesto todavía. Sacar un miembro de vuelta es la × de una fila: una hoja a la que solo puedes añadir filas tiene fugas. Todo aquí es la misma maquinaria de propiedades un nivel más abajo, las mismas listas de enum, la misma guarda de compilación, el mismo rechazo cuando el valor está escrito sin un inicializador en el que ponerlo.

Toda lista, no solo los hijos

El columns de un Grid es una expresión de colección cuyos elementos tienen tramos de verdad, exactamente como un children, y así son también los items de un menú y las actions de un diálogo. Todos reciben lo que recibieron los hijos: el panel los lista, e insertar o quitar toma el nombre de la lista.

Lo que difiere es lo que va en ellos. Un GridTrack es datos: nunca se renderiza, así que nada en el lienzo lleva su tramo y no hay nada que pulsar. Esas listas se direccionan por tanto por posición desde el panel, y su paleta son las maneras propias que el tipo de elemento tiene de escribirse, GridTrack.Flex(), GridTrack.Auto, y new(…) tipado por destino, ya que el framework no da fábrica a los records de valor a propósito. Los dos hechos se leen de la firma en vez de escribirse como una regla sobre grids.

Arreglar: insertar, mover, quitar

Desde 0.2.0-preview.30

Los gestos estructurales están cercados a la forma declarativa, una expresión de colección children: [ … ]:

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

Esa lista es la única forma en la que una inserción puede empalmarse con seguridad: sus elementos tienen tramos de verdad, así que uno nuevo va entre dos de ellos sin tocar nada más. Un contenedor construido con sentencias (var column = new Column(); column.Add(…);) es flujo de datos, no una lista, y reordenarlo es otro problema. El panel pregunta primero y lo dice, en vez de ofrecer un control que se niega.

  • Insertar viene de una paleta derivada de las superficies de fábrica mismas, así que un componente nuevo aparece en ella sin que nadie mantenga una lista. Eso incluye los componentes propios de la app: el generador les escribe una superficie AppUI exactamente como la del framework, y se ofrecen primero, bajo su propio encabezado. Toda entrada está probada de que compila donde aterriza: una prueba inserta cada una en una lista real y falla con cualquiera que no lo hiciera.
  • Mover intercambia dos elementos y deja todo lo que está entre ellos exactamente donde está: la coma, el salto de línea, la sangría. Reescribir la lista desde los textos de sus elementos sería más simple y descartaría en silencio un comentario que alguien dejó entre dos hijos. Queda una imperfección, y se nombra en vez de ocultarse: un comentario escrito encima de un hijo se queda con la posición, no con el hijo.
  • Quitar toma el elemento y exactamente un separador. El elemento solo deja una coma perdida; los dos dejan un agujero.

Ni mover ni quitar se ofrecen donde no significan nada: un nodo que no es elemento de una lista no tiene orden dentro del cual moverse, así que los controles se ocultan en vez de deshabilitarse, no hay nada que explicar sobre un arreglo que no existe.

  • Duplicar copia el texto de un nodo y aterriza la copia inmediatamente después, y la copia es lo que queda seleccionado, porque es la cosa que acabas de hacer.

Cada gesto es un solo WorkspaceEdit, así que cada uno es un solo deshacer.

Desde la lista

La pantalla entera es también un árbol, en el Explorador al lado del Esquema. Cada fila dice dónde está escrita, PaymentsPage.cs:28, que es lo único que una lista puede decir y una imagen no, y alcanza lo que el lienzo no puede: un contenedor llenado de borde a borde por sus propios hijos no tiene ningún píxel propio.

Elegir una fila la selecciona en el lienzo, por el mismo camino que toma un clic, así que el esquema, el panel y el cursor del editor van todos detrás. Seleccionar en el lienzo marca la fila.

Desde el teclado

Todo gesto de arriba es también una tecla, y son el mismo gesto en vez de una segunda implementación de él:

Flechas recorrer el árbol: arriba al padre, abajo al primer hijo, izquierda y derecha por los hermanos
Alt+Flechas mover el nodo en vez de la selección
Delete quitarlo
Cmd/Ctrl+D duplicarlo
Esc soltar: primero la selección, después el modo inspección

Respetan las mismas guardas que el puntero: nada estructural mientras el árbol se asienta, y nada en absoluto mientras el puntero está sobre el panel, donde esas teclas son alguien escribiendo.

Desde el lienzo

Los mismos tres gestos están en el puntero, donde el árbol es legible:

  • Arrastra un hijo para reordenarlo. Un caret marca el hueco en el que aterrizaría, y la insignia dice a dónde va, Text → 3 of 5. Por lejos que viaje, es una edición y un deshacer.
  • Pasa por encima de un hijo y un + aparece en cada extremo suyo, antes de este, después de este, a lo largo del eje propio del contenedor. Qué eje se lee del resultado renderizado en vez de del CSS: una Row es flex, un Grid es grid y un Stack es absoluto, y en pantalla los tres responden «¿están estos lado a lado?» del mismo modo. (Con menos de dos hijos no hay resultado que leer, y solo ahí el lienzo pregunta a la maquetación misma.)
  • Pasa por encima del espacio propio de un contenedor, incluido uno vacío, y el hueco más cercano al puntero se marca con una línea discontinua y un solo +. Esa marca es la respuesta a dónde podría ir algo, que es una pregunta que un lienzo nunca debería hacer adivinar a nadie. La raya es a propósito: el caret marca un compromiso, la raya marca una posibilidad.

Un montón no es una fila. Los hijos de un Stack se solapan, y el orden de pintado es el orden de los hijos, así que su lista es significativa y su geometría no. El lienzo no puede saberlo (dos hijos que se solapan se ven exactamente como dos que nadie maquetó), así que el host dice qué listas están en capas. Allí, la marca de soltar se vuelve una cubierta sobre el hijo delante del cual el nodo vendría a descansar, la insignia lee in front of Card en vez de 3 of 5, y las flechas del panel mantienen su gesto mientras descartan el nombre equivocado para él: Enviar atrás y Traer adelante.

Un arrastre no está confinado a la lista en la que empezó: el destino de soltar se recalcula desde el puntero en cada movimiento, así que el caret va detrás hacia cualquier contenedor sobre el que esté. Salir de su propia lista es un quitar y un insertar, que el host escribe como un solo reemplazo que abarca los dos, una edición, un Ctrl+Z. El texto movido se vuelve a sangrar a su nueva profundidad y por lo demás se lleva literalmente, que es lo que hace del compilador la cerca: un nodo escrito contra una variable local del método del que vino no compila en su nuevo hogar, y el rechazo cita CS0103 en vez de inventar una regla sobre qué es portable.

Los rechazos se dibujan antes del gesto en vez de después. La pantalla y la lista de C# tienen que alinearse hijo por hijo: un hijo que se renderiza como varios elementos, o como ninguno, rompe esa correspondencia, y un índice calculado desde píxeles nombraría entonces otro elemento en el archivo, así que la posibilidad se retira en vez de adivinarse. Un nodo no puede soltarse dentro de su propio subárbol, dentro de un contenedor que no toma lista, ni dentro de una lista en otro archivo, eso editaría dos documentos a la vez.

Un movimiento invalida el tramo que el panel tenía, así que el host responde con dónde aterrizó el nodo y la selección va detrás hacia allí. Sin eso, preguntar por las viejas coordenadas describe al hermano con el que acababa de intercambiarse.

Un proyecto nativo también tiene vista previa

A la vista previa no le importa por qué SDK se entrega el proyecto. Un proyecto nativo (Photon), que no tiene wwwroot y nunca corre eqc, tiene vista previa igual: el SDK nativo escribe la lista de referencias para las herramientas, la extensión lleva el runtime de navegador que el proyecto legítimamente no tiene, y el mismo árbol abstracto de componentes se renderiza por el realizador web. La barra de herramientas lo dice exactamente, native project · web realizer, y abre en el shell de teléfono, porque esa es la forma en la que la app se entrega. Un valor por defecto, no una jaula: en cuanto una mano toca el selector de formato, el proyecto deja de tener opinión.

El estado sobrevive a la recompilación

Un contador pulsado hasta 7 se queda en 7 a través de la siguiente pulsación. Antes de cada remontaje los campos propios de la página se capturan, distinguidos de los del framework sondeando la clase base propia de la página, no una lista escrita a mano, y se devuelven por la misma puerta __INITIAL_STATE__ que usa el traspaso del SSR. Guardado por nombre de clase, así que una página renombrada empieza de cero en vez de heredar los campos de un extraño.

Un fotograma de Photon, desde el motor mismo

El botón del cubo en la barra de herramientas renderiza el búfer actual por el motor nativo y muestra el fotograma, glifos reales, tema real, en el formato que el selector haya elegido. Estos son los píxeles contra los que se fijan los backends de GPU: el fotograma viene del rasterizador de Referencia, la fuente normativa de píxeles del motor, y una prueba de paridad permanente demuestra que Metal dibuja la misma lista de display a los mismos píxeles.

Corre en un proceso hijo descartable, porque el Build() de una página es el código del autor: uno que nunca retorna se mata tras diez segundos y vuelve como una frase, con la vista previa todavía viva detrás. Los servicios que pide el constructor de una página se satisfacen como lo haría el contenedor de DI, menos el contenedor, una implementación del ensamblado propio de la app cuando hay una, un stub benigno si no, y una página que aun así lanza renderiza la superficie de diagnóstico propia del framework, nombrando el componente y la excepción.

Una imagen fija, a propósito, pero una pulsable. Antes de que el fotograma se emita, toda expresión que construye un nodo se reescribe para sellar el tramo de C# que lo construyó (una pasada de C# a C# sobre la compilación de diseño, nunca sobre nada que un usuario entregue), y el renderizador devuelve un mapa de impactos junto al PNG: el origen de cada nodo y sus límites maquetados, en orden de pintado. Un clic en la imagen encuentra el componente más arriba bajo el cursor, la última entrada que contiene el punto, lo marca en el fotograma, abre el inspector y revela la línea exacta. Es la misma cadena de origen con la que selecciona el lienzo web, así que la imagen y la página viva responden a un clic de forma idéntica; los métodos ayudantes se sellan en su sitio de llamada, y el tramo propio de una construcción siempre le gana a la llamada que solo la devolvió.

Las interacciones más allá de la selección se quedan en la vista previa viva, y una recompilación descarta un fotograma de la página que reemplazó.

Ejecutarlo hoy

Empaquétalo una vez e instala el .vsix:

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

Eso compila la extensión, publica el host de diseño dentro de ella y escribe equantic-ui.vsix (10,7 MB), el host viaja con la extensión, así que una instalación no necesita checkout. Es dependiente del framework: una publicación autocontenida serían ~70 MB por plataforma y tres de ellas, y la única dependencia que esto asume es el runtime de .NET contra el que ya estás compilando.

Trabajando EN la extensión en cambio, npm run compile es suficiente: en desarrollo la compilación propia del repositorio del host gana sobre una empaquetada, así que un .vsix compilado ayer no puede responder en silencio por la fuente de hoy.

Luego abre una página o componente de C# y ejecuta eQuantic UI: Open Preview. El proyecto debe haberse compilado una vez, la vista previa se apoya en la salida de una compilación ordinaria (la lista de referencias que escribe el SDK, y wwwroot/_equantic/runtime.js), así que «compila el proyecto una vez» es la instrucción honesta cuando falta una pieza.

Los controles propios de la vista previa viven en la barra de título del editor, aportados como comandos ordinarios, así que heredan el tema y el conjunto de iconos como cualquier otra acción del editor: el puntero empieza y detiene la inspección, y un reinicio está ahí para cuando hay que traer de vuelta al host.

Lo que todavía no hace

  • No arrastra entre archivos. Un nodo puede arrastrarse a cualquier lista del archivo que se está editando, pero un contenedor escrito en otro se rechaza: esa edición tocaría dos documentos a la vez.
  • Grid y Stack necesitan su propio gesto. Las columnas de un Grid son pistas cuya adición vuelve a fluir a cada hijo, y el orden de lista de un Stack es orden de pintado, ninguno de los dos es el gesto «inserta un hermano» que comparten los otros contenedores.
  • Sin vista previa nativa. La identidad ya está resuelta en Photon (LayoutNode lleva el nodo, los límites absolutos y una ruta estable); solo falta meter los fotogramas en un webview.
  • Sin llamadas a [ServerAction]. Los ids de acción vienen de un escaneo de ensamblado al arrancar, así que una clase de vista previa ad-hoc nunca está en el registro.
  • El tema de base. Una app selecciona su tema al arrancar, y leer eso de vuelta significa ejecutar la raíz de composición de la app. Las formas y la maquetación son exactas; una paleta de otra marca todavía no se refleja.

Ver también

Clone this wiki locally