Add connect mode, portals, backlinks and renames (stage 8d, v1)
Part 8d, the last of stage 8, and with it everything on DESIGN.md's v1 list. Connect mode (C): the cursor goes to another node, a dashed arrow in the visual color follows it, and Enter links the two; then the cursor goes back to where C was typed (the user's pick). d removes the line between them (a branch is cut, and the child comes loose), > puts the node under the other one, and <Space> a, l, c, s and t style the line as you watch, making it when it isn't there yet. Everything connect mode changes is one undo group, and Esc takes it back with History::cancel, which leaves nothing to redo. From visual mode every selected node connects, and the line pickers go back to connect mode when they close. Portals show their document live, from a cache of what each document file holds (open documents from their buffers, the others from their files, looked at every second): the root's title, its color and how many nodes it has, or ? when the document isn't there. Enter or gf goes through, as a jump, and the pane's breadcrumb starts with the documents it came through. <Space>p types :portal (the user's pick): a path as :e takes it, or the title of a new document. Backlinks are put right whenever a document is opened or saved; the root shows how many, the Note view lists them, gu goes up to them (the user's pick) and :backlinks lists them. File names follow the root's title: when it changed, the next save renames the file (-2 when the name is taken; a folder's default document keeps its name), and the portals pointing at it, its backlinks in the documents it leads to, the recent documents, the capital marks and the jump lists follow. A portal whose file was moved by hand finds its document in that folder by uid. Portals in a register keep full paths, so a pasted portal leads to the same document from any folder. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
bd819409f4
commit
ebe8a5988e
45 files changed
+2329
-148
No files matched your search
+52
-12
@@ -24,14 +24,16 @@ pub mod calls;
|
||||
mod clipboard;
|
||||
pub mod commands;
|
||||
mod configure;
|
||||
pub mod connect;
|
||||
pub mod documents;
|
||||
mod files;
|
||||
mod input;
|
||||
mod jumps;
|
||||
pub mod jumps;
|
||||
mod marks;
|
||||
pub mod mode;
|
||||
pub mod panes;
|
||||
pub mod popup;
|
||||
pub mod portals;
|
||||
pub mod registers;
|
||||
mod render;
|
||||
mod repeat;
|
||||
@@ -43,7 +45,7 @@ mod update;
|
||||
pub mod visual;
|
||||
mod yank;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
|
||||
use ratatui::layout::Rect;
|
||||
@@ -59,6 +61,7 @@ use crate::input::bindings::Context;
|
||||
use crate::input::engine::KeyEngine;
|
||||
use crate::input::keymap::Keymap;
|
||||
use crate::input::mouse::MouseReader;
|
||||
use crate::model::DocUid;
|
||||
use crate::storage::memory::Memory;
|
||||
use crate::ui::theme::Theme;
|
||||
use crate::view::canvas::{Camera, CanvasView};
|
||||
@@ -71,6 +74,7 @@ use documents::{BufferId, Documents};
|
||||
use jumps::Jumps;
|
||||
use mode::Mode;
|
||||
use panes::{PaneId, Panes};
|
||||
use portals::Portals;
|
||||
use registers::Registers;
|
||||
use startup::Opening;
|
||||
|
||||
@@ -121,18 +125,28 @@ struct Shared {
|
||||
mode: Mode,
|
||||
theme: Theme,
|
||||
view: ViewSettings,
|
||||
/// What the documents portals lead to look like.
|
||||
portals: Portals,
|
||||
}
|
||||
|
||||
impl Shared {
|
||||
/// What a view showing document `buffer` looks at.
|
||||
fn ctx(&self, buffer: BufferId) -> ViewContext<'_> {
|
||||
let buffer = &self.docs.get(buffer).buffer;
|
||||
self.ctx_in(buffer, &self.mode)
|
||||
}
|
||||
|
||||
/// What a view showing document `buffer` looks at, in `mode` (the panes
|
||||
/// without the focus are drawn as in normal mode).
|
||||
fn ctx_in<'a>(&'a self, buffer: BufferId, mode: &'a Mode) -> ViewContext<'a> {
|
||||
let open = self.docs.get(buffer);
|
||||
ViewContext {
|
||||
doc: buffer.doc(),
|
||||
cursor: buffer.cursor(),
|
||||
mode: &self.mode,
|
||||
doc: open.buffer.doc(),
|
||||
cursor: open.buffer.cursor(),
|
||||
mode,
|
||||
theme: &self.theme,
|
||||
settings: &self.view,
|
||||
portals: &self.portals,
|
||||
folder: open.file.path.parent().unwrap_or(Path::new(".")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,11 +156,23 @@ impl Shared {
|
||||
}
|
||||
}
|
||||
|
||||
/// A pane: one of the documents, through a view, and where it jumped from.
|
||||
/// A pane: one of the documents, through a view, where it jumped from, and
|
||||
/// the portals it came through.
|
||||
struct Pane {
|
||||
buffer: BufferId,
|
||||
view: Box<dyn View>,
|
||||
jumps: Jumps,
|
||||
/// The documents the pane came to this one through, by portals, the
|
||||
/// first one first: the breadcrumb starts with them.
|
||||
trail: Vec<Crumb>,
|
||||
}
|
||||
|
||||
/// A document a pane came through, by a portal.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Crumb {
|
||||
pub uid: DocUid,
|
||||
/// Its name then.
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
impl Pane {
|
||||
@@ -160,14 +186,24 @@ impl Pane {
|
||||
if let Some(camera) = camera {
|
||||
view.set_camera(camera);
|
||||
}
|
||||
Pane { buffer, view, jumps: Jumps::default() }
|
||||
Pane { buffer, view, jumps: Jumps::default(), trail: Vec::new() }
|
||||
}
|
||||
|
||||
/// The pane shows `buffer` in a new view (a canvas starting at `camera`);
|
||||
/// its jump list stays.
|
||||
/// its jump list and its trail stay.
|
||||
fn show(&mut self, buffer: BufferId, kind: ViewKind, camera: Option<Camera>) {
|
||||
let jumps = std::mem::take(&mut self.jumps);
|
||||
*self = Pane { jumps, ..Pane::new(buffer, kind, camera) };
|
||||
let (jumps, trail) = (std::mem::take(&mut self.jumps), std::mem::take(&mut self.trail));
|
||||
*self = Pane { jumps, trail, ..Pane::new(buffer, kind, camera) };
|
||||
}
|
||||
|
||||
/// The pane got to document `uid` another way than a portal: if it came
|
||||
/// through that one, the trail goes back to it; otherwise it starts
|
||||
/// again.
|
||||
fn arrive(&mut self, uid: DocUid) {
|
||||
match self.trail.iter().position(|crumb| crumb.uid == uid) {
|
||||
Some(at) => self.trail.truncate(at),
|
||||
None => self.trail.clear(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +229,7 @@ impl App {
|
||||
let first = docs.at(0);
|
||||
let mut app = App {
|
||||
panes: Panes::new(Pane::new(first, ViewKind::Canvas, None)),
|
||||
shared: Shared { docs, mode: Mode::Normal, theme, view },
|
||||
shared: Shared { docs, mode: Mode::Normal, theme, view, portals: Portals::default() },
|
||||
command_history: CommandHistory::from_lines(memory.history.clone()),
|
||||
paths,
|
||||
settings,
|
||||
@@ -223,6 +259,10 @@ impl App {
|
||||
for loaded in others {
|
||||
said.extend(app.add_document(loaded).1);
|
||||
}
|
||||
// Once every document named is open, so one that's about to open
|
||||
// isn't written underneath it.
|
||||
app.sync_backlinks(first);
|
||||
app.refresh_portals(true);
|
||||
app.message = Message::joined(said);
|
||||
app
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user