Merge pull request 'print markdown text nicely' (#75) from pretty-text into main

Reviewed-on: https://codeberg.org/Cyborus/forgejo-cli/pulls/75
This commit is contained in:
Cyborus 2024-06-08 20:43:36 +00:00
commit d10b5172be
7 changed files with 879 additions and 24 deletions

406
Cargo.lock generated
View file

@ -17,6 +17,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.13"
@ -126,6 +135,30 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bit-set"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bitflags"
version = "1.3.2"
@ -196,6 +229,7 @@ dependencies = [
"anstyle",
"clap_lex",
"strsim",
"terminal_size",
]
[[package]]
@ -222,6 +256,26 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "comrak"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a972c8ec1be8065f7b597b5f7f5b3be535db780280644aebdcd1966decf58dc"
dependencies = [
"clap",
"derive_builder",
"entities",
"memchr",
"once_cell",
"regex",
"shell-words",
"slug",
"syntect",
"typed-arena",
"unicode_categories",
"xdg",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
@ -247,6 +301,40 @@ dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossterm"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
dependencies = [
"bitflags 2.5.0",
"crossterm_winapi",
"libc",
"mio",
"parking_lot",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
dependencies = [
"winapi",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
@ -257,6 +345,41 @@ dependencies = [
"typenum",
]
[[package]]
name = "darling"
version = "0.20.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.20.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "deranged"
version = "0.3.11"
@ -267,6 +390,43 @@ dependencies = [
"serde",
]
[[package]]
name = "derive_builder"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7"
dependencies = [
"derive_builder_macro",
]
[[package]]
name = "derive_builder_core"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "derive_builder_macro"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b"
dependencies = [
"derive_builder_core",
"syn",
]
[[package]]
name = "deunicode"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00"
[[package]]
name = "digest"
version = "0.10.7"
@ -316,6 +476,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "entities"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
[[package]]
name = "equivalent"
version = "1.0.1"
@ -342,6 +508,16 @@ dependencies = [
"once_cell",
]
[[package]]
name = "fancy-regex"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2"
dependencies = [
"bit-set",
"regex",
]
[[package]]
name = "fastrand"
version = "2.1.0"
@ -355,6 +531,8 @@ dependencies = [
"auth-git2",
"base64ct",
"clap",
"comrak",
"crossterm",
"directories",
"eyre",
"forgejo-api",
@ -374,6 +552,16 @@ dependencies = [
"uuid",
]
[[package]]
name = "flate2"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -743,6 +931,12 @@ dependencies = [
"tokio",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.5.0"
@ -880,6 +1074,18 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "line-wrap"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd1bc4d24ad230d21fb898d1116b1801d7adfc449d42026475862ab48b11e70e"
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.4.13"
@ -940,6 +1146,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.48.0",
]
@ -993,6 +1200,28 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "onig"
version = "6.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f"
dependencies = [
"bitflags 1.3.2",
"libc",
"once_cell",
"onig_sys",
]
[[package]]
name = "onig_sys"
version = "69.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7"
dependencies = [
"cc",
"pkg-config",
]
[[package]]
name = "open"
version = "5.1.2"
@ -1107,6 +1336,20 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]]
name = "plist"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9d34169e64b3c7a80c8621a48adaf44e0cf62c78a9b25dd9dd35f1881a17cf9"
dependencies = [
"base64",
"indexmap",
"line-wrap",
"quick-xml",
"serde",
"time",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
@ -1128,6 +1371,15 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "quick-xml"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
version = "1.0.36"
@ -1187,6 +1439,35 @@ dependencies = [
"thiserror",
]
[[package]]
name = "regex"
version = "1.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
[[package]]
name = "reqwest"
version = "0.11.27"
@ -1262,6 +1543,15 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.23"
@ -1367,6 +1657,33 @@ dependencies = [
"tokio",
]
[[package]]
name = "shell-words"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
[[package]]
name = "signal-hook"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
@ -1385,6 +1702,16 @@ dependencies = [
"autocfg",
]
[[package]]
name = "slug"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bd94acec9c8da640005f8e135a39fc0372e74535e6b368b7a04b875f784c8c4"
dependencies = [
"deunicode",
"wasm-bindgen",
]
[[package]]
name = "smallvec"
version = "1.13.2"
@ -1430,6 +1757,29 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "syntect"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1"
dependencies = [
"bincode",
"bitflags 1.3.2",
"fancy-regex",
"flate2",
"fnv",
"once_cell",
"onig",
"plist",
"regex-syntax",
"serde",
"serde_derive",
"serde_json",
"thiserror",
"walkdir",
"yaml-rust",
]
[[package]]
name = "system-configuration"
version = "0.5.1"
@ -1473,6 +1823,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "terminal_size"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
dependencies = [
"rustix",
"windows-sys 0.48.0",
]
[[package]]
name = "thiserror"
version = "1.0.59"
@ -1624,6 +1984,12 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "typed-arena"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
[[package]]
name = "typenum"
version = "1.17.0"
@ -1660,6 +2026,12 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode_categories"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "url"
version = "2.5.0"
@ -1699,6 +2071,16 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "want"
version = "0.3.1"
@ -1806,6 +2188,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
@ -1961,6 +2352,21 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "xdg"
version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546"
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]
[[package]]
name = "zeroize"
version = "1.7.0"

View file

@ -9,6 +9,8 @@ edition = "2021"
auth-git2 = "0.5.3"
base64ct = { version = "1.6.0", features = ["std"] }
clap = { version = "4.3.11", features = ["derive"] }
comrak = "0.24.1"
crossterm = "0.27.0"
directories = "5.0.1"
eyre = "0.6.8"
forgejo-api = "0.3.0"

View file

@ -253,7 +253,7 @@ pub async fn view_issue(repo: &RepoName, api: &Forgejo, id: u64) -> eyre::Result
println!("By {}", username);
if let Some(body) = &issue.body {
println!();
println!("{}", body);
println!("{}", crate::markdown(body));
}
Ok(())
}
@ -355,7 +355,7 @@ fn print_comment(comment: &Comment) -> eyre::Result<()> {
.as_ref()
.ok_or_else(|| eyre::eyre!("user does not have login"))?;
println!("{} said:", username);
println!("{}", body);
println!("{}", crate::markdown(&body));
let assets = comment
.assets
.as_ref()

View file

@ -167,11 +167,12 @@ enum Style {
}
struct SpecialRender {
colors: bool,
fancy: bool,
dash: char,
bullet: char,
body_prefix: char,
horiz_rule: char,
red: &'static str,
bright_red: &'static str,
@ -189,11 +190,21 @@ struct SpecialRender {
dark_grey: &'static str,
light_grey: &'static str,
white: &'static str,
no_fg: &'static str,
reset: &'static str,
dark_grey_bg: &'static str,
no_bg: &'static str,
hide_cursor: &'static str,
show_cursor: &'static str,
clear_line: &'static str,
italic: &'static str,
bold: &'static str,
strike: &'static str,
no_italic_bold: &'static str,
no_strike: &'static str,
}
impl SpecialRender {
@ -208,11 +219,12 @@ impl SpecialRender {
fn fancy() -> Self {
Self {
colors: true,
fancy: true,
dash: '',
bullet: '',
body_prefix: '',
horiz_rule: '',
red: "\x1b[31m",
bright_red: "\x1b[91m",
@ -230,21 +242,32 @@ impl SpecialRender {
dark_grey: "\x1b[90m",
light_grey: "\x1b[37m",
white: "\x1b[97m",
no_fg: "\x1b[39m",
reset: "\x1b[0m",
dark_grey_bg: "\x1b[100m",
no_bg: "\x1b[49",
hide_cursor: "\x1b[?25l",
show_cursor: "\x1b[?25h",
clear_line: "\x1b[2K",
italic: "\x1b[3m",
bold: "\x1b[1m",
strike: "\x1b[9m",
no_italic_bold: "\x1b[23m",
no_strike: "\x1b[29m",
}
}
fn minimal() -> Self {
Self {
colors: false,
fancy: false,
dash: '-',
bullet: '-',
body_prefix: '>',
horiz_rule: '-',
red: "",
bright_red: "",
@ -262,11 +285,445 @@ impl SpecialRender {
dark_grey: "",
light_grey: "",
white: "",
no_fg: "",
reset: "",
dark_grey_bg: "",
no_bg: "",
hide_cursor: "",
show_cursor: "",
clear_line: "",
italic: "",
bold: "",
strike: "~~",
no_italic_bold: "",
no_strike: "~~",
}
}
}
fn markdown(text: &str) -> String {
let SpecialRender {
fancy,
bullet,
horiz_rule,
bright_blue,
dark_grey_bg,
body_prefix,
..
} = *special_render();
if !fancy {
let mut out = String::new();
for line in text.lines() {
use std::fmt::Write;
let _ = writeln!(&mut out, "{body_prefix} {line}");
}
return out;
}
let arena = comrak::Arena::new();
let mut options = comrak::Options::default();
options.extension.strikethrough = true;
let root = comrak::parse_document(&arena, text, &options);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Side {
Start,
End,
}
let mut explore_stack = Vec::new();
let mut render_queue = Vec::new();
explore_stack.extend(root.reverse_children().map(|x| (x, Side::Start)));
while let Some((node, side)) = explore_stack.pop() {
if side == Side::Start {
explore_stack.push((node, Side::End));
explore_stack.extend(node.reverse_children().map(|x| (x, Side::Start)));
}
render_queue.push((node, side));
}
let mut list_numbers = Vec::new();
let (terminal_width, _) = crossterm::terminal::size().unwrap_or((80, 24));
let max_line_len = (terminal_width as usize - 2).min(80);
let mut links = Vec::new();
let mut ansi_printer = AnsiPrinter::new(max_line_len);
ansi_printer.pause_style();
ansi_printer.prefix();
ansi_printer.resume_style();
for (item, side) in render_queue {
use comrak::nodes::NodeValue;
use Side::*;
match (&item.data.borrow().value, side) {
(NodeValue::Paragraph, Start) => (),
(NodeValue::Paragraph, End) => {
ansi_printer.newline();
ansi_printer.newline();
}
(NodeValue::Text(s), Start) => ansi_printer.text(s),
(NodeValue::Link(_), Start) => {
ansi_printer.start_fg(bright_blue);
}
(NodeValue::Link(link), End) => {
use std::fmt::Write;
ansi_printer.stop_fg();
links.push(link.url.clone());
let _ = write!(&mut ansi_printer, "({})", links.len());
}
(NodeValue::Image(_), Start) => {
ansi_printer.start_fg(bright_blue);
}
(NodeValue::Image(link), End) => {
use std::fmt::Write;
ansi_printer.stop_fg();
links.push(link.url.clone());
let _ = write!(&mut ansi_printer, "({})", links.len());
}
(NodeValue::Code(code), Start) => {
ansi_printer.pause_style();
ansi_printer.start_bg(dark_grey_bg);
ansi_printer.text(&code.literal);
ansi_printer.resume_style();
}
(NodeValue::CodeBlock(code), Start) => {
if ansi_printer.cur_line_len != 0 {
ansi_printer.newline();
}
ansi_printer.pause_style();
ansi_printer.start_bg(dark_grey_bg);
ansi_printer.text(&code.literal);
ansi_printer.newline();
ansi_printer.resume_style();
ansi_printer.newline();
}
(NodeValue::BlockQuote, Start) => {
ansi_printer.blockquote_depth += 1;
ansi_printer.pause_style();
ansi_printer.prefix();
ansi_printer.resume_style();
}
(NodeValue::BlockQuote, End) => {
ansi_printer.blockquote_depth -= 1;
ansi_printer.newline();
}
(NodeValue::HtmlInline(html), Start) => {
ansi_printer.pause_style();
ansi_printer.text(html);
ansi_printer.resume_style();
}
(NodeValue::HtmlBlock(html), Start) => {
if ansi_printer.cur_line_len != 0 {
ansi_printer.newline();
}
ansi_printer.pause_style();
ansi_printer.text(&html.literal);
ansi_printer.newline();
ansi_printer.resume_style();
}
(NodeValue::Heading(heading), Start) => {
ansi_printer.reset();
ansi_printer.start_bold();
ansi_printer
.out
.extend(std::iter::repeat('#').take(heading.level as usize));
ansi_printer.out.push(' ');
ansi_printer.cur_line_len += heading.level as usize + 1;
}
(NodeValue::Heading(_), End) => {
ansi_printer.reset();
ansi_printer.newline();
}
(NodeValue::List(list), Start) => {
if list.list_type == comrak::nodes::ListType::Ordered {
list_numbers.push(0);
}
}
(NodeValue::List(list), End) => {
if list.list_type == comrak::nodes::ListType::Ordered {
list_numbers.pop();
}
}
(NodeValue::Item(list), Start) => {
if list.list_type == comrak::nodes::ListType::Ordered {
use std::fmt::Write;
let number: usize = if let Some(number) = list_numbers.last_mut() {
*number += 1;
*number
} else {
0
};
let _ = write!(&mut ansi_printer, "{number}. ");
} else {
ansi_printer.out.push(bullet);
ansi_printer.out.push(' ');
ansi_printer.cur_line_len += 2;
}
}
(NodeValue::LineBreak, Start) => ansi_printer.newline(),
(NodeValue::SoftBreak, Start) => ansi_printer.newline(),
(NodeValue::ThematicBreak, Start) => {
if ansi_printer.cur_line_len != 0 {
ansi_printer.newline();
}
ansi_printer
.out
.extend(std::iter::repeat(horiz_rule).take(max_line_len));
ansi_printer.newline();
ansi_printer.newline();
}
(NodeValue::Emph, Start) => ansi_printer.start_italic(),
(NodeValue::Emph, End) => ansi_printer.stop_italic(),
(NodeValue::Strong, Start) => ansi_printer.start_bold(),
(NodeValue::Strong, End) => ansi_printer.stop_bold(),
(NodeValue::Strikethrough, Start) => ansi_printer.start_strike(),
(NodeValue::Strikethrough, End) => ansi_printer.stop_strike(),
(NodeValue::Escaped, Start) => (),
(_, End) => (),
(_, Start) => ansi_printer.text("?TODO?"),
}
}
if !links.is_empty() {
ansi_printer.out.push('\n');
for (i, url) in links.into_iter().enumerate() {
use std::fmt::Write;
let _ = writeln!(&mut ansi_printer.out, "({}. {url} )", i + 1);
}
}
ansi_printer.out
}
#[derive(Default)]
struct RenderStyling {
bold: bool,
italic: bool,
strike: bool,
fg: Option<&'static str>,
bg: Option<&'static str>,
}
struct AnsiPrinter {
special_render: &'static SpecialRender,
out: String,
cur_line_len: usize,
max_line_len: usize,
blockquote_depth: usize,
style_frames: Vec<RenderStyling>,
}
impl AnsiPrinter {
fn new(max_line_len: usize) -> Self {
Self {
special_render: special_render(),
out: String::new(),
cur_line_len: 0,
max_line_len,
blockquote_depth: 0,
style_frames: vec![RenderStyling::default()],
}
}
fn text(&mut self, text: &str) {
let mut iter = text.lines().peekable();
while let Some(mut line) = iter.next() {
loop {
let this_len = line.chars().count();
if self.cur_line_len + this_len > self.max_line_len {
let mut split_at = self.max_line_len - self.cur_line_len;
loop {
if line.is_char_boundary(split_at) {
break;
}
split_at -= 1;
}
let split_at = line
.split_at(split_at)
.0
.char_indices()
.rev()
.find(|(_, c)| c.is_whitespace())
.map(|(i, _)| i)
.unwrap_or(split_at);
let (head, tail) = line.split_at(split_at);
self.out.push_str(head);
self.cur_line_len += split_at;
self.newline();
line = tail.trim_start();
} else {
self.out.push_str(line);
self.cur_line_len += this_len;
break;
}
}
if iter.peek().is_some() {
self.newline();
}
}
}
fn current_fg(&self) -> Option<&'static str> {
self.current_style().fg
}
fn start_fg(&mut self, color: &'static str) {
self.current_style_mut().fg = Some(color);
self.out.push_str(color);
}
fn stop_fg(&mut self) {
self.current_style_mut().fg = None;
self.out.push_str(self.special_render.no_fg);
}
fn current_bg(&self) -> Option<&'static str> {
self.current_style().bg
}
fn start_bg(&mut self, color: &'static str) {
self.current_style_mut().bg = Some(color);
self.out.push_str(color);
}
fn stop_bg(&mut self) {
self.current_style_mut().bg = None;
self.out.push_str(self.special_render.no_bg);
}
fn is_bold(&self) -> bool {
self.current_style().bold
}
fn start_bold(&mut self) {
self.current_style_mut().bold = true;
self.out.push_str(self.special_render.bold);
}
fn stop_bold(&mut self) {
self.current_style_mut().bold = false;
self.out.push_str(self.special_render.reset);
if self.is_italic() {
self.out.push_str(self.special_render.italic);
}
if self.is_strike() {
self.out.push_str(self.special_render.strike);
}
}
fn is_italic(&self) -> bool {
self.current_style().italic
}
fn start_italic(&mut self) {
self.current_style_mut().italic = true;
self.out.push_str(self.special_render.italic);
}
fn stop_italic(&mut self) {
self.current_style_mut().italic = false;
self.out.push_str(self.special_render.no_italic_bold);
if self.is_bold() {
self.out.push_str(self.special_render.bold);
}
}
fn is_strike(&self) -> bool {
self.current_style().strike
}
fn start_strike(&mut self) {
self.current_style_mut().strike = true;
self.out.push_str(self.special_render.strike);
}
fn stop_strike(&mut self) {
self.current_style_mut().strike = false;
self.out.push_str(self.special_render.no_strike);
}
fn reset(&mut self) {
*self.current_style_mut() = RenderStyling::default();
self.out.push_str(self.special_render.reset);
}
fn pause_style(&mut self) {
self.out.push_str(self.special_render.reset);
self.style_frames.push(RenderStyling::default());
}
fn resume_style(&mut self) {
self.out.push_str(self.special_render.reset);
self.style_frames.pop();
if let Some(bg) = self.current_bg() {
self.out.push_str(bg);
}
if self.is_bold() {
self.out.push_str(self.special_render.bold);
}
if self.is_italic() {
self.out.push_str(self.special_render.italic);
}
if self.is_strike() {
self.out.push_str(self.special_render.strike);
}
}
fn newline(&mut self) {
if self.current_bg().is_some() {
self.out
.extend(std::iter::repeat(' ').take(self.max_line_len - self.cur_line_len));
}
self.pause_style();
self.out.push('\n');
self.prefix();
for _ in 0..self.blockquote_depth {
self.prefix();
}
self.resume_style();
self.cur_line_len = self.blockquote_depth * 2;
}
fn prefix(&mut self) {
self.out.push_str(self.special_render.dark_grey);
self.out.push(self.special_render.body_prefix);
self.out.push(' ');
}
fn current_style(&self) -> &RenderStyling {
self.style_frames.last().expect("Ran out of style frames")
}
fn current_style_mut(&mut self) -> &mut RenderStyling {
self.style_frames
.last_mut()
.expect("Ran out of style frames")
}
}
impl std::fmt::Write for AnsiPrinter {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.text(s);
Ok(())
}
}

View file

@ -393,7 +393,6 @@ impl PrCommand {
pub async fn view_pr(repo: &RepoName, api: &Forgejo, id: Option<u64>) -> eyre::Result<()> {
let crate::SpecialRender {
dash,
body_prefix,
bright_red,
bright_green,
@ -489,9 +488,7 @@ pub async fn view_pr(repo: &RepoName, api: &Forgejo, id: Option<u64>) -> eyre::R
if let Some(body) = &pr.body {
if !body.trim().is_empty() {
println!();
for line in body.lines() {
println!("{dark_grey}{body_prefix}{reset} {line}");
}
println!("{}", crate::markdown(body));
}
}
println!();
@ -507,13 +504,13 @@ async fn view_pr_labels(repo: &RepoName, api: &Forgejo, pr: Option<u64>) -> eyre
let pr = try_get_pr(repo, api, pr).await?;
let labels = pr.labels.as_deref().unwrap_or_default();
let SpecialRender {
colors,
fancy,
black,
white,
reset,
..
} = *crate::special_render();
if colors {
if fancy {
let mut total_width = 0;
for label in labels {
let name = label.name.as_deref().unwrap_or("???").trim();

View file

@ -375,22 +375,14 @@ async fn view_release(
&time::format_description::well_known::Rfc2822,
)?;
println!();
let SpecialRender {
bullet,
body_prefix,
dark_grey,
reset,
..
} = crate::special_render();
let SpecialRender { bullet, .. } = crate::special_render();
let body = release
.body
.as_ref()
.ok_or_else(|| eyre::eyre!("release does not have body"))?;
if !body.is_empty() {
println!();
for line in body.lines() {
println!("{dark_grey}{body_prefix}{reset} {line}");
}
println!("{}", crate::markdown(&body));
println!();
}
let assets = release

View file

@ -371,6 +371,7 @@ impl RepoCommand {
}
}
let desc = repo.description.as_deref().unwrap_or_default();
// Don't use body::markdown, this is plain text.
if !desc.is_empty() {
if desc.lines().count() > 1 {
println!();
@ -475,7 +476,7 @@ impl RepoCommand {
let path = path.unwrap_or_else(|| PathBuf::from(format!("./{repo_name}")));
let SpecialRender {
colors, // actually using it to indicate fanciness FIXME
fancy, // actually using it to indicate fanciness FIXME
hide_cursor,
show_cursor,
clear_line,
@ -489,7 +490,7 @@ impl RepoCommand {
let mut callbacks = git2::RemoteCallbacks::new();
callbacks.credentials(auth.credentials(&git_config));
if colors {
if fancy {
print!("{hide_cursor}");
print!(" Preparing...");
let _ = std::io::stdout().flush();
@ -529,7 +530,7 @@ impl RepoCommand {
let local_repo = git2::build::RepoBuilder::new()
.fetch_options(options)
.clone(clone_url.as_str(), &path)?;
if colors {
if fancy {
print!("{clear_line}{show_cursor}\r");
}
println!("Cloned {} into {}", repo_full_name, path.display());