Communityコーディング&開発github.com

omrehbein/ace-editor

Webページを操作するAIエージェントが、Aceコードエディタ(クラウドコンソールのポリシーエディタ、Cloud9、react-aceなど)内のテキストの検索、読み取り、置換、検証を行うために使用します。また、コードエディタへのキー入力が文字化けする場合や、DOM上やスクリーンショットでドキュメントの一部しか見えない場合、エディタの構文エラーを確認する場合にも役立ちます。

ace-editor とは?

このスキルは、Aceエディタ(コード、JSON、YAML、SQLエディタ)を操作するAIエージェント向けのツールです。Aceエディタは画面上の行のみをレンダリングし、隠しテキストエリアを介してキー入力を受け取り、自動で引用符や括弧を閉じたり、自動インデントを行ったりします。そのため、単純なキー入力による操作では文字化けや重複インデントが発生する可能性があります。 このスキルでは、ページ内でJavaScriptを実行してエディタのAPIを介して読み書きを行うことを推奨します。具体的には、エディタの検索、全ドキュメントの置換、対象を絞った編集、ハンドルがない場合の隠しテキストエリアへのペースト、やむを得ないキー入力、構文エラーの取得など、6つのステップで操作をガイドします。 対応プラットフォームとしては、AWSポリシーエディタなどのクラウドコンソール、Cloudscape/awsui、Cloud9スタイルのIDE、react-ace、管理パネルなど、Aceエディタを使用するあらゆるWebページが対象です。また、ブラウザ拡張機能のコンテンツスクリプトなど、ページのメインワールドから分離された環境で正しく動作させるための注意点も含まれています。

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

Installed? Explore more コーディング&開発 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Aceエディタの自動操作

Aceは画面上の行のみをレンダリングし、隠し textarea を介してキー入力を受け取ります。このtextareaは引用符/括弧を自動的に閉じ、新しい行を自動インデントします。デフォルトのAce JSONエディタで {、Enter、"a": [1]、Enter、} と入力すると、以下のようになります:

{
        "a": [1]
}
}

そのため:

  • エディタAPIを介して読み書きする には、ページ内でJavaScriptを実行します。.ace_line 要素やスクリーンショットからコンテンツをスクレイピングしたり、コードをキー入力で1つずつ入力したりしないでください。
  • ページのメインワールドで実行します。 ページが要素に設定したプロパティ(例: el.env)は、ブラウザ拡張機能のコンテンツスクリプトなどの分離されたワールドからは見えません。ツールのメインワールドオプション、またはステップ5のフォールバックを使用してください。
  • 読み取り専用エディタを尊重します。 APIは引き続き変更できますが、アプリはそれを想定していません。ユーザーが要求した場合にのみ行ってください。
  • エディタを変更しても保存されません。 [保存] / [送信] / [適用] をクリックする前にユーザーに確認してください。

以下のスニペットはプレーンなページのJavaScriptで、最後の式が結果となります。ステップ7では await を使用します。ツールにトップレベルの await がない場合は、スニペットを非同期関数でラップしてください(例: Playwrightの page.evaluate(async () => { ... }))。

1. エディタを見つける

Aceのレンダラーは、すべてのバージョンですべてのエディタコンテナに ace_editor クラスを追加するため、セレクタは安定しています。異なるのは、エディタがどこにあるか(メインドキュメント、開いているシャドウルート、同一オリジンのiframe)、どの要素が実際のエディタか(オートコンプリートのポップアップもAceエディタです)、インスタンスにアクセスできるかどうかです。動的に検索します:

(() => {
  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');
  // オートコンプリートポップアップもAceエディタです: スキップします
  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: 'このページからはアクセスできません: フレームのURLを直接開いてください' };
    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),
    };
  });
})()
  • label、id、mode、firstLine、visible でターゲットを選択します。次のステップではその i を使用します(例では 0 を使用)。
  • ナビゲーションや再レンダリング後は、ファインダーを再実行してください。シングルページアプリケーションで空のリストが返された場合、通常はエディタがまだマウントされていないことを意味します: 待機して再試行してください。
  • handle: false → ページが ace.edit() を使用せずにエディタを構築した(el.env がない)、または分離されたワールドにいる場合: ステップ5を使用してください。ハンドルを取得するために ace.edit(el) を決して呼び出さないでください — env のない要素に対しては、ページのエディタの上に2つ目のエディタを作成してしまいます。

