From 307864d9dd1407e7394ad0eec0ca6cd936cbe85a Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 16 Sep 2025 20:45:23 -0500 Subject: [PATCH 1/7] clean up position conversion methods --- crates/djls-workspace/src/document.rs | 101 ++++++++++---------------- 1 file changed, 39 insertions(+), 62 deletions(-) diff --git a/crates/djls-workspace/src/document.rs b/crates/djls-workspace/src/document.rs index 2d538a26..c4158b20 100644 --- a/crates/djls-workspace/src/document.rs +++ b/crates/djls-workspace/src/document.rs @@ -77,9 +77,10 @@ impl TextDocument { #[must_use] pub fn get_text_range(&self, range: Range, encoding: PositionEncoding) -> Option { let start_offset = - self.offset_for_position_with_text(range.start, &self.content, encoding)? as usize; + Self::calculate_offset(&self.line_index, range.start, &self.content, encoding)? + as usize; let end_offset = - self.offset_for_position_with_text(range.end, &self.content, encoding)? as usize; + Self::calculate_offset(&self.line_index, range.end, &self.content, encoding)? as usize; Some(self.content[start_offset..end_offset].to_string()) } @@ -112,20 +113,12 @@ impl TextDocument { if let Some(range) = change.range { // Convert LSP range to byte offsets using the current line index // that matches the current state of new_content - let start_offset = Self::position_to_offset_with( - &new_line_index, - range.start, - &new_content, - encoding, - ) - .unwrap_or(0) as usize; - let end_offset = Self::position_to_offset_with( - &new_line_index, - range.end, - &new_content, - encoding, - ) - .unwrap_or(0) as usize; + let start_offset = + Self::calculate_offset(&new_line_index, range.start, &new_content, encoding) + .unwrap_or(0) as usize; + let end_offset = + Self::calculate_offset(&new_line_index, range.end, &new_content, encoding) + .unwrap_or(0) as usize; // Apply change new_content.replace_range(start_offset..end_offset, &change.text); @@ -144,20 +137,11 @@ impl TextDocument { self.version = version; } - #[must_use] - pub fn position_to_offset( - &self, - position: Position, - encoding: PositionEncoding, - ) -> Option { - self.offset_for_position_with_text(position, &self.content, encoding) - } - - /// Convert position to text offset using a specific line index. + /// Calculate byte offset from an LSP position using the given line index and text. /// - /// This is used during incremental updates where we have a temporary line index - /// that matches the temporary content state. - fn position_to_offset_with( + /// This handles the encoding-aware conversion from LSP positions (line/character) + /// to byte offsets, supporting UTF-8, UTF-16, and UTF-32 encodings. + fn calculate_offset( line_index: &LineIndex, position: Position, text: &str, @@ -229,19 +213,6 @@ impl TextDocument { } } } - - /// Convert position to text offset using the specified encoding. - /// - /// Returns a valid offset, clamping out-of-bounds positions to document/line boundaries. - /// This method uses the document's current line index and content. - fn offset_for_position_with_text( - &self, - position: Position, - text: &str, - encoding: PositionEncoding, - ) -> Option { - Self::position_to_offset_with(&self.line_index, position, text, encoding) - } } #[cfg(test)] @@ -391,20 +362,24 @@ mod tests { let content = "Hello 🌍!\nSecond 行 line"; let doc = TextDocument::new(content.to_string(), 1, LanguageId::HtmlDjango); - // Test position after emoji + // Test position after emoji by extracting text up to that position // "Hello 🌍!" - the 🌍 emoji is 4 UTF-8 bytes but 2 UTF-16 code units - // Position after the emoji should be at UTF-16 position 7 (Hello + space + emoji) - let pos_after_emoji = Position::new(0, 7); - let offset = doc - .position_to_offset(pos_after_emoji, PositionEncoding::Utf16) - .expect("Should get offset"); + // "Hello " = 6 UTF-16 units, emoji = 2 UTF-16 units, so position 8 is after emoji + let range_to_after_emoji = Range::new(Position::new(0, 0), Position::new(0, 8)); + let text_to_after_emoji = doc + .get_text_range(range_to_after_emoji, PositionEncoding::Utf16) + .expect("Should get text range"); + assert_eq!(text_to_after_emoji, "Hello 🌍"); - // The UTF-8 byte offset should be at the "!" character - assert_eq!(doc.content().chars().nth(7).unwrap(), '!'); - assert_eq!(&doc.content()[(offset as usize)..=(offset as usize)], "!"); + // Verify the next character is "!" + let range_exclamation = Range::new(Position::new(0, 8), Position::new(0, 9)); + let exclamation = doc + .get_text_range(range_exclamation, PositionEncoding::Utf16) + .expect("Should get exclamation"); + assert_eq!(exclamation, "!"); // Test range extraction with non-ASCII characters - let range = Range::new(Position::new(0, 0), Position::new(0, 7)); + let range = Range::new(Position::new(0, 0), Position::new(0, 8)); let text = doc .get_text_range(range, PositionEncoding::Utf16) .expect("Should get text range"); @@ -413,16 +388,18 @@ mod tests { // Test position on second line with CJK character // "Second 行 line" - 行 is 3 UTF-8 bytes but 1 UTF-16 code unit // Position after the CJK character should be at UTF-16 position 8 - let pos_after_cjk = Position::new(1, 8); - let offset_cjk = doc - .position_to_offset(pos_after_cjk, PositionEncoding::Utf16) - .expect("Should get offset"); - - // Find the start of line 2 in UTF-8 bytes - let line2_start = doc.content().find('\n').unwrap() + 1; - let line2_offset = offset_cjk as usize - line2_start; - let line2 = &doc.content()[line2_start..]; - assert_eq!(&line2[line2_offset..=line2_offset], " "); + let range_to_after_cjk = Range::new(Position::new(1, 0), Position::new(1, 8)); + let text_to_after_cjk = doc + .get_text_range(range_to_after_cjk, PositionEncoding::Utf16) + .expect("Should get text range"); + assert_eq!(text_to_after_cjk, "Second 行"); + + // Verify the next character is a space + let range_space = Range::new(Position::new(1, 8), Position::new(1, 9)); + let space = doc + .get_text_range(range_space, PositionEncoding::Utf16) + .expect("Should get space"); + assert_eq!(space, " "); } #[test] From e4d773ea9f00e0e22d1243801924a285648b74e7 Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 16 Sep 2025 21:19:13 -0500 Subject: [PATCH 2/7] wip --- crates/djls-server/src/server.rs | 8 +- crates/djls-server/src/session.rs | 2 +- crates/djls-source/src/lib.rs | 2 + crates/djls-source/src/position.rs | 41 ++++++ crates/djls-source/src/protocol.rs | 185 +++++++++++++++++++++++++ crates/djls-workspace/src/document.rs | 74 +--------- crates/djls-workspace/src/encoding.rs | 175 ++++++++--------------- crates/djls-workspace/src/lib.rs | 5 +- crates/djls-workspace/src/workspace.rs | 4 +- 9 files changed, 306 insertions(+), 190 deletions(-) create mode 100644 crates/djls-source/src/protocol.rs diff --git a/crates/djls-server/src/server.rs b/crates/djls-server/src/server.rs index 2e1da39d..89d4a277 100644 --- a/crates/djls-server/src/server.rs +++ b/crates/djls-server/src/server.rs @@ -157,7 +157,7 @@ impl LanguageServer for DjangoLanguageServer { save: Some(lsp_types::SaveOptions::default().into()), }, )), - position_encoding: Some(lsp_types::PositionEncodingKind::from(encoding)), + position_encoding: Some(djls_workspace::position_encoding_to_lsp(encoding)), diagnostic_provider: Some(lsp_types::DiagnosticServerCapabilities::Options( lsp_types::DiagnosticOptions { identifier: None, @@ -172,7 +172,11 @@ impl LanguageServer for DjangoLanguageServer { name: SERVER_NAME.to_string(), version: Some(SERVER_VERSION.to_string()), }), - offset_encoding: Some(encoding.to_string()), + offset_encoding: Some(match encoding { + djls_workspace::PositionEncoding::Utf8 => "utf-8".to_string(), + djls_workspace::PositionEncoding::Utf16 => "utf-16".to_string(), + djls_workspace::PositionEncoding::Utf32 => "utf-32".to_string(), + }), }) } diff --git a/crates/djls-server/src/session.rs b/crates/djls-server/src/session.rs index f3cf3025..ff0096f0 100644 --- a/crates/djls-server/src/session.rs +++ b/crates/djls-server/src/session.rs @@ -77,7 +77,7 @@ impl Session { settings, workspace, client_capabilities: params.capabilities.clone(), - position_encoding: PositionEncoding::negotiate(params), + position_encoding: djls_workspace::negotiate_position_encoding(params), db, } } diff --git a/crates/djls-source/src/lib.rs b/crates/djls-source/src/lib.rs index b2227aef..3f290be3 100644 --- a/crates/djls-source/src/lib.rs +++ b/crates/djls-source/src/lib.rs @@ -1,6 +1,7 @@ mod db; mod file; mod position; +mod protocol; pub use db::Db; pub use file::File; @@ -9,3 +10,4 @@ pub use position::ByteOffset; pub use position::LineCol; pub use position::LineIndex; pub use position::Span; +pub use protocol::PositionEncoding; diff --git a/crates/djls-source/src/position.rs b/crates/djls-source/src/position.rs index 385d6ef7..63e99e54 100644 --- a/crates/djls-source/src/position.rs +++ b/crates/djls-source/src/position.rs @@ -120,6 +120,7 @@ impl LineIndex { #[cfg(test)] mod tests { use super::*; + use crate::protocol::PositionEncoding; #[test] fn test_line_index_unix_endings() { @@ -164,4 +165,44 @@ mod tests { assert_eq!(index.to_line_col(ByteOffset(7)), LineCol((1, 0))); assert_eq!(index.to_line_col(ByteOffset(8)), LineCol((1, 1))); } + + #[test] + fn test_line_col_to_offset_utf16() { + let text = "Hello 🌍 world"; + let index = LineIndex::from_text(text); + + // "Hello " = 6 UTF-16 units, "🌍" = 2 UTF-16 units + // So position (0, 8) in UTF-16 should be after the emoji + let offset = index + .line_col_to_offset(LineCol((0, 8)), text, PositionEncoding::Utf16) + .expect("Should get offset"); + assert_eq!(offset, ByteOffset(10)); // "Hello 🌍" is 10 bytes + + // In UTF-8, character 10 would be at the 'r' in 'world' + let offset_utf8 = index + .line_col_to_offset(LineCol((0, 10)), text, PositionEncoding::Utf8) + .expect("Should get offset"); + assert_eq!(offset_utf8, ByteOffset(10)); + } + + #[test] + fn test_line_col_to_offset_ascii_fast_path() { + let text = "Hello world"; + let index = LineIndex::from_text(text); + + // For ASCII text, all encodings should give the same result + let offset_utf8 = index + .line_col_to_offset(LineCol((0, 5)), text, PositionEncoding::Utf8) + .expect("Should get offset"); + let offset_utf16 = index + .line_col_to_offset(LineCol((0, 5)), text, PositionEncoding::Utf16) + .expect("Should get offset"); + let offset_utf32 = index + .line_col_to_offset(LineCol((0, 5)), text, PositionEncoding::Utf32) + .expect("Should get offset"); + + assert_eq!(offset_utf8, ByteOffset(5)); + assert_eq!(offset_utf16, ByteOffset(5)); + assert_eq!(offset_utf32, ByteOffset(5)); + } } diff --git a/crates/djls-source/src/protocol.rs b/crates/djls-source/src/protocol.rs new file mode 100644 index 00000000..a1d7e98b --- /dev/null +++ b/crates/djls-source/src/protocol.rs @@ -0,0 +1,185 @@ +/// Protocol-specific text position handling. +/// +/// This module provides types and functions for converting between different +/// text position representations used by various protocols and editors. +use crate::position::ByteOffset; +/// Protocol-specific text position handling. +/// +/// This module provides types and functions for converting between different +/// text position representations used by various protocols and editors. +use crate::position::LineCol; +/// Protocol-specific text position handling. +/// +/// This module provides types and functions for converting between different +/// text position representations used by various protocols and editors. +use crate::position::LineIndex; + +/// Specifies how column positions are counted in text. +/// +/// While motivated by LSP (Language Server Protocol) requirements, this enum +/// represents a fundamental choice about text position measurement that any +/// text processing system must make. Different systems count "column" positions +/// differently: +/// +/// - Some count bytes (fast but breaks on multi-byte characters) +/// - Some count UTF-16 code units (common in JavaScript/Windows ecosystems) +/// - Some count Unicode codepoints (intuitive but slower) +/// +/// This crate provides encoding-aware position conversion to support different +/// client expectations without coupling to specific protocol implementations. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum PositionEncoding { + /// Column positions count UTF-8 code units (bytes from line start) + Utf8, + /// Column positions count UTF-16 code units (common in VS Code and Windows editors) + #[default] + Utf16, + /// Column positions count Unicode scalar values (codepoints) + Utf32, +} + +impl LineIndex { + /// Convert a line/column position to a byte offset with encoding awareness. + /// + /// The `encoding` parameter specifies how the column value should be interpreted: + /// - `PositionEncoding::Utf8`: column is a byte offset from line start + /// - `PositionEncoding::Utf16`: column counts UTF-16 code units + /// - `PositionEncoding::Utf32`: column counts Unicode codepoints + /// + /// This method is primarily used to convert protocol-specific positions + /// (which may use different column counting methods) into byte offsets + /// that can be used to index into the actual UTF-8 text. + /// + /// # Examples + /// + /// ``` + /// # use djls_source::{LineIndex, LineCol, ByteOffset, PositionEncoding}; + /// let text = "Hello 🌍 world"; + /// let index = LineIndex::from_text(text); + /// + /// // UTF-16: "Hello " (6) + "🌍" (2 UTF-16 units) = position 8 + /// let offset = index.line_col_to_offset( + /// LineCol((0, 8)), + /// text, + /// PositionEncoding::Utf16 + /// ); + /// assert_eq!(offset, Some(ByteOffset(10))); // "Hello 🌍" is 10 bytes + /// ``` + #[must_use] + pub fn line_col_to_offset( + &self, + line_col: LineCol, + text: &str, + encoding: PositionEncoding, + ) -> Option { + let line = line_col.line(); + let character = line_col.column(); + + // Handle line bounds - if line > line_count, return document length + let line_start_utf8 = match self.lines().get(line as usize) { + Some(start) => *start, + None => return Some(ByteOffset(u32::try_from(text.len()).unwrap_or(u32::MAX))), + }; + + if character == 0 { + return Some(ByteOffset(line_start_utf8)); + } + + let next_line_start = self + .lines() + .get(line as usize + 1) + .copied() + .unwrap_or_else(|| u32::try_from(text.len()).unwrap_or(u32::MAX)); + + let line_text = text.get(line_start_utf8 as usize..next_line_start as usize)?; + + // Fast path optimization for ASCII text, all encodings are equivalent to byte offsets + if line_text.is_ascii() { + let char_offset = character.min(u32::try_from(line_text.len()).unwrap_or(u32::MAX)); + return Some(ByteOffset(line_start_utf8 + char_offset)); + } + + match encoding { + PositionEncoding::Utf8 => { + // UTF-8: character positions are already byte offsets + let char_offset = character.min(u32::try_from(line_text.len()).unwrap_or(u32::MAX)); + Some(ByteOffset(line_start_utf8 + char_offset)) + } + PositionEncoding::Utf16 => { + // UTF-16: count UTF-16 code units + let mut utf16_pos = 0; + let mut utf8_pos = 0; + + for c in line_text.chars() { + if utf16_pos >= character { + break; + } + utf16_pos += u32::try_from(c.len_utf16()).unwrap_or(0); + utf8_pos += u32::try_from(c.len_utf8()).unwrap_or(0); + } + + // If character position exceeds line length, clamp to line end + Some(ByteOffset(line_start_utf8 + utf8_pos)) + } + PositionEncoding::Utf32 => { + // UTF-32: count Unicode code points (characters) + let mut utf8_pos = 0; + + for (char_count, c) in line_text.chars().enumerate() { + if char_count >= character as usize { + break; + } + utf8_pos += u32::try_from(c.len_utf8()).unwrap_or(0); + } + + // If character position exceeds line length, clamp to line end + Some(ByteOffset(line_start_utf8 + utf8_pos)) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_line_col_to_offset_utf16() { + let text = "Hello 🌍 world"; + let index = LineIndex::from_text(text); + + // "Hello " = 6 UTF-16 units, "🌍" = 2 UTF-16 units + // So position (0, 8) in UTF-16 should be after the emoji + let offset = index + .line_col_to_offset(LineCol((0, 8)), text, PositionEncoding::Utf16) + .expect("Should get offset"); + assert_eq!(offset, ByteOffset(10)); // "Hello 🌍" is 10 bytes + + // In UTF-8, character 10 would be at the 'r' in 'world' + let offset_utf8 = index + .line_col_to_offset(LineCol((0, 10)), text, PositionEncoding::Utf8) + .expect("Should get offset"); + assert_eq!(offset_utf8, ByteOffset(10)); + } + + #[test] + fn test_line_col_to_offset_ascii_fast_path() { + let text = "Hello world"; + let index = LineIndex::from_text(text); + + // For ASCII text, all encodings should give the same result + let offset_utf8 = index + .line_col_to_offset(LineCol((0, 5)), text, PositionEncoding::Utf8) + .expect("Should get offset"); + let offset_utf16 = index + .line_col_to_offset(LineCol((0, 5)), text, PositionEncoding::Utf16) + .expect("Should get offset"); + let offset_utf32 = index + .line_col_to_offset(LineCol((0, 5)), text, PositionEncoding::Utf32) + .expect("Should get offset"); + + assert_eq!(offset_utf8, ByteOffset(5)); + assert_eq!(offset_utf16, ByteOffset(5)); + assert_eq!(offset_utf32, ByteOffset(5)); + } +} diff --git a/crates/djls-workspace/src/document.rs b/crates/djls-workspace/src/document.rs index c4158b20..773375ce 100644 --- a/crates/djls-workspace/src/document.rs +++ b/crates/djls-workspace/src/document.rs @@ -6,10 +6,10 @@ //! and diagnostics. use djls_source::LineIndex; +use djls_source::PositionEncoding; use tower_lsp_server::lsp_types::Position; use tower_lsp_server::lsp_types::Range; -use crate::encoding::PositionEncoding; use crate::language::LanguageId; /// In-memory representation of an open document in the LSP. @@ -139,79 +139,17 @@ impl TextDocument { /// Calculate byte offset from an LSP position using the given line index and text. /// - /// This handles the encoding-aware conversion from LSP positions (line/character) - /// to byte offsets, supporting UTF-8, UTF-16, and UTF-32 encodings. + /// This delegates to the encoding-aware conversion in `djls_source`. fn calculate_offset( line_index: &LineIndex, position: Position, text: &str, encoding: PositionEncoding, ) -> Option { - // Handle line bounds - if line > line_count, return document length - let line_start_utf8 = match line_index.lines().get(position.line as usize) { - Some(start) => *start, - None => return Some(u32::try_from(text.len()).unwrap_or(u32::MAX)), // Past end of document - }; - - if position.character == 0 { - return Some(line_start_utf8); - } - - let next_line_start = line_index - .lines() - .get(position.line as usize + 1) - .copied() - .unwrap_or_else(|| u32::try_from(text.len()).unwrap_or(u32::MAX)); - - let line_text = text.get(line_start_utf8 as usize..next_line_start as usize)?; - - // Fast path optimization for ASCII text, all encodings are equivalent to byte offsets - if line_text.is_ascii() { - let char_offset = position - .character - .min(u32::try_from(line_text.len()).unwrap_or(u32::MAX)); - return Some(line_start_utf8 + char_offset); - } - - match encoding { - PositionEncoding::Utf8 => { - // UTF-8: character positions are already byte offsets - let char_offset = position - .character - .min(u32::try_from(line_text.len()).unwrap_or(u32::MAX)); - Some(line_start_utf8 + char_offset) - } - PositionEncoding::Utf16 => { - // UTF-16: count UTF-16 code units - let mut utf16_pos = 0; - let mut utf8_pos = 0; - - for c in line_text.chars() { - if utf16_pos >= position.character { - break; - } - utf16_pos += u32::try_from(c.len_utf16()).unwrap_or(0); - utf8_pos += u32::try_from(c.len_utf8()).unwrap_or(0); - } - - // If character position exceeds line length, clamp to line end - Some(line_start_utf8 + utf8_pos) - } - PositionEncoding::Utf32 => { - // UTF-32: count Unicode code points (characters) - let mut utf8_pos = 0; - - for (char_count, c) in line_text.chars().enumerate() { - if char_count >= position.character as usize { - break; - } - utf8_pos += u32::try_from(c.len_utf8()).unwrap_or(0); - } - - // If character position exceeds line length, clamp to line end - Some(line_start_utf8 + utf8_pos) - } - } + let line_col = djls_source::LineCol((position.line, position.character)); + line_index + .line_col_to_offset(line_col, text, encoding) + .map(|djls_source::ByteOffset(offset)| offset) } } diff --git a/crates/djls-workspace/src/encoding.rs b/crates/djls-workspace/src/encoding.rs index 93cda1f1..888c31a7 100644 --- a/crates/djls-workspace/src/encoding.rs +++ b/crates/djls-workspace/src/encoding.rs @@ -1,86 +1,52 @@ -use std::fmt; -use std::str::FromStr; - +use djls_source::PositionEncoding; use tower_lsp_server::lsp_types::InitializeParams; use tower_lsp_server::lsp_types::PositionEncodingKind; -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] -pub enum PositionEncoding { - Utf8, - #[default] - Utf16, - Utf32, -} - -impl PositionEncoding { - /// Negotiate the best encoding with the client based on their capabilities. - /// Prefers UTF-8 > UTF-32 > UTF-16 for performance reasons. - pub fn negotiate(params: &InitializeParams) -> Self { - let client_encodings: &[PositionEncodingKind] = params - .capabilities - .general - .as_ref() - .and_then(|general| general.position_encodings.as_ref()) - .map_or(&[], |encodings| encodings.as_slice()); - - // Try to find the best encoding in preference order - for preferred in [ - PositionEncoding::Utf8, - PositionEncoding::Utf32, - PositionEncoding::Utf16, - ] { - if client_encodings - .iter() - .any(|kind| PositionEncoding::try_from(kind.clone()).ok() == Some(preferred)) - { - return preferred; - } +/// Negotiate the best encoding with the client based on their capabilities. +/// Prefers UTF-8 > UTF-32 > UTF-16 for performance reasons. +pub fn negotiate_position_encoding(params: &InitializeParams) -> PositionEncoding { + let client_encodings: &[PositionEncodingKind] = params + .capabilities + .general + .as_ref() + .and_then(|general| general.position_encodings.as_ref()) + .map_or(&[], |encodings| encodings.as_slice()); + + // Try to find the best encoding in preference order + for preferred in [ + PositionEncoding::Utf8, + PositionEncoding::Utf32, + PositionEncoding::Utf16, + ] { + if client_encodings + .iter() + .any(|kind| position_encoding_from_lsp(kind) == Some(preferred)) + { + return preferred; } - - // Fallback to UTF-16 if client doesn't specify encodings - PositionEncoding::Utf16 } -} - -impl FromStr for PositionEncoding { - type Err = (); - fn from_str(s: &str) -> Result { - match s { - "utf-8" => Ok(PositionEncoding::Utf8), - "utf-16" => Ok(PositionEncoding::Utf16), - "utf-32" => Ok(PositionEncoding::Utf32), - _ => Err(()), - } - } -} - -impl fmt::Display for PositionEncoding { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = match self { - PositionEncoding::Utf8 => "utf-8", - PositionEncoding::Utf16 => "utf-16", - PositionEncoding::Utf32 => "utf-32", - }; - write!(f, "{s}") - } + // Fallback to UTF-16 if client doesn't specify encodings + PositionEncoding::Utf16 } -impl From for PositionEncodingKind { - fn from(encoding: PositionEncoding) -> Self { - match encoding { - PositionEncoding::Utf8 => PositionEncodingKind::new("utf-8"), - PositionEncoding::Utf16 => PositionEncodingKind::new("utf-16"), - PositionEncoding::Utf32 => PositionEncodingKind::new("utf-32"), - } +// Helper functions to convert between LSP types and our PositionEncoding +#[must_use] +pub fn position_encoding_to_lsp(encoding: PositionEncoding) -> PositionEncodingKind { + match encoding { + PositionEncoding::Utf8 => PositionEncodingKind::new("utf-8"), + PositionEncoding::Utf16 => PositionEncodingKind::new("utf-16"), + PositionEncoding::Utf32 => PositionEncodingKind::new("utf-32"), } } -impl TryFrom for PositionEncoding { - type Error = (); - - fn try_from(kind: PositionEncodingKind) -> Result { - kind.as_str().parse() +#[must_use] +pub fn position_encoding_from_lsp(kind: &PositionEncodingKind) -> Option { + match kind.as_str() { + "utf-8" => Some(PositionEncoding::Utf8), + "utf-16" => Some(PositionEncoding::Utf16), + "utf-32" => Some(PositionEncoding::Utf32), + _ => None, } } @@ -92,61 +58,38 @@ mod tests { use super::*; #[test] - fn test_string_parsing_and_display() { - // Valid encodings parse correctly + fn test_lsp_type_conversions() { + // position_encoding_from_lsp for valid encodings assert_eq!( - "utf-8".parse::(), - Ok(PositionEncoding::Utf8) + position_encoding_from_lsp(&PositionEncodingKind::new("utf-8")), + Some(PositionEncoding::Utf8) ); assert_eq!( - "utf-16".parse::(), - Ok(PositionEncoding::Utf16) + position_encoding_from_lsp(&PositionEncodingKind::new("utf-16")), + Some(PositionEncoding::Utf16) ); assert_eq!( - "utf-32".parse::(), - Ok(PositionEncoding::Utf32) + position_encoding_from_lsp(&PositionEncodingKind::new("utf-32")), + Some(PositionEncoding::Utf32) ); - // Invalid encoding returns error - assert!("invalid".parse::().is_err()); - assert!("UTF-8".parse::().is_err()); // case sensitive - - // Display produces correct strings - assert_eq!(PositionEncoding::Utf8.to_string(), "utf-8"); - assert_eq!(PositionEncoding::Utf16.to_string(), "utf-16"); - assert_eq!(PositionEncoding::Utf32.to_string(), "utf-32"); - } - - #[test] - fn test_lsp_type_conversions() { - // TryFrom for valid encodings - assert_eq!( - PositionEncoding::try_from(PositionEncodingKind::new("utf-8")), - Ok(PositionEncoding::Utf8) - ); + // Invalid encoding returns None assert_eq!( - PositionEncoding::try_from(PositionEncodingKind::new("utf-16")), - Ok(PositionEncoding::Utf16) + position_encoding_from_lsp(&PositionEncodingKind::new("unknown")), + None ); - assert_eq!( - PositionEncoding::try_from(PositionEncodingKind::new("utf-32")), - Ok(PositionEncoding::Utf32) - ); - - // Invalid encoding returns error - assert!(PositionEncoding::try_from(PositionEncodingKind::new("unknown")).is_err()); - // From produces correct LSP types + // position_encoding_to_lsp produces correct LSP types assert_eq!( - PositionEncodingKind::from(PositionEncoding::Utf8).as_str(), + position_encoding_to_lsp(PositionEncoding::Utf8).as_str(), "utf-8" ); assert_eq!( - PositionEncodingKind::from(PositionEncoding::Utf16).as_str(), + position_encoding_to_lsp(PositionEncoding::Utf16).as_str(), "utf-16" ); assert_eq!( - PositionEncodingKind::from(PositionEncoding::Utf32).as_str(), + position_encoding_to_lsp(PositionEncoding::Utf32).as_str(), "utf-32" ); } @@ -168,7 +111,7 @@ mod tests { ..Default::default() }; - assert_eq!(PositionEncoding::negotiate(¶ms), PositionEncoding::Utf8); + assert_eq!(negotiate_position_encoding(¶ms), PositionEncoding::Utf8); } #[test] @@ -188,7 +131,7 @@ mod tests { }; assert_eq!( - PositionEncoding::negotiate(¶ms), + negotiate_position_encoding(¶ms), PositionEncoding::Utf32 ); } @@ -207,7 +150,7 @@ mod tests { }; assert_eq!( - PositionEncoding::negotiate(¶ms), + negotiate_position_encoding(¶ms), PositionEncoding::Utf16 ); } @@ -226,7 +169,7 @@ mod tests { }; assert_eq!( - PositionEncoding::negotiate(¶ms), + negotiate_position_encoding(¶ms), PositionEncoding::Utf16 ); } @@ -235,7 +178,7 @@ mod tests { fn test_negotiate_fallback_with_no_capabilities() { let params = InitializeParams::default(); assert_eq!( - PositionEncoding::negotiate(¶ms), + negotiate_position_encoding(¶ms), PositionEncoding::Utf16 ); } @@ -257,7 +200,7 @@ mod tests { }; assert_eq!( - PositionEncoding::negotiate(¶ms), + negotiate_position_encoding(¶ms), PositionEncoding::Utf16 ); } diff --git a/crates/djls-workspace/src/lib.rs b/crates/djls-workspace/src/lib.rs index d28a48e9..dffc8af9 100644 --- a/crates/djls-workspace/src/lib.rs +++ b/crates/djls-workspace/src/lib.rs @@ -23,8 +23,11 @@ mod workspace; pub use buffers::Buffers; pub use db::Db; +pub use djls_source::PositionEncoding; pub use document::TextDocument; -pub use encoding::PositionEncoding; +pub use encoding::negotiate_position_encoding; +pub use encoding::position_encoding_from_lsp; +pub use encoding::position_encoding_to_lsp; pub use fs::FileSystem; pub use fs::InMemoryFileSystem; pub use fs::OsFileSystem; diff --git a/crates/djls-workspace/src/workspace.rs b/crates/djls-workspace/src/workspace.rs index 500be313..1328d61b 100644 --- a/crates/djls-workspace/src/workspace.rs +++ b/crates/djls-workspace/src/workspace.rs @@ -110,7 +110,7 @@ impl Workspace { url: &Url, changes: Vec, version: i32, - encoding: crate::encoding::PositionEncoding, + encoding: djls_source::PositionEncoding, ) -> Option { if let Some(mut document) = self.buffers.get(url) { document.update(changes, version, encoding); @@ -204,11 +204,11 @@ mod tests { use camino::Utf8Path; use camino::Utf8PathBuf; + use djls_source::PositionEncoding; use tempfile::tempdir; use url::Url; use super::*; - use crate::encoding::PositionEncoding; use crate::LanguageId; #[salsa::db] From 8332b0b6a6e4bc1a8203adfdb4ca7c8c7fbd2998 Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 16 Sep 2025 21:21:39 -0500 Subject: [PATCH 3/7] fix --- crates/djls-server/src/server.rs | 6 +----- crates/djls-source/src/protocol.rs | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/crates/djls-server/src/server.rs b/crates/djls-server/src/server.rs index 89d4a277..f92504c8 100644 --- a/crates/djls-server/src/server.rs +++ b/crates/djls-server/src/server.rs @@ -172,11 +172,7 @@ impl LanguageServer for DjangoLanguageServer { name: SERVER_NAME.to_string(), version: Some(SERVER_VERSION.to_string()), }), - offset_encoding: Some(match encoding { - djls_workspace::PositionEncoding::Utf8 => "utf-8".to_string(), - djls_workspace::PositionEncoding::Utf16 => "utf-16".to_string(), - djls_workspace::PositionEncoding::Utf32 => "utf-32".to_string(), - }), + offset_encoding: Some(encoding.to_string()), }) } diff --git a/crates/djls-source/src/protocol.rs b/crates/djls-source/src/protocol.rs index a1d7e98b..3b1a7b1b 100644 --- a/crates/djls-source/src/protocol.rs +++ b/crates/djls-source/src/protocol.rs @@ -1,3 +1,5 @@ +use std::fmt; + /// Protocol-specific text position handling. /// /// This module provides types and functions for converting between different @@ -38,6 +40,16 @@ pub enum PositionEncoding { Utf32, } +impl fmt::Display for PositionEncoding { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Utf8 => write!(f, "utf-8"), + Self::Utf16 => write!(f, "utf-16"), + Self::Utf32 => write!(f, "utf-32"), + } + } +} + impl LineIndex { /// Convert a line/column position to a byte offset with encoding awareness. /// @@ -162,6 +174,13 @@ mod tests { assert_eq!(offset_utf8, ByteOffset(10)); } + #[test] + fn test_position_encoding_display() { + assert_eq!(PositionEncoding::Utf8.to_string(), "utf-8"); + assert_eq!(PositionEncoding::Utf16.to_string(), "utf-16"); + assert_eq!(PositionEncoding::Utf32.to_string(), "utf-32"); + } + #[test] fn test_line_col_to_offset_ascii_fast_path() { let text = "Hello world"; From 07475b37685990443df4bad6e530ae14598363cf Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 16 Sep 2025 21:41:09 -0500 Subject: [PATCH 4/7] silly --- crates/djls-source/src/position.rs | 41 ----------------- crates/djls-source/src/protocol.rs | 64 +++++++++++---------------- crates/djls-workspace/src/document.rs | 4 +- 3 files changed, 28 insertions(+), 81 deletions(-) diff --git a/crates/djls-source/src/position.rs b/crates/djls-source/src/position.rs index 63e99e54..385d6ef7 100644 --- a/crates/djls-source/src/position.rs +++ b/crates/djls-source/src/position.rs @@ -120,7 +120,6 @@ impl LineIndex { #[cfg(test)] mod tests { use super::*; - use crate::protocol::PositionEncoding; #[test] fn test_line_index_unix_endings() { @@ -165,44 +164,4 @@ mod tests { assert_eq!(index.to_line_col(ByteOffset(7)), LineCol((1, 0))); assert_eq!(index.to_line_col(ByteOffset(8)), LineCol((1, 1))); } - - #[test] - fn test_line_col_to_offset_utf16() { - let text = "Hello 🌍 world"; - let index = LineIndex::from_text(text); - - // "Hello " = 6 UTF-16 units, "🌍" = 2 UTF-16 units - // So position (0, 8) in UTF-16 should be after the emoji - let offset = index - .line_col_to_offset(LineCol((0, 8)), text, PositionEncoding::Utf16) - .expect("Should get offset"); - assert_eq!(offset, ByteOffset(10)); // "Hello 🌍" is 10 bytes - - // In UTF-8, character 10 would be at the 'r' in 'world' - let offset_utf8 = index - .line_col_to_offset(LineCol((0, 10)), text, PositionEncoding::Utf8) - .expect("Should get offset"); - assert_eq!(offset_utf8, ByteOffset(10)); - } - - #[test] - fn test_line_col_to_offset_ascii_fast_path() { - let text = "Hello world"; - let index = LineIndex::from_text(text); - - // For ASCII text, all encodings should give the same result - let offset_utf8 = index - .line_col_to_offset(LineCol((0, 5)), text, PositionEncoding::Utf8) - .expect("Should get offset"); - let offset_utf16 = index - .line_col_to_offset(LineCol((0, 5)), text, PositionEncoding::Utf16) - .expect("Should get offset"); - let offset_utf32 = index - .line_col_to_offset(LineCol((0, 5)), text, PositionEncoding::Utf32) - .expect("Should get offset"); - - assert_eq!(offset_utf8, ByteOffset(5)); - assert_eq!(offset_utf16, ByteOffset(5)); - assert_eq!(offset_utf32, ByteOffset(5)); - } } diff --git a/crates/djls-source/src/protocol.rs b/crates/djls-source/src/protocol.rs index 3b1a7b1b..9667d490 100644 --- a/crates/djls-source/src/protocol.rs +++ b/crates/djls-source/src/protocol.rs @@ -1,19 +1,7 @@ use std::fmt; -/// Protocol-specific text position handling. -/// -/// This module provides types and functions for converting between different -/// text position representations used by various protocols and editors. use crate::position::ByteOffset; -/// Protocol-specific text position handling. -/// -/// This module provides types and functions for converting between different -/// text position representations used by various protocols and editors. use crate::position::LineCol; -/// Protocol-specific text position handling. -/// -/// This module provides types and functions for converting between different -/// text position representations used by various protocols and editors. use crate::position::LineIndex; /// Specifies how column positions are counted in text. @@ -50,10 +38,10 @@ impl fmt::Display for PositionEncoding { } } -impl LineIndex { +impl PositionEncoding { /// Convert a line/column position to a byte offset with encoding awareness. /// - /// The `encoding` parameter specifies how the column value should be interpreted: + /// The encoding specifies how the column value should be interpreted: /// - `PositionEncoding::Utf8`: column is a byte offset from line start /// - `PositionEncoding::Utf16`: column counts UTF-16 code units /// - `PositionEncoding::Utf32`: column counts Unicode codepoints @@ -70,25 +58,25 @@ impl LineIndex { /// let index = LineIndex::from_text(text); /// /// // UTF-16: "Hello " (6) + "🌍" (2 UTF-16 units) = position 8 - /// let offset = index.line_col_to_offset( + /// let offset = PositionEncoding::Utf16.line_col_to_offset( + /// &index, /// LineCol((0, 8)), - /// text, - /// PositionEncoding::Utf16 + /// text /// ); /// assert_eq!(offset, Some(ByteOffset(10))); // "Hello 🌍" is 10 bytes /// ``` #[must_use] pub fn line_col_to_offset( &self, + index: &LineIndex, line_col: LineCol, text: &str, - encoding: PositionEncoding, ) -> Option { let line = line_col.line(); let character = line_col.column(); // Handle line bounds - if line > line_count, return document length - let line_start_utf8 = match self.lines().get(line as usize) { + let line_start_utf8 = match index.lines().get(line as usize) { Some(start) => *start, None => return Some(ByteOffset(u32::try_from(text.len()).unwrap_or(u32::MAX))), }; @@ -97,7 +85,7 @@ impl LineIndex { return Some(ByteOffset(line_start_utf8)); } - let next_line_start = self + let next_line_start = index .lines() .get(line as usize + 1) .copied() @@ -111,7 +99,7 @@ impl LineIndex { return Some(ByteOffset(line_start_utf8 + char_offset)); } - match encoding { + match self { PositionEncoding::Utf8 => { // UTF-8: character positions are already byte offsets let char_offset = character.min(u32::try_from(line_text.len()).unwrap_or(u32::MAX)); @@ -155,6 +143,13 @@ impl LineIndex { mod tests { use super::*; + #[test] + fn test_position_encoding_display() { + assert_eq!(PositionEncoding::Utf8.to_string(), "utf-8"); + assert_eq!(PositionEncoding::Utf16.to_string(), "utf-16"); + assert_eq!(PositionEncoding::Utf32.to_string(), "utf-32"); + } + #[test] fn test_line_col_to_offset_utf16() { let text = "Hello 🌍 world"; @@ -162,39 +157,32 @@ mod tests { // "Hello " = 6 UTF-16 units, "🌍" = 2 UTF-16 units // So position (0, 8) in UTF-16 should be after the emoji - let offset = index - .line_col_to_offset(LineCol((0, 8)), text, PositionEncoding::Utf16) + let offset = PositionEncoding::Utf16 + .line_col_to_offset(&index, LineCol((0, 8)), text) .expect("Should get offset"); assert_eq!(offset, ByteOffset(10)); // "Hello 🌍" is 10 bytes // In UTF-8, character 10 would be at the 'r' in 'world' - let offset_utf8 = index - .line_col_to_offset(LineCol((0, 10)), text, PositionEncoding::Utf8) + let offset_utf8 = PositionEncoding::Utf8 + .line_col_to_offset(&index, LineCol((0, 10)), text) .expect("Should get offset"); assert_eq!(offset_utf8, ByteOffset(10)); } - #[test] - fn test_position_encoding_display() { - assert_eq!(PositionEncoding::Utf8.to_string(), "utf-8"); - assert_eq!(PositionEncoding::Utf16.to_string(), "utf-16"); - assert_eq!(PositionEncoding::Utf32.to_string(), "utf-32"); - } - #[test] fn test_line_col_to_offset_ascii_fast_path() { let text = "Hello world"; let index = LineIndex::from_text(text); // For ASCII text, all encodings should give the same result - let offset_utf8 = index - .line_col_to_offset(LineCol((0, 5)), text, PositionEncoding::Utf8) + let offset_utf8 = PositionEncoding::Utf8 + .line_col_to_offset(&index, LineCol((0, 5)), text) .expect("Should get offset"); - let offset_utf16 = index - .line_col_to_offset(LineCol((0, 5)), text, PositionEncoding::Utf16) + let offset_utf16 = PositionEncoding::Utf16 + .line_col_to_offset(&index, LineCol((0, 5)), text) .expect("Should get offset"); - let offset_utf32 = index - .line_col_to_offset(LineCol((0, 5)), text, PositionEncoding::Utf32) + let offset_utf32 = PositionEncoding::Utf32 + .line_col_to_offset(&index, LineCol((0, 5)), text) .expect("Should get offset"); assert_eq!(offset_utf8, ByteOffset(5)); diff --git a/crates/djls-workspace/src/document.rs b/crates/djls-workspace/src/document.rs index 773375ce..1be2df67 100644 --- a/crates/djls-workspace/src/document.rs +++ b/crates/djls-workspace/src/document.rs @@ -147,8 +147,8 @@ impl TextDocument { encoding: PositionEncoding, ) -> Option { let line_col = djls_source::LineCol((position.line, position.character)); - line_index - .line_col_to_offset(line_col, text, encoding) + encoding + .line_col_to_offset(line_index, line_col, text) .map(|djls_source::ByteOffset(offset)| offset) } } From cd0680bad439a7828e984b012d5af6ba579fe59e Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 16 Sep 2025 21:50:33 -0500 Subject: [PATCH 5/7] updates and things --- crates/djls-workspace/src/encoding.rs | 57 +++++++++++++-------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/crates/djls-workspace/src/encoding.rs b/crates/djls-workspace/src/encoding.rs index 888c31a7..49d9285d 100644 --- a/crates/djls-workspace/src/encoding.rs +++ b/crates/djls-workspace/src/encoding.rs @@ -1,18 +1,16 @@ use djls_source::PositionEncoding; -use tower_lsp_server::lsp_types::InitializeParams; -use tower_lsp_server::lsp_types::PositionEncodingKind; +use tower_lsp_server::lsp_types; /// Negotiate the best encoding with the client based on their capabilities. /// Prefers UTF-8 > UTF-32 > UTF-16 for performance reasons. -pub fn negotiate_position_encoding(params: &InitializeParams) -> PositionEncoding { - let client_encodings: &[PositionEncodingKind] = params +pub fn negotiate_position_encoding(params: &lsp_types::InitializeParams) -> PositionEncoding { + let client_encodings: &[lsp_types::PositionEncodingKind] = params .capabilities .general .as_ref() .and_then(|general| general.position_encodings.as_ref()) .map_or(&[], |encodings| encodings.as_slice()); - // Try to find the best encoding in preference order for preferred in [ PositionEncoding::Utf8, PositionEncoding::Utf32, @@ -30,18 +28,19 @@ pub fn negotiate_position_encoding(params: &InitializeParams) -> PositionEncodin PositionEncoding::Utf16 } -// Helper functions to convert between LSP types and our PositionEncoding #[must_use] -pub fn position_encoding_to_lsp(encoding: PositionEncoding) -> PositionEncodingKind { +pub fn position_encoding_to_lsp(encoding: PositionEncoding) -> lsp_types::PositionEncodingKind { match encoding { - PositionEncoding::Utf8 => PositionEncodingKind::new("utf-8"), - PositionEncoding::Utf16 => PositionEncodingKind::new("utf-16"), - PositionEncoding::Utf32 => PositionEncodingKind::new("utf-32"), + PositionEncoding::Utf8 => lsp_types::PositionEncodingKind::new("utf-8"), + PositionEncoding::Utf16 => lsp_types::PositionEncodingKind::new("utf-16"), + PositionEncoding::Utf32 => lsp_types::PositionEncodingKind::new("utf-32"), } } #[must_use] -pub fn position_encoding_from_lsp(kind: &PositionEncodingKind) -> Option { +pub fn position_encoding_from_lsp( + kind: &lsp_types::PositionEncodingKind, +) -> Option { match kind.as_str() { "utf-8" => Some(PositionEncoding::Utf8), "utf-16" => Some(PositionEncoding::Utf16), @@ -61,21 +60,21 @@ mod tests { fn test_lsp_type_conversions() { // position_encoding_from_lsp for valid encodings assert_eq!( - position_encoding_from_lsp(&PositionEncodingKind::new("utf-8")), + position_encoding_from_lsp(&lsp_types::PositionEncodingKind::new("utf-8")), Some(PositionEncoding::Utf8) ); assert_eq!( - position_encoding_from_lsp(&PositionEncodingKind::new("utf-16")), + position_encoding_from_lsp(&lsp_types::PositionEncodingKind::new("utf-16")), Some(PositionEncoding::Utf16) ); assert_eq!( - position_encoding_from_lsp(&PositionEncodingKind::new("utf-32")), + position_encoding_from_lsp(&lsp_types::PositionEncodingKind::new("utf-32")), Some(PositionEncoding::Utf32) ); // Invalid encoding returns None assert_eq!( - position_encoding_from_lsp(&PositionEncodingKind::new("unknown")), + position_encoding_from_lsp(&lsp_types::PositionEncodingKind::new("unknown")), None ); @@ -96,13 +95,13 @@ mod tests { #[test] fn test_negotiate_prefers_utf8_when_all_available() { - let params = InitializeParams { + let params = lsp_types::InitializeParams { capabilities: ClientCapabilities { general: Some(GeneralClientCapabilities { position_encodings: Some(vec![ - PositionEncodingKind::new("utf-16"), - PositionEncodingKind::new("utf-8"), - PositionEncodingKind::new("utf-32"), + lsp_types::PositionEncodingKind::new("utf-16"), + lsp_types::PositionEncodingKind::new("utf-8"), + lsp_types::PositionEncodingKind::new("utf-32"), ]), ..Default::default() }), @@ -116,12 +115,12 @@ mod tests { #[test] fn test_negotiate_prefers_utf32_over_utf16() { - let params = InitializeParams { + let params = lsp_types::InitializeParams { capabilities: ClientCapabilities { general: Some(GeneralClientCapabilities { position_encodings: Some(vec![ - PositionEncodingKind::new("utf-16"), - PositionEncodingKind::new("utf-32"), + lsp_types::PositionEncodingKind::new("utf-16"), + lsp_types::PositionEncodingKind::new("utf-32"), ]), ..Default::default() }), @@ -138,10 +137,10 @@ mod tests { #[test] fn test_negotiate_accepts_utf16_when_only_option() { - let params = InitializeParams { + let params = lsp_types::InitializeParams { capabilities: ClientCapabilities { general: Some(GeneralClientCapabilities { - position_encodings: Some(vec![PositionEncodingKind::new("utf-16")]), + position_encodings: Some(vec![lsp_types::PositionEncodingKind::new("utf-16")]), ..Default::default() }), ..Default::default() @@ -157,7 +156,7 @@ mod tests { #[test] fn test_negotiate_fallback_with_empty_encodings() { - let params = InitializeParams { + let params = lsp_types::InitializeParams { capabilities: ClientCapabilities { general: Some(GeneralClientCapabilities { position_encodings: Some(vec![]), @@ -176,7 +175,7 @@ mod tests { #[test] fn test_negotiate_fallback_with_no_capabilities() { - let params = InitializeParams::default(); + let params = lsp_types::InitializeParams::default(); assert_eq!( negotiate_position_encoding(¶ms), PositionEncoding::Utf16 @@ -185,12 +184,12 @@ mod tests { #[test] fn test_negotiate_fallback_with_unknown_encodings() { - let params = InitializeParams { + let params = lsp_types::InitializeParams { capabilities: ClientCapabilities { general: Some(GeneralClientCapabilities { position_encodings: Some(vec![ - PositionEncodingKind::new("utf-7"), - PositionEncodingKind::new("ascii"), + lsp_types::PositionEncodingKind::new("utf-7"), + lsp_types::PositionEncodingKind::new("ascii"), ]), ..Default::default() }), From 536deb3da9cd2e750788ece916923df6f9f8ab52 Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 16 Sep 2025 21:53:14 -0500 Subject: [PATCH 6/7] ugh --- crates/djls-workspace/src/workspace.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/djls-workspace/src/workspace.rs b/crates/djls-workspace/src/workspace.rs index 1328d61b..99a5d698 100644 --- a/crates/djls-workspace/src/workspace.rs +++ b/crates/djls-workspace/src/workspace.rs @@ -10,6 +10,7 @@ use camino::Utf8Path; use camino::Utf8PathBuf; use dashmap::DashMap; use djls_source::File; +use djls_source::PositionEncoding; use tower_lsp_server::lsp_types::TextDocumentContentChangeEvent; use url::Url; @@ -110,7 +111,7 @@ impl Workspace { url: &Url, changes: Vec, version: i32, - encoding: djls_source::PositionEncoding, + encoding: PositionEncoding, ) -> Option { if let Some(mut document) = self.buffers.get(url) { document.update(changes, version, encoding); @@ -204,7 +205,6 @@ mod tests { use camino::Utf8Path; use camino::Utf8PathBuf; - use djls_source::PositionEncoding; use tempfile::tempdir; use url::Url; From 8852e5da38c9dd6a4581df8398e28ad9623a3a03 Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 16 Sep 2025 21:59:33 -0500 Subject: [PATCH 7/7] bluh --- crates/djls-workspace/src/document.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/djls-workspace/src/document.rs b/crates/djls-workspace/src/document.rs index 1be2df67..e14f1c4c 100644 --- a/crates/djls-workspace/src/document.rs +++ b/crates/djls-workspace/src/document.rs @@ -138,8 +138,6 @@ impl TextDocument { } /// Calculate byte offset from an LSP position using the given line index and text. - /// - /// This delegates to the encoding-aware conversion in `djls_source`. fn calculate_offset( line_index: &LineIndex, position: Position,