@@ -1,7 +1,8 @@
1use helix_core::{
1use helix_core::{
2 comment, coords_at_pos, find_first_non_whitespace_char, find_root, graphemes, indent,
2 comment, coords_at_pos, find_first_non_whitespace_char, find_root, graphemes, indent,
3 line_ending::{
3 line_ending::{
4− get_line_ending, get_line_ending_of_str, line_end_char_index, str_is_line_ending,
4+ get_line_ending, get_line_ending_of_str, line_end_char_index, rope_end_without_line_ending,
6 match_brackets,
7 match_brackets,
7 movement::{self, Direction},
8 movement::{self, Direction},
@@ -612,7 +613,7 @@ fn replace(cx: &mut Context) {
612 if let Some(ch) = ch {
613 if let Some(ch) = ch {
613 let transaction =
614 let transaction =
614 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
615 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
615− let max_to = doc.text().len_chars().saturating_sub(1);
616+ let max_to = rope_end_without_line_ending(&doc.text().slice(..));
616 let to = std::cmp::min(max_to, range.to() + 1);
617 let to = std::cmp::min(max_to, range.to() + 1);
617 let text: String = RopeGraphemes::new(doc.text().slice(range.from()..to))
618 let text: String = RopeGraphemes::new(doc.text().slice(range.from()..to))
618 .map(|g| {
619 .map(|g| {
@@ -769,7 +770,7 @@ fn extend_line_start(cx: &mut Context) {
769fn select_all(cx: &mut Context) {
770fn select_all(cx: &mut Context) {
770 let (view, doc) = current!(cx.editor);
771 let (view, doc) = current!(cx.editor);
772− let end = doc.text().len_chars().saturating_sub(1);
773+ let end = rope_end_without_line_ending(&doc.text().slice(..));
773 doc.set_selection(view.id, Selection::single(0, end))
774 doc.set_selection(view.id, Selection::single(0, end))
@@ -900,13 +901,13 @@ fn extend_line(cx: &mut Context) {
901 let line_start = text.char_to_line(pos.anchor);
902 let line_start = text.char_to_line(pos.anchor);
902 let mut line = text.char_to_line(pos.head);
903 let mut line = text.char_to_line(pos.head);
903− let line_end = text.line_to_char(line + 1).saturating_sub(1);
904+ let line_end = line_end_char_index(&text.slice(..), line);
904− if line_start <= pos.anchor && pos.head == line_end && line != text.len_lines() {
905+ if line_start <= pos.anchor && pos.head == line_end && line < (text.len_lines() - 2) {
905 line += 1;
906 line += 1;
908 let start = text.line_to_char(line_start);
909 let start = text.line_to_char(line_start);
909− let end = text.line_to_char(line + 1).saturating_sub(1);
910+ let end = line_end_char_index(&text.slice(..), line);
911 doc.set_selection(view.id, Selection::single(start, end));
912 doc.set_selection(view.id, Selection::single(start, end));
@@ -924,13 +925,9 @@ fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId
924 // then delete
925 // then delete
925 let transaction =
926 let transaction =
926 Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
927 Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
927− let alltext = doc.text();
928+ let alltext = doc.text().slice(..);
928 let line = alltext.char_to_line(range.head);
929 let line = alltext.char_to_line(range.head);
929− let max_to = doc.text().len_chars().saturating_sub(
930+ let max_to = rope_end_without_line_ending(&alltext);
930− get_line_ending(&alltext.line(line))
931− .map(|le| le.len_chars())
934 let to = std::cmp::min(max_to, range.to() + 1);
931 let to = std::cmp::min(max_to, range.to() + 1);
935 (range.from(), to, None)
932 (range.from(), to, None)
@@ -1348,7 +1345,7 @@ mod cmd {
1348 Ok(contents) => {
1345 Ok(contents) => {
1349 let transaction =
1346 let transaction =
1350 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
1347 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
1351− let max_to = doc.text().len_chars().saturating_sub(1);
1348+ let max_to = rope_end_without_line_ending(&doc.text().slice(..));
1352 let to = std::cmp::min(max_to, range.to() + 1);
1349 let to = std::cmp::min(max_to, range.to() + 1);
1353 (range.from(), to, Some(contents.as_str().into()))
1350 (range.from(), to, Some(contents.as_str().into()))
@@ -1831,7 +1828,10 @@ fn open(cx: &mut Context, open: Open) {
1833 // insert newlines after this index for both Above and Below variants
1830 // insert newlines after this index for both Above and Below variants
1834− let linend_index = doc.text().line_to_char(line).saturating_sub(1);
1831+ let linend_index = doc.text().line_to_char(line)
1832+ - get_line_ending(&doc.text().line(line))
1833+ .map(|le| le.len_chars())
1836 // TODO: share logic with insert_newline for indentation
1836 // TODO: share logic with insert_newline for indentation
1837 let indent_level = indent::suggested_indent_for_pos(
1837 let indent_level = indent::suggested_indent_for_pos(
@@ -2702,7 +2702,7 @@ fn replace_with_yanked(cx: &mut Context) {
2702 if let Some(yank) = values.first() {
2702 if let Some(yank) = values.first() {
2703 let transaction =
2703 let transaction =
2704 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
2704 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
2705− let max_to = doc.text().len_chars().saturating_sub(1);
2705+ let max_to = rope_end_without_line_ending(&doc.text().slice(..));
2706 let to = std::cmp::min(max_to, range.to() + 1);
2706 let to = std::cmp::min(max_to, range.to() + 1);
2707 (range.from(), to, Some(yank.as_str().into()))
2707 (range.from(), to, Some(yank.as_str().into()))
@@ -2720,7 +2720,7 @@ fn replace_selections_with_clipboard_impl(editor: &mut Editor) {
2720 Ok(contents) => {
2720 Ok(contents) => {
2721 let transaction =
2721 let transaction =
2722 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
2722 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
2723− let max_to = doc.text().len_chars().saturating_sub(1);
2723+ let max_to = rope_end_without_line_ending(&doc.text().slice(..));
2724 let to = std::cmp::min(max_to, range.to() + 1);
2724 let to = std::cmp::min(max_to, range.to() + 1);
2725 (range.from(), to, Some(contents.as_str().into()))
2725 (range.from(), to, Some(contents.as_str().into()))
@@ -2906,8 +2906,8 @@ fn join_selections(cx: &mut Context) {
2906 changes.reserve(lines.len());
2906 changes.reserve(lines.len());
2908 for line in lines {
2908 for line in lines {
2909+ let mut start = line_end_char_index(&slice, line);
2909− let mut start = text.line_to_char(line + 1).saturating_sub(1);
2910+ let mut end = text.line_to_char(line + 1);
2910− let mut end = start + 1;
2911 end = skip_while(slice, end, |ch| matches!(ch, ' ' | '\t')).unwrap_or(end);
2911 end = skip_while(slice, end, |ch| matches!(ch, ' ' | '\t')).unwrap_or(end);
2913 // need to skip from start, not end
2913 // need to skip from start, not end
@@ -3371,11 +3371,7 @@ fn surround_add(cx: &mut Context) {
3371 for (i, range) in selection.iter().enumerate() {
3371 for (i, range) in selection.iter().enumerate() {
3372 let from = range.from();
3372 let from = range.from();
3373 let line = text.char_to_line(range.to());
3373 let line = text.char_to_line(range.to());
3374− let max_to = doc.text().len_chars().saturating_sub(
3374+ let max_to = rope_end_without_line_ending(&text);
3375− get_line_ending(&text.line(line))
3376− .map(|le| le.len_chars())
3379 let to = std::cmp::min(range.to() + 1, max_to);
3375 let to = std::cmp::min(range.to() + 1, max_to);
3381 changes.push((from, from, Some(Tendril::from_char(open))));
3377 changes.push((from, from, Some(Tendril::from_char(open))));