Add TUI viewer using cursive

This patch adds a new viewer, tui, that provides an interactive
interface using cursive.  This viewer makes it possible to follow links
to other documentation items and to open external links in a web
browser.
This commit is contained in:
Robin Krahl 2020-09-15 17:32:06 +02:00
parent 4260aab85a
commit e222a1f443
No known key found for this signature in database
GPG Key ID: 8E9B0870524F69D8
12 changed files with 1337 additions and 28 deletions

View File

@ -12,6 +12,8 @@ SPDX-License-Identifier: MIT
the configuration file.
- Add `merge` dependency in version 0.1.0.
- Add syntax highlighting for code snippets in the doc comments.
- Add interactive tui viewer that adds support for following links from the
documentation.
## v0.3.0 (2020-09-11)

480
Cargo.lock generated
View File

@ -7,6 +7,22 @@ name = "adler"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ahash"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"const-random 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ahash"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"const-random 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
@ -20,6 +36,16 @@ name = "anyhow"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "arc-swap"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "array-macro"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "assert_cmd"
version = "1.0.1"
@ -66,6 +92,11 @@ name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bumpalo"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byteorder"
version = "1.3.4"
@ -88,6 +119,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -113,6 +145,24 @@ dependencies = [
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "const-random"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"const-random-macro 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro-hack 0.5.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "const-random-macro"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro-hack 0.5.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crc32fast"
version = "1.2.0"
@ -121,6 +171,25 @@ dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-channel"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-utils"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cssparser"
version = "0.27.2"
@ -146,6 +215,80 @@ dependencies = [
"syn 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cursive"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ahash 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-channel 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"cursive_core 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"enumset 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
"maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"ncurses 5.99.0 (registry+https://github.com/rust-lang/crates.io-index)",
"signal-hook 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
"term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cursive_core"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ahash 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-channel 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"enum-map 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
"enumset 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"owning_ref 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"signal-hook 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"xi-unicode 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "darling"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"darling_macro 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "darling_core"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "darling_macro"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "derive_more"
version = "0.99.9"
@ -184,6 +327,45 @@ name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "enum-map"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"array-macro 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"enum-map-derive 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "enum-map-derive"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "enumset"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"enumset_derive 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "enumset_derive"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "env_logger"
version = "0.7.1"
@ -306,6 +488,21 @@ dependencies = [
"syn 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "idna"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-normalization 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "indexmap"
version = "1.5.1"
@ -333,6 +530,14 @@ name = "itoa"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "js-sys"
version = "0.3.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"wasm-bindgen 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "kuchiki"
version = "0.8.0"
@ -385,6 +590,11 @@ name = "mac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "markup5ever"
version = "0.10.0"
@ -417,6 +627,11 @@ name = "matches"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "maybe-uninit"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "merge"
version = "0.1.0"
@ -445,6 +660,16 @@ dependencies = [
"adler 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ncurses"
version = "5.99.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "new_debug_unreachable"
version = "1.0.4"
@ -455,6 +680,26 @@ name = "nodrop"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "num"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-complex 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
"num-iter 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-rational 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-complex"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-integer"
version = "0.1.43"
@ -464,6 +709,26 @@ dependencies = [
"num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-iter"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-rational"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.2.12"
@ -492,6 +757,14 @@ dependencies = [
"pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "owning_ref"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"stable_deref_trait 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pager"
version = "0.15.0"
@ -501,6 +774,11 @@ dependencies = [
"libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "percent-encoding"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "phf"
version = "0.8.0"
@ -726,6 +1004,7 @@ dependencies = [
"anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)",
"assert_cmd 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"cursive 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"html2text 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"insta 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -744,7 +1023,10 @@ dependencies = [
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"terminal_size 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
"text-style 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
"toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
"url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"webbrowser 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
"xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -865,6 +1147,24 @@ dependencies = [
"stable_deref_trait 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "signal-hook"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)",
"signal-hook-registry 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "signal-hook-registry"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arc-swap 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "siphasher"
version = "0.3.3"
@ -903,6 +1203,11 @@ dependencies = [
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "strsim"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "structopt"
version = "0.3.15"
@ -989,6 +1294,15 @@ dependencies = [
"utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "term_size"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "terminal_size"
version = "0.1.13"
@ -1012,6 +1326,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cursive 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
"syntect 4.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -1023,6 +1338,14 @@ dependencies = [
"unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "textwrap"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thin-slice"
version = "0.1.1"
@ -1037,6 +1360,11 @@ dependencies = [
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tinyvec"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "toml"
version = "0.5.6"
@ -1050,6 +1378,22 @@ name = "treeline"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-bidi"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-normalization"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"tinyvec 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-segmentation"
version = "1.6.0"
@ -1065,6 +1409,16 @@ name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "url"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "utf-8"
version = "0.7.5"
@ -1098,6 +1452,79 @@ name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "wasm-bindgen"
version = "0.2.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-macro 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bumpalo 3.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-shared 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-macro-support 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-backend 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-shared 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "web-sys"
version = "0.3.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"js-sys 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "webbrowser"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"web-sys 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)",
"widestring 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "widestring"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.9"
@ -1130,6 +1557,11 @@ name = "xdg"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "xi-unicode"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "xml-rs"
version = "0.8.3"
@ -1156,29 +1588,47 @@ dependencies = [
[metadata]
"checksum adler 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
"checksum ahash 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217"
"checksum ahash 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0adac150c2dd5a9c864d054e07bda5e6bc010cd10036ea5f17e82a2f5867f735"
"checksum ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
"checksum anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f"
"checksum arc-swap 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034"
"checksum array-macro 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "06e97b4e522f9e55523001238ac59d13a8603af57f69980de5d8de4bbbe8ada6"
"checksum assert_cmd 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c88b9ca26f9c16ec830350d309397e74ee9abdfd8eb1f71cb6ecc71a3fc818da"
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
"checksum base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
"checksum bincode 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum bumpalo 3.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
"checksum cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)" = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum chrono 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "c74d84029116787153e02106bf53e66828452a4b325cc8652b788b5967c0a0b6"
"checksum clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
"checksum console 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8c0994e656bba7b922d8dd1245db90672ffb701e684e45be58f20719d69abc5a"
"checksum const-random 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "2f1af9ac737b2dd2d577701e59fd09ba34822f6f2ebdb30a7647405d9e55e16a"
"checksum const-random-macro 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "25e4c606eb459dd29f7c57b2e0879f2b6f14ee130918c2b78ccb58a9624e6c7a"
"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
"checksum crossbeam-channel 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87"
"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
"checksum cssparser 0.27.2 (registry+https://github.com/rust-lang/crates.io-index)" = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a"
"checksum cssparser-macros 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e"
"checksum cursive 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a9f12332ab2bca26979ef00cfef9a1c2e287db03b787a83d892ad9961f81374"
"checksum cursive_core 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "85fc5b6a8ba2f1bc743892068bde466438f78d6247197e2dc094bfd53fdea4b7"
"checksum darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
"checksum darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
"checksum darling_macro 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
"checksum derive_more 0.99.9 (registry+https://github.com/rust-lang/crates.io-index)" = "298998b1cf6b5b2c8a7b023dfd45821825ce3ba8a8af55c921a0e734e4653f76"
"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
"checksum doc-comment 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
"checksum dtoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b"
"checksum dtoa-short 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "59020b8513b76630c49d918c33db9f4c91638e7d3404a28084083b87e33f76f2"
"checksum encode_unicode 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
"checksum enum-map 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2f81f09b9cb18af2ea1da2688a1d6b1762b4f938d7495bb034bce48d4c608043"
"checksum enum-map-derive 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e57001dfb2532f5a103ff869656887fae9a8defa7d236f3e39d2ee86ed629ad7"
"checksum enumset 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "959a80a2062fedd66ed41d99736212de987b3a8c83a4c2cef243968075256bd1"
"checksum enumset_derive 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "74bef436ac71820c5cf768d7af9ba33121246b09a00e09a55d94ef8095a875ac"
"checksum env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
"checksum errno 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "6eab5ee3df98a279d9b316b1af6ac95422127b1290317e6d18c1743c99418b01"
"checksum errno-dragonfly 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067"
@ -1193,9 +1643,12 @@ dependencies = [
"checksum hermit-abi 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c30f6d0bc6b00693347368a67d41b58f2fb851215ff1da49e90fe2c5c667151"
"checksum html2text 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a26379dcb715e237b96102a12b505c553e2bffa74bae2e54658748d298660ef1"
"checksum html5ever 0.25.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b"
"checksum ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
"checksum indexmap 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "86b45e59b16c76b11bf9738fd5d38879d3bd28ad292d7b313608becb17ae2df9"
"checksum insta 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)" = "617e921abc813f96a3b00958c079e7bf1e2db998f8a04f1546dd967373a418ee"
"checksum itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
"checksum js-sys 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)" = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8"
"checksum kuchiki 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1beeffc5ae5ab0def2cb85e26063a8e6b4f579b0adec3805bf87472086948956"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f"
@ -1204,19 +1657,28 @@ dependencies = [
"checksum linked-hash-map 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
"checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
"checksum mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
"checksum maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
"checksum markup5ever 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aae38d669396ca9b707bfc3db254bc382ddb94f57cc5c235f34623a669a01dab"
"checksum markup5ever_rcdom 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f015da43bcd8d4f144559a3423f4591d69b8ce0652c905374da7205df336ae2b"
"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
"checksum merge 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "10bbef93abb1da61525bbc45eeaff6473a41907d19f8f9aa5168d214e10693e9"
"checksum merge_derive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "209d075476da2e63b4b29e72a2ef627b840589588e71400a25e3565c4f849d07"
"checksum miniz_oxide 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f"
"checksum ncurses 5.99.0 (registry+https://github.com/rust-lang/crates.io-index)" = "15699bee2f37e9f8828c7b35b2bc70d13846db453f2d507713b758fabe536b82"
"checksum new_debug_unreachable 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
"checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
"checksum num 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ab3e176191bc4faad357e3122c4747aa098ac880e88b168f106386128736cf4a"
"checksum num-complex 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b05ad05bd8977050b171b3f6b48175fea6e0565b7981059b486075e1026a9fb5"
"checksum num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"
"checksum num-iter 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f"
"checksum num-rational 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138"
"checksum num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
"checksum onig 6.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd91ccd8a02fce2f7e8a86655aec67bc6c171e6f8e704118a0e8c4b866a05a8a"
"checksum onig_sys 69.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3814583fad89f3c60ae0701d80e87e1fd3028741723deda72d0d4a0ecf0cb0db"
"checksum owning_ref 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce"
"checksum pager 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b69ced2bfa977c4541743a7427b89c94120684791a9629941fe6028dccab6528"
"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
"checksum phf 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
"checksum phf_codegen 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
"checksum phf_generator 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
@ -1256,11 +1718,14 @@ dependencies = [
"checksum serde_tuple_macros 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4076151d1a2b688e25aaf236997933c66e18b870d0369f8b248b8ab2be630d7e"
"checksum serde_yaml 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3e2dd40a7cdc18ca80db804b7f461a39bb721160a85c9a1fa30134bf3c02a5"
"checksum servo_arc 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432"
"checksum signal-hook 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "604508c1418b99dfe1925ca9224829bb2a8a9a04dda655cc01fcad46f4ab05ed"
"checksum signal-hook-registry 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a3e12110bc539e657a646068aaf5eb5b63af9d0c1f7b29c97113fad80e15f035"
"checksum siphasher 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7"
"checksum smallvec 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3757cb9d89161a2f24e1cf78efa0c1fcff485d18e3f55e0aa3480824ddaa0f3f"
"checksum stable_deref_trait 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
"checksum string_cache 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2940c75beb4e3bf3a494cef919a747a2cb81e52571e212bfbd185074add7208a"
"checksum string_cache_codegen 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97"
"checksum strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
"checksum structopt 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "de2f5e239ee807089b62adce73e48c625e0ed80df02c7ab3f068f5db5281065c"
"checksum structopt-derive 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "510413f9de616762a4fbeab62509bf15c729603b72d7cd71280fbca431b1c118"
"checksum syn 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)" = "936cae2873c940d92e697597c5eee105fb570cd5689c695806f672883653349b"
@ -1268,27 +1733,42 @@ dependencies = [
"checksum syntect 4.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b57a45fdcf4891bc79f635be5c559210a4cfa464891f969724944c713282eedb"
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
"checksum tendril 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "707feda9f2582d5d680d733e38755547a3e8fb471e7ba11452ecfd9ce93a5d3b"
"checksum term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9"
"checksum terminal_size 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "9a14cd9f8c72704232f0bfc8455c0e861f0ad4eb60cc9ec8a170e231414c1e13"
"checksum termios 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6f0fcee7b24a25675de40d5bb4de6e41b0df07bc9856295e7e2b3a3600c400c2"
"checksum text-style 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "01ecc55ab699ed1b4d0ebe64d7a8ee2611f55d0de4042e9c1ee5a81dee89e332"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum textwrap 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789"
"checksum thin-slice 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
"checksum time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
"checksum tinyvec 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117"
"checksum toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a"
"checksum treeline 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41"
"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
"checksum unicode-normalization 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977"
"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
"checksum unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
"checksum unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
"checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
"checksum utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
"checksum version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
"checksum wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
"checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
"checksum wasm-bindgen 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)" = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42"
"checksum wasm-bindgen-backend 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)" = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68"
"checksum wasm-bindgen-macro 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)" = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038"
"checksum wasm-bindgen-macro-support 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)" = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe"
"checksum wasm-bindgen-shared 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)" = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307"
"checksum web-sys 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d"
"checksum webbrowser 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ecad156490d6b620308ed411cfee90d280b3cbd13e189ea0d3fada8acc89158a"
"checksum widestring 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a763e303c0e0f23b0da40888724762e802a8ffefbc22de4127ef42493c2ea68c"
"checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
"checksum xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57"
"checksum xi-unicode 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e71b85d8b1b8bfaf4b5c834187554d201a8cd621c2bbfa33efd41a3ecabd48b2"
"checksum xml-rs 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a"
"checksum xml5ever 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b1b52e6e8614d4a58b8e70cf51ec0cc21b256ad8206708bcff8139b5bbd6a59"
"checksum yaml-rust 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d"

View File

@ -19,6 +19,7 @@ exclude = [".builds/*", "tests/html/*", "tests/snapshots/*"]
ansi_term = "0.12.1"
anyhow = "1.0.31"
atty = "0.2.14"
cursive = "0.15.0"
html2text = "0.2.1"
kuchiki = "0.8.0"
log = "0.4.11"
@ -28,7 +29,10 @@ pager = "0.15.0"
serde_json = "1.0.56"
serde_tuple = "0.5.0"
terminal_size = "0.1.13"
textwrap = "0.12.1"
toml = "0.5.6"
url = "2.1.1"
webbrowser = "0.5.5"
xdg = "2.2.0"
[dependencies.env_logger]
@ -50,7 +54,7 @@ features = ["parsing", "regex-onig", "assets", "dump-load"]
[dependencies.text-style]
version = "0.2.0"
features = ["ansi_term", "syntect"]
features = ["ansi_term", "cursive", "syntect"]
[dev-dependencies]
assert_cmd = "1.0.1"

View File

@ -16,7 +16,23 @@ rusty-man packages are available for these distributions:
### Build Requirements
To compile rusty-man, you need Rust 1.40 or later.
To compile rusty-man, you need Rust 1.40 or later. The `tui` backend requires
the ncurses library in the default search path and a C compiler.
If you dont want to use ncurses, you can select another [`cursive`][] backend
by replacing this line in `Cargo.toml`:
```toml
cursive = "0.15.0"
```
with this:
```toml
cursive = { version = "0.15.0", default-features = false, feature = ["termion-backend"] }
```
[`cursive`]: https://lib.rs/cursive
### Installing from Git

View File

@ -53,6 +53,17 @@ Found mulitple matches for u8 select one of:
For more usage information, run `rusty-man --help`.
## Viewers
rusty-man has three viewers (select one with the `--viewer` option) that
determine the output format:
- `plain` prints the documentation to the standard output without formatting.
This is the default for non-interactive use.
- `rich` prints the documentation to the standard output with formatting and
optional syntax highlighting. This is the default for interactive use.
- `tui` is an interactive terminal interface. You can read the documentation
and follow links.
## Installation
To install rusty-man from source, clone this repository and use cargo to build
@ -60,8 +71,10 @@ the project, or use `cargo install`. See the [`INSTALL.md`][] file for more
information. rusty-man is also available as a package for these distributions:
- Arch Linux: [`rusty-man`][pkg-aur] in the Arch User Repository
rusty-man should run on all platforms, but the `rich` viewer is only available
for ANSI terminals. If you have trouble running rusty-man on your platform or
rusty-man is developed for Unix-like systems. It should run on other platforms
too, but with some limitations: The `rich` viewer uses ANSI escape codes,
which are not supported on older Windows versions. The `tui` viewer requires
the ncurses library. If you have trouble running rusty-man on your platform or
if you want to help porting rusty-man to other platforms, please let me know.
## Integrations

View File

@ -65,9 +65,9 @@ fn main() -> anyhow::Result<()> {
"Could not find examples for {}",
&args.keyword
);
viewer.open_examples(args.viewer_args, &doc, examples)
viewer.open_examples(sources, args.viewer_args, &doc, examples)
} else {
viewer.open(args.viewer_args, &doc)
viewer.open(sources, args.viewer_args, &doc)
}
} else {
// item selection cancelled by user

View File

@ -2,18 +2,26 @@
// SPDX-License-Identifier: MIT
mod text;
mod tui;
mod utils;
use std::fmt;
use crate::args;
use crate::doc;
use crate::source;
pub trait Viewer: fmt::Debug {
fn open(&self, args: args::ViewerArgs, doc: &doc::Doc) -> anyhow::Result<()>;
fn open(
&self,
sources: Vec<Box<dyn source::Source>>,
args: args::ViewerArgs,
doc: &doc::Doc,
) -> anyhow::Result<()>;
fn open_examples(
&self,
sources: Vec<Box<dyn source::Source>>,
args: args::ViewerArgs,
doc: &doc::Doc,
examples: Vec<doc::Example>,
@ -24,6 +32,7 @@ pub fn get_viewer(s: &str) -> anyhow::Result<Box<dyn Viewer>> {
let viewer: Box<dyn Viewer> = match s.to_lowercase().as_ref() {
"plain" => Box::new(text::TextViewer::new(text::TextMode::Plain)),
"rich" => Box::new(text::TextViewer::new(text::TextMode::Rich)),
"tui" => Box::new(tui::TuiViewer::new()),
_ => anyhow::bail!("The viewer {} is not supported", s),
};
Ok(viewer)

View File

@ -8,6 +8,7 @@ use std::io;
use crate::args;
use crate::doc;
use crate::source;
use crate::viewer::{self, utils};
#[derive(Clone, Debug)]
@ -41,12 +42,18 @@ impl TextViewer {
}
impl viewer::Viewer for TextViewer {
fn open(&self, args: args::ViewerArgs, doc: &doc::Doc) -> anyhow::Result<()> {
fn open(
&self,
_sources: Vec<Box<dyn source::Source>>,
args: args::ViewerArgs,
doc: &doc::Doc,
) -> anyhow::Result<()> {
self.exec(args, |mut viewer| viewer.render_doc(doc))
}
fn open_examples(
&self,
_sources: Vec<Box<dyn source::Source>>,
args: args::ViewerArgs,
doc: &doc::Doc,
examples: Vec<doc::Example>,

View File

@ -40,7 +40,7 @@ impl utils::ManRenderer for RichTextRenderer {
} else {
indent
};
let decorator = utils::RichDecorator::new(super::list_link);
let decorator = utils::RichDecorator::new(super::list_link, utils::LinkMode::List);
let lines = html2text::parse(s.html.as_bytes())
.render(self.line_length - indent, decorator)
.into_lines();

376
src/viewer/tui/mod.rs Normal file
View File

@ -0,0 +1,376 @@
// SPDX-FileCopyrightText: 2020 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT
mod views;
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 crate::args;
use crate::doc;
use crate::source;
use crate::viewer::{self, utils, utils::ManRenderer as _};
use views::{CodeView, HtmlView};
#[derive(Clone, Debug)]
pub struct TuiViewer {}
impl TuiViewer {
pub fn new() -> TuiViewer {
TuiViewer {}
}
fn render<F>(
&self,
sources: Vec<Box<dyn source::Source>>,
args: args::ViewerArgs,
doc: &doc::Doc,
f: F,
) -> anyhow::Result<()>
where
F: Fn(&mut TuiManRenderer) -> Result<(), convert::Infallible>,
{
let mut s = create_cursive(sources, args)?;
let mut renderer = context(&mut s).create_renderer(&doc);
f(&mut renderer)?;
let view = renderer.into_view();
s.add_fullscreen_layer(view);
s.run();
Ok(())
}
}
impl viewer::Viewer for TuiViewer {
fn open(
&self,
sources: Vec<Box<dyn source::Source>>,
args: args::ViewerArgs,
doc: &doc::Doc,
) -> anyhow::Result<()> {
self.render(sources, args, doc, |renderer| renderer.render_doc(doc))
}
fn open_examples(
&self,
sources: Vec<Box<dyn source::Source>>,
args: args::ViewerArgs,
doc: &doc::Doc,
examples: Vec<doc::Example>,
) -> anyhow::Result<()> {
self.render(sources, args, doc, |renderer| {
renderer.render_examples(doc, &examples)
})
}
}
pub struct Context {
pub sources: Vec<Box<dyn source::Source>>,
pub args: args::ViewerArgs,
pub highlighter: Option<utils::Highlighter>,
}
impl Context {
pub fn new(
sources: Vec<Box<dyn source::Source>>,
args: args::ViewerArgs,
) -> anyhow::Result<Context> {
let highlighter = utils::get_highlighter(&args)?;
Ok(Context {
sources,
args,
highlighter,
})
}
pub fn create_renderer(&self, doc: &doc::Doc) -> TuiManRenderer {
TuiManRenderer::new(
doc,
self.args.max_width.unwrap_or(100),
self.highlighter.as_ref(),
)
}
}
pub struct TuiManRenderer<'s> {
doc_name: doc::Fqn,
doc_ty: doc::ItemType,
layout: LinearLayout,
max_width: usize,
highlighter: Option<&'s utils::Highlighter>,
}
impl<'s> TuiManRenderer<'s> {
pub fn new(
doc: &doc::Doc,
max_width: usize,
highlighter: Option<&'s utils::Highlighter>,
) -> TuiManRenderer<'s> {
TuiManRenderer {
doc_name: doc.name.clone(),
doc_ty: doc.ty,
layout: LinearLayout::vertical(),
max_width,
highlighter,
}
}
fn into_view(self) -> impl cursive::View {
let title = format!("{} {}", self.doc_ty.name(), self.doc_name);
let scroll = self.layout.scrollable().full_screen();
Panel::new(scroll).title(title)
}
}
impl<'s> utils::ManRenderer for TuiManRenderer<'s> {
type Error = convert::Infallible;
fn print_title(&mut self, _left: &str, _center: &str, _right: &str) -> Result<(), Self::Error> {
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));
Ok(())
}
fn print_code(&mut self, indent: u8, code: &doc::Code) -> Result<(), Self::Error> {
if let Some(highlighter) = self.highlighter {
let code = CodeView::new(&code.to_string(), highlighter);
self.layout.add_child(indent_view(indent, code));
} else {
let text = TextView::new(code.to_string());
self.layout.add_child(indent_view(indent, text));
}
Ok(())
}
fn print_text(&mut self, indent: u8, text: &doc::Text) -> Result<(), Self::Error> {
let indent = usize::from(indent);
let mut text = HtmlView::new(
&text.html,
self.highlighter.cloned(),
self.max_width.saturating_sub(indent),
);
let doc_name = self.doc_name.clone();
let doc_ty = self.doc_ty;
text.set_on_link(move |s, link| handle_link(s, &doc_name.clone(), doc_ty, link));
self.layout.add_child(indent_view(indent, text));
Ok(())
}
fn println(&mut self) -> Result<(), Self::Error> {
self.layout.add_child(TextView::new(" "));
Ok(())
}
}
fn indent_view<V>(indent: impl Into<usize>, view: V) -> PaddedView<V> {
PaddedView::lrtb(indent.into(), 0, 0, 0, view)
}
fn create_cursive(
sources: Vec<Box<dyn source::Source>>,
args: args::ViewerArgs,
) -> anyhow::Result<cursive::Cursive> {
let mut cursive = cursive::default();
cursive.set_user_data(Context::new(sources, args)?);
cursive.add_global_callback('q', |s| s.quit());
cursive.add_global_callback(event::Key::Backspace, |s| {
let screen = s.screen_mut();
if screen.len() > 1 {
screen.pop_layer();
}
});
let mut theme = theme::Theme::default();
theme.shadow = false;
theme.palette[theme::PaletteColor::Background] = theme::Color::TerminalDefault;
theme.palette[theme::PaletteColor::View] = theme::Color::TerminalDefault;
theme.palette[theme::PaletteColor::Primary] = theme::Color::TerminalDefault;
cursive.set_theme(theme);
Ok(cursive)
}
fn context(s: &mut cursive::Cursive) -> &mut Context {
s.user_data()
.expect("Missing context in cursive application")
}
fn report_error(s: &mut cursive::Cursive, error: anyhow::Error) {
let context: Vec<_> = error
.chain()
.skip(1)
.map(|e| format!(" {}", e.to_string()))
.collect();
let mut msg = error.to_string();
if !context.is_empty() {
msg.push_str("\n\nContext:\n");
msg.push_str(&context.join("\n"));
}
let dialog = Dialog::info(msg).title("Error");
s.add_layer(dialog);
}
fn handle_link(s: &mut cursive::Cursive, doc_name: &doc::Fqn, doc_ty: doc::ItemType, link: String) {
let result = resolve_link(doc_name, doc_ty, link).and_then(|link| open_link(s, link));
if let Err(err) = result {
report_error(s, err);
}
}
fn find_doc(
sources: &[Box<dyn source::Source>],
ty: Option<doc::ItemType>,
name: &doc::Fqn,
) -> anyhow::Result<doc::Doc> {
for source in sources {
if let Some(doc) = source.find_doc(name, ty)? {
return Ok(doc);
}
}
Err(anyhow::anyhow!(
"Could not find documentation for item: {}",
name
))
}
fn open_link(s: &mut cursive::Cursive, link: ResolvedLink) -> anyhow::Result<()> {
match link {
ResolvedLink::Doc(ty, name) => {
let doc = find_doc(&context(s).sources, ty, &name)?;
let mut renderer = context(s).create_renderer(&doc);
renderer.render_doc(&doc).unwrap();
let view = renderer.into_view();
s.add_fullscreen_layer(view);
Ok(())
}
ResolvedLink::External(link) => webbrowser::open(&link)
.map(|_| {})
.context("Failed to open web browser"),
}
}
enum ResolvedLink {
Doc(Option<doc::ItemType>, doc::Fqn),
External(String),
}
fn resolve_link(
doc_name: &doc::Fqn,
doc_ty: doc::ItemType,
link: String,
) -> anyhow::Result<ResolvedLink> {
// TODO: support docs.rs and doc.rust-lang.org links
match url::Url::parse(&link) {
Ok(_) => Ok(ResolvedLink::External(link)),
Err(url::ParseError::RelativeUrlWithoutBase) => resolve_doc_link(doc_name, doc_ty, &link)
.with_context(|| format!("Could not parse relative link URL: {}", &link)),
Err(e) => {
Err(anyhow::Error::new(e).context(format!("Could not parse link URL: {}", &link)))
}
}
}
fn resolve_doc_link(
doc_name: &doc::Fqn,
doc_ty: doc::ItemType,
link: &str,
) -> anyhow::Result<ResolvedLink> {
// TODO: use a proper URL parser instead of manually parsing the URL
let (link, fragment) = {
let parts: Vec<_> = link.splitn(2, '#').collect();
if parts.len() > 1 {
(parts[0], Some(parts[1]))
} else {
(parts[0], None)
}
};
let parts: Vec<_> = link
.split('/')
.filter(|s| !s.is_empty())
.filter(|s| *s != ".")
.collect();
let (mut ty, mut name) = if doc_ty != doc::ItemType::Module && !parts.is_empty() {
(None, doc_name.parent())
} else {
(Some(doc_ty), Some(doc_name.to_owned()))
};
for part in parts {
// We support "..", "index.html", "<module>" and "<type>.<name>.html".
match part {
".." => {
ty = None;
name = name.context("Exceeded root level")?.parent();
}
"index.html" => {}
_ => {
if let Some((part_ty, part_name)) = parse_url_part(part, Some(".html")) {
// part == "type.name.html"
ty = Some(part_ty.parse()?);
name = if let Some(name) = name {
Some(name.child(&part_name))
} else {
Some(part_name.to_owned().into())
};
} else {
// part == "<module>"
ty = Some(doc::ItemType::Module);
name = if let Some(name) = name {
Some(name.child(&part))
} else {
Some(part.to_owned().into())
};
}
}
}
}
if let Some(fragment) = fragment {
// If the fragment is "<type>.:name>", we add it to the name, otherwise we ignore it
// because it just points to some other element on the page.
if let Some((fragment_ty, fragment_name)) = parse_url_part(fragment, None) {
ty = Some(fragment_ty.parse()?);
name = if let Some(name) = name {
Some(name.child(&fragment_name))
} else {
Some(fragment_name.to_owned().into())
};
}
}
Ok(ResolvedLink::Doc(
ty,
name.context("Cannot handle link to root")?,
))
}
fn parse_url_part<'s>(s: &'s str, suffix: Option<&str>) -> Option<(&'s str, &'s str)> {
let s = if let Some(suffix) = suffix {
if s.ends_with(suffix) {
&s[..s.len() - suffix.len()]
} else {
return None;
}
} else {
s
};
let parts: Vec<_> = s.split('.').collect();
if parts.len() == 2 {
Some((parts[0], parts[1]))
} else {
None
}
}

383
src/viewer/tui/views.rs Normal file
View File

@ -0,0 +1,383 @@
// SPDX-FileCopyrightText: 2020 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT
use std::cmp;
use std::iter;
use std::rc;
use cursive::{event, theme, utils::markup};
use html2text::render::text_renderer;
use crate::viewer::utils;
pub struct HtmlView {
render_tree: html2text::RenderTree,
highlighter: Option<utils::Highlighter>,
max_width: usize,
rendered_html: Option<RenderedHtml>,
focus: Option<usize>,
on_link: Option<rc::Rc<dyn Fn(&mut cursive::Cursive, String)>>,
}
impl HtmlView {
pub fn new(html: &str, highlighter: Option<utils::Highlighter>, max_width: usize) -> HtmlView {
HtmlView {
render_tree: html2text::parse(html.as_bytes()),
highlighter,
max_width,
rendered_html: None,
focus: None,
on_link: None,
}
}
pub fn set_on_link<F>(&mut self, cb: F)
where
F: Fn(&mut cursive::Cursive, String) + 'static,
{
self.on_link = Some(rc::Rc::new(cb));
}
fn render(&self, width: usize) -> RenderedHtml {
let mut rendered_html = RenderedHtml::new(width);
let decorator = utils::RichDecorator::new(show_link, utils::LinkMode::Annotate);
let raw_lines = self
.render_tree
.clone()
.render(width, decorator)
.into_lines();
let highlighted_lines = utils::highlight_html(&raw_lines, self.highlighter.as_ref());
for (y, line) in highlighted_lines.enumerate() {
rendered_html.push_line(y, line);
}
rendered_html
}
fn update(&mut self, constraint: cursive::XY<usize>) -> cursive::XY<usize> {
let width = cmp::min(self.max_width, constraint.x);
// If we already have rendered the tree with the same width, we can reuse the cached data.
if let Some(rendered_html) = &self.rendered_html {
if rendered_html.width == width {
return rendered_html.size;
}
}
let rendered_html = self.render(width);
// Due to changed wrapping, the link count may have changed. So we have to make sure that
// our focus is still valid.
if let Some(focus) = self.focus {
// TODO: Ideally, we would also want to adjust the focus if a previous link was
// re-wrapped.
if focus >= rendered_html.links.len() {
self.focus = Some(rendered_html.links.len() - 1);
}
}
let size = rendered_html.size;
self.rendered_html = Some(rendered_html);
size
}
}
impl cursive::View for HtmlView {
fn draw(&self, printer: &cursive::Printer) {
let lines = &self
.rendered_html
.as_ref()
.expect("layout not called before draw")
.lines;
for (y, line) in lines.iter().enumerate() {
let mut x = 0;
for element in line {
let mut style = element.style;
if element.link_idx == self.focus && printer.focused {
style = style.combine(theme::PaletteColor::Highlight);
}
printer.with_style(style, |printer| printer.print((x, y), &element.text));
x += element.text.len();
}
}
}
fn layout(&mut self, constraint: cursive::XY<usize>) {
self.update(constraint);
}
fn required_size(&mut self, constraint: cursive::XY<usize>) -> cursive::XY<usize> {
self.update(constraint)
}
fn take_focus(&mut self, direction: cursive::direction::Direction) -> bool {
let link_count = self
.rendered_html
.as_ref()
.map(|html| html.links.len())
.unwrap_or_default();
if link_count > 0 {
use cursive::direction::{Absolute, Direction, Relative};
let focus = match direction {
Direction::Abs(abs) => match abs {
Absolute::Up | Absolute::Left | Absolute::None => 0,
Absolute::Down | Absolute::Right => link_count - 1,
},
Direction::Rel(rel) => match rel {
Relative::Front => 0,
Relative::Back => link_count - 1,
},
};
self.focus = Some(focus);
true
} else {
false
}
}
fn on_event(&mut self, event: event::Event) -> event::EventResult {
use event::{Event, EventResult, Key};
let links = if let Some(rendered_html) = &self.rendered_html {
if rendered_html.links.is_empty() {
return EventResult::Ignored;
} else {
&rendered_html.links
}
} else {
return EventResult::Ignored;
};
let focus = if let Some(focus) = self.focus {
focus
} else {
return EventResult::Ignored;
};
match event {
Event::Key(Key::Left) => {
if focus == 0 {
EventResult::Ignored
} else if links[focus].position.y == links[focus - 1].position.y {
self.focus = Some(focus - 1);
EventResult::Consumed(None)
} else {
EventResult::Ignored
}
}
Event::Key(Key::Up) => {
let y = links[focus].position.y;
let next_focus = links[..focus]
.iter()
.enumerate()
.rev()
.find(|(_, link)| link.position.y < y)
.map(|(idx, _)| idx);
match next_focus {
Some(focus) => {
self.focus = Some(focus);
EventResult::Consumed(None)
}
None => EventResult::Ignored,
}
}
Event::Key(Key::Down) => {
let y = links[focus].position.y;
let next_focus = links
.iter()
.enumerate()
.skip(focus)
.find(|(_, link)| link.position.y > y)
.map(|(idx, _)| idx);
match next_focus {
Some(focus) => {
self.focus = Some(focus);
EventResult::Consumed(None)
}
None => EventResult::Ignored,
}
}
Event::Key(Key::Right) => {
if focus + 1 >= links.len() {
EventResult::Ignored
} else if links[focus].position.y == links[focus + 1].position.y {
self.focus = Some(focus + 1);
EventResult::Consumed(None)
} else {
EventResult::Ignored
}
}
Event::Key(Key::Enter) => {
let link = links[focus].target.clone();
let cb = self
.on_link
.clone()
.map(|cb| event::Callback::from_fn(move |s| cb(s, link.clone())));
EventResult::Consumed(cb)
}
_ => EventResult::Ignored,
}
}
fn important_area(&self, _: cursive::XY<usize>) -> cursive::Rect {
if let Some((focus, rendered_html)) = self.focus.zip(self.rendered_html.as_ref()) {
let origin = rendered_html.links[focus].position;
cursive::Rect::from_size(origin, (rendered_html.links[focus].width, 1))
} else {
cursive::Rect::from((0, 0))
}
}
}
fn show_link(url: &str) -> bool {
// We dont want to show fragment links as we cannot jump to HTML elements by ID
!url.starts_with('#')
}
#[derive(Clone, Debug)]
struct HtmlElement {
text: String,
style: theme::Style,
link_idx: Option<usize>,
}
#[derive(Clone, Debug)]
struct Link {
position: cursive::XY<usize>,
target: String,
width: usize,
}
#[derive(Clone, Debug)]
struct RenderedHtml {
width: usize,
size: cursive::XY<usize>,
lines: Vec<Vec<HtmlElement>>,
links: Vec<Link>,
}
impl RenderedHtml {
pub fn new(width: usize) -> RenderedHtml {
RenderedHtml {
width,
size: (0, 0).into(),
lines: Vec::new(),
links: Vec::new(),
}
}
pub fn push_link(&mut self, link: Link) -> usize {
self.links.push(link);
self.links.len() - 1
}
pub fn push_line(&mut self, y: usize, elements: Vec<utils::HighlightedHtmlElement>) {
let mut len = 0;
let mut line = Vec::new();
for element in elements {
let element = match element {
utils::HighlightedHtmlElement::RichString(ts) => {
let tag: Tag = ts.tag.iter().collect();
HtmlElement {
text: ts.s.clone(),
style: tag.style,
link_idx: tag.link_target.map(|target| {
self.push_link(Link {
position: (len, y).into(),
target,
width: ts.s.len(),
})
}),
}
}
utils::HighlightedHtmlElement::StyledString(s) => {
let s = utils::reset_background(s);
HtmlElement {
text: s.s.to_owned(),
style: s.style.map_or_else(Default::default, From::from),
link_idx: None,
}
}
};
len += element.text.len();
line.push(element);
}
self.lines.push(line);
self.size = self.size.stack_vertical(&(len, 1).into());
}
}
#[derive(Clone, Debug, Default, PartialEq)]
struct Tag {
style: theme::Style,
link_target: Option<String>,
}
impl<'a> iter::FromIterator<&'a text_renderer::RichAnnotation> for Tag {
fn from_iter<I: IntoIterator<Item = &'a text_renderer::RichAnnotation>>(iter: I) -> Tag {
let mut tag = Tag::default();
for annotation in iter {
if let text_renderer::RichAnnotation::Link(target) = annotation {
tag.link_target = Some(target.clone());
}
if let Some(style) = get_rich_style(annotation) {
tag.style = tag.style.combine(style);
}
}
tag
}
}
fn get_rich_style(annotation: &text_renderer::RichAnnotation) -> Option<theme::Style> {
use text_renderer::RichAnnotation;
match annotation {
RichAnnotation::Default => None,
RichAnnotation::Link(_) => Some(theme::Effect::Underline.into()),
RichAnnotation::Image => None,
RichAnnotation::Emphasis => Some(theme::Effect::Italic.into()),
RichAnnotation::Strong => Some(theme::Effect::Bold.into()),
RichAnnotation::Strikeout => Some(theme::Effect::Strikethrough.into()),
RichAnnotation::Code => Some(theme::PaletteColor::Secondary.into()),
RichAnnotation::Preformat(_) => None,
}
}
pub struct CodeView {
lines: Vec<markup::StyledString>,
width: usize,
}
impl CodeView {
pub fn new(code: &str, highlighter: &utils::Highlighter) -> CodeView {
let mut lines = Vec::new();
let mut width = 0;
for line in highlighter.highlight(code) {
let s = line
.iter()
.map(text_style::StyledStr::from)
.map(utils::reset_background)
.map(markup::StyledString::from)
.fold(markup::StyledString::new(), |mut acc, s| {
acc.append(s);
acc
});
width = cmp::max(width, s.width());
lines.push(s);
}
CodeView { lines, width }
}
}
impl cursive::View for CodeView {
fn draw(&self, printer: &cursive::Printer) {
for (y, line) in self.lines.iter().enumerate() {
printer.print_styled((0, y), line.into());
}
}
fn required_size(&mut self, _constraint: cursive::XY<usize>) -> cursive::XY<usize> {
(self.width, self.lines.len()).into()
}
}

View File

@ -285,18 +285,29 @@ fn print_heading<M: ManRenderer + ?Sized>(
viewer.print_heading(indent, text.as_ref())
}
/// Link handling mode for the [`RichDecorator`].
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum LinkMode {
/// Annotate links with `RichAnnotation::Link`.
Annotate,
/// List links at the end of the block.
List,
}
/// A decorator that generates rich text.
#[derive(Clone)]
pub struct RichDecorator {
link_filter: fn(&str) -> bool,
link_mode: LinkMode,
ignore_next_link: bool,
links: Vec<String>,
}
impl RichDecorator {
pub fn new(link_filter: fn(&str) -> bool) -> RichDecorator {
pub fn new(link_filter: fn(&str) -> bool, link_mode: LinkMode) -> RichDecorator {
RichDecorator {
link_filter,
link_mode,
ignore_next_link: false,
links: Vec::new(),
}
@ -311,11 +322,14 @@ impl text_renderer::TextDecorator for RichDecorator {
if self.ignore_next_link {
(String::new(), text_renderer::RichAnnotation::Default)
} else {
self.links.push(url.to_owned());
(
"[".to_owned(),
text_renderer::RichAnnotation::Link(url.to_owned()),
)
let annotation = text_renderer::RichAnnotation::Link(url.to_owned());
match self.link_mode {
LinkMode::Annotate => (String::new(), annotation),
LinkMode::List => {
self.links.push(url.to_owned());
("[".to_owned(), annotation)
}
}
}
}
@ -323,7 +337,10 @@ impl text_renderer::TextDecorator for RichDecorator {
if self.ignore_next_link {
String::new()
} else {
format!("][{}]", self.links.len() - 1)
match self.link_mode {
LinkMode::Annotate => String::new(),
LinkMode::List => format!("][{}]", self.links.len() - 1),
}
}
}
@ -373,23 +390,25 @@ impl text_renderer::TextDecorator for RichDecorator {
fn finalise(self) -> Vec<text_renderer::TaggedLine<text_renderer::RichAnnotation>> {
let mut lines = Vec::new();
for (idx, link) in self.links.into_iter().enumerate() {
let mut line = text_renderer::TaggedLine::new();
line.push_str(text_renderer::TaggedString {
s: format!("[{}] ", idx),
tag: text_renderer::RichAnnotation::Default,
});
line.push_str(text_renderer::TaggedString {
s: link.clone(),
tag: text_renderer::RichAnnotation::Link(link),
});
lines.push(line);
if self.link_mode == LinkMode::List {
for (idx, link) in self.links.into_iter().enumerate() {
let mut line = text_renderer::TaggedLine::new();
line.push_str(text_renderer::TaggedString {
s: format!("[{}] ", idx),
tag: text_renderer::RichAnnotation::Default,
});
line.push_str(text_renderer::TaggedString {
s: link.clone(),
tag: text_renderer::RichAnnotation::Link(link),
});
lines.push(line);
}
}
lines
}
fn make_subblock_decorator(&self) -> Self {
RichDecorator::new(self.link_filter)
RichDecorator::new(self.link_filter, self.link_mode)
}
}