terrazzo-terminal 0.2.7

A simple web-based terminal emulator built on Terrazzo.
#![cfg(feature = "client")]

use std::path::Path;
use std::sync::Arc;

use terrazzo::prelude::diagnostics;

use self::diagnostics::debug_span;
use self::diagnostics::info;
use self::diagnostics::warn;
use super::SideViewList;
use super::SideViewNode;
use super::SvnItem;
use super::SvnProperties;
use super::SvnStatus;
use crate::text_editor::file_path::FilePath;
use crate::text_editor::fsio::FileMetadata;
use crate::text_editor::notify::manager::SideViewNotify;
use crate::utils::more_path::MorePathRef as _;

mod add;
mod live;
mod manager;
mod remove;

#[cfg(test)]
mod tests;

fn add_node(
    manager: &impl SideViewNotify,
    node: Option<&SideViewNode>,
    path: &FilePath<Arc<Path>>,
    make: impl FnOnce(Option<&SideViewNode>) -> Option<SideViewNode>,
) -> Option<SideViewNode> {
    let _span = debug_span!("Add node", ?path).entered();
    let relative_path = make_relative_path_iterator(&path.file);
    self::add::add_node_rec(manager, path, make, relative_path, node)
}

fn remove_node(node: Option<&SideViewNode>, path: &Path) -> Option<SideViewNode> {
    let _span = debug_span!("Remove node", ?path).entered();
    let relative_path = make_relative_path_iterator(path);
    match self::remove::remove_node_rec(Path::new(""), relative_path, node) {
        Ok(node) => node,
        Err(error) => {
            warn!("Failed to remove node: {error}");
            None
        }
    }
}

pub fn show_folder_content(
    manager: &impl SideViewNotify,
    node: Option<&SideViewNode>,
    path: &FilePath<Arc<Path>>,
    folder_content: &[FileMetadata],
) -> Option<SideViewNode> {
    info!(?path, len = folder_content.len(), "Showing folder content");
    add_node(manager, node, path, |old| {
        info!("Setting folder content: {}", old.is_some());
        let (properties, mut new_folder, notify) = if let Some(SideViewNode {
            properties,
            item: SvnItem::Folder { folder, notify },
        }) = old
        {
            (properties, (**folder).clone(), notify)
        } else {
            warn!("Old folder not found");
            return None;
        };
        for metadata in folder_content {
            let name = Path::new(metadata.name.as_ref());
            if new_folder.contains_key(name) {
                continue;
            }
            let item = if metadata.is_dir {
                SvnItem::Folder {
                    folder: Arc::default(),
                    notify: manager.watch_side_view_folder(&FilePath {
                        base: path.base.clone(),
                        file: path.file.join(name).into(),
                    }),
                }
            } else {
                SvnItem::File {
                    metadata: Arc::new(metadata.clone()),
                }
            };
            let child = SideViewNode {
                properties: SvnProperties {
                    status: SvnStatus::Show,
                },
                item,
            };
            new_folder.insert(name.into(), child.into());
        }
        Some(SideViewNode {
            properties: properties.clone(),
            item: SvnItem::Folder {
                folder: new_folder.into(),
                notify: notify.clone(),
            },
        })
    })
}

pub fn filter_active_folder_content(
    manager: &impl SideViewNotify,
    node: Option<&SideViewNode>,
    path: &FilePath<Arc<Path>>,
) -> Option<SideViewNode> {
    info!(?path, "Filter folder content");
    add_node(manager, node, path, |old| {
        info!("Setting folder content: {}", old.is_some());
        let (properties, folder, notify) = if let Some(SideViewNode {
            properties,
            item: SvnItem::Folder { folder, notify },
        }) = old
        {
            (properties, (**folder).clone(), notify)
        } else {
            warn!("Old folder not found");
            return None;
        };

        let mut new_folder = SideViewList::default();
        for (name, child) in folder.iter() {
            if child.properties.status == SvnStatus::Show {
                continue;
            }
            let child = match &child.item {
                SvnItem::Folder {
                    folder: sub_folder,
                    notify,
                } => Arc::new(SideViewNode {
                    properties: child.properties.clone(),
                    item: SvnItem::Folder {
                        folder: remove_displayed(sub_folder.clone()),
                        notify: notify.clone(),
                    },
                }),
                SvnItem::File { .. } => child.clone(),
            };
            new_folder.insert(name.clone(), child);
        }
        Some(SideViewNode {
            properties: properties.clone(),
            item: SvnItem::Folder {
                folder: new_folder.into(),
                notify: notify.clone(),
            },
        })
    })
}

fn remove_displayed(folder: Arc<SideViewList>) -> Arc<SideViewList> {
    let mut new_folder = SideViewList::default();
    for (name, child) in folder.iter() {
        if child.properties.status == SvnStatus::Show {
            continue;
        }
        let child = match &child.item {
            SvnItem::Folder { folder, notify } => Arc::new(SideViewNode {
                properties: child.properties.clone(),
                item: SvnItem::Folder {
                    folder: remove_displayed(folder.clone()),
                    notify: notify.clone(),
                },
            }),
            SvnItem::File { .. } => child.clone(),
        };
        new_folder.insert(name.clone(), child);
    }
    Arc::new(new_folder)
}

fn make_relative_path_iterator(path: &Path) -> impl Iterator<Item = &Path> {
    path.make_relative().iter().map(|c| c.as_ref())
}