Ace-Editor-Automatisierung
Ace rendert nur die auf dem Bildschirm sichtbaren Zeilen und empfängt Tastatureingaben über ein verstecktes textarea, das automatisch Anführungszeichen/Klammern schließt und neue Zeilen automatisch einrückt. Die Eingabe von {, Enter, "a": [1], Enter, } in einen standardmäßigen Ace-JSON-Editor ergibt:
{
"a": [1]
}
}
Daher:
- Lesen und Schreiben über die Editor-API mit JavaScript, das auf der Seite ausgeführt wird. Kratzen Sie keine
.ace_line-Elemente oder Screenshots nach Inhalten ab, und tippen Sie keinen Code Tastendruck für Tastendruck ein. - Im Hauptkontext der Seite ausführen. Eigenschaften, die die Seite auf Elemente setzt (z. B.
el.env), sind aus isolierten Kontexten unsichtbar, z. B. Browsererweiterungs-Content-Scripts. Verwenden Sie die Hauptkontext-Option Ihres Tools oder den Fallback aus Schritt 5. - Schreibgeschützte Editoren respektieren. Die API kann sie zwar trotzdem ändern, aber die App erwartet das nicht – tun Sie es nur, wenn der Benutzer es verlangt.
- Die Änderung des Editors speichert nicht. Bestätigen Sie mit dem Benutzer, bevor Sie auf Speichern / Senden / Anwenden klicken.
Die folgenden Snippets sind einfaches JavaScript der Seite, dessen letzter Ausdruck das Ergebnis ist. Schritt 7 verwendet await; wenn Ihr Tool kein await auf oberster Ebene hat, verpacken Sie das Snippet in eine asynchrone Funktion (z. B. Playwright page.evaluate(async () => { ... })).
1. Die Editoren finden
Aces Renderer fügt jedem Editor-Container in jeder Version die Klasse ace_editor hinzu, daher ist der Selektor stabil. Was variiert, ist wo Editoren leben (Hauptdokument, offene Shadow-Roots, iframes gleicher Herkunft), welche Elemente echte Editoren sind (das Autovervollständigungs-Popup ist auch ein Ace-Editor) und ob die Instanz erreichbar ist. Dynamisch suchen:
(() => {
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');
// das Autovervollständigungs-Popup ist auch ein Ace-Editor: überspringen
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: 'von dieser Seite nicht erreichbar: öffnen Sie die Frame-URL direkt' };
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),
};
});
})()
- Wählen Sie das Ziel nach
label,id,mode,firstLine,visibleaus; verwenden Sie desseniin den nächsten Schritten (die Beispiele verwenden0). - Führen Sie den Finder nach Navigation oder Neu-Renderings erneut aus. Eine leere Liste in einer Single-Page-App bedeutet normalerweise, dass der Editor noch nicht eingehängt ist: warten und wiederholen.
handle: false→ die Seite hat den Editor ohneace.edit()erstellt (keinel.env) oder Sie befinden sich in einem isolierten Kontext: verwenden Sie Schritt 5. Rufen Sie niemalsace.edit(el)auf, um einen Handle zu erhalten – bei einem Element ohneenverstellt dies einen zweiten Editor über dem der Seite.
2. Inspizieren und lesen
const editor = window.__aceEditors[0].env.editor; // Index aus dem Finder
({
mode: editor.session.getMode().$id, // z. B. ace/mode/json (ace/mode/text, während ein Modus noch lädt)
readOnly: editor.getReadOnly(),
lines: editor.session.getLength(),
indent: JSON.stringify(editor.session.getTabString()),
cursor: editor.getCursorPosition(), // { row, column }, 0-basiert
selection: editor.getSelectedText(),
text: editor.getValue(),
})
Für sehr große Dokumente lesen Sie Ausschnitte mit editor.session.getLines(firstRow, lastRow) (0-basiert, inklusive).
3. Das gesamte Dokument ersetzen
const editor = window.__aceEditors[0].env.editor;
const newText = '...'; // der vollständige neue Inhalt
editor.session.getUndoManager().startNewGroup?.(); // diese Änderung als eigenen Rückgängig-Schritt behalten
editor.setValue(newText, -1); // -1 setzt den Cursor an den Anfang; löst 'change' aus, sodass die App den Status aktualisiert
editor.getValue() === newText
Generieren Sie strukturierte Inhalte aus Daten, anstatt sie von Hand zu schreiben: JSON.stringify(data, null, editor.session.getTabString()) entspricht der Einrückung des Editors.
4. Gezielte Bearbeitungen
const editor = window.__aceEditors[0].env.editor;
// erste Übereinstimmung; Optionen: wrap, caseSensitive, wholeWord, regExp, backwards, range
if (editor.find('oldValue', { wrap: true, caseSensitive: true })) editor.replace('newValue');
// jede Übereinstimmung, gibt die Anzahl zurück; mit regExp funktionieren $1-Rückverweise
const replaced = editor.replaceAll('version-$1', { needle: 'v(\\d+)', regExp: true });
// nach Position: Zeilen und Spalten sind 0-basiert, Spalten jenseits des Zeilenendes werden abgeschnitten
editor.session.replace({ start: { row: 2, column: 0 }, end: { row: 2, column: Infinity } }, 'ganze neue Zeile 3');
editor.session.insert({ row: editor.session.getLength(), column: 0 }, '\\nangehängte Zeile');
({ replaced, text: editor.getValue() })
Mehr – Cursor, Auswahl, Faltung, Scrollen, Befehle, Ereignisse: references/ace-api.md.
5. Kein Handle? In das versteckte Textarea einfügen
const el = window.__aceEditors[0]; // funktioniert auch mit handle: false
const newText = '...';
const input = el.querySelector('textarea.ace_text-input');
const mac = /Mac|iPhone|iPad/.test(navigator.platform);
input.focus();
// alles auswählen: Ace bindet Strg+A oder Cmd+A auf Apple-Plattformen
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 }));
'eingefügt'
- Eingefügter Text wird wörtlich eingefügt: keine automatische Paarung, keine automatische Einrückung. Schreibgeschützte Editoren ignorieren es.
- Alternative, wenn das Textarea fokussiert und alles ausgewählt ist:
document.execCommand('insertText', false, newText). - Ohne JavaScript: Den Text in die Zwischenablage des Betriebssystems legen, in den Editor klicken,
Strg+A(Cmd+Aauf macOS) drücken, dannStrg+V(Cmd+V). - Ohne Handle können Sie nur gerenderte Zeilen lesen (
.ace_text-layer .ace_line, nach dem nächsten Frame). Für den vollständigen Text alles auswählen und über die Betriebssystem-Zwischenablage kopieren.
6. Wenn Sie wirklich tippen müssen
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 // nach dem Tippen wiederherstellen: editor.setOptions(window.__aceSavedOptions)
Tippen, dann die Optionen wiederherstellen. Bevorzugen Sie einzeilige Inhalte und