├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.rst ├── rustfmt.toml ├── src ├── bin │ └── view.rs └── main.rs └── textbox ├── .gitignore ├── Cargo.toml └── src ├── lib.rs ├── nix.rs └── win.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | *.bk 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "textbox" 3 | version = "0.0.1" 4 | dependencies = [ 5 | "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 7 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 8 | "num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 9 | "termbox-sys 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", 10 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 11 | "wio 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 12 | ] 13 | 14 | [[package]] 15 | name = "advapi32-sys" 16 | version = "0.2.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | dependencies = [ 19 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 20 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 21 | ] 22 | 23 | [[package]] 24 | name = "aho-corasick" 25 | version = "0.6.3" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | dependencies = [ 28 | "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 29 | ] 30 | 31 | [[package]] 32 | name = "bit-set" 33 | version = "0.4.0" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | dependencies = [ 36 | "bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 37 | ] 38 | 39 | [[package]] 40 | name = "bit-vec" 41 | version = "0.4.4" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | 44 | [[package]] 45 | name = "bitflags" 46 | version = "0.9.1" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | 49 | [[package]] 50 | name = "build-your-own-editor" 51 | version = "0.0.1" 52 | dependencies = [ 53 | "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 54 | "textbox 0.0.1", 55 | ] 56 | 57 | [[package]] 58 | name = "kernel32-sys" 59 | version = "0.2.2" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | dependencies = [ 62 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 63 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 64 | ] 65 | 66 | [[package]] 67 | name = "lazy_static" 68 | version = "0.2.8" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | 71 | [[package]] 72 | name = "libc" 73 | version = "0.2.29" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | 76 | [[package]] 77 | name = "memchr" 78 | version = "1.0.1" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | dependencies = [ 81 | "libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", 82 | ] 83 | 84 | [[package]] 85 | name = "num-traits" 86 | version = "0.1.40" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | 89 | [[package]] 90 | name = "regex" 91 | version = "0.2.2" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | dependencies = [ 94 | "aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", 95 | "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 96 | "regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 97 | "thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 98 | "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 99 | ] 100 | 101 | [[package]] 102 | name = "regex-syntax" 103 | version = "0.4.1" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | 106 | [[package]] 107 | name = "termbox-sys" 108 | version = "0.2.10" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | 111 | [[package]] 112 | name = "thread_local" 113 | version = "0.3.4" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | dependencies = [ 116 | "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 117 | "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 118 | ] 119 | 120 | [[package]] 121 | name = "unreachable" 122 | version = "1.0.0" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | dependencies = [ 125 | "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 126 | ] 127 | 128 | [[package]] 129 | name = "user32-sys" 130 | version = "0.1.2" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | dependencies = [ 133 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 134 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 135 | ] 136 | 137 | [[package]] 138 | name = "utf8-ranges" 139 | version = "1.0.0" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | 142 | [[package]] 143 | name = "void" 144 | version = "1.0.2" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | 147 | [[package]] 148 | name = "winapi" 149 | version = "0.2.8" 150 | source = "git+https://github.com/oconnor0/winapi-rs.git?rev=textbox#52c61af42e863f73b37beba1accee3df6a5a81a2" 151 | 152 | [[package]] 153 | name = "winapi" 154 | version = "0.2.8" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | replace = "winapi 0.2.8 (git+https://github.com/oconnor0/winapi-rs.git?rev=textbox)" 157 | 158 | [[package]] 159 | name = "winapi-build" 160 | version = "0.1.1" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | 163 | [[package]] 164 | name = "wio" 165 | version = "0.1.2" 166 | source = "git+https://github.com/oconnor0/wio-rs.git?rev=textbox#86feb99c1ad2357d6518ed8a1a1f042651a3fc36" 167 | dependencies = [ 168 | "advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 169 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 170 | "user32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 171 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 172 | ] 173 | 174 | [[package]] 175 | name = "wio" 176 | version = "0.1.2" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | replace = "wio 0.1.2 (git+https://github.com/oconnor0/wio-rs.git?rev=textbox)" 179 | 180 | [metadata] 181 | "checksum advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a" 182 | "checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699" 183 | "checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c" 184 | "checksum bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f" 185 | "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" 186 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 187 | "checksum lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf" 188 | "checksum libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "8a014d9226c2cc402676fbe9ea2e15dd5222cd1dd57f576b5b283178c944a264" 189 | "checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4" 190 | "checksum num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "99843c856d68d8b4313b03a17e33c4bb42ae8f6610ea81b28abe076ac721b9b0" 191 | "checksum regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1731164734096285ec2a5ec7fea5248ae2f5485b3feeb0115af4fda2183b2d1b" 192 | "checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db" 193 | "checksum termbox-sys 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "e32daa27881ea4b2ef36e4972d6cdefb241c19ac48d318381b688d1a631b8760" 194 | "checksum thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1697c4b57aeeb7a536b647165a2825faddffb1d3bad386d507709bd51a90bb14" 195 | "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" 196 | "checksum user32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6717129de5ac253f5642fc78a51d0c7de6f9f53d617fc94e9bae7f6e71cf5504" 197 | "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" 198 | "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 199 | "checksum winapi 0.2.8 (git+https://github.com/oconnor0/winapi-rs.git?rev=textbox)" = "" 200 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 201 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 202 | "checksum wio 0.1.2 (git+https://github.com/oconnor0/wio-rs.git?rev=textbox)" = "" 203 | "checksum wio 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae5a4e42c0d7f399771f3f2f7b56d22e0a51e5915c9d8f7b5bf7e1b8e056fc3d" 204 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "build-your-own-editor" 3 | version = "0.0.1" 4 | authors = ["Matthew O'Connor "] 5 | 6 | [dependencies] 7 | textbox = { path = "textbox" } 8 | regex = "*" 9 | 10 | [workspace] 11 | members = [".", "textbox"] 12 | 13 | [replace] 14 | "winapi:0.2.8" = { git = "https://github.com/oconnor0/winapi-rs.git", rev = "textbox" } 15 | "wio:0.1.2" = { git = "https://github.com/oconnor0/wio-rs.git", rev = "textbox" } 16 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Build Your Own Editor 2 | ===================== 3 | 4 | :Author: Matthew O'Connor 5 | 6 | Build Your Own Editor is intended to be a simple set of libraries and tools 7 | for creating editor-like, console-based applications. I was inspired by kilo_ 8 | but wanted to learn Rust and write something that worked on both Windows and 9 | Unix consoles. This proved to be a challenge as every terminal console library 10 | that I found supported only Unix-like terminals. Inspired by Termbox I decided 11 | to write a Termbox-like library that supported both Windows and Unix consoles. 12 | 13 | Textbox 14 | ------- 15 | 16 | The public API of Textbox is provided in lib.rs_. The library is called 17 | Textbox since I kept mistyping Termbox as Textbox. It is presently implemented 18 | as a wrapper around Termbox on Unix and with direct Windows API calls (through 19 | cloned and modified winapi-rs and wio-rs) on Windows. 20 | 21 | View 22 | ---- 23 | 24 | ``view`` is the sample/reference user of Textbox. This file is written 25 | using it. Eventually, I intend make ``view`` respect its name and remove 26 | its editing capabilities - probably by providing a tool named ``edit``. The 27 | shared code - specifically buffers - will likely be extracted into a library 28 | for reuse. It currently uses a line-oriented data structure to hold the text. 29 | Inserting a character requires copying all characters to the right of the 30 | insert point out one in the backing vector. This hasn't caused a performance 31 | issue yet. 32 | 33 | Current status of ``view``: 34 | 35 | - implemented cursor up/down/left/right 36 | - implemented home/end 37 | - implemented page up/down 38 | - implemented backspace/delete/enter/tab 39 | - all "regular characters" on my keyboard appear to work 40 | - status bar displays file name, dirty status, column and line location and max 41 | - save files with ``Ctrl-S`` 42 | - quit with ``Ctrl-Q`` (no save/dirty check) 43 | - open multiple files with ``view a b c`` 44 | - ``Esc`` closes the current file and goes to the next 45 | - all features work on Windows console and Linux shell 46 | - virtually no error checking/handling 47 | - opening `The Majestic Million CSV`_ - a 75 MB CSV - on a year old i7 takes a fraction of a second 48 | 49 | .. _kilo: https://github.com/antirez/kilo 50 | .. _lib.rs: https://github.com/oconnor0/build-your-own-editor/blob/master/textbox/src/lib.rs 51 | .. _`The Majestic Million CSV`: http://downloads.majestic.com/majestic_million.csv 52 | 53 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | ideal_width = 80 3 | tab_spaces = 2 4 | fn_args_density = "Compressed" 5 | fn_call_width = 78 6 | fn_single_line = true 7 | reorder_imports = true 8 | reorder_imported_names = true 9 | single_line_if_else_max_width = 78 10 | # space_before_bound = true 11 | # space_before_type_annotation = true 12 | struct_lit_width = 78 13 | -------------------------------------------------------------------------------- /src/bin/view.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | extern crate textbox; 3 | use std::fs::{File, OpenOptions}; 4 | use std::io; 5 | use std::io::{BufRead, BufReader, BufWriter, Error, ErrorKind, Write}; 6 | use std::path::PathBuf; 7 | use textbox::*; 8 | 9 | trait Buffer { 10 | fn name(&self) -> &str; 11 | fn paint(&self, tbox: &mut Textbox, at: Coord, active: bool); 12 | fn status(&self) -> String; 13 | fn view_size(&self) -> Coord; 14 | } 15 | 16 | trait Save { 17 | fn save(&mut self) -> io::Result; 18 | } 19 | 20 | trait Navigable { 21 | fn cursor_up(&mut self); 22 | fn cursor_down(&mut self); 23 | fn cursor_left(&mut self); 24 | fn cursor_right(&mut self); 25 | 26 | fn page_up(&mut self); 27 | fn page_down(&mut self); 28 | fn home(&mut self); 29 | fn end(&mut self); 30 | 31 | fn goto_line(&mut self, line: usize); 32 | } 33 | 34 | trait Editable { 35 | fn insert(&mut self, ch: char); 36 | fn delete_line(&mut self) -> String; 37 | } 38 | 39 | struct FileEdit { 40 | path: Option, 41 | lines: Vec, 42 | offset: Coord, 43 | cursor: Coord, 44 | v_size: Coord, 45 | dirty: bool, 46 | } 47 | 48 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 49 | enum Mode { 50 | Edit, 51 | Find, 52 | Goto, 53 | } 54 | 55 | impl Mode { 56 | fn is_edit(&self) -> bool { *self == Mode::Edit } 57 | fn is_cmd(&self) -> bool { *self == Mode::Find || *self == Mode::Goto } 58 | } 59 | 60 | struct CommandBar { 61 | prompt: String, 62 | entry: String, 63 | v_size: Coord, 64 | buf: Box, 65 | mode: Mode, 66 | } 67 | 68 | impl CommandBar { 69 | fn new(v_size: Coord, buf: B) -> Self { 70 | CommandBar { 71 | prompt: ":".to_string(), 72 | entry: String::new(), 73 | v_size: v_size, 74 | buf: Box::new(buf), 75 | mode: Mode::Edit, 76 | } 77 | } 78 | 79 | fn push_mode(&mut self, mode: Mode) { self.mode = mode; } 80 | 81 | 82 | fn pop_mode(&mut self) { 83 | self.mode = match self.mode { 84 | Mode::Edit => Mode::Edit, 85 | Mode::Find => Mode::Edit, 86 | Mode::Goto => Mode::Edit, 87 | }; 88 | } 89 | } 90 | 91 | impl Buffer for CommandBar { 92 | fn name(&self) -> &str { &"command bar" } 93 | fn status(&self) -> String { 94 | match self.mode { 95 | Mode::Edit => "*edit*".to_string(), 96 | Mode::Find => "*find*".to_string(), 97 | Mode::Goto => "*goto*".to_string(), 98 | } 99 | } 100 | 101 | fn paint(&self, tbox: &mut Textbox, at: Coord, active: bool) { 102 | self.buf.paint(tbox, at, active & self.mode.is_edit()); 103 | let at = at + (self.buf.view_size().row() + 1).to_row(); 104 | 105 | let Coord(cols, rows) = tbox.size(); 106 | let status = self.buf.status(); 107 | for col in 0..cols { 108 | tbox.set_cell(Coord(col, rows - 2), ' ', DEFAULT, DEFAULT | REVERSE); 109 | // tbox.set_cell(Coord(col, rows - 1), ' ', DEFAULT, DEFAULT); 110 | } 111 | tbox.set_cells(Coord(cols - 2 - status.len(), rows - 2), 112 | &status, 113 | DEFAULT, 114 | DEFAULT | REVERSE); 115 | let status = self.status(); 116 | tbox.set_cells(Coord(2, rows - 2), &status, DEFAULT, DEFAULT | REVERSE); 117 | 118 | if active & self.mode.is_cmd() { 119 | tbox.set_cells(at, &self.prompt, DEFAULT, DEFAULT); 120 | let at = at + self.prompt.len().to_col() + 1.to_col(); 121 | tbox.set_cells(at, &self.entry, DEFAULT, DEFAULT); 122 | let at = at + self.entry.len().to_col(); 123 | tbox.set_cursor(at); 124 | } 125 | } 126 | 127 | fn view_size(&self) -> Coord { 128 | Coord(self.v_size.col(), self.v_size.row() + self.buf.view_size().row()) 129 | } 130 | } 131 | 132 | impl Editable for CommandBar { 133 | fn insert(&mut self, ch: char) { 134 | if self.mode.is_edit() { 135 | self.buf.insert(ch); 136 | } else { 137 | match ch { 138 | '\n' => { 139 | if self.mode == Mode::Goto { 140 | if let Ok(line) = self.entry.trim().parse::() { 141 | self.buf.goto_line(if line > 0 { line - 1 } else { 0 }); 142 | } 143 | self.entry.clear(); 144 | } 145 | self.mode = Mode::Edit; 146 | } 147 | '\x08' => { 148 | // backspace 149 | if self.entry.len() > 0 { 150 | self.entry.pop(); 151 | } 152 | } 153 | '\x7f' => (), // delete - ignore 154 | _ => self.entry.push(ch), 155 | } 156 | } 157 | } 158 | 159 | fn delete_line(&mut self) -> String { 160 | if self.mode.is_edit() { 161 | self.buf.delete_line() 162 | } else { 163 | let out = self.entry.to_string(); 164 | self.entry.clear(); 165 | out 166 | } 167 | } 168 | } 169 | 170 | impl Navigable for CommandBar { 171 | fn cursor_up(&mut self) { 172 | if self.mode.is_edit() { 173 | self.buf.cursor_up(); 174 | } 175 | } 176 | 177 | fn cursor_down(&mut self) { 178 | if self.mode.is_edit() { 179 | self.buf.cursor_down(); 180 | } 181 | } 182 | 183 | fn cursor_left(&mut self) { 184 | if self.mode.is_edit() { 185 | self.buf.cursor_left(); 186 | } 187 | } 188 | 189 | fn cursor_right(&mut self) { 190 | if self.mode.is_edit() { 191 | self.buf.cursor_right(); 192 | } 193 | } 194 | 195 | fn page_up(&mut self) { 196 | if self.mode.is_edit() { 197 | self.buf.page_up(); 198 | } 199 | } 200 | 201 | fn page_down(&mut self) { 202 | if self.mode.is_edit() { 203 | self.buf.page_down(); 204 | } 205 | } 206 | 207 | fn home(&mut self) { 208 | if self.mode.is_edit() { 209 | self.buf.home(); 210 | } 211 | } 212 | 213 | fn end(&mut self) { 214 | if self.mode.is_edit() { 215 | self.buf.end(); 216 | } 217 | } 218 | 219 | fn goto_line(&mut self, line: usize) { 220 | if self.mode.is_edit() { 221 | self.buf.goto_line(line); 222 | } 223 | } 224 | } 225 | 226 | impl Save for CommandBar { 227 | fn save(&mut self) -> io::Result { self.buf.save() } 228 | } 229 | 230 | impl FileEdit { 231 | fn from_file(v_size: Coord, filename: &str) -> Self { 232 | let path = PathBuf::from(filename); 233 | let mut lines = vec![]; 234 | 235 | if path.exists() && path.is_file() { 236 | let file = File::open(path.as_path()).unwrap(); 237 | let bufr = BufReader::new(&file); 238 | for line in bufr.lines() { 239 | lines.push(line.unwrap()); 240 | } 241 | } 242 | if lines.len() == 0 { 243 | lines.push(String::new()); 244 | } 245 | 246 | FileEdit { 247 | path: Some(path), 248 | lines: lines, 249 | offset: zero(), 250 | cursor: zero(), 251 | v_size: v_size, 252 | dirty: false, 253 | } 254 | } 255 | } 256 | 257 | impl Save for FileEdit { 258 | fn save(&mut self) -> io::Result { 259 | if self.dirty { 260 | if let Some(ref path) = self.path { 261 | let file = OpenOptions::new() 262 | .create(true) 263 | .write(true) 264 | .truncate(true) 265 | .open(path) 266 | .unwrap(); 267 | let mut file = BufWriter::new(file); 268 | let nl = vec!['\n' as u8]; 269 | let mut written = 0; 270 | for ref line in self.lines.iter() { 271 | written += try!(file.write(line.as_bytes())); 272 | written += try!(file.write(&nl)); 273 | } 274 | self.dirty = false; 275 | Ok(written) 276 | } else { 277 | Err(Error::new(ErrorKind::NotFound, "no filename given")) 278 | } 279 | } else { 280 | Ok(0) 281 | } 282 | } 283 | } 284 | 285 | impl Buffer for FileEdit { 286 | fn name(&self) -> &str { 287 | match self.path { 288 | Some(ref path) => path.to_str().unwrap(), 289 | None => "-- buffer --", 290 | } 291 | } 292 | 293 | fn paint(&self, tbox: &mut Textbox, global: Coord, active: bool) { 294 | if active { 295 | tbox.set_cursor(global + self.cursor); 296 | } 297 | for (row, line) in self.lines[self.offset.row()..].iter().enumerate() { 298 | if row >= self.v_size.row() { 299 | break; 300 | } 301 | // let mut initial_spaces = true; 302 | if self.offset.col() < line.len() { 303 | for (col, ch) in line[self.offset.col()..].chars().enumerate() { 304 | if col >= self.v_size.col() { 305 | break; 306 | // } else if ch == ' ' { 307 | // tbox.set_cell(Coord(col, row), 183 as char, BRIGHT | DEFAULT, DEFAULT); 308 | } else { 309 | // initial_spaces = false; 310 | tbox.set_cell(Coord(col, row), ch, DEFAULT, DEFAULT); 311 | } 312 | } 313 | } 314 | } 315 | } 316 | 317 | fn status(&self) -> String { 318 | let curr_col = 1 + self.offset.0 + self.cursor.0; 319 | let curr_row = 1 + self.offset.1 + self.cursor.1; 320 | let rows_in_buf = self.lines.len(); 321 | let cols_in_row = //if curr_row < rows_in_buf { 322 | self.lines[self.offset.1 + self.cursor.1].len(); 323 | // } else { 324 | // 0 325 | // }; 326 | format!(// "{} - {:2}/{:2} - {:3}/{:3}", 327 | "{}{} - {}/{} - {}/{}", 328 | self.name(), 329 | if self.dirty { "*" } else { "" }, 330 | curr_col, 331 | cols_in_row, 332 | curr_row, 333 | rows_in_buf) 334 | } 335 | 336 | fn view_size(&self) -> Coord { self.v_size } 337 | } 338 | 339 | impl Navigable for FileEdit { 340 | fn cursor_up(&mut self) { 341 | if self.offset.row() + self.cursor.row() == 0 { 342 | // do nothing 343 | } else if self.cursor.row() == 0 { 344 | self.offset.1 -= 1; 345 | } else { 346 | self.cursor.1 -= 1; 347 | } 348 | 349 | if self.offset.0 + self.cursor.0 >= 350 | self.lines[self.offset.1 + self.cursor.1].len() { 351 | self.end(); 352 | } 353 | } 354 | 355 | fn cursor_down(&mut self) { 356 | if self.offset.row() + self.cursor.row() >= self.lines.len() - 1 { 357 | // do nothing 358 | } else if self.cursor.row() >= self.v_size.row() - 1 { 359 | self.cursor.1 = self.v_size.row() - 1; 360 | self.offset.1 += 1; 361 | } else { 362 | self.cursor.1 += 1; 363 | } 364 | 365 | if self.offset.0 + self.cursor.0 >= 366 | self.lines[self.offset.1 + self.cursor.1].len() { 367 | self.end(); 368 | } 369 | } 370 | 371 | fn cursor_left(&mut self) { 372 | if self.offset.0 + self.cursor.0 == 0 { 373 | if self.offset.1 + self.cursor.1 > 0 { 374 | self.cursor_up(); 375 | self.end(); 376 | } 377 | } else if self.cursor.0 == 0 { 378 | self.offset.0 -= 1; 379 | } else { 380 | self.cursor.0 -= 1; 381 | } 382 | } 383 | 384 | fn cursor_right(&mut self) { 385 | let offset_row = self.offset.1; 386 | let cursor_row = self.cursor.1; 387 | let view_cols = self.v_size.0; 388 | let line_len = self.lines[offset_row + cursor_row].len(); 389 | 390 | if self.offset.0 + self.cursor.0 >= line_len { 391 | if offset_row + cursor_row < self.lines.len() - 1 { 392 | self.cursor_down(); 393 | self.home(); 394 | } else { 395 | self.end(); 396 | } 397 | } else if self.cursor.0 >= view_cols - 1 { 398 | self.offset.0 += 1; 399 | self.cursor.0 = view_cols - 1; 400 | } else { 401 | self.cursor.0 += 1; 402 | } 403 | } 404 | 405 | fn page_up(&mut self) { 406 | if self.offset.row() + self.cursor.row() == 0 { 407 | // do nothing 408 | } else if self.offset.1 == 0 { 409 | self.cursor.1 = 0; 410 | } else if self.offset.row() <= self.v_size.row() - 1 { 411 | if self.cursor.row() > self.v_size.row() - self.offset.row() { 412 | self.cursor.1 -= self.v_size.row() - self.offset.row(); 413 | } 414 | self.offset.1 = 0; 415 | } else { 416 | self.offset.1 -= self.v_size.row(); 417 | } 418 | 419 | if self.offset.0 + self.cursor.0 >= 420 | self.lines[self.offset.1 + self.cursor.1].len() { 421 | self.end(); 422 | } 423 | } 424 | 425 | fn page_down(&mut self) { 426 | if self.offset.1 + self.cursor.1 >= self.lines.len() - 1 { 427 | // do nothing 428 | } else if self.lines.len() < self.v_size.1 { 429 | self.cursor.1 = self.lines.len() - 1; 430 | } else if self.offset.1 >= self.lines.len() - self.v_size.1 { 431 | self.cursor.1 = self.lines.len() - self.offset.1 - 1; 432 | } else { 433 | self.offset.1 += self.v_size.1; 434 | if self.offset.1 + self.v_size.1 >= self.lines.len() - 1 { 435 | self.offset.1 = self.lines.len() - self.v_size.1; 436 | } 437 | } 438 | 439 | if self.offset.0 + self.cursor.0 >= 440 | self.lines[self.offset.1 + self.cursor.1].len() { 441 | self.end(); 442 | } 443 | } 444 | 445 | fn home(&mut self) { 446 | self.offset.0 = 0; 447 | self.cursor.0 = 0; 448 | } 449 | 450 | fn end(&mut self) { 451 | let offset_row = self.offset.1; 452 | let cursor_row = self.cursor.1; 453 | let view_cols = self.v_size.0; 454 | let line_len = self.lines[offset_row + cursor_row].len(); 455 | 456 | if view_cols >= line_len { 457 | self.offset.0 = 0; 458 | self.cursor.0 = line_len; 459 | } else { 460 | self.offset.0 = line_len + 1 - view_cols; 461 | self.cursor.0 = view_cols - 1; 462 | } 463 | } 464 | 465 | fn goto_line(&mut self, line: usize) { 466 | let len = self.lines.len(); 467 | let line = if line >= len { len - 1 } else { line }; 468 | if line < self.v_size.1 { 469 | self.offset.1 = 0; 470 | self.cursor.1 = line; 471 | } else { 472 | self.offset.1 = line - self.cursor.1; 473 | } 474 | } 475 | } 476 | 477 | impl Editable for FileEdit { 478 | fn insert(&mut self, ch: char) { 479 | use std::cmp::min; 480 | self.dirty = true; 481 | 482 | let col_at = self.offset.col() + self.cursor.col(); 483 | let cols = self.lines[self.offset.row() + self.cursor.row()].len(); 484 | let row_at = self.offset.row() + self.cursor.row(); 485 | match ch { 486 | '\n' => { 487 | let curr = self.lines[row_at][0..min(col_at, cols)].to_string(); 488 | let next = self.lines[row_at][min(col_at, cols)..cols].to_string(); 489 | self.lines[row_at] = curr; 490 | self.lines.insert(row_at + 1, next); 491 | self.cursor_down(); 492 | self.home(); 493 | } 494 | '\x08' => { 495 | // backspace 496 | let curr_row = self.offset.1 + self.cursor.1; 497 | let curr_col = self.offset.0 + self.cursor.0; 498 | if curr_col == 0 { 499 | if curr_row > 0 { 500 | // join lines 501 | let prev_row = curr_row - 1; 502 | self.cursor_up(); 503 | self.end(); 504 | let curr_str = self.lines.remove(curr_row); 505 | self.lines[prev_row].push_str(&curr_str); 506 | } 507 | } else { 508 | self.lines[curr_row].remove(curr_col - 1); 509 | self.cursor_left(); 510 | } 511 | } 512 | '\x7f' => { 513 | // delete 514 | let curr_row = self.offset.1 + self.cursor.1; 515 | let curr_col = self.offset.0 + self.cursor.0; 516 | let line_len = self.lines[curr_row].len(); 517 | if curr_col == line_len { 518 | if curr_row < self.lines.len() - 1 { 519 | // join lines 520 | let next_row = curr_row + 1; 521 | let next_str = self.lines.remove(next_row); 522 | self.lines[curr_row].push_str(&next_str); 523 | } 524 | } else { 525 | self.lines[curr_row].remove(curr_col); 526 | } 527 | } 528 | _ => { 529 | self.lines[row_at].insert(col_at, ch); 530 | self.cursor_right(); 531 | } 532 | } 533 | } 534 | 535 | fn delete_line(&mut self) -> String { 536 | self.dirty = true; 537 | let curr_row = self.offset.1 + self.cursor.1; 538 | let curr_col = self.offset.0 + self.cursor.0; 539 | let curr_str = self.lines.remove(curr_row); 540 | if self.lines.len() == 0 { 541 | self.lines.push(String::new()); 542 | } 543 | if curr_row >= self.lines.len() { 544 | self.cursor_up(); 545 | } 546 | let curr_row = self.offset.1 + self.cursor.1; 547 | if curr_col >= self.lines[curr_row].len() { 548 | self.end(); 549 | } 550 | curr_str 551 | } 552 | } 553 | 554 | fn main() { 555 | let mut tbox = TextboxImpl::init().unwrap(); 556 | let size = tbox.size(); 557 | tbox.set_clear_style(DEFAULT, DEFAULT); 558 | 559 | 'arg_loop: for arg in std::env::args().skip(1) { 560 | tbox.clear(); 561 | tbox.present(); 562 | 563 | let buf = FileEdit::from_file(size - 2.to_row(), &arg); 564 | let mut cmd = CommandBar::new(Coord(size.col(), 1), buf); 565 | cmd.paint(&mut tbox, zero(), true); 566 | tbox.present(); 567 | 568 | { 569 | 'event_loop: loop { 570 | { 571 | if let Some(e) = tbox.pop_event() { 572 | match e { 573 | Event::Key(_, CTRL, Key::Char('Q')) => break 'arg_loop, 574 | Event::Key(_, NO_MODS, Key::Escape) => cmd.pop_mode(), 575 | Event::Key(_, CTRL, Key::Char('S')) => { 576 | cmd.save().unwrap(); 577 | } 578 | Event::Key(_, CTRL, Key::Char('G')) => cmd.push_mode(Mode::Goto), 579 | Event::Key(_, CTRL, Key::Char('F')) => cmd.push_mode(Mode::Find), 580 | Event::Key(_, CTRL, Key::Char('X')) => { 581 | cmd.delete_line(); 582 | } 583 | Event::Key(_, NO_MODS, Key::Up) | 584 | Event::Key(_, CTRL, Key::Char('K')) => cmd.cursor_up(), 585 | Event::Key(_, NO_MODS, Key::Down) | 586 | Event::Key(_, CTRL, Key::Char('J')) => cmd.cursor_down(), 587 | Event::Key(_, NO_MODS, Key::Left) | 588 | Event::Key(_, CTRL, Key::Char('H')) => cmd.cursor_left(), 589 | Event::Key(_, NO_MODS, Key::Right) | 590 | Event::Key(_, CTRL, Key::Char('L')) => cmd.cursor_right(), 591 | Event::Key(_, NO_MODS, Key::PageUp) | 592 | Event::Key(_, CTRL_SHIFT, Key::Char('K')) => cmd.page_up(), 593 | Event::Key(_, NO_MODS, Key::PageDown) | 594 | Event::Key(_, CTRL_SHIFT, Key::Char('J')) => cmd.page_down(), 595 | Event::Key(_, NO_MODS, Key::Home) | 596 | Event::Key(_, CTRL_SHIFT, Key::Char('H')) => cmd.home(), 597 | Event::Key(_, NO_MODS, Key::End) | 598 | Event::Key(_, CTRL_SHIFT, Key::Char('L')) => cmd.end(), 599 | // Event::Key('/', ALT, _) => { 600 | // println!("divide"); 601 | // cmd.insert(0xf7 as char); 602 | // } 603 | Event::Key(ch, NO_MODS, Key::Char(_)) | 604 | Event::Key(ch, SHIFT, Key::Char(_)) => cmd.insert(ch), 605 | Event::Key(_, NO_MODS, Key::Enter) => cmd.insert('\n'), 606 | Event::Key(_, NO_MODS, Key::Backspace) => cmd.insert('\x08'), 607 | Event::Key(_, NO_MODS, Key::Delete) => cmd.insert('\x7f'), 608 | Event::Key(_, NO_MODS, Key::Tab) => { 609 | cmd.insert(' '); 610 | cmd.insert(' '); 611 | } 612 | _ => (), 613 | } 614 | } 615 | } 616 | 617 | tbox.clear(); 618 | cmd.paint(&mut tbox, zero(), true); 619 | tbox.present(); 620 | } 621 | } 622 | } 623 | } 624 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | extern crate textbox; 3 | use std::path::PathBuf; 4 | use textbox::*; 5 | 6 | struct Buffer { 7 | path: Option, 8 | lines: Vec, 9 | offset: Coord, 10 | cursor: Coord, 11 | view_size: Coord, 12 | } 13 | 14 | impl Buffer { 15 | fn from_file(view_size: Coord, filename: &str) -> Buffer { 16 | use std::io::{BufRead, BufReader}; 17 | use std::fs::File; 18 | 19 | let path = PathBuf::from(filename); 20 | let file = File::open(path.as_path()).unwrap(); 21 | let bufr = BufReader::new(&file); 22 | let mut lines = vec![]; 23 | for line in bufr.lines() { 24 | lines.push(line.unwrap()); 25 | } 26 | 27 | Buffer { 28 | path: Some(path), 29 | lines: lines, 30 | offset: zero(), 31 | cursor: zero(), 32 | view_size: view_size, 33 | } 34 | } 35 | 36 | fn name(&self) -> &str { 37 | match self.path { 38 | Some(ref path) => path.to_str().unwrap(), 39 | None => "-- buffer --", 40 | } 41 | } 42 | 43 | fn paint(&self, tbox: &mut Textbox, global: Coord) { 44 | // TODO: Only set cursor if active buffer. 45 | tbox.set_cursor(global + self.cursor); 46 | for (row, line) in self.lines[self.offset.row()..].iter().enumerate() { 47 | if row >= self.view_size.row() { 48 | break; 49 | } 50 | // let mut initial_spaces = true; 51 | if self.offset.col() < line.len() { 52 | for (col, ch) in line[self.offset.col()..].chars().enumerate() { 53 | if col >= self.view_size.col() { 54 | break; 55 | // } else if ch == ' ' { 56 | // tbox.set_cell(Coord(col, row), 183 as char, BRIGHT | DEFAULT, DEFAULT); 57 | } else { 58 | // initial_spaces = false; 59 | tbox.set_cell(Coord(col, row), ch, DEFAULT, DEFAULT); 60 | } 61 | } 62 | } 63 | } 64 | } 65 | 66 | fn cursor_down(&mut self) { 67 | if self.offset.row() + self.cursor.row() >= self.lines.len() - 1 { 68 | // do nothing 69 | } else if self.cursor.row() >= self.view_size.row() - 1 { 70 | self.cursor.1 = self.view_size.row() - 1; 71 | self.offset.1 += 1; 72 | } else { 73 | self.cursor.1 += 1; 74 | } 75 | } 76 | 77 | fn page_down(&mut self) { 78 | if self.offset.1 + self.cursor.1 >= self.lines.len() - 1 { 79 | // do nothing 80 | } else if self.lines.len() < self.view_size.row() { 81 | self.cursor.1 = self.lines.len() - 1; 82 | } else { 83 | self.offset.1 += self.view_size.1; 84 | if self.offset.1 + self.view_size.1 >= self.lines.len() - 1 { 85 | self.offset.1 = self.lines.len() - self.view_size.1; 86 | self.cursor.1 = self.view_size.1 - 1; 87 | } 88 | } 89 | } 90 | 91 | fn cursor_up(&mut self) { 92 | if self.offset.row() + self.cursor.row() == 0 { 93 | // do nothing 94 | } else if self.cursor.row() == 0 { 95 | self.offset.1 -= 1; 96 | } else { 97 | self.cursor.1 -= 1; 98 | } 99 | } 100 | 101 | fn cursor_right(&mut self) { 102 | let offset_row = self.offset.1; 103 | let cursor_row = self.cursor.1; 104 | let view_cols = self.view_size.0; 105 | let line_len = self.lines[offset_row + cursor_row].len(); 106 | 107 | if self.offset.0 + self.cursor.0 >= line_len { 108 | if offset_row + cursor_row < self.lines.len() - 1 { 109 | self.cursor_down(); 110 | self.home(); 111 | } else { 112 | self.end(); 113 | } 114 | } else if self.cursor.0 >= view_cols - 1 { 115 | self.offset.0 += 1; 116 | self.cursor.0 = view_cols - 1; 117 | } else { 118 | self.cursor.0 += 1; 119 | } 120 | } 121 | 122 | fn cursor_left(&mut self) { 123 | if self.offset.0 + self.cursor.0 == 0 { 124 | if self.offset.1 + self.cursor.1 > 0 { 125 | self.cursor_up(); 126 | self.end(); 127 | } 128 | } else if self.cursor.0 == 0 { 129 | self.offset.0 -= 1; 130 | } else { 131 | self.cursor.0 -= 1; 132 | } 133 | } 134 | 135 | fn page_up(&mut self) { 136 | if self.offset.row() + self.cursor.row() == 0 { 137 | // do nothing 138 | } else if self.offset.1 == 0 { 139 | self.cursor.1 = 0; 140 | } else if self.offset.row() <= self.view_size.row() - 1 { 141 | if self.cursor.row() > self.view_size.row() - self.offset.row() { 142 | self.cursor.1 -= self.view_size.row() - self.offset.row(); 143 | } 144 | self.offset.1 = 0; 145 | } else { 146 | self.offset.1 -= self.view_size.row(); 147 | } 148 | } 149 | 150 | fn home(&mut self) { 151 | self.offset.0 = 0; 152 | self.cursor.0 = 0; 153 | } 154 | 155 | fn end(&mut self) { 156 | let offset_row = self.offset.1; 157 | let cursor_row = self.cursor.1; 158 | let view_cols = self.view_size.0; 159 | let line_len = self.lines[offset_row + cursor_row].len(); 160 | 161 | if view_cols >= line_len { 162 | self.offset.0 = 0; 163 | self.cursor.0 = line_len; 164 | } else { 165 | self.offset.0 = line_len + 1 - view_cols; 166 | self.cursor.0 = view_cols - 1; 167 | } 168 | } 169 | } 170 | 171 | fn main() { 172 | let mut tbox = TextboxImpl::init().unwrap(); 173 | let size = tbox.size(); 174 | let Coord(cols, rows) = size; 175 | // tbox.set_cursor(0, rows - 1); 176 | tbox.set_clear_style(DEFAULT, DEFAULT); 177 | tbox.clear(); 178 | tbox.present(); 179 | 180 | let mut buf = Buffer::from_file(size - 2.to_row(), "src/main.rs"); 181 | buf.paint(&mut tbox, zero()); 182 | for col in 0..cols { 183 | tbox.set_cell(Coord(col, rows - 2), ' ', WHITE, BLACK | REVERSE); 184 | } 185 | // tbox.set_cells(Coord(2, rows - 2), 186 | // buf.name(), 187 | // WHITE, 188 | // DEFAULT | REVERSE); 189 | let pos = format!("{} - {:2}/{:2} - {:3}/{:3}", 190 | buf.name(), 191 | buf.offset.0 + buf.cursor.0 + 1, 192 | buf.lines[buf.offset.1 + buf.cursor.1].len(), 193 | buf.offset.1 + buf.cursor.1 + 1, 194 | buf.lines.len()); 195 | tbox.set_cells(Coord(cols - 2 - pos.len(), rows - 2), 196 | &pos, 197 | WHITE, 198 | BLACK | REVERSE); 199 | tbox.present(); 200 | 201 | { 202 | // let mut ch = ' '; 203 | let mut changed = false; 204 | // let mut x = 0; 205 | loop { 206 | { 207 | match tbox.pop_event() { 208 | Some(Event::Key(_, _, Key::Escape)) => return, 209 | Some(Event::Key(_, _, Key::PageUp)) => { 210 | buf.page_up(); 211 | changed = true; 212 | } 213 | Some(Event::Key(_, _, Key::Up)) => { 214 | buf.cursor_up(); 215 | changed = true; 216 | } 217 | Some(Event::Key(_, _, Key::Down)) => { 218 | buf.cursor_down(); 219 | changed = true; 220 | } 221 | Some(Event::Key(_, _, Key::Left)) => { 222 | buf.cursor_left(); 223 | changed = true; 224 | } 225 | Some(Event::Key(_, _, Key::Right)) => { 226 | buf.cursor_right(); 227 | changed = true; 228 | } 229 | Some(Event::Key(_, _, Key::PageDown)) => { 230 | buf.page_down(); 231 | changed = true; 232 | } 233 | Some(Event::Key(_, _, Key::End)) => { 234 | buf.end(); 235 | changed = true; 236 | } 237 | Some(Event::Key(_, _, Key::Home)) => { 238 | buf.home(); 239 | changed = true; 240 | } 241 | Some(Event::Key(_, _, _)) => { 242 | // println!("({:?}, {:?}, {:?})", c, k, m); 243 | // ch = c; 244 | changed = true; 245 | } 246 | _ => (), 247 | } 248 | } 249 | { 250 | if changed { 251 | tbox.clear(); 252 | buf.paint(&mut tbox, Coord(0, 0)); 253 | for col in 0..cols { 254 | tbox.set_cell(Coord(col, rows - 2), ' ', WHITE, BLACK | REVERSE); 255 | } 256 | // tbox.set_cells(Coord(2, rows - 2), 257 | // buf.name(), 258 | // WHITE, 259 | // DEFAULT | REVERSE); 260 | let pos = format!("{} - {:2}/{:2} - {:3}/{:3}", 261 | buf.name(), 262 | buf.offset.0 + buf.cursor.0 + 1, 263 | buf.lines[buf.offset.1 + buf.cursor.1].len(), 264 | buf.offset.1 + buf.cursor.1 + 1, 265 | buf.lines.len()); 266 | tbox.set_cells(Coord(cols - 2 - pos.len(), rows - 2), 267 | &pos, 268 | WHITE, 269 | BLACK | REVERSE); 270 | // tbox.set_cell(Coord(x, rows - 1), ch, WHITE, DEFAULT); 271 | changed = false; 272 | // tbox.set_cursor(x + 1, rows - 1); 273 | tbox.present(); 274 | // x += 1; 275 | } 276 | } 277 | } 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /textbox/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | Cargo.lock 3 | *.bk 4 | -------------------------------------------------------------------------------- /textbox/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "textbox" 3 | version = "0.0.1" 4 | authors = ["Matthew O'Connor "] 5 | workspace = ".." 6 | 7 | [dependencies] 8 | bitflags = "*" 9 | bit-set = "*" 10 | num-traits = "*" 11 | 12 | [target.'cfg(windows)'.dependencies] 13 | winapi = "0.2.8" 14 | wio = "0.1.2" 15 | kernel32-sys = "*" 16 | 17 | [target.'cfg(unix)'.dependencies] 18 | termbox-sys = "*" 19 | -------------------------------------------------------------------------------- /textbox/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate bitflags; 3 | extern crate bit_set; 4 | extern crate num_traits; 5 | 6 | mod types { 7 | use std::ops::{Add, Sub}; 8 | pub use num_traits::{zero, Zero}; 9 | use std::result; 10 | 11 | pub type Result = result::Result; 12 | 13 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 14 | pub struct Coord(pub usize, pub usize); 15 | 16 | impl Coord { 17 | pub fn col(&self) -> usize { self.0 } 18 | pub fn row(&self) -> usize { self.1 } 19 | } 20 | 21 | impl Add for Coord { 22 | type Output = Self; 23 | fn add(self, rhs: Coord) -> Coord { Coord(self.0 + rhs.0, self.1 + rhs.1) } 24 | } 25 | 26 | impl Sub for Coord { 27 | type Output = Self; 28 | fn sub(self, rhs: Coord) -> Coord { Coord(self.0 - rhs.0, self.1 - rhs.1) } 29 | } 30 | 31 | impl Zero for Coord { 32 | fn zero() -> Self { Coord(0, 0) } 33 | fn is_zero(&self) -> bool { self.0 == 0 && self.1 == 0 } 34 | } 35 | 36 | pub trait ToCol { 37 | fn to_col(&self) -> Coord; 38 | } 39 | 40 | impl ToCol for usize { 41 | fn to_col(&self) -> Coord { Coord(*self, 0) } 42 | } 43 | 44 | pub trait ToRow { 45 | fn to_row(&self) -> Coord; 46 | } 47 | 48 | impl ToRow for usize { 49 | fn to_row(&self) -> Coord { Coord(0, *self) } 50 | } 51 | 52 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 53 | pub enum InputMode { 54 | Current, 55 | Esc, 56 | Alt, 57 | Mouse, 58 | } 59 | 60 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 61 | pub enum OutputMode { 62 | Current, 63 | Normal, 64 | Colors256, 65 | Colors216, 66 | Grayscale, 67 | } 68 | 69 | bitflags! { 70 | pub struct Mod: u16 { 71 | const NO_MODS = 0; 72 | 73 | // const RIGHT_ALT = 0x0001, 74 | // const LEFT_ALT = 0x0002, 75 | const ALT = 0x0003; //RIGHT_ALT.bits|LEFT_ALT.bits, 76 | 77 | // const RIGHT_CTRL = 0x0004, 78 | // const LEFT_CTRL = 0x0008, 79 | const CTRL = 0x000c; //RIGHT_CTRL.bits|LEFT_CTRL.bits, 80 | 81 | const SHIFT = 0x0010; 82 | 83 | const ALT_CTRL = ALT.bits|CTRL.bits; 84 | const ALT_SHIFT = ALT.bits|SHIFT.bits; 85 | const CTRL_SHIFT = CTRL.bits|SHIFT.bits; 86 | const ALT_CTRL_SHIFT = ALT.bits|CTRL.bits|SHIFT.bits; 87 | 88 | // const CAPS_LOCK = 0x0020, 89 | // const NUM_LOCK = 0x0040, 90 | // const SCROLL_LOCK = 0x0080, 91 | // const META = 0x0100, 92 | // const MENU = 0x0200, 93 | } 94 | } 95 | 96 | 97 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 98 | pub enum Key { 99 | F(u8), 100 | Char(char), 101 | Left, 102 | Up, 103 | Right, 104 | Down, 105 | Escape, 106 | Insert, 107 | Delete, 108 | Home, 109 | End, 110 | PageUp, 111 | PageDown, 112 | Backspace, 113 | Tab, 114 | Enter, 115 | } 116 | 117 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 118 | pub enum Event { 119 | Key(char, Mod, Key), 120 | } 121 | 122 | bitflags! { 123 | pub struct Style: u16 { 124 | const DEFAULT = 0x8000; 125 | 126 | const BLACK = 0x0001; 127 | const RED = 0x0002; 128 | const GREEN = 0x0004; 129 | const YELLOW = 0x0008; 130 | const BLUE = 0x0010; 131 | 132 | const MAGENTA = 0x0020; 133 | const CYAN = 0x0040; 134 | const WHITE = 0x0080; 135 | 136 | const BRIGHT = 0x0100; 137 | const BOLD = 0x0200; 138 | const UNDERLINE = 0x0400; 139 | const REVERSE = 0x0800; 140 | } 141 | } 142 | 143 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 144 | pub struct Cell { 145 | pub ch: char, 146 | pub fg: Style, 147 | pub bg: Style, 148 | } 149 | 150 | pub trait Textbox { 151 | fn init() -> Result where Self: Sized; 152 | 153 | #[inline] 154 | fn cols(&self) -> usize { self.size().0 } 155 | #[inline] 156 | fn rows(&self) -> usize { self.size().1 } 157 | #[inline] 158 | fn size(&self) -> Coord; 159 | 160 | fn set_clear_style(&mut self, fg: Style, bg: Style); 161 | fn clear(&mut self); 162 | 163 | fn present(&mut self); 164 | 165 | fn set_cursor(&mut self, coord: Coord); 166 | fn hide_cursor(&mut self); 167 | 168 | #[inline] 169 | fn put_cell(&mut self, coord: Coord, cell: Cell); 170 | #[inline] 171 | fn set_cell(&mut self, coord: Coord, ch: char, fg: Style, bg: Style) { 172 | self.put_cell(coord, Cell { ch: ch, fg: fg, bg: bg }) 173 | } 174 | 175 | fn set_cells(&mut self, coord: Coord, chs: &str, fg: Style, bg: Style) { 176 | for (i, ch) in chs.chars().enumerate() { 177 | self.set_cell(coord + i.to_col(), ch, fg, bg); 178 | } 179 | } 180 | 181 | fn set_input_mode(&mut self, _: InputMode) -> InputMode; 182 | fn set_output_mode(&mut self, _: OutputMode) -> OutputMode; 183 | 184 | fn pop_event(&mut self) -> Option; 185 | } 186 | } 187 | 188 | #[cfg(unix)] 189 | mod nix; 190 | #[cfg(unix)] 191 | pub use nix::*; 192 | #[cfg(unix)] 193 | pub type TextboxImpl = nix::TermboxWrapper; 194 | 195 | #[cfg(windows)] 196 | mod win; 197 | #[cfg(windows)] 198 | pub use win::*; 199 | #[cfg(windows)] 200 | pub type TextboxImpl = win::WinConsoleWrapper; 201 | -------------------------------------------------------------------------------- /textbox/src/nix.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_imports)] 3 | #![allow(unused_variables)] 4 | 5 | extern crate termbox_sys; 6 | 7 | use self::termbox_sys::*; 8 | use std::char; 9 | use std::ops::Drop; 10 | use std::os::raw::c_int; 11 | 12 | pub use types::*; 13 | 14 | #[derive(Debug)] 15 | pub struct TermboxWrapper { 16 | } 17 | 18 | fn to_tb_style(s: Style) -> u16 { 19 | let mut tb = TB_DEFAULT; 20 | 21 | if s.contains(BRIGHT) { 22 | tb |= TB_BOLD 23 | } 24 | if s.contains(BOLD) { 25 | tb |= TB_BOLD 26 | } 27 | if s.contains(REVERSE) { 28 | tb |= TB_REVERSE 29 | } 30 | if s.contains(UNDERLINE) { 31 | tb |= TB_UNDERLINE 32 | } 33 | 34 | if s.contains(WHITE) { 35 | tb |= TB_WHITE 36 | } else if s.contains(MAGENTA) { 37 | tb |= TB_MAGENTA 38 | } else if s.contains(CYAN) { 39 | tb |= TB_CYAN 40 | } else if s.contains(YELLOW) { 41 | tb |= TB_YELLOW 42 | } else if s.contains(BLUE) { 43 | tb |= TB_BLUE 44 | } else if s.contains(GREEN) { 45 | tb |= TB_GREEN 46 | } else if s.contains(RED) { 47 | tb |= TB_RED 48 | } else if s.contains(BLACK) { 49 | tb |= TB_BLACK 50 | } else if s.contains(DEFAULT) { 51 | tb |= TB_DEFAULT 52 | } 53 | 54 | tb 55 | } 56 | 57 | fn to_mods(raw: u8) -> Mod { NO_MODS } 58 | 59 | fn to_event(raw: RawEvent) -> Option { 60 | if raw.etype == TB_EVENT_KEY { 61 | let mut ch = char::from_u32(raw.ch).unwrap(); 62 | let mut mods = to_mods(raw.emod); 63 | let kc = match raw.key { 64 | kc if kc == TB_KEY_ESC => Key::Escape, 65 | kc if kc == TB_KEY_ARROW_LEFT => Key::Left, 66 | kc if kc == TB_KEY_ARROW_UP => Key::Up, 67 | kc if kc == TB_KEY_ARROW_RIGHT => Key::Right, 68 | kc if kc == TB_KEY_ARROW_DOWN => Key::Down, 69 | kc if kc == TB_KEY_HOME => Key::Home, 70 | kc if kc == TB_KEY_END => Key::End, 71 | kc if kc == TB_KEY_PGUP => Key::PageUp, 72 | kc if kc == TB_KEY_PGDN => Key::PageDown, 73 | kc if /*kc == TB_KEY_BACKSPACE || */kc == TB_KEY_BACKSPACE2 => Key::Backspace, 74 | kc if kc == TB_KEY_DELETE => Key::Delete, 75 | kc if kc == TB_KEY_TAB => Key::Tab, 76 | kc if kc == TB_KEY_ENTER => Key::Enter, 77 | kc if kc == TB_KEY_SPACE => { 78 | ch = ' '; 79 | Key::Char(' ') 80 | } 81 | kc if kc == TB_KEY_CTRL_F => { 82 | mods |= CTRL; 83 | Key::Char('F') 84 | } 85 | kc if kc == TB_KEY_CTRL_G => { 86 | mods |= CTRL; 87 | Key::Char('G') 88 | } 89 | kc if kc == TB_KEY_CTRL_H => { 90 | mods |= CTRL; 91 | Key::Char('H') 92 | } 93 | kc if kc == TB_KEY_CTRL_J => { 94 | mods |= CTRL; 95 | Key::Char('J') 96 | } 97 | kc if kc == TB_KEY_CTRL_K => { 98 | mods |= CTRL; 99 | Key::Char('K') 100 | } 101 | kc if kc == TB_KEY_CTRL_L => { 102 | mods |= CTRL; 103 | Key::Char('L') 104 | } 105 | kc if kc == TB_KEY_CTRL_S => { 106 | mods |= CTRL; 107 | Key::Char('S') 108 | } 109 | kc if kc == TB_KEY_CTRL_Q => { 110 | mods |= CTRL; 111 | Key::Char('Q') 112 | } 113 | // kc if kc >= w::VK_F1 as u16 && kc <= w::VK_F24 as u16 => { 114 | // Key::F((kc - w::VK_F1 as u16 + 1) as u8) 115 | // } 116 | // // Doesn't handle shift. 117 | // // Letters 118 | // kc if kc >= 65 && kc <= 90 => Key::Char(kc as u8 as char), 119 | // // Numbers 120 | // kc if kc >= 48 && kc <= 57 => Key::Char(kc as u8 as char), 121 | _ => Key::Char('\0'), 122 | }; 123 | Some(Event::Key(ch, mods, kc)) 124 | } else { 125 | None 126 | } 127 | } 128 | 129 | impl Textbox for TermboxWrapper { 130 | fn init() -> Result { 131 | unsafe { 132 | let err = tb_init(); 133 | if err != 0 { 134 | panic!("tb_init failed! {}", err); 135 | } 136 | } 137 | Ok(TermboxWrapper {}) 138 | } 139 | 140 | fn size(&self) -> Coord { 141 | let cols = unsafe { tb_width() }; 142 | let rows = unsafe { tb_height() }; 143 | Coord(cols as usize, rows as usize) 144 | } 145 | 146 | fn set_clear_style(&mut self, fg: Style, bg: Style) { 147 | let tb_fg = to_tb_style(fg); 148 | let tb_bg = to_tb_style(bg); 149 | unsafe { tb_set_clear_attributes(tb_fg, tb_bg) } 150 | } 151 | fn clear(&mut self) { unsafe { tb_clear() } } 152 | 153 | fn present(&mut self) { unsafe { tb_present() } } 154 | 155 | fn set_cursor(&mut self, coord: Coord) { 156 | unsafe { tb_set_cursor(coord.0 as c_int, coord.1 as c_int) } 157 | } 158 | fn hide_cursor(&mut self) { 159 | unsafe { tb_set_cursor(TB_HIDE_CURSOR, TB_HIDE_CURSOR) } 160 | } 161 | 162 | fn put_cell(&mut self, coord: Coord, cell: Cell) { 163 | let tb_ch = cell.ch as u32; 164 | let tb_fg = to_tb_style(cell.fg); 165 | let tb_bg = to_tb_style(cell.bg); 166 | unsafe { 167 | tb_change_cell(coord.0 as c_int, coord.1 as c_int, tb_ch, tb_fg, tb_bg) 168 | } 169 | } 170 | 171 | fn set_input_mode(&mut self, _: InputMode) -> InputMode { unimplemented!() } 172 | fn set_output_mode(&mut self, _: OutputMode) -> OutputMode { 173 | unimplemented!() 174 | } 175 | 176 | fn pop_event(&mut self) -> Option { 177 | use std::mem::uninitialized; 178 | unsafe { 179 | let mut raw: RawEvent = uninitialized(); 180 | let ty = tb_poll_event(&mut raw as *mut RawEvent); 181 | if ty > 0 { to_event(raw) } else { None } 182 | } 183 | } 184 | } 185 | 186 | impl Drop for TermboxWrapper { 187 | fn drop(&mut self) { 188 | unsafe { 189 | tb_shutdown(); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /textbox/src/win.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_imports)] 3 | 4 | extern crate winapi; 5 | extern crate wio; 6 | extern crate kernel32; 7 | 8 | use bit_set::BitSet; 9 | use self::winapi as w; 10 | use self::wio::console::{CharInfo, Input, InputBuffer, ScreenBuffer}; 11 | use std::collections::VecDeque; 12 | 13 | pub use types::*; 14 | 15 | pub struct WinConsoleWrapper { 16 | stdin: InputBuffer, 17 | events: VecDeque, 18 | frontbuf: ScreenBuffer, 19 | backbuf: Box<[Cell]>, 20 | dirty_rows: BitSet, 21 | size: Coord, 22 | fg_clear: Style, 23 | bg_clear: Style, 24 | } 25 | 26 | fn to_fg(s: Style) -> u16 { 27 | use super::*; 28 | 29 | let mut fg = 0; 30 | 31 | if s.contains(BRIGHT) { 32 | fg |= w::FOREGROUND_INTENSITY 33 | } 34 | if s.contains(BOLD) { 35 | fg |= w::FOREGROUND_INTENSITY; 36 | } 37 | if s.contains(REVERSE) { 38 | fg |= w::COMMON_LVB_REVERSE_VIDEO 39 | } 40 | if s.contains(UNDERLINE) { 41 | fg |= w::COMMON_LVB_UNDERSCORE 42 | } 43 | 44 | if s.contains(WHITE) || s.contains(DEFAULT) { 45 | fg |= w::FOREGROUND_RED | w::FOREGROUND_GREEN | w::FOREGROUND_BLUE; 46 | } else if s.contains(MAGENTA) { 47 | fg |= w::FOREGROUND_RED | w::FOREGROUND_BLUE; 48 | } else if s.contains(CYAN) { 49 | fg |= w::FOREGROUND_GREEN | w::FOREGROUND_BLUE; 50 | } else if s.contains(YELLOW) { 51 | fg |= w::FOREGROUND_RED | w::FOREGROUND_GREEN; 52 | } else if s.contains(BLUE) { 53 | fg |= w::FOREGROUND_BLUE; 54 | } else if s.contains(GREEN) { 55 | fg |= w::FOREGROUND_GREEN; 56 | } else if s.contains(RED) { 57 | fg |= w::FOREGROUND_RED; 58 | } else if s.contains(BLACK) { 59 | // do nothing 60 | } 61 | 62 | fg 63 | } 64 | 65 | fn to_bg(s: Style) -> u16 { 66 | use super::*; 67 | 68 | let mut bg = 0; 69 | 70 | if s.contains(BRIGHT) { 71 | bg |= w::BACKGROUND_INTENSITY 72 | } 73 | if s.contains(BOLD) { 74 | bg |= w::BACKGROUND_INTENSITY; 75 | } 76 | if s.contains(REVERSE) { 77 | bg |= w::COMMON_LVB_REVERSE_VIDEO 78 | } 79 | if s.contains(UNDERLINE) { 80 | bg |= w::COMMON_LVB_UNDERSCORE 81 | } 82 | 83 | if s.contains(WHITE) { 84 | bg |= w::BACKGROUND_RED | w::BACKGROUND_GREEN | w::BACKGROUND_BLUE; 85 | } else if s.contains(MAGENTA) { 86 | bg |= w::BACKGROUND_RED | w::BACKGROUND_BLUE; 87 | } else if s.contains(CYAN) { 88 | bg |= w::BACKGROUND_GREEN | w::BACKGROUND_BLUE; 89 | } else if s.contains(YELLOW) { 90 | bg |= w::BACKGROUND_RED | w::BACKGROUND_GREEN; 91 | } else if s.contains(BLUE) { 92 | bg |= w::BACKGROUND_BLUE; 93 | } else if s.contains(GREEN) { 94 | bg |= w::BACKGROUND_GREEN; 95 | } else if s.contains(RED) { 96 | bg |= w::BACKGROUND_RED; 97 | } 98 | 99 | bg 100 | } 101 | 102 | fn to_mod(raw: u32) -> Mod { 103 | let mut m = NO_MODS; 104 | 105 | if raw & w::RIGHT_ALT_PRESSED > 0 { 106 | m |= ALT 107 | } 108 | if raw & w::LEFT_ALT_PRESSED > 0 { 109 | m |= ALT 110 | } 111 | 112 | if raw & w::RIGHT_CTRL_PRESSED > 0 { 113 | m |= CTRL 114 | } 115 | if raw & w::LEFT_CTRL_PRESSED > 0 { 116 | m |= CTRL 117 | } 118 | 119 | if raw & w::SHIFT_PRESSED > 0 { 120 | m |= SHIFT 121 | } 122 | // if raw & w::CAPSLOCK_ON > 0 { 123 | // m |= CAPS_LOCK 124 | // } 125 | // if raw & w::NUMLOCK_ON > 0 { 126 | // m |= NUM_LOCK 127 | // } 128 | // if raw & w::SCROLLLOCK_ON > 0 { 129 | // m |= SCROLL_LOCK 130 | // } 131 | 132 | // META 133 | // MENU 134 | // if raw & w::RIGHT_CTRL_PRESSED > 0 { 135 | // m |= RIGHT_CTRL 136 | // } 137 | // if raw & w::LEFT_CTRL_PRESSED > 0 { 138 | // m |= LEFT_CTRL 139 | // } 140 | 141 | m 142 | } 143 | 144 | fn to_charinfo(c: Cell) -> CharInfo { 145 | CharInfo::new(c.ch as u16, to_fg(c.fg) | to_bg(c.bg)) 146 | } 147 | 148 | fn to_event(raw: Input) -> Option { 149 | println!("{:?}", raw); 150 | match raw { 151 | Input::Key { key_down, key_code, wide_char, control_key_state, .. } => { 152 | if key_down { 153 | let ch = wide_char as u8 as char; 154 | let mods = to_mod(control_key_state); 155 | let kc = match key_code { 156 | kc if kc == w::VK_ESCAPE as u16 => Key::Escape, 157 | kc if kc == w::VK_LEFT as u16 => Key::Left, 158 | kc if kc == w::VK_UP as u16 => Key::Up, 159 | kc if kc == w::VK_RIGHT as u16 => Key::Right, 160 | kc if kc == w::VK_DOWN as u16 => Key::Down, 161 | kc if kc == w::VK_DOWN as u16 => Key::Down, 162 | kc if kc == w::VK_INSERT as u16 => Key::Insert, 163 | kc if kc == w::VK_DELETE as u16 => Key::Delete, 164 | kc if kc == w::VK_HOME as u16 => Key::Home, 165 | kc if kc == w::VK_END as u16 => Key::End, 166 | kc if kc == w::VK_PRIOR as u16 => Key::PageUp, 167 | kc if kc == w::VK_NEXT as u16 => Key::PageDown, 168 | kc if kc == w::VK_BACK as u16 => Key::Backspace, 169 | kc if kc == w::VK_TAB as u16 => Key::Tab, 170 | kc if kc == w::VK_RETURN as u16 => Key::Enter, 171 | kc if kc >= w::VK_F1 as u16 && kc <= w::VK_F24 as u16 => { 172 | Key::F((kc - w::VK_F1 as u16 + 1) as u8) 173 | } 174 | // Doesn't handle shift. 175 | // Printable characters. 176 | kc if kc >= 0x20 && kc <= 0x7e => Key::Char(kc as u8 as char), 177 | _ => Key::Char('\0'), 178 | }; 179 | if ch == '\0' && !mods.is_empty() { 180 | None 181 | } else { 182 | Some(Event::Key(ch, mods, kc)) 183 | } 184 | } else { 185 | None 186 | } 187 | } 188 | _ => None, 189 | } 190 | } 191 | 192 | impl Textbox for WinConsoleWrapper { 193 | fn init() -> Result { 194 | let stdin = InputBuffer::from_conin().unwrap(); 195 | let frontbuf = ScreenBuffer::new().unwrap(); 196 | let (cols, rows) = frontbuf.info().unwrap().size(); 197 | let cell_count = (rows * cols) as usize; 198 | let backbuf = vec![Cell { ch: ' ', fg: DEFAULT, bg: DEFAULT, }; cell_count] 199 | .into_boxed_slice(); 200 | frontbuf.set_active().unwrap(); 201 | stdin.set_console_mode(w::ENABLE_WINDOW_INPUT | w::ENABLE_MOUSE_INPUT | 202 | w::ENABLE_EXTENDED_FLAGS) 203 | .unwrap(); 204 | Ok(WinConsoleWrapper { 205 | stdin: stdin, 206 | events: VecDeque::new(), 207 | backbuf: backbuf, 208 | frontbuf: frontbuf, 209 | size: Coord(cols as usize, rows as usize), 210 | fg_clear: DEFAULT, 211 | bg_clear: DEFAULT, 212 | dirty_rows: BitSet::with_capacity(rows as usize), 213 | }) 214 | } 215 | 216 | fn size(&self) -> Coord { self.size } 217 | 218 | fn set_clear_style(&mut self, fg: Style, bg: Style) { 219 | self.fg_clear = fg; 220 | self.bg_clear = bg; 221 | } 222 | fn clear(&mut self) { 223 | for row in 0..self.size.1 { 224 | self.dirty_rows.insert(row); 225 | } 226 | for ref mut cell in &mut *self.backbuf { 227 | cell.ch = ' '; 228 | cell.fg = self.fg_clear; 229 | cell.bg = self.bg_clear; 230 | } 231 | } 232 | 233 | fn present(&mut self) { 234 | if self.dirty_rows.len() == self.size.1 { 235 | let slice: Vec = self.backbuf 236 | .iter() 237 | .map(|&cell| to_charinfo(cell)) 238 | .collect(); 239 | self.frontbuf 240 | .write_output(&slice, (self.size.0 as i16, self.size.1 as i16), (0, 0)) 241 | .unwrap(); 242 | } else { 243 | for row in self.dirty_rows.iter() { 244 | let slice: Vec = self.backbuf[row * self.size.0..(row + 1) * 245 | self.size.0] 246 | .iter() 247 | .map(|&cell| to_charinfo(cell)) 248 | .collect(); 249 | self.frontbuf 250 | .write_output(&slice, (self.size.0 as i16, 1), (0, row as i16)) 251 | .unwrap(); 252 | } 253 | } 254 | self.dirty_rows.clear() 255 | } 256 | 257 | fn set_cursor(&mut self, coord: Coord) { 258 | // TODO: Should this make these changes to the backbuf? 259 | if coord.0 < self.size.0 && coord.1 < self.size.1 { 260 | self.frontbuf 261 | .set_cursor_position((coord.0 as i16, coord.1 as i16)) 262 | .unwrap() 263 | } 264 | } 265 | fn hide_cursor(&mut self) { unimplemented!() } 266 | 267 | fn put_cell(&mut self, coord: Coord, cell: Cell) { 268 | if coord.0 < self.size.0 && coord.1 < self.size.1 { 269 | self.dirty_rows.insert(coord.1); 270 | self.backbuf[coord.0 + coord.1 * self.size.0] = cell 271 | } 272 | } 273 | 274 | fn set_input_mode(&mut self, _: InputMode) -> InputMode { unimplemented!() } 275 | fn set_output_mode(&mut self, _: OutputMode) -> OutputMode { 276 | OutputMode::Normal 277 | } 278 | 279 | fn pop_event(&mut self) -> Option { 280 | if self.events.len() > 0 { 281 | to_event(self.events.pop_front().unwrap()) 282 | } else 283 | // if self.stdin.available_input().unwrap_or(0) > 0 284 | { 285 | match self.stdin.read_input() { 286 | Ok(inputs) => { 287 | self.events.extend(inputs); 288 | if let Some(e) = to_event(self.events.pop_front().unwrap()) { 289 | Some(e) 290 | } else { 291 | // TODO: Possible stack overflow. 292 | self.pop_event() 293 | } 294 | } 295 | Err(_) => None, 296 | } 297 | // } else { 298 | // None 299 | } 300 | } 301 | } 302 | 303 | // pub fn set_cells(&mut self, 304 | // x: i16, 305 | // y: i16, 306 | // chs: &str, 307 | // fg: Color, 308 | // bg: Color) { 309 | // let color = (bg | fg).bits() as u16; 310 | // let cis: Vec<_> = chs.chars() 311 | // .map(|ch| CharInfo::new(ch as u16, color)) 312 | // .collect(); 313 | // self.backbuf 314 | // .write_output(&cis, (chs.len() as i16, 1), (x, y)) 315 | // .unwrap(); 316 | // } 317 | --------------------------------------------------------------------------------