summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCyborus <cyborus@noreply.codeberg.org>2024-06-08 22:43:36 +0200
committerCyborus <cyborus@noreply.codeberg.org>2024-06-08 22:43:36 +0200
commitd10b5172bef9a293f4f038587c07503c81b3d932 (patch)
treeafc21423caacf414b71c7dd408364c11276a8e46
parentMerge pull request 'guess pr number from commit' (#74) from guess-pr into main (diff)
parentfix first line of blockquote being grey (diff)
downloadforgejo-cli-d10b5172bef9a293f4f038587c07503c81b3d932.tar.xz
forgejo-cli-d10b5172bef9a293f4f038587c07503c81b3d932.zip
Merge pull request 'print markdown text nicely' (#75) from pretty-text into main
Reviewed-on: https://codeberg.org/Cyborus/forgejo-cli/pulls/75
-rw-r--r--Cargo.lock406
-rw-r--r--Cargo.toml2
-rw-r--r--src/issues.rs4
-rw-r--r--src/main.rs463
-rw-r--r--src/prs.rs9
-rw-r--r--src/release.rs12
-rw-r--r--src/repo.rs7
7 files changed, 879 insertions, 24 deletions
diff --git a/Cargo.lock b/Cargo.lock
index cd2931a..5e21b85 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -18,6 +18,15 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -127,6 +136,30 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -196,6 +229,7 @@ dependencies = [
"anstyle",
"clap_lex",
"strsim",
+ "terminal_size",
]
[[package]]
@@ -223,6 +257,26 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -248,6 +302,40 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -258,6 +346,41 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -268,6 +391,43 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -317,6 +477,12 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -343,6 +509,16 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -355,6 +531,8 @@ dependencies = [
"auth-git2",
"base64ct",
"clap",
+ "comrak",
+ "crossterm",
"directories",
"eyre",
"forgejo-api",
@@ -375,6 +553,16 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -744,6 +932,12 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -881,6 +1075,18 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -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",
]
@@ -994,6 +1201,28 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1108,6 +1337,20 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1129,6 +1372,15 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1188,6 +1440,35 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1263,6 +1544,15 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1368,6 +1658,33 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1386,6 +1703,16 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1431,6 +1758,29 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1474,6 +1824,16 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1625,6 +1985,12 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1661,6 +2027,12 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1700,6 +2072,16 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1807,6 +2189,15 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1962,6 +2353,21 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 241ec37..bd14324 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/src/issues.rs b/src/issues.rs
index b7b3a6e..233d603 100644
--- a/src/issues.rs
+++ b/src/issues.rs
@@ -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()
diff --git a/src/main.rs b/src/main.rs
index 1cc81e5..6bd8a45 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -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(())
+ }
}
diff --git a/src/prs.rs b/src/prs.rs
index ec42f01..b4e6e30 100644
--- a/src/prs.rs
+++ b/src/prs.rs
@@ -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();
diff --git a/src/release.rs b/src/release.rs
index b5a7566..1745a5e 100644
--- a/src/release.rs
+++ b/src/release.rs
@@ -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
diff --git a/src/repo.rs b/src/repo.rs
index 7520af8..33befec 100644
--- a/src/repo.rs
+++ b/src/repo.rs
@@ -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());