add color themes

pull/33/head
chris west 1 year ago
parent 6861848217
commit 8ebeb8dec9

@ -1,6 +1,43 @@
## v1.2.0 (dev)
This release adds a few new config options, for your convenience:
phetch is all about fun colors, but your options are limited. You
can turn off colors with the `NO_COLOR` env variable or you can
leave them on. That's it.
Well, not anymore. As of `v1.2`, phetch now supports themes.
### Themes
Themes are simple files with the same format as `phetch.conf`:
$ cat ~/.config/phetch/default.theme
# Color Scheme
## UI
ui.cursor white bold
ui.number magenta
ui.menu yellow
ui.text white
## Items
item.text cyan
item.menu blue
item.error red
item.search white
item.telnet grey
item.external green
item.download white underline
item.media green underline
item.unsupported whitebg red
Create your theme file and launch phetch with `-t FILE`, or set
the `theme FILE` option in your `~/.config/phetch/phetch.conf`
You can see available colors and learn more about themes by opening
phetch's help - press `h` then `7` to get there quickly.
### Config Options
This release also adds a few new config options, for your convenience:
- `scroll` controls how many lines to jump by when paging up/down.
If set to 0 (the new default), you'll jump by an entire screen.
@ -8,9 +45,10 @@ This release adds a few new config options, for your convenience:
or not. By default it's false, but one might find it handy to set
to `true` if hosting, say, a Gopher-powered music server.
And a bonus:
### Keyboard Shortcuts
- `R` keyboard shortcut to refresh the current page.
Last but not least, you can now reload the current URL by pressing `R`.
Handy when developing your own Gopherhole!
## v1.1.0

@ -63,6 +63,8 @@ the gophersphere.
-c, --config FILE Use instead of ~/.config/phetch/phetch.conf
-C, --no-config Don't use any config file
-t, --theme FILE Use FILE for color theme or print current theme.
--print-theme Print current theme.
-h, --help Show this screen
-v, --version Show phetch version

@ -5,7 +5,7 @@
.nh
.ad l
.\" Begin generated content:
.TH "PHETCH" "1" "2022-11-13"
.TH "PHETCH" "1" "2022-11-14"
.P
.SH NAME
.P
@ -110,6 +110,16 @@ Use \fIFILE\fR instead of \fI~/.\&config/phetch/phetch.\&conf\fR
Do not use any config file.\&
.P
.RE
\fB-t\fR, \fB--theme\fR \fIFILE\fR
.RS 4
Use \fIFILE\fR for color theme.\&
.P
.RE
\fB--print-theme\fR
.RS 4
Print current theme.\&
.P
.RE
\fB-e\fR, \fB--encoding\fR \fIENCODING\fR
.RS 4
Render text views in CP437 or UTF8 (default) encoding.\&
@ -335,6 +345,80 @@ encoding utf8
# Wrap text at N columns\&. 0 = off (--wrap)
wrap 0
# How many lines to page up/down by? 0 = full screen
scroll 0
# Path to theme file, if you want to use one
theme ~/\&.config/phetch/dark\&.theme
.fi
.RE
.P
.SH THEMES
.P
You can change phetch'\&s color scheme by supplying your own theme
file with `--theme`/`-t` or by setting `theme FILE` in your
phetch.\&conf.\&
.P
You can also view the current theme with:
.P
.RS 4
$ phetch --print-theme
.P
.RE
Theme files look like this:
.P
.nf
.RS 4
ui\&.cursor white bold
ui\&.number magenta
ui\&.menu yellow
ui\&.text white
item\&.text cyan
item\&.menu blue
item\&.error red
item\&.search white
item\&.telnet grey
item\&.external green
item\&.download white underline
item\&.media green underline
item\&.unsupported whitebg red
.fi
.RE
.P
Valid colors for use in phetch themes:
.P
.nf
.RS 4
bold
underline
grey
red
green
yellow
blue
magenta
cyan
white
black
darkred
darkgreen
darkyellow
darkblue
darkmagenta
darkcyan
darkwhite
blackbg
redbg
greenbg
yellowbg
bluebg
magentabg
cyanbg
whitebg
.fi
.RE
.P
@ -346,6 +430,10 @@ using the `-m` command line flag.\& To test it out, visit a compatible
Gopher server or check out the "gopher types" help page by lauching
\fBphetch\fR and then pressing `ctrl-h` then `3`.\&
.P
By default \fBphetch\fR will prompt you when you try to open a media file,
but you can change this behavior by starting it with `--autoplay`/`-a`
or by setting `autoplayer true` in your config file.\&
.P
.SH ABOUT
.P
\fBphetch\fR is maintained by chris west, and released under the MIT license.\&

