CommunityProgramación y desarrollogithub.com

omrehbein/ace-editor

Útil para encontrar, leer, reemplazar o validar texto dentro de un editor de código Ace (elementos con clase `ace_editor`) — editores de código/JSON/YAML/SQL integrados en consolas en la nube (por ejemplo, editores de políticas de AWS), entornos como Cloudscape/awsui, IDEs estilo Cloud9, react-ace y paneles de administración. También se usa cuando el texto escrito en un editor de código web aparece distorsionado (comillas/corchetes extraños o faltantes, sangría duplicada), cuando solo una parte del documento es visible en el DOM o una captura de pantalla, o cuando se necesitan los errores de sintaxis del editor.

¿Qué es ace-editor?

Esta habilidad automatiza editores de código Ace, que son comunes en consolas en la nube como AWS, paneles de administración y entornos tipo Cloud9. El problema principal es que Ace renderiza solo las filas visibles en pantalla y recibe la entrada del teclado a través de un `textarea` oculto con autocompletado automático de corchetes y sangría, lo que puede provocar texto distorsionado al escribir tecla por tecla. La solución es leer y escribir mediante la API del editor con JavaScript ejecutado directamente en la página, evitando extraer contenido de los elementos `.ace_line` o capturas de pantalla, y respetando los editores de solo lectura. Incluye pasos para encontrar los editores (incluso en shadow DOM, iframes del mismo origen o popups de autocompletado), inspeccionar y leer contenido, reemplazar documentos completos o realizar ediciones dirigidas, y un método alternativo para casos sin acceso a la API usando pegado en el textarea oculto. Soporta herramientas como Playwright, Puppeteer y extensiones de navegador, y requiere ejecución en el ámbito principal de la página para evitar problemas con propiedades como `el.env`.

Compatible con~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/omrehbein/agent-skills/tree/main/skills/ace-editor

Installed? Explore more Programación y desarrollo skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Automatización del editor Ace

Ace solo renderiza las filas visibles en pantalla y recibe las pulsaciones de teclado a través de un textarea oculto que cierra automáticamente comillas/corchetes y aplica sangría automática en nuevas líneas. Escribir {, Enter, "a": [1], Enter, } en un editor JSON predeterminado produce:

{
        "a": [1]
}
}

Por lo tanto:

  • Lee y escribe a través de la API del editor con JavaScript ejecutado en la página. No extraigas contenido de elementos .ace_line ni de capturas de pantalla, y no escribas código tecla por tecla.
  • Ejecuta en el ámbito principal de la página. Las propiedades que la página establece en los elementos (como el.env) son invisibles desde ámbitos aislados, por ejemplo, scripts de contenido de extensiones de navegador. Usa la opción de ámbito principal de tu herramienta, o el método alternativo en el paso 5.
  • Respeta los editores de solo lectura. La API aún puede modificarlos, pero la aplicación no lo espera; solo hazlo cuando el usuario lo solicite.
  • Cambiar el editor no equivale a guardar. Confirma con el usuario antes de hacer clic en Guardar / Enviar / Aplicar.

Los fragmentos a continuación son JavaScript plano de página cuya última expresión es el resultado. El paso 7 usa await; si tu herramienta no tiene await de nivel superior, envuelve el fragmento en una función async (por ejemplo, page.evaluate(async () => { ... }) en Playwright).

1. Encontrar los editores

El renderizador de Ace añade la clase ace_editor a cada contenedor de editor, en todas las versiones, por lo que el selector es estable. Lo que varía es dónde viven los editores (documento principal, shadow roots abiertos, iframes del mismo origen), qué elementos son editores reales (el popup de autocompletado también es un editor Ace) y si la instancia es accesible. Busca dinámicamente:

(() => {
  const hits = [];
  const walk = (root, where) => {
    for (const el of root.querySelectorAll('.ace_editor')) hits.push({ el, where });
    for (const node of root.querySelectorAll('*')) {
      if (node.shadowRoot) walk(node.shadowRoot, `${where} > ${node.localName}::shadow`);
    }
    for (const frame of root.querySelectorAll('iframe, frame')) {
      let doc = null;
      try { doc = frame.contentDocument; } catch {}
      if (doc) walk(doc, `${where} > iframe${frame.id ? '#' + frame.id : ''}`);
      else hits.push({ el: null, where: `${where} > iframe de origen cruzado ${frame.src}` });
    }
  };
  walk(document, 'document');
  // el popup de autocompletado también es un editor Ace: saltarlo
  const found = hits.filter(h => !h.el?.classList.contains('ace_autocomplete'));
  window.__aceEditors = found.map(h => h.el);
  return found.map(({ el, where }, i) => {
    if (!el) return { i, where, nota: 'no accesible desde esta página: abre la URL del frame directamente' };
    const ed = el.env?.editor;
    return {
      i,
      where,
      id: el.id || undefined,
      etiqueta: el.closest('[aria-label]')?.getAttribute('aria-label') || undefined,
      visible: el.getClientRects().length > 0,
      handle: !!ed,
      modo: ed?.session.getMode().$id,
      soloLectura: ed?.getReadOnly(),
      lineas: ed?.session.getLength(),
      primeraLinea: (ed ? ed.session.getLine(0) : el.querySelector('.ace_line')?.textContent ?? '').slice(0, 60),
    };
  });
})()
  • Selecciona el objetivo por etiqueta, id, modo, primeraLinea, visible; usa su i en los siguientes pasos (los ejemplos usan 0).
  • Vuelve a ejecutar el buscador después de navegación o re-renderizados. Una lista vacía en una aplicación de una sola página generalmente significa que el editor aún no se ha montado: espera y reintenta.
  • handle: false → la página construyó el editor sin ace.edit() (sin el.env) o estás en un ámbito aislado: usa el paso 5. Nunca llames a ace.edit(el) para obtener un handle — en un elemento sin env crea un segundo editor encima del editor de la página.

