@@ -4,6 +4,7 @@ use crate::{graphics::Rect, Document, DocumentId, ViewId};
44use helix_core::{
55 coords_at_pos,
66 graphemes::{grapheme_width, RopeGraphemes},
7+ line_ending::line_end_char_index,
78 Position, RopeSlice, Selection,
89};
910
@@ -165,6 +166,74 @@ impl View {
165166 Some(Position::new(row, col))
166167 }
167168
169+ /// Verifies whether a screen position is inside the view
170+ /// Returns true when position is inside the view
171+ pub fn verify_screen_coords(&self, row: usize, column: usize) -> bool {
172+ // 2 for status
173+ if row < self.area.y as usize || row > self.area.y as usize + self.area.height as usize - 2
174+ {
175+ return false;
176+ }
177+
178+ // TODO: not ideal
179+ const OFFSET: usize = 7; // 1 diagnostic + 5 linenr + 1 gutter
180+
181+ if column < self.area.x as usize + OFFSET
182+ || column > self.area.x as usize + self.area.width as usize
183+ {
184+ return false;
185+ }
186+ true
187+ }
188+
189+ pub fn text_pos_at_screen_coords(
190+ &self,
191+ text: &RopeSlice,
192+ row: usize,
193+ column: usize,
194+ tab_width: usize,
195+ ) -> Option<usize> {
196+ if !self.verify_screen_coords(row, column) {
197+ return None;
198+ }
199+
200+ let line_number = row - self.area.y as usize + self.first_line;
201+
202+ if line_number > text.len_lines() - 1 {
203+ return Some(text.len_chars());
204+ }
205+
206+ let mut pos = text.line_to_char(line_number);
207+
208+ let current_line = text.line(line_number);
209+
210+ // TODO: not ideal
211+ const OFFSET: usize = 7; // 1 diagnostic + 5 linenr + 1 gutter
212+
213+ let target = column - OFFSET - self.area.x as usize + self.first_col;
214+ let mut selected = 0;
215+
216+ for grapheme in RopeGraphemes::new(current_line) {
217+ if selected >= target {
218+ break;
219+ }
220+ if grapheme == "\t" {
221+ selected += tab_width;
222+ } else {
223+ let width = grapheme_width(&Cow::from(grapheme));
224+ selected += width;
225+ }
226+ pos += grapheme.chars().count();
227+ }
228+
229+ Some(pos.min(line_end_char_index(&text.slice(..), line_number)))
230+ }
231+
232+ /// Translates a screen position to position in the text document.
233+ /// Returns a usize typed position in bounds of the text if found in this view, None if out of view.
234+ pub fn pos_at_screen_coords(&self, doc: &Document, row: usize, column: usize) -> Option<usize> {
235+ self.text_pos_at_screen_coords(&doc.text().slice(..), row, column, doc.tab_width())
236+ }
168237 // pub fn traverse<F>(&self, text: RopeSlice, start: usize, end: usize, fun: F)
169238 // where
170239 // F: Fn(usize, usize),
@@ -186,3 +255,81 @@ impl View {
186255 // }
187256 // }
188257}
258+
259+#[cfg(test)]
260+mod tests {
261+ use super::*;
262+ use helix_core::Rope;
263+
264+ #[test]
265+ fn test_text_pos_at_screen_coords() {
266+ let mut view = View::new(DocumentId::default());
267+ view.area = Rect::new(40, 40, 40, 40);
268+ let text = Rope::from_str("abc\n\tdef");
269+
270+ assert_eq!(
271+ view.text_pos_at_screen_coords(&text.slice(..), 40, 2, 4),
272+ None
273+ );
274+
275+ assert_eq!(
276+ view.text_pos_at_screen_coords(&text.slice(..), 40, 41, 4),
277+ None
278+ );
279+
280+ assert_eq!(
281+ view.text_pos_at_screen_coords(&text.slice(..), 0, 2, 4),
282+ None
283+ );
284+
285+ assert_eq!(
286+ view.text_pos_at_screen_coords(&text.slice(..), 0, 49, 4),
287+ None
288+ );
289+
290+ assert_eq!(
291+ view.text_pos_at_screen_coords(&text.slice(..), 0, 41, 4),
292+ None
293+ );
294+
295+ assert_eq!(
296+ view.text_pos_at_screen_coords(&text.slice(..), 40, 81, 4),
297+ None
298+ );
299+
300+ assert_eq!(
301+ view.text_pos_at_screen_coords(&text.slice(..), 78, 41, 4),
302+ None
303+ );
304+
305+ assert_eq!(
306+ view.text_pos_at_screen_coords(&text.slice(..), 40, 40 + 7 + 3, 4),
307+ Some(3)
308+ );
309+
310+ assert_eq!(
311+ view.text_pos_at_screen_coords(&text.slice(..), 40, 80, 4),
312+ Some(3)
313+ );
314+
315+ assert_eq!(
316+ view.text_pos_at_screen_coords(&text.slice(..), 41, 40 + 7 + 1, 4),
317+ Some(5)
318+ );
319+
320+ assert_eq!(
321+ view.text_pos_at_screen_coords(&text.slice(..), 41, 40 + 7 + 4, 4),
322+ Some(5)
323+ );
324+
325+ assert_eq!(
326+ view.text_pos_at_screen_coords(&text.slice(..), 41, 40 + 7 + 7, 4),
327+ Some(8)
328+ );
329+
330+ assert_eq!(
331+ view.text_pos_at_screen_coords(&text.slice(..), 41, 80, 4),
332+ Some(8)
333+ );
334+ }
335+}