Skip to main content

cooklang_language_server/lsp/
from_proto.rs

1//! Conversion from LSP types to internal types.
2//!
3//! This module handles all conversions from lsp_types to our internal
4//! representations, including position encoding (UTF-16 to UTF-8 byte offsets).
5
6use anyhow::{format_err, Result};
7use text_size::{TextRange, TextSize};
8use tower_lsp::lsp_types;
9
10use crate::utils::line_index::LineIndex;
11
12/// The position encoding used by the LSP client.
13#[derive(Debug, Clone, Copy, Default)]
14pub enum PositionEncoding {
15    /// UTF-8 byte offsets (rare, but some clients support it)
16    Utf8,
17    /// UTF-16 code units (default, used by most editors)
18    #[default]
19    Utf16,
20}
21
22/// Convert an LSP Position to a byte offset in the document.
23pub fn offset(
24    line_index: &LineIndex,
25    position: lsp_types::Position,
26    encoding: PositionEncoding,
27) -> Result<TextSize> {
28    let line = position.line;
29    let col = position.character;
30
31    // Get the byte offset of the start of the line
32    let line_start = line_index.line_start(line).ok_or_else(|| {
33        format_err!(
34            "Invalid line {} (document has {} lines)",
35            line,
36            line_index.line_count()
37        )
38    })?;
39
40    // Convert the column based on encoding
41    let col_offset = match encoding {
42        PositionEncoding::Utf8 => TextSize::from(col),
43        PositionEncoding::Utf16 => line_index
44            .utf16_to_utf8_col(line, col)
45            .map(TextSize::from)
46            .ok_or_else(|| format_err!("Invalid UTF-16 column {} on line {}", col, line))?,
47    };
48
49    Ok(line_start + col_offset)
50}
51
52/// Convert an LSP Range to a TextRange.
53pub fn text_range(
54    line_index: &LineIndex,
55    range: lsp_types::Range,
56    encoding: PositionEncoding,
57) -> Result<TextRange> {
58    let start = offset(line_index, range.start, encoding)?;
59    let end = offset(line_index, range.end, encoding)?;
60
61    if end < start {
62        return Err(format_err!("Invalid range: end before start"));
63    }
64
65    Ok(TextRange::new(start, end))
66}
67
68/// Convert an LSP Position directly to (line, column) in UTF-8 bytes.
69pub fn line_col(
70    line_index: &LineIndex,
71    position: lsp_types::Position,
72    encoding: PositionEncoding,
73) -> Result<(u32, u32)> {
74    let line = position.line;
75    let col = match encoding {
76        PositionEncoding::Utf8 => position.character,
77        PositionEncoding::Utf16 => line_index
78            .utf16_to_utf8_col(line, position.character)
79            .ok_or_else(|| {
80                format_err!(
81                    "Invalid UTF-16 column {} on line {}",
82                    position.character,
83                    line
84                )
85            })?,
86    };
87
88    Ok((line, col))
89}