1use serde::{Deserialize, Serialize};
7
8use super::translator::{Position, Position2D};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
24#[serde(try_from = "String", into = "&'static str")]
25pub enum PositionEncoding {
26 #[default]
28 Utf8,
29 Utf16,
31 Utf32,
33}
34
35#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
37#[error("invalid position encoding '{0}'; expected one of \"utf-8\", \"utf-16\", \"utf-32\"")]
38pub struct InvalidPositionEncoding(String);
39
40impl PositionEncoding {
41 pub const ALL: [Self; 3] = [Self::Utf8, Self::Utf16, Self::Utf32];
43
44 #[must_use]
46 pub fn from_lsp(kind: &str) -> Option<Self> {
47 Self::ALL.into_iter().find(|e| e.to_lsp() == kind)
48 }
49
50 #[must_use]
52 pub const fn to_lsp(&self) -> &'static str {
53 match self {
54 Self::Utf8 => "utf-8",
55 Self::Utf16 => "utf-16",
56 Self::Utf32 => "utf-32",
57 }
58 }
59
60 #[must_use]
62 pub const fn to_kind(self) -> lsp_types::PositionEncodingKind {
63 match self {
64 Self::Utf8 => lsp_types::PositionEncodingKind::UTF8,
65 Self::Utf16 => lsp_types::PositionEncodingKind::UTF16,
66 Self::Utf32 => lsp_types::PositionEncodingKind::UTF32,
67 }
68 }
69}
70
71impl TryFrom<String> for PositionEncoding {
72 type Error = InvalidPositionEncoding;
73
74 fn try_from(kind: String) -> Result<Self, Self::Error> {
75 Self::from_lsp(&kind).ok_or(InvalidPositionEncoding(kind))
76 }
77}
78
79impl From<PositionEncoding> for &'static str {
80 fn from(encoding: PositionEncoding) -> Self {
81 encoding.to_lsp()
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum ColumnFidelity {
88 Exact,
90 PassedThrough,
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96#[must_use]
97pub struct Converted<T> {
98 pub value: T,
100 pub fidelity: ColumnFidelity,
102}
103
104impl<T> Converted<T> {
105 const fn exact(value: T) -> Self {
106 Self {
107 value,
108 fidelity: ColumnFidelity::Exact,
109 }
110 }
111
112 const fn passed_through(value: T) -> Self {
113 Self {
114 value,
115 fidelity: ColumnFidelity::PassedThrough,
116 }
117 }
118}
119
120pub fn mcp_to_lsp_position(
135 position: Position,
136 line_text: Option<&str>,
137 encoding: PositionEncoding,
138) -> Converted<lsp_types::Position> {
139 let column = convert_column(
140 position.lsp_character(),
141 line_text,
142 PositionEncoding::Utf16,
143 encoding,
144 );
145 Converted {
146 value: lsp_types::Position {
147 line: position.lsp_line(),
148 character: column.value,
149 },
150 fidelity: column.fidelity,
151 }
152}
153
154pub fn lsp_to_mcp_position(
161 pos: lsp_types::Position,
162 line_text: Option<&str>,
163 encoding: PositionEncoding,
164) -> Converted<Position2D> {
165 let column = if pos.character == u32::MAX {
166 Converted::exact(u32::MAX)
167 } else {
168 convert_column(pos.character, line_text, encoding, PositionEncoding::Utf16)
169 };
170
171 Converted {
172 value: Position2D {
173 line: pos.line.saturating_add(1),
174 character: column.value.saturating_add(1),
175 },
176 fidelity: column.fidelity,
177 }
178}
179
180fn convert_column(
183 column: u32,
184 line_text: Option<&str>,
185 from: PositionEncoding,
186 to: PositionEncoding,
187) -> Converted<u32> {
188 if from == to || column == 0 {
189 return Converted::exact(column);
190 }
191 let Some(text) = line_text else {
192 return Converted::passed_through(column);
193 };
194
195 let line_len_in = |encoding| {
196 EncodingConverter::new(encoding)
197 .byte_offset_to_character(text, text.len())
198 .ok()
199 };
200 let past_end =
202 || column as usize > text.len() || line_len_in(from).is_some_and(|len| column > len);
203
204 let exact = (column as usize <= text.len())
205 .then(|| exact_byte_offset(text, column, from))
206 .flatten()
207 .and_then(|byte_offset| {
208 EncodingConverter::new(to)
209 .byte_offset_to_character(text, byte_offset)
210 .ok()
211 });
212 if let Some(converted) = exact {
213 return Converted::exact(converted);
214 }
215 if past_end()
216 && let Some(clamped) = line_len_in(to)
217 {
218 return if from == PositionEncoding::Utf16 {
220 Converted::exact(clamped)
221 } else {
222 Converted {
223 value: clamped,
224 fidelity: ColumnFidelity::PassedThrough,
225 }
226 };
227 }
228 Converted::passed_through(column)
229}
230
231fn exact_byte_offset(
243 text: &str,
244 character_offset: u32,
245 encoding: PositionEncoding,
246) -> Option<usize> {
247 let converter = EncodingConverter::new(encoding);
248 let byte_offset = converter
249 .character_to_byte_offset(text, character_offset)
250 .ok()?;
251 let round_trip = converter.byte_offset_to_character(text, byte_offset).ok()?;
252 (round_trip == character_offset).then_some(byte_offset)
253}
254
255#[derive(Debug)]
265pub struct LabelOffsets<'a> {
266 label: &'a str,
267 resolved: Vec<(u32, Option<usize>)>,
270}
271
272impl<'a> LabelOffsets<'a> {
273 #[must_use]
275 pub fn new(
276 label: &'a str,
277 offsets: impl IntoIterator<Item = u32>,
278 encoding: PositionEncoding,
279 ) -> Self {
280 let mut wanted: Vec<u32> = offsets.into_iter().collect();
281 wanted.sort_unstable();
282 wanted.dedup();
283
284 let mut resolved = Vec::with_capacity(wanted.len());
285 let mut next = 0;
286 let mut settle = |unit: usize, byte: usize| {
287 while let Some(&offset) = wanted.get(next) {
288 if offset as usize > unit {
289 break;
290 }
291 resolved.push((offset, (offset as usize == unit).then_some(byte)));
292 next = next.saturating_add(1);
293 }
294 };
295 let mut unit = 0;
296 for (byte, ch) in label.char_indices() {
297 settle(unit, byte);
298 unit = unit.saturating_add(match encoding {
299 PositionEncoding::Utf8 => ch.len_utf8(),
300 PositionEncoding::Utf16 => ch.len_utf16(),
301 PositionEncoding::Utf32 => 1,
302 });
303 }
304 settle(unit, label.len());
305 resolved.extend(
306 wanted
307 .get(next..)
308 .unwrap_or_default()
309 .iter()
310 .map(|&offset| (offset, None)),
311 );
312 Self { label, resolved }
313 }
314
315 #[must_use]
320 pub fn substring(&self, start: u32, end: u32) -> Option<&'a str> {
321 let at = |unit: u32| {
322 let index = self
323 .resolved
324 .binary_search_by_key(&unit, |&(offset, _)| offset)
325 .ok()?;
326 self.resolved.get(index)?.1
327 };
328 self.label.get(at(start)?..at(end)?)
329 }
330}
331
332#[derive(Debug, Clone)]
338pub struct EncodingConverter {
339 encoding: PositionEncoding,
340}
341
342impl EncodingConverter {
343 #[must_use]
345 pub const fn new(encoding: PositionEncoding) -> Self {
346 Self { encoding }
347 }
348
349 #[expect(
357 clippy::cast_possible_truncation,
358 reason = "LSP positions are u32; truncation is acceptable"
359 )]
360 pub fn byte_offset_to_character(&self, text: &str, byte_offset: usize) -> Result<u32, String> {
361 if byte_offset > text.len() {
362 let text_len = text.len();
363 return Err(format!(
364 "Byte offset {byte_offset} exceeds text length {text_len}"
365 ));
366 }
367 if !text.is_char_boundary(byte_offset) {
371 return Err(format!(
372 "Byte offset {byte_offset} is not on a character boundary"
373 ));
374 }
375
376 match self.encoding {
377 PositionEncoding::Utf8 => Ok(byte_offset as u32),
378 PositionEncoding::Utf16 => {
379 let utf16_units = text[..byte_offset].encode_utf16().count();
380 Ok(utf16_units as u32)
381 }
382 PositionEncoding::Utf32 => {
383 let code_points = text[..byte_offset].chars().count();
384 Ok(code_points as u32)
385 }
386 }
387 }
388
389 #[expect(
397 clippy::cast_possible_truncation,
398 reason = "LSP positions are u32; truncation is acceptable"
399 )]
400 pub fn character_to_byte_offset(
401 &self,
402 text: &str,
403 character_offset: u32,
404 ) -> Result<usize, String> {
405 match self.encoding {
406 PositionEncoding::Utf8 => {
407 let byte_offset = character_offset as usize;
408 if byte_offset > text.len() {
409 let text_len = text.len();
410 return Err(format!(
411 "Character offset {character_offset} exceeds text length {text_len}"
412 ));
413 }
414 if !text.is_char_boundary(byte_offset) {
420 return Err(format!(
421 "Character offset {character_offset} is not on a character boundary"
422 ));
423 }
424 Ok(byte_offset)
425 }
426 PositionEncoding::Utf16 => {
427 let mut utf16_count = 0u32;
428 for (byte_idx, ch) in text.char_indices() {
429 if utf16_count >= character_offset {
430 return Ok(byte_idx);
431 }
432 utf16_count = utf16_count.saturating_add(ch.len_utf16() as u32);
433 }
434 if utf16_count == character_offset {
435 Ok(text.len())
436 } else {
437 Err(format!(
438 "Character offset {character_offset} out of bounds (max UTF-16 units: {utf16_count})"
439 ))
440 }
441 }
442 PositionEncoding::Utf32 => text
443 .char_indices()
444 .nth(character_offset as usize)
445 .map(|(byte_idx, _)| byte_idx)
446 .or_else(|| {
447 if character_offset == text.chars().count() as u32 {
448 Some(text.len())
449 } else {
450 None
451 }
452 })
453 .ok_or_else(|| {
454 let max_code_points = text.chars().count();
455 format!(
456 "Character offset {character_offset} out of bounds (max code points: {max_code_points})"
457 )
458 }),
459 }
460 }
461}
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466
467 #[test]
468 fn test_string_kind_and_enum_round_trip_for_every_encoding() {
469 for encoding in PositionEncoding::ALL {
470 let kind = encoding.to_lsp();
471 assert_eq!(PositionEncoding::try_from(kind.to_string()), Ok(encoding));
472 assert_eq!(<&str>::from(encoding), kind);
473 assert_eq!(encoding.to_kind().as_str(), kind);
474 }
475 }
476
477 #[test]
478 fn test_invalid_encoding_error_lists_valid_values() {
479 let err = PositionEncoding::try_from("utf-7".to_string())
480 .unwrap_err()
481 .to_string();
482 assert!(err.contains("utf-7"));
483 for encoding in PositionEncoding::ALL {
484 assert!(err.contains(encoding.to_lsp()), "{err}");
485 }
486 }
487
488 fn label_substring(
489 label: &str,
490 start: u32,
491 end: u32,
492 encoding: PositionEncoding,
493 ) -> Option<&str> {
494 LabelOffsets::new(label, [start, end], encoding).substring(start, end)
495 }
496
497 #[test]
498 fn test_label_substring_resolves_offsets_per_encoding() {
499 let label = "é𝄞xy";
500 for (encoding, start, end) in [
501 (PositionEncoding::Utf8, 6, 8),
502 (PositionEncoding::Utf16, 3, 5),
503 (PositionEncoding::Utf32, 2, 4),
504 ] {
505 assert_eq!(
506 label_substring(label, start, end, encoding),
507 Some("xy"),
508 "{encoding:?}"
509 );
510 assert_eq!(label_substring(label, 0, 0, encoding), Some(""));
511 }
512 }
513
514 #[test]
515 fn test_label_substring_rejects_invalid_offsets() {
516 let label = "é𝄞x";
517 assert_eq!(label_substring(label, 0, 99, PositionEncoding::Utf16), None);
518 assert_eq!(label_substring(label, 0, 99, PositionEncoding::Utf8), None);
519 assert_eq!(label_substring(label, 3, 1, PositionEncoding::Utf16), None);
520 assert_eq!(label_substring(label, 4, 3, PositionEncoding::Utf8), None);
521 assert_eq!(label_substring(label, 1, 2, PositionEncoding::Utf8), None);
523 assert_eq!(label_substring(label, 0, 2, PositionEncoding::Utf16), None);
524 }
525
526 fn lsp_at(
527 line: u32,
528 character: u32,
529 line_text: Option<&str>,
530 encoding: PositionEncoding,
531 ) -> Converted<lsp_types::Position> {
532 mcp_to_lsp_position(Position::at(line, character), line_text, encoding)
533 }
534
535 fn mcp_at(
536 line: u32,
537 character: u32,
538 line_text: Option<&str>,
539 encoding: PositionEncoding,
540 ) -> Converted<(u32, u32)> {
541 let converted =
542 lsp_to_mcp_position(lsp_types::Position { line, character }, line_text, encoding);
543 Converted {
544 value: (converted.value.line, converted.value.character),
545 fidelity: converted.fidelity,
546 }
547 }
548
549 #[test]
550 fn test_mcp_to_lsp_position() {
551 let lsp_pos = lsp_at(1, 1, None, PositionEncoding::Utf16).value;
552 assert_eq!(lsp_pos.line, 0);
553 assert_eq!(lsp_pos.character, 0);
554
555 let lsp_pos = lsp_at(10, 5, None, PositionEncoding::Utf16).value;
556 assert_eq!(lsp_pos.line, 9);
557 assert_eq!(lsp_pos.character, 4);
558 }
559
560 #[test]
561 fn test_lsp_to_mcp_position() {
562 let (line, char) = mcp_at(0, 0, None, PositionEncoding::Utf16).value;
563 assert_eq!(line, 1);
564 assert_eq!(char, 1);
565
566 let (line, char) = mcp_at(9, 4, None, PositionEncoding::Utf16).value;
567 assert_eq!(line, 10);
568 assert_eq!(char, 5);
569 }
570
571 #[test]
572 fn test_roundtrip() {
573 for line in 1..100 {
574 for char in 1..100 {
575 let lsp_pos = lsp_at(line, char, None, PositionEncoding::Utf16).value;
576 let Position2D {
577 line: mcp_line,
578 character: mcp_char,
579 } = lsp_to_mcp_position(lsp_pos, None, PositionEncoding::Utf16).value;
580 assert_eq!(line, mcp_line);
581 assert_eq!(char, mcp_char);
582 }
583 }
584 }
585
586 #[test]
591 fn test_utf16_negotiated_ignores_line_text() {
592 let line_text = "let 😀 = \"héllo\";";
593 let lsp_pos = lsp_at(1, 6, Some(line_text), PositionEncoding::Utf16).value;
594 assert_eq!(lsp_pos.character, 5);
595
596 let (_, mcp_char) = mcp_at(0, 5, Some(line_text), PositionEncoding::Utf16).value;
597 assert_eq!(mcp_char, 6);
598 }
599
600 #[test]
605 fn test_mcp_to_lsp_position_utf8_negotiated_multibyte() {
606 let line_text = "héllo";
607 let lsp_pos = lsp_at(1, 3, Some(line_text), PositionEncoding::Utf8).value;
609 assert_eq!(lsp_pos.character, 3);
611 }
612
613 #[test]
614 fn test_lsp_to_mcp_position_utf8_negotiated_multibyte() {
615 let line_text = "héllo";
616 let (_, mcp_char) = mcp_at(0, 3, Some(line_text), PositionEncoding::Utf8).value;
618 assert_eq!(mcp_char, 3);
620 }
621
622 #[test]
623 fn test_mcp_to_lsp_position_ascii_identical_across_encodings() {
624 let line_text = "let x = 5;";
625 for encoding in [
626 PositionEncoding::Utf8,
627 PositionEncoding::Utf16,
628 PositionEncoding::Utf32,
629 ] {
630 let pos = lsp_at(1, 5, Some(line_text), encoding).value;
631 assert_eq!(
632 pos.character, 4,
633 "encoding {encoding:?} must agree on ASCII"
634 );
635 }
636 }
637
638 #[test]
643 fn test_mcp_to_lsp_position_past_end_of_line_clamps_exactly() {
644 for (text, column, expected) in [("short", 1000, 5), ("éééé", 6, 8)] {
645 let pos = lsp_at(1, column, Some(text), PositionEncoding::Utf8);
646 assert_eq!(pos.value.character, expected, "{text}");
647 assert_eq!(pos.fidelity, ColumnFidelity::Exact, "{text}");
648 }
649 }
650
651 #[test]
652 fn test_lsp_to_mcp_position_past_end_of_line_clamps_but_is_flagged_as_stale_text() {
653 let pos = mcp_at(0, 100, Some("éééé"), PositionEncoding::Utf8);
654 assert_eq!(pos.value, (1, 5));
655 assert_eq!(pos.fidelity, ColumnFidelity::PassedThrough);
656 }
657
658 #[test]
659 fn test_mcp_to_lsp_position_missing_line_text_passes_through_flagged() {
660 let pos = lsp_at(1, 4, None, PositionEncoding::Utf8);
661 assert_eq!(pos.value.character, 3);
662 assert_eq!(pos.fidelity, ColumnFidelity::PassedThrough);
663 }
664
665 #[test]
666 fn test_column_zero_is_exact_without_line_text_in_both_directions() {
667 let to_lsp = lsp_at(3, 1, None, PositionEncoding::Utf8);
668 assert_eq!(to_lsp.value.character, 0);
669 assert_eq!(to_lsp.fidelity, ColumnFidelity::Exact);
670
671 let to_mcp = mcp_at(2, 0, None, PositionEncoding::Utf32);
672 assert_eq!(to_mcp.value, (3, 1));
673 assert_eq!(to_mcp.fidelity, ColumnFidelity::Exact);
674 }
675
676 #[test]
677 fn test_lsp_to_mcp_position_missing_line_text_passes_through_flagged() {
678 let pos = mcp_at(0, 4, None, PositionEncoding::Utf8);
679 assert_eq!(pos.value, (1, 5));
680 assert_eq!(pos.fidelity, ColumnFidelity::PassedThrough);
681 }
682
683 #[test]
684 fn test_utf16_is_always_exact() {
685 let pos = lsp_at(1, 500, Some("x"), PositionEncoding::Utf16);
686 assert_eq!(pos.value.character, 499);
687 assert_eq!(pos.fidelity, ColumnFidelity::Exact);
688 }
689
690 #[test]
691 fn test_position_encoding_parsing() {
692 assert_eq!(
693 PositionEncoding::from_lsp("utf-8"),
694 Some(PositionEncoding::Utf8)
695 );
696 assert_eq!(
697 PositionEncoding::from_lsp("utf-16"),
698 Some(PositionEncoding::Utf16)
699 );
700 assert_eq!(
701 PositionEncoding::from_lsp("utf-32"),
702 Some(PositionEncoding::Utf32)
703 );
704 assert_eq!(PositionEncoding::from_lsp("invalid"), None);
705 }
706
707 #[test]
708 fn test_utf8_encoding() {
709 let converter = EncodingConverter::new(PositionEncoding::Utf8);
710 let text = "Hello, world!";
711
712 let char_offset = converter.byte_offset_to_character(text, 7).unwrap();
713 assert_eq!(char_offset, 7);
714
715 let byte_offset = converter.character_to_byte_offset(text, 7).unwrap();
716 assert_eq!(byte_offset, 7);
717 }
718
719 #[test]
720 fn test_utf16_encoding_with_emoji() {
721 let converter = EncodingConverter::new(PositionEncoding::Utf16);
722 let text = "Hello 😀 world";
723
724 let char_offset = converter.byte_offset_to_character(text, 6).unwrap();
725 assert_eq!(char_offset, 6);
726
727 let char_offset = converter.byte_offset_to_character(text, 10).unwrap();
728 assert_eq!(char_offset, 8);
729
730 let byte_offset = converter.character_to_byte_offset(text, 6).unwrap();
731 assert_eq!(byte_offset, 6);
732
733 let byte_offset = converter.character_to_byte_offset(text, 8).unwrap();
734 assert_eq!(byte_offset, 10);
735 }
736
737 #[test]
738 fn test_utf16_encoding_roundtrip() {
739 let converter = EncodingConverter::new(PositionEncoding::Utf16);
740 let text = "Hello 🌍 world!";
741
742 for byte_idx in [0, 6, 10, 11] {
743 let char_offset = converter.byte_offset_to_character(text, byte_idx).unwrap();
744 let back_to_byte = converter
745 .character_to_byte_offset(text, char_offset)
746 .unwrap();
747 assert_eq!(byte_idx, back_to_byte);
748 }
749 }
750
751 #[test]
752 fn test_utf32_encoding() {
753 let converter = EncodingConverter::new(PositionEncoding::Utf32);
754 let text = "Hello 😀 world";
755
756 let char_offset = converter.byte_offset_to_character(text, 6).unwrap();
757 assert_eq!(char_offset, 6);
758
759 let char_offset = converter.byte_offset_to_character(text, 10).unwrap();
760 assert_eq!(char_offset, 7);
761
762 let byte_offset = converter.character_to_byte_offset(text, 7).unwrap();
763 assert_eq!(byte_offset, 10);
764 }
765
766 #[test]
767 fn test_encoding_edge_cases() {
768 let converter = EncodingConverter::new(PositionEncoding::Utf8);
769
770 assert!(converter.byte_offset_to_character("test", 100).is_err());
771 assert!(converter.character_to_byte_offset("test", 100).is_err());
772
773 let end_offset = converter.byte_offset_to_character("test", 4).unwrap();
774 assert_eq!(end_offset, 4);
775 }
776
777 #[test]
782 fn test_byte_offset_to_character_mid_char_boundary_does_not_panic() {
783 let text = "héllo";
784 let byte_offset = 2; for encoding in [
787 PositionEncoding::Utf8,
788 PositionEncoding::Utf16,
789 PositionEncoding::Utf32,
790 ] {
791 let converter = EncodingConverter::new(encoding);
792 assert!(
793 converter
794 .byte_offset_to_character(text, byte_offset)
795 .is_err(),
796 "encoding {encoding:?} must reject a mid-character byte offset instead of panicking"
797 );
798 }
799 }
800
801 #[test]
806 fn test_lsp_to_mcp_position_utf8_mid_char_lsp_offset_falls_back() {
807 let line_text = "héllo";
808 let converted = mcp_at(0, 2, Some(line_text), PositionEncoding::Utf8);
809 assert_eq!(converted.value.1, 3); assert_eq!(converted.fidelity, ColumnFidelity::PassedThrough);
811 }
812
813 #[test]
817 fn test_mcp_to_lsp_position_utf8_negotiated_astral_char() {
818 let line_text = "𝄞x";
819 let lsp_pos = lsp_at(1, 3, Some(line_text), PositionEncoding::Utf8).value;
822 assert_eq!(lsp_pos.character, 4); let (_, mcp_char) = mcp_at(0, 4, Some(line_text), PositionEncoding::Utf8).value;
825 assert_eq!(mcp_char, 3);
826 }
827
828 #[test]
834 fn test_mcp_to_lsp_position_mid_surrogate_falls_back() {
835 let line_text = "𝄞x";
836 let lsp_pos = lsp_at(1, 2, Some(line_text), PositionEncoding::Utf8);
837 assert_eq!(lsp_pos.value.character, 1);
840 assert_eq!(lsp_pos.fidelity, ColumnFidelity::PassedThrough);
841 }
842
843 #[test]
847 fn test_mcp_to_lsp_position_utf8_negotiated_crlf_line_text() {
848 let line_text = "héllo"; let lsp_pos = lsp_at(1, 3, Some(line_text), PositionEncoding::Utf8).value;
850 assert_eq!(lsp_pos.character, 3);
851 }
852
853 #[test]
857 fn test_lsp_to_mcp_position_character_max_does_not_overflow() {
858 let (_, mcp_char) = mcp_at(0, u32::MAX, None, PositionEncoding::Utf16).value;
859 assert_eq!(mcp_char, u32::MAX);
860 }
861
862 #[test]
865 fn test_lsp_to_mcp_position_character_max_is_exact_for_non_utf16() {
866 for line_text in [None, Some("héllo")] {
867 let converted = mcp_at(0, u32::MAX, line_text, PositionEncoding::Utf8);
868 assert_eq!(converted.value.1, u32::MAX);
869 assert_eq!(converted.fidelity, ColumnFidelity::Exact);
870 }
871 }
872
873 #[test]
876 fn test_lsp_to_mcp_position_line_max_does_not_overflow() {
877 let (mcp_line, _) = mcp_at(u32::MAX, 0, None, PositionEncoding::Utf16).value;
878 assert_eq!(mcp_line, u32::MAX);
879 }
880
881 #[test]
883 fn test_lsp_to_mcp_position_both_max_does_not_overflow() {
884 let (mcp_line, mcp_char) = mcp_at(u32::MAX, u32::MAX, None, PositionEncoding::Utf16).value;
885 assert_eq!(mcp_line, u32::MAX);
886 assert_eq!(mcp_char, u32::MAX);
887 }
888}