CommunityProgramación y desarrollogithub.com

omrehbein/ace-editor

Use when an agent automating a web page must find, read, replace or validate text inside an Ace code editor (elements with class `ace_editor`) — code/JSON/YAML/SQL editors embedded in cloud consoles (e.g. AWS policy editors), Cloudscape/awsui, Cloud9-style IDEs, react-ace, admin panels. Also use when text typed into a web code editor comes out mangled (extra or missing quotes/brackets, doubled indentation), when only part of a document is visible in the DOM or a screenshot, or when you need the editor's syntax errors.

¿Qué es ace-editor?

ace-editor is a Cursor agent skill that use when an agent automating a web page must find, read, replace or validate text inside an Ace code editor (elements with class `ace_editor`) — code/JSON/YAML/SQL editors embedded in cloud consoles (e.g. AWS policy editors), Cloudscape/awsui, Cloud9-style IDEs, react-ace, admin panels. Also use when text typed into a web code editor comes out mangled (extra or missing quotes/brackets, doubled indentation), when only part of a document is visible in the DOM or a screenshot, or when you need the editor's syntax errors.

Compatible con~Claude Code~Codex CLICursor
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

Ace editor automation

Ace renders only the rows on screen and receives keystrokes through a hidden textarea that auto-closes quotes/brackets and auto-indents new lines. Typing {, Enter, "a": [1], Enter, } into a default Ace JSON editor produces:

{
        "a": [1]
}
}

So:

  • Read and write through the editor API with JavaScript running in the page. Don't scrape .ace_line elements or screenshots for content, and don't type code key by key.
  • Run in the page's main world. Properties the page sets on elements (such as el.env) are invisible from isolated worlds, e.g. browser-extension content scripts. Use your tool's main-world option, or the fallback in step 5.
  • Respect read-only editors. The API can still change them, but the app doesn't expect it — only do it when the user asks.
  • Changing the editor is not saving. Confirm with the user before clicking Save / Submit / Apply.

Snippets below are plain page JavaScript whose last expression is the result. Step 7 uses await; if your tool has no top-level await, wrap the snippet in an async function (e.g. Playwright page.evaluate(async () => { ... })).

1. Find the editors

Ace's renderer adds the ace_editor class to every editor container, in every version, so the selector is stable. What varies is where editors live (main document, open shadow roots, same-origin iframes), which elements are real editors (the autocomplete popup is an Ace editor too) and whether the instance is reachable. Search dynamically:

(() => {
  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} > cross-origin iframe ${frame.src}` });
    }
  };
  walk(document, 'document');
  // the autocomplete popup is also an Ace editor: skip it
  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, note: 'not reachable from this page: open the frame URL directly' };
    const ed = el.env?.editor;
    return {
      i,
      where,
      id: el.id || undefined,
      label: el.closest('[aria-label]')?.getAttribute('aria-label') || undefined,
      visible: el.getClientRects().length > 0,
      handle: !!ed,
      mode: ed?.session.getMode().$id,
      readOnly: ed?.getReadOnly(),
      lines: ed?.session.getLength(),
      firstLine: (ed ? ed.session.getLine(0) : el.querySelector('.ace_line')?.textContent ?? '').slice(0, 60),
    };
  });
})()
  • Pick the target by label, id, mode, firstLine, visible; use its i in the next steps (the examples use 0).
  • Re-run the finder after navigation or re-renders. An empty list on a single-page app usually means the editor isn't mounted yet: wait and retry.
  • handle: false → the page built the editor without ace.edit() (no el.env) or you are in an isolated world: use step 5. Never call ace.edit(el) to get a handle — on an element without env it creates a second editor on top of the page's one.

2. Inspect and read

const editor = window.__aceEditors[0].env.editor; // index from the finder
({
  mode: editor.session.getMode().$id, // e.g. ace/mode/json (ace/mode/text while a mode is still loading)
  readOnly: editor.getReadOnly(),
  lines: editor.session.getLength(),
  indent: JSON.stringify(editor.session.getTabString()),
  cursor: editor.getCursorPosition(), // { row, column }, 0-based
  selection: editor.getSelectedText(),
  text: editor.getValue(),
})

For very large documents read slices with editor.session.getLines(firstRow, lastRow) (0-based, inclusive).

3. Replace the whole document

const editor = window.__aceEditors[0].env.editor;
const newText = '...'; // the full new content
editor.session.getUndoManager().startNewGroup?.(); // keep this change as its own undo step
editor.setValue(newText, -1); // -1 puts the cursor at the start; fires 'change' so the app updates its state
editor.getValue() === newText

Generate structured content from data instead of writing it by hand: JSON.stringify(data, null, editor.session.getTabString()) matches the editor's indentation.

4. Targeted edits

const editor = window.__aceEditors[0].env.editor;
// first match; options: wrap, caseSensitive, wholeWord, regExp, backwards, range
if (editor.find('oldValue', { wrap: true, caseSensitive: true })) editor.replace('newValue');
// every match, returns the count; with regExp, $1 back-references work
const replaced = editor.replaceAll('version-$1', { needle: 'v(\\d+)', regExp: true });
// by position: rows and columns are 0-based, columns past the end of the line are clipped
editor.session.replace({ start: { row: 2, column: 0 }, end: { row: 2, column: Infinity } }, 'whole new line 3');
editor.session.insert({ row: editor.session.getLength(), column: 0 }, '\nappended line');
({ replaced, text: editor.getValue() })

More — cursor, selection, folding, scrolling, commands, events: references/ace-api.md.

5. No handle? Paste into the hidden textarea

const el = window.__aceEditors[0]; // works with handle: false
const newText = '...';
const input = el.querySelector('textarea.ace_text-input');
const mac = /Mac|iPhone|iPad/.test(navigator.platform);
input.focus();
// select all: Ace binds Ctrl+A, or Cmd+A on Apple platforms
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', newText);
input.dispatchEvent(new ClipboardEvent('paste', { clipboardData: data, bubbles: true, cancelable: true }));
'pasted'
  • Pasted text is inserted verbatim: no auto-pairing, no auto-indent. Read-only editors ignore it.
  • Alternative with the textarea focused and everything selected: document.execCommand('insertText', false, newText).
  • Without JavaScript: put the text on the OS clipboard, click inside the editor, press Ctrl+A (Cmd+A on macOS), then Ctrl+V (Cmd+V).
  • Without a handle you can only read rendered rows (.ace_text-layer .ace_line, after the next frame). For the full text, select all and copy via the OS clipboard.

6. If you really must type

const editor = window.__aceEditors[0].env.editor;
const assists = ['behavioursEnabled', 'wrapBehavioursEnabled', 'enableAutoIndent', 'enableLiveAutocompletion', 'enableBasicAutocompletion', 'enableSnippets'];
window.__aceSavedOptions = Object.fromEntries(assists.filter(name => editor.$options[name]).map(name => [name, editor.getOption(name)]));
editor.setOptions(Object.fromEntries(Object.keys(window.__aceSavedOptions).map(name => [name, false])));
editor.focus();
window.__aceSavedOptions // restore after typing: editor.setOptions(window.__aceSavedOptions)

Type, then restore the options. Prefer single-line content and press Escape befor

Skills relacionados