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) |