Port screen.cpp

This commit is contained in:
Johannes Altmanninger
2023-11-25 19:40:31 +01:00
parent 5f1499cd67
commit 31ad182aa5
24 changed files with 2462 additions and 1932 deletions

View File

@@ -98,6 +98,7 @@ fn main() {
"fish-rust/src/proc.rs",
"fish-rust/src/reader.rs",
"fish-rust/src/redirection.rs",
"fish-rust/src/screen.rs",
"fish-rust/src/signal.rs",
"fish-rust/src/smoke.rs",
"fish-rust/src/termsize.rs",

View File

@@ -1,4 +1,4 @@
use crate::wcstringutil::fish_wcwidth_visible;
use crate::{screen::escape_code_length, wcstringutil::fish_wcwidth_visible};
// Forward some imports to make subcmd implementations easier
use super::prelude::*;
@@ -267,16 +267,6 @@ fn width_without_escapes(ins: &wstr, start_pos: usize) -> usize {
return width as usize;
}
fn escape_code_length(code: &wstr) -> Option<usize> {
use crate::ffi::escape_code_length_ffi;
use crate::wchar_ffi::wstr_to_u32string;
match escape_code_length_ffi(wstr_to_u32string(code).as_ptr()).into() {
-1 => None,
n => Some(n as usize),
}
}
/// Empirically determined.
/// This is probably down to some pipe buffer or some such,
/// but too small means we need to call `read(2)` and str2wcstring a lot.

View File

@@ -1008,8 +1008,15 @@ pub fn exit_without_destructors(code: libc::c_int) -> ! {
unsafe { libc::_exit(code) };
}
/// Save the shell mode on startup so we can restore them on exit.
static SHELL_MODES: Lazy<Mutex<libc::termios>> = Lazy::new(|| Mutex::new(unsafe { mem::zeroed() }));
pub fn shell_modes() -> &'static libc::termios {
let modes = crate::ffi::shell_modes_ffi() as *const libc::termios;
unsafe { &*modes }
}
pub fn shell_modes_mut() -> &'static mut libc::termios {
let modes = crate::ffi::shell_modes_ffi() as *mut libc::termios;
unsafe { &mut *modes }
}
/// The character to use where the text has been truncated. Is an ellipsis on unicode system and a $
/// on other systems.

View File

