From 6559ededa253e822c946fa6a2123ad55de7f82a8 Mon Sep 17 00:00:00 2001 From: zjp Date: Sat, 23 Mar 2024 11:07:07 +0800 Subject: [PATCH] feat: basic support for declaration jump (close #11) potential improvements: * expand before jump: currently only search for ids in treelines instead of DModule tree, because the expansion and jump interaction is vague. For simplicity, expand all, search and jump. * use defining ids in treelines: replace reexport ids by inner id in DModule * width & wrapping: this needs to add a new_with_width API on declarationLines --- src/bin/page/content.rs | 47 ++++++++++++++++++++++++++++++++++++- src/bin/page/mod.rs | 4 ++++ src/bin/page/page_scroll.rs | 32 +++++++++++++++++++++++++ src/bin/page/panel.rs | 3 +++ src/tree/id.rs | 21 +++++++++++++++++ 5 files changed, 106 insertions(+), 1 deletion(-) diff --git a/src/bin/page/content.rs b/src/bin/page/content.rs index 591eed4..9570ae5 100644 --- a/src/bin/page/content.rs +++ b/src/bin/page/content.rs @@ -10,10 +10,12 @@ use ratatui::{ prelude::*, widgets::{Block, BorderType, Borders}, }; +use std::ops::Range; use term_rustdoc::{ - tree::{CrateDoc, IDMap}, + tree::{CrateDoc, IDMap, ID}, type_name::{DeclarationLine, DeclarationLines}, }; +use unicode_width::UnicodeWidthStr; #[derive(Default)] pub(super) struct ContentInner { @@ -76,6 +78,10 @@ impl ContentInner { pub fn reset_doc(&mut self) { self.md.lines.reset_doc(); } + + pub fn jumpable_id(&self, x: u16, y: u16) -> Option { + self.decl.display.jumpable_id(x, y) + } } struct Declaration { @@ -101,6 +107,14 @@ impl Default for Declaration { #[derive(Default)] struct DeclarationInner { inner: Scroll, + jumpable_ids: Vec, +} + +#[derive(Debug)] +struct JumpableId { + y: u16, + x: Range, + id: ID, } impl DeclarationInner { @@ -127,8 +141,38 @@ impl DeclarationInner { } fn update_decl(&mut self, lines: DeclarationLines) { + // self.jumpable_ids = lines.it; + let mut jumpable_ids = Vec::new(); + let mut col = 0; + for (y, line) in lines.iter().enumerate() { + for tt in &**line { + let width = tt.text.width(); + if let Some(id) = &tt.id { + let end = col + width; + jumpable_ids.push(JumpableId { + y: y as u16, + x: col as u16..end as u16, + id: id.clone(), + }); + } + col += width; + } + col = 0; + } + info!(?jumpable_ids); + self.jumpable_ids = jumpable_ids; self.inner.lines = lines; } + + fn jumpable_id(&self, x: u16, y: u16) -> Option { + let area = self.inner.area; + let x = x.checked_sub(area.x)?; + let y = y.checked_sub(area.y)?; + info!(y, x); + self.jumpable_ids + .iter() + .find_map(|jump| (jump.y == y && jump.x.contains(&x)).then(|| jump.id.clone())) + } } /// No need to query state for previous line. @@ -145,6 +189,7 @@ impl Declaration { let lines = DeclarationLines::new(id, map); if lines.is_empty() { self.display.scroll_text().lines = Default::default(); + self.display.jumpable_ids = Vec::new(); } else { self.display.update_decl(lines); // self.display.rust_code(&code, width); diff --git a/src/bin/page/mod.rs b/src/bin/page/mod.rs index e8409b3..f8d2193 100644 --- a/src/bin/page/mod.rs +++ b/src/bin/page/mod.rs @@ -162,4 +162,8 @@ impl Content { fn update_doc(&mut self, id: &str) -> Option { self.inner.update_doc(id, self.border.inner()) } + + fn jumpable_id(&self, x: u16, y: u16) -> Option { + self.inner.jumpable_id(x, y) + } } diff --git a/src/bin/page/page_scroll.rs b/src/bin/page/page_scroll.rs index b3f6ce1..cb7f396 100644 --- a/src/bin/page/page_scroll.rs +++ b/src/bin/page/page_scroll.rs @@ -172,4 +172,36 @@ impl Page { self.content().lines.toggle_sytect(); self.update_content(); } + + pub fn jump_to_id(&mut self, id: &str) { + let outline = self.outline.display_ref(); + let map = outline.lines.doc_ref(); + if let Some(current) = outline.get_line_of_current_cursor() { + if let Some(current_id) = ¤t.id { + if map.is_same_id(current_id, id) { + info!(?current_id, ?id, path = %map.path(id), "no need to jump to"); + return; + } + } + } + let iter = &mut outline.lines.iter(); + if let Some(pos) = iter.position(|l| { + l.id.as_deref() + // .map(|src| src == id) + .map(|src| map.is_same_id(src, id)) + .unwrap_or(false) + }) { + let start = pos.saturating_sub(6); + let y = (pos - start) as u16; + self.outline().start = start; + self.outline().set_cursor(y); + self.update_content(); + info!( + "succeed to jump to {:?}", + self.outline().lines.doc_ref().path(id) + ); + } else { + error!(?id, path = %map.path(id), "unable to jump to"); + } + } } diff --git a/src/bin/page/panel.rs b/src/bin/page/panel.rs index c44b08e..fe443a9 100644 --- a/src/bin/page/panel.rs +++ b/src/bin/page/panel.rs @@ -33,6 +33,9 @@ impl super::Page { self.update_content(); set!(outline) } else if self.content.border.area().contains(position) { + if let Some(id) = self.content.jumpable_id(x, y) { + self.jump_to_id(&id); + } set!(content) } else if self.navi.contains(position) { if self.heading_jump(y) { diff --git a/src/tree/id.rs b/src/tree/id.rs index 07301b0..df8889d 100644 --- a/src/tree/id.rs +++ b/src/tree/id.rs @@ -261,6 +261,27 @@ impl IDMap { id.into() } } + + /// Since there will be reexported item id, we have to check the real defining id. + pub fn is_same_id(&self, src: &str, target: &str) -> bool { + if src == target { + info!("found exactly same id {src}"); + return true; + } + self.get_item(src) + .map(|item| match &item.inner { + // FIXME: check id for primitive types + ItemEnum::Import(x) => { + let res = x.id.as_ref().map(|id| id.0 == target).unwrap_or(false); + if res { + info!(src, target, "found same id through reexport item"); + } + res + } + _ => false, + }) + .unwrap_or(false) + } } /// Deduce the name from its item type.