Add links to module items

This patch adds links to the items in the module member list.  This only
applies for the tui viewer, as the links wouldn’t provide any additional
information for the plain and rich viewers.
This commit is contained in:
Robin Krahl 2020-10-06 20:35:25 +02:00
parent 8e8f7728e2
commit 4c5d5808aa
No known key found for this signature in database
GPG Key ID: 8E9B0870524F69D8
5 changed files with 116 additions and 16 deletions

View File

@ -56,7 +56,12 @@ impl utils::ManRenderer for PlainTextRenderer {
Ok(())
}
fn print_heading(&mut self, indent: u8, s: &str) -> io::Result<()> {
fn print_heading(
&mut self,
indent: u8,
s: &str,
_link: Option<utils::DocLink>,
) -> io::Result<()> {
writeln!(io::stdout(), "{}{}", " ".repeat(indent.into()), s)
}

View File

@ -76,7 +76,12 @@ impl utils::ManRenderer for RichTextRenderer {
Ok(())
}
fn print_heading(&mut self, indent: u8, s: &str) -> io::Result<()> {
fn print_heading(
&mut self,
indent: u8,
s: &str,
_link: Option<utils::DocLink>,
) -> io::Result<()> {
write!(io::stdout(), "{}", " ".repeat(usize::from(indent)))?;
render(text_style::StyledStr::plain(s).bold())?;
writeln!(io::stdout())

View File

@ -8,14 +8,14 @@ use std::convert;
use anyhow::Context as _;
use cursive::view::{Resizable as _, Scrollable as _};
use cursive::views::{Dialog, LinearLayout, PaddedView, Panel, TextView};
use cursive::{event, theme};
use cursive::{event, theme, utils::markup};
use crate::args;
use crate::doc;
use crate::source;
use crate::viewer::{self, utils, utils::ManRenderer as _};
use views::{CodeView, HtmlView};
use views::{CodeView, HtmlView, LinkView};
#[derive(Clone, Debug)]
pub struct TuiViewer {}
@ -133,9 +133,25 @@ impl<'s> utils::ManRenderer for TuiManRenderer<'s> {
Ok(())
}
fn print_heading(&mut self, indent: u8, text: &str) -> Result<(), Self::Error> {
let heading = TextView::new(text).effect(theme::Effect::Bold);
self.layout.add_child(indent_view(indent, heading));
fn print_heading(
&mut self,
indent: u8,
text: &str,
link: Option<utils::DocLink>,
) -> Result<(), Self::Error> {
let text = markup::StyledString::styled(text, theme::Effect::Bold);
if let Some(link) = link {
// TODO: bold
let heading = LinkView::new(text, move |s| {
if let Err(err) = open_link(s, link.clone().into()) {
report_error(s, err);
}
});
self.layout.add_child(indent_view(indent, heading));
} else {
let heading = TextView::new(text);
self.layout.add_child(indent_view(indent, heading));
}
Ok(())
}
@ -266,6 +282,12 @@ enum ResolvedLink {
External(String),
}
impl From<utils::DocLink> for ResolvedLink {
fn from(link: utils::DocLink) -> ResolvedLink {
ResolvedLink::Doc(link.ty, link.name)
}
}
fn resolve_link(
doc_name: &doc::Fqn,
doc_ty: doc::ItemType,

View File

@ -346,6 +346,54 @@ fn get_rich_style(annotation: &text_renderer::RichAnnotation) -> Option<theme::S
}
}
pub struct LinkView {
text: markup::StyledString,
cb: event::Callback,
is_focused: bool,
}
impl LinkView {
pub fn new<F>(text: impl Into<markup::StyledString>, cb: F) -> LinkView
where
F: Fn(&mut cursive::Cursive) + 'static,
{
LinkView {
text: text.into(),
cb: event::Callback::from_fn(cb),
is_focused: false,
}
}
}
impl cursive::View for LinkView {
fn draw(&self, printer: &cursive::Printer) {
let mut style = theme::Style::from(theme::Effect::Underline);
if self.is_focused && printer.focused {
style = style.combine(theme::PaletteColor::Highlight);
};
printer.with_style(style, |printer| {
printer.print_styled((0, 0), (&self.text).into())
});
}
fn required_size(&mut self, _constraint: cursive::XY<usize>) -> cursive::XY<usize> {
(self.text.width(), 1).into()
}
fn take_focus(&mut self, _direction: cursive::direction::Direction) -> bool {
self.is_focused = true;
true
}
fn on_event(&mut self, event: event::Event) -> event::EventResult {
if event == event::Event::Key(event::Key::Enter) {
event::EventResult::Consumed(Some(self.cb.clone()))
} else {
event::EventResult::Ignored
}
}
}
pub struct CodeView {
lines: Vec<markup::StyledString>,
width: usize,

View File

@ -183,12 +183,23 @@ fn is_pre(ts: &RichString) -> bool {
})
}
#[derive(Clone, Debug, Default)]
pub struct DocLink {
pub name: doc::Fqn,
pub ty: Option<doc::ItemType>,
}
/// A trait for viewer implementations that display the documentation in a man-like style.
pub trait ManRenderer {
type Error: std::error::Error + Sized + Send;
fn print_title(&mut self, left: &str, center: &str, right: &str) -> Result<(), Self::Error>;
fn print_heading(&mut self, indent: u8, text: &str) -> Result<(), Self::Error>;
fn print_heading(
&mut self,
indent: u8,
text: &str,
link: Option<DocLink>,
) -> Result<(), Self::Error>;
fn print_code(&mut self, indent: u8, code: &doc::Code) -> Result<(), Self::Error>;
fn print_text(&mut self, indent: u8, text: &doc::Text) -> Result<(), Self::Error>;
fn println(&mut self) -> Result<(), Self::Error>;
@ -197,28 +208,36 @@ pub trait ManRenderer {
print_title(self, doc)?;
if let Some(text) = &doc.definition {
print_heading(self, 1, "Synopsis")?;
print_heading(self, 1, "Synopsis", None)?;
self.print_code(6, text)?;
self.println()?;
}
if let Some(text) = &doc.description {
print_heading(self, 1, "Description")?;
print_heading(self, 1, "Description", None)?;
self.print_text(6, text)?;
self.println()?;
}
for (ty, groups) in &doc.groups {
print_heading(self, 1, ty.group_name())?;
print_heading(self, 1, ty.group_name(), None)?;
for group in groups {
if let Some(title) = &group.title {
print_heading(self, 2, title)?;
print_heading(self, 2, title, None)?;
}
for member in &group.members {
let link = if doc::ItemType::Module == doc.ty {
Some(DocLink {
name: member.name.clone(),
ty: Some(*ty),
})
} else {
None
};
// TODO: use something link strip_prefix instead of last()
print_heading(self, 3, member.name.last())?;
print_heading(self, 3, member.name.last(), link)?;
if let Some(definition) = &member.definition {
self.print_code(12, definition)?;
}
@ -244,12 +263,12 @@ pub trait ManRenderer {
examples: &[doc::Example],
) -> Result<(), Self::Error> {
print_title(self, doc)?;
print_heading(self, 1, "Examples")?;
print_heading(self, 1, "Examples", None)?;
let n = examples.len();
for (i, example) in examples.iter().enumerate() {
if n > 1 {
print_heading(self, 2, &format!("Example {} of {}", i + 1, n))?;
print_heading(self, 2, &format!("Example {} of {}", i + 1, n), None)?;
}
if let Some(description) = &example.description {
self.print_text(6, description)?;
@ -272,6 +291,7 @@ fn print_heading<M: ManRenderer + ?Sized>(
viewer: &mut M,
level: u8,
text: &str,
link: Option<DocLink>,
) -> Result<(), M::Error> {
let text = match level {
1 => std::borrow::Cow::from(text.to_uppercase()),
@ -282,7 +302,7 @@ fn print_heading<M: ManRenderer + ?Sized>(
2 => 3,
_ => 6,
};
viewer.print_heading(indent, text.as_ref())
viewer.print_heading(indent, text.as_ref(), link)
}
/// Link handling mode for the [`RichDecorator`].