use std::fmt::Display;
use crate::{
lexer::{Name, Scope},
parser::{AliasKind, Divider, Module, Node},
Accept, FromLuaCATS, Layout, Settings, Visitor,
};
const TW: usize = 80;
#[derive(Debug)]
pub struct VimDoc(String);
impl Visitor for VimDoc {
type R = String;
type S = Settings;
fn module(&self, n: &Module, s: &Self::S) -> Self::R {
let mut doc = String::new();
let desc = n.desc.as_deref().unwrap_or_default();
doc.push_str(&self.divider(&Divider('='), s));
doc.push_str(desc);
doc.push_str(&format!(
"{:>w$}",
format!("*{}*", n.name),
w = TW - desc.len()
));
doc.push('\n');
doc
}
fn divider(&self, n: &crate::parser::Divider, _: &Self::S) -> Self::R {
let mut doc = String::with_capacity(TW - 1);
for _ in 0..TW - 2 {
doc.push(n.0);
}
doc.push('\n');
doc
}
fn brief(&self, n: &crate::parser::Brief, _: &Self::S) -> Self::R {
let mut doc = n.desc.join("\n");
doc.push('\n');
doc
}
fn tag(&self, n: &crate::parser::Tag, _: &Self::S) -> Self::R {
format!("{:>w$}", format!("*{}*", n.0), w = TW)
}
fn func(&self, n: &crate::parser::Func, s: &Self::S) -> Self::R {
let mut doc = String::new();
let name_with_param = if !n.params.is_empty() {
let args = n
.params
.iter()
.map(|p| format!("{{{}}}", p.name))
.collect::<Vec<String>>()
.join(", ");
format!(
"{}{}({args})",
n.prefix.left.as_deref().unwrap_or_default(),
n.op
)
} else {
format!("{}{}()", n.prefix.left.as_deref().unwrap_or_default(), n.op)
};
doc.push_str(&header(
&name_with_param,
&format!("{}{}", n.prefix.right.as_deref().unwrap_or_default(), n.op),
));
if !n.desc.is_empty() {
doc.push_str(&description(&n.desc.join("\n"), s.indent_width))
}
doc.push('\n');
if !n.params.is_empty() {
doc.push_str(&description("Parameters: ~", s.indent_width));
doc.push_str(&self.params(&n.params, s));
doc.push('\n');
}
if !n.returns.is_empty() {
doc.push_str(&description("Returns: ~", s.indent_width));
doc.push_str(&self.returns(&n.returns, s));
doc.push('\n');
}
if !n.see.refs.is_empty() {
doc.push_str(&self.see(&n.see, s));
}
if let Some(usage) = &n.usage {
doc.push_str(&self.usage(usage, s));
}
doc
}
fn params(&self, n: &[crate::parser::Param], s: &Self::S) -> Self::R {
let mut table = Table::new(s.indent_width);
for param in n {
let (name, ty) = match (s.expand_opt, ¶m.name) {
(true, Name::Opt(n)) => (format!("{{{n}}}"), format!("(nil|{})", param.ty)),
(_, n) => (format!("{{{n}}}"), format!("({})", param.ty)),
};
match s.layout {
Layout::Default => {
table.add_row([name, ty, param.desc.join("\n")]);
}
Layout::Compact(n) => {
table.add_row([
name,
format!(
"{ty} {}",
param.desc.join(&format!("\n{}", " ".repeat(n as usize)))
),
]);
}
Layout::Mini(n) => {
table.add_row([format!(
"{name} {ty} {}",
param.desc.join(&format!("\n{}", " ".repeat(n as usize)))
)]);
}
}
}
table.to_string()
}
fn r#returns(&self, n: &[crate::parser::Return], s: &Self::S) -> Self::R {
let mut table = Table::new(s.indent_width);
for entry in n {
if let Layout::Mini(n) = s.layout {
table.add_row([format!(
"({}) {}",
entry.ty,
if entry.desc.is_empty() {
entry.name.clone().unwrap_or_default()
} else {
entry.desc.join(&format!("\n{}", " ".repeat(n as usize)))
}
)]);
} else {
table.add_row([
format!("({})", entry.ty),
if entry.desc.is_empty() {
entry.name.clone().unwrap_or_default()
} else {
entry.desc.join("\n")
},
]);
}
}
table.to_string()
}
fn class(&self, n: &crate::parser::Class, s: &Self::S) -> Self::R {
let mut doc = String::new();
let name = format!(
"{}{}",
n.name,
n.parent
.as_ref()
.map_or(String::new(), |parent| format!(" : {parent}"))
);
if let Some(prefix) = &n.prefix.right {
doc.push_str(&header(&name, &format!("{prefix}.{}", n.name)));
} else {
doc.push_str(&header(&name, &n.name));
}
if !n.desc.is_empty() {
doc.push_str(&description(&n.desc.join("\n"), s.indent_width));
}
doc.push('\n');
if !n.fields.is_empty() {
doc.push_str(&description("Fields: ~", s.indent_width));
doc.push_str(&self.fields(&n.fields, s));
doc.push('\n');
}
if !n.see.refs.is_empty() {
doc.push_str(&self.see(&n.see, s));
}
doc
}
fn fields(&self, n: &[crate::parser::Field], s: &Self::S) -> Self::R {
let mut table = Table::new(s.indent_width);
for field in n {
let (name, ty) = match (s.expand_opt, &field.name) {
(true, Name::Opt(n)) => (format!("{{{n}}}"), format!("(nil|{})", field.ty)),
(_, n) => (format!("{{{n}}}"), format!("({})", field.ty)),
};
if field.scope == Scope::Public {
match s.layout {
Layout::Default => {
table.add_row([name, ty, field.desc.join("\n")]);
}
Layout::Compact(n) => {
table.add_row([
name,
format!(
"{ty} {}",
field.desc.join(&format!("\n{}", " ".repeat(n as usize)))
),
]);
}
Layout::Mini(n) => {
table.add_row([format!(
"{name} {ty} {}",
field.desc.join(&format!("\n{}", " ".repeat(n as usize)))
)]);
}
};
}
}
table.to_string()
}
fn r#enum(&self, n: &crate::parser::Enum, s: &Self::S) -> Self::R {
let mut doc = String::new();
doc.push_str(&header(&n.name, &n.name));
if !n.desc.is_empty() {
doc.push_str(&description(&n.desc.join("\n"), s.indent_width));
}
doc.push('\n');
if !n.values.is_empty() {
doc.push_str(&description("Values: ~", s.indent_width));
doc.push_str(&self.enum_values(&n.values, s));
doc.push('\n');
}
doc
}
fn enum_values(&self, n: &[crate::parser::EnumValue], s: &Self::S) -> Self::R {
let mut table = Table::new(s.indent_width);
for value in n {
table.add_row([format!("{}", &value.name), value.desc.join("\n")]);
}
table.to_string()
}
fn alias(&self, n: &crate::parser::Alias, s: &Self::S) -> Self::R {
let mut doc = String::new();
if let Some(prefix) = &n.prefix.right {
doc.push_str(&header(&n.name, &format!("{prefix}.{}", n.name)));
} else {
doc.push_str(&header(&n.name, &n.name));
}
if !n.desc.is_empty() {
doc.push_str(&description(&n.desc.join("\n"), s.indent_width));
}
doc.push('\n');
match &n.kind {
AliasKind::Type(ty) => {
doc.push_str(&description("Type: ~", s.indent_width));
doc.push_str(&(" ").repeat(s.indent_width * 2));
doc.push_str(&ty.to_string());
doc.push('\n');
}
AliasKind::Enum(variants) => {
doc.push_str(&description("Variants: ~", s.indent_width));
let mut table = Table::new(s.indent_width);
for (ty, desc) in variants {
table.add_row([&format!("({})", ty), desc.as_deref().unwrap_or_default()]);
}
doc.push_str(&table.to_string());
}
}
doc.push('\n');
doc
}
fn r#type(&self, n: &crate::parser::Type, s: &Self::S) -> Self::R {
let mut doc = String::new();
doc.push_str(&header(
&format!("{}{}", n.prefix.left.as_deref().unwrap_or_default(), n.op),
&format!("{}{}", n.prefix.right.as_deref().unwrap_or_default(), n.op),
));
let (extract, desc) = &n.desc;
if !extract.is_empty() {
doc.push_str(&description(&extract.join("\n"), s.indent_width));
}
doc.push('\n');
doc.push_str(&description("Type: ~", s.indent_width));
let mut table = Table::new(s.indent_width);
table.add_row([&format!("({})", n.ty), desc.as_deref().unwrap_or_default()]);
doc.push_str(&table.to_string());
doc.push('\n');
if !n.see.refs.is_empty() {
doc.push_str(&self.see(&n.see, s));
}
if let Some(usage) = &n.usage {
doc.push_str(&self.usage(usage, s));
}
doc
}
fn see(&self, n: &crate::parser::See, s: &Self::S) -> Self::R {
let mut doc = String::new();
doc.push_str(&description("See: ~", s.indent_width));
for reff in &n.refs {
doc.push_str(&(" ").repeat(s.indent_width * 2));
doc.push_str(&format!("|{reff}|\n"));
}
doc.push('\n');
doc
}
fn usage(&self, n: &crate::parser::Usage, s: &Self::S) -> Self::R {
let mut doc = String::new();
doc.push_str(&description("Usage: ~", s.indent_width));
doc.push('>');
doc.push_str(n.lang.as_deref().unwrap_or("lua"));
doc.push('\n');
doc.push_str(&textwrap::indent(
&n.code,
&(" ").repeat(s.indent_width * 2),
));
doc.push_str("\n<\n\n");
doc
}
fn toc(&self, n: &str, nodes: &[Node], s: &Self::S) -> Self::R {
let mut doc = String::new();
let module = self.module(
&Module {
name: n.to_string(),
desc: Some("Table of Contents".into()),
},
s,
);
doc.push_str(&module);
doc.push('\n');
for nod in nodes {
if let Node::Module(x) = nod {
let desc = x.desc.as_deref().unwrap_or_default();
doc.push_str(&format!(
"{desc} {:ยท>w$}\n",
format!(" |{}|", x.name),
w = (TW - 1) - desc.len()
));
}
}
doc
}
}
impl FromLuaCATS for VimDoc {
type Settings = Settings;
fn from_emmy(t: &impl crate::Nodes, s: &Self::Settings) -> Self {
let mut shelf = Self(String::new());
let nodes = t.nodes();
for node in nodes {
if let Node::Toc(x) = node {
shelf.0.push_str(&shelf.toc(x, nodes, s));
} else {
shelf.0.push_str(&node.accept(&shelf, s));
}
shelf.0.push('\n');
}
shelf
}
}
impl Display for VimDoc {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0.as_str())
}
}
struct Table(comfy_table::Table, usize);
impl Table {
pub fn new(ident: usize) -> Self {
let mut tbl = comfy_table::Table::new();
tbl.load_preset(comfy_table::presets::NOTHING);
Self(tbl, ident)
}
pub fn add_row<T: Into<comfy_table::Row>>(&mut self, row: T) -> &Self {
self.0.add_row(row);
self
}
}
impl std::fmt::Display for Table {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&textwrap::indent(
&self.0.trim_fmt(),
&(" ").repeat((self.1 * 2) - 1),
))?;
f.write_str("\n")
}
}
#[inline]
fn description(desc: &str, indent: usize) -> String {
let mut d = textwrap::indent(desc, &(" ").repeat(indent));
d.push('\n');
d
}
#[inline]
fn header(name: &str, tag: &str) -> String {
let len = name.len();
if len > 40 || tag.len() > 40 {
return format!("{:>w$}\n{}\n", format!("*{}*", tag), name, w = TW);
}
format!("{}{:>w$}\n", name, format!("*{}*", tag), w = TW - len)
}