Files
zterm/REDUNDANCY_AUDIT.md
T
2025-12-22 00:22:55 +01:00

18 KiB

ZTerm Redundancy Audit

Generated: 2025-12-20

Summary

File Current Lines Est. Savings % Reduction
renderer.rs 8426 ~1000-1400 12-17%
terminal.rs 2366 ~150-180 ~7%
main.rs 2787 ~150-180 ~6%
vt_parser.rs 1044 ~60-90 6-9%
Other files ~3000 ~100 ~3%
Total ~17600 ~1500-2000 ~10%

High Priority

1. renderer.rs: Pipeline Creation Boilerplate

Lines: 2200-2960 (~760 lines)
Estimated Savings: 400-500 lines

Problem: Nearly identical pipeline creation code with slight variations in entry points, blend modes, and bind group layouts.

Suggested Fix: Create a pipeline builder:

struct PipelineBuilder<'a> {
    device: &'a wgpu::Device,
    shader: &'a wgpu::ShaderModule,
    layout: &'a wgpu::PipelineLayout,
    format: wgpu::TextureFormat,
}

impl PipelineBuilder<'_> {
    fn build(self, vs_entry: &str, fs_entry: &str, blend: Option<wgpu::BlendState>) -> wgpu::RenderPipeline
}

2. renderer.rs: Box Drawing Character Rendering

Lines: 4800-5943 (~1150 lines)
Estimated Savings: 400-600 lines

Problem: Massive match statement with repeated hline()/vline()/fill_rect() patterns. Many similar light/heavy/double line variants.

Suggested Fix:

  • Use lookup tables for line positions/thicknesses
  • Create a BoxDrawingSpec struct with line positions
  • Consolidate repeated drawing patterns into parameterized helpers

3. main.rs: Duplicate NamedKey-to-String Matching

Lines: 1778-1812, 2146-2181
Estimated Savings: ~70 lines

Problem: check_keybinding() and handle_keyboard_input() both have nearly identical NamedKey to string/FunctionalKey matching logic for F1-F12, arrow keys, Home/End/PageUp/PageDown, etc.

Suggested Fix:

fn named_key_to_str(named: &NamedKey) -> Option<&'static str> { ... }
fn named_key_to_functional(named: &NamedKey) -> Option<FunctionalKey> { ... }

4. terminal.rs: Duplicated SGR Extended Color Parsing

Lines: 2046-2106
Estimated Savings: ~30 lines

Problem: SGR 38 (foreground) and SGR 48 (background) extended color parsing logic is nearly identical - duplicated twice each (once for sub-params, once for regular params).

Suggested Fix:

fn parse_extended_color(&self, params: &CsiParams, i: &mut usize) -> Option<Color> {
    let mode = params.get(*i + 1, 0);
    if mode == 5 && *i + 2 < params.num_params {
        *i += 2;
        Some(Color::Indexed(params.params[*i] as u8))
    } else if mode == 2 && *i + 4 < params.num_params {
        let color = Color::Rgb(
            params.params[*i + 2] as u8,
            params.params[*i + 3] as u8,
            params.params[*i + 4] as u8,
        );
        *i += 4;
        Some(color)
    } else {
        None
    }
}

5. terminal.rs: Cursor-Row-With-Scroll Pattern

Lines: 1239-1246, 1262-1270, 1319-1324, 1836-1842, 1844-1851, 1916-1922, 1934-1940
Estimated Savings: ~25 lines

Problem: The pattern "increment cursor_row, check against scroll_bottom, scroll_up(1) if needed" is repeated 7 times.

Suggested Fix:

#[inline]
fn advance_row(&mut self) {
    self.cursor_row += 1;
    if self.cursor_row > self.scroll_bottom {
        self.scroll_up(1);
        self.cursor_row = self.scroll_bottom;
    }
}

6. terminal.rs: Cell Construction with Current Attributes

Lines: 1278-1287, 1963-1972, 1985-1994
Estimated Savings: ~20 lines

Problem: Cell construction using current attributes is repeated 3 times with nearly identical code.

Suggested Fix:

#[inline]
fn current_cell(&self, character: char, wide_continuation: bool) -> Cell {
    Cell {
        character,
        fg_color: self.current_fg,
        bg_color: self.current_bg,
        bold: self.current_bold,
        italic: self.current_italic,
        underline_style: self.current_underline_style,
        strikethrough: self.current_strikethrough,
        wide_continuation,
    }
}

7. config.rs: normalize_key_name Allocates String for Static Values

Lines: 89-160
Estimated Savings: Eliminates 50+ string allocations

Problem: Every match arm allocates a new String even though most are static:

"left" | "arrowleft" | "arrow_left" => "left".to_string(),

Suggested Fix:

