omrehbein/ace-editor

À utiliser lorsqu'un agent automatisant une page web doit trouver, lire, remplacer ou valider du texte dans un éditeur de code Ace (éléments avec la classe `ace_editor`) — éditeurs de code/JSON/YAML/SQL intégrés dans les consoles cloud (p. ex. AWS Policy Editor), interfaces Cloudscape/awsui, IDE de type Cloud9, react-ace, panneaux d'administration. À utiliser également lorsque du texte saisi dans un éditeur de code web apparaît déformé (guillemets ou crochets manquants ou en trop, double indentation), lorsqu'une seule partie du document est visible dans le DOM ou une capture d'écran, ou lorsque vous avez besoin des erreurs de syntaxe de l'éditeur.

Qu'est-ce que ace-editor ?

Cette compétence fournit des scripts JavaScript automatisés pour interagir avec les éditeurs de code Ace (classe CSS `ace_editor`) dans des pages web. Elle permet de trouver, lire, remplacer et valider du texte, en contournant les limitations des approches basées sur la frappe ou le scraping du DOM. La compétence utilise l'API de l'éditeur pour des opérations fiables et précises, et gère les cas où le handle de l'éditeur n'est pas accessible (par exemple, dans des mondes isolés de scripts de contenu de navigateur). Elle inclut également des stratégies de repli telles que le collage dans le textarea caché, et des instructions pour désactiver les fonctionnalités d'assistance à la frappe. Cette compétence est conçue pour être utilisée avec des outils d'automatisation de pages web comme Playwright ou les outils d'IA agent qui peuvent exécuter JavaScript dans le contexte principal de la page.

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

Installed? Explore more Développement et programmation skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Automatisation de l'éditeur Ace

Ace n'affiche que les lignes visibles à l'écran et reçoit les frappes via un textarea caché qui ferme automatiquement les guillemets/crochets et auto-indente les nouvelles lignes. Taper {, Entrée, "a": [1], Entrée, } dans un éditeur Ace JSON par défaut produit :

{
        "a": [1]
}
}

Ainsi :

  • Lire et écrire via l'API de l'éditeur avec JavaScript s'exécutant dans la page. Ne pas extraire le contenu des éléments .ace_line ou des captures d'écran, et ne pas taper le code touche par touche.
  • Exécuter dans le contexte principal de la page. Les propriétés que la page définit sur les éléments (telles que el.env) sont invisibles depuis des mondes isolés, par exemple les scripts de contenu d'extensions de navigateur. Utilisez l'option de contexte principal de votre outil, ou la solution de repli à l'étape 5.
  • Respecter les éditeurs en lecture seule. L'API peut quand même les modifier, mais l'application ne s'y attend pas — ne le faites que si l'utilisateur le demande.
  • Modifier l'éditeur ne sauvegarde pas. Confirmez avec l'utilisateur avant de cliquer sur Enregistrer / Soumettre / Appliquer.

Les extraits ci-dessous sont du JavaScript de page simple dont la dernière expression est le résultat. L'étape 7 utilise await ; si votre outil n'a pas de await de niveau supérieur, enveloppez l'extrait dans une fonction asynchrone (par exemple Playwright page.evaluate(async () => { ... })).

1. Trouver les éditeurs

Le moteur de rendu d'Ace ajoute la classe ace_editor à chaque conteneur d'éditeur, dans toutes les versions, donc le sélecteur est stable. Ce qui varie, c'est où se trouvent les éditeurs (document principal, racines shadow ouvertes, iframes de même origine), quels éléments sont de véritables éditeurs (la popup d'autocomplétion est aussi un éditeur Ace) et si l'instance est accessible. Recherchez dynamiquement :