@ -73,6 +73,12 @@ If no URL is given, however, *phetch* will launch and open its default
*-C*, *--no-config*
Do not use any config file.
*-t*, *--theme* _FILE_
Use _FILE_ for color theme.
*--print-theme*
Print current theme.
*-e*, *--encoding* _ENCODING_
Render text views in CP437 or UTF8 (default) encoding.
@ -238,6 +244,71 @@ wrap 0
# How many lines to page up/down by? 0 = full screen
scroll 0
# Path to theme file, if you want to use one
theme ~/.config/phetch/dark.theme
```
# THEMES
You can change phetch's color scheme by supplying your own theme
file with `--theme`/`-t` or by setting `theme FILE` in your
phetch.conf.
You can also view the current theme with:
$ phetch --print-theme
Theme files look like this:
```
ui.cursor white bold
ui.number magenta
ui.menu yellow
ui.text white
item.text cyan
item.menu blue
item.error red
item.search white
item.telnet grey
item.external green
item.download white underline
item.media green underline
item.unsupported whitebg red
```
Valid colors for use in phetch themes:
```
bold
underline
grey
red
green
yellow
blue
magenta
cyan
white
black
darkred
darkgreen
darkyellow
darkblue
darkmagenta
darkcyan
darkwhite
blackbg
redbg
greenbg
yellowbg
bluebg
magentabg
cyanbg
whitebg
```
# MEDIA PLAYER SUPPORT
@ -248,6 +319,10 @@ using the `-m` command line flag. To test it out, visit a compatible
Gopher server or check out the "gopher types" help page by lauching
*phetch* and then pressing `ctrl-h` then `3`.
By default *phetch* will prompt you when you try to open a media file,
but you can change this behavior by starting it with `--autoplay`/`-a`
or by setting `autoplayer true` in your config file.
# ABOUT
*phetch* is maintained by chris west, and released under the MIT license.

@ -0,0 +1,20 @@
tls true
emoji true
# Color Scheme
## UI
ui.cursor white bold
ui.number magenta
ui.menu yellow
ui.text white
## Items
item.text cyan
item.menu blue
item.error red
item.search white
item.telnet grey
item.external green
item.download white underline
item.media green underline
item.unsupported whitebg red

@ -131,6 +131,18 @@ pub fn parse<T: AsRef<str>>(args: &[T]) -> Result<Config, ArgError> {
iter.next(); // skip arg
}
arg if arg.starts_with("--config=") || arg.starts_with("-config=") => {}
"-t" | "--theme" | "-theme" => {
if let Some(arg) = iter.next() {
cfg.theme = config::load_file(arg.as_ref())
.map_err(|e| ArgError::new(format!("error loading theme: {}", e)))?
.theme;
} else {
return Err(ArgError::new("need a theme file"));
}
}
"--print-theme" => {
cfg.mode = Mode::PrintTheme;
}
"-s" | "--tls" | "-tls" => {
if set_notls {
return Err(ArgError::new("can't set both --tls and --no-tls"));
@ -196,17 +208,17 @@ pub fn parse<T: AsRef<str>>(args: &[T]) -> Result<Config, ArgError> {
}
"-a" | "--autoplay" | "-autoplay" => {
if set_nomedia {
return Err(ArgError::new("can't set both --no-media and --autoplay"))
return Err(ArgError::new("can't set both --no-media and --autoplay"));
}
if set_noautoplay {
return Err(ArgError::new("can't set both --autoplay and --no-autoplay"))
return Err(ArgError::new("can't set both --autoplay and --no-autoplay"));
}
set_autoplay = true;
cfg.autoplay = true;
}
"-A" | "--no-autoplay" | "-no-autoplay" => {
if set_autoplay {
return Err(ArgError::new("can't set both --autoplay and --no-autoplay"))
return Err(ArgError::new("can't set both --autoplay and --no-autoplay"));
}
cfg.autoplay = false;
set_noautoplay = true;
@ -238,7 +250,9 @@ pub fn parse<T: AsRef<str>>(args: &[T]) -> Result<Config, ArgError> {
#[cfg(not(test))]
{
if !atty::is(atty::Stream::Stdout) && cfg.mode != Mode::Raw {
if !atty::is(atty::Stream::Stdout)
&& !matches!(cfg.mode, Mode::Raw | Mode::Print | Mode::PrintTheme)
{
cfg.mode = Mode::NoTTY;
}
}

@ -1,121 +0,0 @@
//! Terminal colors.
//! Provides a macro to color text as well as sturcts to get their
//! raw ansi codes.
use std::fmt;
/// Shortcut to produce a String colored with one or more colors.
/// Example:
/// ```ignore
/// let s = color_string!("Red string", Red);
/// let x = color_string!("Hyperlink-ish", Blue, Underline);
macro_rules! color_string {
($s:expr, $( $color:ident ),+) => {{
if *crate::NO_COLOR {
$s.to_string()
} else {
let mut out = String::from("\x1b[");
$( out.push_str(crate::color::$color::code()); out.push_str(";"); )+
out.push('m');
out.push_str(&$s);
out.push_str(crate::color::Reset.as_ref());
out.replace(";m", "m")
}
}};
}
/// Shortcut to produce a color's ANSI escape code. Don't forget to Reset!
/// ```ignore
/// let mut o = String::new();
/// o.push_str(color!(Blue));
/// o.push_str(color!(Underline));
/// o.push_str("Hyperlinkish.");
/// o.push_str(color!(Reset));
macro_rules! color {
($color:ident) => {
if *crate::NO_COLOR {
""
} else {
crate::color::$color.as_ref()
}
};
}
/// Create a color:: struct that can be used with format!.
/// Example:
/// ```ignore
/// define_color(Red, 91);
/// define_color(Reset, 0);
///
/// println!("{}Error: {}{}", color::Red, msg, color::Reset);
macro_rules! define_color {
($color:ident, $code:literal) => {
#[allow(missing_docs)]
pub struct $color;
impl fmt::Display for $color {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_ref())
}
}
impl $color {
#[allow(missing_docs)]
#[inline]
pub fn code() -> &'static str {
concat!($code)
}
}
impl AsRef<str> for $color {
#[inline]
fn as_ref(&self) -> &'static str {
concat!("\x1b[", $code, "m")
}
}
};
}
define_color!(Reset, 0);
define_color!(Bold, 1);
define_color!(Underline, 4);
define_color!(Grey, 90);
define_color!(Red, 91);
define_color!(Green, 92);
define_color!(Yellow, 93);
define_color!(Blue, 94);
define_color!(Magenta, 95);
define_color!(Cyan, 96);
define_color!(White, 97);
define_color!(Black, 30);
define_color!(DarkRed, 31);
define_color!(DarkGreen, 32);
define_color!(DarkYellow, 33);
define_color!(DarkBlue, 34);
define_color!(DarkMagenta, 35);
define_color!(DarkCyan, 36);
define_color!(DarkWhite, 37);
define_color!(BlackBG, 40);
define_color!(RedBG, 41);
define_color!(GreenBG, 42);
define_color!(YellowBG, 43);
define_color!(BlueBG, 44);
define_color!(MagentaBG, 45);
define_color!(CyanBG, 46);
define_color!(WhiteBG, 47);
#[cfg(test)]
mod tests {
#[test]
fn test_colors() {
assert_eq!(color_string!("Error", Red), "\x1b[91mError\x1b[0m");
assert_eq!(
color_string!("Fancy Pants", Blue, Underline),
"\x1b[94;4mFancy Pants\x1b[0m"
);
assert_eq!(
color_string!("Super-duper-fancy-pants", Magenta, Underline, Bold, BlueBG),
"\x1b[95;4;1;44mSuper-duper-fancy-pants\x1b[0m"
)
}
}

