Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
723fab0
Initial rewrite to elembic
Dherse Aug 28, 2026
3ff26ee
First working code blocks
Dherse Aug 28, 2026
f52390e
New API
Dherse Aug 28, 2026
24257f2
Working on fixing bugs in codly + updating issue tests
Dherse Aug 28, 2026
8663b9f
Fixed some types
Dherse Sep 14, 2026
33f5f97
Updated Codly highlights & rendering
Dherse Sep 14, 2026
32ef6a6
Various highlight optimizations
Dherse Sep 14, 2026
2efbab2
Updated test
Dherse Sep 14, 2026
da021f9
Updated entire issues test suite
Dherse Sep 14, 2026
2f7aff9
Optimization galore
Dherse Sep 14, 2026
4551ef5
Improved performance & refs, more tests
Dherse Sep 15, 2026
702ddf3
Improved testing (& CI)
Dherse Sep 15, 2026
90f0531
Add skip formatting arrays, corner radii, and auto baselines
Dherse Sep 15, 2026
0ce2dcb
Improved accessibility
Dherse Sep 15, 2026
1485b36
Delete old lib I accidentally commited
Dherse Sep 15, 2026
deadb8e
Fix annotation geometry, outside borders, aliases, and accessibility
Dherse Sep 20, 2026
f40baa2
Improved tests for a few issues
Dherse Sep 20, 2026
08e5425
Add lang icon alignment setting (implements #112)
Dherse Sep 20, 2026
b552292
Initial support for sub-code blocks with different languages
Dherse Sep 20, 2026
e9bb14f
Sublang now support codly features + tests refs
Dherse Sep 20, 2026
c7cfadf
Grammar aware rainbow delimiters (closes #130)
Dherse Sep 20, 2026
63bf3a9
Rainbow benchmark
Dherse Sep 20, 2026
e6f70ba
Added regression test for #133
Dherse Sep 20, 2026
e467be8
Regression test for (#131)
Dherse Sep 20, 2026
8cb67cb
Regression test for #67
Dherse Sep 20, 2026
608c18d
Missing 16th image for issues tests
Dherse Sep 20, 2026
d8829be
Improved smart indentation and implemented indentation guides (implem…
Dherse Sep 20, 2026
4e7a25a
Indent guides bench
Dherse Sep 20, 2026
f9943b2
Implements line break markers (implements #54)
Dherse Sep 20, 2026
5941a0d
Code formatting + CI for fmt (closes #104)
Dherse Sep 20, 2026
17109c8
Improved some performance on smart-indent and line breaking algorithms
Dherse Sep 20, 2026
1795b38
Implemented unnumbered lines (#83), regression test for #96, small op…
Dherse Sep 20, 2026
242c7bd
Regression test for #91
Dherse Sep 20, 2026
3b24719
New `number-set_` and `number-show_` helpers
Dherse Sep 20, 2026
0a2e1ad
Updated args.json (legacy)
Dherse Sep 20, 2026
4973e15
Regression test for #108 dark theme stuff, perf gains
Dherse Sep 20, 2026
22c6512
Format
Dherse Sep 20, 2026
e7b6404
Various performance improvements to default show bodies
Dherse Sep 20, 2026
bcf27b3
Added `width` param to codly to control block width (implements #109)
Dherse Sep 20, 2026
f25c9c6
Improved auto width
Dherse Sep 20, 2026
1a033e6
fmt
Dherse Sep 20, 2026
ce734a5
Support for custom line number col fill (#98)
Dherse Sep 20, 2026
929a779
Removed `codly.enabled` arg, implemented customizable refs (impls #92…
Dherse Sep 20, 2026
61f21be
Deleted the old docs (to be replaced in a second PR)
Dherse Sep 20, 2026
6079822
Fixed justfile for docs fmt + fixed worflow + re-included fonts
Dherse Sep 20, 2026
8155d42
New control of leading spaces & gutters (impl #125)
Dherse Sep 22, 2026
5d8d48f
Added callout rows
Dherse Sep 22, 2026
82a0544
Fully implemented callouts #72
Dherse Sep 22, 2026
6022934
Updated test runner
Dherse Sep 22, 2026
7d54973
Fixed line numbering, added dedicated file feature,
Dherse Sep 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# This raw-code fixture intentionally contains tab-indented Scala.
tests/number-placement/4-longcode.typ whitespace=-space-before-tab
27 changes: 22 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,23 @@ on:
branches: [ main ]

jobs:
formatting:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install Typstyle
uses: baptiste0928/cargo-install@v3.0.0
with:
crate: typstyle
version: '0.15.1'

- name: Check Typst formatting
run: >-
typstyle --check --line-width 100 --indent-width 2
--no-reorder-import-items codly.typ src tests

tests:
runs-on: ubuntu-latest
steps:
Expand All @@ -17,20 +34,20 @@ jobs:
- name: Probe runner package cache
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: cargo
packages: cargo poppler-utils
version: 1.0

- name: Install tytanic from github
- name: Install Tytanic
uses: baptiste0928/cargo-install@v3.0.0
with:
crate: tytanic
version: '0.2.1'
version: '0.4.1'
- name: Setup typst
uses: typst-community/setup-typst@v4
with:
version: 'v0.13.1'
version: 'v0.15.1'
- name: Run test suite
run: tt run --no-fail-fast --font-path ./docs/fonts
run: python3 tests/run.py
- name: Archive diffs
uses: actions/upload-artifact@v4
if: always()
Expand Down
22 changes: 11 additions & 11 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,25 @@ export TYPST_ROOT := root
default:
@just --list --unsorted

# generate manual
doc *args:
typst compile docs/docs.typ docs.pdf --root . --font-path ./docs/fonts --ignore-system-fonts {{ args }}

# watch the manual
doc-watch *args:
typst watch docs/docs.typ docs.pdf --root . --font-path ./docs/fonts --ignore-system-fonts {{ args }}

# generate the codly function signature in codly.typ
signature:
python3 ./scripts/gen-signature.py

# run test suite
test *args:
tt run --no-fail-fast --font-path ./docs/fonts {{ args }}
tt run --no-fail-fast --font-path ./fonts {{ args }}

# update test cases
update *args:
tt update --font-path ./docs/fonts {{ args }}
tt update --font-path ./fonts {{ args }}

# format Typst library, tests, and documentation sources
fmt:
typstyle --inplace --line-width 100 --indent-width 2 --no-reorder-import-items codly.typ src tests

# verify Typst formatting without changing files
fmt-check:
typstyle --check --line-width 100 --indent-width 2 --no-reorder-import-items codly.typ src tests

# package the library into the specified destination folder
package target:
Expand Down Expand Up @@ -55,4 +55,4 @@ uninstall: (remove "@local")
uninstall-preview: (remove "@preview")

# run ci suite
ci: test doc
ci: fmt-check test
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,19 @@ Codly offers a wide range of features for referencing code blocks, lines, highli

![highlight-ref](./assets/highlight-ref.png)

### Querying a code block (development API)

References remain native Typst references and do not require a custom `ref` show rule. For queried information, `codly.info` reads a small metadata record:

```typ
#import "codly.typ" as codly
#codly.new(raw("first\nsecond", block: true))<source>

#context [This block has #codly.info(<source>).lines source lines.]
```

`lines` counts source lines before ranges and skips. `last-number` is the last displayed line number, including offsets, or `none` for an empty block. `offset-from` uses the same record. Pass the label of the code block or its containing figure.

### Setting an offset

If you wish to add an offset to your code block, but without selecting a subset of lines, you can use the `codly-offset` function:
Expand Down
67 changes: 67 additions & 0 deletions bench/indent-guides.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""Compare indentation guides with the disabled path and an optional old checkout."""
import argparse
import json
from pathlib import Path
import statistics
import subprocess
import tempfile
import time

ROOT = Path(__file__).resolve().parents[1]
SOURCES = {
"typical": "\n".join(
f"fn f{i}() {{\n if true {{\n call({i});\n }}\n}}"
for i in range(200)
),
"deep": "\n".join(" " * (i % 16) + "call(value);" for i in range(1000)),
"blank-lines": "\n".join("root\n child\n\n leaf\n\n child\nroot" for _ in range(140)),
"no-indent": "\n".join(f"let value{i} = {i};" for i in range(1000)),
"wrapped": "\n".join(" " * (i % 4) + "call(" + "long_argument, " * 12 + ");" for i in range(300)),
}


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--baseline", type=Path, help="An old checkout containing codly.typ")
parser.add_argument("--repeats", type=int, default=3)
parser.add_argument("--case", choices=SOURCES, action="append", dest="cases")
args = parser.parse_args()
if args.repeats < 1:
parser.error("--repeats must be positive")
variants = [("disabled", ROOT, ""), ("monochrome", ROOT, ", indent-guides: true"),
("rainbow-guides", ROOT, ", indent-guides: (rainbow: true)"),
("both", ROOT, ", indent-guides: true, rainbow: true")]
if args.baseline:
variants.insert(0, ("baseline", args.baseline.resolve(), ""))
with tempfile.TemporaryDirectory(prefix="codly-indent-bench-") as directory:
temporary = Path(directory)
for case in args.cases or SOURCES:
paths = {}
for name, root, options in variants:
path = temporary / f"{name}.typ"
path.write_text(
f'#import {json.dumps(str(root / "codly.typ"), ensure_ascii=False)} as codly\n'
'#set page(width: 400pt, height: 600pt, margin: 10pt)\n'
f'#codly.new(raw({json.dumps(SOURCES[case])}, lang: "rs", block: true)'
+ options + ")\n"
)
paths[name] = path
times = {name: [] for name in paths}
for _ in range(args.repeats):
for name, path in paths.items():
start = time.perf_counter()
subprocess.run(
["typst", "compile", "--root", "/", str(path), str(temporary / "out.pdf")],
check=True, stdout=subprocess.DEVNULL,
)
times[name].append(time.perf_counter() - start)
print(json.dumps({
"case": case,
"median_seconds": {name: round(statistics.median(runs), 3) for name, runs in times.items()},
"runs_seconds": {name: [round(t, 3) for t in runs] for name, runs in times.items()},
}), flush=True)


if __name__ == "__main__":
main()
66 changes: 66 additions & 0 deletions bench/rainbow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env python3
"""Compare rainbow coloring with the disabled path and an optional old checkout."""
import argparse
import json
from pathlib import Path
import statistics
import subprocess
import tempfile
import time

ROOT = Path(__file__).resolve().parents[1]
SOURCES = {
"typical": "\n".join(
f'fn f{i}() {{\n let value = {i};\n println!("value", value);\n}}'
for i in range(250)
),
"dense": "\n".join(
f'fn f{i}() {{ let x = [1, 2]; println!("{{}}", x[0]); }}'
for i in range(1000)
),
"no-delimiters": "\n".join(f"let value{i} = {i};" for i in range(1000)),
}


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--baseline", type=Path, help="An old checkout containing codly.typ")
parser.add_argument("--repeats", type=int, default=3)
parser.add_argument("--case", choices=SOURCES, action="append", dest="cases")
args = parser.parse_args()
if args.repeats < 1:
parser.error("--repeats must be positive")
variants = [("disabled", ROOT, False), ("enabled", ROOT, True)]
if args.baseline:
variants.insert(0, ("baseline", args.baseline.resolve(), False))
with tempfile.TemporaryDirectory(prefix="codly-rainbow-bench-") as directory:
temporary = Path(directory)
for case in args.cases or SOURCES:
paths = {}
for name, root, enabled in variants:
path = temporary / f"{name}.typ"
path.write_text(
f'#import {json.dumps(str(root / "codly.typ"), ensure_ascii=False)} as codly\n'
'#set page(width: 400pt, height: 600pt, margin: 10pt)\n'
f'#codly.new(raw({json.dumps(SOURCES[case])}, lang: "rs", block: true)'
+ (", rainbow: true" if enabled else "") + ")\n"
)
paths[name] = path
times = {name: [] for name in paths}
for _ in range(args.repeats):
for name, path in paths.items():
start = time.perf_counter()
subprocess.run(
["typst", "compile", "--root", "/", str(path), str(temporary / "out.pdf")],
check=True, stdout=subprocess.DEVNULL,
)
times[name].append(time.perf_counter() - start)
print(json.dumps({
"case": case,
"median_seconds": {name: round(statistics.median(runs), 3) for name, runs in times.items()},
"runs_seconds": {name: [round(t, 3) for t in runs] for name, runs in times.items()},
}), flush=True)


if __name__ == "__main__":
main()
63 changes: 63 additions & 0 deletions bench/wrap-marker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
"""Compare soft-wrap markers with the disabled path and an optional old checkout."""
import argparse
import json
from pathlib import Path
import statistics
import subprocess
import tempfile
import time

ROOT = Path(__file__).resolve().parents[1]
SOURCES = {
"short": "\n".join(f" call({i});" for i in range(1000)),
"wrapped": "\n".join(f" call({i}, " + "long_argument, " * 12 + ");" for i in range(300)),
"unicode": "\n".join(" " + "中文内容" * 24 for _ in range(200)),
}


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--baseline", type=Path, help="An old checkout containing codly.typ")
parser.add_argument("--repeats", type=int, default=3)
parser.add_argument("--case", choices=SOURCES, action="append", dest="cases")
args = parser.parse_args()
if args.repeats < 1:
parser.error("--repeats must be positive")
variants = [("disabled", ROOT, ""),
("marker", ROOT, ", wrap-marker: [↪]"),
("smart-off", ROOT, ", wrap-marker: [↪], smart-indent: false"),
("combined", ROOT, ", wrap-marker: [↪], indent-guides: true, rainbow: true")]
if args.baseline:
variants.insert(0, ("baseline", args.baseline.resolve(), ""))
with tempfile.TemporaryDirectory(prefix="codly-wrap-bench-") as directory:
temporary = Path(directory)
for case in args.cases or SOURCES:
paths = {}
for name, root, options in variants:
path = temporary / f"{name}.typ"
path.write_text(
f'#import {json.dumps(str(root / "codly.typ"), ensure_ascii=False)} as codly\n'
'#set page(width: 400pt, height: 600pt, margin: 10pt)\n'
f'#codly.new(raw({json.dumps(SOURCES[case])}, lang: "rs", block: true)'
+ options + ")\n"
)
paths[name] = path
times = {name: [] for name in paths}
for _ in range(args.repeats):
for name, path in paths.items():
start = time.perf_counter()
subprocess.run(
["typst", "compile", "--root", "/", str(path), str(temporary / "out.pdf")],
check=True, stdout=subprocess.DEVNULL,
)
times[name].append(time.perf_counter() - start)
print(json.dumps({
"case": case,
"median_seconds": {name: round(statistics.median(runs), 3) for name, runs in times.items()},
"runs_seconds": {name: [round(t, 3) for t in runs] for name, runs in times.items()},
}), flush=True)


if __name__ == "__main__":
main()
Loading
Loading