add cp437 encoding for menus

cp437-menus
chris west 4 years ago
parent 6de0dbc53f
commit 96959a7a04

@ -111,7 +111,6 @@ fn print_plain(url: &str, tls: bool, tor: bool) -> Result<(), Box<dyn Error>> {
let mut out = String::new();
let typ = gopher::type_for_url(url);
let (_, response) = gopher::fetch_url(url, tls, tor)?;
let response = gopher::response_to_string(&response);
match typ {
gopher::Type::Menu => {
let menu = menu::parse(url, response);
@ -120,7 +119,10 @@ fn print_plain(url: &str, tls: bool, tor: bool) -> Result<(), Box<dyn Error>> {
out.push('\n');
}
}
gopher::Type::Text => println!("{}", response.trim_end_matches(".\r\n")),
gopher::Type::Text => println!(
"{}",
gopher::response_to_string(&response).trim_end_matches(".\r\n")
),
_ => {
return Err(Box::new(io::Error::new(
io::ErrorKind::Other,

@ -8,11 +8,12 @@
use crate::{
config::SharedConfig as Config,
encoding::Encoding,
gopher::{self, Type},
terminal,
ui::{self, Action, Key, View, MAX_COLS, SCROLL_LINES},
};
use std::fmt;
use std::{fmt, str};
/// The Menu holds our Gopher Lines, a list of links, and maintains
/// both where the cursor is on screen and which lines need to be
@ -23,6 +24,8 @@ use std::fmt;
pub struct Menu {
/// Gopher URL
pub url: String,
/// Ref to our global config
config: Config,
/// Lines in the menu. Not all are links. Use the `lines()` iter
/// or `line(N)` or `link(N)` to access one.
spans: Vec<LineSpan>,
@ -33,7 +36,9 @@ pub struct Menu {
/// Size of the longest line, for wrapping purposes
pub longest: usize,
/// Actual Gopher response
pub raw: String,
raw: Vec<u8>,
/// Encoded Gopher response
encoded_response: String,
/// User input on a prompt() line
pub input: String,
/// UI mode. Interactive (Run), Printing, Raw mode...
@ -48,6 +53,8 @@ pub struct Menu {
tor: bool,
/// Size of the screen currently, cols and rows
pub size: (usize, usize),
/// Text Encoding of Response
encoding: Encoding,
/// Wide mode?
wide: bool,
}
@ -217,8 +224,12 @@ impl View for Menu {
self.tor
}
fn encoding(&self) -> Encoding {
self.encoding
}
fn raw(&self) -> &str {
self.raw.as_ref()
str::from_utf8(&self.raw).unwrap_or_default()
}
fn render(&mut self) -> String {
@ -249,19 +260,34 @@ impl View for Menu {
impl Menu {
/// Create a representation of a Gopher Menu from a raw Gopher
/// response and a few options.
pub fn from(url: &str, response: String, config: Config, tls: bool) -> Menu {
Menu {
pub fn from(url: &str, response: Vec<u8>, config: Config, tls: bool) -> Menu {
let encoding = config.read().unwrap().encoding;
let mut menu = Menu {
url: url.into(),
encoded_response: encoding.encode(&response).into(),
raw: response,
tls,
tor: config.read().unwrap().tor,
wide: config.read().unwrap().wide,
mode: config.read().unwrap().mode,
..parse(url, response)
}
encoding,
config: config.clone(),
input: String::new(),
spans: vec![],
links: vec![],
link: 0,
longest: 0,
scroll: 0,
searching: false,
size: (0, 0),
};
menu.parse();
menu
}
/// Lines in this menu. Main iterator for getting Line with text.
pub fn lines(&self) -> LinesIter {
LinesIter::new(&self.spans, &self.raw)
LinesIter::new(&self.spans, self.raw())
}
/// Get a single Line in this menu by index.
@ -269,7 +295,7 @@ impl Menu {
if idx >= self.spans.len() {
None
} else {
Some(Line::new(&self.spans[idx], &self.raw))
Some(Line::new(&self.spans[idx], self.raw()))
}
}
@ -343,6 +369,59 @@ impl Menu {
Some((x as u16, y as u16))
}
/// Parse our `encoded_response` and cache information, like the
/// number of links.
fn parse(&mut self) {
let mut spans = vec![];
let mut links = vec![];
let mut longest = 0;
let mut start = 0;
for line in self.encoded_response.split_terminator('\n') {
// Check for Gopher's weird "end of response" message.
if line == ".\r" || line == "." {
break;
}
if line == "" {
start += 1;
continue;
}
if let Some(mut span) = parse_line(start, &self.encoded_response) {
if span.text_len() > longest {
longest = span.text_len();
}
if span.typ.is_link() {
span.link = links.len();
links.push(spans.len());
}
spans.push(span);
}
start += line.len() + 1;
}
self.spans = spans;
self.links = links;
self.longest = longest;
self.link = 0;
self.scroll = 0;
}
/// Toggle between our two encodings.
fn toggle_encoding(&mut self) -> Action {
if matches!(self.encoding, Encoding::UTF8) {
self.encoding = Encoding::CP437;
} else {
self.encoding = Encoding::UTF8;
}
self.config.write().unwrap().encoding = self.encoding;
self.encoded_response = self.encoding.encode(&self.raw).into();
self.parse();
Action::Redraw
}
fn render_lines(&mut self) -> String {
let mut out = String::new();
let limit = if self.mode == ui::Mode::Run {
@ -849,6 +928,7 @@ impl Menu {
match key {
Key::Char('\n') => self.action_open(),
Key::Ctrl('e') => self.toggle_encoding(),
Key::Up | Key::Ctrl('p') | Key::Char('p') | Key::Ctrl('k') | Key::Char('k') => {
self.action_up()
}
@ -924,53 +1004,8 @@ impl Menu {
}
/// Parse gopher response into a Menu object.
pub fn parse(url: &str, raw: String) -> Menu {
let mut spans = vec![];
let mut links = vec![];
let mut longest = 0;
let mut start = 0;
for line in raw.split_terminator('\n') {
// Check for Gopher's weird "end of response" message.
if line == ".\r" || line == "." {
break;
}
if line == "" {
start += 1;
continue;
}
if let Some(mut span) = parse_line(start, &raw) {
if span.text_len() > longest {
longest = span.text_len();
}
if span.typ.is_link() {
span.link = links.len();
links.push(spans.len());
}
spans.push(span);
}
start += line.len() + 1;
}
Menu {
url: url.into(),
spans,
links,
longest,
raw,
input: String::new(),
link: 0,
mode: Default::default(),
scroll: 0,
searching: false,
size: (0, 0),
tls: false,
tor: false,
wide: false,
}
pub fn parse(url: &str, raw: Vec<u8>) -> Menu {
Menu::from(url, raw, Config::default(), false)
}
/// Parses a single line from a Gopher menu into a `LineSpan` struct.
@ -1042,7 +1077,7 @@ mod tests {
macro_rules! parse {
($s:expr) => {
parse("test", $s.to_string());
parse("test", $s.as_bytes().to_vec());
};
}

@ -275,12 +275,9 @@ impl UI {
};
let typ = gopher::type_for_url(&url);
match typ {
Type::Menu | Type::Search => Ok(Box::new(Menu::from(
url,
gopher::response_to_string(&res),
self.config.clone(),
tls,
))),
Type::Menu | Type::Search => {
Ok(Box::new(Menu::from(url, res, self.config.clone(), tls)))
}
Type::Text | Type::HTML => Ok(Box::new(Text::from(url, res, self.config.clone(), tls))),
_ => Err(error!("Unsupported Gopher Response: {:?}", typ)),
}
@ -294,7 +291,7 @@ impl UI {
) {
Ok(Box::new(Menu::from(
url,
source,
source.as_bytes().to_vec(),
self.config.clone(),
false,
)))

Loading…
Cancel
Save