fn normalize_key_name(name: &str) -> Cow<'static, str> {
    match name {
        "left" | "arrowleft" | "arrow_left" => Cow::Borrowed("left"),
        // ...
        _ => Cow::Owned(name.to_string()),
    }
}

8. config.rs: Repeated Parse-and-Insert Blocks in build_action_map

Lines: 281-349
Estimated Savings: ~40 lines

Problem: 20+ repeated blocks:

if let Some(parsed) = self.new_tab.parse() {
    map.insert(parsed, Action::NewTab);
}

Suggested Fix:

let bindings: &[(&Keybind, Action)] = &[
    (&self.new_tab, Action::NewTab),
    (&self.next_tab, Action::NextTab),
    // ...
];
for (keybind, action) in bindings {
    if let Some(parsed) = keybind.parse() {
        map.insert(parsed, *action);
    }
}

9. vt_parser.rs: Duplicate OSC/String Command Terminator Handling

Lines: 683-755, 773-843
Estimated Savings: ~30 lines

Problem: consume_osc and consume_string_command have nearly identical structure for finding and handling terminators (ESC, BEL, C1 ST).

Suggested Fix: Extract a common helper:

fn consume_st_terminated<F>(
    &mut self,
    bytes: &[u8],
    pos: usize,
    buffer: &mut Vec<u8>,
    include_bel: bool,
    on_complete: F,
) -> usize
where
    F: FnOnce(&mut Self, &[u8])

10. vt_parser.rs: Duplicate Control Char Handling in CSI States

Lines: 547-551, 593-596, 650-653
Estimated Savings: ~15 lines

Problem: Identical control character handling appears in all three CsiState match arms:

0x00..=0x1F => {
    if ch != 0x1B {
        handler.control(ch);
    }
}

Suggested Fix: Move control char handling before the match self.csi.state block:

if ch <= 0x1F && ch != 0x1B {
    handler.control(ch);
    consumed += 1;
    continue;
}

11. main.rs: Repeated active_tab().and_then(active_pane()) Pattern

Lines: Various (10+ occurrences)
Estimated Savings: ~30 lines

Problem: This nested Option chain appears throughout the code.

Suggested Fix:

fn active_pane(&self) -> Option<&Pane> {
    self.active_tab().and_then(|t| t.active_pane())
}

fn active_pane_mut(&mut self) -> Option<&mut Pane> {
    self.active_tab_mut().and_then(|t| t.active_pane_mut())
}

Medium Priority

12. renderer.rs: set_scale_factor and set_font_size Duplicate Cell Metric Recalc

Lines: 3306-3413
Estimated Savings: ~50 lines

Problem: ~100 lines of nearly identical cell metric recalculation logic.

Suggested Fix: Extract to a shared recalculate_cell_metrics(&mut self) method.


13. renderer.rs: find_font_for_char and find_color_font_for_char Similar

Lines: 939-1081
Estimated Savings: ~60-80 lines

Problem: ~140 lines of similar fontconfig query patterns.

Suggested Fix: Extract common fontconfig query helper, parameterize the charset/color requirements.


14. renderer.rs: place_glyph_in_cell_canvas vs Color Variant

Lines: 6468-6554
Estimated Savings: ~40-50 lines

Problem: ~90 lines of nearly identical logic, differing only in bytes-per-pixel (1 vs 4).

Suggested Fix:

fn place_glyph_in_cell_canvas_impl(&self, bitmap: &[u8], ..., bytes_per_pixel: usize) -> Vec<u8>

15. renderer.rs: render_rect vs render_overlay_rect Near-Identical

Lines: 7192-7215
Estimated Savings: ~10 lines

Problem: Near-identical functions pushing to different Vec.

Suggested Fix:

fn render_quad(&mut self, x: f32, y: f32, w: f32, h: f32, color: [f32; 4], overlay: bool)

16. renderer.rs: Pane Border Adjacency Checks Repeated

Lines: 7471-7587
Estimated Savings: ~60-80 lines

Problem: ~120 lines of repetitive adjacency detection for 4 directions.

Suggested Fix:

fn check_pane_adjacency(&self, a: &PaneInfo, b: &PaneInfo) -> Vec<Border>

17. terminal.rs: to_rgba and to_rgba_bg Nearly Identical

Lines: 212-239
Estimated Savings: ~15 lines

Problem: These two methods differ only in the Color::Default case.

Suggested Fix:

pub fn to_rgba(&self, color: &Color, is_bg: bool) -> [f32; 4] {
    let [r, g, b] = match color {
        Color::Default => if is_bg { self.default_bg } else { self.default_fg },
        Color::Rgb(r, g, b) => [*r, *g, *b],
        Color::Indexed(idx) => self.colors[*idx as usize],
    };
    [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0]
}

18. terminal.rs: insert_lines/delete_lines Share Dirty Marking

Lines: 1080-1134
Estimated Savings: ~6 lines

