mirror of
https://github.com/microsoft/edit.git
synced 2026-06-13 21:45:49 -05:00
Slight sys cleanup & Fix more sys::unix bugs
This commit is contained in:
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
@@ -4,9 +4,8 @@
|
||||
{
|
||||
"name": "Launch Debug",
|
||||
"preLaunchTask": "rust: cargo build",
|
||||
"type": "cppvsdbg",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"console": "externalTerminal",
|
||||
"program": "${workspaceFolder}/target/debug/edit",
|
||||
"args": [
|
||||
"${workspaceFolder}/README.md"
|
||||
|
||||
197
src/sys/unix.rs
197
src/sys/unix.rs
@@ -8,22 +8,27 @@ use std::ptr::{null, null_mut};
|
||||
use std::thread;
|
||||
use std::time;
|
||||
|
||||
pub fn preferred_languages() -> Vec<String> {
|
||||
let mut locales = Vec::new();
|
||||
|
||||
for key in ["LANGUAGE", "LC_ALL", "LANG"] {
|
||||
if let Ok(val) = std::env::var(key) {
|
||||
locales.extend(
|
||||
val.split(':')
|
||||
.filter(|val| !val.is_empty())
|
||||
.map(String::from),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
locales
|
||||
struct State {
|
||||
stdin: libc::c_int,
|
||||
stdin_flags: libc::c_int,
|
||||
stdout: libc::c_int,
|
||||
stdout_initial_termios: libc::termios,
|
||||
inject_resize: bool,
|
||||
// Buffer for incomplete UTF-8 sequences (max 4 bytes needed)
|
||||
utf8_buf: [u8; 4],
|
||||
utf8_len: usize,
|
||||
}
|
||||
|
||||
static mut STATE: State = State {
|
||||
stdin: libc::STDIN_FILENO,
|
||||
stdin_flags: 0,
|
||||
stdout: libc::STDOUT_FILENO,
|
||||
stdout_initial_termios: unsafe { mem::zeroed() },
|
||||
inject_resize: false,
|
||||
utf8_buf: [0; 4],
|
||||
utf8_len: 0,
|
||||
};
|
||||
|
||||
extern "C" fn sigwinch_handler(_: libc::c_int) {
|
||||
unsafe {
|
||||
STATE.inject_resize = true;
|
||||
@@ -40,6 +45,8 @@ pub fn init() -> apperr::Result<()> {
|
||||
STATE.stdout = check_int_return(libc::open(c"/dev/tty".as_ptr(), libc::O_WRONLY))?;
|
||||
}
|
||||
|
||||
STATE.stdin_flags = check_int_return(libc::fcntl(STATE.stdin, libc::F_GETFL))?;
|
||||
|
||||
check_int_return(libc::tcgetattr(
|
||||
STATE.stdout,
|
||||
&raw mut STATE.stdout_initial_termios,
|
||||
@@ -108,25 +115,6 @@ fn get_window_size() -> (u16, u16) {
|
||||
(w, h)
|
||||
}
|
||||
|
||||
struct State {
|
||||
stdin: libc::c_int,
|
||||
stdout: libc::c_int,
|
||||
stdout_initial_termios: libc::termios,
|
||||
inject_resize: bool,
|
||||
// Buffer for incomplete UTF-8 sequences (max 4 bytes needed)
|
||||
utf8_buf: [u8; 4],
|
||||
utf8_len: usize,
|
||||
}
|
||||
|
||||
static mut STATE: State = State {
|
||||
stdin: libc::STDIN_FILENO,
|
||||
stdout: libc::STDOUT_FILENO,
|
||||
stdout_initial_termios: unsafe { mem::zeroed() },
|
||||
inject_resize: false,
|
||||
utf8_buf: [0; 4],
|
||||
utf8_len: 0,
|
||||
};
|
||||
|
||||
/// Reads from stdin.
|
||||
///
|
||||
/// Returns `None` if there was an error reading from stdin.
|
||||
@@ -134,6 +122,13 @@ static mut STATE: State = State {
|
||||
/// Otherwise, it returns the read, non-empty string.
|
||||
pub fn read_stdin(timeout: time::Duration) -> Option<String> {
|
||||
unsafe {
|
||||
let mut read_more = true;
|
||||
let mut read_poll = false;
|
||||
|
||||
if STATE.inject_resize {
|
||||
read_poll = true;
|
||||
}
|
||||
|
||||
if timeout != time::Duration::MAX {
|
||||
let mut pollfd = libc::pollfd {
|
||||
fd: STATE.stdin,
|
||||
@@ -148,62 +143,78 @@ pub fn read_stdin(timeout: time::Duration) -> Option<String> {
|
||||
if ret < 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
read_poll = true; // there is a timeout -> don't block in read()
|
||||
if ret == 0 {
|
||||
return Some(String::new());
|
||||
read_more = false; // if timeout -> don't read
|
||||
}
|
||||
};
|
||||
|
||||
let mut input = String::new();
|
||||
|
||||
if read_more {
|
||||
let mut buf = Vec::with_capacity(1024);
|
||||
|
||||
if STATE.utf8_len != 0 {
|
||||
buf.extend_from_slice(&STATE.utf8_buf[..STATE.utf8_len]);
|
||||
STATE.utf8_len = 0;
|
||||
}
|
||||
|
||||
let is_nonblock = (STATE.stdin_flags & libc::O_NONBLOCK) != 0;
|
||||
if read_poll != is_nonblock {
|
||||
STATE.stdin_flags ^= libc::O_NONBLOCK;
|
||||
let _ = libc::fcntl(STATE.stdin, libc::F_SETFL, STATE.stdin_flags);
|
||||
}
|
||||
|
||||
loop {
|
||||
let spare = buf.spare_capacity_mut();
|
||||
let ret = libc::read(STATE.stdin, spare.as_mut_ptr() as *mut _, spare.len());
|
||||
if ret > 0 {
|
||||
buf.set_len(buf.len() + ret as usize);
|
||||
break;
|
||||
}
|
||||
if ret == 0 {
|
||||
return None;
|
||||
}
|
||||
match *libc::__errno_location() {
|
||||
libc::EINTR => continue,
|
||||
libc::EAGAIN => break,
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
|
||||
if !buf.is_empty() {
|
||||
// We only need to check the last 3 bytes for UTF-8 continuation bytes,
|
||||
// because we should be able to assume that any 4 byte sequence is complete.
|
||||
let lim = buf.len().saturating_sub(3);
|
||||
let mut off = buf.len() - 1;
|
||||
|
||||
// Find the start of the last potentially incomplete UTF-8 sequence.
|
||||
while off > lim && buf[off] & 0b1100_0000 == 0b1000_0000 {
|
||||
off -= 1;
|
||||
}
|
||||
|
||||
let seq_len = match buf[off] {
|
||||
b if b & 0b1000_0000 == 0 => 1,
|
||||
b if b & 0b1110_0000 == 0b1100_0000 => 2,
|
||||
b if b & 0b1111_0000 == 0b1110_0000 => 3,
|
||||
b if b & 0b1111_1000 == 0b1111_0000 => 4,
|
||||
// If the lead byte we found isn't actually one, we don't cache it.
|
||||
// `string_from_utf8_lossy_owned` will replace it with U+FFFD.
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
// Cache incomplete sequence if any.
|
||||
if off + seq_len > buf.len() {
|
||||
STATE.utf8_len = buf.len() - off;
|
||||
STATE.utf8_buf[..STATE.utf8_len].copy_from_slice(&buf[off..]);
|
||||
buf.truncate(off);
|
||||
}
|
||||
}
|
||||
|
||||
input = helpers::string_from_utf8_lossy_owned(buf);
|
||||
}
|
||||
|
||||
let mut buf = Vec::with_capacity(1024);
|
||||
|
||||
if STATE.utf8_len != 0 {
|
||||
buf.extend_from_slice(&STATE.utf8_buf[..STATE.utf8_len]);
|
||||
STATE.utf8_len = 0;
|
||||
}
|
||||
|
||||
// Read new data
|
||||
loop {
|
||||
let spare = buf.spare_capacity_mut();
|
||||
let ret = libc::read(STATE.stdin, spare.as_mut_ptr() as *mut _, spare.len());
|
||||
if ret > 0 {
|
||||
buf.set_len(buf.len() + ret as usize);
|
||||
break;
|
||||
}
|
||||
if ret == 0 || *libc::__errno_location() != libc::EINTR {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if !buf.is_empty() {
|
||||
// We only need to check the last 3 bytes for UTF-8 continuation bytes,
|
||||
// because we should be able to assume that any 4 byte sequence is complete.
|
||||
let lim = buf.len().saturating_sub(3);
|
||||
let mut off = buf.len() - 1;
|
||||
|
||||
// Find the start of the last potentially incomplete UTF-8 sequence.
|
||||
while off > lim && buf[off] & 0b1100_0000 == 0b1000_0000 {
|
||||
off -= 1;
|
||||
}
|
||||
|
||||
let seq_len = match buf[off] {
|
||||
b if b & 0b1000_0000 == 0 => 1,
|
||||
b if b & 0b1110_0000 == 0b1100_0000 => 2,
|
||||
b if b & 0b1111_0000 == 0b1110_0000 => 3,
|
||||
b if b & 0b1111_1000 == 0b1111_0000 => 4,
|
||||
// If the lead byte we found isn't actually one, we don't cache it.
|
||||
// `string_from_utf8_lossy_owned` will replace it with U+FFFD.
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
// Cache incomplete sequence if any.
|
||||
if off + seq_len > buf.len() {
|
||||
STATE.utf8_len = buf.len() - off;
|
||||
STATE.utf8_buf[..STATE.utf8_len].copy_from_slice(&buf[off..]);
|
||||
buf.truncate(off);
|
||||
}
|
||||
}
|
||||
|
||||
let mut input = helpers::string_from_utf8_lossy_owned(buf);
|
||||
|
||||
if STATE.inject_resize {
|
||||
STATE.inject_resize = false;
|
||||
let (w, h) = get_window_size();
|
||||
@@ -310,6 +321,22 @@ pub unsafe fn load_icu() -> apperr::Result<*mut c_void> {
|
||||
unsafe { load_library(c"icu.dll") }
|
||||
}
|
||||
|
||||
pub fn preferred_languages() -> Vec<String> {
|
||||
let mut locales = Vec::new();
|
||||
|
||||
for key in ["LANGUAGE", "LC_ALL", "LANG"] {
|
||||
if let Ok(val) = std::env::var(key) {
|
||||
locales.extend(
|
||||
val.split(':')
|
||||
.filter(|val| !val.is_empty())
|
||||
.map(String::from),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
locales
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn io_error_to_apperr(err: std::io::Error) -> apperr::Error {
|
||||
unsafe { apperr::Error::new(err.raw_os_error().unwrap_or(0) as u32) }
|
||||
|
||||
@@ -19,51 +19,6 @@ use windows_sys::Win32::System::Memory;
|
||||
use windows_sys::Win32::System::Threading;
|
||||
use windows_sys::w;
|
||||
|
||||
pub fn preferred_languages() -> Vec<String> {
|
||||
unsafe {
|
||||
const LEN: usize = 256;
|
||||
|
||||
let mut lang_num = 0;
|
||||
let mut lang_buf = [const { MaybeUninit::<u16>::uninit() }; LEN];
|
||||
let mut lang_buf_len = lang_buf.len() as u32;
|
||||
if Globalization::GetUserPreferredUILanguages(
|
||||
Globalization::MUI_LANGUAGE_NAME,
|
||||
&mut lang_num,
|
||||
lang_buf[0].as_mut_ptr(),
|
||||
&mut lang_buf_len,
|
||||
) == 0
|
||||
|| lang_num == 0
|
||||
{
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
// Drop the terminating double-null character.
|
||||
lang_buf_len = lang_buf_len.saturating_sub(1);
|
||||
|
||||
let mut lang_buf_utf8 = [const { MaybeUninit::<u8>::uninit() }; 3 * LEN];
|
||||
let lang_buf_utf8_len = Globalization::WideCharToMultiByte(
|
||||
Globalization::CP_UTF8,
|
||||
0,
|
||||
lang_buf[0].as_mut_ptr(),
|
||||
lang_buf_len as i32,
|
||||
lang_buf_utf8[0].as_mut_ptr(),
|
||||
lang_buf_utf8.len() as i32,
|
||||
null(),
|
||||
null_mut(),
|
||||
);
|
||||
if lang_buf_utf8_len == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let result = helpers::str_from_raw_parts_mut(
|
||||
lang_buf_utf8[0].as_mut_ptr(),
|
||||
lang_buf_utf8_len as usize,
|
||||
);
|
||||
result.make_ascii_lowercase();
|
||||
result.split_terminator('\0').map(String::from).collect()
|
||||
}
|
||||
}
|
||||
|
||||
type ReadConsoleInputExW = unsafe extern "system" fn(
|
||||
h_console_input: Foundation::HANDLE,
|
||||
lp_buffer: *mut Console::INPUT_RECORD,
|
||||
@@ -72,6 +27,16 @@ type ReadConsoleInputExW = unsafe extern "system" fn(
|
||||
w_flags: u16,
|
||||
) -> Foundation::BOOL;
|
||||
|
||||
unsafe extern "system" fn read_console_input_ex_placeholder(
|
||||
_: Foundation::HANDLE,
|
||||
_: *mut Console::INPUT_RECORD,
|
||||
_: u32,
|
||||
_: *mut u32,
|
||||
_: u16,
|
||||
) -> Foundation::BOOL {
|
||||
panic!();
|
||||
}
|
||||
|
||||
const CONSOLE_READ_NOWAIT: u16 = 0x0002;
|
||||
|
||||
struct State {
|
||||
@@ -100,16 +65,6 @@ static mut STATE: State = State {
|
||||
wants_exit: false,
|
||||
};
|
||||
|
||||
unsafe extern "system" fn read_console_input_ex_placeholder(
|
||||
_: Foundation::HANDLE,
|
||||
_: *mut Console::INPUT_RECORD,
|
||||
_: u32,
|
||||
_: *mut u32,
|
||||
_: u16,
|
||||
) -> Foundation::BOOL {
|
||||
panic!();
|
||||
}
|
||||
|
||||
extern "system" fn console_ctrl_handler(_ctrl_type: u32) -> Foundation::BOOL {
|
||||
unsafe {
|
||||
STATE.wants_exit = true;
|
||||
@@ -235,13 +190,6 @@ pub fn read_stdin(timeout: time::Duration) -> Option<String> {
|
||||
unsafe { STATE.inject_resize = false };
|
||||
}
|
||||
|
||||
if unsafe { STATE.leading_surrogate } != 0 {
|
||||
utf16_buf[0] = MaybeUninit::new(unsafe { STATE.leading_surrogate });
|
||||
utf16_buf_len = 1;
|
||||
input_buf_cap -= 1;
|
||||
unsafe { STATE.leading_surrogate = 0 };
|
||||
}
|
||||
|
||||
if timeout != time::Duration::MAX {
|
||||
read_poll = true;
|
||||
let wait_result =
|
||||
@@ -260,6 +208,13 @@ pub fn read_stdin(timeout: time::Duration) -> Option<String> {
|
||||
// This loops exists, just in case there's events in the input buffer that we aren't interested in.
|
||||
// It should be rare for this to loop.
|
||||
while read_more {
|
||||
if unsafe { STATE.leading_surrogate } != 0 {
|
||||
utf16_buf[0] = MaybeUninit::new(unsafe { STATE.leading_surrogate });
|
||||
utf16_buf_len = 1;
|
||||
input_buf_cap -= 1;
|
||||
unsafe { STATE.leading_surrogate = 0 };
|
||||
}
|
||||
|
||||
let input = unsafe {
|
||||
// If we had a `inject_resize`, we don't want to block indefinitely for other pending input on startup,
|
||||
// but are still interested in any other pending input that may be waiting for us.
|
||||
@@ -476,6 +431,51 @@ pub unsafe fn load_icu() -> apperr::Result<Foundation::HMODULE> {
|
||||
unsafe { load_library(w!("icu.dll")) }
|
||||
}
|
||||
|
||||
pub fn preferred_languages() -> Vec<String> {
|
||||
unsafe {
|
||||
const LEN: usize = 256;
|
||||
|
||||
let mut lang_num = 0;
|
||||
let mut lang_buf = [const { MaybeUninit::<u16>::uninit() }; LEN];
|
||||
let mut lang_buf_len = lang_buf.len() as u32;
|
||||
if Globalization::GetUserPreferredUILanguages(
|
||||
Globalization::MUI_LANGUAGE_NAME,
|
||||
&mut lang_num,
|
||||
lang_buf[0].as_mut_ptr(),
|
||||
&mut lang_buf_len,
|
||||
) == 0
|
||||
|| lang_num == 0
|
||||
{
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
// Drop the terminating double-null character.
|
||||
lang_buf_len = lang_buf_len.saturating_sub(1);
|
||||
|
||||
let mut lang_buf_utf8 = [const { MaybeUninit::<u8>::uninit() }; 3 * LEN];
|
||||
let lang_buf_utf8_len = Globalization::WideCharToMultiByte(
|
||||
Globalization::CP_UTF8,
|
||||
0,
|
||||
lang_buf[0].as_mut_ptr(),
|
||||
lang_buf_len as i32,
|
||||
lang_buf_utf8[0].as_mut_ptr(),
|
||||
lang_buf_utf8.len() as i32,
|
||||
null(),
|
||||
null_mut(),
|
||||
);
|
||||
if lang_buf_utf8_len == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let result = helpers::str_from_raw_parts_mut(
|
||||
lang_buf_utf8[0].as_mut_ptr(),
|
||||
lang_buf_utf8_len as usize,
|
||||
);
|
||||
result.make_ascii_lowercase();
|
||||
result.split_terminator('\0').map(String::from).collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn get_last_error() -> apperr::Error {
|
||||
unsafe { gle_to_apperr(Foundation::GetLastError()) }
|
||||
|
||||
Reference in New Issue
Block a user