//! Connect mode (`C`): the cursor goes to another node, a line follows it //! there, and Enter links the two (DESIGN.md, "Keys"). On the way, `d` //! removes the line already there (a branch between a parent and its child //! is cut, and the child comes loose), `>` puts the node under the other //! one, and the `` keys style the line, branch or link, as you watch. //! Everything connect mode changes is one undo step, which Esc takes back; //! when it's done, the cursor goes back to where it started (the user's //! pick in part 8d). //! //! From visual mode, every selected node connects to the one you go to. use ratatui::style::Color; use super::action::{self, Action, AppAction, DocAction, Message}; use super::mode::Connect; use super::popup::{LinePick, PickKind}; use crate::config::theme_file::{ARROWS, STROKES}; use crate::input::command::Command; use crate::input::engine::Typed; use crate::model::{EditOp, Label, LineLook, LineRef, Link, NodeId, Spot}; use crate::ui::theme::LineStyle; use crate::view::ViewContext; /// Connect mode's answer to a command, or `None` for the ones that move the /// cursor as usual. pub fn command(typed: &Typed, connect: &Connect, ctx: &ViewContext<'_>) -> Option> { let actions = match typed.command { Command::Confirm => link(connect, ctx), Command::RemoveLink => remove(connect, ctx), Command::AttachAsChild => attach(connect, ctx), Command::CycleArrows => cycle_arrows(connect, ctx), Command::CycleLine => cycle_line(connect, ctx), Command::PickColor => picker(connect, ctx, LinePick::Color), Command::PickStyle => picker(connect, ctx, LinePick::Style), Command::EditLabel => picker(connect, ctx, LinePick::Label), Command::Escape => cancel(connect), // The other end is in this document. Command::FollowPortal | Command::JumpBack | Command::JumpForward | Command::GoUp => { say(Message::info("connect mode stays in this document: Enter or Esc first")) } Command::SetMark(name) | Command::JumpToMark(name) if name.is_ascii_uppercase() => { say(Message::info("connect mode stays in this document: Enter or Esc first")) } Command::Search => { say(Message::info("n and N find the last search again here: / starts one in normal mode")) } _ => return None, }; Some(actions) } /// The nodes being connected to `target`: the sources, but not the target /// itself. pub fn others(connect: &Connect, target: NodeId) -> Vec { connect.sources.iter().copied().filter(|&id| id != target).collect() } /// What's being connected to what: `Raised beds → Watering`. pub fn ends(connect: &Connect, ctx: &ViewContext<'_>) -> String { let target = ctx.cursor; match others(connect, target).as_slice() { [] => format!("{} → ?", what(ctx, &connect.sources)), sources => format!("{} → {}", what(ctx, sources), title(ctx, target)), } } /// What the status bar says in connect mode, in place of the breadcrumb: /// what's being connected to what, and what joins them already. pub fn status(connect: &Connect, ctx: &ViewContext<'_>) -> String { let target = ctx.cursor; let sources = others(connect, target); let mut text = ends(connect, ctx); if sources.is_empty() { text.push_str(" · go to the node to connect"); } if let [source] = sources.as_slice() { match ctx.doc.line_between(*source, target) { Some(LineRef::Link(_)) => text.push_str(" · linked"), Some(LineRef::Branch(child)) if child == target => text.push_str(" · its child"), Some(LineRef::Branch(_)) => text.push_str(" · its parent"), None => {} } } text } /// The lines from each node being connected to the cursor's, changed by /// `change`: a line that isn't there yet is made, as a link with that look. pub fn restyle(connect: &Connect, ctx: &ViewContext<'_>, change: impl Fn(&mut LineLook)) -> Vec { let target = ctx.cursor; others(connect, target) .into_iter() .filter_map(|source| { let op = match ctx.doc.line_between(source, target) { Some(line) => { let mut look = ctx.doc.line_look(line).cloned().unwrap_or_default(); change(&mut look); EditOp::SetLine { line, look } } None => { let mut link = Link::new(source, target)?; change(&mut link.look); EditOp::Link(link) } }; Some(Action::edit_in(op, connect.group)) }) .collect() } /// The labels of the links from each node being connected to the cursor's /// (made if they aren't there yet). A branch has no label: it's left alone. pub fn relabel(connect: &Connect, ctx: &ViewContext<'_>, label: &Option