diff --git a/Cargo.lock b/Cargo.lock index 6a293972..399d7c3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -371,14 +371,11 @@ dependencies = [ ] [[package]] -name = "biome_lsp_converters" +name = "biome_line_index" version = "0.1.0" -source = "git+https://github.com/biomejs/biome?rev=2648fa4201be4afd26f44eca1a4e77aac0a67272#2648fa4201be4afd26f44eca1a4e77aac0a67272" dependencies = [ - "anyhow", - "biome_rowan", + "biome_text_size", "rustc-hash", - "tower-lsp 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1360,7 +1357,7 @@ dependencies = [ "anyhow", "assert_matches", "biome_formatter", - "biome_lsp_converters", + "biome_line_index", "biome_parser", "biome_rowan", "biome_text_size", @@ -1386,7 +1383,7 @@ dependencies = [ "time", "tokio", "tokio-util", - "tower-lsp 0.20.0 (git+https://github.com/lionel-/tower-lsp?branch=bugfix%2Fpatches)", + "tower-lsp", "tracing", "tracing-subscriber", "tree-sitter", @@ -1423,7 +1420,7 @@ dependencies = [ "serde_json", "tokio", "tokio-util", - "tower-lsp 0.20.0 (git+https://github.com/lionel-/tower-lsp?branch=bugfix%2Fpatches)", + "tower-lsp", "tracing", "url", ] @@ -2337,29 +2334,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" -[[package]] -name = "tower-lsp" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ba052b54a6627628d9b3c34c176e7eda8359b7da9acd497b9f20998d118508" -dependencies = [ - "async-trait", - "auto_impl", - "bytes", - "dashmap 5.5.3", - "futures", - "httparse", - "lsp-types", - "memchr", - "serde", - "serde_json", - "tokio", - "tokio-util", - "tower", - "tower-lsp-macros 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tracing", -] - [[package]] name = "tower-lsp" version = "0.20.0" @@ -2378,21 +2352,10 @@ dependencies = [ "tokio", "tokio-util", "tower", - "tower-lsp-macros 0.9.0 (git+https://github.com/lionel-/tower-lsp?branch=bugfix%2Fpatches)", + "tower-lsp-macros", "tracing", ] -[[package]] -name = "tower-lsp-macros" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "tower-lsp-macros" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index ce5ea5b4..f1c2b190 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ air_r_factory = { path = "./crates/air_r_factory" } air_r_formatter = { path = "./crates/air_r_formatter" } air_r_parser = { path = "./crates/air_r_parser" } air_r_syntax = { path = "./crates/air_r_syntax" } +biome_line_index = { path = "./crates/biome_line_index" } biome_ungrammar = { path = "./crates/biome_ungrammar" } comments = { path = "./crates/comments" } crates = { path = "./crates/crates" } @@ -42,7 +43,6 @@ assert_matches = "1.5.0" biome_console = { git = "https://github.com/biomejs/biome", rev = "2648fa4201be4afd26f44eca1a4e77aac0a67272" } biome_diagnostics = { git = "https://github.com/biomejs/biome", rev = "2648fa4201be4afd26f44eca1a4e77aac0a67272" } biome_formatter = { git = "https://github.com/biomejs/biome", rev = "2648fa4201be4afd26f44eca1a4e77aac0a67272" } -biome_lsp_converters = { git = "https://github.com/biomejs/biome", rev = "2648fa4201be4afd26f44eca1a4e77aac0a67272" } biome_parser = { git = "https://github.com/biomejs/biome", rev = "2648fa4201be4afd26f44eca1a4e77aac0a67272" } biome_rowan = { git = "https://github.com/biomejs/biome", rev = "2648fa4201be4afd26f44eca1a4e77aac0a67272" } biome_string_case = { git = "https://github.com/biomejs/biome", rev = "2648fa4201be4afd26f44eca1a4e77aac0a67272" } diff --git a/crates/biome_line_index/Cargo.toml b/crates/biome_line_index/Cargo.toml new file mode 100644 index 00000000..2a6e6da1 --- /dev/null +++ b/crates/biome_line_index/Cargo.toml @@ -0,0 +1,20 @@ +[package] +authors.workspace = true +categories.workspace = true +description = "Biome's tools for converting between byte offsets and line / column positions" +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +name = "biome_line_index" +repository.workspace = true +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +biome_text_size = { workspace = true } +rustc-hash = { workspace = true } + +[lints] +workspace = true diff --git a/crates/biome_line_index/src/lib.rs b/crates/biome_line_index/src/lib.rs new file mode 100644 index 00000000..34f23f5f --- /dev/null +++ b/crates/biome_line_index/src/lib.rs @@ -0,0 +1,171 @@ +//! The crate contains tools for converting between byte offsets and line / column positions. + +#![deny(clippy::use_self)] + +use biome_text_size::TextSize; + +mod line_index; + +pub use line_index::LineIndex; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum WideEncoding { + Utf16, + Utf32, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct LineCol { + /// Zero-based + pub line: u32, + /// Zero-based utf8 offset + pub col: u32, +} + +/// Deliberately not a generic type and different from `LineCol`. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct WideLineCol { + /// Zero-based + pub line: u32, + /// Zero-based + pub col: u32, +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct WideChar { + /// Start offset of a character inside a line, zero-based + pub start: TextSize, + /// End offset of a character inside a line, zero-based + pub end: TextSize, +} + +impl WideChar { + /// Returns the length in 8-bit UTF-8 code units. + fn len(&self) -> TextSize { + self.end - self.start + } + + /// Returns the length in UTF-16 or UTF-32 code units. + fn wide_len(&self, enc: WideEncoding) -> usize { + match enc { + WideEncoding::Utf16 => { + if self.len() == TextSize::from(4) { + 2 + } else { + 1 + } + } + + WideEncoding::Utf32 => 1, + } + } +} + +#[cfg(test)] +mod tests { + use crate::WideEncoding::{Utf16, Utf32}; + use crate::WideLineCol; + use crate::line_index::LineIndex; + use crate::{LineCol, WideEncoding}; + use biome_text_size::TextSize; + + macro_rules! check_conversion { + ($line_index:ident : $wide_line_col:expr => $text_size:expr ) => { + let encoding = WideEncoding::Utf16; + + let line_col = $line_index.to_utf8(encoding, $wide_line_col); + let offset = $line_index.offset(line_col); + assert_eq!(offset, Some($text_size)); + + let line_col = $line_index.line_col(offset.unwrap()); + let wide_line_col = $line_index.to_wide(encoding, line_col.unwrap()); + assert_eq!(wide_line_col, Some($wide_line_col)); + }; + } + + #[test] + fn empty_string() { + let line_index = LineIndex::new(""); + check_conversion!(line_index: WideLineCol { line: 0, col: 0 } => TextSize::from(0)); + } + + #[test] + fn empty_line() { + let line_index = LineIndex::new("\n\n"); + check_conversion!(line_index: WideLineCol { line: 1, col: 0 } => TextSize::from(1)); + } + + #[test] + fn line_end() { + let line_index = LineIndex::new("abc\ndef\nghi"); + check_conversion!(line_index: WideLineCol { line: 1, col: 3 } => TextSize::from(7)); + } + + #[test] + fn out_of_bounds_line() { + let line_index = LineIndex::new("abcde\nfghij\n"); + + let offset = line_index.offset(LineCol { line: 5, col: 0 }); + assert!(offset.is_none()); + } + + #[test] + fn unicode() { + let line_index = LineIndex::new("'Jan 1, 2018 – Jan 1, 2019'"); + + check_conversion!(line_index: WideLineCol { line: 0, col: 0 } => TextSize::from(0)); + check_conversion!(line_index: WideLineCol { line: 0, col: 1 } => TextSize::from(1)); + check_conversion!(line_index: WideLineCol { line: 0, col: 12 } => TextSize::from(12)); + check_conversion!(line_index: WideLineCol { line: 0, col: 13 } => TextSize::from(15)); + check_conversion!(line_index: WideLineCol { line: 0, col: 14 } => TextSize::from(18)); + check_conversion!(line_index: WideLineCol { line: 0, col: 15 } => TextSize::from(21)); + check_conversion!(line_index: WideLineCol { line: 0, col: 26 } => TextSize::from(32)); + check_conversion!(line_index: WideLineCol { line: 0, col: 27 } => TextSize::from(33)); + } + + #[ignore] + #[test] + fn test_every_chars() { + let text: String = { + let mut chars: Vec = ((0 as char)..char::MAX).collect(); + chars.extend("\n".repeat(chars.len() / 16).chars()); + chars.into_iter().collect() + }; + + let line_index = LineIndex::new(&text); + + let mut lin_col = LineCol { line: 0, col: 0 }; + let mut col_utf16 = 0; + let mut col_utf32 = 0; + for (offset, char) in text.char_indices() { + let got_offset = line_index.offset(lin_col).unwrap(); + assert_eq!(usize::from(got_offset), offset); + + let got_lin_col = line_index.line_col(got_offset).unwrap(); + assert_eq!(got_lin_col, lin_col); + + for enc in [Utf16, Utf32] { + let wide_lin_col = line_index.to_wide(enc, lin_col).unwrap(); + let got_lin_col = line_index.to_utf8(enc, wide_lin_col); + assert_eq!(got_lin_col, lin_col); + + let want_col = match enc { + Utf16 => col_utf16, + Utf32 => col_utf32, + }; + assert_eq!(wide_lin_col.col, want_col) + } + + if char == '\n' { + lin_col.line += 1; + lin_col.col = 0; + col_utf16 = 0; + col_utf32 = 0; + } else { + lin_col.col += char.len_utf8() as u32; + col_utf16 += char.len_utf16() as u32; + col_utf32 += 1; + } + } + } +} diff --git a/crates/biome_line_index/src/line_index.rs b/crates/biome_line_index/src/line_index.rs new file mode 100644 index 00000000..f4b7d8c7 --- /dev/null +++ b/crates/biome_line_index/src/line_index.rs @@ -0,0 +1,144 @@ +//! `LineIndex` maps flat `TextSize` offsets into `(Line, Column)` +//! representation. + +use std::mem; + +use biome_text_size::TextSize; +use rustc_hash::FxHashMap; + +use crate::{LineCol, WideChar, WideEncoding, WideLineCol}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct LineIndex { + /// Offset the beginning of each line, zero-based. + pub newlines: Vec, + /// List of non-ASCII characters on each line. + pub line_wide_chars: FxHashMap>, +} + +impl LineIndex { + pub fn new(text: &str) -> Self { + let mut line_wide_chars = FxHashMap::default(); + let mut wide_chars = Vec::new(); + + let mut newlines = vec![TextSize::from(0)]; + + let mut current_col = TextSize::from(0); + + let mut line = 0; + for (offset, char) in text.char_indices() { + let char_size = TextSize::of(char); + + if char == '\n' { + // SAFETY: the conversion from `usize` to `TextSize` can fail if `offset` + // is larger than 2^32. We don't support such large files. + let char_offset = TextSize::try_from(offset).expect("TextSize overflow"); + newlines.push(char_offset + char_size); + + // Save any utf-16 characters seen in the previous line + if !wide_chars.is_empty() { + line_wide_chars.insert(line, mem::take(&mut wide_chars)); + } + + // Prepare for processing the next line + current_col = TextSize::from(0); + line += 1; + continue; + } + + if !char.is_ascii() { + wide_chars.push(WideChar { + start: current_col, + end: current_col + char_size, + }); + } + + current_col += char_size; + } + + // Save any utf-16 characters seen in the last line + if !wide_chars.is_empty() { + line_wide_chars.insert(line, wide_chars); + } + + Self { + newlines, + line_wide_chars, + } + } + + /// Return the number of lines in the index, clamped to [u32::MAX] + pub fn len(&self) -> u32 { + self.newlines.len().try_into().unwrap_or(u32::MAX) + } + + /// Return `true` if the index contains no lines. + pub fn is_empty(&self) -> bool { + self.newlines.is_empty() + } + + pub fn line_col(&self, offset: TextSize) -> Option { + let line = self.newlines.partition_point(|&it| it <= offset) - 1; + let line_start_offset = self.newlines.get(line)?; + let col = offset - line_start_offset; + + Some(LineCol { + line: u32::try_from(line).ok()?, + col: col.into(), + }) + } + + pub fn offset(&self, line_col: LineCol) -> Option { + self.newlines + .get(line_col.line as usize) + .map(|offset| offset + TextSize::from(line_col.col)) + } + + pub fn to_wide(&self, enc: WideEncoding, line_col: LineCol) -> Option { + let col = self.utf8_to_wide_col(enc, line_col.line, line_col.col.into()); + Some(WideLineCol { + line: line_col.line, + col: u32::try_from(col).ok()?, + }) + } + + pub fn to_utf8(&self, enc: WideEncoding, line_col: WideLineCol) -> LineCol { + let col = self.wide_to_utf8_col(enc, line_col.line, line_col.col); + LineCol { + line: line_col.line, + col: col.into(), + } + } + + fn utf8_to_wide_col(&self, enc: WideEncoding, line: u32, col: TextSize) -> usize { + let mut res: usize = col.into(); + if let Some(wide_chars) = self.line_wide_chars.get(&line) { + for c in wide_chars { + if c.end <= col { + res -= usize::from(c.len()) - c.wide_len(enc); + } else { + // From here on, all utf16 characters come *after* the character we are mapping, + // so we don't need to take them into account + break; + } + } + } + res + } + + fn wide_to_utf8_col(&self, enc: WideEncoding, line: u32, mut col: u32) -> TextSize { + if let Some(wide_chars) = self.line_wide_chars.get(&line) { + for c in wide_chars { + if col > u32::from(c.start) { + col += u32::from(c.len()) - c.wide_len(enc) as u32; + } else { + // From here on, all utf16 characters come *after* the character we are mapping, + // so we don't need to take them into account + break; + } + } + } + + col.into() + } +} diff --git a/crates/crates/src/snapshots/crates__tests__crate_names.snap b/crates/crates/src/snapshots/crates__tests__crate_names.snap index 3914755c..1ae047f4 100644 --- a/crates/crates/src/snapshots/crates__tests__crate_names.snap +++ b/crates/crates/src/snapshots/crates__tests__crate_names.snap @@ -9,6 +9,7 @@ expression: AIR_CRATE_NAMES "air_r_formatter", "air_r_parser", "air_r_syntax", + "biome_line_index", "biome_ungrammar", "comments", "crates", diff --git a/crates/lsp/Cargo.toml b/crates/lsp/Cargo.toml index 32685961..7449b441 100644 --- a/crates/lsp/Cargo.toml +++ b/crates/lsp/Cargo.toml @@ -18,7 +18,7 @@ air_r_parser.workspace = true air_r_syntax.workspace = true anyhow.workspace = true biome_formatter.workspace = true -biome_lsp_converters.workspace = true +biome_line_index.workspace = true biome_parser.workspace = true biome_rowan.workspace = true biome_text_size.workspace = true diff --git a/crates/lsp/src/documents.rs b/crates/lsp/src/documents.rs index 3d6160a2..87286bdb 100644 --- a/crates/lsp/src/documents.rs +++ b/crates/lsp/src/documents.rs @@ -5,10 +5,10 @@ // // -use biome_lsp_converters::{line_index, PositionEncoding}; use settings::LineEnding; use tower_lsp::lsp_types; +use crate::proto::PositionEncoding; use crate::rust_analyzer::line_index::LineIndex; use crate::rust_analyzer::utils::apply_document_changes; use crate::settings::DocumentSettings; @@ -67,7 +67,7 @@ impl Document { // Create line index to keep track of newline offsets let line_index = LineIndex { - index: triomphe::Arc::new(line_index::LineIndex::new(&contents)), + index: triomphe::Arc::new(biome_line_index::LineIndex::new(&contents)), endings, encoding: position_encoding, }; @@ -132,7 +132,8 @@ impl Document { self.parse = parse; self.contents = contents; - self.line_index.index = triomphe::Arc::new(line_index::LineIndex::new(&self.contents)); + self.line_index.index = + triomphe::Arc::new(biome_line_index::LineIndex::new(&self.contents)); self.version = Some(new_version); } @@ -242,7 +243,7 @@ mod tests { let mut document = Document::new( "a𐐀b".into(), None, - PositionEncoding::Wide(biome_lsp_converters::WideEncoding::Utf16), + PositionEncoding::Wide(biome_line_index::WideEncoding::Utf16), ); document.on_did_change(utf16_replace_params); assert_eq!(document.contents, "a𐐀bar"); diff --git a/crates/lsp/src/from_proto.rs b/crates/lsp/src/from_proto.rs index a3329d30..585d6373 100644 --- a/crates/lsp/src/from_proto.rs +++ b/crates/lsp/src/from_proto.rs @@ -1,9 +1,49 @@ -pub(crate) use biome_lsp_converters::from_proto::offset; -pub(crate) use biome_lsp_converters::from_proto::text_range; - +use anyhow::Context; +use biome_line_index::LineCol; +use biome_line_index::LineIndex; +use biome_line_index::WideLineCol; use tower_lsp::lsp_types; use crate::documents::Document; +use crate::proto::PositionEncoding; + +/// The function is used to convert a LSP position to TextSize. +/// From `biome_lsp_converters::from_proto::offset()`. +pub(crate) fn offset( + line_index: &LineIndex, + position: lsp_types::Position, + position_encoding: PositionEncoding, +) -> anyhow::Result { + let line_col = match position_encoding { + PositionEncoding::Utf8 => LineCol { + line: position.line, + col: position.character, + }, + PositionEncoding::Wide(enc) => { + let line_col = WideLineCol { + line: position.line, + col: position.character, + }; + line_index.to_utf8(enc, line_col) + } + }; + + line_index + .offset(line_col) + .with_context(|| format!("Position {position:?} is out of range")) +} + +/// The function is used to convert a LSP range to TextRange. +/// From `biome_lsp_converters::from_proto::text_range()`. +pub(crate) fn text_range( + line_index: &LineIndex, + range: lsp_types::Range, + position_encoding: PositionEncoding, +) -> anyhow::Result { + let start = offset(line_index, range.start, position_encoding)?; + let end = offset(line_index, range.end, position_encoding)?; + Ok(biome_text_size::TextRange::new(start, end)) +} pub fn apply_text_edits( doc: &Document, diff --git a/crates/lsp/src/handlers_format.rs b/crates/lsp/src/handlers_format.rs index 91e455d2..a600dcdc 100644 --- a/crates/lsp/src/handlers_format.rs +++ b/crates/lsp/src/handlers_format.rs @@ -276,10 +276,10 @@ fn find_expression_lists(node: &RSyntaxNode, offset: TextSize, end: bool) -> Vec #[cfg(test)] mod tests { use crate::documents::Document; + use crate::proto::PositionEncoding; use crate::test::new_test_client; use crate::test::FileName; use crate::test::TestClientExt; - use biome_lsp_converters::PositionEncoding; use std::path::Path; use tower_lsp::lsp_types::DidChangeWorkspaceFoldersParams; use tower_lsp::lsp_types::WorkspaceFolder; diff --git a/crates/lsp/src/handlers_state.rs b/crates/lsp/src/handlers_state.rs index 37cdd47c..9dcdf729 100644 --- a/crates/lsp/src/handlers_state.rs +++ b/crates/lsp/src/handlers_state.rs @@ -9,8 +9,7 @@ use std::array::IntoIter; use anyhow::anyhow; use anyhow::Context; -use biome_lsp_converters::PositionEncoding; -use biome_lsp_converters::WideEncoding; +use biome_line_index::WideEncoding; use serde_json::Value; use struct_field_names_as_array::FieldNamesAsArray; use tower_lsp::lsp_types; @@ -39,6 +38,7 @@ use crate::documents::Document; use crate::logging; use crate::logging::LogMessageSender; use crate::main_loop::LspState; +use crate::proto::PositionEncoding; use crate::settings::DocumentSettings; use crate::settings::InitializationOptions; use crate::settings_vsc::indent_style_from_vsc; diff --git a/crates/lsp/src/lib.rs b/crates/lsp/src/lib.rs index 0696e011..6b05b322 100644 --- a/crates/lsp/src/lib.rs +++ b/crates/lsp/src/lib.rs @@ -11,6 +11,7 @@ pub mod handlers_state; pub mod logging; pub mod main_loop; pub mod notifications; +pub mod proto; pub mod rust_analyzer; pub mod settings; pub mod settings_vsc; diff --git a/crates/lsp/src/main_loop.rs b/crates/lsp/src/main_loop.rs index 74740af1..95e3a9de 100644 --- a/crates/lsp/src/main_loop.rs +++ b/crates/lsp/src/main_loop.rs @@ -9,8 +9,7 @@ use std::future; use std::pin::Pin; use anyhow::anyhow; -use biome_lsp_converters::PositionEncoding; -use biome_lsp_converters::WideEncoding; +use biome_line_index::WideEncoding; use futures::StreamExt; use tokio::sync::mpsc::unbounded_channel as tokio_unbounded_channel; use tokio::task::JoinHandle; @@ -27,6 +26,7 @@ use crate::handlers_state::ConsoleInputs; use crate::logging; use crate::logging::LogMessageSender; use crate::logging::LogThreadState; +use crate::proto::PositionEncoding; use crate::settings::GlobalSettings; use crate::state::WorldState; use crate::tower_lsp::LspMessage; diff --git a/crates/lsp/src/proto.rs b/crates/lsp/src/proto.rs new file mode 100644 index 00000000..26289746 --- /dev/null +++ b/crates/lsp/src/proto.rs @@ -0,0 +1,5 @@ +#[derive(Clone, Copy, Debug)] +pub enum PositionEncoding { + Utf8, + Wide(biome_line_index::WideEncoding), +} diff --git a/crates/lsp/src/rust_analyzer/line_index.rs b/crates/lsp/src/rust_analyzer/line_index.rs index e46e03f1..b8009b5e 100644 --- a/crates/lsp/src/rust_analyzer/line_index.rs +++ b/crates/lsp/src/rust_analyzer/line_index.rs @@ -7,13 +7,14 @@ //! Enhances `ide::LineIndex` with additional info required to convert offsets //! into lsp positions. -use biome_lsp_converters::line_index; use settings::LineEnding; use triomphe::Arc; +use crate::proto::PositionEncoding; + #[derive(Debug, Clone)] pub struct LineIndex { - pub index: Arc, + pub index: Arc, pub endings: LineEnding, - pub encoding: biome_lsp_converters::PositionEncoding, + pub encoding: PositionEncoding, } diff --git a/crates/lsp/src/rust_analyzer/to_proto.rs b/crates/lsp/src/rust_analyzer/to_proto.rs index 33cae579..e1ffd914 100644 --- a/crates/lsp/src/rust_analyzer/to_proto.rs +++ b/crates/lsp/src/rust_analyzer/to_proto.rs @@ -17,11 +17,7 @@ pub(crate) fn text_edit( line_index: &LineIndex, indel: Indel, ) -> anyhow::Result { - let range = biome_lsp_converters::to_proto::range( - &line_index.index, - indel.delete, - line_index.encoding, - )?; + let range = crate::to_proto::range(line_index, indel.delete, line_index.encoding)?; let new_text = match line_index.endings { LineEnding::Lf => indel.insert, LineEnding::Crlf => indel.insert.replace('\n', "\r\n"), diff --git a/crates/lsp/src/rust_analyzer/utils.rs b/crates/lsp/src/rust_analyzer/utils.rs index e8602a99..dcf4f7a9 100644 --- a/crates/lsp/src/rust_analyzer/utils.rs +++ b/crates/lsp/src/rust_analyzer/utils.rs @@ -6,13 +6,13 @@ use std::ops::Range; -use biome_lsp_converters::line_index; use tower_lsp::lsp_types; use crate::from_proto; +use crate::proto::PositionEncoding; pub(crate) fn apply_document_changes( - encoding: biome_lsp_converters::PositionEncoding, + encoding: PositionEncoding, file_contents: &str, mut content_changes: Vec, ) -> String { @@ -32,7 +32,7 @@ pub(crate) fn apply_document_changes( return text; } - let mut line_index = line_index::LineIndex::new(&text); + let mut line_index = biome_line_index::LineIndex::new(&text); // The changes we got must be applied sequentially, but can cross lines so we // have to keep our line index updated. @@ -44,7 +44,7 @@ pub(crate) fn apply_document_changes( // The None case can't happen as we have handled it above already if let Some(range) = change.range { if index_valid <= range.end.line { - line_index = line_index::LineIndex::new(&text); + line_index = biome_line_index::LineIndex::new(&text); } index_valid = range.start.line; if let Ok(range) = from_proto::text_range(&line_index, range, encoding) { diff --git a/crates/lsp/src/test/client_ext.rs b/crates/lsp/src/test/client_ext.rs index 171daf9b..a7082fd2 100644 --- a/crates/lsp/src/test/client_ext.rs +++ b/crates/lsp/src/test/client_ext.rs @@ -121,7 +121,7 @@ impl TestClientExt for TestClient { ) -> Option> { let lsp_doc = self.open_document(doc, filename).await; - let range = to_proto::range(&doc.line_index.index, range, doc.line_index.encoding).unwrap(); + let range = to_proto::range(&doc.line_index, range, doc.line_index.encoding).unwrap(); self.range_formatting(lsp_types::DocumentRangeFormattingParams { text_document: lsp_types::TextDocumentIdentifier { diff --git a/crates/lsp/src/to_proto.rs b/crates/lsp/src/to_proto.rs index 4e4f0b97..7d979e9e 100644 --- a/crates/lsp/src/to_proto.rs +++ b/crates/lsp/src/to_proto.rs @@ -7,15 +7,57 @@ // Utilites for converting internal types to LSP types +use anyhow::Context; pub(crate) use rust_analyzer::to_proto::text_edit_vec; -#[cfg(test)] -pub(crate) use biome_lsp_converters::to_proto::range; - +use crate::proto::PositionEncoding; use crate::rust_analyzer::{self, line_index::LineIndex, text_edit::TextEdit}; use biome_text_size::TextRange; +use biome_text_size::TextSize; use tower_lsp::lsp_types; +// TODO!: We use `rust_analyzer::LineIndex` here, but `biome_line_index::LineIndex` +// in `from_proto.rs`. We should use `biome_line_index::LineIndex` everywhere, and +// consider getting rid of `rust_analyzer::LineIndex` entirely. + +/// The function is used to convert TextSize to a LSP position. +/// From `biome_lsp_converters::to_proto::position()`. +pub fn position( + line_index: &LineIndex, + offset: TextSize, + position_encoding: PositionEncoding, +) -> anyhow::Result { + let line_col = line_index + .index + .line_col(offset) + .with_context(|| format!("Could not convert offset {offset:?} into a line-column index"))?; + + let position = match position_encoding { + PositionEncoding::Utf8 => lsp_types::Position::new(line_col.line, line_col.col), + PositionEncoding::Wide(enc) => { + let line_col = line_index + .index + .to_wide(enc, line_col) + .with_context(|| format!("Could not convert {line_col:?} into wide line column"))?; + lsp_types::Position::new(line_col.line, line_col.col) + } + }; + + Ok(position) +} + +/// The function is used to convert TextRange to a LSP range. +/// From `biome_lsp_converters::to_proto::range()`. +pub fn range( + line_index: &LineIndex, + range: TextRange, + position_encoding: PositionEncoding, +) -> anyhow::Result { + let start = position(line_index, range.start(), position_encoding)?; + let end = position(line_index, range.end(), position_encoding)?; + Ok(lsp_types::Range::new(start, end)) +} + #[cfg(test)] pub(crate) fn doc_edit_vec( line_index: &LineIndex,