@pierre/diffs is an open source diff and code rendering library. It's built on Shiki for syntax highlighting and theming, is super customizable, and comes packed with features. Made with love by The Pierre Computer Company.
Currently v1.0.0
Choose from stacked (unified) or split (side-by-side). Both use CSS Grid and Shadow DOM under the hood, meaning fewer DOM nodes and faster rendering.
11 unmodified lines12 ThemesType,13} from '../types';1415export function createSpanFromToken(token: ThemedToken) {16 const element = document.createElement('div');17 const style = getTokenStyleObject(token);18 element.style = stringifyTokenStyle(style);19 return element;20}2122export function createRow(line: number) {23 const row = document.createElement('div');24 row.dataset.line = `${line}`;2526 const lineColumn = document.createElement('div');27 lineColumn.dataset.columnNumber = '';28 lineColumn.textContent = `${line}`;2930 const content = document.createElement('div');31 content.dataset.columnContent = '';3233 row.appendChild(lineColumn);34 row.appendChild(content);35 return { row, content };36}3730 unmodified lines11 unmodified lines12 ThemesType,13} from '../types';1415export function createSpanFromToken(token: ThemedToken) {16 const element = document.createElement('span');17 const style = token.htmlStyle ?? getTokenStyleObject(token);18 element.style = stringifyTokenStyle(style);19 element.textContent = token.content;20 element.dataset.span = ''21 return element;22}2324export function createRow(line: number) {25 const row = document.createElement('div');26 row.dataset.line = `${line}`;2728 const content = document.createElement('div');29 content.dataset.columnContent = '';3031 row.appendChild(content);32 return { row, content };33}3430 unmodified lines
We built @pierre/diffs on top of Shiki for syntax highlighting and general theming. Our components automatically adapt to blend in with your theme selection, including across color modes.
1use std::io;23fn main() {4 println!("What is your name?");5 let mut name = String::new();6 io::stdin().read_line(&mut name).unwrap();7 println!("Hello, {}", name.trim());8}910fn add(x: i32, y: i32) -> i32 {11 return x + y;12}1use std::io;23fn main() {4 println!("Enter your name:");5 let mut name = String::new();6 io::stdin().read_line(&mut name).expect("read error");7 println!("Hello, {}!", name.trim());8}910fn add(a: i32, b: i32) -> i32 {11 a + b12}
Love the Pierre themes? Install our Pierre VS Code Theme pack with light and dark flavors.
Your diffs, your choice. Render changed lines with classic diff indicators (+/–), full-width background colors, or vertical bars. You can even highlight inline changes—character or word based—and toggle line wrapping, hide numbers, and more.
1const std = @import("std");2const Allocator = std.heap.page_allocator;3const ArrayList = std.ArrayList;45pub fn main() !void {6 const stdout_writer_instance = std.io.getStdOut().writer();7 try stdout_writer_instance.print("Hi You, {s}! Welcome to our application.\n", .{"World"});89 var list = ArrayList(i32).init(allocator);10 defer list.deinit();1112 const configuration_options = .{ .enable_logging = true, .max_buffer_size = 1024, .timeout_milliseconds = 5000 };13 _ = configuration_options;14}1const std = @import("std");2const GeneralPurposeAllocator = std.heap.GeneralPurposeAllocator;3const ArrayList = std.ArrayList;45pub fn main() !void {6 var gpa = GeneralPurposeAllocator(.{}){};7 defer _ = gpa.deinit();8 const allocator = gpa.allocator();910 const stdout_writer_instance = std.io.getStdOut().writer();11 try stdout_writer_instance.print("Hello There, {s}! Welcome to the updated Zig application.\n", .{"Zig"});1213 var list = ArrayList(i32).init(allocator);14 defer list.deinit();15 try list.append(42);1617 const configuration_options = .{ .enable_logging = true, .max_buffer_size = 2048, .timeout_milliseconds = 10000, .retry_count = 3 };18 _ = configuration_options;19}
@pierre/diffs adapts to any font, font-size, line-height, and even font-feature-settings you may have set. Configure font options with your preferred CSS method globally or on a per-component basis.
38 unmodified lines39 if !exists {40 return nil, false41 }42
43 now := time.Now()44 expired := now.After(item.ExpiresAt)45 if expired {43 if time.Now().After(item.ExpiresAt) {44 delete(c.items, key)47 c.onEviction(key, item.Value)45 return nil, false46 }47
48 return item.Value, true10 unmodified lines@pierre/diffs provide a flexible annotation framework for injecting additional content and context. Use it to render your own line comments, annotations from CI jobs, and other third-party content.
2 unmodified lines3from typing import Optional4
5SECRET_KEY = "your-secret-key"6
7def create_token(user_id: str, expires_in: int = 3600) -> str:7def create_token(user_id: str, role: str = "user", expires_in: int = 3600) -> str:8 payload = {9 "sub": user_id,10 "role": role,11 "exp": time.time() + expires_in12 }13 return jwt.encode(payload, SECRET_KEY, algorithm="HS256")14
14def verify_token(token: str) -> Optional[str]:15def verify_token(token: str) -> Optional[dict]:16 try:17 payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])18 if payload["exp"] < time.time():19 return None19 return payload["sub"]20 return {"user_id": payload["sub"], "role": payload["role"]} 21 except jwt.InvalidTokenError:22 return NoneShould we validate the role parameter? We could restrict it to a set of allowed values.
Good idea, maybe use a Literal type or an enum.
Agreed, we should also update verify_token to return the role.
Annotations can also be used to build interactive code review interfaces similar to AI-assisted coding tools like Cursor. Use it to track the state of each change, inject custom UI like accept/reject buttons, and provide immediate visual feedback.
4 unmodified lines5 <title>Welcome</title>6</head>7<body>8 <header>9 <h1>Welcome</h1>10 <p>Thanks for visiting</p>9 <h1>Welcome to Our Site</h1>10 <p>We're glad you're here</p>11 <a href="/about" class="btn">Learn More</a> 12 </header>13 <footer>14 <p>© Acme Inc.</p>15 </footer>2 unmodified linesTurn on line selection with enableLineSelection: true. When enabled, clicking a line number will select that line. Click and drag to select multiple lines, or hold Shift and click to extend your selection. You can also control the selection programmatically. Also selections will elegantly manage the differences between split and unified views.
16 unmodified lines17 }1819 ~Vector() {20 delete[] data;21 data = nullptr;22 }2324 void push_back(const T& value) {25 if (length >= capacity) {26 reserve(capacity * 2);27 }28 data[length++] = value;29 }3031 T& operator[](size_t index) {32 if (index >= length) {33 throw std::out_of_range("Index out of bounds");34 }35 return data[index];36 }3738 size_t size() const { return length; }39 bool empty() const { return length == 0; }4041 void reserve(size_t newCapacity) {10 unmodified lines16 unmodified lines17 }1819 ~Vector() {20 delete[] data;21 }2223 void push_back(const T& value) {24 if (length >= capacity) {25 size_t newCap = capacity == 0 ? 1 : capacity * 2;26 reserve(newCap);27 }28 data[length++] = value;29 }3031 T& operator[](size_t index) {32 return data[index];33 }3435 void clear() {36 length = 0;37 }3839 T& front() { return data[0]; }40 T& back() { return data[length - 1]; }4142 size_t size() const { return length; }43 bool empty() const { return length == 0; }4445 void reserve(size_t newCapacity) {10 unmodified lines
In addition to rendering standard Git diffs and patches, you can pass any two files in @pierre/diffs and get a diff between them. This is especially useful when comparing across generative snapshots where linear history isn't always available. Edit the css below to see the diff.
1.pizza {2 display: flex;3 justify-content: center;3}Our team has decades of cumulative experience in open source, developer tools, and more. We’ve worked on projects like Coinbase, GitHub, Bootstrap, Twitter, Medium, and more. This stuff is our bread and butter, and we’re happy to share it with you.