Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove nix and signal-hook dependencies #50

Merged
merged 3 commits into from
May 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 4 additions & 57 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ unicode-width = "0.1.7"

[target.'cfg(unix)'.dependencies]
libc = "0.2.69"
nix = "0.17.0"
signal-hook = "0.1.14"

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.8", default-features = false, features = ["wincon"] }
Expand Down
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,13 +232,10 @@ This project is inspired by [`kilo`](https://github.com/antirez/kilo), a text ed
This project must remain tiny, so using advanced dependencies such as [`ncurses`](https://crates.io/crates/ncurses),
[`toml`](https://crates.io/crates/toml) or [`ansi-escapes`](https://crates.io/crates/ansi-escapes) would be cheating.

The following dependencies provide wrappers around system calls. Safe wrappers are preferred to avoid using `unsafe`
code as much as possible:
The following dependencies provide wrappers around system calls.

* On UNIX systems (Linux, macOS):
* `libc`
* `nix`
* `signal-hook`
* On Windows:
* `winapi`
* `winapi-util`
Expand Down
19 changes: 4 additions & 15 deletions src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

use std::io::{self, BufRead, BufReader, ErrorKind::NotFound, Read, Seek, Write};
use std::iter::{self, repeat, successors};
use std::sync::mpsc::{Receiver, TryRecvError};
use std::{fmt::Display, fs::File, path::Path, thread, time::Instant};

use crate::row::{HLState, Row};
Expand Down Expand Up @@ -102,9 +101,6 @@ pub struct Editor {
syntax: SyntaxConf,
/// The number of bytes contained in `rows`. This excludes new lines.
n_bytes: u64,
/// A channel receiver for the "window size changed" message. A message is received shortly
/// after a SIGWINCH signal is received.
ws_changed_receiver: Option<Receiver<()>>,
/// The original terminal mode. It will be restored when the `Editor` instance is dropped.
orig_term_mode: Option<sys::TermMode>,
}
Expand Down Expand Up @@ -146,10 +142,10 @@ impl Editor {
/// Will return `Err` if an error occurs when enabling termios raw mode, creating the signal hook
/// or when obtaining the terminal window size.
pub fn new(config: Config) -> Result<Self, Error> {
sys::register_winsize_change_signal_handler()?;
let mut editor = Self::default();
editor.quit_times = config.quit_times;
editor.config = config;
editor.ws_changed_receiver = sys::get_window_size_update_receiver()?;

// Enable raw mode and store the original (non-raw) terminal mode.
editor.orig_term_mode = Some(sys::enable_raw_mode()?);
Expand Down Expand Up @@ -209,16 +205,9 @@ impl Editor {
fn loop_until_keypress(&mut self) -> Result<Key, Error> {
loop {
// Handle window size if a signal has be received
match self.ws_changed_receiver.as_ref().map(Receiver::try_recv) {
// If there is no receiver or no "window size updated" signal has been received, do
// nothing.
None | Some(Err(TryRecvError::Empty)) => (),
// A "window size updated" signal has been received
Some(Ok(())) => {
self.update_window_size()?;
self.refresh_screen()?;
}
Some(Err(err)) => return Err(Error::MPSCTryRecv(err)),
if sys::has_window_size_changed() {
self.update_window_size()?;
self.refresh_screen()?;
}
let mut bytes = io::stdin().bytes();
// Match on the next byte received or, if the first byte is <ESC> ('\x1b'), on the next
Expand Down
11 changes: 0 additions & 11 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@
pub enum Error {
/// Wrapper around `std::io::Error`
IO(std::io::Error),
/// Wrapper around `nix::Error`
#[cfg(unix)]
Nix(nix::Error),
/// Wrapper around `std::sync::mpsc::TryRecvError`
MPSCTryRecv(std::sync::mpsc::TryRecvError),
/// Error returned when the window size obtained through a system call is invalid.
InvalidWindowSize,
/// Error setting or retrieving the cursor position.
Expand All @@ -26,9 +21,3 @@ impl From<std::io::Error> for Error {
/// Convert an IO Error into a Kibi Error.
fn from(err: std::io::Error) -> Self { Self::IO(err) }
}

#[cfg(unix)]
impl From<nix::Error> for Error {
/// Convert a nix Error into a Kibi Error.
fn from(err: nix::Error) -> Self { Self::Nix(err) }
}
77 changes: 48 additions & 29 deletions src/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@
//! UNIX-specific structs and functions. Will be imported as `sys` on UNIX systems.

use std::env::var;
use std::sync::mpsc::{self, Receiver};
use std::sync::atomic::{AtomicBool, Ordering};

use libc::{STDIN_FILENO, STDOUT_FILENO, TIOCGWINSZ, VMIN, VTIME};
use nix::{pty::Winsize, sys::termios};
use signal_hook::{iterator::Signals, SIGWINCH};

// On UNIX systems, Termios represents the terminal mode.
pub use nix::sys::termios::Termios as TermMode;
// On UNIX systems, termios represents the terminal mode.
pub use libc::termios as TermMode;
use libc::{c_int, c_void, sigaction, sighandler_t, siginfo_t, winsize};
use libc::{SA_SIGINFO, STDIN_FILENO, STDOUT_FILENO, TCSAFLUSH, TIOCGWINSZ, VMIN, VTIME};

use crate::Error;

fn cerr(err: c_int) -> Result<(), Error> {
if err < 0 {
Err(std::io::Error::last_os_error().into())
} else {
Ok(())
}
}

/// Return directories following the XDG Base Directory Specification
///
/// See `conf_dirs()` and `data_dirs()` for usage example.
Expand Down Expand Up @@ -52,42 +58,55 @@ pub fn data_dirs() -> Vec<String> {
/// populated.
/// This ioctl is described here: <http://man7.org/linux/man-pages/man4/tty_ioctl.4.html>
pub fn get_window_size() -> Result<(usize, usize), Error> {
nix::ioctl_read_bad!(get_ws, TIOCGWINSZ, Winsize);

let mut maybe_ws = std::mem::MaybeUninit::<Winsize>::uninit();

unsafe { get_ws(STDOUT_FILENO, maybe_ws.as_mut_ptr()).ok().map(|_| maybe_ws.assume_init()) }
let mut maybe_ws = std::mem::MaybeUninit::<winsize>::uninit();
cerr(unsafe { libc::ioctl(STDOUT_FILENO, TIOCGWINSZ, maybe_ws.as_mut_ptr()) })
.map_or(None, |_| unsafe { Some(maybe_ws.assume_init()) })
.filter(|ws| ws.ws_col != 0 && ws.ws_row != 0)
.map_or(Err(Error::InvalidWindowSize), |ws| Ok((ws.ws_row as usize, ws.ws_col as usize)))
}

/// Return a MPSC receiver that receives a message whenever the window size is updated.
pub fn get_window_size_update_receiver() -> Result<Option<Receiver<()>>, Error> {
// Create a channel for receiving window size update requests
let (ws_changed_tx, ws_changed_rx) = mpsc::sync_channel(1);
// Spawn a new thread that will push to the aforementioned channel every time the SIGWINCH
// signal is received
let signals = Signals::new(&[SIGWINCH])?;
std::thread::spawn(move || signals.forever().for_each(|_| ws_changed_tx.send(()).unwrap()));
Ok(Some(ws_changed_rx))
/// Register a signal handler that sets a global variable when the window size changes.
/// After calling this function, use has_window_size_changed to query the global variable.
pub fn register_winsize_change_signal_handler() -> Result<(), Error> {
#[no_mangle]
extern "C" fn handle_window_size_changed(_: c_int, _: *mut siginfo_t, _: *mut c_void) {
WIN_CHANGED.store(true, Ordering::Relaxed);
}
unsafe {
let mut maybe_sa = std::mem::MaybeUninit::<sigaction>::uninit();
cerr(libc::sigemptyset(&mut (*maybe_sa.as_mut_ptr()).sa_mask))?;
// We could use sa_handler here, however, sigaction defined in libc does not have
// sa_handler field, so we use sa_sigaction instead.
(*maybe_sa.as_mut_ptr()).sa_flags = SA_SIGINFO;
(*maybe_sa.as_mut_ptr()).sa_sigaction = handle_window_size_changed as sighandler_t;
cerr(libc::sigaction(libc::SIGWINCH, maybe_sa.as_ptr(), std::ptr::null_mut()))
}
}

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

/// Check if the windows size has changed since the last call to this function.
/// The register_winsize_change_signal_handler needs to be called before this function.
pub fn has_window_size_changed() -> bool { WIN_CHANGED.swap(false, Ordering::Relaxed) }

/// Set the terminal mode.
pub fn set_term_mode(term: &TermMode) -> Result<(), nix::Error> {
termios::tcsetattr(STDIN_FILENO, termios::SetArg::TCSAFLUSH, term)
pub fn set_term_mode(term: &TermMode) -> Result<(), Error> {
cerr(unsafe { libc::tcsetattr(STDIN_FILENO, TCSAFLUSH, term) })
}

/// Setup the termios to enable raw mode, and return the original termios.
///
/// termios manual is available at: <http://man7.org/linux/man-pages/man3/termios.3.html>
pub fn enable_raw_mode() -> Result<TermMode, Error> {
let orig_termios = termios::tcgetattr(STDIN_FILENO)?;
let mut term = orig_termios.clone();
termios::cfmakeraw(&mut term);
let mut maybe_term = std::mem::MaybeUninit::<TermMode>::uninit();
let orig_term = cerr(unsafe { libc::tcgetattr(STDIN_FILENO, maybe_term.as_mut_ptr()) })
.map(|_| unsafe { maybe_term.assume_init() })?;
let mut term = orig_term;
unsafe { libc::cfmakeraw(&mut term) };
// Set the minimum number of characters for non-canonical reads
term.control_chars[VMIN] = 0;
term.c_cc[VMIN] = 0;
// Set the timeout in deciseconds for non-canonical reads
term.control_chars[VTIME] = 1;
term.c_cc[VTIME] = 1;
set_term_mode(&term)?;
Ok(orig_termios)
Ok(orig_term)
}
6 changes: 4 additions & 2 deletions src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

#![allow(clippy::wildcard_imports)]

use std::{convert::TryInto, env::var, io, sync::mpsc::Receiver};
use std::{convert::TryInto, env::var, io};

use winapi::um::wincon::*;
use winapi_util::{console as cons, HandleRef};
Expand All @@ -30,7 +30,9 @@ pub fn get_window_size() -> Result<(usize, usize), Error> {
}
}

pub fn get_window_size_update_receiver() -> Result<Option<Receiver<()>>, Error> { Ok(None) }
pub fn register_winsize_change_signal_handler() -> Result<(), Error> { Ok(()) }

pub fn has_window_size_changed() -> bool { false }

/// Set the terminal mode.
#[allow(clippy::trivially_copy_pass_by_ref)]
Expand Down