@ -4,7 +4,12 @@
//! An example default config is provided but unused by this module.
use {
crate::{encoding::Encoding, phetchdir, ui},
crate::{
encoding::Encoding,
phetchdir,
theme::{to_color, Theme},
ui,
},
std::{
collections::HashMap,
fs::OpenOptions,
@ -58,6 +63,24 @@ wrap 0
# How many lines to page up/down by? 0 = full screen
scroll 0
# Path to theme file, if any
theme ~/.config/phetch/pink.theme
# Inline Theme
ui.cursor white bold
ui.number magenta
ui.menu yellow
ui.text white
item.text cyan
item.menu blue
item.error red
item.search white
item.telnet grey
item.external green
item.download white underline
item.media green underline
item.unsupported whitebg red
";
/// Not all the config options are available in the phetch.conf. We
@ -87,6 +110,8 @@ pub struct Config {
pub wrap: usize,
/// Scroll by how many lines? 0 = full screen
pub scroll: usize,
/// Color Scheme
pub theme: Theme,
}
impl Default for Config {
@ -103,6 +128,7 @@ impl Default for Config {
mode: ui::Mode::default(),
wrap: 0,
scroll: 0,
theme: Theme::default(),
}
}
}
@ -200,6 +226,33 @@ fn parse(text: &str) -> Result<Config> {
cfg.encoding = Encoding::from_str(val)
.map_err(|e| error!("{} on line {}: {:?}", e, linenum, line))?;
}
"theme" => {
let homevar = std::env::var("HOME");
if homevar.is_err() && val.contains('~') {
return Err(error!("$HOME not set, can't decode `~`"));
}
cfg.theme = load_file(&val.replace('~', &homevar.unwrap()))
.map_err(|e| error!("error loading theme: {}", e))?
.theme
}
// color scheme
"ui.cursor" => cfg.theme.ui_cursor = to_color(val),
"ui.number" => cfg.theme.ui_number = to_color(val),
"ui.menu" => cfg.theme.ui_menu = to_color(val),
"ui.text" => cfg.theme.ui_text = to_color(val),
"item.text" => cfg.theme.item_text = to_color(val),
"item.menu" => cfg.theme.item_menu = to_color(val),
"item.error" => cfg.theme.item_error = to_color(val),
"item.search" => cfg.theme.item_search = to_color(val),
"item.telnet" => cfg.theme.item_telnet = to_color(val),
"item.external" => cfg.theme.item_external = to_color(val),
"item.download" => cfg.theme.item_download = to_color(val),
"item.media" => cfg.theme.item_media = to_color(val),
"item.unsupported" => cfg.theme.item_unsupported = to_color(val),
_ => return Err(error!("Unknown key on line {}: {}", linenum, key)),
}
keys.insert(key, true);