2. Inspeccionar y leer

const editor = window.__aceEditors[0].env.editor; // índice del buscador
({
  modo: editor.session.getMode().$id, // por ejemplo, ace/mode/json (ace/mode/text mientras se carga el modo)
  soloLectura: editor.getReadOnly(),
  lineas: editor.session.getLength(),
  indentacion: JSON.stringify(editor.session.getTabString()),
  cursor: editor.getCursorPosition(), // { row, column }, basado en 0
  seleccion: editor.getSelectedText(),
  texto: editor.getValue(),
})

Para documentos muy grandes, lee fragmentos con editor.session.getLines(primeraFila, ultimaFila) (basado en 0, inclusivo).

3. Reemplazar todo el documento

const editor = window.__aceEditors[0].env.editor;
const nuevoTexto = '...'; // el contenido nuevo completo
editor.session.getUndoManager().startNewGroup?.(); // mantiene este cambio como un paso de deshacer propio
editor.setValue(nuevoTexto, -1); // -1 coloca el cursor al inicio; dispara 'change' para que la aplicación actualice su estado
editor.getValue() === nuevoTexto

Genera contenido estructurado a partir de datos en lugar de escribirlo manualmente: JSON.stringify(datos, null, editor.session.getTabString()) coincide con la indentación del editor.

4. Ediciones dirigidas

const editor = window.__aceEditors[0].env.editor;
// primera coincidencia; opciones: wrap, caseSensitive, wholeWord, regExp, backwards, range
if (editor.find('valorAntiguo', { wrap: true, caseSensitive: true })) editor.replace('nuevoValor');
// cada coincidencia, devuelve el recuento; con regExp, las referencias $1 funcionan
const reemplazadas = editor.replaceAll('version-$1', { needle: 'v(\\d+)', regExp: true });
// por posición: filas y columnas están basadas en 0, las columnas después del final de la línea se recortan
editor.session.replace({ start: { row: 2, column: 0 }, end: { row: 2, column: Infinity } }, 'línea 3 completamente nueva');
editor.session.insert({ row: editor.session.getLength(), column: 0 }, '\nlínea añadida');
({ reemplazadas, texto: editor.getValue() })

Más — cursor, selección, plegado, desplazamiento, comandos, eventos: references/ace-api.md.

5. ¿Sin handle? Pega en el textarea oculto

const el = window.__aceEditors[0]; // funciona con handle: false
const nuevoTexto = '...';
const input = el.querySelector('textarea.ace_text-input');
const mac = /Mac|iPhone|iPad/.test(navigator.platform);
input.focus();
// seleccionar todo: Ace asocia Ctrl+A, o Cmd+A en plataformas Apple
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'a', code: 'KeyA', keyCode: 65, which: 65, ctrlKey: !mac, metaKey: mac, bubbles: true, cancelable: true }));
const data = new DataTransfer();
data.setData('text/plain', nuevoTexto);
input.dispatchEvent(new ClipboardEvent('paste', { clipboardData: data, bubbles: true, cancelable: true }));
'pegado'
  • El texto pegado se inserta tal cual: sin emparejamiento automático ni sangría automática. Los editores de solo lectura lo ignoran.
  • Alternativa con el textarea enfocado y todo seleccionado: document.execCommand('insertText', false, nuevoTexto).
  • Sin JavaScript: coloca el texto en el portapapeles del SO, haz clic dentro del editor, presiona Ctrl+A (Cmd+A en macOS) y luego Ctrl+V (Cmd+V).
  • Sin un handle solo puedes leer las filas renderizadas (.ace_text-layer .ace_line, después del siguiente frame). Para el texto completo, selecciona todo y copia a través del portapapeles del SO.

6. Si realmente debes escribir

const editor = window.__aceEditors[0].env.editor;
const asistencias = ['behavioursEnabled', 'wrapBehavioursEnabled', 'enableAutoIndent', 'enableLiveAutocompletion', 'enableBasicAutocompletion', 'enableSnippets'];
window.__aceOpcionesGuardadas = Object.fromEntries(asistencias.filter(nombre => editor.$options[nombre]).map(nombre => [nombre, editor.getOption(nombre)]));
editor.setOptions(Object.fromEntries(Object.keys(window.__aceOpcionesGuardadas).map(nombre => [nombre, false])));
editor.focus();
window.__aceOpcionesGuardadas // restaurar después de escribir: editor.setOptions(window.__aceOpcionesGuardadas)

Escribe y luego restaura las opciones. Prefiere contenido de una sola línea y presiona Escape antes de continuar para evitar activar el autocompletado.

Skills relacionados