diff options
Diffstat (limited to 'src/bin/client/repl.rs')
| -rw-r--r-- | src/bin/client/repl.rs | 215 |
1 files changed, 160 insertions, 55 deletions
diff --git a/src/bin/client/repl.rs b/src/bin/client/repl.rs index d38c54d..1512091 100644 --- a/src/bin/client/repl.rs +++ b/src/bin/client/repl.rs | |||
| @@ -1,15 +1,29 @@ | |||
| 1 | use std::io::{BufRead, BufReader, Write, stdin, stdout}; | 1 | use std::io::{BufRead, BufReader, Write, stdout}; |
| 2 | use std::net::TcpStream; | 2 | use std::net::TcpStream; |
| 3 | use rusqlite::Connection; | 3 | use rusqlite::Connection; |
| 4 | use std::time::Instant; | ||
| 4 | 5 | ||
| 5 | use crate::connection; | 6 | use crate::connection; |
| 6 | use crate::database; | 7 | use crate::database; |
| 7 | 8 | ||
| 9 | use crossterm::{event::{read, Event, KeyCode, KeyModifiers}, terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType}, cursor::{MoveToColumn, Show}, execute}; | ||
| 10 | |||
| 11 | macro_rules! println { | ||
| 12 | () => { | ||
| 13 | print!("\r\n") | ||
| 14 | }; | ||
| 15 | ($($arg:tt)*) => { | ||
| 16 | print!("\r{}\n", format_args!($($arg)*)) | ||
| 17 | }; | ||
| 18 | } | ||
| 19 | |||
| 8 | pub struct ClientState { | 20 | pub struct ClientState { |
| 9 | pub stream: Option<TcpStream>, | 21 | pub stream: Option<TcpStream>, |
| 10 | pub conn: Connection, | 22 | pub conn: Connection, |
| 11 | pub addr: Option<String>, | 23 | pub addr: Option<String>, |
| 12 | pub default_port: u16, | 24 | pub default_port: u16, |
| 25 | pub history: Vec<String>, | ||
| 26 | pub history_index: usize, | ||
| 13 | } | 27 | } |
| 14 | 28 | ||
| 15 | impl ClientState { | 29 | impl ClientState { |
| @@ -26,6 +40,8 @@ impl ClientState { | |||
| 26 | conn, | 40 | conn, |
| 27 | addr, | 41 | addr, |
| 28 | default_port, | 42 | default_port, |
| 43 | history: vec![], | ||
| 44 | history_index: 0, | ||
| 29 | } | 45 | } |
| 30 | } | 46 | } |
| 31 | 47 | ||
| @@ -41,68 +57,157 @@ impl ClientState { | |||
| 41 | } | 57 | } |
| 42 | 58 | ||
| 43 | pub fn run(&mut self) { | 59 | pub fn run(&mut self) { |
| 44 | loop { | 60 | enable_raw_mode().unwrap(); |
| 45 | print!("> "); | 61 | 'outer: loop { |
| 46 | stdout().flush().unwrap(); | 62 | let mut current_input = String::new(); |
| 47 | let mut line = String::new(); | 63 | self.history_index = self.history.len(); |
| 48 | stdin().read_line(&mut line).unwrap(); | 64 | redraw(""); |
| 49 | let line = line.trim(); | 65 | loop { |
| 50 | if line.is_empty() { | 66 | match read().unwrap() { |
| 51 | continue; | 67 | Event::Key(key) => match key.code { |
| 52 | } | 68 | KeyCode::Up => { |
| 53 | let parts: Vec<&str> = line.splitn(2, ' ').collect(); | 69 | if self.history_index > 0 { |
| 54 | match parts[0] { | 70 | self.history_index -= 1; |
| 55 | "connect" => { | 71 | current_input = self.history[self.history_index].clone(); |
| 56 | let addr_str = if parts.len() < 2 { | 72 | redraw(¤t_input); |
| 57 | if let Some(h) = &self.addr { | 73 | } |
| 58 | h.clone() | ||
| 59 | } else { | ||
| 60 | println!("No host specified and no previous connection. Usage: connect <ip>:<port>"); | ||
| 61 | continue; | ||
| 62 | } | 74 | } |
| 63 | } else { | 75 | KeyCode::Down => { |
| 64 | let mut a = parts[1].to_string(); | 76 | if self.history_index < self.history.len() { |
| 65 | if !a.contains(':') { | 77 | self.history_index += 1; |
| 66 | a = format!("{}:{}", a, self.default_port); | 78 | current_input = if self.history_index == self.history.len() { "".to_string() } else { self.history[self.history_index].clone() }; |
| 79 | redraw(¤t_input); | ||
| 80 | } | ||
| 67 | } | 81 | } |
| 68 | a | 82 | KeyCode::Enter => { |
| 69 | }; | 83 | let line = current_input.trim().to_string(); |
| 70 | self.connect_to(&addr_str); | 84 | if !line.is_empty() { |
| 71 | } | 85 | self.history.push(line.clone()); |
| 72 | "send" => { | 86 | } |
| 73 | if self.stream.is_none() { | 87 | // Process command |
| 74 | println!("Not connected. Use 'connect <ip>:<port>' first."); | 88 | let parts: Vec<&str> = line.splitn(2, ' ').collect(); |
| 75 | continue; | 89 | match parts[0] { |
| 76 | } | 90 | "connect" => { |
| 77 | if parts.len() < 2 { | 91 | let addr_str = if parts.len() < 2 { |
| 78 | println!("Usage: send <message>"); | 92 | if let Some(h) = &self.addr { |
| 79 | continue; | 93 | h.clone() |
| 94 | } else { | ||
| 95 | println!("\nNo host specified and no previous connection. Usage: connect <ip>:<port>"); | ||
| 96 | continue 'outer; | ||
| 97 | } | ||
| 98 | } else { | ||
| 99 | let mut a = parts[1].to_string(); | ||
| 100 | if !a.contains(':') { | ||
| 101 | a = format!("{}:{}", a, self.default_port); | ||
| 102 | } | ||
| 103 | a | ||
| 104 | }; | ||
| 105 | self.connect_to(&addr_str); | ||
| 106 | } | ||
| 107 | "send" => { | ||
| 108 | if self.stream.is_none() { | ||
| 109 | println!("\nNot connected. Use 'connect <ip>:<port>' first."); | ||
| 110 | continue 'outer; | ||
| 111 | } | ||
| 112 | if parts.len() < 2 { | ||
| 113 | println!("\nUsage: send <message>"); | ||
| 114 | continue 'outer; | ||
| 115 | } | ||
| 116 | let msg = parts[1]; | ||
| 117 | let s = self.stream.as_mut().unwrap(); | ||
| 118 | let writer_stream = s.try_clone().unwrap(); | ||
| 119 | let mut writer = writer_stream; | ||
| 120 | let response_reader_stream = s.try_clone().unwrap(); | ||
| 121 | let mut response_reader = BufReader::new(response_reader_stream); | ||
| 122 | if let Err(_) = writeln!(writer, "{}", msg) { | ||
| 123 | self.stream = None; | ||
| 124 | println!("\nConnection lost"); | ||
| 125 | continue 'outer; | ||
| 126 | } | ||
| 127 | let mut response = String::new(); | ||
| 128 | if let Err(_) = response_reader.read_line(&mut response) { | ||
| 129 | self.stream = None; | ||
| 130 | println!("\nConnection lost"); | ||
| 131 | } else { | ||
| 132 | println!("\nEcho: {}", response.trim()); | ||
| 133 | } | ||
| 134 | } | ||
| 135 | "disconnect" => { | ||
| 136 | self.stream = None; | ||
| 137 | println!("\nDisconnected"); | ||
| 138 | } | ||
| 139 | "ping" => { | ||
| 140 | if self.stream.is_none() { | ||
| 141 | println!("\nNot connected. Use 'connect <ip>:<port>' first."); | ||
| 142 | continue 'outer; | ||
| 143 | } | ||
| 144 | let s = self.stream.as_mut().unwrap(); | ||
| 145 | let writer_stream = s.try_clone().unwrap(); | ||
| 146 | let mut writer = writer_stream; | ||
| 147 | let start = Instant::now(); | ||
| 148 | let response_reader_stream = s.try_clone().unwrap(); | ||
| 149 | let mut response_reader = BufReader::new(response_reader_stream); | ||
| 150 | if let Err(_) = writeln!(writer, "PING") { | ||
| 151 | self.stream = None; | ||
| 152 | println!("\nConnection lost"); | ||
| 153 | continue 'outer; | ||
| 154 | } | ||
| 155 | let mut response = String::new(); | ||
| 156 | if let Err(_) = response_reader.read_line(&mut response) { | ||
| 157 | self.stream = None; | ||
| 158 | println!("\nConnection lost"); | ||
| 159 | } else { | ||
| 160 | if response.trim() == "PING" { | ||
| 161 | let elapsed = start.elapsed(); | ||
| 162 | println!("\nPong ({:?})", elapsed); | ||
| 163 | } else { | ||
| 164 | self.stream = None; | ||
| 165 | println!("\nConnection lost"); | ||
| 166 | } | ||
| 167 | } | ||
| 168 | } | ||
| 169 | "help" => { | ||
| 170 | help(); | ||
| 171 | } | ||
| 172 | "quit" => break 'outer, | ||
| 173 | _ => help(), | ||
| 174 | } | ||
| 175 | break; | ||
| 176 | } | ||
| 177 | KeyCode::Backspace => { | ||
| 178 | if !current_input.is_empty() { | ||
| 179 | current_input.pop(); | ||
| 180 | redraw(¤t_input); | ||
| 181 | } | ||
| 182 | } | ||
| 183 | KeyCode::Char(c) => { | ||
| 184 | if key.modifiers.contains(KeyModifiers::CONTROL) && c == 'c' { | ||
| 185 | disable_raw_mode().unwrap(); | ||
| 186 | execute!(stdout(), Show).unwrap(); | ||
| 187 | return; | ||
| 188 | } else { | ||
| 189 | current_input.push(c); | ||
| 190 | redraw(¤t_input); | ||
| 191 | } | ||
| 192 | } | ||
| 193 | _ => {} | ||
| 80 | } | 194 | } |
| 81 | let msg = parts[1]; | 195 | _ => {} |
| 82 | let s = self.stream.as_mut().unwrap(); | ||
| 83 | let writer_stream = s.try_clone().unwrap(); | ||
| 84 | let mut writer = writer_stream; | ||
| 85 | let response_reader_stream = s.try_clone().unwrap(); | ||
| 86 | let mut response_reader = BufReader::new(response_reader_stream); | ||
| 87 | writeln!(writer, "{}", msg).unwrap(); | ||
| 88 | let mut response = String::new(); | ||
| 89 | response_reader.read_line(&mut response).unwrap(); | ||
| 90 | println!("Echo: {}", response.trim()); | ||
| 91 | } | ||
| 92 | "disconnect" => { | ||
| 93 | self.stream = None; | ||
| 94 | println!("Disconnected"); | ||
| 95 | } | 196 | } |
| 96 | "help" => { | ||
| 97 | help(); | ||
| 98 | } | ||
| 99 | "quit" => break, | ||
| 100 | _ => help(), | ||
| 101 | } | 197 | } |
| 102 | } | 198 | } |
| 199 | disable_raw_mode().unwrap(); | ||
| 200 | execute!(stdout(), Show).unwrap(); | ||
| 103 | } | 201 | } |
| 104 | } | 202 | } |
| 105 | 203 | ||
| 204 | fn redraw(current_input: &str) { | ||
| 205 | execute!(stdout(), Clear(ClearType::CurrentLine), MoveToColumn(0)).unwrap(); | ||
| 206 | print!("> {}", current_input); | ||
| 207 | stdout().flush().unwrap(); | ||
| 208 | execute!(stdout(), MoveToColumn((2 + current_input.len()) as u16)).unwrap(); | ||
| 209 | } | ||
| 210 | |||
| 106 | fn help() { | 211 | fn help() { |
| 107 | println!("Available commands: connect <ip>:<port>, send <message>, disconnect, help, quit"); | 212 | println!("Available commands: connect <ip>:<port>, send <message>, disconnect, ping, help, quit"); |
| 108 | } | 213 | } |
