This repository has moved to GitHub.
The project is now maintained at AdGuardSoftwareLimited/ext-rules-editor. Please use GitHub for the latest code, issues, and pull requests.
A browser-based library for editing and tokenizing AdGuard filter rules.
It provides a CodeMirror 6 text editor with TextMate syntax highlighting
(via WebAssembly Oniguruma backed by vscode-textmate + vscode-oniguruma)
and a WASM-backed tokenizer for custom rule rendering.
vscode-oniguruma and CodeMirror/Lezer packages are peer dependencies —
your project must install them separately. vscode-oniguruma is required
so the WASM binary is available in your bundle; the CodeMirror packages
are required because the library returns a live EditorView instance.
CodeMirror's @codemirror/state relies on instanceof checks for
extensions and facets — if your bundler duplicates @codemirror/state
(the library bundles one copy and your app another), these checks will
fail. Externalizing the peer deps ensures a single shared copy.`
pnpm add @adguard/rules-editor vscode-oniguruma @codemirror/state @codemirror/view @codemirror/language @codemirror/commands @codemirror/search @lezer/highlight- Editor — a CodeMirror 6 instance with adblock syntax highlighting
(embedded JavaScript regions are scoped as
source.jsbut use a minimal placeholder grammar to keep the bundle small), powered by WASM-based Oniguruma regex fromvscode-oniguruma. - Tokenizer — splits a rule into highlighted segments using WASM; highest precision.
- Token — an enum of token types aligned with the CodeMirror 6 /
@lezer/highlighttag taxonomy (keyword,operator,string,comment,regexp, etc.). - inspectLine — returns per-token segments with full TextMate scope stacks for debugging and tests.
import { initEditor } from '@adguard/rules-editor';
// Let your bundler (rspack / Vite) emit the asset and compute the URL.
const wasm = new URL('vscode-oniguruma/release/onig.wasm', import.meta.url);
const textarea = document.getElementById('textarea');
const view = await initEditor(textarea, wasm, {
hotkeys: { mode: 'mac' },
});
view.dispatch({ changes: { from: 0, insert: '||example.org^' } });Tokens are highlighted using standard @lezer/highlight tags, so any
CodeMirror 6 theme works out of the box. By default the editor applies
CodeMirror's defaultHighlightStyle; pass your own theme (or
HighlightStyle) via conf.extensions to override it:
import { oneDark } from '@codemirror/theme-one-dark';
const view = await initEditor(textarea, wasm, {
hotkeys: { mode: 'mac' },
extensions: [oneDark],
});By default the editor uses full TextMate highlighting backed by Oniguruma
WASM. When you do not need syntax highlighting, choose
highlight: 'none' — it never loads WASM, so the wasm argument can be
undefined:
// No highlighting at all, no WASM:
const plain = await initEditor(textarea, undefined, {
hotkeys: { mode: 'mac' },
highlight: 'none',
});initEditor still returns a Promise<EditorView> for every strategy, so
existing await initEditor(...) call sites are unaffected.
import { getTokenizer } from '@adguard/rules-editor';
// WASM-based (async init, highest precision)
const wasm = new URL('vscode-oniguruma/release/onig.wasm', import.meta.url);
const tokenize = await getTokenizer(wasm);
const tokens = tokenize('||example.org^$important');import { inspectLine } from '@adguard/rules-editor';
const wasm = new URL('vscode-oniguruma/release/onig.wasm', import.meta.url);
const segments = await inspectLine(wasm, '||example.org^$important');
// segments: TokenSegment[] — each with text, startIndex, endIndex,
// scopes (full TextMate scope stack), and token (resolved class)async function initEditor(
element: HTMLTextAreaElement,
wasm: WasmSource,
conf: InitEditorConfig,
): Promise<EditorView>| Parameter | Description |
|---|---|
element |
Textarea element to attach the editor to |
wasm |
WASM source — URL/string (fetched), Response, ArrayBuffer, or a Promise/thunk of these. Required for highlight: 'full' (the default); pass undefined when using 'none' |
conf.hotkeys.mode |
OS mode for hotkey mapping ('windows' or 'mac') |
conf.hotkeys.toggleRule |
Callback for Ctrl/Cmd+/ (toggle rule breakpoint) |
conf.hotkeys.onSave |
Callback for Ctrl/Cmd+S |
conf.hotkeys.markerColor |
CSS color for the breakpoint marker |
conf.hotkeys.markerHTML |
Custom innerHTML for the breakpoint marker |
conf.withBreakpoints |
Enable breakpoint gutter |
conf.onChange |
Called after each document change |
conf.extensions |
Extra CodeMirror 6 extensions appended last |
conf.highlight |
Highlight strategy: 'full' (WASM TextMate, default) or 'none' (no WASM) |
Returns a CodeMirror.EditorView instance. See the CodeMirror 6 docs for
events and
keymaps.
async function getTokenizer(
wasm: WasmSource,
): Promise<(rule: string) => RuleTokens>| Parameter | Description |
|---|---|
wasm |
WASM source — URL/string (fetched), Response, ArrayBuffer, or a Promise/thunk of these |
Returns a function that accepts a rule string and returns RuleTokens
({ str: string, token: Token | null }[]).
async function inspectLine(
wasm: WasmSource,
line: string,
scopeName?: string,
): Promise<TokenSegment[]>| Parameter | Description |
|---|---|
wasm |
WASM source — URL/string (fetched), Response, ArrayBuffer, or a Promise/thunk of these |
line |
The line of filter rule text to tokenize |
scopeName |
Grammar scope; defaults to text.adblock |
Returns a contiguous, gap-free array of TokenSegment objects covering
the input line. Each segment has text, startIndex, endIndex,
scopes (full scope stack), and token (resolved class or null).
For read-only views (e.g. a virtualized list of rule rows) you can render a
token list to colorized HTML whose classes match the editor — without creating
a CodeMirror editor per row. Set white-space: pre on the container to
preserve spacing.
function renderTokensToHtml(
tokens: RuleTokens,
options?: RenderOptions,
): string| Parameter | Description |
|---|---|
tokens |
Token list from getTokenizer |
options.highlightStyle |
HighlightStyle or array; defaults to defaultHighlightStyle |
Returns an HTML string safe for innerHTML/dangerouslySetInnerHTML.
async function getHtmlRenderer(
wasm: WasmSource,
options?: RenderOptions,
): Promise<(rule: string, search?: SearchHighlightOptions) => string>| Parameter | Description |
|---|---|
wasm |
WASM source — URL/string/Response/ArrayBuffer/Promise/thunk |
options.highlightStyle |
Same as renderTokensToHtml |
Returns an async factory that initializes the grammar once, then returns a
synchronous (rule, search?) => html function for full-precision (WASM)
highlighting reusable across many rows.
The returned function accepts an optional search argument to highlight a
search term within the rule:
interface SearchHighlightOptions {
searchTerm?: string; // plain-text, case-insensitive
searchClassName?: string; // CSS class on each matched chunk
}When searchTerm is a non-empty string, every case-insensitive occurrence —
including matches that span multiple tokens — is wrapped in a <span>
carrying searchClassName. Omitting search (or passing an empty term)
leaves the output identical to plain rendering. Both the matched text and
searchClassName are HTML-escaped.
const render = await getHtmlRenderer(wasm);
// Plain rendering — unchanged from previous versions:
const html = render('||example.org^');
// With search highlighting:
const highlighted = render('||example.org^', {
searchTerm: 'example',
searchClassName: 'search-hit',
});function mountHighlightStyle(
highlightStyle?: HighlightStyle,
root?: Document | ShadowRoot,
): void| Parameter | Description |
|---|---|
highlightStyle |
Style whose CSS to mount; defaults to defaultHighlightStyle |
root |
Target document or shadow root; defaults to document (no-ops in non-browser envs) |
Mounts a HighlightStyle's CSS so emitted classes are colorized without an
editor. Call once; repeated calls are idempotent.
interface RenderOptions {
highlightStyle?: HighlightStyle | HighlightStyle[];
}Pass a custom HighlightStyle (e.g. oneDarkHighlightStyle) to match a
custom editor theme.
| Class | Description |
|---|---|
WasmLoadError |
Thrown when the Oniguruma WASM binary fails to load |
GrammarNotFoundError |
Thrown when a grammar scope has no registration |
| Package | Version |
|---|---|
vscode-oniguruma |
^2.0.1 |
@codemirror/commands |
^6.10.3 |
@codemirror/language |
^6.12.3 |
@codemirror/search |
^6.7.0 |
@codemirror/state |
^6.6.0 |
@codemirror/view |
^6.43.0 |
@lezer/highlight |
^1.2.3 |