Problem: Both use identical dirty marking loop that could use existing mark_region_dirty.

Suggested Fix: Replace:

for line in self.cursor_row..=self.scroll_bottom {
    self.mark_line_dirty(line);
}

with:

self.mark_region_dirty(self.cursor_row, self.scroll_bottom);

19. terminal.rs: handle_dec_private_mode_set/reset Are Mirror Images

Lines: 2148-2295
Estimated Savings: ~50 lines

Problem: ~70 lines each, almost mirror images with true vs false.

Suggested Fix:

fn handle_dec_private_mode(&mut self, params: &CsiParams, set: bool) {
    for i in 0..params.num_params {
        match params.params[i] {
            1 => self.application_cursor_keys = set,
            7 => self.auto_wrap = set,
            25 => self.cursor_visible = set,
            // ...
        }
    }
}

20. main.rs: Duplicate Git Status String Building

Lines: 1095-1118
Estimated Savings: ~15 lines

Problem: Identical logic for building working_string and staging_string with ~N, +N, -N format.

Suggested Fix:

fn format_git_changes(modified: usize, added: usize, deleted: usize) -> String {
    let mut parts = Vec::new();
    if modified > 0 { parts.push(format!("~{}", modified)); }
    if added > 0 { parts.push(format!("+{}", added)); }
    if deleted > 0 { parts.push(format!("-{}", deleted)); }
    parts.join(" ")
}

21. main.rs: Repeated StatuslineComponent RGB Color Application

Lines: 1169-1212
Estimated Savings: ~10 lines

Problem: Multiple StatuslineComponent::new(...).rgb_fg(fg_color.0, fg_color.1, fg_color.2) calls with exact same color.

Suggested Fix:

let with_fg = |text: &str| StatuslineComponent::new(text).rgb_fg(fg_color.0, fg_color.1, fg_color.2);
components.push(with_fg(" "));
components.push(with_fg(&head_text));

22. main.rs: Tab1-Tab9 as Separate Match Arms

Lines: 1862-1870
Estimated Savings: ~8 lines

Problem: Nine separate match arms each calling self.switch_to_tab(N).

Suggested Fix:

impl Action {
    fn tab_index(&self) -> Option<usize> {
        match self {
            Action::Tab1 => Some(0),
            Action::Tab2 => Some(1),
            // ...
        }
    }
}
// Then in match:
action if action.tab_index().is_some() => {
    self.switch_to_tab(action.tab_index().unwrap());
}

23. keyboard.rs: encode_arrow and encode_f1_f4 Are Identical

Lines: 462-485
Estimated Savings: ~15 lines

Problem: These two methods have identical implementations.

Suggested Fix: Remove encode_f1_f4 and use encode_arrow for both, or rename to encode_ss3_key.


24. keyboard.rs: Repeated to_string().as_bytes() Allocations

Lines: 356, 365, 372, 378, 466, 479, 490, 493, 554
Estimated Savings: Reduced allocations

Problem: Multiple places call .to_string().as_bytes() on integers.

Suggested Fix:

fn write_u32_to_vec(n: u32, buf: &mut Vec<u8>) {
    use std::io::Write;
    write!(buf, "{}", n).unwrap();
}

Or use itoa crate for zero-allocation integer formatting.


25. graphics.rs: Duplicate AnimationData Construction

Lines: 444-456, 623-635
Estimated Savings: ~10 lines

Problem: Both decode_gif and decode_webm create identical AnimationData structs.

Suggested Fix:

impl AnimationData {
    pub fn new(frames: Vec<AnimationFrame>, total_duration_ms: u64) -> Self {
        Self {
            frames,
            current_frame: 0,
            frame_start: None,
            looping: true,
            total_duration_ms,
            state: AnimationState::Running,
            loops_remaining: None,
        }
    }
}

26. graphics.rs: Duplicate RGBA Stride Handling

Lines: 554-566, 596-607
Estimated Savings: ~15 lines

Problem: Code for handling RGBA stride appears twice (nearly identical).

Suggested Fix:

fn copy_with_stride(data: &[u8], width: u32, height: u32, stride: usize) -> Vec<u8> {
    let row_bytes = (width * 4) as usize;
    if stride == row_bytes {
        data[..(width * height * 4) as usize].to_vec()
    } else {
        let mut result = Vec::with_capacity((width * height * 4) as usize);
        for row in 0..height as usize {
            let start = row * stride;
            result.extend_from_slice(&data[start..start + row_bytes]);
        }
        result
    }
}

27. graphics.rs: Duplicate File/TempFile/SharedMemory Reading Logic

Lines: 1051-1098, 1410-1489
Estimated Savings: ~30 lines

Problem: Both handle_animation_frame and store_image have similar file reading logic.

Suggested Fix:

