Browse Source

Ugly progress commit

master
Timothy Warren 1 month ago
parent
commit
e6d3995e4c
6 changed files with 105 additions and 52 deletions
  1. 7
    0
      Cargo.lock
  2. 1
    0
      Cargo.toml
  3. 2
    1
      README.md
  4. 63
    20
      src/editor.rs
  5. 23
    14
      src/main.rs
  6. 9
    17
      src/terminal_helpers.rs

+ 7
- 0
Cargo.lock View File

@@ -15,6 +15,11 @@ name = "cfg-if"
15 15
 version = "0.1.9"
16 16
 source = "registry+https://github.com/rust-lang/crates.io-index"
17 17
 
18
+[[package]]
19
+name = "lazy_static"
20
+version = "1.4.0"
21
+source = "registry+https://github.com/rust-lang/crates.io-index"
22
+
18 23
 [[package]]
19 24
 name = "libc"
20 25
 version = "0.2.62"
@@ -37,6 +42,7 @@ name = "rs-kilo"
37 42
 version = "0.1.0"
38 43
 dependencies = [
39 44
  "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
45
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
40 46
  "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
41 47
  "nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
42 48
 ]
@@ -50,6 +56,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
50 56
 "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd"
51 57
 "checksum cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "b548a4ee81fccb95919d4e22cfea83c7693ebfd78f0495493178db20b3139da7"
52 58
 "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
59
+"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
53 60
 "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba"
54 61
 "checksum nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229"
55 62
 "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"

+ 1
- 0
Cargo.toml View File

@@ -8,6 +8,7 @@ edition = "2018"
8 8
 
9 9
 [dependencies]
10 10
 bitflags = "1.1.0"
11
+lazy_static = "1.4.0"
11 12
 libc = "0.2"
12 13
 # Rust wrappers for C/POSIX headers
13 14
 nix = "0.15.0"

+ 2
- 1
README.md View File

@@ -8,7 +8,8 @@ tutorial with a Rust implementation.
8 8
 uses `impl`s on a shared `Editor` struct, the prefix is redundant
9 9
 * Any C equivalent functionality based on memory allocating/deallocating, or other manual book-keeping is instead
10 10
 implemented in a more idiomatic Rust fashion.
11
-
11
+* Row structs are referenced by their index in the Editor struct, rather than as a direct reference in method calls. 
12
+This generally simplifies dealing with the rules of Rust (borrow checker).
12 13
 
13 14
 ### Known issues:
