From aba55fd071c6a8c7ef19e170dc54cd5a8a13854e Mon Sep 17 00:00:00 2001
From: Tom Smeding <tom.smeding@gmail.com>
Date: Mon, 14 Sep 2020 23:02:00 +0200
Subject: rustclient: First prototype

---
 rust/src/editor.rs |  81 +++++++++++++++++++++++++++++++
 rust/src/main.rs   | 138 ++++++++++++++++++++++++++++++++++++++++++-----------
 2 files changed, 190 insertions(+), 29 deletions(-)
 create mode 100644 rust/src/editor.rs

(limited to 'rust/src')

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;
         }
     }
-- 
cgit v1.2.3-70-g09d2