summaryrefslogtreecommitdiff
path: root/src/core.rs
blob: 9e6772289a5f08fcd5a2d02034f4a6d52d69e398 (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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
use std::char;
use std::io;
use std::marker::PhantomData;
use std::sync::atomic::{AtomicBool, Ordering};

use crate::bindings;
use crate::util;

pub const KEY_TAB: i32 = bindings::KEY_TAB as i32;
pub const KEY_LF: i32 = bindings::KEY_LF as i32;
pub const KEY_CR: i32 = bindings::KEY_CR as i32;
pub const KEY_ESC: i32 = bindings::KEY_ESC as i32;
pub const KEY_BACKSPACE: i32 = bindings::KEY_BACKSPACE as i32;

pub const KEY_RIGHT: i32 = bindings::KEY_RIGHT as i32;
pub const KEY_UP: i32 = bindings::KEY_UP as i32;
pub const KEY_LEFT: i32 = bindings::KEY_LEFT as i32;
pub const KEY_DOWN: i32 = bindings::KEY_DOWN as i32;
pub const KEY_PAGEUP: i32 = bindings::KEY_PAGEUP as i32;
pub const KEY_PAGEDOWN: i32 = bindings::KEY_PAGEDOWN as i32;
pub const KEY_DELETE: i32 = bindings::KEY_DELETE as i32;
pub const KEY_SHIFTTAB: i32 = bindings::KEY_SHIFTTAB as i32;

/// Modifier combinator.
pub const KEY_CTRL: i32 = bindings::KEY_CTRL as i32;
/// Modifier combinator.
pub const KEY_SHIFT: i32 = bindings::KEY_SHIFT as i32;
/// Modifier combinator.
pub const KEY_ALT: i32 = bindings::KEY_ALT as i32;
/// Modifier combinator.
pub const KEY_CTRLALT: i32 = bindings::KEY_CTRLALT as i32;
/// Modifier combinator.
pub const KEY_CTRLSHIFT: i32 = bindings::KEY_CTRLSHIFT as i32;

/// A style descriptor for a cell on the screen.
#[derive(Debug, Copy, Clone)]
pub struct Style {
    /// The foreground color; valid values are 0, 1, ..., 7 (the eight ANSI
    /// colors) and 9 (the default color).
    pub fg: u8,

    /// The background color; valid values are 0, 1, ..., 7 (the eight ANSI
    /// colors) and 9 (the default color).
    pub bg: u8,

    /// Whether the character in the cell should be printed bold. If this is
    /// enabled, the terminal generally uses the bright version of the
    /// foreground color instead of the normal version. Therefore, there are
    /// effectively 16, not 8, foreground colors (in addition to the default
    /// color, 9).
    pub bold: bool,

    /// Whether to display an underline.
    pub ul: bool
}

/// RAII struct for keyboard-input initialization
///
/// Only one instance of this struct can be active at a time. Attempting to
/// create a second instance will panic.
pub struct Keyboard {
    _phantom: PhantomData<*mut u8>,
}

static KEYBOARD_REFCOUNT: AtomicBool = AtomicBool::new(false);

impl Keyboard {
    /// Grab the keyboard, i.e. enable raw mode.
    ///
    /// This disables automatic printing of typed characters (echo), and allows
    /// reading a single character even when a newline has not yet been
    /// entered.
    ///
    /// If `nosigkeys` is true, interrupt keycodes like ctrl-C, ctrl-\ and
    /// ctrl-Z will no longer be handled by the shell but will be available to
    /// be read using `get_key()` just like the other keys.
    pub fn grab(nosigkeys: bool) -> Self {
        let prev = KEYBOARD_REFCOUNT.compare_and_swap(
                        false, true, Ordering::SeqCst);
        if prev {
            panic!("Can only hold one Keyboard struct at a time!");
        }

        unsafe { bindings::initkeyboard(nosigkeys); }
        Self { _phantom: PhantomData }
    }

    /// Explicitly release the keyboard.
    ///
    /// This prevents further use of the Keyboard functionality and
    /// simultaneously forces its lifetime to extend until this call, delaying
    /// deinitialization of the keyboard until this call.
    pub fn end(self) {}

    // None on EOF, otherwise a combination of the KEY_* constants
    pub fn get_key(&self) -> io::Result<Option<i32>> {
        match unsafe { bindings::tgetkey() } {
            -2 => Err(io::Error::last_os_error()),
            -1 => Ok(None),
            value => Ok(Some(value)),
        }
    }

    pub fn get_line(&self) -> Option<String> {
        let ptr = unsafe { bindings::tgetline() };
        if ptr.is_null() {
            None
        } else {
            unsafe { Some(util::string_from_utf8_charp(ptr)) }
        }
    }
}

impl Drop for Keyboard {
    fn drop(&mut self) {
        let prev = KEYBOARD_REFCOUNT.compare_and_swap(
                        true, false, Ordering::SeqCst);
        if !prev {
            panic!("Internal: No Keyboard was held on Drop of a Keyboard?");
        }

        unsafe { bindings::endkeyboard(); }
    }
}

/// RAII struct for screen-drawing initialization
///
/// Only one instance of this struct can be active at a time. Attempting to
/// create a second instance will panic.
pub struct Screen<'a> {
    _keyboard: &'a Keyboard,
}