@ -10,6 +10,7 @@ pub fn lookup(name: &str) -> Option<String> {
"history" => history::as_raw_menu(),
"bookmarks" => bookmarks::as_raw_menu(),
"help/config" => format!("{}{}", HEADER, CONFIG),
"help/themes" => format!("{}{}", HEADER, THEMES),
"help/keys" => format!("{}{}", HEADER, KEYS),
"help/nav" => format!("{}{}", HEADER, NAV),
"help/types" => format!("{}{}", HEADER, TYPES),
@ -84,6 +85,7 @@ i
1bookmarks /help/bookmarks phetch
1history /help/history phetch
1phetch.conf /help/config phetch
1themes /help/themes phetch
i
i ~ * ~
i
@ -122,7 +124,7 @@ ia show history
i
ir view raw source
iw toggle wide mode
ie toggle encoding (cp437 & utf8)
ie toggle encoding
iq quit phetch
ih show help
i
@ -238,6 +240,79 @@ iwide no
i
i# show emoji status indicators
iemoji no
i
i# cp437 or utf8 encoding
iencoding utf8
i
i# wrap text at N cols. 0 = off
iwrap 0
i
i# page up/down by N lines.
i# 0 = full screen
iscroll 0
i
i# path to theme file, if any
itheme ~/.config/phetch/fun.theme
";
const THEMES: &str = "
i ** themes **
i
iyou can change phetch's color
ischeme by supplying your own
itheme file with --theme/-t or
iby setting `theme FILE` in
iyour phetch.conf.
i
iyou can also view the current
itheme with:
i
i$ phetch --print-theme
i
itheme files look like this:
i
iui.cursor white bold
iui.number magenta
iui.menu yellow
iui.text white
iitem.text cyan
iitem.menu blue
iitem.error red
iitem.search white
iitem.telnet grey
iitem.external green
iitem.download white underline
iitem.media green underline
iitem.unsupported whitebg red
i
ivalid colors:
i
ibold
iunderline
igrey
ired
igreen
iyellow
iblue
imagenta
icyan
iwhite
iblack
idarkred
idarkgreen
idarkyellow
idarkblue
idarkmagenta
idarkcyan
idarkwhite
iblackbg
iredbg
igreenbg
iyellowbg
ibluebg
imagentabg
icyanbg
iwhitebg
";
const TYPES: &str = "

@ -39,7 +39,7 @@ extern crate lazy_static;
#[macro_use]
pub mod utils;
#[macro_use]
pub mod color;
pub mod theme;
pub mod args;
pub mod bookmarks;
pub mod config;

@ -1,5 +1,7 @@
use phetch::{
args, color, gopher, menu, terminal,
args,
config::{Config, SharedConfig},
gopher, menu, terminal, theme,
ui::{Mode, UI},
};
use std::{
@ -26,6 +28,7 @@ fn run() -> Result<(), Box<dyn Error>> {
Mode::Raw => return print_raw(&cfg.start, cfg.tls, cfg.tor),
Mode::Version => return print_version(),
Mode::Help => return print_usage(),
Mode::PrintTheme => return print_theme(cfg),
Mode::NoTTY => return print_plain(&cfg.start, cfg.tls, cfg.tor),
Mode::Print => cfg.wide = true,
Mode::Run => {}
@ -91,6 +94,8 @@ Options:
-c, --config FILE Use instead of ~/.config/phetch/phetch.conf
-C, --no-config Don't use any config file
-t, --theme FILE Use FILE for color theme or print current theme.
--print-theme Print current theme.
-h, --help Show this screen
-v, --version Show phetch version
@ -118,7 +123,7 @@ fn print_plain(url: &str, tls: bool, tor: bool) -> Result<(), Box<dyn Error>> {
let response = gopher::response_to_string(&response);
match typ {
gopher::Type::Menu => {
let menu = menu::parse(url, response);
let menu = menu::parse(url, response, SharedConfig::default());
for line in menu.lines() {
out.push_str(line.text());
out.push('\n');
@ -136,6 +141,12 @@ fn print_plain(url: &str, tls: bool, tor: bool) -> Result<(), Box<dyn Error>> {
Ok(())
}
/// Print current theme as plaintext
fn print_theme(cfg: Config) -> Result<(), Box<dyn Error>> {
println!("{}", cfg.theme.to_string());
Ok(())
}
/// Put the terminal into raw mode, enter the alternate screen, and
/// setup the panic handler.
fn setup_terminal() {
@ -156,7 +167,7 @@ fn cleanup_terminal() {
write!(
stdout,
"{}{}{}{}{}",
color::Reset,
theme::color::Reset,
terminal::ClearAll,
terminal::Goto(1, 1),
terminal::ShowCursor,

@ -52,6 +52,8 @@ pub struct Menu {
wide: bool,
/// Scroll by how many lines?
scroll: usize,
/// Global config
config: Config,
}
/// Represents a line in a Gopher menu. Provides the actual text of
@ -258,7 +260,7 @@ impl Menu {
wide: config.read().unwrap().wide,
scroll: config.read().unwrap().scroll,
mode: config.read().unwrap().mode,
..parse(url, response)
..parse(url, response, config.clone())
}
}
@ -369,26 +371,27 @@ impl Menu {
for line in iter {
out.push_str(&left_margin);
let config = self.config.read().unwrap();
if line.typ == Type::Info {
out.push_str(" ");
} else {
if line.link == self.link && self.show_cursor() {
out.push_str(color!(Bold));
out.push_str(&config.theme.ui_cursor);
out.push('*');
out.push_str(color!(Reset));
out.push_str(reset_color!());
} else {
out.push(' ');
}
out.push(' ');
out.push_str(color!(Magenta));
out.push_str(&config.theme.ui_number);
if line.link < 9 {
out.push(' ');
}
let num = (line.link + 1).to_string();
out.push_str(&num);
out.push_str(". ");
out.push_str(color!(Reset));
out.push_str(reset_color!());
}
// truncate long lines, instead of wrapping
@ -396,28 +399,25 @@ impl Menu {
// color the line
if line.typ.is_media() {
out.push_str(color!(Underline));
out.push_str(color!(Green));
out.push_str(&config.theme.item_media);
} else if line.typ.is_download() {
out.push_str(color!(Underline));
out.push_str(color!(White));
out.push_str(&config.theme.item_download);
} else if !line.typ.is_supported() {
out.push_str(color!(WhiteBG));
out.push_str(color!(Red));
out.push_str(&self.config.read().unwrap().theme.item_unsupported);
} else {
out.push_str(match line.typ {
Type::Text => color!(Cyan),
Type::Menu => color!(Blue),
Type::Info => color!(Yellow),
Type::HTML => color!(Green),
Type::Error => color!(Red),
Type::Telnet => color!(Grey),
Type::Search => color!(White),
_ => color!(Red),
Type::Text => &config.theme.item_text,
Type::Menu => &config.theme.item_menu,
Type::Info => &config.theme.ui_menu,
Type::HTML => &config.theme.item_external,
Type::Error => &config.theme.item_error,
Type::Telnet => &config.theme.item_telnet,
Type::Search => &config.theme.item_search,
_ => &config.theme.item_error,
});
}
out.push_str(&text);
out.push_str(color!(Reset));
out.push_str(reset_color!());
// clear rest of line
out.push_str(terminal::ClearUntilNewline.as_ref());
@ -935,7 +935,7 @@ impl Menu {
}
/// Parse gopher response into a Menu object.
pub fn parse(url: &str, raw: String) -> Menu {
pub fn parse(url: &str, raw: String, config: Config) -> Menu {
let mut spans = vec![];
let mut links = vec![];
let mut longest = 0;
@ -982,6 +982,7 @@ pub fn parse(url: &str, raw: String) -> Menu {
tor: false,
wide: false,
scroll: 0,
config: config,
}
}
@ -1054,7 +1055,7 @@ mod tests {
macro_rules! parse {
($s:expr) => {
parse("test", $s.to_string())
parse("test", $s.to_string(), Config::default())
};
}

@ -0,0 +1,228 @@
//! Terminal color scheme.
//! Provides the Theme struct and functions/macros for making use of it.
/// Provides a shortcut to the Reset color code.
pub mod color {
use std::fmt;
/// Can be used with fmt calls to reset to terminal defaults.
pub struct Reset;
impl fmt::Display for Reset {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "\x1b[0m")
}
}
}
/// Use with push_str() or something.
macro_rules! reset_color {
() => {
"\x1b[0m"
};
}
/// Color scheme for UI and menu items.
#[derive(Debug)]
pub struct Theme {
// UI Colors
/// The * cursor that appears next to the selected menu item.
pub ui_cursor: String,
/// The Number that appears to the left of a menu item.
pub ui_number: String,
/// The text in a menu.
pub ui_menu: String,
/// The color of the text content in a document.
pub ui_text: String,
// Menu Item Colors
/// Text document.
pub item_text: String,
/// Another menu.
pub item_menu: String,
/// Something went wrong.
pub item_error: String,
/// Gopher search prompt
pub item_search: String,
/// Telnet item. MUDs and stuff.
pub item_telnet: String,
/// External link. HTTP, usually.
pub item_external: String,
/// Binary file that can be downloaded to disk.
pub item_download: String,
/// Media that can be opened, like an image or mp3.
pub item_media: String,
/// An unknown or unsupported Gopher type.
pub item_unsupported: String,
}
impl Default for Theme {
fn default() -> Theme {
Theme {
ui_cursor: to_color("white bold").into(),
ui_number: to_color("magenta").into(),
ui_menu: to_color("yellow").into(),
ui_text: to_color("white").into(),
item_text: to_color("cyan").into(),
item_menu: to_color("blue").into(),
item_error: to_color("red").into(),
item_search: to_color("white").into(),
item_telnet: to_color("grey").into(),
item_external: to_color("green").into(),
item_download: to_color("white underline").into(),
item_media: to_color("green underline").into(),
item_unsupported: to_color("whitebg red").into(),
}
}
}
impl Theme {
/// Return theme file for this theme
pub fn to_string(&self) -> String {
format!(
"# phetch theme
ui.cursor {ui_cursor}
ui.number {ui_number}
ui.menu {ui_menu}
ui.text {ui_text}
item.text {item_text}
item.menu {item_menu}
item.error {item_error}
item.search {item_search}
item.telnet {item_telnet}
item.external {item_external}
item.download {item_download}
item.media {item_media}
item.unsupported {item_unsupported}",
ui_cursor = to_words(&self.ui_cursor),
ui_number = to_words(&self.ui_number),
ui_menu = to_words(&self.ui_menu),
ui_text = to_words(&self.ui_text),
item_text = to_words(&self.item_text),
item_menu = to_words(&self.item_menu),
item_error = to_words(&self.item_error),
item_search = to_words(&self.item_search),
item_telnet = to_words(&self.item_telnet),
item_external = to_words(&self.item_external),
item_download = to_words(&self.item_download),
item_media = to_words(&self.item_media),
item_unsupported = to_words(&self.item_unsupported),
)
}
}
/// Convert a string like "blue underline" or "red" into a color code.
pub fn to_color<S: AsRef<str>>(line: S) -> String {
let parts = line.as_ref().split(' ').collect::<Vec<_>>();
if parts.is_empty() {
return "".into();
}
let mut out = String::from("\x1b[");
let len = parts.len();
for (i, part) in parts.iter().enumerate() {
out.push_str(&color_code(part).to_string());
if i < len - 1 {
out.push(';');
}
}
out.push('m');
out
}
/// Convert color code like "\x1b[91m" into something like "red"
pub fn to_words<S: AsRef<str>>(code: S) -> String {
code.as_ref()
.replace("\x1b[", "")
.replace('m', "")
.split(';')
.map(color_word)
.collect::<Vec<_>>()
.join(" ")
}
fn color_code(color: &str) -> usize {
match color {
"bold" => 1,
"underline" => 4,
"grey" => 90,
"red" => 91,
"green" => 92,
"yellow" => 93,
"blue" => 94,
"magenta" => 95,
"cyan" => 96,
"white" => 97,
"black" => 30,
"darkred" => 31,
"darkgreen" => 32,
"darkyellow" => 33,
"darkblue" => 34,
"darkmagenta" => 35,
"darkcyan" => 36,
"darkwhite" => 37,
"blackbg" => 40,
"redbg" => 41,
"greenbg" => 42,
"yellowbg" => 43,
"bluebg" => 44,
"magentabg" => 45,
"cyanbg" => 46,
"whitebg" => 47,
_ => 0,
}
}
fn color_word(code: &str) -> &'static str {
match code {
"1" => "bold",
"4" => "underline",
"90" => "grey",
"91" => "red",
"92" => "green",
"93" => "yellow",
"94" => "blue",
"95" => "magenta",
"96" => "cyan",
"97" => "white",
"30" => "black",
"31" => "darkred",
"32" => "darkgreen",
"33" => "darkyellow",
"34" => "darkblue",
"35" => "darkmagenta",
"36" => "darkcyan",
"37" => "darkwhite",
"40" => "blackbg",
"41" => "redbg",
"42" => "greenbg",
"43" => "yellowbg",
"44" => "bluebg",
"45" => "magentabg",
"46" => "cyanbg",
"47" => "whitebg",
_ => "white",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_color_scheme() {
let mut theme = Theme::default();
theme.ui_cursor = to_color("bold").into();
theme.ui_menu = to_color("red").into();
theme.item_menu = to_color("blue underline").into();
assert_eq!("\u{1b}[1m", theme.ui_cursor);
assert_eq!("\u{1b}[91m", theme.ui_menu);
assert_eq!("\u{1b}[94;4m", theme.item_menu);
}
}

@ -17,7 +17,7 @@ mod view;
pub use self::{action::Action, mode::Mode, view::View};
use crate::{
bookmarks, color,
bookmarks,
config::{Config, SharedConfig},
encoding::Encoding,
gopher::{self, Type},
@ -25,7 +25,7 @@ use crate::{
menu::Menu,
terminal,
text::Text,
utils, BUG_URL,
theme, utils, BUG_URL,
};
use std::{
io::{stdin, stdout, Result, Write},
@ -153,7 +153,12 @@ impl UI {
self.status.clear();
}
if let Err(e) = self.process_action(action) {
self.set_status(&format!("{}{}{}", color::Red, e, terminal::HideCursor));
self.set_status(&format!(
"{}{}{}",
&self.config.read().unwrap().theme.item_error,
e,
terminal::HideCursor
));
}
}
@ -203,7 +208,9 @@ impl UI {
if typ.is_media() && self.config.read().unwrap().media.is_some() {
self.dirty = true;
return if self.config.read().unwrap().autoplay || self.confirm(&format!("Open in media player? {}", url)) {
return if self.config.read().unwrap().autoplay
|| self.confirm(&format!("Open in media player? {}", url))
{
utils::open_media(self.config.read().unwrap().media.as_ref().unwrap(), url)
} else {
Ok(())
@ -339,7 +346,7 @@ impl UI {
label,
".".repeat(i),
terminal::ClearUntilNewline,
color::Reset,
theme::color::Reset,
terminal::ShowCursor,
);
stdout().flush().expect(ERR_STDOUT);
@ -414,7 +421,7 @@ impl UI {
terminal::Goto(self.cols() - len as u16, self.rows()),
status
.iter()
.map(|s| color_string!(s, Bold, White))
.map(|s| theme::to_color("bold white") + s + reset_color!())
.collect::<Vec<_>>()
.join(" "),
))
@ -430,7 +437,7 @@ impl UI {
terminal::ClearCurrentLine,
self.status,
self.render_conn_status().unwrap_or_else(|| "".into()),
color::Reset,
theme::color::Reset,
)
}
@ -454,7 +461,7 @@ impl UI {
write!(
out,
"{}{}{}{} [Y/n]: {}",
color::Reset,
theme::color::Reset,
terminal::Goto(1, rows),
terminal::ClearCurrentLine,
question,
@ -479,7 +486,7 @@ impl UI {
write!(
out,
"{}{}{}{}{}{}",
color::Reset,
theme::color::Reset,
terminal::Goto(1, rows),
terminal::ClearCurrentLine,
prompt,

@ -21,6 +21,9 @@ pub enum Mode {
/// Show command line help.
/// phetch --help
Help,
/// Print current theme
/// phetch --theme
PrintTheme,
}
impl Default for Mode {

Loading…
Cancel
Save