(() => {
  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 d'origine croisée ${frame.src}` });
    }
  };
  walk(document, 'document');
  // la popup d'autocomplétion est aussi un éditeur Ace : l'ignorer
  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: 'inaccessible depuis cette page : ouvrez l'URL de l'iframe directement' };
    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),
    };
  });
})()
  • Choisissez la cible par label, id, mode, firstLine, visible ; utilisez son i dans les étapes suivantes (les exemples utilisent 0).
  • Réexécutez le chercheur après une navigation ou des re-rendus. Une liste vide sur une application monopage signifie généralement que l'éditeur n'est pas encore monté : attendez et réessayez.
  • handle: false → la page a construit l'éditeur sans ace.edit() (pas de el.env) ou vous êtes dans un monde isolé : utilisez l'étape 5. N'appelez jamais ace.edit(el) pour obtenir un handle — sur un élément sans env, cela crée un second éditeur par-dessus celui de la page.

2. Inspecter et lire

const editor = window.__aceEditors[0].env.editor; // index du chercheur
({
  mode: editor.session.getMode().$id, // p. ex. ace/mode/json (ace/mode/text pendant le chargement du mode)
  readOnly: editor.getReadOnly(),
  lines: editor.session.getLength(),
  indent: JSON.stringify(editor.session.getTabString()),
  cursor: editor.getCursorPosition(), // { row, column }, base 0
  selection: editor.getSelectedText(),
  text: editor.getValue(),
})

Pour les très grands documents, lisez des portions avec editor.session.getLines(firstRow, lastRow) (base 0, inclusif).

3. Remplacer tout le document

const editor = window.__aceEditors[0].env.editor;
const newText = '...'; // le nouveau contenu complet
editor.session.getUndoManager().startNewGroup?.(); // garder cette modification comme une étape d'annulation distincte
editor.setValue(newText, -1); // -1 place le curseur au début ; déclenche 'change' pour que l'application mette à jour son état
editor.getValue() === newText

Générez du contenu structuré à partir de données plutôt que de l'écrire à la main : JSON.stringify(data, null, editor.session.getTabString()) correspond à l'indentation de l'éditeur.

4. Modifications ciblées

const editor = window.__aceEditors[0].env.editor;
// première correspondance ; options : wrap, caseSensitive, wholeWord, regExp, backwards, range
if (editor.find('oldValue', { wrap: true, caseSensitive: true })) editor.replace('newValue');
// chaque correspondance, renvoie le nombre ; avec regExp, les références arrière $1 fonctionnent
const replaced = editor.replaceAll('version-$1', { needle: 'v(\\d+)', regExp: true });
// par position : les lignes et colonnes sont en base 0, les colonnes après la fin de la ligne sont tronquées
editor.session.replace({ start: { row: 2, column: 0 }, end: { row: 2, column: Infinity } }, 'toute nouvelle ligne 3');
editor.session.insert({ row: editor.session.getLength(), column: 0 }, '\\nligne ajoutée');
({ replaced, text: editor.getValue() })

Plus — curseur, sélection, pliage, défilement, commandes, événements : references/ace-api.md.

5. Pas de handle ? Coller dans le textarea caché

const el = window.__aceEditors[0]; // fonctionne même avec handle: false
const newText = '...';
const input = el.querySelector('textarea.ace_text-input');
const mac = /Mac|iPhone|iPad/.test(navigator.platform);
input.focus();
// tout sélectionner : Ace lie Ctrl+A, ou Cmd+A sur les plateformes 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', newText);
input.dispatchEvent(new ClipboardEvent('paste', { clipboardData: data, bubbles: true, cancelable: true }));
'collé'
  • Le texte collé est inséré textuellement : pas de couplage automatique, pas d'auto-indentation. Les éditeurs en lecture seule l'ignorent.
  • Alternative avec le textarea focalisé et tout sélectionné : document.execCommand('insertText', false, newText).
  • Sans JavaScript : placez le texte dans le presse-papiers du système d'exploitation, cliquez à l'intérieur de l'éditeur, appuyez sur Ctrl+A (Cmd+A sur macOS), puis Ctrl+V (Cmd+V).
  • Sans handle, vous ne pouvez lire que les lignes rendues (.ace_text-layer .ace_line, après l'image suivante). Pour le texte complet, tout sélectionner et copier via le presse-papiers du système d'exploitation.

6. Si vous devez vraiment taper

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 // restaurer après la frappe : editor.setOptions(window.__aceSavedOptions)

Tapez, puis restaurez les options. Préférez le contenu sur une seule ligne et appuyez sur Échap avan

Skills associés