<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: brevity1swos</title>
    <description>The latest articles on DEV Community by brevity1swos (@brevity1swos).</description>
    <link>https://dev.to/brevity1swos</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3947757%2F66e87c21-8c75-4943-ad5f-47d98ff34bac.png</url>
      <title>DEV Community: brevity1swos</title>
      <link>https://dev.to/brevity1swos</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/brevity1swos"/>
    <language>en</language>
    <item>
      <title>Building a regex debugger for the terminal in Rust</title>
      <dc:creator>brevity1swos</dc:creator>
      <pubDate>Sat, 23 May 2026 13:59:24 +0000</pubDate>
      <link>https://dev.to/brevity1swos/building-a-regex-debugger-for-the-terminal-in-rust-977</link>
      <guid>https://dev.to/brevity1swos/building-a-regex-debugger-for-the-terminal-in-rust-977</guid>
      <description>&lt;p&gt;&lt;em&gt;Cross-post from: &lt;a href="https://github.com/brevity1swos/rgx" rel="noopener noreferrer"&gt;https://github.com/brevity1swos/rgx&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;I built &lt;a href="https://github.com/brevity1swos/rgx" rel="noopener noreferrer"&gt;rgx&lt;/a&gt;, a terminal regex debugger written in Rust. The v0.12.3 release is out today. Here's what's in it and why I built it the way I did.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;I write regex-heavy code in terminal-centric workflows — SSH sessions, containers, scripts that pipe data around. Every time I needed to debug a pattern, I'd break flow to open a browser, tab over to regex101.com, paste in the pattern and test string, iterate, copy the pattern back, close the tab.&lt;/p&gt;

&lt;p&gt;The gap isn't just convenience. regex101.com uses PCRE2, JavaScript, or Python regex engines. When you're writing Rust code, you're running the &lt;code&gt;regex&lt;/code&gt; crate — which has no lookaround, different Unicode semantics, and different performance characteristics. Testing in the wrong engine gives you false confidence.&lt;/p&gt;

&lt;p&gt;I wanted something that worked in the terminal, stayed in my workflow, and ran against the actual engines my code uses.&lt;/p&gt;

&lt;h2&gt;
  
  
  What rgx does
&lt;/h2&gt;

&lt;p&gt;The core loop is fast: type a pattern, see matches update in real time with capture group highlighting and a plain-English explanation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rgx &lt;span class="s1"&gt;'\d{3}-\d{3}-\d{4}'&lt;/span&gt;                    &lt;span class="c"&gt;# start with a pattern&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Call 555-123-4567"&lt;/span&gt; | rgx &lt;span class="s1"&gt;'\d+'&lt;/span&gt;        &lt;span class="c"&gt;# pipe stdin as test string&lt;/span&gt;
rgx &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"error 404"&lt;/span&gt; &lt;span class="s1"&gt;'\d+'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt;          &lt;span class="c"&gt;# batch mode in a pipeline&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beyond the basics:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step-through debugger (Ctrl+D, PCRE2)
&lt;/h3&gt;

&lt;p&gt;This is the part I haven't seen elsewhere. Pressing Ctrl+D opens a dual-cursor trace over the PCRE2 engine's execution — one cursor on the pattern, one on the test string, moving together as the engine works through the match. Backtracking steps are marked distinctly. There's a heatmap mode (H) that shows which parts of the pattern were touched most often, which makes catastrophic backtracking obvious at a glance.&lt;/p&gt;

&lt;p&gt;It's the difference between "the pattern doesn't match, I don't know why" and "here is exactly where the engine gave up."&lt;/p&gt;

&lt;h3&gt;
  
  
  Three engines with auto-selection
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rust &lt;code&gt;regex&lt;/code&gt;&lt;/strong&gt; (default) — linear time, no lookaround, no backreferences&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fancy-regex&lt;/strong&gt; — adds lookaround and backreferences, pure Rust&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PCRE2&lt;/strong&gt; — adds possessive quantifiers, recursion, conditionals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you type a pattern that uses lookahead, rgx upgrades automatically to the simplest engine that supports it. The status bar shows the current engine. You can override with Ctrl+E or &lt;code&gt;--engine&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code generation (Ctrl+G)
&lt;/h3&gt;

&lt;p&gt;Once a pattern works, Ctrl+G generates ready-to-paste code for 8 languages: Rust, Python, JavaScript, Go, Java, C#, PHP, Ruby. It handles the escaping, the compile call, the match loop — everything you'd have to look up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate regex from examples (Ctrl+X)
&lt;/h3&gt;

