add-renderer
GitHub提供在diffsitter中新增差异输出渲染器的分步指南,涵盖模块创建、类型实现及注册流程。
Trigger Scenarios
Install
npx skills add afnanenayet/diffsitter --skill add-renderer -g -y
SKILL.md
Frontmatter
{
"name": "add-renderer",
"description": "Step-by-step guide for adding a new diff output renderer to diffsitter. Use when adding a new output format.",
"allowed-tools": "Read, Grep, Glob, Bash, Edit, Write",
"argument-hint": "[name] Name of the new renderer (e.g., 'delta', 'html')",
"user-invocable": true
}
Adding a New Renderer to diffsitter
Follow this checklist to add a new diff output renderer named $ARGUMENTS. If the user did not provide a name, ask for one before proceeding.
Prerequisites
Read these files first to understand the existing patterns:
src/render/mod.rs-- trait definition, enum, configsrc/render/json.rs-- minimal renderer examplesrc/render/unified.rs-- full-featured renderer examplesrc/config.rs-- top-level config structassets/sample_config.json5-- sample config (CI parses this as a test)
Step-by-Step Checklist
Step 1: Create the renderer module
Create src/render/$ARGUMENTS.rs with a struct that derives the required traits:
use super::DisplayData;
use crate::render::Renderer;
use console::Term;
use serde::{Deserialize, Serialize};
use std::io::Write;
/// A renderer that outputs diffs in $ARGUMENTS format.
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug, Default)]
pub struct $ARGUMENTS_PASCAL_CASE {
// Add configuration fields here.
// Each field should be serializable for the config system.
}
impl Renderer for $ARGUMENTS_PASCAL_CASE {
fn render(
&self,
writer: &mut dyn Write,
data: &DisplayData,
term_info: Option<&Term>,
) -> anyhow::Result<()> {
// Implementation goes here.
// `data.hunks` contains `RichHunks` (Vec<RichHunk> where RichHunk = DocumentType<Hunk>)
// `data.old` and `data.new` contain DocumentDiffData { filename, text }
// `term_info` provides terminal dimensions if the output is a TTY
todo!()
}
}
Key types available in DisplayData:
data.hunks.0--Vec<RichHunk<'a>>whereRichHunkisDocumentType<Hunk>- Each
HunkcontainsVec<Line>, eachLinehasline_index: usizeandentries: Vec<&Entry> - Each
Entryhastext: Cow<str>,start_position: Point,end_position: Point,kind_id: u16 DocumentType::Old(hunk)/DocumentType::New(hunk)distinguishes old vs new document hunks
Use src/render/json.rs as a minimal reference (just serializes DisplayData to JSON). Use src/render/unified.rs for a full-featured example with terminal colors, hunk titles, and line-by-line rendering.
Step 2: Register the module in src/render/mod.rs
Add the module declaration and use statement near the top:
mod $ARGUMENTS;
use self::$ARGUMENTS::$ARGUMENTS_PASCAL_CASE;
These go alongside the existing:
mod json;
mod unified;
use self::json::Json;
use unified::Unified;
Step 3: Add a variant to the Renderers enum
Add your variant to the Renderers enum in src/render/mod.rs:
#[enum_dispatch]
#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize, Display, EnumIter, EnumString)]
#[strum(serialize_all = "snake_case")]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Renderers {
Unified,
Json,
$ARGUMENTS_PASCAL_CASE, // <-- add this
}
The enum_dispatch attribute automatically generates the Renderer trait dispatch for the new variant. The strum and serde derives handle string conversion and serialization using the snake_case name.
Step 4: Add a field to RenderConfig
In src/render/mod.rs, add a field to RenderConfig:
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
#[serde(rename_all = "snake_case", default)]
pub struct RenderConfig {
default: String,
unified: unified::Unified,
json: json::Json,
$ARGUMENTS: $ARGUMENTS::$ARGUMENTS_PASCAL_CASE, // <-- add this
}
Update the Default impl for RenderConfig:
impl Default for RenderConfig {
fn default() -> Self {
let default_renderer = Renderers::default();
RenderConfig {
default: default_renderer.to_string(),
unified: Unified::default(),
json: Json::default(),
$ARGUMENTS: $ARGUMENTS_PASCAL_CASE::default(), // <-- add this
}
}
}
Step 5: Update assets/sample_config.json5
Add a section for the new renderer's configuration under the "formatting" key. CI parses this file as a test (test_sample_config in src/config.rs), so it must be valid.
Step 6: Add tests
At minimum:
- Add a
#[test_case("$ARGUMENTS")]line to thetest_get_renderer_custom_tagtest insrc/render/mod.rs:
#[test_case("unified")]
#[test_case("json")]
#[test_case("$ARGUMENTS")] // <-- add this
fn test_get_renderer_custom_tag(tag: &str) {
-
Add unit tests in your renderer module for any non-trivial logic.
-
Consider adding snapshot tests with
instaif the output format is complex.
Step 7: Build and test
cargo build
cargo test --all
If you updated sample_config.json5, the test_sample_config test will verify it parses correctly.
Common Pitfalls
- Forgetting
Defaultderive/impl: TheRenderConfiguses#[serde(default)], so your struct must implementDefault. - Case sensitivity: The
Renderersenum usessnake_caseserialization via strum/serde. Your variantMyRendererbecomes"my_renderer"as a string tag. - The
writeris generic: Don't assume stdout. The renderer receives&mut dyn Writewhich could be a buffered terminal, a pager, or a file. term_infomay beNone: If the output is piped or redirected, there is no terminal. Handle gracefully (see howunified.rshandles missing terminal width).
Version History
- 7ef35a7 Current 2026-08-02 21:47


