aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndy Wortman <ixineeringeverywhere@gmail.com>2017-12-02 21:40:41 -0800
committerAndy Wortman <ixineeringeverywhere@gmail.com>2017-12-02 21:40:41 -0800
commit6ae94473e31c0f7d2c1ca79294f529419f1c536f (patch)
treecea89b2fbcaa44872a7d458a12981080d558daa2
parentf74ece81c99d94c2b5250d7fd7fde0d1df15cbeb (diff)
track dirty bit to know if we should redraw display
also clean up a bunch of TODOs
-rw-r--r--src/commands/fav.rs4
-rw-r--r--src/commands/profile.rs3
-rw-r--r--src/commands/twete.rs4
-rw-r--r--src/display/mod.rs125
-rw-r--r--src/main.rs74
-rw-r--r--src/tw/mod.rs56
-rw-r--r--todo1
7 files changed, 167 insertions, 100 deletions
diff --git a/src/commands/fav.rs b/src/commands/fav.rs
index 5e5f2a2..02ec7dd 100644
--- a/src/commands/fav.rs
+++ b/src/commands/fav.rs
@@ -21,7 +21,7 @@ fn unfav(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, di
let maybe_id = TweetId::parse(line.to_owned());
match maybe_id {
Ok(twid) => {
- if let Some(twete) = tweeter.retrieve_tweet(&twid).map(|x| x.clone()) { // TODO: no clone when this stops taking &mut self
+ if let Some(twete) = tweeter.retrieve_tweet(&twid) {
let result = match tweeter.current_profile() {
Some(user_profile) => queryer.do_api_post(&format!("{}?id={}", UNFAV_TWEET_URL, twete.id), &tweeter.app_key, &user_profile.creds),
None => Err("No logged in user to unfav from".to_owned())
@@ -53,7 +53,7 @@ fn fav(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, disp
match maybe_id {
Ok(twid) => {
// tweeter.to_twitter_tweet_id(twid)...
- if let Some(twete) = tweeter.retrieve_tweet(&twid).map(|x| x.clone()) { // TODO: no clone when this stops taking &mut self
+ if let Some(twete) = tweeter.retrieve_tweet(&twid) {
let result = match tweeter.current_profile() {
Some(user_profile) => queryer.do_api_post(&format!("{}?id={}", FAV_TWEET_URL, twete.id), &tweeter.app_key, &user_profile.creds),
None => Err("No logged in user to fav from".to_owned())
diff --git a/src/commands/profile.rs b/src/commands/profile.rs
index e20859b..f3bfd8d 100644
--- a/src/commands/profile.rs
+++ b/src/commands/profile.rs
@@ -1,8 +1,5 @@
use display::DisplayInfo;
use tw;
-use std;
-use std::collections::HashMap;
-use hyper;
use ::Queryer;
use commands::Command;
diff --git a/src/commands/twete.rs b/src/commands/twete.rs
index ebf3c3d..d727b9e 100644
--- a/src/commands/twete.rs
+++ b/src/commands/twete.rs
@@ -54,7 +54,7 @@ fn twete(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, di
// if it's just "t", enter compose mode.
let text = line.trim().to_owned();
if text.len() == 0 {
- display_info.mode = Some(::display::DisplayMode::Compose(text));
+ display_info.set_mode(Some(::display::DisplayMode::Compose(text)));
} else {
send_twete(text, tweeter, queryer, display_info);
}
@@ -178,7 +178,7 @@ fn rep(line: String, tweeter: &mut tw::TwitterCache, queryer: &mut Queryer, disp
if reply.len() > 0 {
send_reply(full_reply, twid, tweeter, queryer, user_profile.creds, display_info);
} else {
- display_info.mode = Some(::display::DisplayMode::Reply(twid, full_reply));
+ display_info.set_mode(Some(::display::DisplayMode::Reply(twid, full_reply)));
}
} else {
display_info.status(format!("No tweet for id: {:?}", twid));
diff --git a/src/display/mod.rs b/src/display/mod.rs
index c3c4692..04b200f 100644
--- a/src/display/mod.rs
+++ b/src/display/mod.rs
@@ -35,14 +35,15 @@ pub enum Infos {
const COMPOSE_HEIGHT: u16 = 5;
pub struct DisplayInfo {
- pub log_height: u16,
- pub prompt_height: u16,
- pub mode: Option<DisplayMode>,
- pub log_seek: u32,
- pub infos_seek: u32,
- pub log: Vec<String>,
- pub infos: Vec<Infos>,
- pub input_buf: Vec<char>
+ log_height: u16,
+ prompt_height: u16,
+ mode: Option<DisplayMode>,
+ log_seek: u32,
+ infos_seek: u32,
+ log: Vec<String>,
+ infos: Vec<Infos>,
+ input_buf: Vec<char>,
+ dirty: bool
}
impl Default for DisplayInfo {
@@ -55,18 +56,91 @@ impl Default for DisplayInfo {
infos_seek: 0,
log: Vec::new(),
infos: Vec::new(),
- input_buf: Vec::new()
+ input_buf: Vec::new(),
+ dirty: false
}
}
}
impl DisplayInfo {
+ pub fn set_mode(&mut self, new_mode: Option<DisplayMode>) {
+ self.mode = new_mode;
+ self.dirty = true;
+ }
+
+ pub fn get_mode(&self) -> &Option<DisplayMode> {
+ &self.mode
+ }
+
+ pub fn adjust_infos_seek(&mut self, seek_adjust: Option<i32>) {
+ self.infos_seek = match seek_adjust {
+ Some(adjust) => {
+ /*
+ * So this might be weird to read.
+ *
+ * I don't know how to add an i32 in a saturating manner to a u32.
+ *
+ * That's what this does:
+ * if adjust is negative, negate to positive and saturating_sub it
+ * if adjust is positive, we can just saturating_add it
+ */
+ if adjust < 0 {
+ self.infos_seek.saturating_sub(-adjust as u32)
+ } else {
+ self.infos_seek.saturating_add(adjust as u32)
+ }
+ },
+ None => 0
+ };
+ self.dirty = true;
+ }
+
+ pub fn adjust_log_seek(&mut self, seek_adjust: Option<i32>) {
+ self.log_seek = match seek_adjust {
+ Some(adjust) => {
+ /*
+ * So this might be weird to read.
+ *
+ * I don't know how to add an i32 in a saturating manner to a u32.
+ *
+ * That's what this does:
+ * if adjust is negative, negate to positive and saturating_sub it
+ * if adjust is positive, we can just saturating_add it
+ */
+ if adjust < 0 {
+ self.log_seek.saturating_sub(-adjust as u32)
+ } else {
+ self.log_seek.saturating_add(adjust as u32)
+ }
+ },
+ None => 0
+ };
+ self.dirty = true;
+ }
+
pub fn status(&mut self, stat: String) {
self.log.push(stat);
+ self.dirty = true;
}
pub fn recv(&mut self, info: Infos) {
self.infos.push(info);
+ self.dirty = true;
+ }
+
+ pub fn input_buf_push(&mut self, c: char) {
+ self.input_buf.push(c);
+ self.dirty = true;
+ }
+
+ pub fn input_buf_pop(&mut self) {
+ self.input_buf.pop();
+ self.dirty = true;
+ }
+
+ pub fn input_buf_drain(&mut self) -> String {
+ self.dirty = true;
+ self.input_buf.drain(..).collect()
}
pub fn ui_height(&self) -> u16 {
@@ -79,20 +153,6 @@ impl DisplayInfo {
*/
fn into_display_lines(x: Vec<String>, width: u16) -> Vec<String> {
ansi_aware_into_display_lines(x, width)
- /*
- let split_on_newline: Vec<String> = x.into_iter()
- .flat_map(|x| x.split("\n")
- .map(|x| x.to_owned())
- .collect::<Vec<String>>()
- ).collect();
- let wrapped: Vec<String> = split_on_newline.iter()
- .map(|x| x.chars().collect::<Vec<char>>())
- .flat_map(|x| x.chunks(width as usize)
- .map(|x| x.into_iter().collect::<String>())
- .collect::<Vec<String>>())
- .collect();
- wrapped
- */
}
#[derive(Clone)]
@@ -211,7 +271,6 @@ fn ansi_aware_into_display_lines(x: Vec<String>, width: u16) -> Vec<String> {
"".to_owned()
},
Some(AnsiInfo::Esc) => {
- // TODO: flush
ansi_code = None;
format!("{}{}", AnsiInfo::Esc, c)
},
@@ -342,6 +401,9 @@ fn ansi_aware_into_display_lines(x: Vec<String>, width: u16) -> Vec<String> {
pub fn paint(tweeter: &::tw::TwitterCache, display_info: &mut DisplayInfo) -> Result<(), std::io::Error> {
match termion::terminal_size() {
Ok((width, height)) => {
+ if !display_info.dirty {
+ return Ok(());
+ }
// draw input prompt
let mut i = 0;
let log_size = 4;
@@ -405,7 +467,7 @@ pub fn paint(tweeter: &::tw::TwitterCache, display_info: &mut DisplayInfo) -> Re
lines_drawn += 1;
}
h += lines_drawn - 3;
- (cursor_idx as u16 + 3, height as u16 - 5) // TODO: panic on underflow
+ (cursor_idx as u16 + 3, height as u16 - 5) // TODO: this panics on underflow
}
Some(DisplayMode::Reply(twid, msg)) => {
let mut lines: Vec<String> = vec![];
@@ -432,7 +494,7 @@ pub fn paint(tweeter: &::tw::TwitterCache, display_info: &mut DisplayInfo) -> Re
lines_drawn += 1;
}
h += lines_drawn - 3;
- (cursor_idx as u16 + 3, height as u16 - 5) // TODO: panic on underflow
+ (cursor_idx as u16 + 3, height as u16 - 5) // TODO: this panics on underflow
}
};
@@ -522,6 +584,7 @@ pub fn paint(tweeter: &::tw::TwitterCache, display_info: &mut DisplayInfo) -> Re
println!("Can't get term dimensions: {}", e);
}
}
+ display_info.dirty = false;
Ok(())
}
@@ -778,6 +841,14 @@ pub fn render_twete_no_recurse(twete_id: &TweetId, tweeter: &tw::TwitterCache, d
}
}
}
+ /*
+ for elem in tweet.media.iter() {
+ if line.contains(elem.0) {
+ result = result.replace(elem.0, &format!("[{}]", urls_to_include.len()));
+ urls_to_include.push(elem.0);
+ }
+ }
+ */
result
})
.collect();
@@ -792,8 +863,6 @@ pub fn render_twete_no_recurse(twete_id: &TweetId, tweeter: &tw::TwitterCache, d
if expanded.len() < (width - 9) as usize { // "[XX]: " is 6 + some padding space?
text.push(format!("[{}]: {}", i, expanded));
} else {
- // TODO: try to just show domain, THEN fall back to just a link if the
- // domain is too long
text.push(format!("[{}]: {}", i, short_url));
}
}
diff --git a/src/main.rs b/src/main.rs
index 7595ea0..e5c3910 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -260,60 +260,68 @@ fn main() {
fn handle_input(event: termion::event::Event, tweeter: &mut tw::TwitterCache, queryer: &mut ::Queryer, display_info: &mut display::DisplayInfo) {
match event {
Event::Key(Key::Backspace) => {
- match display_info.mode.clone() {
- None => { display_info.input_buf.pop(); },
+ let new_mode = match display_info.get_mode().clone() {
+ None => { display_info.input_buf_pop(); None },
Some(display::DisplayMode::Compose(msg)) => {
let mut newstr = msg.clone();
newstr.pop();
- display_info.mode = Some(display::DisplayMode::Compose(newstr));
+ Some(display::DisplayMode::Compose(newstr))
},
Some(display::DisplayMode::Reply(twid, msg)) => {
let mut newstr = msg.clone();
newstr.pop();
- display_info.mode = Some(display::DisplayMode::Reply(twid, newstr));
+ Some(display::DisplayMode::Reply(twid, newstr))
}
- }
+ };
+ display_info.set_mode(new_mode);
}
// would Shift('\n') but.. that doesn't exist.
// would Ctrl('\n') but.. that doesn't work.
Event::Key(Key::Ctrl('u')) => {
- match display_info.mode.clone() {
- None => display_info.input_buf = vec![],
+ let new_mode = match display_info.get_mode().clone() {
+ None => { display_info.input_buf_drain(); None},
Some(display::DisplayMode::Compose(msg)) => {
- // TODO: clear only one line?
- display_info.mode = Some(display::DisplayMode::Compose("".to_owned()));
+ Some(display::DisplayMode::Compose("".to_owned()))
}
Some(display::DisplayMode::Reply(twid, msg)) => {
- display_info.mode = Some(display::DisplayMode::Reply(twid, "".to_owned()));
+ Some(display::DisplayMode::Reply(twid, "".to_owned()))
}
- }
+ };
+ display_info.set_mode(new_mode);
}
Event::Key(Key::Ctrl('n')) => {
- match display_info.mode.clone() {
+ let new_mode = match display_info.get_mode().clone() {
Some(display::DisplayMode::Compose(msg)) => {
- display_info.mode = Some(display::DisplayMode::Compose(format!("{}{}", msg, "\n")));
+ Some(display::DisplayMode::Compose(format!("{}{}", msg, "\n")))
}
- _ => {}
- }
+ mode @ _ => mode
+ };
+ display_info.set_mode(new_mode);
}
- // TODO: ctrl+u, ctrl+w
+ // TODO: ctrl+w
Event::Key(Key::Char(x)) => {
- match display_info.mode.clone() {
+ // Unlike other cases where we care about DisplayMode here,
+ // we can't just set the display mode in this function..
+ //
+ // commands can change display mode, but might not, so just
+ // let them do their thing and only explicitly set display
+ // mode when we know we ought to
+ match display_info.get_mode().clone() {
None => {
if x == '\n' {
- let line = display_info.input_buf.drain(..).collect::<String>();
+ let line = display_info.input_buf_drain();
tweeter.handle_user_input(line.into_bytes(), queryer, display_info);
} else {
- display_info.input_buf.push(x);
+ display_info.input_buf_push(x);
}
}
Some(display::DisplayMode::Compose(msg)) => {
if x == '\n' {
// TODO: move this somewhere better.
::commands::twete::send_twete(msg, tweeter, queryer, display_info);
- display_info.mode = None;
+ display_info.set_mode(None)
} else {
- display_info.mode = Some(display::DisplayMode::Compose(format!("{}{}", msg, x)));
+ display_info.set_mode(Some(display::DisplayMode::Compose(format!("{}{}", msg, x))))
}
}
Some(display::DisplayMode::Reply(twid, msg)) => {
@@ -327,27 +335,27 @@ fn handle_input(event: termion::event::Event, tweeter: &mut tw::TwitterCache, qu
display_info.status("Cannot reply when not logged in".to_owned());
}
}
- display_info.mode = None;
+ display_info.set_mode(None)
} else {
- display_info.mode = Some(display::DisplayMode::Reply(twid, format!("{}{}", msg, x)));
+ display_info.set_mode(Some(display::DisplayMode::Reply(twid, format!("{}{}", msg, x))))
}
}
- }
+ };
},
Event::Key(Key::PageUp) => {
- display_info.infos_seek = display_info.infos_seek.saturating_add(1);
+ display_info.adjust_infos_seek(Some(1));
}
Event::Key(Key::PageDown) => {
- display_info.infos_seek = display_info.infos_seek.saturating_sub(1);
+ display_info.adjust_infos_seek(Some(-1));
}
Event::Key(Key::Home) => {
- display_info.log_seek = display_info.log_seek.saturating_add(1);
+ display_info.adjust_log_seek(Some(1));
}
Event::Key(Key::End) => {
- display_info.log_seek = display_info.log_seek.saturating_sub(1);
+ display_info.adjust_log_seek(Some(-1));
}
Event::Key(Key::Esc) => {
- display_info.mode = None;
+ display_info.set_mode(None);
}
Event::Key(_) => { }
Event::Mouse(_) => { }
@@ -356,9 +364,7 @@ fn handle_input(event: termion::event::Event, tweeter: &mut tw::TwitterCache, qu
}
fn handle_twitter_line(conn_id: u8, line: Vec<u8>, mut tweeter: &mut tw::TwitterCache, mut queryer: &mut ::Queryer, display_info: &mut display::DisplayInfo) {
- let jsonstr = std::str::from_utf8(&line).unwrap().trim();
- /* TODO: replace from_str with from_slice? */
- match serde_json::from_str(&jsonstr) {
+ match serde_json::from_slice(&line) {
Ok(json) => {
tw::handle_message(conn_id, json, &mut tweeter, display_info, &mut queryer);
if tweeter.needs_save && tweeter.caching_permitted {
@@ -366,7 +372,7 @@ fn handle_twitter_line(conn_id: u8, line: Vec<u8>, mut tweeter: &mut tw::Twitter
}
},
Err(e) =>
- display_info.status(format!("Error reading twitter line: {}", jsonstr))
+ display_info.status(format!("Error reading twitter line: {:?}", std::str::from_utf8(&line)))
}
}
@@ -445,7 +451,7 @@ fn do_ui(
for command in commands::COMMANDS {
help_lines.push(format!("{}{: <width$} {}", command.keyword, command.param_str, command.help_str, width=(35 - command.keyword.len())));
}
- display_info.infos.push(display::Infos::Text(help_lines));
+ display_info.recv(display::Infos::Text(help_lines));
display::paint(tweeter, display_info).unwrap();
tweeter.state = tw::AppState::View;
}
diff --git a/src/tw/mod.rs b/src/tw/mod.rs
index 6dbc259..ffa2a56 100644
--- a/src/tw/mod.rs
+++ b/src/tw/mod.rs
@@ -252,7 +252,6 @@ mod tests {
impl TweetId {
pub fn parse(id_str: String) -> Result<TweetId, String> {
- // TODO: figure out how to return a Result<TweetId, <.. the result types ..>>
if id_str.starts_with("twitter:") {
Ok(TweetId::Twitter(id_str.chars().skip("twitter:".len()).collect()))
} else if id_str.starts_with(":") {
@@ -429,49 +428,37 @@ impl TwitterProfile {
}
/*
* Returns: "did this change?"
- *
- * but currently pessimistically always returns true right now
- * TODO: check if this should return false! (that's probably a cache bug though.)
*/
pub fn add_following(&mut self, user_id: &String) -> bool {
- self.following.insert(user_id.to_owned());
- self.following_history.insert(user_id.to_owned(), ("following".to_string(), Utc::now().timestamp()));
- true
+ let mut changed = self.following.insert(user_id.to_owned());
+ changed |= self.following_history.insert(user_id.to_owned(), ("following".to_string(), Utc::now().timestamp())).is_none();
+ changed
}
/*
* Returns: "did this change?"
- *
- * but currently pessimistically always returns true right now
- * TODO: check if this should return false! (that's probably a cache bug though.)
*/
pub fn remove_following(&mut self, user_id: &String) -> bool {
- self.following.remove(user_id);
- self.following_history.insert(user_id.to_owned(), ("unfollowing".to_string(), Utc::now().timestamp()));
- true
+ let mut changed = self.following.remove(user_id);
+ changed |= self.following_history.insert(user_id.to_owned(), ("unfollowing".to_string(), Utc::now().timestamp())).is_some();
+ changed
}
/*
* Returns: "did this change?"
- *
- * but currently pessimistically always returns true right now
- * TODO: check if this should return false! (that's probably a cache bug though.)
*/
pub fn add_follower(&mut self, user_id: &String) -> bool {
- self.followers.insert(user_id.to_owned());
- self.lost_followers.remove(user_id);
- self.follower_history.insert(user_id.to_owned(), ("follow".to_string(), Utc::now().timestamp()));
- true
+ let mut changed = self.followers.insert(user_id.to_owned());
+ changed |= self.lost_followers.remove(user_id);
+ changed |= self.follower_history.insert(user_id.to_owned(), ("follow".to_string(), Utc::now().timestamp())).is_none();
+ changed
}
/*
* Returns: "did this change?"
- *
- * but currently pessimistically always returns true right now
- * TODO: check if this should return false! (that's probably a cache bug though.)
*/
pub fn remove_follower(&mut self, user_id: &String) -> bool {
- self.followers.remove(user_id);
- self.lost_followers.insert(user_id.to_owned());
- self.follower_history.insert(user_id.to_owned(), ("unfollow".to_string(), Utc::now().timestamp()));
- true
+ let mut changed = self.followers.remove(user_id);
+ changed |= self.lost_followers.insert(user_id.to_owned());
+ changed |= self.follower_history.insert(user_id.to_owned(), ("unfollow".to_string(), Utc::now().timestamp())).is_some();
+ changed
}
}
@@ -851,7 +838,9 @@ fn handle_twitter_event(
tweeter.cache_api_event(conn_id, structure.clone(), queryer, display_info);
match events::Event::from_json(structure) {
Ok(event) => {
- display_info.recv(display::Infos::Event(event));
+ if !tweeter.event_muted(&event) {
+ display_info.recv(display::Infos::Event(event));
+ }
},
Err(e) => {
display_info.status(format!("Unknown twitter json: {:?}", e));
@@ -882,10 +871,15 @@ fn handle_twitter_twete(
display_info: &mut DisplayInfo,
_queryer: &mut ::Queryer) {
//display_info.recv(display::Infos::Text(vec![format!("{:?}", structure)]));
- let twete_id = structure["id_str"].as_str().unwrap().to_string();
+ let twete_id = TweetId::Twitter(
+ structure["id_str"].as_str().unwrap().to_string()
+ );
tweeter.cache_api_tweet(serde_json::Value::Object(structure));
- display_info.recv(display::Infos::Tweet(TweetId::Twitter(twete_id)));
- // display::render_twete(&twete_id, tweeter);
+ if let Some(twete) = tweeter.retrieve_tweet(&twete_id) {
+ if !tweeter.tweet_muted(twete) {
+ display_info.recv(display::Infos::Tweet(twete_id));
+ }
+ }
}
fn handle_twitter_dm(
diff --git a/todo b/todo
index 764b787..acc1f20 100644
--- a/todo
+++ b/todo
@@ -45,3 +45,4 @@ mute
rt_fav/rt_rt
[ ] do not duplicate notify when you fav or rt a retweet
+handle ctrl+c gracefully (save cache and don't trash terminal state lol)