@@ -55,23 +55,54 @@ pub mod util {
55 use super::*;
55 use super::*;
56 use helix_core::{Range, Rope, Transaction};
56 use helix_core::{Range, Rope, Transaction};
58+ /// Converts [`lsp::Position`] to a position in the document.
60+ /// Returns `None` if position exceeds document length or an operation overflows.
58 pub fn lsp_pos_to_pos(
61 pub fn lsp_pos_to_pos(
59 doc: &Rope,
62 doc: &Rope,
60 pos: lsp::Position,
63 pos: lsp::Position,
61 offset_encoding: OffsetEncoding,
64 offset_encoding: OffsetEncoding,
62− ) -> usize {
65+ ) -> Option<usize> {
66+ let max_line = doc.lines().count().saturating_sub(1);
67+ let pos_line = pos.line as usize;
68+ let pos_line = if pos_line > max_line {
63 match offset_encoding {
73 match offset_encoding {
64 OffsetEncoding::Utf8 => {
74 OffsetEncoding::Utf8 => {
65− let line = doc.line_to_char(pos.line as usize);
76+ .line_to_char(max_line)
77+ .checked_add(doc.line(max_line).len_chars())?;
78+ let line = doc.line_to_char(pos_line);
66− line + pos.character as usize
79+ let pos = line.checked_add(pos.character as usize)?;
68 OffsetEncoding::Utf16 => {
86 OffsetEncoding::Utf16 => {
69− let line = doc.line_to_char(pos.line as usize);
88+ .line_to_char(max_line)
89+ .checked_add(doc.line(max_line).len_chars())?;
90+ let max_cu = doc.char_to_utf16_cu(max_char);
91+ let line = doc.line_to_char(pos_line);
70 let line_start = doc.char_to_utf16_cu(line);
92 let line_start = doc.char_to_utf16_cu(line);
71− doc.utf16_cu_to_char(line_start + pos.character as usize)
93+ let pos = line_start.checked_add(pos.character as usize)?;
95+ Some(doc.utf16_cu_to_char(pos))
103+ /// Converts position in the document to [`lsp::Position`].
105+ /// Panics when `pos` is out of `doc` bounds or operation overflows.
75 pub fn pos_to_lsp_pos(
106 pub fn pos_to_lsp_pos(
76 doc: &Rope,
107 doc: &Rope,
77 pos: usize,
108 pos: usize,
@@ -95,6 +126,7 @@ pub mod util {
129+ /// Converts a range in the document to [`lsp::Range`].
98 pub fn range_to_lsp_range(
130 pub fn range_to_lsp_range(
99 doc: &Rope,
131 doc: &Rope,
100 range: Range,
132 range: Range,
@@ -121,8 +153,17 @@ pub mod util {
124− let start = lsp_pos_to_pos(doc, edit.range.start, offset_encoding);
157+ if let Some(start) = lsp_pos_to_pos(doc, edit.range.start, offset_encoding) {
160+ return (0, 0, None);
125− let end = lsp_pos_to_pos(doc, edit.range.end, offset_encoding);
162+ let end = if let Some(end) = lsp_pos_to_pos(doc, edit.range.end, offset_encoding) {
165+ return (0, 0, None);
126 (start, end, replacement)
167 (start, end, replacement)
@@ -248,3 +289,34 @@ impl Registry {
248// there needs to be a way to process incoming lsp messages from all clients.
289// there needs to be a way to process incoming lsp messages from all clients.
249// -> notifications need to be dispatched to wherever
290// -> notifications need to be dispatched to wherever
250// -> requests need to generate a reply and travel back to the same lsp!
291// -> requests need to generate a reply and travel back to the same lsp!
295+ use super::{lsp, util::*, OffsetEncoding};
296+ use helix_core::Rope;
299+ fn converts_lsp_pos_to_pos() {
300+ macro_rules! test_case {
301+ ($doc:expr, ($x:expr, $y:expr) => $want:expr) => {
302+ let doc = Rope::from($doc);
303+ let pos = lsp::Position::new($x, $y);
304+ assert_eq!($want, lsp_pos_to_pos(&doc, pos, OffsetEncoding::Utf16));
305+ assert_eq!($want, lsp_pos_to_pos(&doc, pos, OffsetEncoding::Utf8))
309+ test_case!("", (0, 0) => Some(0));
310+ test_case!("", (0, 1) => None);
311+ test_case!("", (1, 0) => None);
312+ test_case!("\n\n", (0, 0) => Some(0));
313+ test_case!("\n\n", (1, 0) => Some(1));
314+ test_case!("\n\n", (1, 1) => Some(2));
315+ test_case!("\n\n", (2, 0) => Some(2));
316+ test_case!("\n\n", (3, 0) => None);
317+ test_case!("test\n\n\n\ncase", (4, 3) => Some(11));
318+ test_case!("test\n\n\n\ncase", (4, 4) => Some(12));
319+ test_case!("test\n\n\n\ncase", (4, 5) => None);
320+ test_case!("", (u32::MAX, u32::MAX) => None);