syntax-highlighting
Add new language grammars, fix highlighting bugs, or add file extension mappings to Zepto's syntax highlighting system.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Add new language grammars, fix highlighting bugs, or add file extension mappings to Zepto's syntax highlighting system.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | syntax-highlighting |
| description | Add new language grammars, fix highlighting bugs, or add file extension mappings to Zepto's syntax highlighting system. |
| argument-hint | <task> (e.g. "add Haskell support", "fix Python decorator highlighting", "add .mjs extension for JavaScript") |
| allowed-tools | Read, Glob, Grep, Bash, Edit, Write, Agent |
Modify Zepto's syntax highlighting system. The user's request: $ARGUMENTS
Determine which task type this is, then follow the corresponding workflow below.
Create lib/Zepto/Syntax/YourLanguage.pm. Use this template:
package Zepto::Syntax::YourLanguage;
# =============================================================================
# YourLanguage Syntax Grammar
# =============================================================================
use parent 'Zepto::Syntax::Base';
use Zepto::Syntax::Base; # Import TOKEN_*, STATE_*, and _token()
use strict;
use warnings;
sub line_comment_prefix { '#' } # or '//' or undef
my $KEYWORDS = qr/\b(?:
keyword1 | keyword2 | keyword3
)\b/x;
my $TYPES = qr/\b(?:
type1 | type2
)\b/x;
my $BUILTINS = qr/\b(?:
builtin1 | builtin2
)\b/x;
sub keyword_list {
return [qw(keyword1 keyword2 keyword3 type1 type2 builtin1 builtin2)];
}
sub tokenize {
my ($self, $line, $state) = @_;
my @tokens;
my $pos = 0;
my $len = length($line);
# Handle multi-line state continuations FIRST (block comments, heredocs, etc.)
# if ($state == STATE_COMMENT_BLOCK) { ... }
while ($pos < $len) {
my $rest = substr($line, $pos);
# Skip whitespace
if ($rest =~ /^(\s+)/) { $pos += length($1); next; }
# Line comments
# if ($rest =~ m{^(//.*|#.*)}) {
# push @tokens, _token($pos, $pos + length($1), TOKEN_COMMENT);
# last;
# }
# Strings
# if ($rest =~ /^("(?:[^"\\]|\\.)*")/) { ... TOKEN_STRING ... }
# if ($rest =~ /^('(?:[^'\\]|\\.)*')/) { ... TOKEN_STRING ... }
# Keywords, types, builtins
if ($rest =~ /^($KEYWORDS)/) {
push @tokens, _token($pos, $pos + length($1), TOKEN_KEYWORD);
$pos += length($1);
next;
}
if ($rest =~ /^($TYPES)/) {
push @tokens, _token($pos, $pos + length($1), TOKEN_TYPE);
$pos += length($1);
next;
}
if ($rest =~ /^($BUILTINS)(?=\s*\()/) {
push @tokens, _token($pos, $pos + length($1), TOKEN_FUNCTION);
$pos += length($1);
next;
}
# Numbers
if ($rest =~ /^(0x[0-9a-fA-F]+|0b[01]+|\d+\.?\d*(?:e[+-]?\d+)?)/) {
push @tokens, _token($pos, $pos + length($1), TOKEN_NUMBER);
$pos += length($1);
next;
}
# Function calls: word followed by (
if ($rest =~ /^(\w+)(?=\s*\()/) {
push @tokens, _token($pos, $pos + length($1), TOKEN_FUNCTION);
$pos += length($1);
next;
}
$pos++;
}
return (\@tokens, STATE_NORMAL);
}
1;
Available token types (from Zepto::Syntax::Base):
TOKEN_KEYWORD — language keywords (if, while, return, etc.)TOKEN_STRING — string literalsTOKEN_COMMENT — commentsTOKEN_NUMBER — numeric literalsTOKEN_OPERATOR — operators (+, -, ==, flags like -f)TOKEN_FUNCTION — function names and callsTOKEN_TYPE — type names and annotationsTOKEN_VARIABLE — variables ($foo, self)TOKEN_CONSTANT — constants (UPPER_CASE, true, false)TOKEN_REGEX — regex literalsTOKEN_ATTRIBUTE — decorators, annotations (@decorator, #[attr])TOKEN_TAG — HTML/XML tagsTOKEN_PUNCTUATION — brackets, braces, semicolonsTOKEN_ESCAPE — escape sequences in strings (\n, \t)TOKEN_HEADING — headings (for markup languages)TOKEN_LINK — URLs/hyperlinksAvailable multi-line states:
STATE_NORMAL (0), STATE_STRING_DOUBLE (1), STATE_STRING_SINGLE (2), STATE_STRING_TEMPLATE (3), STATE_COMMENT_BLOCK (4), STATE_HEREDOC (5), STATE_POD (6), STATE_STRING_RAW (7)The tokenize() contract:
($self, $line, $state) — one line of text (no trailing newline) and the state from the previous line's end(\@tokens, $end_state) — arrayref of tokens and state for next line_token($start, $end, $type) — start is inclusive, end is exclusive (like substr)Study 2-3 existing grammars in lib/Zepto/Syntax/ for the language family closest to your target. For example, look at Go.pm for C-like languages, Python.pm for indentation-based, Shell.pm for scripting languages.
Edit lib/Zepto/Highlighter.pm and add entries to these maps as appropriate:
%EXTENSION_MAP (line ~77) — map file extensions (lowercase, no dot):
hs => 'Zepto::Syntax::Haskell',
lhs => 'Zepto::Syntax::Haskell',
%FILENAME_MAP (line ~326) — map special filenames (exact match):
'stack.yaml' => 'Zepto::Syntax::YAML',
%SHEBANG_MAP (line ~396) — map shebang interpreter names:
runhaskell => 'Zepto::Syntax::Haskell',
Create tests/samples/yourlanguage_complete.ext with representative code covering ALL language features the grammar should highlight. This is critical — it serves as both a test fixture and a regression guard.
Include examples of:
Look at existing samples in tests/samples/ for the level of thoroughness expected. Aim for 50-100 lines of realistic, varied code.
# Generate the expected tokenization output
perl scripts/regenerate_expected.pl yourlanguage_complete.ext
# Run the sample-based tests
prove -v tests/syntax_samples.t
# Run the full highlighter tests
prove -v tests/highlighter.t
# Check it compiles and bundles
make check && make build
prove -v tests/bundled_syntax.t
This is mandatory. Build and open the sample file in zepto to visually verify highlighting looks correct:
make build
hangon stopall 2>/dev/null
hangon start process --name zepto -- ./zepto tests/samples/yourlanguage_complete.ext
sleep 1
hangon screen zepto
# Scroll through the file to check all sections
hangon keys zepto "ctrl-end"
sleep 0.3
hangon screen zepto
hangon keys zepto "ctrl-q"
hangon stop zepto
Tell the user they can also manually verify with:
./zepto tests/samples/yourlanguage_complete.ext
The grammar is at lib/Zepto/Syntax/LanguageName.pm. Extension-to-grammar mappings are in lib/Zepto/Highlighter.pm in %EXTENSION_MAP (line ~77).
Write a quick Perl one-liner to see exactly what the tokenizer produces for the problematic line:
perl -Ilib -e '
use Zepto::Syntax::LanguageName;
my $h = Zepto::Syntax::LanguageName->new();
my $line = q{paste the problematic line here};
my ($tokens, $state) = $h->tokenize($line, 0);
for my $t (@$tokens) {
my $text = substr($line, $t->{start}, $t->{end} - $t->{start});
print "[$t->{start}-$t->{end}] type=$t->{type} text=|$text|\n";
}
'
This shows exactly which tokens are produced and where the mismatch is.
For multi-line bugs, pass the appropriate state as the second arg to tokenize() (e.g. STATE_COMMENT_BLOCK is 4).
Edit lib/Zepto/Syntax/LanguageName.pm. Common bug patterns:
".*" matches across strings; use "[^"]*" or "(?:[^"\\]|\\.)*"\bif\b prevents matching ifdef as a keyword$pos == 0 || substr($line, $pos - 1, 1) =~ /\s/)# Re-run the one-liner to confirm the fix
# Then regenerate expected output (the fix may change tokenization of the sample)
perl scripts/regenerate_expected.pl languagename_complete.ext
# Run tests
prove -v tests/syntax_samples.t
prove -v tests/highlighter.t
make check && make build
make build
# Write a test file with the problematic code
echo 'paste problematic code here' > /tmp/test_highlight.ext
hangon stopall 2>/dev/null
hangon start process --name zepto -- ./zepto /tmp/test_highlight.ext
sleep 1
hangon screen zepto
hangon keys zepto "ctrl-q"
hangon stop zepto
rm /tmp/test_highlight.ext
Check what grammars exist:
ls lib/Zepto/Syntax/*.pm
Edit lib/Zepto/Highlighter.pm. Add to the appropriate map:
File extension → add to %EXTENSION_MAP (line ~77), grouped with related extensions:
newext => 'Zepto::Syntax::ExistingLanguage',
Special filename (e.g. .eslintrc, Brewfile) → add to %FILENAME_MAP (line ~326):
'.eslintrc' => 'Zepto::Syntax::JSON',
Shebang interpreter → add to %SHEBANG_MAP (line ~396):
newinterp => 'Zepto::Syntax::ExistingLanguage',
make check && make build
# Quick verification — open a file with the new extension
echo 'some code' > /tmp/test.newext
hangon stopall 2>/dev/null
hangon start process --name zepto -- ./zepto /tmp/test.newext
sleep 1
hangon screen zepto
# Verify the language name shows in status bar or that highlighting is active
hangon keys zepto "ctrl-q"
hangon stop zepto
rm /tmp/test.newext
# Run full tests to check nothing broke
prove -v tests/syntax_samples.t
prove -v tests/highlighter.t
| Command | Purpose |
|---|---|
make check | Perl syntax check on all modules |
make build | Bundle into single ./zepto binary |
prove -v tests/syntax_samples.t | Test all sample files against expected output |
prove -v tests/highlighter.t | Unit tests for highlighter and language detection |
prove -v tests/bundled_syntax.t | Verify bundled binary compiles cleanly |
make test | Run ALL tests |
perl scripts/regenerate_expected.pl [file] | Regenerate .expected file from current tokenizer |
| File | Purpose |
|---|---|
lib/Zepto/Syntax/Base.pm | Base class — token types, state constants, _token() helper |
lib/Zepto/Syntax/*.pm | One grammar per language |
lib/Zepto/Highlighter.pm | Extension/filename/shebang maps, orchestrates tokenization |
tests/samples/*_complete.* | Sample source files for each language |
tests/samples/*.expected | Expected tokenization output (auto-generated, checked in) |
scripts/regenerate_expected.pl | Regenerates .expected files from current tokenizer output |
build.pl | Bundles all modules into single binary (auto-discovers Syntax/*.pm) |