diff options
Diffstat (limited to 'rust/src')
| -rw-r--r-- | rust/src/editor.rs | 81 | ||||
| -rw-r--r-- | rust/src/main.rs | 138 | 
2 files changed, 190 insertions, 29 deletions
diff --git a/rust/src/editor.rs b/rust/src/editor.rs new file mode 100644 index 0000000..7d83904 --- /dev/null +++ b/rust/src/editor.rs @@ -0,0 +1,81 @@ +use std::io::Write; +use termion::event::Key; + +#[derive(Debug, Default)] +pub struct Editor { +    s: String, +    left: usize, +    cursor: usize, +    wid: usize, +} + +impl Editor { +    pub fn keypress(&mut self, key: Key) -> Option<String> { +        let mut ret = None; + +        match key { +            Key::Char('\n') => { +                ret = Some(String::new()); +                std::mem::swap(ret.as_mut().unwrap(), &mut self.s); +                self.cursor = 0; +            } + +            Key::Char(c) if !c.is_control() => { +                self.s.insert(self.cursor, c); +                self.cursor += 1; +            } + +            Key::Backspace if self.cursor > 0 => { +                self.cursor -= 1; +                self.s.remove(self.cursor); +            } + +            Key::Delete if self.s.len() > self.cursor => { +                self.s.remove(self.cursor); +            } + +            Key::Home | Key::Ctrl('a') => { +                self.cursor = 0; +            } + +            Key::End | Key::Ctrl('e') => { +                self.cursor = self.s.len(); +            } + +            Key::Left | Key::Ctrl('b') if self.cursor > 0 => { +                self.cursor -= 1; +            } + +            Key::Right | Key::Ctrl('f') if self.cursor < self.s.len() => { +                self.cursor += 1; +            } + +            Key::Ctrl('u') => { +                self.s.replace_range(0..self.cursor, ""); +                self.cursor = 0; +            } + +            _ => { +                print!("\x07"); +                std::io::stdout().flush().unwrap(); +            } +        }; + +        self.fix_left(); +        ret +    } + +    pub fn set_wid(&mut self, wid: usize) { +        self.wid = wid; +        self.fix_left(); +    } + +    pub fn displayed(&self) -> (&str, usize) { +        let endidx = (self.left + self.wid).min(self.s.len()); +        (&self.s[self.left .. endidx], self.cursor - self.left) +    } + +    fn fix_left(&mut self) { +        self.left = self.left.min(self.cursor).max(self.cursor + 1 - self.wid.min(self.cursor + 1)); +    } +} diff --git a/rust/src/main.rs b/rust/src/main.rs index 2d08656..d2377c7 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -23,7 +23,10 @@ use tomsg_rs::Reply;  use tomsg_rs::Message;  use tomsg_rs::Id;  use unicode_width::UnicodeWidthChar; +use crate::editor::Editor; +use crate::error::IntoIOError; +mod editor;  mod error;  static DEBUG_FILE: Lazy<Mutex<File>> = Lazy::new(|| { @@ -82,7 +85,17 @@ fn wrap_text(s: &str, maxwid: u16) -> Vec<String> {      lines  } +async fn send_command(conn: &Connection, cmd: Command) -> io::Result<Reply> { +    match conn.send_command(cmd).await { +        Ok(Ok(reply)) => Ok(reply), +        Ok(Err(connection::CloseReason::EOF)) => Err("EOF on server connection".ioerr()), +        Ok(Err(connection::CloseReason::Err(err))) => Err(format!("Error from server: {}", err).ioerr()), +        Err(err) => Err(err), +    } +} +  type Stdout = MouseTerminal<AlternateScreen<RawTerminal<io::Stdout>>>; +// type Stdout = MouseTerminal<io::Stdout>;  #[derive(Debug, Copy, Clone)]  struct Layout { @@ -108,6 +121,7 @@ impl Layout {  struct App {      conn: Connection, +    username: Word,      state: AsyncMutex<State>,  } @@ -126,6 +140,7 @@ struct RoomData {      members: Vec<Word>,      history: Vec<HItem>,      maxnicklen: usize, +    editor: Editor,  }  #[derive(Debug)] @@ -135,7 +150,7 @@ enum HItem {  }  impl App { -    fn new(conn: Connection, stdout: Stdout) -> Self { +    fn new(conn: Connection, stdout: Stdout, username: Word) -> Self {          let termsize = (0, 0);          let state = State {              stdout, @@ -148,13 +163,14 @@ impl App {          };          App {              conn, +            username,              state: AsyncMutex::new(state)          }      }      async fn fetch_rooms_list(self: &Arc<Self>) -> io::Result<()> {          debug!("fetch_rooms_list"); -        if let Ok(Reply::List(rooms)) = self.conn.send_command(Command::ListRooms).await? { +        if let Reply::List(rooms) = send_command(&self.conn, Command::ListRooms).await? {              debug!("fetched room list: {:?}", rooms);              let newrooms = rooms.iter().collect::<HashSet<_>>(); @@ -198,8 +214,8 @@ impl App {          let room2 = room.clone();          task::spawn(async move {              if let Ok(Reply::List(members)) = -                    app.conn.send_command(Command::ListMembers { room_name: room2.clone() }) -                        .await.unwrap() { +                    send_command(&app.conn, Command::ListMembers { room_name: room2.clone() }) +                        .await {                  app.put_members(&room2, members).await.unwrap();              }          }); @@ -207,8 +223,8 @@ impl App {          let room2 = room.clone();          task::spawn(async move {              if let Ok(Reply::History(hist)) = -                    app.conn.send_command(Command::History { room_name: room2.clone(), count: 20 }) -                        .await.unwrap() { +                    send_command(&app.conn, Command::History { room_name: room2.clone(), count: 20 }) +                        .await {                  app.put_history(&room2, hist).await.unwrap();              }          }); @@ -245,7 +261,7 @@ impl App {                  let app = self.clone();                  task::spawn(async move {                      if let Ok(Reply::Message(msg2)) = -                            app.conn.send_command(Command::GetMessage(replyid)).await.unwrap() { +                            send_command(&app.conn, Command::GetMessage(replyid)).await {                          app.state.lock().await.put_message(msg2).unwrap();                      }                  }); @@ -278,7 +294,7 @@ impl App {                  let app = self.clone();                  task::spawn(async move {                      if let Ok(Reply::Message(msg2)) = -                            app.conn.send_command(Command::GetMessage(replyid)).await.unwrap() { +                            send_command(&app.conn, Command::GetMessage(replyid)).await {                          app.state.lock().await.put_message(msg2).unwrap();                      }                  }); @@ -323,15 +339,76 @@ impl App {      }      // Returns whether application should quit -    async fn on_key(self: &Arc<Self>, key: Key) -> bool { +    async fn on_key(self: &Arc<Self>, key: Key) -> io::Result<bool> {          match key { -            Key::Ctrl('c') => { return true; } -            Key::Ctrl('l') => self.full_redraw().await.unwrap(), -            Key::F(5) => self.switch_buffer(-1).await.unwrap(), -            Key::F(6) => self.switch_buffer(1).await.unwrap(), -            _ => debug!("{:?}", key) +            Key::Ctrl('c') => { return Ok(true); } +            Key::Ctrl('l') => self.full_redraw().await?, +            Key::F(5) => self.switch_buffer(-1).await?, +            Key::F(6) => self.switch_buffer(1).await?, +            _ => { +                let submitted = { +                    let mut state = self.state.lock().await; +                    let state = &mut *state; +                    let data = state.rooms.get_mut(&state.currentroom).unwrap(); +                    data.editor.keypress(key) +                }; +                if let Some(submitted) = submitted { +                    if self.handle_input(&submitted).await? { +                        return Ok(true); +                    } +                } +                self.full_redraw().await?; +            } +        } +        Ok(false) +    } + +    // Returns whether application should quit +    async fn handle_input(self: &Arc<Self>, line: &str) -> io::Result<bool> { +        let parsed = if line.starts_with("//") { +            Err(&line[1..]) +        } else if line.starts_with("/") { +            match line.find(' ') { +                Some(idx) => Ok((&line[1..idx], &line[idx+1..])), +                None => Ok((&line[1..], "")) +            } +        } else { +            Err(line) +        }; + +        match parsed { +            Err(msg) => { +                let msg = msg.to_string(); +                let app = self.clone(); +                task::spawn(async move { +                    let room = app.state.lock().await.currentroom.clone(); +                    let line = Line::try_from(msg).unwrap(); +                    if let Ok(Reply::Number(id)) = send_command(&app.conn, Command::Send { +                        room_name: room.clone(), reply_on: None, message: line.clone() +                    }).await { +                        app.add_message(Message { +                            id: Id::try_from(id).unwrap(), +                            reply_on: None, +                            roomname: room, +                            username: app.username.clone(), +                            timestamp: std::time::SystemTime::now(), +                            message: line, +                        }).await.unwrap(); +                    } +                }); +            } + +            Ok(("quit", _)) | Ok(("exit", _)) => { +                return Ok(true); +            } + +            Ok((cmd, _)) => { +                let room = self.state.lock().await.currentroom.clone(); +                self.append_history_item(&room, HItem::Service(format!("Unknown command: '{}'", cmd))).await?; +            }          } -        false + +        Ok(false)      }      async fn full_redraw(self: &Arc<Self>) -> io::Result<()> { @@ -358,7 +435,7 @@ impl State {          }          if self.currentroom.as_str().len() > 0 { -            let data = self.rooms.get(&self.currentroom).unwrap(); +            let data = self.rooms.get_mut(&self.currentroom).unwrap();              for (i, user) in data.members.iter().enumerate() {                  print!("{}", cursor::Goto(self.layout.nlsepx + 2, u16::try_from(i).unwrap() + 1)); @@ -395,9 +472,16 @@ impl State {                  }                  if done { break; }              } -        } -        print!("{}", cursor::Goto(self.layout.rlsepx + 2, self.termsize.1)); +            data.editor.set_wid(usize::from(self.termsize.0 - self.layout.rlsepx - 1)); + +            let (editor_str, editor_cursor) = data.editor.displayed(); +            let editor_cursor = u16::try_from(editor_cursor).unwrap(); +            print!("{}{}{}", +                   cursor::Goto(self.layout.rlsepx + 2, self.termsize.1), +                   editor_str, +                   cursor::Goto(self.layout.rlsepx + 2 + editor_cursor, self.termsize.1)); +        }          self.stdout.flush()      } @@ -439,13 +523,10 @@ async fn async_main() -> io::Result<()> {      let user = Word::try_from(String::from("tom")).unwrap();      let pass = Line::try_from(String::from("kaas")).unwrap(); -    match conn.send_command(Command::Register { username: user.clone(), password: pass.clone() }).await? { -        Ok(Reply::Ok) => {}, -        _ => {}, -    } +    send_command(&conn, Command::Register { username: user.clone(), password: pass.clone() }).await?; -    match conn.send_command(Command::Login { username: user, password: pass }).await? { -        Ok(Reply::Ok) => {}, +    match send_command(&conn, Command::Login { username: user.clone(), password: pass }).await? { +        Reply::Ok => {},          _ => {              eprintln!("Failed to login!");              return Ok(()); @@ -454,11 +535,10 @@ async fn async_main() -> io::Result<()> {      debug!("initializing"); -    let stdout = io::stdout().into_raw_mode()?; -    let stdout = AlternateScreen::from(stdout); -    let stdout = MouseTerminal::from(stdout); +    let stdout = MouseTerminal::from(AlternateScreen::from(io::stdout().into_raw_mode()?)); +    // let stdout = MouseTerminal::from(io::stdout()); -    let app = Arc::new(App::new(conn, stdout)); +    let app = Arc::new(App::new(conn, stdout, user));      task::spawn(pushchan_thread(pushchan, app.clone())); @@ -470,7 +550,7 @@ async fn async_main() -> io::Result<()> {      });      for key in io::stdin().keys() { -        if app.on_key(key?).await { +        if app.on_key(key?).await? {              break;          }      }  | 
