Agent Skillsafnanenayet/diffsitter › add-renderer

add-renderer

GitHub

提供在diffsitter中新增差异输出渲染器的分步指南,涵盖模块创建、类型实现及注册流程。

.claude/skills/add-renderer/SKILL.md afnanenayet/diffsitter

Trigger Scenarios

添加新的 diff 输出格式 扩展 diffsitter 渲染器功能

Install

npx skills add afnanenayet/diffsitter --skill add-renderer -g -y
More Options

Non-standard path

npx skills add https://github.com/afnanenayet/diffsitter/tree/main/.claude/skills/add-renderer -g -y

Use without installing

npx skills use afnanenayet/diffsitter@add-renderer

指定 Agent (Claude Code)

npx skills add afnanenayet/diffsitter --skill add-renderer -a claude-code -g -y

安装 repo 全部 skill

npx skills add afnanenayet/diffsitter --all -g -y

预览 repo 内 skill

npx skills add afnanenayet/diffsitter --list

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, config
  • src/render/json.rs -- minimal renderer example
  • src/render/unified.rs -- full-featured renderer example
  • src/config.rs -- top-level config struct
  • assets/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>> where RichHunk is DocumentType<Hunk>
  • Each Hunk contains Vec<Line>, each Line has line_index: usize and entries: Vec<&Entry>
  • Each Entry has text: 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:

  1. Add a #[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) {
  1. Add unit tests in your renderer module for any non-trivial logic.

  2. Consider adding snapshot tests with insta if 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 Default derive/impl: The RenderConfig uses #[serde(default)], so your struct must implement Default.
  • Case sensitivity: The Renderers enum uses snake_case serialization via strum/serde. Your variant MyRenderer becomes "my_renderer" as a string tag.
  • The 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).

Version History

  • 7ef35a7 Current 2026-08-02 21:47

Same Skill Collection

.claude/skills/add-grammar/SKILL.md
.claude/skills/debug-diff/SKILL.md
.claude/skills/rust-expert/SKILL.md
plugins/tree-sitter-mcp/skills/ast-navigation/SKILL.md

Metadata

Files
0
Version
7ef35a7
Hash
781543fd
Indexed
2026-08-02 21:47

Home - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-03 04:14
浙ICP备14020137号-1 $Map of visitor$