use std::collections::{btree_map::Entry, BTreeMap};
use failure::{self, Error, ResultExt};
use neovim_lib::{Neovim, NeovimApi, Value};
use itertools::Itertools;
use card::keyword::Keyword;
use lines::{Line, ParsedLine};
use nocommentiter::CommentLess;
macro_rules! unwrap_or_ok {
($option:expr) => {
match $option {
None => return Ok(()),
Some(t) => t,
}
};
($option:expr, $ret:expr) => {
match $option {
None => return Ok($ret),
Some(t) => t,
}
};
}
#[derive(Default, Debug)]
pub struct FoldList {
folds: BTreeMap<[u64; 2], Keyword>,
folds_level2: BTreeMap<[u64; 2], Keyword>,
fold_texts: BTreeMap<[u64; 2], String>,
fold_texts_level2: BTreeMap<[u64; 2], String>,
}
impl FoldList {
pub fn new() -> FoldList {
FoldList {
folds: BTreeMap::new(),
folds_level2: BTreeMap::new(),
fold_texts: BTreeMap::new(),
fold_texts_level2: BTreeMap::new(),
}
}
pub fn clear(&mut self) {
self.folds.clear();
self.folds_level2.clear();
self.fold_texts.clear();
self.fold_texts_level2.clear();
}
fn insert(&mut self, start: u64, end: u64, kw: Keyword) -> Result<(), Error> {
match self.folds.entry([start, end]) {
Entry::Occupied(_) => {
return Err(failure::err_msg("Fold already in foldlist!"))
}
Entry::Vacant(entry) => {
entry.insert(kw);
}
}
match self.fold_texts.entry([start, end]) {
Entry::Occupied(_) => {
return Err(failure::err_msg("Foldtext already in fold_texts!"))
}
Entry::Vacant(entry) => {
entry.insert(format!(" {} lines: {:?} ", end - start + 1, kw));
}
}
Ok(())
}
pub fn checked_insert(
&mut self,
start: u64,
end: u64,
kw: Keyword,
) -> Result<(), Error> {
if start <= end {
self.insert(start, end, kw)?
}
Ok(())
}
pub fn remove(&mut self, start: u64, end: u64) -> Result<(), Error> {
self
.folds
.remove(&[start, end])
.ok_or_else(|| failure::err_msg("Could not remove fold from foldlist"))?;
self
.fold_texts
.remove(&[start, end])
.ok_or_else(|| failure::err_msg("Could not remove fold from foldlist"))?;
Ok(())
}
pub fn recreate_all(
&mut self,
keywords: &[Option<Keyword>],
lines: &[Line],
) -> Result<(), Error> {
self.clear();
self.add_folds(keywords, lines)?;
self.recreate_level2()
}
fn recreate_level2(&mut self) -> Result<(), Error> {
self.folds_level2.clear();
if self.folds.len() < 2 {
return Ok(());
}
let grouped = self.folds.iter().group_by(|(_, &kw)| kw);
for (kw, mut group) in &grouped {
let mut group = group.enumerate();
let firstfold = group.next().expect("Empty group from group_by!").1;
let (nr, lastfold) = match group.last() {
None => continue, Some((i, e)) => (i, e),
};
let firstline = firstfold.0[0];
let lastline = lastfold.0[1];
if firstline < lastline {
match self.folds_level2.entry([firstline, lastline]) {
Entry::Occupied(_) => {
return Err(failure::err_msg("Fold already in foldlist_level2!"))
}
Entry::Vacant(entry) => {
entry.insert(kw);
}
}
match self.fold_texts_level2.entry([firstline, lastline]) {
Entry::Occupied(_) => {
return Err(failure::err_msg("Foldtext already in fold_texts!"))
}
Entry::Vacant(entry) => {
entry.insert(format!(" {} {:?}s ", nr + 1, kw));
}
}
}
}
Ok(())
}
pub fn resend_all(&self, nvim: &mut Neovim) -> Result<(), Error> {
let luafn = "require('nvimpam').update_folds(...)";
let mut luaargs = vec![];
for (range, text) in self.fold_texts.iter().chain(&self.fold_texts_level2) {
luaargs.push(Value::from(vec![
Value::from(range[0] + 1),
Value::from(range[1] + 1),
Value::from(text.to_string()),
]));
}
nvim
.execute_lua(luafn, vec![Value::from(luaargs)])
.context("Execute lua failed")?;
Ok(())
}
pub fn to_vec(&self, level: u8) -> Vec<(u64, u64, Keyword)> {
if level == 1 {
self.folds.iter().map(|(r, k)| (r[0], r[1], *k)).collect()
} else if level == 2 {
self
.folds_level2
.iter()
.map(|(r, k)| (r[0], r[1], *k))
.collect()
} else {
unimplemented!()
}
}
pub fn add_folds(
&mut self,
keywords: &[Option<Keyword>],
lines: &[Line],
) -> Result<(), Error> {
debug_assert!(keywords.len() == lines.len());
let mut li = keywords
.iter()
.zip(lines)
.enumerate()
.map(ParsedLine::from)
.remove_comments();
let mut foldstart;
let mut foldend;
let mut foldkw;
let mut skipped;
let mut nextline = unwrap_or_ok!(li.skip_to_next_keyword());
loop {
foldkw = nextline.keyword;
foldstart = nextline.number;
skipped = li.skip_fold(nextline);
foldend = skipped.skip_end.unwrap_or_else(|| lines.len() - 1);
self.checked_insert(foldstart as u64, foldend as u64, *foldkw)?;
if let Some(Some(kl)) =
skipped.nextline.map(|pl| pl.try_into_keywordline())
{
nextline = kl;
} else {
nextline = unwrap_or_ok!(li.skip_to_next_keyword());
}
}
}
}
#[cfg(test)]
mod tests {
use card::keyword::Keyword::*;
const LINES: [&'static str; 20] = [
"NODE / 1 0. 0.5 0.",
"NODE / 1 0. 0.5 0.",
"NODE / 1 0. 0.5 0.",
"NODE / 1 0. 0.5 0.",
"#Comment here",
"SHELL / 3129 1 1 2967 2971 2970",
"invalid line here",
"SHELL / 3129 1 1 2967 2971 2970",
"SHELL / 3129 1 1 2967 2971 2970",
"#Comment",
"#Comment",
"SHELL / 3129 1 1 2967 2971 2970",
"SHELL / 3129 1 1 2967 2971 2970",
"$Comment",
"SHELL / 3129 1 1 2967 2971 2970",
"SHELL / 3129 1 1 2967 2971 2970",
"$Comment",
"#Comment",
"NODE / 1 0. 0.5 0.",
"NODE / 1 0. 0.5 0.",
];
cardtest!(
fold_general1,
LINES,
vec![(0, 3, Node), (5, 5, Shell), (7, 15, Shell), (18, 19, Node)]
);
cardtest!(
fold_general2,
LINES[4..],
vec![(1, 1, Shell), (3, 11, Shell), (14, 15, Node)]
);
cardtest!(
fold_general3,
LINES[6..],
vec![(1, 9, Shell), (12, 13, Node)]
);
cardtest!(
fold_general4,
LINES[13..19],
vec![(1, 2, Shell), (5, 5, Node)]
);
const LINES2: [&'static str; 24] = [
"NODE / 1 0. 0.5 0.",
"NODE / 1 0. 0.5 0.",
"NODE / 1 0. 0.5 0.",
"NODE / 1 0. 0.5 0.",
"#Comment here",
"SHELL / 3129 1 1 2967 2971 2970",
"NODE / 3129 1 1 2967 2971 2970",
"NODE / 3129 1 1 2967 2971 2970",
"#Comment",
"#Comment",
"SHELL / 3129 1 1 2967 2971 2970",
"SHELL / 3129 1 1 2967 2971 2970",
"$Comment",
"SHELL / 3129 1 1 2967 2971 2970",
"SHELL / 3129 1 1 2967 2971 2970",
"$Comment",
"#Comment",
"NODE / 1 0. 0.5 0.",
"NODE / 1 0. 0.5 0.",
"NODE / 1 0. 0.5 0.",
"SHELL / 3129 1 1 2967 2971 2970",
"SHELL / 3129 1 1 2967 2971 2970",
"SHELL / 3129 1 1 2967 2971 2970",
"SHELL / 3129 1 1 2967 2971 2970",
];
cardtest!(
fold_general_gather,
LINES2,
vec![
(0, 3, Node),
(5, 5, Shell),
(6, 7, Node),
(10, 14, Shell),
(17, 19, Node),
(20, 23, Shell),
]
);
const RBODIES: [&'static str; 13] = [
"RBODY / 1 0 0 0 0 ",
"NAME RBODY / ->1 ",
" END",
"RBODY / 1 0 0 0 0 ",
"NAME RBODY / ->1 ",
" END",
"RBODY / 1 0 0 0 0 ",
"#COMMENT",
"NAME RBODY / ->1 ",
" END",
"RBODY / 1 0 0 0 0 ",
"NAME RBODY / ->1 ",
" END",
];
cardtest!(
fold_level2_rbodies,
RBODIES,
vec![
(0, 2, Rbody0),
(3, 5, Rbody0),
(6, 9, Rbody0),
(10, 12, Rbody0),
],
vec![(0, 12, Rbody0)]
);
}