diff options
Diffstat (limited to 'src/files.rs')
-rw-r--r-- | src/files.rs | 490 |
1 files changed, 381 insertions, 109 deletions
diff --git a/src/files.rs b/src/files.rs index 1a038ac..637b99a 100644 --- a/src/files.rs +++ b/src/files.rs @@ -1,14 +1,16 @@ use std::cmp::{Ord, Ordering}; use std::ops::Index; +use std::fs::Metadata; use std::os::unix::fs::MetadataExt; use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, RwLock}; +use std::sync::mpsc::Sender; use std::hash::{Hash, Hasher}; use std::os::unix::ffi::{OsStringExt, OsStrExt}; use std::ffi::{OsStr, OsString}; use lscolors::LsColors; -use mime_detective; +use tree_magic; use users::{get_current_username, get_current_groupname, get_user_by_uid, @@ -16,24 +18,50 @@ use users::{get_current_username, use chrono::TimeZone; use failure::Error; use notify::DebouncedEvent; +use rayon::{ThreadPool, ThreadPoolBuilder}; -use crate::fail::{HResult, HError}; -use crate::dirty::{DirtyBit, Dirtyable}; - - +use crate::fail::{HResult, HError, ErrorLog}; +use crate::dirty::{AsyncDirtyBit, DirtyBit, Dirtyable}; +use crate::preview::{Async, Stale}; +use crate::widget::Events; lazy_static! { static ref COLORS: LsColors = LsColors::from_env().unwrap(); - static ref TAGS: Mutex<(bool, Vec<PathBuf>)> = Mutex::new((false, vec![])); + static ref TAGS: RwLock<(bool, Vec<PathBuf>)> = RwLock::new((false, vec![])); +} + +fn make_pool(sender: Option<Sender<Events>>) -> ThreadPool { + let sender = Arc::new(Mutex::new(sender)); + ThreadPoolBuilder::new() + .num_threads(8) + .exit_handler(move |thread_num| { + if thread_num == 0 { + if let Ok(lock) = sender.lock() { + if let Some(sender) = lock.as_ref() { + sender.send(Events::WidgetReady).ok(); + } + } + } + }) + .build() + .expect("Failed to create thread pool") } pub fn load_tags() -> HResult<()> { std::thread::spawn(|| -> HResult<()> { let tag_path = crate::paths::tagfile_path()?; + + if !tag_path.exists() { + import_tags().log(); + } + let tags = std::fs::read_to_string(tag_path)?; - let mut tags = tags.lines().map(|f| PathBuf::from(f)).collect::<Vec<PathBuf>>(); - let mut tag_lock = TAGS.lock()?; + let mut tags = tags.lines() + .map(|f| + PathBuf::from(f)) + .collect::<Vec<PathBuf>>(); + let mut tag_lock = TAGS.write()?; tag_lock.0 = true; tag_lock.1.append(&mut tags); Ok(()) @@ -41,28 +69,43 @@ pub fn load_tags() -> HResult<()> { Ok(()) } +pub fn import_tags() -> HResult<()> { + let mut ranger_tags = crate::paths::ranger_path()?; + ranger_tags.push("tagged"); + + if ranger_tags.exists() { + let tag_path = crate::paths::tagfile_path()?; + std::fs::copy(ranger_tags, tag_path)?; + } + Ok(()) +} + pub fn check_tag(path: &PathBuf) -> HResult<bool> { tags_loaded()?; - let tagged = TAGS.lock()?.1.contains(path); + let tagged = TAGS.read()?.1.contains(path); Ok(tagged) } pub fn tags_loaded() -> HResult<()> { - let loaded = TAGS.lock()?.0; + let loaded = TAGS.read()?.0; if loaded { Ok(()) } else { HError::tags_not_loaded() } } + #[derive(PartialEq, Eq, Hash, Clone, Debug)] pub struct Files { pub directory: File, pub files: Vec<File>, + pub meta_upto: Option<usize>, + pub meta_updated: bool, pub sort: SortBy, pub dirs_first: bool, pub reverse: bool, pub show_hidden: bool, pub filter: Option<String>, - pub dirty: DirtyBit + pub dirty: DirtyBit, + pub dirty_meta: AsyncDirtyBit, } impl Index<usize> for Files { @@ -74,12 +117,16 @@ impl Index<usize> for Files { impl Dirtyable for Files { - fn get_bit(&self) -> &DirtyBit { - &self.dirty + fn is_dirty(&self) -> bool { + self.dirty.is_dirty() + } + + fn set_dirty(&mut self) { + self.dirty.set_dirty(); } - fn get_bit_mut(&mut self) -> &mut DirtyBit { - &mut self.dirty + fn set_clean(&mut self) { + self.dirty.set_clean(); } } @@ -87,6 +134,8 @@ impl Dirtyable for Files { impl Files { pub fn new_from_path(path: &Path) -> Result<Files, Error> { let direntries: Result<Vec<_>, _> = std::fs::read_dir(&path)?.collect(); + let dirty = DirtyBit::new(); + let dirty_meta = AsyncDirtyBit::new(); let files: Vec<_> = direntries? .iter() @@ -94,23 +143,30 @@ impl Files { let name = file.file_name(); let name = name.to_string_lossy(); let path = file.path(); - File::new(&name, path) + File::new(&name, + path, + Some(dirty_meta.clone())) }) .collect(); let mut files = Files { - directory: File::new_from_path(&path)?, + directory: File::new_from_path(&path, None)?, files: files, + meta_upto: None, + meta_updated: false, sort: SortBy::Name, dirs_first: true, reverse: false, show_hidden: true, filter: None, - dirty: DirtyBit::new() + dirty: dirty, + dirty_meta: dirty_meta, }; files.sort(); + + if files.files.len() == 0 { files.files = vec![File::new_placeholder(&path)?]; } @@ -118,8 +174,12 @@ impl Files { Ok(files) } - pub fn new_from_path_cancellable(path: &Path, stale: Arc<Mutex<bool>>) -> Result<Files, Error> { + pub fn new_from_path_cancellable(path: &Path, + stale: Stale) + -> Result<Files, Error> { let direntries: Result<Vec<_>, _> = std::fs::read_dir(&path)?.collect(); + let dirty = DirtyBit::new(); + let dirty_meta = AsyncDirtyBit::new(); let files: Vec<_> = direntries? .iter() @@ -130,7 +190,10 @@ impl Files { let name = file.file_name(); let name = name.to_string_lossy(); let path = file.path(); - Some(File::new(&name, path)) + Some(File::new_with_stale(&name, + path, + Some(dirty_meta.clone()), + stale.clone())) } }) .fuse() @@ -144,14 +207,17 @@ impl Files { } let mut files = Files { - directory: File::new_from_path(&path)?, + directory: File::new_from_path(&path, None)?, files: files, + meta_upto: None, + meta_updated: false, sort: SortBy::Name, dirs_first: true, reverse: false, show_hidden: true, filter: None, - dirty: DirtyBit::new() + dirty: dirty, + dirty_meta: dirty_meta, }; files.sort(); @@ -163,13 +229,46 @@ impl Files { Ok(files) } + pub fn get_file_mut(&mut self, index: usize) -> Option<&mut File> { + let filter = self.filter.clone(); + let show_hidden = self.show_hidden; + + let file = self.files + .iter_mut() + .filter(|f| !(filter.is_some() && + !f.name.contains(filter.as_ref().unwrap()))) + .filter(|f| !(!show_hidden && f.name.starts_with("."))) + .nth(index); + file + } + + pub fn get_files(&self) -> Vec<&File> { + self.files + .iter() + .filter(|f| !(self.filter.is_some() && + !f.name.contains(self.filter.as_ref().unwrap()))) + .filter(|f| !(!self.show_hidden && f.name.starts_with("."))) + .collect() + } + + pub fn get_files_mut(&mut self) -> Vec<&mut File> { + let filter = self.filter.clone(); + let show_hidden = self.show_hidden; + self.files + .iter_mut() + .filter(|f| !(filter.is_some() && + !f.name.contains(filter.as_ref().unwrap()))) + .filter(|f| !(!show_hidden && f.name.starts_with("."))) + .collect() + } + pub fn sort(&mut self) { match self.sort { SortBy::Name => self .files .sort_by(|a, b| alphanumeric_sort::compare_str(&a.name, &b.name)), SortBy::Size => { - self.meta_all(); + self.meta_all_sync().log(); self.files.sort_by(|a, b| { if a.meta().unwrap().size() == b.meta().unwrap().size() { return alphanumeric_sort::compare_str(&b.name, &a.name); @@ -178,7 +277,7 @@ impl Files { }); } SortBy::MTime => { - self.meta_all(); + self.meta_all_sync().log(); self.files.sort_by(|a, b| { if a.meta().unwrap().mtime() == b.meta().unwrap().mtime() { return alphanumeric_sort::compare_str(&a.name, &b.name); @@ -227,25 +326,37 @@ impl Files { self.show_hidden = !self.show_hidden } - pub fn reload_files(&mut self) { - let dir = self.directory.clone(); - let files = Files::new_from_path(&dir.path()).unwrap(); - let files = files - .files - .into_iter() - .skip_while(|f| f.name.starts_with(".") && !self.show_hidden ) - .collect(); - - self.files = files; - self.set_dirty(); + pub fn replace_file(&mut self, + old: Option<&File>, + new: Option<File>) -> HResult<()> { + let (tag, selected) = if let Some(old) = old { + if let Some(old) = self.find_file_with_path(&old.path) { + (old.tag, old.selected) + } else { + (None, false) + } + } else { + (None, false) + }; + old.map(|old| self.files.remove_item(old)); + new.map(|mut new| { + new.tag = tag; + new.selected = selected; + self.files.push(new); + }); + self.sort(); + Ok(()) } - pub fn handle_event(&mut self, event: &DebouncedEvent) -> HResult<()> { + pub fn handle_event(&mut self, + event: &DebouncedEvent) -> HResult<()> { match event { DebouncedEvent::Create(path) => { self.path_in_here(&path)?; - let file = File::new_from_path(&path)?; + let file = File::new_from_path(&path, + Some(self.dirty_meta.clone()))?; self.files.push(file); + self.sort(); }, DebouncedEvent::Write(path) | DebouncedEvent::Chmod(path) => { self.path_in_here(&path)?; @@ -262,6 +373,7 @@ impl Files { let mut file = self.find_file_with_path(&old_path)?; file.name = new_path.file_name()?.to_string_lossy().to_string(); file.path = new_path.into(); + file.reload_meta()?; }, DebouncedEvent::Error(err, path) => { dbg!(err); @@ -274,12 +386,12 @@ impl Files { } pub fn path_in_here(&self, path: &Path) -> HResult<bool> { - let dir = self.directory.path(); + let dir = &self.directory.path; let path = if path.is_dir() { path } else { path.parent().unwrap() }; if dir == path { Ok(true) } else { - HError::wrong_directory(path.into(), dir)? + HError::wrong_directory(path.into(), dir.to_path_buf())? } } @@ -287,17 +399,62 @@ impl Files { self.files.iter_mut().find(|file| file.path == path) } + pub fn meta_all_sync(&mut self) -> HResult<()> { + for file in self.files.iter_mut() { + if !file.meta_processed { + file.meta_sync().log(); + } + } + self.set_dirty(); + self.meta_updated = true; + Ok(()) + } + pub fn meta_all(&mut self) { - let len = self.files.len(); - self.meta_upto(len); + let len = self.len(); + self.meta_upto(len, None); } - pub fn meta_upto(&mut self, to: usize) { - for file in self.files.iter_mut().take(to) { - file.get_meta().ok(); + pub fn meta_upto(&mut self, to: usize, sender: Option<Sender<Events>>) { + let meta_files = if self.meta_upto > Some(to) { + self.meta_upto.unwrap() + } else { + if to > self.len() { + self.len() + } else { + to + } + }; + + if self.meta_upto >= Some(meta_files) && !self.dirty_meta.is_dirty() { return } + + self.set_dirty(); + self.dirty_meta.set_clean(); + + let meta_pool = make_pool(sender.clone()); + let show_hidden = self.show_hidden; + + for file in self.files + .iter_mut() + .filter(|f| !(!show_hidden && f.name.starts_with("."))) + .take(meta_files) { + if !file.meta_processed { + file.take_meta(&meta_pool, &mut self.meta_updated).ok(); + } + if file.is_dir() { + file.take_dirsize(&meta_pool, &mut self.meta_updated).ok(); + } } + + self.meta_upto = Some(meta_files); } + pub fn meta_set_fresh(&self) -> HResult<()> { + self.files.get(0)?.meta.set_fresh()?; + Ok(()) + } + + pub fn set_filter(&mut self, filter: Option<String>) { self.filter = filter; self.set_dirty(); @@ -308,15 +465,7 @@ impl Files { } pub fn len(&self) -> usize { - match &self.filter { - None => self.files.len(), - Some(filter) => { - self.files - .iter() - .filter(|f| f.name.contains(filter)) - .count() - } - } + self.get_files().len() } pub fn get_selected(&self) -> Vec<&File> { @@ -364,86 +513,224 @@ impl Hash for File { fn hash<H: Hasher>(&self, state: &mut H) { self.name.hash(state); self.path.hash(state); - self.selected.hash(state); } } impl Eq for File {} -#[derive(Debug, Clone)] + +#[derive(Clone, Debug)] pub struct File { pub name: String, pub path: PathBuf, pub kind: Kind, + pub dirsize: Option<Async<usize>>, pub target: Option<PathBuf>, pub color: Option<lscolors::Color>, - pub meta: Option<std::fs::Metadata>, + pub meta: Async<Metadata>, + pub dirty_meta: Option<AsyncDirtyBit>, + pub meta_processed: bool, pub selected: bool, pub tag: Option<bool> - // flags: Option<String>, } impl File { pub fn new( name: &str, path: PathBuf, - ) -> File { + dirty_meta: Option<AsyncDirtyBit>) -> File { let tag = check_tag(&path).ok(); + let meta = File::make_async_meta(&path, dirty_meta.clone(), None); + let dirsize = if path.is_dir() { + Some(File::make_async_dirsize(&path, dirty_meta.clone(), None)) + } else { None }; File { name: name.to_string(), kind: if path.is_dir() { Kind::Directory } else { Kind::File }, path: path, + dirsize: dirsize, target: None, - meta: None, + meta: meta, + meta_processed: false, + dirty_meta: dirty_meta, color: None, selected: false, tag: tag, } } - pub fn new_from_path(path: &Path) -> HResult<File> { + pub fn new_with_stale(name: &str, + path: PathBuf, + dirty_meta: Option<AsyncDirtyBit>, + stale: Stale) -> File { + let tag = check_tag(&path).ok(); + let meta = File::make_async_meta(&path, + dirty_meta.clone(), + Some(stale.clone())); + let dirsize = if path.is_dir() { + Some(File::make_async_dirsize(&path, + dirty_meta.clone(), + Some(stale))) + } else { None }; + + File { + name: name.to_string(), + kind: if path.is_dir() { Kind::Directory } else { Kind::File }, + path: path, + dirsize: dirsize, + target: None, + meta: meta, + meta_processed: false, + dirty_meta: dirty_meta, + color: None, + selected: false, + tag: tag, + } + } + + pub fn new_from_path(path: &Path, + dirty_meta: Option<AsyncDirtyBit>) -> HResult<File> { let pathbuf = path.to_path_buf(); let name = path .file_name() .map(|name| name.to_string_lossy().to_string()) .unwrap_or("/".to_string()); - Ok(File::new(&name, pathbuf)) + Ok(File::new(&name, pathbuf, dirty_meta)) } pub fn new_placeholder(path: &Path) -> Result<File, Error> { - let mut file = File::new_from_path(path)?; + let mut file = File::new_from_path(path, None)?; file.name = "<empty>".to_string(); file.kind = Kind::Placeholder; Ok(file) } - pub fn meta(&self) -> HResult<std::fs::Metadata> { - match &self.meta { - Some(meta) => Ok(meta.clone()), - None => { Ok(std::fs::symlink_metadata(&self.path)?) } + pub fn meta_sync(&mut self) -> HResult<()> { + let stale = self.meta.get_stale(); + let meta = std::fs::metadata(&self.path)?; + self.meta = Async::new_with_value(meta); + self.meta.put_stale(stale); + self.process_meta() + } + + pub fn make_async_meta(path: &PathBuf, + dirty_meta: Option<AsyncDirtyBit>, + stale_preview: Option<Stale>) -> Async<Metadata> { + let path = path.clone(); + + let meta_closure = Box::new(move |stale: Stale| { + if stale.is_stale()? { HError::stale()? } + Ok(std::fs::symlink_metadata(&path)?) + }); + + let mut meta = match stale_preview { + Some(stale) => Async::new_with_stale(meta_closure, stale), + None => Async::new(meta_closure) + }; + if let Some(dirty_meta) = dirty_meta { + meta.on_ready(Box::new(move || { + let mut dirty_meta = dirty_meta.clone(); + dirty_meta.set_dirty(); + + Ok(()) + })); } + meta } - pub fn get_meta(&mut self) -> HResult<()> { - if let Some(_) = self.meta { return Ok(()) } + pub fn make_async_dirsize(path: &PathBuf, + dirty_meta: Option<AsyncDirtyBit>, + stale_preview: Option<Stale>) -> Async<usize> { + let path = path.clone(); - let meta = std::fs::symlink_metadata(&self.path)?; - let color = self.get_color(&meta); - let target = if meta.file_type().is_symlink() { - self.path.read_link().ok() - } else { None }; + let dirsize_closure = Box::new(move |stale: Stale| { + if stale.is_stale()? { HError::stale()? } + Ok(std::fs::read_dir(&path)?.count()) + }); - self.meta = Some(meta); - self.color = color; - self.target = target; + let mut dirsize = match stale_preview { + Some(stale) => Async::new_with_stale(dirsize_closure, stale), + None => Async::new(dirsize_closure) + }; + + if let Some(dirty_meta) = dirty_meta { + dirsize.on_ready(Box::new(move || { + let mut dirty_meta = dirty_meta.clone(); + dirty_meta.set_dirty(); + + Ok(()) + })); + } + dirsize + } + + pub fn meta(&self) -> HResult<&Metadata> { + self.meta.get() + } + + fn take_dirsize(&mut self, + pool: &ThreadPool, + meta_updated: &mut bool) -> HResult<()> { + let dirsize = self.dirsize.as_mut()?; + if let Ok(_) = dirsize.value { return Ok(()) } + + match dirsize.take_async() { + Ok(_) => { *meta_updated = true; }, + Err(HError::AsyncNotReadyError) => { dirsize.run_pooled(&*pool).ok(); }, + Err(HError::AsyncAlreadyTakenError) => {}, + Err(HError::NoneError) => {}, + err @ Err(_) => { err?; } + } + Ok(()) + } + + pub fn take_meta(&mut self, + pool: &ThreadPool, + meta_updated: &mut bool) -> HResult<()> { + if self.meta_processed { return Ok(()) } + + match self.meta.take_async() { + Ok(_) => { *meta_updated = true; }, + Err(HError::AsyncNotReadyError) => { self.meta.run_pooled(&*pool).ok(); }, + Err(HError::AsyncAlreadyTakenError) => {}, + Err(HError::NoneError) => {}, + err @ Err(_) => { err?; } + } + + self.process_meta()?; + + Ok(()) + } + + pub fn process_meta(&mut self) -> HResult<()> { + if let Ok(meta) = self.meta.get() { + let color = self.get_color(&meta); + let target = if meta.file_type().is_symlink() { + self.path.read_link().ok() + } else { None }; + + self.color = color; + self.target = target; + self.meta_processed = true; + } Ok(()) } pub fn reload_meta(&mut self) -> HResult<()> { - self.meta = None; - self.get_meta() + self.meta_processed = false; + self.meta = File::make_async_meta(&self.path, + self.dirty_meta.clone(), + None); + self.meta.run().log(); + + if self.dirsize.is_some() { + self.dirsize + = Some(File::make_async_dirsize(&self.path, self.dirty_meta.clone(), None)); + self.dirsize.as_mut()?.run().log(); + } + Ok(()) } fn get_color(&self, meta: &std::fs::Metadata) -> Option<lscolors::Color> { @@ -454,13 +741,8 @@ impl File { } pub fn calculate_size(&self) -> HResult<(u64, String)> { - if self.is_dir() { - let dir_iterator = std::fs::read_dir(&self.path); - match dir_iterator { - Ok(dir_iterator) => return Ok((dir_iterator.count() as u64, - "".to_string())), - Err(_) => return Ok((0, "".to_string())) - } + if let Some(ref dirsize) = self.dirsize { + return Ok((dirsize.value.clone()? as u64, "".to_string())) } @@ -483,10 +765,12 @@ impl File { Ok((size, unit)) } - pub fn get_mime(&self) -> Option<String> { - let detective = mime_detective::MimeDetective::new().ok()?; - let mime = detective.detect_filepath(&self.path).ok()?; - Some(mime.type_().as_str().to_string()) + // pub fn get_mime(&self) -> String { + // tree_magic::from_filepath(&self.path) + // } + + pub fn is_text(&self) -> bool { + tree_magic::match_filepath("text/plain", &self.path) } @@ -496,7 +780,7 @@ impl File { pub fn parent_as_file(&self) -> HResult<File> { let pathbuf = self.parent()?; - File::new_from_path(&pathbuf) + File::new_from_path(&pathbuf, None) } pub fn grand_parent(&self) -> Option<PathBuf> { @@ -505,7 +789,7 @@ impl File { pub fn grand_parent_as_file(&self) -> HResult<File> { let pathbuf = self.grand_parent()?; - File::new_from_path(&pathbuf) + File::new_from_path(&pathbuf, None) } pub fn is_dir(&self) -> bool { @@ -559,8 +843,8 @@ impl File { self.tag = Some(new_state); match new_state { - true => TAGS.lock()?.1.push(self.path.clone()), - false => { TAGS.lock()?.1.remove_item(&self.path); }, + true => TAGS.write()?.1.push(self.path.clone()), + false => { TAGS.write()?.1.remove_item(&self.path); }, } self.save_tags()?; Ok(()) @@ -569,7 +853,7 @@ impl File { pub fn save_tags(&self) -> HResult<()> { std::thread::spawn(|| -> HResult<()> { let tagfile_path = crate::paths::tagfile_path()?; - let tags = TAGS.lock()?.clone(); + let tags = TAGS.read()?.clone(); let tags_str = tags.1.iter().map(|p| { let path = p.to_string_lossy().to_string(); format!("{}\n", path) @@ -642,7 +926,7 @@ impl File { if file_user.name() == cur_user { crate::term::color_green() } else { - crate::term::color_yellow() }; + crate::term::color_red() }; Some(format!("{}{}", color, file_user.name().to_string_lossy())) } @@ -655,13 +939,12 @@ impl File { if file_group.name() == cur_group { crate::term::color_green() } else { - crate::term::color_yellow() }; + crate::term::color_red() }; Some(format!("{}{}", color, file_group.name().to_string_lossy())) } pub fn pretty_mtime(&self) -> Option<String> { if self.meta().is_err() { return None } - //let time = chrono::DateTime::from_timestamp(self.mtime, 0); let time: chrono::DateTime<chrono::Local> = chrono::Local.timestamp(self.meta().unwrap().mtime(), 0); Some(time.format("%F %R").to_string()) @@ -719,7 +1002,6 @@ impl PathBufExt for PathBuf { if let Some(name) = self.file_name() { let mut name = name.as_bytes().to_vec(); let mut quote = "\"".as_bytes().to_vec(); - //let mut quote_after = "\"".as_bytes().to_vec(); let mut quoted = vec![]; quoted.append(&mut quote.clone()); quoted.append(&mut name); @@ -761,14 +1043,10 @@ impl OsStrTools for OsStr { let pat = pat.as_bytes().to_vec(); let pat_len = pat.len(); - dbg!(&self); - let split_string = orig_string .windows(pat_len) .enumerate() .fold(Vec::new(), |mut split_pos, (i, chars)| { - dbg!(&chars); - dbg!(&split_pos); if chars == pat.as_slice() { if split_pos.len() == 0 { split_pos.push((0, i)); @@ -781,7 +1059,6 @@ impl OsStrTools for OsStr { split_pos }).iter() .map(|(start, end)| { - //let orig_string = orig_string.clone(); OsString::from_vec(orig_string[*start..*end] .to_vec()).replace(&OsString::from_vec(pat.clone()), &OsString::from("")) @@ -816,8 +1093,6 @@ impl OsStrTools for OsStr { return vec![OsString::from(self)]; } - dbg!(&self); - let pos = pos.unwrap(); let string = self.as_bytes().to_vec(); let from = from.as_bytes().to_vec(); @@ -826,9 +1101,6 @@ impl OsStrTools for OsStr { let lpart = OsString::from_vec(string[0..pos].to_vec()); let rpart = OsString::from_vec(string[pos+fromlen..].to_vec()); - dbg!(&lpart); - dbg!(&rpart); - let mut result = vec![ vec![lpart.trim_last_space()], to, |