14 15
 * The cursor is invisible :(

+ 63
- 20
src/editor.rs View File

@@ -1,5 +1,5 @@
1 1
 //! Editor functionality
2
-use crate::helpers::*;
2
+use crate::terminal_helpers::*;
3 3
 
4 4
 use std::cmp::PartialEq;
5 5
 use std::fs::File;
@@ -208,7 +208,12 @@ impl Default for Editor {
208 208
             dirty: 0,
209 209
             filename: String::new(),
210 210
             status_message: String::new(),
211
+
212
+            // This is the only reason I had to implement this method
213
+            // manually, instead of it being derived. Apparently an
214
+            // `Instant` struct has no default
211 215
             status_message_time: Instant::now(),
216
+
212 217
             syntax: None,
213 218
 
214 219
             output_buffer: String::new(),
@@ -255,7 +260,8 @@ impl Editor {
255 260
             Ok(_) => (),
256 261
             Err(e) => {
257 262
                 if e.kind() != io::ErrorKind::UnexpectedEof {
258
-                    panic!(e);
263
+                    let error = format!("{:?}", e);
264
+                    self.set_status_message(&error);
259 265
                 }
260 266
             }
261 267
         }
@@ -289,7 +295,8 @@ impl Editor {
289 295
             Ok(_) => (),
290 296
             Err(e) => {
291 297
                 if e.kind() != io::ErrorKind::UnexpectedEof {
292
-                    panic!(e);
298
+                    let error = format!("{:?}", e);
299
+                    self.set_status_message(&error);
293 300
                 }
294 301
             }
295 302
         }
@@ -403,6 +410,10 @@ impl Editor {
403 410
     fn get_window_size(&mut self) -> TermSize {
404 411
         match get_term_size() {
405 412
             Some(size) => size,
413
+
414
+            // I could have implemented this, but I felt that parsing
415
+            // an escape code from stdin was of minimal value,
416
+            // when the ioctrl method works on any computer I've tried
406 417
             None => unimplemented!("The easy way usually works"),
407 418
         }
408 419
     }
@@ -413,19 +424,19 @@ impl Editor {
413 424
 
414 425
     fn update_syntax(&mut self, index: usize) {
415 426
         let rows = &mut self.rows;
416
-
417 427
         let prev_row = if index > 0 {
418 428
             // I shouldn't have to clone this, but the lifetime is
419 429
             // different than the `row` variable above, so it
420 430
             // can't be a immutable borrow. It also can't be a
421 431
             // mutable borrow, because it would be considered a
422
-            // second mutable borrow
432
+            // second mutable borrow...so a clone it is
423 433
             Some((&mut rows[index - 1]).clone())
424 434
         } else {
425 435
             None
426 436
         };
427
-
428 437
         let row = &mut rows[index];
438
+
439
+        // Reset the highlighting of the row
429 440
         row.highlight = vec![Highlight::Normal; row.render.len()];
430 441
 
431 442
         if self.syntax.is_none() {
@@ -444,7 +455,7 @@ impl Editor {
444 455
         let mcs = &current_syntax.multiline_comment_start;
445 456
         let mce = &current_syntax.multiline_comment_end;
446 457
 
447
-        let mut prev_separator = false;
458
+        let mut prev_separator = true;
448 459
         let mut in_string = false;
449 460
         let mut str_start = '\0';
450 461
         let mut in_comment = prev_row.map_or(false, |row| row.highlight_comment_start);
@@ -574,7 +585,6 @@ impl Editor {
574 585
                     if &row.render[search_range.clone()] == keyword && is_separator(next_char) {
575 586
                         highlight_range(&mut row.highlight, search_range, Highlight::Keyword1);
576 587
                         i += keyword.len();
577
-                        break;
578 588
                     }
579 589
                 }
580 590
 
@@ -592,7 +602,6 @@ impl Editor {
592 602
                     if &row.render[search_range.clone()] == keyword && is_separator(next_char) {
593 603
                         highlight_range(&mut row.highlight, search_range, Highlight::Keyword2);
594 604
                         i += keyword.len();
595
-                        break;
596 605
                     }
597 606
                 }
598 607
             }
@@ -927,16 +936,16 @@ impl Editor {
927 936
                     // Center welcome message
928 937
                     let mut padding = (self.screen_cols - welcome.len()) / 2;
929 938
                     if padding > 0 {
930
-                        self.append_out("~");
939
+                        self.append_out_char('~');
931 940
                         padding -= 1;
932 941
                     }
933 942
                     for _ in 0..padding {
934
-                        self.append_out(" ");
943
+                        self.append_out_char(' ');
935 944
                     }
936 945
 
937 946
                     self.append_out(&welcome);
938 947
                 } else {
939
-                    self.append_out("~");
948
+                    self.append_out_char('~');
940 949
                 }
941 950
             } else {
942 951
                 let output = self.rows[file_row].render.clone();
@@ -1045,8 +1054,8 @@ impl Editor {
1045 1054
         self.draw_message_bar();
1046 1055
 
1047 1056
         // Move cursor to state position
1048
-        let y = (self.cursor_y - self.row_offset) + 1;
1049
-        let x = (self.render_x - self.col_offset) + 1;
1057
+        let y = self.cursor_y - self.row_offset + 1;
1058
+        let x = self.render_x - self.col_offset + 1;
1050 1059
         let cursor_code = format!("\x1b[{y};{x}H", y = y, x = x);
1051 1060
         self.append_out(&cursor_code);
1052 1061
 
@@ -1265,8 +1274,8 @@ impl Editor {
1265 1274
         for row in &self.rows {
1266 1275
             // When the file is opened, newlines are stripped
1267 1276
             // make sure to add them back when saving!
1268
-            let row_chars = row.chars.clone() + "\n";
1269
-            output.push_str(&row_chars)
1277
+            output += &row.chars;
1278
+            output.push('\n');
1270 1279
         }
1271 1280
 
1272 1281
         output
@@ -1281,7 +1290,7 @@ impl Editor {
1281 1290
         let file = File::open(&self.filename)?;
1282 1291
         let buf_reader = BufReader::new(file);
1283 1292
 
1284
-        let lines = buf_reader.lines().map(|l| l.unwrap());
1293
+        let lines = buf_reader.lines().map(|l| clean_unwrap(l));
1285 1294
 
1286 1295
         for line in lines {
1287 1296
             self.insert_row(self.rows.len(), &line);
@@ -1299,6 +1308,7 @@ impl Editor {
1299 1308
                 self.set_status_message("Save aborted");
1300 1309
                 return Ok(());
1301 1310
             }
1311
+
1302 1312
             self.select_syntax_highlight();
1303 1313
         }
1304 1314
 
@@ -1411,6 +1421,7 @@ impl Editor {
1411 1421
 // Functions
1412 1422
 // ------------------------------------------------------------------------
1413 1423
 
1424
+/// Get the language highlighting config
1414 1425
 fn get_syntax_db() -> Vec<Syntax> {
1415 1426
     vec![
1416 1427
         Syntax::new(
@@ -1421,7 +1432,8 @@ fn get_syntax_db() -> Vec<Syntax> {
1421 1432
                 "union", "class", "else", "enum", "for", "case", "if",
1422 1433
             ],
1423 1434
             vec![
1424
-                "unsigned", "double", "signed", "float", "long", "char", "int", "void",
1435
+                "#include", "unsigned", "#define", "#ifndef", "double", "signed", "#endif",
1436
+                "#ifdef", "float", "#error", "#undef", "long", "char", "int", "void", "#if",
1425 1437
             ],
1426 1438
             "//",
1427 1439
             "/*",
@@ -1504,6 +1516,20 @@ fn get_syntax_db() -> Vec<Syntax> {
1504 1516
     ]
1505 1517
 }
1506 1518
 
1519
+/// Convert Ctrl+letter chords to their
1520
+/// ASCII table equivalents
1521
+pub fn ctrl_key(c: char) -> char {
1522
+    let key = c as u8;
1523
+
1524
+    if !c.is_ascii() {
1525
+        panic!("CTRL_KEY only accepts ASCII characters");
1526
+    }
1527
+
1528
+    // Intentionally "overflow" u8 to wrap around to the
1529
+    // beginning of the ASCII table. Ctrl+a is 1, Ctrl+b is 2, etc.
1530
+    (key & 0x1f) as char
1531
+}
1532
+
1507 1533
 /// Determine whether a character is one which separates tokens
1508 1534
 /// in the language to highlight
1509 1535
 fn is_separator(input_char: char) -> bool {
@@ -1522,7 +1548,8 @@ fn is_separator(input_char: char) -> bool {
1522 1548
     false
1523 1549
 }
1524 1550
 
1525
-/// Get a range for a slice of a string or vector.
1551
+/// Get a range for a slice of a string or vector, checking the length of the
1552
+/// string or vector to prevent panics on invalid access.
1526 1553
 ///
1527 1554
 /// If `start` to `start + search_len`, is within the size of the search target (`haystack_len`)
1528 1555
 /// that range is returned. Otherwise, the range is from `start` to `haystack_len`.
@@ -1543,12 +1570,28 @@ fn highlight_range(vec: &mut Vec<Highlight>, range: Range<usize>, value: Highlig
1543 1570
     }
1544 1571
 }
1545 1572
 
1573
+/// Do the equivalent of a Result::unwrap, but cleanup terminal output
1574
+/// first, so it doesn't destroy console output afterwards.
1575
+fn clean_unwrap<O, E>(res: Result<O, E>) -> O
1576
+where
1577
+    E: std::fmt::Debug,
1578
+{
1579
+    match res {
1580
+        Ok(value) => value,
1581
+        Err(e) => {
1582
+            disable_raw_mode();
1583
+            println!("\r\n");
1584
+            panic!("{:?}", e);
1585
+        }
1586
+    }
1587
+}
1588
+
1546 1589
 #[cfg(test)]
1547 1590
 mod tests {
1548 1591
     use super::*;
1549 1592
 
1550 1593
     #[test]
1551
-    fn select_syntax_hightlight_selects_language() {
1594
+    fn select_syntax_highlight_selects_language() {
1552 1595
         let langs = get_syntax_db();
1553 1596
         let mut editor = Editor::new();
1554 1597
         editor.filename = String::from("foo.c");

+ 23
- 14
src/main.rs View File

@@ -1,22 +1,30 @@
1 1
 #[macro_use]
2 2
 extern crate bitflags;
3 3
 
4
+#[macro_use]
5
+extern crate lazy_static;
6
+
4 7
 mod editor;
5
-mod helpers;
8
+mod terminal_helpers;
6 9
 
7 10
 use crate::editor::Editor;
8
-use crate::helpers::*;
11
+use crate::terminal_helpers::*;
9 12
 use libc::STDIN_FILENO;
13
+use nix::sys::termios::Termios;
10 14
 use std::env;
11 15
 use std::io::Error;
12 16
 use std::result::Result;
17
+use std::sync::{Arc, Mutex};
18
+
19
+// Much ugliness to get and keep a reference to the original terminal settings
20
+lazy_static! {
21
+    // Save terminal flags on start
22
+    pub static ref ORIGINAL_TERMIOS: Arc<Mutex<Termios>> = Arc::new(Mutex::new(get_termios(STDIN_FILENO)));
23
+}
13 24
 
14 25
 fn main() -> Result<(), Error> {
15 26
     let args: Vec<String> = env::args().collect();
16 27
 
17
-    // Save original terminal flags
18
-    let original_termios = get_termios(STDIN_FILENO);
19
-
20 28
     // Disable canonical/"cooked" terminal mode
21 29
     enable_raw_mode();
22 30
 
@@ -36,19 +44,20 @@ fn main() -> Result<(), Error> {
36 44
     loop {
37 45
         editor.refresh_screen();
38 46
 
39
-        let key = editor.process_keypress();
40
-        if key.is_none() {
41
-            break;
47
+        match editor.process_keypress() {
48
+            Some(_key) => {
49
+                /* match key {
50
+                    editor::EditorKey::OtherKey('\0') => (),
51
+                    _ => println!("{:?}\r\n", key)
52
+                }*/
53
+                ()
54
+            }
55
+            None => break,
42 56
         }
43
-
44
-        /*match key.unwrap() {
45
-            editor::EditorKey::OtherKey('\0') => (),
46
-            _ => println!("{:?}\r\n", key)
47
-        } */
48 57
     }
49 58
 
50 59
     // Restore previous terminal flags before exit
51
-    disable_raw_mode(&original_termios);
60
+    disable_raw_mode();
52 61
 
53 62
     Ok(())
54 63
 }

src/helpers.rs → src/terminal_helpers.rs View File

@@ -1,3 +1,6 @@
1
+//! # Helpers
2
+//!
3
+//! Various functions calling C wrappers to get/set terminal functionality
1 4
 use libc::ioctl;
2 5
 use libc::{c_ushort, STDIN_FILENO, STDOUT_FILENO, TIOCGWINSZ};
3 6
 use nix::sys::termios;
@@ -5,6 +8,7 @@ use nix::sys::termios::{
5 8
     ControlFlags, InputFlags, LocalFlags, OutputFlags, SpecialCharacterIndices, Termios,
6 9
 };
7 10
 use std::os::unix::io::RawFd;
11
+use std::sync::Arc;
8 12
 
9 13
 #[derive(Debug)]
10 14
 pub struct TermSize {
@@ -25,21 +29,6 @@ struct UnixTermSize {
25 29
     y: c_ushort,
26 30
 }
27 31
 
28
-/// Convert Ctrl+letter chords to their
29
-/// ASCII table equivalents
30
-#[inline]
31
-pub fn ctrl_key(c: char) -> char {
32
-    let key = c as u8;
33
-
34
-    if !c.is_ascii() {
35
-        panic!("CTRL_KEY only accepts ASCII characters");
36
-    }
37
-
38
-    // Intentionally "overflow" u8 to wrap around to the
39
-    // beginning of the ASCII table. Ctrl+a is 1, Ctrl+b is 2, etc.
40
-    (key & 0x1f) as char
41
-}
42
-
43 32
 /// Get a `Termios` struct, for getting/setting terminal flags
44 33
 pub fn get_termios(fd: RawFd) -> Termios {
45 34
     termios::tcgetattr(fd).unwrap()
@@ -73,8 +62,11 @@ pub fn enable_raw_mode() {
73 62
 }
74 63
 
75 64
 /// Restore terminal to "cooked"/canonical mode
76
-pub fn disable_raw_mode(original: &Termios) {
77
-    termios::tcsetattr(STDIN_FILENO, termios::SetArg::TCSAFLUSH, original).unwrap();
65
+pub fn disable_raw_mode() {
66
+    let mutex = Arc::clone(&super::ORIGINAL_TERMIOS);
67
+    let termios = mutex.lock().unwrap();
68
+
69
+    termios::tcsetattr(STDIN_FILENO, termios::SetArg::TCSAFLUSH, &termios).unwrap();
78 70
 }
79 71
 
80 72
 /// Attempt to get the size of the terminal (in rows and columns) from an `ioctl` call

Loading…
Cancel
Save