Community코딩 & 개발github.com

omrehbein/ace-editor

웹 페이지에서 Ace 코드 편집기(클라우드 콘솔의 JSON/YAML/SQL 편집기, Cloudscape/awsui, Cloud9 스타일 IDE, react-ace, 관리자 패널 등) 내부의 텍스트를 찾고, 읽고, 바꾸거나 검증해야 할 때 사용합니다. 또한 웹 코드 편집기에 입력한 텍스트가 깨지거나(따옴표/괄호 누락, 중복 들여쓰기), DOM이나 스크린샷에 문서의 일부만 보일 때, 또는 편집기의 구문 오류를 확인해야 할 때 유용합니다.

ace-editor란 무엇인가요?

이 스킬은 Ace 코드 편집기가 포함된 웹 페이지를 자동화하는 AI 에이전트를 위해 설계되었습니다. Ace 편집기는 화면에 보이는 행만 렌더링하고, 숨겨진 textarea를 통해 키 입력을 받으며 따옴표/괄호 자동 닫기와 자동 들여쓰기를 수행합니다. 따라서 단순히 DOM 요소나 스크린샷을 스크래핑하거나 키를 하나씩 입력하는 방식은 부정확하고 오류가 발생하기 쉽습니다. 이 스킬은 JavaScript 코드 조각을 제공하여 편집기 API를 통해 직접 문서를 읽고 쓰고, 에이전트가 정확한 텍스트 작업(전체 문서 대체, 대상 편집, 커서 조작 등)을 실행할 수 있도록 합니다. 또한 편집기 인스턴스에 직접 접근할 수 없는 경우(예: 브라우저 확장 프로그램의 격리된 세계)를 위한 대체 방법(숨겨진 textarea에 붙여넣기)도 포함되어 있습니다. 주요 사용 사례로는 AWS 정책 편집기, Cloud9 스타일 IDE, react-ace 기반 관리 패널, Cloudscape/awsui 컴포넌트 등이 있습니다.

지원 대상~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를 통해 키 입력을 받으며 따옴표/괄호를 자동으로 닫고 새 줄을 자동으로 들여씁니다. 기본 Ace JSON 편집기에 {, Enter, "a": [1], Enter, }를 입력하면 다음과 같이 출력됩니다:

{
        "a": [1]
}
}

따라서:

  • 페이지에서 JavaScript를 실행하여 편집기 API를 통해 읽고 쓰세요. .ace_line 요소나 스크린샷을 스크래핑하지 말고, 코드를 키 하나씩 입력하지 마세요.
  • 페이지의 메인 세계에서 실행하세요. 페이지가 요소에 설정한 속성(예: el.env)은 격리된 세계(예: 브라우저 확장 프로그램 콘텐츠 스크립트)에서 보이지 않습니다. 도구의 메인 세계 옵션을 사용하거나 5단계의 대체 방법을 사용하세요.
  • 읽기 전용 편집기를 존중하세요. API로 여전히 변경할 수 있지만, 앱은 이를 예상하지 않습니다 — 사용자가 요청할 때만 변경하세요.
  • 편집기를 변경하는 것은 저장이 아닙니다. 저장/제출/적용을 클릭하기 전에 사용자에게 확인하세요.

아래 코드 조각은 일반 페이지 JavaScript이며, 마지막 표현식이 결과입니다. 7단계는 await를 사용합니다. 도구에 최상위 await가 없으면 코드 조각을 async 함수로 감싸세요(예: 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} > 교차 출처 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: '이 페이지에서 접근 불가: iframe 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단계를 사용하세요. el.env가 없는 요소에서 ace.edit(el)을 호출하여 핸들을 얻지 마세요 — 페이지의 편집기 위에 두 번째 편집기를 생성합니다.

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-based
  selection: editor.getSelectedText(),
  text: editor.getValue(),
})

매우 큰 문서의 경우 editor.session.getLines(firstRow, lastRow)로 조각을 읽으세요(0-based, 포함).

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-based, 열이 줄 끝을 넘으면 잘림
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() })

더 많은 내용 — 커서, 선택, 접기, 스크롤, 명령, 이벤트: references/ace-api.md.

5. 핸들이 없나요? 숨겨진 textarea에 붙여넣기

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'
  • 붙여넣은 텍스트는 그대로 삽입됩니다: 자동 페어링이나 자동 들여쓰기 없음. 읽기 전용 편집기는 무시합니다.
  • textarea에 포커스가 있고 모두 선택된 상태의 대안: document.execCommand('insertText', false, newText).
  • JavaScript 없이: 텍스트를 OS 클립보드에 복사하고, 편집기 내부를 클릭한 다음 Ctrl+A(Cmd+A macOS)를 누르고, 이어서 Ctrl+V(Cmd+V macOS)를 누르세요.
  • 핸들 없이 렌더링된 행만 읽을 수 있습니다(.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)

입력한 후 옵션을 복원하세요. 한 줄 콘텐츠를 선호하고, 입력하기 전에 Escape를 누르세요.

관련 스킬