Action::Prompt vs ui::prompt()

pull/6/head
dvkt 4 years ago
parent b007c6587e
commit ae33aa2252

@ -51,6 +51,12 @@ Just unzip/untar the `phetch` program into your $PATH and get going!
## todo
- [ ] activate search mode with / or i
- [ ] de-activate search mode with ESC
- [ ] ENTER on non-match is an error
- [ ] show "SEARCH MODE" indicator in bottom right?
- [ ] update help
- [ ] add credits page
- [ ] telnet: gopher://bitreich.org/1/lawn/bbs
## bugs

@ -1,6 +1,5 @@
use crate::gopher;
use crate::gopher::Type;
use crate::ui;
use crate::ui::{Action, Key, View, MAX_COLS, SCROLL_LINES};
use std::fmt;
use std::io::stdout;
@ -468,14 +467,16 @@ impl Menu {
let (typ, _, _, _) = gopher::parse_url(&url);
match typ {
Type::Search => {
if let Some(query) = ui::prompt(&format!("{}> ", line.name)) {
Action::Open(
format!("{}> {}", line.name, query),
format!("{}?{}", url, query),
)
} else {
Action::None
}
let prompt = format!("{}> ", line.name);
Action::Prompt(
prompt.clone(),
Box::new(move |query| {
Action::Open(
format!("{}{}", prompt, query),
format!("{}?{}", url, query),
)
}),
)
}
Type::Error => Action::Error(line.name.to_string()),
Type::Telnet => Action::Error("Telnet support coming soon".into()),

@ -94,7 +94,7 @@ impl UI {
// non-gopher URL
if url.contains("://") && !url.starts_with("gopher://") {
self.dirty = true;
return if confirm(&format!("Open external URL? {}", url)) {
return if self.confirm(&format!("Open external URL? {}", url)) {
open_external(url)
} else {
Ok(())
@ -105,7 +105,7 @@ impl UI {
let (typ, _, _, _) = gopher::parse_url(url);
if typ.is_download() {
self.dirty = true;
return if confirm(&format!("Download {}?", url)) {
return if self.confirm(&format!("Download {}?", url)) {
self.download(url)
} else {
Ok(())
@ -262,6 +262,90 @@ impl UI {
}
}
// Ask user to confirm action with ENTER or Y.
fn confirm(&self, question: &str) -> bool {
let rows = self.rows();
print!(
"{}{}{}{} [Y/n]: {}",
color::Fg(color::Reset),
termion::cursor::Goto(1, rows),
termion::clear::CurrentLine,
question,
termion::cursor::Show,
);
stdout().flush();
if let Some(Ok(key)) = stdin().keys().next() {
match key {
Key::Char('\n') => true,
Key::Char('y') | Key::Char('Y') => true,
_ => false,
}
} else {
false
}
}
// Prompt user for input and return what was entered, if anything.
fn prompt(&self, prompt: &str) -> Option<String> {
let rows = self.rows();
print!(
"{}{}{}{}{}",
color::Fg(color::Reset),
termion::cursor::Goto(1, rows),
termion::clear::CurrentLine,
prompt,
termion::cursor::Show,
);
stdout().flush();
let mut input = String::new();
for k in stdin().keys() {
if let Ok(key) = k {
match key {
Key::Char('\n') => {
print!("{}{}", termion::clear::CurrentLine, termion::cursor::Hide);
stdout().flush();
return Some(input);
}
Key::Char(c) => input.push(c),
Key::Esc | Key::Ctrl('c') => {
if input.is_empty() {
print!("{}{}", termion::clear::CurrentLine, termion::cursor::Hide);
stdout().flush();
return None;
} else {
input.clear();
}
}
Key::Backspace | Key::Delete => {
input.pop();
}
_ => {}
}
} else {
break;
}
print!(
"{}{}{}{}",
termion::cursor::Goto(1, rows),
termion::clear::CurrentLine,
prompt,
input,
);
stdout().flush();
}
if !input.is_empty() {
Some(input)
} else {
None
}
}
fn process_page_input(&mut self) -> Action {
if let Some(page) = self.views.get_mut(self.focused) {
if let Ok(key) = stdin()
@ -297,6 +381,11 @@ impl UI {
Action::Error(e) => return Err(error!(e)),
Action::Redraw => self.dirty = true,
Action::Open(title, url) => self.open(&title, &url)?,
Action::Prompt(query, fun) => {
if let Some(response) = self.prompt(&query) {
self.process_action(fun(response));
}
}
Action::Keypress(Key::Left) | Action::Keypress(Key::Backspace) => {
if self.focused > 0 {
self.dirty = true;
@ -319,7 +408,7 @@ impl UI {
}
}
Action::Keypress(Key::Ctrl('g')) => {
if let Some(url) = prompt("Go to URL: ") {
if let Some(url) = self.prompt("Go to URL: ") {
if !url.contains("://") && !url.starts_with("gopher://") {
self.open(&url, &format!("gopher://{}", url))?;
} else {
@ -412,87 +501,3 @@ fn open_external(url: &str) -> Result<()> {
))
}
}
/// Ask user to confirm action with ENTER or Y.
pub fn confirm(question: &str) -> bool {
let (_cols, rows) = terminal_size().unwrap();
print!(
"{}{}{}{} [Y/n]: {}",
color::Fg(color::Reset),
termion::cursor::Goto(1, rows),
termion::clear::CurrentLine,
question,
termion::cursor::Show,
);
stdout().flush();
if let Some(Ok(key)) = stdin().keys().next() {
match key {
Key::Char('\n') => true,
Key::Char('y') | Key::Char('Y') => true,
_ => false,
}
} else {
false
}
}
/// Prompt user for input and return what was entered, if anything.
pub fn prompt(prompt: &str) -> Option<String> {
let (_cols, rows) = terminal_size().unwrap();
print!(
"{}{}{}{}{}",
color::Fg(color::Reset),
termion::cursor::Goto(1, rows),
termion::clear::CurrentLine,
prompt,
termion::cursor::Show,
);
stdout().flush();
let mut input = String::new();
for k in stdin().keys() {
if let Ok(key) = k {
match key {
Key::Char('\n') => {
print!("{}{}", termion::clear::CurrentLine, termion::cursor::Hide);
stdout().flush();
return Some(input);
}
Key::Char(c) => input.push(c),
Key::Esc | Key::Ctrl('c') => {
if input.is_empty() {
print!("{}{}", termion::clear::CurrentLine, termion::cursor::Hide);
stdout().flush();
return None;
} else {
input.clear();
}
}
Key::Backspace | Key::Delete => {
input.pop();
}
_ => {}
}
} else {
break;
}
print!(
"{}{}{}{}",
termion::cursor::Goto(1, rows),
termion::clear::CurrentLine,
prompt,
input,
);
stdout().flush();
}
if !input.is_empty() {
Some(input)
} else {
None
}
}

@ -1,10 +1,10 @@
use crate::ui::Key;
#[derive(Debug)]
pub enum Action {
None, // do nothing
Open(String, String), // open(title, url)
Keypress(Key), // unknown keypress
Redraw, // redraw everything
Error(String), // error message
None, // do nothing
Open(String, String), // open(title, url)
Keypress(Key), // unknown keypress
Redraw, // redraw everything
Prompt(String, Box<dyn FnOnce(String) -> Action>), // query string, callback on success
Error(String), // error message
}

Loading…
Cancel
Save