chore: Improve RopeExt API and add more docs. (#1289)
This commit is contained in:
parent
512f46aec8
commit
ef1d727c3d
9 changed files with 209 additions and 59 deletions
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
|
|
@ -76,3 +76,4 @@ jobs:
|
||||||
if: ${{ matrix.run_on == 'macos-latest' }}
|
if: ${{ matrix.run_on == 'macos-latest' }}
|
||||||
run: |
|
run: |
|
||||||
cargo test --all
|
cargo test --all
|
||||||
|
cargo test -p gpui-component --doc
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ impl ButtonGroup {
|
||||||
/// The `&Vec<usize>` is the indices of the clicked (selected in `multiple` mode) buttons.
|
/// The `&Vec<usize>` is the indices of the clicked (selected in `multiple` mode) buttons.
|
||||||
/// For example: `[0, 2, 3]` is means the first, third and fourth buttons are clicked.
|
/// For example: `[0, 2, 3]` is means the first, third and fourth buttons are clicked.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```ignore
|
||||||
/// ButtonGroup::new("size-button")
|
/// ButtonGroup::new("size-button")
|
||||||
/// .child(Button::new("large").label("Large").selected(self.size == Size::Large))
|
/// .child(Button::new("large").label("Large").selected(self.size == Size::Large))
|
||||||
/// .child(Button::new("medium").label("Medium").selected(self.size == Size::Medium))
|
/// .child(Button::new("medium").label("Medium").selected(self.size == Size::Medium))
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ pub struct Diagnostic {
|
||||||
impl From<lsp_types::Diagnostic> for Diagnostic {
|
impl From<lsp_types::Diagnostic> for Diagnostic {
|
||||||
fn from(value: lsp_types::Diagnostic) -> Self {
|
fn from(value: lsp_types::Diagnostic) -> Self {
|
||||||
Self {
|
Self {
|
||||||
range: Position::from(value.range.start)..Position::from(value.range.end),
|
range: value.range.start..value.range.end,
|
||||||
severity: value
|
severity: value
|
||||||
.severity
|
.severity
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,6 @@ impl RangeBounds<usize> for Selection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Position = lsp_types::Position;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::input::Position;
|
use crate::input::Position;
|
||||||
|
|
|
||||||
|
|
@ -508,7 +508,7 @@ impl TextElement {
|
||||||
let mut styles = vec![];
|
let mut styles = vec![];
|
||||||
|
|
||||||
for line in text
|
for line in text
|
||||||
.rows()
|
.iter_lines()
|
||||||
.skip(visible_range.start)
|
.skip(visible_range.start)
|
||||||
.take(visible_range.len())
|
.take(visible_range.len())
|
||||||
{
|
{
|
||||||
|
|
@ -675,7 +675,7 @@ impl Element for TextElement {
|
||||||
)
|
)
|
||||||
} else if state.masked {
|
} else if state.masked {
|
||||||
(
|
(
|
||||||
Rope::from("*".repeat(text.chars_count())),
|
Rope::from("*".repeat(text.chars().count())),
|
||||||
cx.theme().foreground,
|
cx.theme().foreground,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -781,7 +781,7 @@ impl Element for TextElement {
|
||||||
// NOTE: Here 50 lines about 150µs
|
// NOTE: Here 50 lines about 150µs
|
||||||
// let measure = crate::Measure::new("shape_text");
|
// let measure = crate::Measure::new("shape_text");
|
||||||
let visible_text = display_text
|
let visible_text = display_text
|
||||||
.slice_rows(visible_range.start..visible_range.end)
|
.slice_lines(visible_range.start..visible_range.end)
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let lines = window
|
let lines = window
|
||||||
|
|
@ -793,7 +793,7 @@ impl Element for TextElement {
|
||||||
let mut longest_line_width = wrap_width.unwrap_or(px(0.));
|
let mut longest_line_width = wrap_width.unwrap_or(px(0.));
|
||||||
if state.mode.is_multi_line() && !state.soft_wrap && lines.len() > 1 {
|
if state.mode.is_multi_line() && !state.soft_wrap && lines.len() > 1 {
|
||||||
let longest_row = state.text_wrapper.longest_row.row;
|
let longest_row = state.text_wrapper.longest_row.row;
|
||||||
let longtest_line: SharedString = state.text.slice_row(longest_row).to_string().into();
|
let longtest_line: SharedString = state.text.slice_line(longest_row).to_string().into();
|
||||||
longest_line_width = window
|
longest_line_width = window
|
||||||
.text_system()
|
.text_system()
|
||||||
.shape_line(
|
.shape_line(
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,9 @@ pub use mask_pattern::MaskPattern;
|
||||||
pub use mode::TabSize;
|
pub use mode::TabSize;
|
||||||
pub use number_input::{NumberInput, NumberInputEvent, StepAction};
|
pub use number_input::{NumberInput, NumberInputEvent, StepAction};
|
||||||
pub use otp_input::*;
|
pub use otp_input::*;
|
||||||
pub use rope_ext::*;
|
|
||||||
pub use ropey::Rope;
|
|
||||||
pub use state::*;
|
pub use state::*;
|
||||||
pub use text_input::*;
|
pub use text_input::*;
|
||||||
|
|
||||||
|
pub use lsp_types::Position;
|
||||||
|
pub use rope_ext::*;
|
||||||
|
pub use ropey::Rope;
|
||||||
|
|
|
||||||
|
|
@ -6,35 +6,156 @@ use tree_sitter::Point;
|
||||||
|
|
||||||
use crate::input::Position;
|
use crate::input::Position;
|
||||||
|
|
||||||
/// An extension trait for `Rope` to provide additional utility methods.
|
/// An iterator over the lines of a `Rope`.
|
||||||
|
pub struct RopeLines<'a> {
|
||||||
|
rope: &'a Rope,
|
||||||
|
row: usize,
|
||||||
|
end_row: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RopeLines<'a> {
|
||||||
|
/// Create a new `RopeLines` iterator.
|
||||||
|
pub fn new(rope: &'a Rope) -> Self {
|
||||||
|
let end_row = rope.lines_len();
|
||||||
|
Self {
|
||||||
|
row: 0,
|
||||||
|
end_row,
|
||||||
|
rope,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> Iterator for RopeLines<'a> {
|
||||||
|
type Item = RopeSlice<'a>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.row >= self.end_row {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let line = self.rope.slice_line(self.row);
|
||||||
|
self.row += 1;
|
||||||
|
Some(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn nth(&mut self, n: usize) -> Option<Self::Item> {
|
||||||
|
self.row = self.row.saturating_add(n);
|
||||||
|
self.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
let len = self.end_row - self.row;
|
||||||
|
(len, Some(len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::iter::ExactSizeIterator for RopeLines<'_> {}
|
||||||
|
impl std::iter::FusedIterator for RopeLines<'_> {}
|
||||||
|
|
||||||
|
/// An extension trait for [`Rope`] to provide additional utility methods.
|
||||||
pub trait RopeExt {
|
pub trait RopeExt {
|
||||||
/// Start offset of the line at the given row (0-based) index.
|
/// Start offset of the line at the given row (0-based) index.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use gpui_component::input::{Rope, RopeExt};
|
||||||
|
///
|
||||||
|
/// let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||||
|
/// assert_eq!(rope.line_start_offset(0), 0);
|
||||||
|
/// assert_eq!(rope.line_start_offset(1), 6);
|
||||||
|
/// ```
|
||||||
fn line_start_offset(&self, row: usize) -> usize;
|
fn line_start_offset(&self, row: usize) -> usize;
|
||||||
|
|
||||||
/// Return a line slice at the given row (0-based) index. including `\r` if present, but not `\n`.
|
|
||||||
fn slice_row(&self, row: usize) -> RopeSlice<'_>;
|
|
||||||
|
|
||||||
fn slice_rows(&self, rows_range: Range<usize>) -> RopeSlice<'_>;
|
|
||||||
|
|
||||||
fn rows(&self) -> impl Iterator<Item = RopeSlice<'_>> + '_ {
|
|
||||||
(0..self.lines_len()).map(|row| self.slice_row(row))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Line the end offset (including `\n`) of the line at the given row (0-based) index.
|
/// Line the end offset (including `\n`) of the line at the given row (0-based) index.
|
||||||
///
|
///
|
||||||
/// Return the end of the rope if the row is out of bounds.
|
/// Return the end of the rope if the row is out of bounds.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use gpui_component::input::{Rope, RopeExt};
|
||||||
|
/// let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||||
|
/// assert_eq!(rope.line_end_offset(0), 5); // "Hello\n"
|
||||||
|
/// assert_eq!(rope.line_end_offset(1), 12); // "World\r\n"
|
||||||
|
/// ```
|
||||||
fn line_end_offset(&self, row: usize) -> usize;
|
fn line_end_offset(&self, row: usize) -> usize;
|
||||||
|
|
||||||
|
/// Return a line slice at the given row (0-based) index. including `\r` if present, but not `\n`.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use gpui_component::input::{Rope, RopeExt};
|
||||||
|
/// let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||||
|
/// assert_eq!(rope.slice_line(0).to_string(), "Hello");
|
||||||
|
/// assert_eq!(rope.slice_line(1).to_string(), "World\r");
|
||||||
|
/// assert_eq!(rope.slice_line(2).to_string(), "This is a test 中文");
|
||||||
|
/// assert_eq!(rope.slice_line(6).to_string(), ""); // out of bounds
|
||||||
|
/// ```
|
||||||
|
fn slice_line(&self, row: usize) -> RopeSlice<'_>;
|
||||||
|
|
||||||
|
/// Return a slice of rows in the given range (0-based, end exclusive).
|
||||||
|
///
|
||||||
|
/// If the range is out of bounds, it will be clamped to the valid range.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use gpui_component::input::{Rope, RopeExt};
|
||||||
|
/// let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||||
|
/// assert_eq!(rope.slice_lines(0..2).to_string(), "Hello\nWorld\r");
|
||||||
|
/// assert_eq!(rope.slice_lines(1..3).to_string(), "World\r\nThis is a test 中文");
|
||||||
|
/// assert_eq!(rope.slice_lines(2..5).to_string(), "This is a test 中文\nRope");
|
||||||
|
/// assert_eq!(rope.slice_lines(3..10).to_string(), "Rope");
|
||||||
|
/// assert_eq!(rope.slice_lines(5..10).to_string(), ""); // out of bounds
|
||||||
|
/// ```
|
||||||
|
fn slice_lines(&self, rows_range: Range<usize>) -> RopeSlice<'_>;
|
||||||
|
|
||||||
|
/// Return an iterator over all lines in the rope.
|
||||||
|
///
|
||||||
|
/// Each line slice includes `\r` if present, but not `\n`.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use gpui_component::input::{Rope, RopeExt};
|
||||||
|
/// let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||||
|
/// let lines: Vec<_> = rope.iter_lines().map(|r| r.to_string()).collect();
|
||||||
|
/// assert_eq!(lines, vec!["Hello", "World\r", "This is a test 中文", "Rope"]);
|
||||||
|
/// ```
|
||||||
|
fn iter_lines(&self) -> RopeLines<'_>;
|
||||||
|
|
||||||
/// Return the number of lines in the rope.
|
/// Return the number of lines in the rope.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use gpui_component::input::{Rope, RopeExt};
|
||||||
|
/// let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||||
|
/// assert_eq!(rope.lines_len(), 4);
|
||||||
|
/// ```
|
||||||
fn lines_len(&self) -> usize;
|
fn lines_len(&self) -> usize;
|
||||||
|
|
||||||
/// Return the length of the row (0-based) in characters, including `\r` if present, but not `\n`.
|
/// Return the length of the row (0-based) in characters, including `\r` if present, but not `\n`.
|
||||||
|
///
|
||||||
|
/// If the row is out of bounds, return 0.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use gpui_component::input::{Rope, RopeExt};
|
||||||
|
/// let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||||
|
/// assert_eq!(rope.line_len(0), 5); // "Hello"
|
||||||
|
/// assert_eq!(rope.line_len(1), 6); // "World\r"
|
||||||
|
/// assert_eq!(rope.line_len(2), 21); // "This is a test 中文"
|
||||||
|
/// assert_eq!(rope.line_len(4), 0); // out of bounds
|
||||||
|
/// ```
|
||||||
fn line_len(&self, row: usize) -> usize;
|
fn line_len(&self, row: usize) -> usize;
|
||||||
|
|
||||||
/// Total number of characters in the rope.
|
|
||||||
fn chars_count(&self) -> usize;
|
|
||||||
|
|
||||||
/// Replace the text in the given byte range with new text.
|
/// Replace the text in the given byte range with new text.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// - If the range is not on char boundary.
|
||||||
|
/// - If the range is out of bounds.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use gpui_component::input::{Rope, RopeExt};
|
||||||
|
/// let mut rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||||
|
/// rope.replace(6..11, "Universe");
|
||||||
|
/// assert_eq!(rope.to_string(), "Hello\nUniverse\r\nThis is a test 中文\nRope");
|
||||||
|
/// ```
|
||||||
fn replace(&mut self, range: Range<usize>, new_text: &str);
|
fn replace(&mut self, range: Range<usize>, new_text: &str);
|
||||||
|
|
||||||
/// Get char at the given offset (byte).
|
/// Get char at the given offset (byte).
|
||||||
|
|
@ -44,24 +165,39 @@ pub trait RopeExt {
|
||||||
fn char_at(&self, offset: usize) -> Option<char>;
|
fn char_at(&self, offset: usize) -> Option<char>;
|
||||||
|
|
||||||
/// Get the byte offset from the given line, column [`Position`] (0-based).
|
/// Get the byte offset from the given line, column [`Position`] (0-based).
|
||||||
|
///
|
||||||
|
/// The column is in characters.
|
||||||
fn position_to_offset(&self, line_col: &Position) -> usize;
|
fn position_to_offset(&self, line_col: &Position) -> usize;
|
||||||
|
|
||||||
/// Get the line, column [`Position`] (0-based) from the given byte offset.
|
/// Get the line, column [`Position`] (0-based) from the given byte offset.
|
||||||
|
///
|
||||||
|
/// The column is in characters.
|
||||||
fn offset_to_position(&self, offset: usize) -> Position;
|
fn offset_to_position(&self, offset: usize) -> Position;
|
||||||
|
|
||||||
|
/// Get point (row, column) from the given byte offset.
|
||||||
|
///
|
||||||
|
/// The column is in bytes.
|
||||||
fn offset_to_point(&self, offset: usize) -> Point;
|
fn offset_to_point(&self, offset: usize) -> Point;
|
||||||
|
|
||||||
|
/// Get byte offset from the given point (row, column).
|
||||||
|
///
|
||||||
|
/// The column is 0-based in bytes.
|
||||||
fn point_to_offset(&self, point: Point) -> usize;
|
fn point_to_offset(&self, point: Point) -> usize;
|
||||||
|
|
||||||
/// Get the word byte range at the given offset (byte).
|
/// Get the word byte range at the given byte offset (0-based).
|
||||||
fn word_range(&self, offset: usize) -> Option<Range<usize>>;
|
fn word_range(&self, offset: usize) -> Option<Range<usize>>;
|
||||||
|
|
||||||
/// Get word at the given offset (byte).
|
/// Get word at the given byte offset (0-based).
|
||||||
fn word_at(&self, offset: usize) -> String;
|
fn word_at(&self, offset: usize) -> String;
|
||||||
|
|
||||||
/// Convert offset_utf16 to offset (byte).
|
/// Convert offset in UTF-16 to byte offset (0-based).
|
||||||
|
///
|
||||||
|
/// Runs in O(log N) time.
|
||||||
fn offset_utf16_to_offset(&self, offset_utf16: usize) -> usize;
|
fn offset_utf16_to_offset(&self, offset_utf16: usize) -> usize;
|
||||||
|
|
||||||
/// Convert offset (byte) to offset_utf16.
|
/// Convert byte offset (0-based) to offset in UTF-16.
|
||||||
|
///
|
||||||
|
/// Runs in O(log N) time.
|
||||||
fn offset_to_offset_utf16(&self, offset: usize) -> usize;
|
fn offset_to_offset_utf16(&self, offset: usize) -> usize;
|
||||||
|
|
||||||
/// Get a clipped offset (avoid in a char boundary).
|
/// Get a clipped offset (avoid in a char boundary).
|
||||||
|
|
@ -69,11 +205,22 @@ pub trait RopeExt {
|
||||||
/// - If Bias::Left and inside the char boundary, return the ix - 1;
|
/// - If Bias::Left and inside the char boundary, return the ix - 1;
|
||||||
/// - If Bias::Right and in inside char boundary, return the ix + 1;
|
/// - If Bias::Right and in inside char boundary, return the ix + 1;
|
||||||
/// - Otherwise return the ix.
|
/// - Otherwise return the ix.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use gpui_component::input::{Rope, RopeExt};
|
||||||
|
/// use sum_tree::Bias;
|
||||||
|
///
|
||||||
|
/// let rope = Rope::from("Hello 中文🎉 test\nRope");
|
||||||
|
/// assert_eq!(rope.clip_offset(5, Bias::Left), 5);
|
||||||
|
/// // Inside multi-byte character '中' (3 bytes)
|
||||||
|
/// assert_eq!(rope.clip_offset(7, Bias::Left), 6);
|
||||||
|
/// assert_eq!(rope.clip_offset(7, Bias::Right), 9);
|
||||||
|
/// ```
|
||||||
fn clip_offset(&self, offset: usize, bias: Bias) -> usize;
|
fn clip_offset(&self, offset: usize, bias: Bias) -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RopeExt for Rope {
|
impl RopeExt for Rope {
|
||||||
fn slice_row(&self, row: usize) -> RopeSlice<'_> {
|
fn slice_line(&self, row: usize) -> RopeSlice<'_> {
|
||||||
if row >= self.lines_len() {
|
if row >= self.lines_len() {
|
||||||
return self.slice(0..0);
|
return self.slice(0..0);
|
||||||
}
|
}
|
||||||
|
|
@ -86,14 +233,18 @@ impl RopeExt for Rope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn slice_rows(&self, rows_range: Range<usize>) -> RopeSlice<'_> {
|
fn slice_lines(&self, rows_range: Range<usize>) -> RopeSlice<'_> {
|
||||||
let start = self.line_start_offset(rows_range.start);
|
let start = self.line_start_offset(rows_range.start);
|
||||||
let end = self.line_end_offset(rows_range.end.saturating_sub(1));
|
let end = self.line_end_offset(rows_range.end.saturating_sub(1));
|
||||||
self.slice(start..end)
|
self.slice(start..end)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn iter_lines(&self) -> RopeLines<'_> {
|
||||||
|
RopeLines::new(&self)
|
||||||
|
}
|
||||||
|
|
||||||
fn line_len(&self, row: usize) -> usize {
|
fn line_len(&self, row: usize) -> usize {
|
||||||
self.slice_row(row).len()
|
self.slice_line(row).len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn line_start_offset(&self, row: usize) -> usize {
|
fn line_start_offset(&self, row: usize) -> usize {
|
||||||
|
|
@ -118,7 +269,7 @@ impl RopeExt for Rope {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn position_to_offset(&self, pos: &Position) -> usize {
|
fn position_to_offset(&self, pos: &Position) -> usize {
|
||||||
let line = self.slice_row(pos.line as usize);
|
let line = self.slice_line(pos.line as usize);
|
||||||
self.line_start_offset(pos.line as usize)
|
self.line_start_offset(pos.line as usize)
|
||||||
+ line
|
+ line
|
||||||
.chars()
|
.chars()
|
||||||
|
|
@ -129,7 +280,7 @@ impl RopeExt for Rope {
|
||||||
|
|
||||||
fn offset_to_position(&self, offset: usize) -> Position {
|
fn offset_to_position(&self, offset: usize) -> Position {
|
||||||
let point = self.offset_to_point(offset);
|
let point = self.offset_to_point(offset);
|
||||||
let line = self.slice_row(point.row);
|
let line = self.slice_line(point.row);
|
||||||
let offset = line.utf16_to_byte_idx(line.byte_to_utf16_idx(point.column));
|
let offset = line.utf16_to_byte_idx(line.byte_to_utf16_idx(point.column));
|
||||||
let character = line.slice(..offset).chars().count();
|
let character = line.slice(..offset).chars().count();
|
||||||
Position::new(point.row as u32, character as u32)
|
Position::new(point.row as u32, character as u32)
|
||||||
|
|
@ -147,10 +298,6 @@ impl RopeExt for Rope {
|
||||||
self.len_lines(LineType::LF_CR)
|
self.len_lines(LineType::LF_CR)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn chars_count(&self) -> usize {
|
|
||||||
self.chars().count()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn char_at(&self, offset: usize) -> Option<char> {
|
fn char_at(&self, offset: usize) -> Option<char> {
|
||||||
if offset > self.len() {
|
if offset > self.len() {
|
||||||
return None;
|
return None;
|
||||||
|
|
@ -248,13 +395,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_line() {
|
fn test_line() {
|
||||||
let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||||
assert_eq!(rope.slice_row(0).to_string(), "Hello");
|
assert_eq!(rope.slice_line(0).to_string(), "Hello");
|
||||||
assert_eq!(rope.slice_row(1).to_string(), "World\r");
|
assert_eq!(rope.slice_line(1).to_string(), "World\r");
|
||||||
assert_eq!(rope.slice_row(2).to_string(), "This is a test 中文");
|
assert_eq!(rope.slice_line(2).to_string(), "This is a test 中文");
|
||||||
assert_eq!(rope.slice_row(3).to_string(), "Rope");
|
assert_eq!(rope.slice_line(3).to_string(), "Rope");
|
||||||
|
|
||||||
// over bounds
|
// over bounds
|
||||||
assert_eq!(rope.slice_row(6).to_string(), "");
|
assert_eq!(rope.slice_line(6).to_string(), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -267,6 +414,16 @@ mod tests {
|
||||||
assert_eq!(rope.lines_len(), 1);
|
assert_eq!(rope.lines_len(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lines() {
|
||||||
|
let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||||
|
let lines: Vec<_> = rope.iter_lines().map(|r| r.to_string()).collect();
|
||||||
|
assert_eq!(
|
||||||
|
lines,
|
||||||
|
vec!["Hello", "World\r", "This is a test 中文", "Rope"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_eq() {
|
fn test_eq() {
|
||||||
let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||||
|
|
@ -278,13 +435,15 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_rows() {
|
fn test_iter_lines() {
|
||||||
let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文\nRope");
|
||||||
let lines: Vec<_> = rope.rows().map(|r| r.to_string()).collect();
|
let lines: Vec<_> = rope
|
||||||
assert_eq!(
|
.iter_lines()
|
||||||
lines,
|
.skip(1)
|
||||||
vec!["Hello", "World\r", "This is a test 中文", "Rope"]
|
.take(2)
|
||||||
);
|
.map(|r| r.to_string())
|
||||||
|
.collect();
|
||||||
|
assert_eq!(lines, vec!["World\r", "This is a test 中文"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -306,16 +465,6 @@ mod tests {
|
||||||
assert_eq!(rope.line_end_offset(4), 39);
|
assert_eq!(rope.line_end_offset(4), 39);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_chars_count() {
|
|
||||||
let rope = Rope::from("Hello\nWorld\r\nThis is a test 中文🎉\nRope");
|
|
||||||
assert_eq!(rope.chars_count(), 36);
|
|
||||||
let rope = Rope::from("");
|
|
||||||
assert_eq!(rope.chars_count(), 0);
|
|
||||||
let rope = Rope::from("Single line");
|
|
||||||
assert_eq!(rope.chars_count(), 11);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_line_column() {
|
fn test_line_column() {
|
||||||
let rope = Rope::from("a 中文🎉 test\nRope");
|
let rope = Rope::from("a 中文🎉 test\nRope");
|
||||||
|
|
|
||||||
|
|
@ -652,7 +652,7 @@ impl InputState {
|
||||||
let mut new_offset = line_start_offset;
|
let mut new_offset = line_start_offset;
|
||||||
|
|
||||||
if let Some((preferred_x, column)) = was_preferred_column {
|
if let Some((preferred_x, column)) = was_preferred_column {
|
||||||
let new_column = column.min(self.text.slice_row(new_row).len());
|
let new_column = column.min(self.text.slice_line(new_row).len());
|
||||||
new_offset = line_start_offset + new_column;
|
new_offset = line_start_offset + new_column;
|
||||||
|
|
||||||
// If in visible range, prefer to use position to get column.
|
// If in visible range, prefer to use position to get column.
|
||||||
|
|
|
||||||
|
|
@ -348,7 +348,7 @@ fn rust_to_style(mut style: StyleRefinement, source: &str) -> (StyleRefinement,
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
let mut method_offset = 0;
|
let mut method_offset = 0;
|
||||||
let mut method = String::new();
|
let mut method = String::new();
|
||||||
for line in rope.rows() {
|
for line in rope.iter_lines() {
|
||||||
if line.to_string().trim().starts_with("//") {
|
if line.to_string().trim().starts_with("//") {
|
||||||
offset += line.len() + 1;
|
offset += line.len() + 1;
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue