use std::{cell::Cell, io::Read};
use crate::structured::yaml::{
deserializer,
yamlz::{self, Row, RowOperation},
};
use crate::structured::{ProjectionViewport, projection_viewport};
#[derive(Clone)]
pub struct Document {
rows: Vec<Row>,
position: usize,
line_numbers: Vec<Option<usize>>,
line_count: usize,
viewport: Cell<ProjectionViewport>,
}
impl Document {
pub fn new<'a, I: IntoIterator<Item = &'a serde_yaml::Value>>(iter: I) -> Self {
Self::from_rows(yamlz::create_rows(iter))
}
#[allow(clippy::should_implement_trait)]
pub fn from_str(input: &str) -> Result<Self, serde_yaml::Error> {
<Self as std::str::FromStr>::from_str(input)
}
pub fn from_reader<R: Read>(reader: R) -> Result<Self, serde_yaml::Error> {
deserializer::from_reader(reader).map(Self::from_rows)
}
fn from_rows(rows: Vec<Row>) -> Self {
let position = rows.head();
let mut line_numbers = vec![None; rows.len()];
let mut line_count = 0;
if !rows.is_empty() {
let mut index = position;
loop {
line_count += 1;
line_numbers[index] = Some(line_count);
let next = rows.down(index);
if next == index {
break;
}
index = next;
}
}
Self {
rows,
position,
line_numbers,
line_count,
viewport: Cell::default(),
}
}
}
impl std::str::FromStr for Document {
type Err = serde_yaml::Error;
fn from_str(input: &str) -> Result<Self, Self::Err> {
deserializer::from_str(input).map(Self::from_rows)
}
}
impl Document {
pub fn rows(&self) -> &[Row] {
&self.rows
}
pub fn extract_rows_from_current(&self, n: usize) -> Vec<Row> {
self.rows.extract(self.position, n)
}
pub(super) fn project_viewport(&self, n: usize) -> (usize, usize) {
projection_viewport(&self.rows, self.position, n, &self.viewport)
}
pub(super) fn extract_rows_from(&self, position: usize, n: usize) -> Vec<Row> {
self.rows.extract(position, n)
}
pub fn visible_rows(&self) -> Vec<Row> {
self.rows.extract(self.rows.head(), usize::MAX)
}
pub(super) fn visible_line_numbers(&self) -> Vec<usize> {
self.line_numbers_from(self.rows.head(), usize::MAX)
}
pub(super) fn line_numbers_from_viewport(&self, position: usize, n: usize) -> Vec<usize> {
self.line_numbers_from(position, n)
}
pub(super) fn line_count(&self) -> usize {
self.line_count
}
fn line_numbers_from(&self, mut index: usize, n: usize) -> Vec<usize> {
if self.rows.is_empty() || n == 0 {
return Vec::new();
}
let mut line_numbers = Vec::new();
for _ in 0..n {
let stable_line_number = self.line_numbers[index].unwrap_or_else(|| {
self.line_numbers[index + 1..]
.iter()
.find_map(|number| *number)
.expect("a collapsed YAML root must have a numbered visible descendant")
});
line_numbers.push(stable_line_number);
let next = self.rows.down(index);
if next == index {
break;
}
index = next;
}
line_numbers
}
pub fn visible_position(&self) -> usize {
visible_position(&self.rows, self.position)
}
pub fn toggle(&mut self) {
let index = self.rows.toggle(self.position);
self.position = index;
}
pub fn toggle_at(&mut self, row_index: usize) {
if row_index < self.rows.len() {
self.position = self.rows.toggle(row_index);
}
}
pub fn row_index_at_visible_position(&self, visible_position: usize) -> Option<usize> {
row_index_at_visible_position(&self.rows, self.rows.head(), visible_position)
}
pub fn row_index_at_visible_offset_from_current(&self, visible_offset: usize) -> Option<usize> {
row_index_at_visible_position(&self.rows, self.position, visible_offset)
}
pub(super) fn row_index_at_viewport_position(&self, visible_position: usize) -> Option<usize> {
let viewport = self.viewport.get();
viewport
.initialized
.then(|| row_index_at_visible_position(&self.rows, viewport.start, visible_position))
.flatten()
}
pub fn set_nodes_visibility(&mut self, collapsed: bool) {
self.rows.set_rows_visibility(collapsed);
self.position = self.rows.head();
}
pub fn up(&mut self) -> bool {
let index = self.rows.up(self.position);
let ret = index != self.position;
self.position = index;
ret
}
pub fn head(&mut self) -> bool {
self.position = self.rows.head();
true
}
pub fn down(&mut self) -> bool {
let index = self.rows.down(self.position);
let ret = index != self.position;
self.position = index;
ret
}
pub fn tail(&mut self) -> bool {
self.position = self.rows.tail();
true
}
}
fn visible_position(rows: &Vec<Row>, target: usize) -> usize {
let mut position = rows.head();
let mut visible = 0;
while position != target {
let next = rows.down(position);
if next == position {
break;
}
position = next;
visible += 1;
}
visible
}
fn row_index_at_visible_position(rows: &Vec<Row>, start: usize, target: usize) -> Option<usize> {
if rows.is_empty() {
return None;
}
let mut position = start;
for _ in 0..target {
let next = rows.down(position);
if next == position {
return None;
}
position = next;
}
Some(position)
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use serde::Deserialize;
use super::Document;
const INPUT: &str = r#"
---
name: alice
1: one
true: enabled
null: nothing
? [first, second]
: sequence-key
tagged: !Thing
nested: value
items:
- null
- {}
- &anchor
aliased: true
- *anchor
---
!Root
second: [1, 2]
"#;
fn via_value(input: &str) -> Document {
let values = serde_yaml::Deserializer::from_str(input)
.map(serde_yaml::Value::deserialize)
.collect::<Result<Vec<_>, _>>()
.unwrap();
Document::new(values.iter())
}
mod from_str {
use super::*;
#[test]
fn matches_value_conversion() {
let expected = via_value(INPUT);
let actual = Document::from_str(INPUT).unwrap();
assert_eq!(actual.rows(), expected.rows());
}
#[test]
fn matches_value_conversion_for_scalar_forms() {
for input in [
"",
"null\n",
"~\n",
".nan\n",
".inf\n",
"-.inf\n",
"'quoted'\n",
"|\n multiline\n text\n",
"!!str 1\n",
"!!int '1'\n",
"---\n...\n---\n{}\n",
] {
let expected = via_value(input);
let actual = Document::from_str(input).unwrap();
assert_eq!(actual.rows(), expected.rows(), "input: {input:?}");
}
}
#[test]
fn reports_invalid_yaml() {
assert!(Document::from_str("key: [unterminated").is_err());
assert!(Document::from_str("duplicate: one\nduplicate: two\n").is_err());
}
}
mod from_reader {
use super::*;
#[test]
fn matches_value_conversion() {
let expected = via_value(INPUT);
let actual = Document::from_reader(Cursor::new(INPUT.as_bytes())).unwrap();
assert_eq!(actual.rows(), expected.rows());
}
#[test]
fn reports_invalid_yaml() {
assert!(Document::from_reader(Cursor::new(b"key: {")).is_err());
}
}
}