&lt;p&gt;Ctrl+X opens a &lt;a href="https://crates.io/crates/grex" rel="noopener noreferrer"&gt;grex&lt;/a&gt; overlay. Type example strings, one per line, and grex generates a pattern that matches all of them. Tab loads it into the main editor. Useful for formats you don't have the regex memorized for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Live filter mode
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;app.jsonl | rgx filter &lt;span class="nt"&gt;--json&lt;/span&gt; &lt;span class="s1"&gt;'.msg'&lt;/span&gt; &lt;span class="s1"&gt;'(?i)error'&lt;/span&gt;
git diff | rgx filter &lt;span class="s1"&gt;'^\+.*console\.log'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;rgx filter&lt;/code&gt; reads stdin or a file and lets you refine the pattern in a TUI. When you press Enter, matching lines go to stdout. Non-TTY stdout skips the TUI entirely, so it composes in pipelines. The &lt;code&gt;--json &amp;lt;PATH&amp;gt;&lt;/code&gt; flag extracts a specific field from JSONL records before matching — useful when you want to filter by message content without the pattern accidentally matching timestamps or IDs in the raw line.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test suite mode
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# tests/urls.toml&lt;/span&gt;
&lt;span class="py"&gt;pattern&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https?://[^&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;s]+"&lt;/span&gt;
&lt;span class="py"&gt;engine&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"rust"&lt;/span&gt;

&lt;span class="nn"&gt;[[tests]]&lt;/span&gt;
&lt;span class="py"&gt;input&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"visit https://example.com today"&lt;/span&gt;
&lt;span class="py"&gt;should_match&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rgx &lt;span class="nt"&gt;--test&lt;/span&gt; tests/urls.toml   &lt;span class="c"&gt;# exit 0 = all pass, 1 = failures, 2 = error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Regex regressions are easy to introduce and hard to catch without CI assertions. This makes it easy to keep a test suite next to the patterns in your repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation notes
&lt;/h2&gt;

&lt;p&gt;rgx is built on &lt;a href="https://ratatui.rs/" rel="noopener noreferrer"&gt;ratatui&lt;/a&gt; + crossterm for the TUI, with arboard for clipboard, grex for example-to-regex generation, and direct PCRE2 FFI for the step-through debugger.&lt;/p&gt;

&lt;p&gt;The step-through debugger uses PCRE2's callout mechanism — a callback that PCRE2 invokes at each match step. The callback records a &lt;code&gt;DebugStep&lt;/code&gt; (pattern offset, subject offset, backtrack flag) into a &lt;code&gt;Vec&lt;/code&gt;, and the UI replays those steps as you navigate with arrow keys. The heatmap is a simple frequency count over pattern offsets, rendered as a color gradient.&lt;/p&gt;

&lt;p&gt;Auto engine selection works by walking the regex AST before compilation: if the pattern uses lookahead, backreferences, or PCRE2-specific syntax, &lt;code&gt;detect_minimum_engine()&lt;/code&gt; returns the smallest engine that supports those features. The engine only upgrades, never downgrades mid-session.&lt;/p&gt;

&lt;p&gt;The filter subsystem (&lt;code&gt;rgx filter&lt;/code&gt;) is intentionally separate from the main TUI state — different UX, different lifecycle. Non-TTY detection happens at startup: if stdout isn't a terminal, the TUI never launches and matching lines go directly to stdout, which keeps the piping semantics clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo &lt;span class="nb"&gt;install &lt;/span&gt;rgx-cli                    &lt;span class="c"&gt;# crates.io (pure Rust, no PCRE2)&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;rgx-cli &lt;span class="nt"&gt;--features&lt;/span&gt; pcre2-engine  &lt;span class="c"&gt;# with step-through debugger&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;brevity1swos/tap/rgx        &lt;span class="c"&gt;# Homebrew&lt;/span&gt;
yay &lt;span class="nt"&gt;-S&lt;/span&gt; rgx-cli                           &lt;span class="c"&gt;# AUR&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub: &lt;a href="https://github.com/brevity1swos/rgx" rel="noopener noreferrer"&gt;https://github.com/brevity1swos/rgx&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Questions or issues: &lt;a href="https://github.com/brevity1swos/rgx/issues" rel="noopener noreferrer"&gt;https://github.com/brevity1swos/rgx/issues&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>cli</category>
      <category>terminal</category>
      <category>regex</category>
    </item>
  </channel>
</rss>