2. 検査と読み取り

const editor = window.__aceEditors[0].env.editor; // ファインダーからのインデックス
({
  mode: editor.session.getMode().$id, // 例: ace/mode/json (モードがまだロード中の場合はace/mode/text)
  readOnly: editor.getReadOnly(),
  lines: editor.session.getLength(),
  indent: JSON.stringify(editor.session.getTabString()),
  cursor: editor.getCursorPosition(), // { row, column }、0ベース
  selection: editor.getSelectedText(),
  text: editor.getValue(),
})

非常に大きなドキュメントの場合は、editor.session.getLines(firstRow, lastRow) でスライスを読み取ります(0ベース、両端を含む)。

3. ドキュメント全体を置換する

const editor = window.__aceEditors[0].env.editor;
const newText = '...'; // 新しい全コンテンツ
editor.session.getUndoManager().startNewGroup?.(); // この変更を独自のアンドゥステップとして保持
editor.setValue(newText, -1); // -1 はカーソルを先頭に置きます; 'change' イベントが発生し、アプリの状態が更新されます
editor.getValue() === newText

構造化されたコンテンツは手書きではなくデータから生成します: JSON.stringify(data, null, editor.session.getTabString()) はエディタのインデントと一致します。

4. 対象を絞った編集

const editor = window.__aceEditors[0].env.editor;
// 最初の一致; オプション: wrap, caseSensitive, wholeWord, regExp, backwards, range
if (editor.find('oldValue', { wrap: true, caseSensitive: true })) editor.replace('newValue');
// すべての一致、カウントを返します; regExpを使用すると、$1の後方参照が機能します
const replaced = editor.replaceAll('version-$1', { needle: 'v(\\d+)', regExp: true });
// 位置による: 行と列は0ベース、行の終わりを超える列はクリップされます
editor.session.replace({ start: { row: 2, column: 0 }, end: { row: 2, column: Infinity } }, '新しい行3全体');
editor.session.insert({ row: editor.session.getLength(), column: 0 }, '\n追加された行');
({ replaced, text: editor.getValue() })

その他 — カーソル、選択、折りたたみ、スクロール、コマンド、イベント: references/ace-api.md を参照してください。

5. ハンドルがない場合? 隠しテキストエリアにペースト

const el = window.__aceEditors[0]; // handle: false でも機能します
const newText = '...';
const input = el.querySelector('textarea.ace_text-input');
const mac = /Mac|iPhone|iPad/.test(navigator.platform);
input.focus();
// すべて選択: AceはCtrl+A(AppleプラットフォームではCmd+A)にバインドされています
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'
  • ペーストされたテキストはそのまま挿入されます: 自動ペアリングも自動インデントも行われません。読み取り専用エディタは無視します。
  • テキストエリアにフォーカスがありすべて選択されている場合の代替: document.execCommand('insertText', false, newText)。
  • JavaScriptを使用しない場合: テキストをOSのクリップボードに置き、エディタ内をクリックし、Ctrl+A(macOSではCmd+A)、次にCtrl+V(Cmd+V)を押します。
  • ハンドルがない場合、レンダリングされた行(.ace_text-layer .ace_line、次のフレーム後)のみを読み取ることができます。全文を取得するには、すべて選択してOSのクリップボードを介してコピーします。

6. どうしてもタイピングする必要がある場合

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 // タイピング後は次のようにしてオプションを復元します: editor.setOptions(window.__aceSavedOptions)

タイピングした後、オプションを復元します。1行のコンテンツを優先し、タイピングの前に Escape を押します。

関連スキル