static SCREEN_REFCOUNT: AtomicBool = AtomicBool::new(false);

impl<'a> Screen<'a> {
    /// Grab the screen, i.e. switch to the alternative screen and allocate draw
    /// buffers.
    ///
    /// This function takes and stores a Keyboard reference to ensure that the
    /// keyboard is initialized while the screen is grabbed.
    pub fn grab<'b: 'a>(keyboard: &'b Keyboard) -> Self {
        let prev = SCREEN_REFCOUNT.compare_and_swap(
                        false, true, Ordering::SeqCst);
        if prev {
            panic!("Can only hold one Screen struct at a time!");
        }

        unsafe { bindings::initscreen(); }
        Self { _keyboard: keyboard }
    }

    pub fn end(self) {}

    /// Install ctrl-L redraw handler.
    ///
    /// If the argument is true, then upon reading ctrl-L in
    /// `Keyboard::get_key()`, a full redraw is issued, and the the call blocks
    /// for the next key. Note that if the user only types ctrl-L, this means
    /// that `get_key` may block while you don't expect it.
    pub fn install_cl_handler(&self, install: bool) {
        unsafe { bindings::installCLhandler(install); }
    }

    /// Install redraw handler.
    ///
    /// The argument indicates whether the redraw is a full redraw. The handler
    /// is called before the redraw is performed. Please do not trigger redraws
    /// from within the handler.
    pub fn on_redraw(&self, handler: extern "C" fn(full_redraw: bool)) {
        unsafe { bindings::installredrawhandler(Some(handler)); }
    }

    pub fn clear(&self) {
        unsafe { bindings::clearscreen(); }
    }

    /// Update style state.
    ///
    /// This modifies the style for the characters printed from now on. Note
    /// that this does not write anything to the terminal.
    pub fn set_style(&self, style: Style) {
        let sty = bindings::Style {
            fg: style.fg as i32,
            bg: style.bg as i32,
            bold: style.bold,
            ul: style.ul,
        };
        unsafe { bindings::setstyle(&sty as *const bindings::Style); }
    }

    /// Update partial style state. see `set_style()`.
    pub fn set_fg(&self, fg: u8) {
        unsafe { bindings::setfg(fg as i32); }
    }

    /// Update partial style state. see `set_style()`.
    pub fn set_bg(&self, bg: u8) {
        unsafe { bindings::setbg(bg as i32); }
    }

    /// Update partial style state. see `set_style()`.
    pub fn set_bold(&self, bold: bool) {
        unsafe { bindings::setbold(bold); }
    }

    /// Update partial style state. see `set_style()`.
    pub fn set_ul(&self, ul: bool) {
        unsafe { bindings::setul(ul); }
    }

    pub fn putc(&self, c: char) {
        unsafe { bindings::tputc(c as i32); }
    }

    /// Newlines ('\n') return to the column on which printing started.
    pub fn print(&self, s: &str) {
        unsafe { bindings::tprint(s.as_bytes().as_ptr() as *const i8); }
    }

    pub fn fill_rect(&self, lefttop: (u32, u32), size: (u32, u32), c: char) {
        unsafe { bindings::fillrect(
                    lefttop.0 as i32, lefttop.1 as i32,
                    size.0 as i32, size.1 as i32,
                    c as i32); }
    }

    pub fn redraw(&self) {
        unsafe { bindings::redraw(); }
    }

    pub fn redraw_full(&self) {
        unsafe { bindings::redrawfull(); }
    }

    /// Scrolls a rectangle on the screen by a signed number of lines.
    pub fn scroll_term(&self, lefttop: (u32, u32), size: (u32, u32), amount: i32) {
        unsafe { bindings::scrollterm(
                    lefttop.0 as i32, lefttop.1 as i32,
                    size.0 as i32, size.1 as i32,
                    amount); }
    }

    pub fn get_buffer_char(&self, pos: (u32, u32)) -> char {
        unsafe {
            char::from_u32(
                bindings::getbufferchar(pos.0 as i32, pos.1 as i32) as u32
            ).unwrap()
        }
    }

    /// Updates state; does not actually move the cursor yet (use `redraw()`).
    pub fn move_to(&self, pos: (u32, u32)) {
        unsafe { bindings::moveto(pos.0 as i32, pos.1 as i32); }
    }

    /// Save the (internal) cursor position on a stack.
    pub fn push_cursor(&self) {
        unsafe { bindings::pushcursor(); }
    }

    /// Pop the (internal) cursor position from the stack.
    pub fn pop_cursor(&self) {
        unsafe { bindings::popcursor(); }
    }

    /// Takes effect immediately!
    pub fn cursor_visible(&self, visible: bool) {
        unsafe { bindings::cursorvisible(visible); }
    }
}

impl<'a> Drop for Screen<'a> {
    fn drop(&mut self) {
        let prev = SCREEN_REFCOUNT.compare_and_swap(
                        true, false, Ordering::SeqCst);
        if !prev {
            panic!("Internal: No Screen was held on Drop of a Screen?");
        }

        unsafe { bindings::endscreen(); }
    }
}

pub fn get_term_size() -> (u32, u32) {
    let sz = unsafe { bindings::gettermsize() };
    (sz.w as u32, sz.h as u32)
}

pub fn bel() {
    unsafe { bindings::bel(); }
}