Skip to main content

winx_code_agent/utils/
mod.rs

1//! Utility modules for the Winx application.
2//!
3//! This module contains various utility functions and types used throughout
4//! the application, such as file and path handling, repository analysis, etc.
5
6pub mod agent_temp;
7pub mod bash_parser;
8pub mod display_tree;
9pub mod encoder;
10pub mod executable;
11pub mod mmap;
12pub mod mode_prompts;
13pub mod orchestration;
14pub mod output_compress;
15pub mod path;
16pub mod path_prob;
17pub mod redact;
18pub mod repo;
19pub mod scratch_file;
20pub mod symbols;
21pub mod syntax;
22pub mod workspace_stats;
23
24use crate::types::Initialize;
25use serde_json::Value;
26use tracing::debug;
27
28/// Largest index `<= idx` that is a char boundary of `s`.
29///
30/// Offsets derived from byte arithmetic over possibly-multibyte text (UTF-8
31/// CJK/emoji/box-drawing, the prompt glyphs, NBSP indentation) can land in the
32/// middle of a code point. Slicing there panics. Snap down to the nearest
33/// boundary so `&s[floor_char_boundary(s, idx)..]` is always valid. For ASCII
34/// input this is the identity, so hot paths keep their existing behavior.
35pub fn floor_char_boundary(s: &str, idx: usize) -> usize {
36    if idx >= s.len() {
37        return s.len();
38    }
39    let mut i = idx;
40    while i > 0 && !s.is_char_boundary(i) {
41        i -= 1;
42    }
43    i
44}
45
46/// Debug helper to test JSON parsing of an Initialize request
47pub fn test_json_parsing(json_str: &str) -> Result<(), String> {
48    // First, try to parse as raw JSON to see if the format is valid
49    let raw_json_result = serde_json::from_str::<Value>(json_str);
50    if let Err(e) = raw_json_result {
51        return Err(format!("Invalid JSON format: {e}"));
52    }
53
54    // Now try to parse into our Initialize struct
55    let init_result = serde_json::from_str::<Initialize>(json_str);
56    match init_result {
57        Ok(init) => {
58            debug!(
59                init_type = ?init.init_type,
60                mode_name = ?init.mode_name,
61                code_writer_config = ?init.code_writer_config,
62                task_id_to_resume = init.task_id_to_resume,
63                "Successfully parsed JSON into Initialize struct"
64            );
65            Ok(())
66        }
67        Err(e) => Err(format!("Failed to parse JSON into Initialize struct: {e}")),
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn floor_char_boundary_snaps_into_multibyte() {
77        let s = "a\u{3000}b"; // 'a' (1B) + ideographic space (3B) + 'b' (1B)
78        assert_eq!(floor_char_boundary(s, 0), 0);
79        assert_eq!(floor_char_boundary(s, 1), 1); // boundary before the wide char
80        assert_eq!(floor_char_boundary(s, 2), 1); // mid code point -> snap back to 1
81        assert_eq!(floor_char_boundary(s, 3), 1); // still mid -> 1
82        assert_eq!(floor_char_boundary(s, 4), 4); // boundary after the wide char
83        assert_eq!(floor_char_boundary(s, 999), s.len()); // past end -> len
84                                                          // every returned index must be sliceable without panicking
85        for i in 0..=s.len() + 5 {
86            let cut = floor_char_boundary(s, i);
87            let _ = &s[cut..];
88        }
89    }
90}