#[cfg(feature = "rope")]
use super::LineColumn;
use super::Position;
use crate::core::errors::{EditorError, Result};
#[derive(Debug, Clone, Default)]
pub struct PositionBuilder {
line: Option<usize>,
column: Option<usize>,
offset: Option<usize>,
}
impl PositionBuilder {
#[must_use]
pub const fn new() -> Self {
Self {
line: None,
column: None,
offset: None,
}
}
#[must_use]
pub const fn line(mut self, line: usize) -> Self {
self.line = Some(line);
self
}
#[must_use]
pub const fn column(mut self, column: usize) -> Self {
self.column = Some(column);
self
}
#[must_use]
pub const fn offset(mut self, offset: usize) -> Self {
self.offset = Some(offset);
self
}
#[must_use]
pub const fn at_line_start(mut self, line: usize) -> Self {
self.line = Some(line);
self.column = Some(1);
self
}
#[must_use]
pub const fn at_line_end(mut self, line: usize) -> Self {
self.line = Some(line);
self.column = None; self
}
#[must_use]
pub const fn at_start() -> Self {
Self {
line: Some(1),
column: Some(1),
offset: Some(0),
}
}
#[cfg(feature = "rope")]
pub fn build(self, rope: &ropey::Rope) -> Result<Position> {
if let Some(offset) = self.offset {
if offset > rope.len_bytes() {
return Err(EditorError::PositionOutOfBounds {
position: offset,
length: rope.len_bytes(),
});
}
Ok(Position::new(offset))
} else if let Some(line) = self.line {
let line_idx = line.saturating_sub(1);
if line_idx >= rope.len_lines() {
return Err(EditorError::InvalidPosition { line, column: 1 });
}
let line_start = rope.line_to_byte(line_idx);
if let Some(column) = self.column {
LineColumn::new(line, column)?;
let col_idx = column.saturating_sub(1);
let line = rope.line(line_idx);
let mut byte_pos = 0;
let mut char_pos = 0;
for ch in line.chars() {
if char_pos == col_idx {
break;
}
byte_pos += ch.len_utf8();
char_pos += 1;
}
if char_pos < col_idx {
return Err(EditorError::InvalidPosition {
line: self.line.unwrap_or(0),
column,
});
}
Ok(Position::new(line_start + byte_pos))
} else {
let line_end = if line_idx + 1 < rope.len_lines() {
rope.line_to_byte(line_idx + 1).saturating_sub(1)
} else {
rope.len_bytes()
};
Ok(Position::new(line_end))
}
} else {
Ok(Position::start())
}
}
#[cfg(not(feature = "rope"))]
pub fn build(self) -> Result<Position> {
if let Some(offset) = self.offset {
Ok(Position::new(offset))
} else {
Err(EditorError::FeatureNotEnabled {
feature: "line/column position".to_string(),
required_feature: "rope".to_string(),
})
}
}
}