@@ -113,18 +113,37 @@ pub struct Term {
pub exit_underline_mode: Option<CString>,
pub enter_reverse_mode: Option<CString>,
pub enter_standout_mode: Option<CString>,
pub exit_standout_mode: Option<CString>,
pub enter_blink_mode: Option<CString>,
pub enter_protected_mode: Option<CString>,
pub enter_shadow_mode: Option<CString>,
pub exit_shadow_mode: Option<CString>,
pub enter_secure_mode: Option<CString>,
pub enter_alt_charset_mode: Option<CString>,
pub exit_alt_charset_mode: Option<CString>,
pub set_a_foreground: Option<CString>,
pub set_foreground: Option<CString>,
pub set_a_background: Option<CString>,
pub set_background: Option<CString>,
pub exit_attribute_mode: Option<CString>,
pub set_title: Option<CString>,
pub clear_screen: Option<CString>,
pub cursor_up: Option<CString>,
pub cursor_down: Option<CString>,
pub cursor_left: Option<CString>,
pub cursor_right: Option<CString>,
pub parm_left_cursor: Option<CString>,
pub parm_right_cursor: Option<CString>,
pub clr_eol: Option<CString>,
pub clr_eos: Option<CString>,
// Number capabilities
pub max_colors: Option<usize>,
pub init_tabs: Option<usize>,
// Flag/boolean capabilities
pub eat_newline_glitch: bool,
pub auto_right_margin: bool,
}
impl Term {
@@ -141,18 +160,37 @@ fn new() -> Self {
exit_underline_mode: get_str_cap("ue"),
enter_reverse_mode: get_str_cap("mr"),
enter_standout_mode: get_str_cap("so"),
exit_standout_mode: get_str_cap("se"),
enter_blink_mode: get_str_cap("mb"),
enter_protected_mode: get_str_cap("mp"),
enter_shadow_mode: get_str_cap("ZM"),
exit_shadow_mode: get_str_cap("ZU"),
enter_secure_mode: get_str_cap("mk"),
enter_alt_charset_mode: get_str_cap("as"),
exit_alt_charset_mode: get_str_cap("ae"),
set_a_foreground: get_str_cap("AF"),
set_foreground: get_str_cap("Sf"),
set_a_background: get_str_cap("AB"),
set_background: get_str_cap("Sb"),
exit_attribute_mode: get_str_cap("me"),
set_title: get_str_cap("ts"),
clear_screen: get_str_cap("cl"),
cursor_up: get_str_cap("up"),
cursor_down: get_str_cap("do"),
cursor_left: get_str_cap("le"),
cursor_right: get_str_cap("nd"),
parm_left_cursor: get_str_cap("LE"),
parm_right_cursor: get_str_cap("RI"),
clr_eol: get_str_cap("ce"),
clr_eos: get_str_cap("cd"),
// Number capabilities
max_colors: get_num_cap("Co"),
init_tabs: get_num_cap("it"),
// Flag/boolean capabilities
eat_newline_glitch: get_flag_cap("xn"),
auto_right_margin: get_flag_cap("am"),
}
}
}
@@ -267,6 +305,16 @@ fn get_flag_cap(code: &str) -> bool {
[code[0] as c_char, code[1] as c_char, b'\0' as c_char]
}
/// Covers over tparm().
pub fn tparm0(cap: &CStr) -> Option<CString> {
// Take the lock because tparm races with del_curterm, etc.
let _term: std::sync::MutexGuard<Option<Arc<Term>>> = TERM.lock().unwrap();
assert!(!cap.to_bytes().is_empty());
let cap_ptr = cap.as_ptr() as *mut libc::c_char;
// Safety: we're trusting tparm here.
unsafe { try_ptr_to_cstr(tparm(cap_ptr)) }
}
/// Covers over tparm().
pub fn tparm1(cap: &CStr, param1: i32) -> Option<CString> {
// Take the lock because tparm races with del_curterm, etc.

View File

@@ -8,6 +8,8 @@
use crate::input_common::{update_wait_on_escape_ms, update_wait_on_sequence_key_ms};
use crate::output::ColorSupport;
use crate::proc::is_interactive_session;
use crate::screen::screen_set_midnight_commander_hack;
use crate::screen::LAYOUT_CACHE_SHARED;
use crate::wchar::prelude::*;
use crate::wchar_ffi::WCharToFFI;
use crate::wutil::fish_wcstoi;
@@ -578,7 +580,7 @@ fn apply_non_term_hacks(vars: &EnvStack) {
// broken if you do '\r' after it like we normally do.
// See https://midnight-commander.org/ticket/4258.
if vars.get(L!("MC_SID")).is_some() {
crate::ffi::screen_set_midnight_commander_hack();
screen_set_midnight_commander_hack();
}
}
@@ -686,7 +688,7 @@ fn init_curses(vars: &EnvStack) {
update_fish_color_support(vars);
// Invalidate the cached escape sequences since they may no longer be valid.
crate::ffi::screen_clear_layout_cache_ffi();
unsafe { LAYOUT_CACHE_SHARED.lock().unwrap() }.clear();
CURSES_INITIALIZED.store(true, Ordering::Relaxed);
}

View File

@@ -36,6 +36,7 @@
#include "parser.h"
#include "parse_util.h"
#include "path.h"
#include "pager.h"
#include "proc.h"
#include "reader.h"
#include "screen.h"
@@ -77,6 +78,7 @@
generate_pod!("pipes_ffi_t")
generate!("shell_modes_ffi")
generate!("make_pipes_ffi")
generate!("log_extra_to_flog_file")
@@ -103,14 +105,16 @@
generate!("commandline_get_state_text_ffi")
generate!("completion_apply_to_command_line")
generate!("pager_t")
generate!("page_rendering_t")
generate!("pager_set_term_size_ffi")
generate!("pager_update_rendering_ffi")
generate!("get_history_variable_text_ffi")
generate_pod!("escape_string_style_t")
generate!("screen_set_midnight_commander_hack")
generate!("screen_clear_layout_cache_ffi")
generate!("escape_code_length_ffi")
generate!("reader_schedule_prompt_repaint")
generate!("reader_change_history")
generate!("reader_change_cursor_selection_mode")
@@ -169,6 +173,8 @@ impl Repin for IoStreams<'_> {}
impl Repin for wcstring_list_ffi_t {}
impl Repin for rgb_color_t {}
impl Repin for OutputStreamFfi<'_> {}
impl Repin for pager_t {}
impl Repin for page_rendering_t {}
pub use autocxx::c_int;
pub use ffi::*;

25
fish-rust/src/future.rs Normal file
View File

@@ -0,0 +1,25 @@
//! stdlib backports
pub trait IsSomeAnd {
type Type;
#[allow(clippy::wrong_self_convention)]
fn is_some_and(self, s: impl FnOnce(Self::Type) -> bool) -> bool;
#[allow(clippy::wrong_self_convention)]
fn is_none_or(self, s: impl FnOnce(Self::Type) -> bool) -> bool;
}
impl<T> IsSomeAnd for Option<T> {
type Type = T;
fn is_some_and(self, f: impl FnOnce(T) -> bool) -> bool {
match self {
Some(v) => f(v),
None => false,
}
}
fn is_none_or(self, f: impl FnOnce(T) -> bool) -> bool {
match self {
Some(v) => f(v),
None => true,
}
}
}

View File

@@ -130,11 +130,11 @@ pub struct HighlightColorResolver {
/// It maintains a cache with no invalidation mechanism. The lifetime of these should typically be
/// one screen redraw.
impl HighlightColorResolver {
fn new() -> Self {
pub fn new() -> Self {
Default::default()
}
/// \return an RGB color for a given highlight spec.
fn resolve_spec(
pub fn resolve_spec(
&mut self,
highlight: &HighlightSpec,
is_background: bool,
@@ -1718,6 +1718,7 @@ fn resolve_spec_ffi(
}
extern "Rust" {
type HighlightSpecListFFI;
fn new_highlight_spec_list() -> Box<HighlightSpecListFFI>;
fn highlight_shell_ffi(
bff: &CxxWString,
ctx: &OperationContext<'_>,
@@ -1732,6 +1733,7 @@ fn colorize_ffi(
colors: &HighlightSpecListFFI,
vars: &EnvStackRefFFI,
) -> Vec<u8>;
fn push(&mut self, highlight: &HighlightSpec);
}
}
@@ -1743,8 +1745,17 @@ fn colorize_ffi(
colorize(text.as_wstr(), &colors.0, &*vars.0)
}
struct HighlightSpecListFFI(Vec<HighlightSpec>);
#[derive(Default)]
pub struct HighlightSpecListFFI(pub Vec<HighlightSpec>);
unsafe impl cxx::ExternType for HighlightSpecListFFI {
type Id = cxx::type_id!("HighlightSpecListFFI");
type Kind = cxx::kind::Opaque;
}
fn new_highlight_spec_list() -> Box<HighlightSpecListFFI> {
Box::default()
}
impl HighlightSpecListFFI {
fn size(&self) -> usize {
self.0.len()
@@ -1752,6 +1763,9 @@ fn size(&self) -> usize {
fn at(&self, index: usize) -> &HighlightSpec {
&self.0[index]
}
fn push(&mut self, highlight: &HighlightSpec) {
self.0.push(*highlight)
}
}
fn highlight_shell_ffi(
buff: &CxxWString,

View File

@@ -58,6 +58,7 @@
mod flog;
mod fork_exec;
mod function;
mod future;
mod future_feature_flags;
mod global_safety;
mod highlight;
@@ -85,6 +86,7 @@
mod re;
mod reader;
mod redirection;
mod screen;
mod signal;
mod smoke;
mod termsize;

View File

@@ -6,7 +6,7 @@
use crate::wchar::prelude::*;
use bitflags::bitflags;
use std::cell::RefCell;
use std::ffi::{CStr, CString};
use std::ffi::CStr;
use std::io::{Result, Write};
use std::os::fd::RawFd;
use std::sync::atomic::{AtomicU8, Ordering};
@@ -406,13 +406,13 @@ fn flush_to(&mut self, fd: RawFd) {
/// Begins buffering. Output will not be automatically flushed until a corresponding
/// end_buffering() call.
fn begin_buffering(&mut self) {
pub fn begin_buffering(&mut self) {
self.buffer_count += 1;
assert!(self.buffer_count > 0, "buffer_count overflow");
}
/// Balance a begin_buffering() call.
fn end_buffering(&mut self) {
pub fn end_buffering(&mut self) {
assert!(self.buffer_count > 0, "buffer_count underflow");
self.buffer_count -= 1;
self.maybe_flush();
@@ -467,9 +467,9 @@ pub fn tputs(&mut self, str: &CStr) {
/// Convenience cover over tputs, in recognition of the fact that our Term has Optional fields.
/// If `str` is Some, write it with tputs and return true. Otherwise, return false.
pub fn tputs_if_some(&mut self, str: &Option<CString>) -> bool {
pub fn tputs_if_some(&mut self, str: &Option<impl AsRef<CStr>>) -> bool {
if let Some(str) = str {
self.tputs(str);
self.tputs(str.as_ref());
true
} else {
false

2019
fish-rust/src/screen.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,7 @@
mod parser;
#[cfg(test)]
mod redirection;
mod screen;
mod string_escape;
#[cfg(test)]
mod tokenizer;

View File

@@ -0,0 +1,238 @@
use crate::common::get_ellipsis_char;
use crate::ffi_tests::add_test;
use crate::screen::{LayoutCache, PromptCacheEntry, PromptLayout};
use crate::wchar::prelude::*;
use crate::wcstringutil::join_strings;
add_test!("test_complete", || {
let mut lc = LayoutCache::new();
assert_eq!(lc.escape_code_length(L!("")), 0);
assert_eq!(lc.escape_code_length(L!("abcd")), 0);
assert_eq!(lc.escape_code_length(L!("\x1B[2J")), 4);
assert_eq!(
lc.escape_code_length(L!("\x1B[38;5;123mABC")),
"\x1B[38;5;123m".len()
);
assert_eq!(lc.escape_code_length(L!("\x1B@")), 2);
// iTerm2 escape sequences.
assert_eq!(
lc.escape_code_length(L!("\x1B]50;CurrentDir=test/foo\x07NOT_PART_OF_SEQUENCE")),
25
);
assert_eq!(
lc.escape_code_length(L!("\x1B]50;SetMark\x07NOT_PART_OF_SEQUENCE")),
13
);
assert_eq!(
lc.escape_code_length(L!("\x1B]6;1;bg;red;brightness;255\x07NOT_PART_OF_SEQUENCE")),
28
);
assert_eq!(
lc.escape_code_length(L!("\x1B]Pg4040ff\x1B\\NOT_PART_OF_SEQUENCE")),
12
);
assert_eq!(lc.escape_code_length(L!("\x1B]blahblahblah\x1B\\")), 16);
assert_eq!(lc.escape_code_length(L!("\x1B]blahblahblah\x07")), 15);
});
add_test!("test_layout_cache", || {
let mut seqs = LayoutCache::new();
// Verify escape code cache.
assert_eq!(seqs.find_escape_code(L!("abc")), 0);
seqs.add_escape_code(L!("abc").to_owned());
seqs.add_escape_code(L!("abc").to_owned());
assert_eq!(seqs.esc_cache_size(), 1);
assert_eq!(seqs.find_escape_code(L!("abc")), 3);
assert_eq!(seqs.find_escape_code(L!("abcd")), 3);
assert_eq!(seqs.find_escape_code(L!("abcde")), 3);
assert_eq!(seqs.find_escape_code(L!("xabcde")), 0);
seqs.add_escape_code(L!("ac").to_owned());
assert_eq!(seqs.find_escape_code(L!("abcd")), 3);
assert_eq!(seqs.find_escape_code(L!("acbd")), 2);
seqs.add_escape_code(L!("wxyz").to_owned());
assert_eq!(seqs.find_escape_code(L!("abc")), 3);
assert_eq!(seqs.find_escape_code(L!("abcd")), 3);
assert_eq!(seqs.find_escape_code(L!("wxyz123")), 4);
assert_eq!(seqs.find_escape_code(L!("qwxyz123")), 0);
assert_eq!(seqs.esc_cache_size(), 3);
seqs.clear();
assert_eq!(seqs.esc_cache_size(), 0);
assert_eq!(seqs.find_escape_code(L!("abcd")), 0);
let huge = usize::MAX;
// Verify prompt layout cache.
for i in 0..LayoutCache::PROMPT_CACHE_MAX_SIZE {
let input = i.to_wstring();
assert!(!seqs.find_prompt_layout(&input, usize::MAX));
seqs.add_prompt_layout(PromptCacheEntry {
text: input.clone(),
max_line_width: huge,
trunc_text: input.clone(),
layout: PromptLayout {
line_breaks: vec![],
max_line_width: i,
last_line_width: 0,
},
});
assert!(seqs.find_prompt_layout(&input, usize::MAX));
assert_eq!(seqs.prompt_cache.front().unwrap().layout.max_line_width, i);
}
let expected_evictee = 3;
for i in 0..LayoutCache::PROMPT_CACHE_MAX_SIZE {
if i != expected_evictee {
assert!(seqs.find_prompt_layout(&i.to_wstring(), usize::MAX));
assert_eq!(seqs.prompt_cache.front().unwrap().layout.max_line_width, i);
}
}
seqs.add_prompt_layout(PromptCacheEntry {
text: "whatever".into(),
max_line_width: huge,
trunc_text: "whatever".into(),
layout: PromptLayout {
line_breaks: vec![],
max_line_width: 100,
last_line_width: 0,
},
});
assert!(!seqs.find_prompt_layout(&expected_evictee.to_wstring(), usize::MAX));
assert!(seqs.find_prompt_layout(L!("whatever"), huge));
assert_eq!(
seqs.prompt_cache.front().unwrap().layout.max_line_width,
100
);
});
add_test!("test_prompt_truncation", || {
let mut cache = LayoutCache::new();
let mut trunc = WString::new();
let ellipsis = || WString::from_chars([get_ellipsis_char()]);
// No truncation.
let layout = cache.calc_prompt_layout(L!("abcd"), Some(&mut trunc), usize::MAX);
assert_eq!(
layout,
PromptLayout {
line_breaks: vec![],
max_line_width: 4,
last_line_width: 4,
}
);
assert_eq!(trunc, L!("abcd"));
// Line break calculation.
let layout = cache.calc_prompt_layout(
L!(concat!(
"0123456789ABCDEF\n",
"012345\n",
"0123456789abcdef\n",
"xyz"
)),
Some(&mut trunc),
80,
);
assert_eq!(
layout,
PromptLayout {
line_breaks: vec![16, 23, 40],
max_line_width: 16,
last_line_width: 3,
}
);
// Basic truncation.
let layout = cache.calc_prompt_layout(L!("0123456789ABCDEF"), Some(&mut trunc), 8);
assert_eq!(
layout,
PromptLayout {
line_breaks: vec![],
max_line_width: 8,
last_line_width: 8,
},
);
assert_eq!(trunc, ellipsis() + L!("9ABCDEF"));
// Multiline truncation.
let layout = cache.calc_prompt_layout(
L!(concat!(
"0123456789ABCDEF\n",
"012345\n",
"0123456789abcdef\n",
"xyz"
)),
Some(&mut trunc),
8,
);
assert_eq!(
layout,
PromptLayout {
line_breaks: vec![8, 15, 24],
max_line_width: 8,
last_line_width: 3,
},
);
assert_eq!(
trunc,
join_strings(
&[
ellipsis() + L!("9ABCDEF"),
L!("012345").to_owned(),
ellipsis() + L!("9abcdef"),
L!("xyz").to_owned(),
],
'\n',
),
);
// Escape sequences are not truncated.
let layout = cache.calc_prompt_layout(
L!("\x1B]50;CurrentDir=test/foo\x07NOT_PART_OF_SEQUENCE"),
Some(&mut trunc),
4,
);
assert_eq!(
layout,
PromptLayout {
line_breaks: vec![],
max_line_width: 4,
last_line_width: 4,
},
);
assert_eq!(trunc, ellipsis() + L!("\x1B]50;CurrentDir=test/foo\x07NCE"));
// Newlines in escape sequences are skipped.
let layout = cache.calc_prompt_layout(
L!("\x1B]50;CurrentDir=\ntest/foo\x07NOT_PART_OF_SEQUENCE"),
Some(&mut trunc),
4,
);
assert_eq!(
layout,
PromptLayout {
line_breaks: vec![],
max_line_width: 4,
last_line_width: 4,
},
);
assert_eq!(
trunc,
ellipsis() + L!("\x1B]50;CurrentDir=\ntest/foo\x07NCE")
);
// We will truncate down to one character if we have to.
let layout = cache.calc_prompt_layout(L!("Yay"), Some(&mut trunc), 1);
assert_eq!(
layout,
PromptLayout {
line_breaks: vec![],
max_line_width: 1,
last_line_width: 1,
},
);
assert_eq!(trunc, ellipsis());
});