웹페이지를 정규식으로 검색하고 강조하는 Chrome 확장(Manifest V3)이다. 엔진은 JS RegExp이고, 부족해지면 Onigmo(WASM)로 바꿀 수 있게 엔진을 한 곳에 모아 두었다.
Chrome 웹 스토어 심사를 통과하면 이 자리에 스토어 링크를 단다. 그 전에는 아래처럼 개발판으로 설치한다.
chrome://extensions를 입력한다.Alt+Shift+F를 누른다.| 동작 | 키 |
|---|---|
| 검색 / 다음 일치 | Enter 또는 ▼ |
| 이전 일치 | Shift+Enter 또는 ▲ |
| 강조 지우기 | 《지우기》 (《이동해도 계속 검색》도 함께 끈다) |
| 즐겨찾기 저장/빼기 | 입력창 옆 ☆ |
| 저장한 패턴 쓰기 | 팝업 아래 《즐겨찾기》·《최근 사용》 목록에서 누르기 |
. 줄바꿈 포함)을 함께 저장하며, 목록의 i·s 표지가 그 옵션이다.《이동해도 계속 검색 (이 탭)》을 켜면 그 탭에서 다른 페이지로 옮겨 가거나 새로고침해도 같은 검색어로 다시 검색한다. 처음 켤 때 Chrome이 《모든 사이트의 데이터 읽기·변경》 권한을 묻는다. 기본 권한(activeTab)은 아이콘을 누른 그 페이지에만 유효해서, 이동한 페이지에 들어가려면 이 권한이 있어야 하기 때문이다. 켠 탭에서만 동작하고, 브라우저를 닫거나 《지우기》를 누르면 꺼진다.
^ $는 줄 단위다(EmEditor와 같다). .은 줄바꿈과 일치하지 않고, 《. 이 줄바꿈 포함》을 켜면 일치한다.v 플래그)는 항상 켜져 있다. \p{Script=Han}, \p{Script=Hangul} 같은 속성 검색을 쓸 수 있다.\p{hangul}·\p{Han}·\p{^Han}·\p{lu}·\p{Digit}처럼 쓰면 JS가 받는 \p{Script=Hangul}·\P{Script=Han}·\p{Lu}·\p{Nd}로 바꿔 검색하고, 바꾼 내용을 팝업에 적는다. 이름은 대소문자·밑줄·공백을 가리지 않는다. 모르는 이름이면 한국어로 알린다.v 모드에서는 문자 집합 안의 ( ) [ { / | - 같은 기호를 이스케이프해야 한다. [(]처럼 쓰면 u 모드로 내려가 검색하고, 팝업에 그 사실을 알린다.웹페이지의 텍스트 노드를 그대로 이으면 두 가지가 틀어진다. <p> 두 개는 줄바꿈 없이 한 줄로 붙고, HTML 소스의 줄바꿈은 화면상 한 줄인 문단을 여러 줄로 쪼갠다. 그래서 src/core.js의 linearize는 브라우저가 화면에 그리는 방식을 따라 텍스트를 잇는다.
display가 block·list-item·table-row 등)의 경계와 <br>에 가상 \n을 넣는다.\t를 넣는다. 名\t字로 검색할 수 있다.<pre>(white-space: pre 계열)는 원문 그대로 두고, pre-line은 줄바꿈만 남긴다.script·style·숨긴 요소(display:none 등)는 건너뛴다. 보이지 않는 일치를 세지 않기 위해서다.이어 붙인 문자열의 글자마다 「어느 텍스트 노드의 몇 번째 글자인가」를 기록해 둔다(segs[].offs). 일치 위치를 DOM Range로 되돌릴 때 이 표를 쓰고, 가상 글자는 범위의 앞뒤에서 잘라 낸다.
브라우저는 페이지가 UTF-8이든 EUC-KR이든 DOM을 만들 때 이미 디코딩한다. 그래서 확장이 보는 문자열·RegExp 결과·Range 오프셋은 모두 UTF-16 코드 단위이고, 인코딩 변환이 끼어들 자리가 없다. Onigmo로 바꿀 때도 ONIG_ENCODING_UTF16_LE로 빌드하면 이 성질이 유지된다. UTF-8로 빌드하면 바이트 오프셋을 변환해야 하므로 그렇게 하지 않는다.
확장 B 이후 한자(𠀀 U+20000 등)는 UTF-16 두 단위(서로게이트 쌍)다. 유니코드 모드가 없으면 .이 반쪽 글자와 일치하고, [𠀀-𪛟] 같은 범위식은 문법 오류가 난다. 빈 일치 뒤에 다음 위치로 넘어갈 때도 코드 포인트 단위로 움직여야 서로게이트 중간에서 멈추지 않는다(matchAll이 그렇게 한다).
페이지 텍스트도 검색어도 NFC·NFD로 바꾸지 않는다. NFC는 CJK 호환 한자(U+F900–FAFF)를 통합 한자로 바꾼다. 예를 들어 독음 구분용 樂(U+F914)이 樂(U+6A02)이 되는데, 한국 자료에서는 이 구분이 의미를 가질 때가 많다. 정규화하면 글자 수도 바뀌어 오프셋 표가 하나 더 필요하다.
그 대가로, 호환 한자로 적힌 페이지를 통합 한자로 검색하면 걸리지 않는다. NFD로 적힌 한글도 완성형 검색어로는 걸리지 않는다. 둘을 같이 찾아야 할 일이 생기면 「접어 찾기」 옵션을 따로 넣는다.
<mark>를 끼워 넣지 않고 CSS Custom Highlight API(CSS.highlights)로 칠한다. 페이지 구조와 그 페이지의 스크립트를 건드리지 않고, 여러 노드에 걸친 일치도 Range 하나로 칠할 수 있다.
JS는 문자 체계 속성을 \p{Script=Hangul}처럼 Script=를 붙이고 대소문자까지 정확히 써야 한다. \p{Hangul}·\p{han}은 문법 오류다. EmEditor에서 쓰던 \p{hangul}+\(\p{han}+\)가 그대로 오류가 난 것이 계기다(2026-10-16, 계약 C-003). 그래서 compile 앞에 translateProps를 두어 이름을 바꾼다.
\\p{han}(백슬래시 문자 + p{han})은 속성이 아니므로 건드리지 않는다.Digit→Nd, Space→White_Space, Cntrl→Cc, XDigit→ASCII_Hex_Digit)만 바꾼다. Alnum·Word·Punct처럼 여러 속성의 합집합이 필요한 이름은 문자 집합 안팎에서 쓰는 법이 달라 바꾸지 않는다.주소만 바뀌고 문서는 그대로인 사이트(SPA)나 내용을 통째로 갈아 끼우는 페이지에서는, 칠해 둔 범위가 이미 사라진 글자를 가리킨다. 처음 판에서는 팝업이 이 낡은 상태를 보고 《이미 검색했다》고 여겨 Enter를 새 검색 대신 낡은 결과 사이 이동으로 처리했다. 그래서 옵션을 껐다 켜야 검색되는 버그가 있었다(2026-10-16 사용자 지적).
엔진이 하는 일은 compile(패턴 → 검색기)과 findAll(문자열 → [시작, 끝) 목록, UTF-16) 두 함수뿐이다. Onigmo로 바꿀 때는 이 둘만 WASM 호출로 바꾸면 된다. 기본 동작이 다르다는 점에 주의한다. Ruby/Onigmo는 ^$가 항상 줄 단위이고, .이 줄바꿈과 일치하게 하는 플래그가 m이다(JS는 s).
_locales에 두고, 한국어는 코드에도 남긴다스토어 등록정보와 화면을 한국어·영어 둘로 낸다(브라우저 언어가 한국어면 한국어, 그 밖에는 영어 — default_locale: en). 문구는 RPSt("열쇠", "한국어", …)로 부른다(src/i18n.js). 확장 안에서는 chrome.i18n이 _locales/<언어>/messages.json의 번역을 주고, 번역이 없으면 둘째 인자의 한국어를 그대로 쓴다.
한국어를 코드에도 남기는 까닭은 core.js가 Node 시험에서 chrome 없이 돌기 때문이다. 시험은 지금처럼 한국어 문구를 본다. 그 대가로 같은 한국어가 두 곳에 있게 되므로, test/i18n.test.js가 코드·popup.html의 한국어와 _locales/ko를 한 글자씩 대조한다. en에 빠진 열쇠가 있는지도 같이 본다.
scripts/build-zip.mjs의 RUNTIME 목록에 든 파일만 zip에 넣는다. E2E도 같은 목록으로 확장 사본을 만든다. 시험 사본과 스토어 판의 파일 구성이 어긋나면 「시험은 통과했는데 스토어 판은 깨지는」 일이 생기기 때문이다. zip 안의 시각은 1980-01-01로 고정했다. 내용이 같으면 같은 zip이 나온다.
package.json과 manifest.json의 판 번호를 같이 올린다(C-005가 대조한다).npm test, node scripts/e2e-capture.mjs(필요하면 RPS_LANG=en으로도 돌린다).node scripts/build-zip.mjs → dist/regex-page-search-<판>.zipnode scripts/store-assets.mjs → store/assets/(스크린숏 1280×800, 홍보 타일 440×280)store/listing.md에서 붙여 넣는다. 개인정보처리방침은 PRIVACY.md이고, GitHub Pages에서 PRIVACY.html로 열린다.py -3 scripts/make-icons.py를 돌린다(Pillow 필요).(a+)+$ 같은 패턴은 그 탭을 멈출 수 있다. Onigmo로 바꾸면 검색 상한을 걸 수 있다.chrome:// 페이지, Chrome 웹 스토어, 내장 PDF 뷰어에서는 동작하지 않는다(확장이 주입될 수 없는 곳).npm test — test/*.test.js 전부: core.js 단위 테스트(가짜 노드 트리), Onigmo 식 속성 이름, 즐겨찾기·최근 사용, 문구 대조(i18n), 배포 zip(package).node scripts/e2e-capture.mjs — Playwright Chromium에 확장을 로드해 test/fixtures/page.html을 팝업으로 검색하고, 기대 개수와 대조한 뒤 캡처한다. 정식 Chrome 137판부터 --load-extension이 막혀서 Playwright 캐시의 Chromium을 쓴다(CHROMIUM_PATH로 지정 가능). 시험 사본에만 host_permissions를 더한다. activeTab은 사람이 아이콘을 눌러야 생기는 권한이라 자동화로 줄 수 없기 때문이다.node --test test/package.test.js — 배포 zip을 실제로 만들어 연 뒤 검사한다. 아이콘 크기, manifest가 참조하는 파일, 개발 파일이 섞였는지, 판 번호, 원격 코드를 본다.harness.contract.json에 있다(C-001 단위 테스트, C-002 E2E 캡처, C-003 Onigmo 식 속성 이름, C-004 즐겨찾기·최근 사용, C-005 배포 zip).MIT 라이선스(LICENSE).