con un clic
add-renderer
// Step-by-step guide for adding a new diff output renderer to diffsitter. Use when adding a new output format.
// Step-by-step guide for adding a new diff output renderer to diffsitter. Use when adding a new output format.
| 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 |
| user-invocable | true |
| argument-hint | [name] Name of the new renderer (e.g., 'delta', 'html') |
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.
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)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>> where RichHunk is DocumentType<Hunk>Hunk contains Vec<Line>, each Line has line_index: usize and entries: Vec<&Entry>Entry has text: Cow<str>, start_position: Point, end_position: Point, kind_id: u16DocumentType::Old(hunk) / DocumentType::New(hunk) distinguishes old vs new document hunksUse 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.
src/render/mod.rsAdd 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;
Renderers enumAdd 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.
RenderConfigIn 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
}
}
}
assets/sample_config.json5Add 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.
At minimum:
#[test_case("$ARGUMENTS")] line to the test_get_renderer_custom_tag test in src/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 insta if the output format is complex.
cargo build
cargo test --all
If you updated sample_config.json5, the test_sample_config test will verify it parses correctly.
Default derive/impl: The RenderConfig uses #[serde(default)], so your struct must implement Default.Renderers enum uses snake_case serialization via strum/serde. Your variant MyRenderer becomes "my_renderer" as a string tag.writer is generic: Don't assume stdout. The renderer receives &mut dyn Write which could be a buffered terminal, a pager, or a file.term_info may be None: If the output is piped or redirected, there is no terminal. Handle gracefully (see how unified.rs handles missing terminal width).Step-by-step guide for adding a new tree-sitter language grammar to diffsitter. Use when adding support for a new programming language.
Diagnose unexpected diff output by tracing the pipeline from parsing through AST processing to hunk generation. Use when diffsitter produces wrong or surprising results.
Expert Rust guidance for diffsitter: tree-sitter FFI patterns, lifetime management, unsafe optimization, edition 2024 idioms. Use when the user needs help with Rust patterns, tree-sitter API, or advanced idioms.