1use std::cell::Cell;
2use std::io::{self, Write};
3
4use anstyle::{AnsiColor, Effects, Style};
5
6use crate::markdown::{MdStream, MdTree};
7
8const DEFAULT_COLUMN_WIDTH: usize = 140;
9
10#[doc = r" Width of the terminal"]
const WIDTH: ::std::thread::LocalKey<Cell<usize>> =
{
const __RUST_STD_INTERNAL_INIT: Cell<usize> =
{ Cell::new(DEFAULT_COLUMN_WIDTH) };
unsafe {
::std::thread::LocalKey::new(const {
if ::std::mem::needs_drop::<Cell<usize>>() {
|_|
{
#[thread_local]
static __RUST_STD_INTERNAL_VAL:
::std::thread::local_impl::EagerStorage<Cell<usize>> =
::std::thread::local_impl::EagerStorage::new(__RUST_STD_INTERNAL_INIT);
__RUST_STD_INTERNAL_VAL.get()
}
} else {
|_|
{
#[thread_local]
static __RUST_STD_INTERNAL_VAL: Cell<usize> =
__RUST_STD_INTERNAL_INIT;
&__RUST_STD_INTERNAL_VAL
}
}
})
}
};thread_local! {
11 static CURSOR: Cell<usize> = const { Cell::new(0) };
13 static WIDTH: Cell<usize> = const { Cell::new(DEFAULT_COLUMN_WIDTH) };
15
16}
17
18pub(crate) fn entrypoint(
21 stream: &MdStream<'_>,
22 buf: &mut Vec<u8>,
23 formatter: Option<&(dyn Fn(&str, &mut Vec<u8>) -> io::Result<()> + 'static)>,
24) -> io::Result<()> {
25 write_stream(stream, buf, None, 0, formatter)?;
26 buf.write_all(b"\n")
27}
28
29fn write_stream(
32 MdStream(stream): &MdStream<'_>,
33 buf: &mut Vec<u8>,
34
35 default: Option<Style>,
36 indent: usize,
37 formatter: Option<&(dyn Fn(&str, &mut Vec<u8>) -> io::Result<()> + 'static)>,
38) -> io::Result<()> {
39 for tt in stream {
40 write_tt(tt, buf, default, indent, formatter)?;
41 }
42 Ok(())
43}
44
45fn write_tt(
46 tt: &MdTree<'_>,
47 buf: &mut Vec<u8>,
48 default: Option<Style>,
49 indent: usize,
50 formatter: Option<&(dyn Fn(&str, &mut Vec<u8>) -> io::Result<()> + 'static)>,
51) -> io::Result<()> {
52 match tt {
53 MdTree::CodeBlock { txt, lang: _ } => {
54 reset_opt_style(buf, default)?;
55 if let Some(formatter) = formatter {
56 formatter(txt, buf)?;
57 } else {
58 let style = Style::new().effects(Effects::DIMMED);
59 buf.write_fmt(format_args!("{0}{1}{0:#}", style, txt))write!(buf, "{style}{txt}{style:#}")?;
60 }
61 render_opt_style(buf, default)?;
62 }
63 MdTree::CodeInline(txt) => {
64 reset_opt_style(buf, default)?;
65 write_wrapping(buf, txt, indent, None, Some(Style::new().effects(Effects::DIMMED)))?;
66 render_opt_style(buf, default)?;
67 }
68 MdTree::Strong(txt) => {
69 reset_opt_style(buf, default)?;
70 write_wrapping(buf, txt, indent, None, Some(Style::new().effects(Effects::BOLD)))?;
71 render_opt_style(buf, default)?;
72 }
73 MdTree::Emphasis(txt) => {
74 reset_opt_style(buf, default)?;
75 write_wrapping(buf, txt, indent, None, Some(Style::new().effects(Effects::ITALIC)))?;
76 render_opt_style(buf, default)?;
77 }
78 MdTree::Strikethrough(txt) => {
79 reset_opt_style(buf, default)?;
80 write_wrapping(
81 buf,
82 txt,
83 indent,
84 None,
85 Some(Style::new().effects(Effects::STRIKETHROUGH)),
86 )?;
87 render_opt_style(buf, default)?;
88 }
89 MdTree::PlainText(txt) => {
90 write_wrapping(buf, txt, indent, None, None)?;
91 }
92 MdTree::Link { disp, link } => {
93 write_wrapping(buf, disp, indent, Some(link), None)?;
94 }
95 MdTree::ParagraphBreak => {
96 buf.write_all(b"\n\n")?;
97 reset_cursor();
98 }
99 MdTree::LineBreak => {
100 buf.write_all(b"\n")?;
101 reset_cursor();
102 }
103 MdTree::HorizontalRule => {
104 (0..WIDTH.get()).for_each(|_| buf.write_all(b"-").unwrap());
105 reset_cursor();
106 }
107 MdTree::Heading(n, stream) => {
108 let cs = match n {
109 1 => AnsiColor::BrightCyan.on_default().effects(Effects::BOLD | Effects::UNDERLINE),
110 2 => AnsiColor::BrightCyan.on_default().effects(Effects::UNDERLINE),
111 3 => AnsiColor::BrightCyan.on_default().effects(Effects::ITALIC),
112 4.. => AnsiColor::Cyan.on_default().effects(Effects::UNDERLINE | Effects::ITALIC),
113 0 => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
114 };
115 reset_opt_style(buf, default)?;
116 buf.write_fmt(format_args!("{0}", cs))write!(buf, "{cs}")?;
117 write_stream(stream, buf, Some(cs), 0, None)?;
118 buf.write_fmt(format_args!("{0:#}", cs))write!(buf, "{cs:#}")?;
119 render_opt_style(buf, default)?;
120 buf.write_all(b"\n")?;
121 }
122 MdTree::OrderedListItem(n, stream) => {
123 let base = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}. ", n))
})format!("{n}. ");
124 write_wrapping(buf, &::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0:<4}", base))
})format!("{base:<4}"), indent, None, None)?;
125 write_stream(stream, buf, None, indent + 4, None)?;
126 }
127 MdTree::UnorderedListItem(stream) => {
128 let base = "* ";
129 write_wrapping(buf, &::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0:<4}", base))
})format!("{base:<4}"), indent, None, None)?;
130 write_stream(stream, buf, None, indent + 4, None)?;
131 }
132 MdTree::Comment(_) | MdTree::LinkDef { .. } | MdTree::RefLink { .. } => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
134 }
135
136 Ok(())
137}
138
139fn render_opt_style(buf: &mut Vec<u8>, style: Option<Style>) -> io::Result<()> {
140 if let Some(style) = &style {
141 buf.write_fmt(format_args!("{0}", style))write!(buf, "{style}")?;
142 }
143 Ok(())
144}
145
146fn reset_opt_style(buf: &mut Vec<u8>, style: Option<Style>) -> io::Result<()> {
147 if let Some(style) = &style {
148 buf.write_fmt(format_args!("{0:#}", style))write!(buf, "{style:#}")?;
149 }
150 Ok(())
151}
152
153fn reset_cursor() {
155 CURSOR.set(0);
156}
157
158fn write_wrapping(
161 buf: &mut Vec<u8>,
162 text: &str,
163 indent: usize,
164 link_url: Option<&str>,
165 style: Option<Style>,
166) -> io::Result<()> {
167 render_opt_style(buf, style)?;
168
169 let ind_ws = &b" "[..indent];
170 let mut to_write = text;
171 if let Some(url) = link_url {
172 buf.write_fmt(format_args!("\u{1b}]8;;{0}\u{1b}\\", url))write!(buf, "\x1b]8;;{url}\x1b\\")?;
174 }
175 CURSOR.with(|cur| {
176 loop {
177 if cur.get() == 0 {
178 buf.write_all(ind_ws)?;
179 cur.set(indent);
180 }
181 let ch_count = WIDTH.get() - cur.get();
182 let mut iter = to_write.char_indices();
183 let Some((end_idx, _ch)) = iter.nth(ch_count) else {
184 buf.write_all(to_write.as_bytes())?;
186 cur.set(cur.get() + to_write.chars().count());
187 break;
188 };
189
190 if let Some((break_idx, ch)) = to_write[..end_idx]
191 .char_indices()
192 .rev()
193 .find(|(_idx, ch)| ch.is_whitespace() || ['_', '-'].contains(ch))
194 {
195 if ch.is_whitespace() {
197 buf.write_fmt(format_args!("{0}\n", &to_write[..break_idx]))writeln!(buf, "{}", &to_write[..break_idx])?;
198 to_write = to_write[break_idx..].trim_start();
199 } else {
200 buf.write_fmt(format_args!("{0}\n",
&to_write.get(..break_idx + 1).unwrap_or(to_write)))writeln!(buf, "{}", &to_write.get(..break_idx + 1).unwrap_or(to_write))?;
202 to_write = to_write.get(break_idx + 1..).unwrap_or_default().trim_start();
203 }
204 } else {
205 let ws_idx =
207 iter.find(|(_, ch)| ch.is_whitespace()).map_or(to_write.len(), |(idx, _)| idx);
208 buf.write_fmt(format_args!("{0}\n", &to_write[..ws_idx]))writeln!(buf, "{}", &to_write[..ws_idx])?;
209 to_write = to_write.get(ws_idx + 1..).map_or("", str::trim_start);
210 }
211 cur.set(0);
212 }
213 if link_url.is_some() {
214 buf.write_all(b"\x1b]8;;\x1b\\")?;
215 }
216 reset_opt_style(buf, style)?;
217 Ok(())
218 })
219}
220
221#[cfg(test)]
222#[path = "tests/term.rs"]
223mod tests;