diff --git a/Cargo.lock b/Cargo.lock index c393756..70d4730 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -448,6 +448,7 @@ dependencies = [ "dashmap 6.1.0", "mist-codegen", "mist-parser", + "ropey", "serde", "serde_json", "tokio", @@ -623,6 +624,16 @@ dependencies = [ "bitflags 2.11.1", ] +[[package]] +name = "ropey" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5" +dependencies = [ + "smallvec", + "str_indices", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -742,6 +753,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "str_indices" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08889ec5408683408db66ad89e0e1f93dff55c73a4ccc71c427d5b277ee47e6" + [[package]] name = "syn" version = "2.0.117" diff --git a/crates/analyzer/Cargo.toml b/crates/analyzer/Cargo.toml index 7a9cc43..d8eda02 100644 --- a/crates/analyzer/Cargo.toml +++ b/crates/analyzer/Cargo.toml @@ -19,3 +19,4 @@ cargo_metadata = "0.23.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +ropey = "1" diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index be45dbd..7397b4c 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -8,6 +8,7 @@ use std::path::{Component, PathBuf}; use std::sync::Arc; use mist_parser::rev_mapper; +use ropey::Rope; use tokio::sync::Mutex; use tower_lsp::jsonrpc::Result; use tower_lsp::lsp_types::notification::Notification; @@ -539,44 +540,50 @@ pub fn from_rust_to_mist(mut path: PathBuf) -> PathBuf { } fn insert_at_position(s: &str, line: usize, col: usize, insert: &str) -> String { - let mut lines: Vec = s.lines().map(|l| l.to_string()).collect(); + let mut rope = Rope::from_str(s); - // Convert to 0-index + // Convert to 0-indexed let line_idx = line.saturating_sub(1); let col_idx = col.saturating_sub(1); - // Ensure line exists (optional behavior: extend with empty lines) - if line_idx >= lines.len() { - lines.resize(line_idx + 1, String::new()); - } + // Clamp line + let line_idx = line_idx.min(rope.len_lines().saturating_sub(1)); - let target = &mut lines[line_idx]; + // Get start char index of the line + let line_start = rope.line_to_char(line_idx); - // Clamp column to valid char boundary - let char_idx = target - .char_indices() - .nth(col_idx) - .map(|(i, _)| i) - .unwrap_or(target.len()); // if past end, append + // Get line length in chars + let line = rope.line(line_idx); + let line_len = line.len_chars(); - target.insert_str(char_idx, insert); + // Clamp column + let col_idx = col_idx.min(line_len); - lines.join("\n") + // Final insertion char index + let insert_idx = line_start + col_idx; + + rope.insert(insert_idx, insert); + + rope.to_string() } fn find_row_col(output: &str, inject: &str) -> Option<(usize, usize)> { - // 1. Find the flat byte index just like your original code + let rope = Rope::from_str(output); + let byte_idx = output.find(inject)?; - // 2. Slice the string up to the match point - let prefix = &output[..byte_idx]; + // Convert byte index -> char index + let char_idx = output[..byte_idx].chars().count(); - // 3. Row = number of newlines found before the match + 1 (1-indexed) - let row = prefix.lines().count(); + // Ropey line lookup + let line_idx = rope.char_to_line(char_idx); - // 4. Column = character count of the remaining text on the current line + 1 - // (Using .chars().count() ensures it works with multi-byte UTF-8 symbols) - let col = prefix.lines().last().unwrap_or("").chars().count() + 1; + // Line start char index + let line_start = rope.line_to_char(line_idx); - Some((row, col)) + // Column within line + let col_idx = char_idx - line_start; + + // Return 1-indexed + Some((line_idx + 1, col_idx + 1)) }