settings.rs

  1use std::ops::Range;
  2use tree_sitter::{Query, QueryMatch};
  3
  4use crate::MigrationPatterns;
  5
  6pub const SETTINGS_PATTERNS: MigrationPatterns = &[(
  7    SETTINGS_CONTEXT_SERVER_PATTERN,
  8    flatten_context_server_command,
  9)];
 10
 11const SETTINGS_CONTEXT_SERVER_PATTERN: &str = r#"(document
 12    (object
 13        (pair
 14            key: (string (string_content) @context-servers)
 15            value: (object
 16                (pair
 17                    key: (string (string_content) @server-name)
 18                    value: (object
 19                        (pair
 20                            key: (string (string_content) @source-key)
 21                            value: (string (string_content) @source-value)
 22                        )
 23                        (pair
 24                            key: (string (string_content) @command-key)
 25                            value: (object) @command-object
 26                        ) @command-pair
 27                    ) @server-settings
 28                )
 29            )
 30        )
 31    )
 32    (#eq? @context-servers "context_servers")
 33    (#eq? @source-key "source")
 34    (#eq? @source-value "custom")
 35    (#eq? @command-key "command")
 36)"#;
 37
 38fn flatten_context_server_command(
 39    contents: &str,
 40    mat: &QueryMatch,
 41    query: &Query,
 42) -> Option<(Range<usize>, String)> {
 43    let command_pair_index = query.capture_index_for_name("command-pair")?;
 44    let command_pair = mat.nodes_for_capture_index(command_pair_index).next()?;
 45
 46    let command_object_index = query.capture_index_for_name("command-object")?;
 47    let command_object = mat.nodes_for_capture_index(command_object_index).next()?;
 48
 49    let server_settings_index = query.capture_index_for_name("server-settings")?;
 50    let _server_settings = mat.nodes_for_capture_index(server_settings_index).next()?;
 51
 52    // Parse the command object to extract path, args, and env
 53    let mut path_value = None;
 54    let mut args_value = None;
 55    let mut env_value = None;
 56
 57    let mut cursor = command_object.walk();
 58    for child in command_object.children(&mut cursor) {
 59        if child.kind() == "pair" {
 60            if let Some(key_node) = child.child_by_field_name("key") {
 61                if let Some(string_content) = key_node.child(1) {
 62                    let key = &contents[string_content.byte_range()];
 63                    if let Some(value_node) = child.child_by_field_name("value") {
 64                        let value_range = value_node.byte_range();
 65                        match key {
 66                            "path" => path_value = Some(&contents[value_range]),
 67                            "args" => args_value = Some(&contents[value_range]),
 68                            "env" => env_value = Some(&contents[value_range]),
 69                            _ => {}
 70                        }
 71                    }
 72                }
 73            }
 74        }
 75    }
 76
 77    let path = path_value?;
 78
 79    // Get the proper indentation from the command pair
 80    let command_pair_start = command_pair.start_byte();
 81    let line_start = contents[..command_pair_start]
 82        .rfind('\n')
 83        .map(|pos| pos + 1)
 84        .unwrap_or(0);
 85    let indent = &contents[line_start..command_pair_start];
 86
 87    // Build the replacement string
 88    let mut replacement = format!("\"command\": {}", path);
 89
 90    // Add args if present - need to reduce indentation
 91    if let Some(args) = args_value {
 92        replacement.push_str(",\n");
 93        replacement.push_str(indent);
 94        replacement.push_str("\"args\": ");
 95        let reduced_args = reduce_indentation(args, 4);
 96        replacement.push_str(&reduced_args);
 97    }
 98
 99    // Add env if present - need to reduce indentation
100    if let Some(env) = env_value {
101        replacement.push_str(",\n");
102        replacement.push_str(indent);
103        replacement.push_str("\"env\": ");
104        replacement.push_str(&reduce_indentation(env, 4));
105    }
106
107    let range_to_replace = command_pair.byte_range();
108    Some((range_to_replace, replacement))
109}
110
111fn reduce_indentation(text: &str, spaces: usize) -> String {
112    let lines: Vec<&str> = text.lines().collect();
113    let mut result = String::new();
114
115    for (i, line) in lines.iter().enumerate() {
116        if i > 0 {
117            result.push('\n');
118        }
119
120        // Count leading spaces
121        let leading_spaces = line.chars().take_while(|&c| c == ' ').count();
122
123        if leading_spaces >= spaces {
124            // Reduce indentation
125            result.push_str(&line[spaces..]);
126        } else {
127            // Keep line as is if it doesn't have enough indentation
128            result.push_str(line);
129        }
130    }
131
132    result
133}