summaryrefslogtreecommitdiffstats
path: root/src/buffer.rs
blob: 8668249b769de2467ba77a537e38e63cc355e46a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
use std::io::{self, Read, Write};
use std::sync::{Arc, Mutex};

// STDIN is file descriptor (fd) 0 on Linux and other UN*X-likes
pub const STDIN: i32 = 0;

// Key input types from the user
enum Key {
    Up,
    Down,
    Right,
    Left,
    Else(u8),
    Ignored,
}

/// Retrieve a single byte of input
///
/// Requires some setup beforehand (see beginning of repl())
fn getchar() -> Key {
    let mut b = [0; 1];
    io::stdout().lock().flush().unwrap();
    io::stdin().read_exact(&mut b).unwrap();

    // Might me an ASNI escape sequence
    if b[0] == 27 {
        io::stdin().read_exact(&mut b).unwrap();

        if b[0] == 91 {
            io::stdin().read_exact(&mut b).unwrap();

            match b[0] {
                // Arrow keys
                65 => return Key::Up,
                66 => return Key::Down,
                67 => return Key::Right,
                68 => return Key::Left,

                // Everything else
                _ => return Key::Ignored,
            }
        }

        return Key::Ignored;
    }

    Key::Else(b[0])
}

/// Handle user input at the repl prompt
///
/// This is required instead of io::stdin().read_line(), because certain
/// keys like `<tab>` and `<up>` have special functions (cycle through
/// autocomplete options, and history, respectively). It leverages
/// [getchar] to read each character as the user inputs it. This also
/// means special cases for handling backspace, newlines, etc. Assumes
/// that (ICANON and ECHO) are off. See the beginning of [crate::repl]
/// for more details.
pub fn getline(buffer: &mut Arc<Mutex<Vec<u8>>>, pos: &mut Arc<Mutex<usize>>) -> usize {
    // Loop over characters until there is a newline
    loop {
        let c = getchar();
        match c {
            Key::Up => {
                continue;
            }
            Key::Down => {
                continue;
            }
            Key::Right => {
                if *pos.lock().unwrap() >= buffer.lock().unwrap().len() {
                    continue;
                }
                print!("\x1b[1C");
                *pos.lock().unwrap() += 1;
            }
            Key::Left => {
                if *pos.lock().unwrap() == 0 {
                    continue;
                }
                print!("\u{8}");
                *pos.lock().unwrap() -= 1;
            }
            Key::Ignored => {
                continue;
            }
            Key::Else(c) => match c {
                // enter/return
                b'\n' => break,

                // tab
                b'\t' => {
                    *pos.lock().unwrap() += 1;
                    print!(" ");
                    buffer.lock().unwrap().push(b' ');
                }

                // ctrl-d
                4 => return 0,

                // backspace
                127 => {
                    if *pos.lock().unwrap() == 0 {
                        continue;
                    }
                    *pos.lock().unwrap() -= 1;

                    if *pos.lock().unwrap() == buffer.lock().unwrap().len() {
                        buffer.lock().unwrap().pop();
                        print!("\u{8} \u{8}");
                    } else {
                        buffer.lock().unwrap().remove(*pos.lock().unwrap());
                        print!(
                            "\u{8}{} ",
                            String::from_utf8_lossy(
                                &buffer.lock().unwrap()[*pos.lock().unwrap()..]
                            )
                        );
                        for _ in *pos.lock().unwrap()..buffer.lock().unwrap().len() + 1 {
                            print!("\u{8}");
                        }
                    }
                }

                // everything else
                _ => {
                    // Print out the character as the user is typing
                    print!("{}", c as char);

                    // Insert the character onto the buffer at whatever *pos.lock().unwrap()ition the cursor is at
                    buffer.lock().unwrap().insert(*pos.lock().unwrap(), c);

                    // Increment our *pos.lock().unwrap()ition
                    *pos.lock().unwrap() += 1;

                    // Reprint the end of the buffer if inserting at the front or middle
                    if *pos.lock().unwrap() != buffer.lock().unwrap().len() {
                        print!(
                            "{}",
                            String::from_utf8_lossy(
                                &buffer.lock().unwrap()[*pos.lock().unwrap()..]
                            )
                        );
                        for _ in *pos.lock().unwrap()..buffer.lock().unwrap().len() {
                            print!("\u{8}");
                        }
                    }
                }
            },
        }
    }

    *pos.lock().unwrap() = 0;
    println!();
    buffer.lock().unwrap().push(b'\n');
    buffer.lock().unwrap().len()
}