fn load_transmission_data(&mut self, cmd: &mut GraphicsCommand) -> Result<Vec<u8>, GraphicsError>

28. pty.rs: Duplicate Winsize/ioctl Pattern

Lines: 57-67, 170-185
Estimated Savings: ~8 lines

Problem: Same libc::winsize struct creation and TIOCSWINSZ ioctl pattern duplicated.

Suggested Fix:

fn set_winsize(fd: RawFd, cols: u16, rows: u16, xpixel: u16, ypixel: u16) -> Result<(), PtyError> {
    let winsize = libc::winsize {
        ws_row: rows,
        ws_col: cols,
        ws_xpixel: xpixel,
        ws_ypixel: ypixel,
    };
    let result = unsafe { libc::ioctl(fd, libc::TIOCSWINSZ, &winsize) };
    if result == -1 {
        Err(PtyError::Io(std::io::Error::last_os_error()))
    } else {
        Ok(())
    }
}

29. vt_parser.rs: Repeated Max Length Check Pattern

Lines: 537-541, 693-697, 745-749, 785-789, 831-835
Estimated Savings: ~15 lines

Problem: This pattern appears 5 times:

if self.escape_len + X > MAX_ESCAPE_LEN {
    log::debug!("... sequence too long, aborting");
    self.state = State::Normal;
    return consumed;
}

Suggested Fix:

#[inline]
fn check_max_len(&mut self, additional: usize) -> bool {
    if self.escape_len + additional > MAX_ESCAPE_LEN {
        log::debug!("Escape sequence too long, aborting");
        self.state = State::Normal;
        true
    } else {
        false
    }
}

Low Priority

30. main.rs: PaneId/TabId Have Identical Implementations

Lines: 230-239, 694-703
Estimated Savings: ~15 lines

Problem: Both use identical new() implementations with static atomics.

Suggested Fix:

macro_rules! define_id {
    ($name:ident) => {
        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
        pub struct $name(u64);
        
        impl $name {
            pub fn new() -> Self {
                use std::sync::atomic::{AtomicU64, Ordering};
                static NEXT_ID: AtomicU64 = AtomicU64::new(1);
                Self(NEXT_ID.fetch_add(1, Ordering::Relaxed))
            }
        }
    };
}
define_id!(PaneId);
define_id!(TabId);

31. main.rs: Duplicate "All Tabs Closed" Exit Checks

Lines: 2622-2653
Estimated Savings: ~5 lines

Problem: Two separate checks with same log message in about_to_wait().

Suggested Fix: Restructure to have a single exit point after cleanup logic.


32. terminal.rs: scroll_viewport_up/down Similarity

Lines: 885-915
Estimated Savings: ~8 lines

Problem: Similar structure - both check for alternate screen, calculate offset, set dirty.

Suggested Fix: Merge into single method with signed delta parameter.


33. terminal.rs: screen_alignment Cell Construction Verbose

Lines: 1881-1890
Estimated Savings: ~6 lines

Problem: Constructs Cell with all default values explicitly.

Suggested Fix:

*cell = Cell { character: 'E', ..Cell::default() };

34. keyboard.rs: Repeated UTF-8 Char Encoding Pattern

Lines: 503-505, 529-532, 539-541
Estimated Savings: ~8 lines

Problem: Pattern appears 3 times:

let mut buf = [0u8; 4];
let s = c.encode_utf8(&mut buf);
return s.as_bytes().to_vec();

Suggested Fix:

fn char_to_vec(c: char) -> Vec<u8> {
    let mut buf = [0u8; 4];
    c.encode_utf8(&mut buf).as_bytes().to_vec()
}

35. config.rs: Tab1-Tab9 as Separate Enum Variants/Fields

Lines: 174-182, 214-230
Estimated Savings: Structural improvement

Problem: Having separate Tab1, Tab2, ... Tab9 variants and corresponding struct fields is verbose.

Suggested Fix: Use Tab(u8) variant and tab_keys: [Keybind; 9] array. Note: This changes the JSON config format.


36. pty.rs: Inconsistent AsRawFd Usage

Lines: 63, 177
Estimated Savings: Cleanup only

Problem: Uses fully-qualified std::os::fd::AsRawFd::as_raw_fd despite importing the trait.

Suggested Fix:

// Change from:
let fd = std::os::fd::AsRawFd::as_raw_fd(&master);
// To:
let fd = master.as_raw_fd();

37. pty.rs: Repeated /proc Path Pattern

Lines: 222-243
Estimated Savings: ~4 lines

Problem: Both foreground_process_name and foreground_cwd build /proc/{pgid}/... paths similarly.

Suggested Fix:

fn proc_path(&self, file: &str) -> Option<std::path::PathBuf> {
    let pgid = self.foreground_pgid()?;
    Some(std::path::PathBuf::from(format!("/proc/{}/{}", pgid, file)))
}