mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-26 12:53:13 +00:00
Port screen.cpp
This commit is contained in:
parent
5f1499cd67
commit
31ad182aa5
24 changed files with 2462 additions and 1932 deletions
|
@ -126,7 +126,6 @@ set(FISH_SRCS
|
|||
src/path.cpp
|
||||
src/reader.cpp
|
||||
src/rustffi.cpp
|
||||
src/screen.cpp
|
||||
src/wcstringutil.cpp
|
||||
src/wgetopt.cpp
|
||||
src/wutil.cpp
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 @@ impl Term {
|
|||
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 @@ const fn to_cstr_code(code: &str) -> [libc::c_char; 3] {
|
|||
[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.
|
||||
|
|
|
@ -8,6 +8,8 @@ use crate::function;
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ include_cpp! {
|
|||
#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 @@ include_cpp! {
|
|||
|
||||
generate_pod!("pipes_ffi_t")
|
||||
|
||||
generate!("shell_modes_ffi")
|
||||
generate!("make_pipes_ffi")
|
||||
|
||||
generate!("log_extra_to_flog_file")
|
||||
|
@ -103,14 +105,16 @@ include_cpp! {
|
|||
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
25
fish-rust/src/future.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 @@ mod highlight_ffi {
|
|||
}
|
||||
extern "Rust" {
|
||||
type HighlightSpecListFFI;
|
||||
fn new_highlight_spec_list() -> Box<HighlightSpecListFFI>;
|
||||
fn highlight_shell_ffi(
|
||||
bff: &CxxWString,
|
||||
ctx: &OperationContext<'_>,
|
||||
|
@ -1732,6 +1733,7 @@ mod highlight_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 @@ impl HighlightSpecListFFI {
|
|||
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,
|
||||
|
|
|
@ -58,6 +58,7 @@ mod fish_indent;
|
|||
mod flog;
|
||||
mod fork_exec;
|
||||
mod function;
|
||||
mod future;
|
||||
mod future_feature_flags;
|
||||
mod global_safety;
|
||||
mod highlight;
|
||||
|
@ -85,6 +86,7 @@ mod proc;
|
|||
mod re;
|
||||
mod reader;
|
||||
mod redirection;
|
||||
mod screen;
|
||||
mod signal;
|
||||
mod smoke;
|
||||
mod termsize;
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::env::EnvVar;
|
|||
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 @@ impl Outputter {
|
|||
|
||||
/// 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 @@ impl Outputter {
|
|||
|
||||
/// 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
2019
fish-rust/src/screen.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -12,6 +12,7 @@ mod history;
|
|||
mod parser;
|
||||
#[cfg(test)]
|
||||
mod redirection;
|
||||
mod screen;
|
||||
mod string_escape;
|
||||
#[cfg(test)]
|
||||
mod tokenizer;
|
||||
|
|
238
fish-rust/src/tests/screen.rs
Normal file
238
fish-rust/src/tests/screen.rs
Normal 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());
|
||||
});
|
|
@ -58,6 +58,8 @@
|
|||
|
||||
struct termios shell_modes;
|
||||
|
||||
struct termios *shell_modes_ffi() { return &shell_modes; }
|
||||
|
||||
const wcstring g_empty_string{};
|
||||
const std::vector<wcstring> g_empty_string_list{};
|
||||
|
||||
|
|
|
@ -656,4 +656,6 @@ __attribute__((always_inline)) bool inline iswdigit(const wchar_t c) {
|
|||
#include "common.rs.h"
|
||||
#endif
|
||||
|
||||
struct termios *shell_modes_ffi();
|
||||
|
||||
#endif // FISH_COMMON_H
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
void mark_as_used(const parser_t& parser, env_stack_t& env_stack) {
|
||||
wcstring s;
|
||||
|
||||
escape_code_length_ffi({});
|
||||
event_fire_generic(parser, {});
|
||||
event_fire_generic(parser, {}, {});
|
||||
expand_tilde(s, env_stack);
|
||||
|
@ -34,8 +33,6 @@ void mark_as_used(const parser_t& parser, env_stack_t& env_stack) {
|
|||
reader_status_count();
|
||||
restore_term_mode();
|
||||
rgb_color_t{};
|
||||
screen_clear_layout_cache_ffi();
|
||||
screen_set_midnight_commander_hack();
|
||||
setenv_lock({}, {}, {});
|
||||
set_inheriteds_ffi();
|
||||
term_copy_modes();
|
||||
|
|
|
@ -993,36 +993,6 @@ static void test_utility_functions() {
|
|||
test_const_strcmp();
|
||||
}
|
||||
|
||||
// todo!("port this");
|
||||
static void test_escape_sequences() {
|
||||
say(L"Testing escape_sequences");
|
||||
layout_cache_t lc;
|
||||
if (lc.escape_code_length(L"") != 0)
|
||||
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
||||
if (lc.escape_code_length(L"abcd") != 0)
|
||||
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
||||
if (lc.escape_code_length(L"\x1B[2J") != 4)
|
||||
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
||||
if (lc.escape_code_length(L"\x1B[38;5;123mABC") != strlen("\x1B[38;5;123m"))
|
||||
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
||||
if (lc.escape_code_length(L"\x1B@") != 2)
|
||||
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
||||
|
||||
// iTerm2 escape sequences.
|
||||
if (lc.escape_code_length(L"\x1B]50;CurrentDir=test/foo\x07NOT_PART_OF_SEQUENCE") != 25)
|
||||
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
||||
if (lc.escape_code_length(L"\x1B]50;SetMark\x07NOT_PART_OF_SEQUENCE") != 13)
|
||||
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
||||
if (lc.escape_code_length(L"\x1B]6;1;bg;red;brightness;255\x07NOT_PART_OF_SEQUENCE") != 28)
|
||||
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
||||
if (lc.escape_code_length(L"\x1B]Pg4040ff\x1B\\NOT_PART_OF_SEQUENCE") != 12)
|
||||
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
||||
if (lc.escape_code_length(L"\x1B]blahblahblah\x1B\\") != 16)
|
||||
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
||||
if (lc.escape_code_length(L"\x1B]blahblahblah\x07") != 15)
|
||||
err(L"test_escape_sequences failed on line %d\n", __LINE__);
|
||||
}
|
||||
|
||||
class test_lru_t : public lru_cache_t<int> {
|
||||
public:
|
||||
static constexpr size_t test_capacity = 16;
|
||||
|
@ -1300,7 +1270,7 @@ struct pager_layout_testcase_t {
|
|||
void run(pager_t &pager) const {
|
||||
pager.set_term_size(termsize_t{this->width, 24});
|
||||
page_rendering_t rendering = pager.render();
|
||||
const screen_data_t &sd = rendering.screen_data;
|
||||
const screen_data_t &sd = *rendering.screen_data;
|
||||
do_test(sd.line_count() == 1);
|
||||
if (sd.line_count() > 0) {
|
||||
wcstring expected = this->expected;
|
||||
|
@ -1311,10 +1281,7 @@ struct pager_layout_testcase_t {
|
|||
std::replace(expected.begin(), expected.end(), L'\x2026', ellipsis_char);
|
||||
}
|
||||
|
||||
wcstring text;
|
||||
for (const auto &p : sd.line(0).text) {
|
||||
text.push_back(p.character);
|
||||
}
|
||||
wcstring text = *(sd.line_ffi(0)->text_characters_ffi());
|
||||
if (text != expected) {
|
||||
std::fwprintf(stderr, L"width %d got %zu<%ls>, expected %zu<%ls>\n", this->width,
|
||||
text.length(), text.c_str(), expected.length(), expected.c_str());
|
||||
|
@ -2315,129 +2282,6 @@ void test_maybe() {
|
|||
do_test(c2.value_or("derp") == "derp");
|
||||
}
|
||||
|
||||
// todo!("delete this")
|
||||
void test_layout_cache() {
|
||||
layout_cache_t seqs;
|
||||
|
||||
// Verify escape code cache.
|
||||
do_test(seqs.find_escape_code(L"abc") == 0);
|
||||
seqs.add_escape_code(L"abc");
|
||||
seqs.add_escape_code(L"abc");
|
||||
do_test(seqs.esc_cache_size() == 1);
|
||||
do_test(seqs.find_escape_code(L"abc") == 3);
|
||||
do_test(seqs.find_escape_code(L"abcd") == 3);
|
||||
do_test(seqs.find_escape_code(L"abcde") == 3);
|
||||
do_test(seqs.find_escape_code(L"xabcde") == 0);
|
||||
seqs.add_escape_code(L"ac");
|
||||
do_test(seqs.find_escape_code(L"abcd") == 3);
|
||||
do_test(seqs.find_escape_code(L"acbd") == 2);
|
||||
seqs.add_escape_code(L"wxyz");
|
||||
do_test(seqs.find_escape_code(L"abc") == 3);
|
||||
do_test(seqs.find_escape_code(L"abcd") == 3);
|
||||
do_test(seqs.find_escape_code(L"wxyz123") == 4);
|
||||
do_test(seqs.find_escape_code(L"qwxyz123") == 0);
|
||||
do_test(seqs.esc_cache_size() == 3);
|
||||
seqs.clear();
|
||||
do_test(seqs.esc_cache_size() == 0);
|
||||
do_test(seqs.find_escape_code(L"abcd") == 0);
|
||||
|
||||
auto huge = std::numeric_limits<size_t>::max();
|
||||
|
||||
// Verify prompt layout cache.
|
||||
for (size_t i = 0; i < layout_cache_t::prompt_cache_max_size; i++) {
|
||||
wcstring input = std::to_wstring(i);
|
||||
do_test(!seqs.find_prompt_layout(input));
|
||||
seqs.add_prompt_layout({input, huge, input, {{}, i, 0}});
|
||||
do_test(seqs.find_prompt_layout(input)->layout.max_line_width == i);
|
||||
}
|
||||
|
||||
size_t expected_evictee = 3;
|
||||
for (size_t i = 0; i < layout_cache_t::prompt_cache_max_size; i++) {
|
||||
if (i != expected_evictee)
|
||||
do_test(seqs.find_prompt_layout(std::to_wstring(i))->layout.max_line_width == i);
|
||||
}
|
||||
|
||||
seqs.add_prompt_layout({L"whatever", huge, L"whatever", {{}, 100, 0}});
|
||||
do_test(!seqs.find_prompt_layout(std::to_wstring(expected_evictee)));
|
||||
do_test(seqs.find_prompt_layout(L"whatever", huge)->layout.max_line_width == 100);
|
||||
}
|
||||
|
||||
// todo!("port this")
|
||||
void test_prompt_truncation() {
|
||||
layout_cache_t cache;
|
||||
wcstring trunc;
|
||||
prompt_layout_t layout;
|
||||
|
||||
/// Helper to return 'layout' formatted as a string for easy comparison.
|
||||
auto format_layout = [&] {
|
||||
wcstring line_breaks;
|
||||
bool first = true;
|
||||
for (const size_t line_break : layout.line_breaks) {
|
||||
if (!first) {
|
||||
line_breaks.push_back(L',');
|
||||
}
|
||||
line_breaks.append(format_string(L"%lu", (unsigned long)line_break));
|
||||
first = false;
|
||||
}
|
||||
return format_string(L"[%ls],%lu,%lu", line_breaks.c_str(),
|
||||
(unsigned long)layout.max_line_width,
|
||||
(unsigned long)layout.last_line_width);
|
||||
};
|
||||
|
||||
/// Join some strings with newline.
|
||||
auto join = [](std::initializer_list<wcstring> vals) { return join_strings(vals, L'\n'); };
|
||||
|
||||
wcstring ellipsis = {get_ellipsis_char()};
|
||||
|
||||
// No truncation.
|
||||
layout = cache.calc_prompt_layout(L"abcd", &trunc);
|
||||
do_test(format_layout() == L"[],4,4");
|
||||
do_test(trunc == L"abcd");
|
||||
|
||||
// Line break calculation.
|
||||
layout = cache.calc_prompt_layout(join({
|
||||
L"0123456789ABCDEF", //
|
||||
L"012345", //
|
||||
L"0123456789abcdef", //
|
||||
L"xyz" //
|
||||
}),
|
||||
&trunc, 80);
|
||||
do_test(format_layout() == L"[16,23,40],16,3");
|
||||
|
||||
// Basic truncation.
|
||||
layout = cache.calc_prompt_layout(L"0123456789ABCDEF", &trunc, 8);
|
||||
do_test(format_layout() == L"[],8,8");
|
||||
do_test(trunc == ellipsis + L"9ABCDEF");
|
||||
|
||||
// Multiline truncation.
|
||||
layout = cache.calc_prompt_layout(join({
|
||||
L"0123456789ABCDEF", //
|
||||
L"012345", //
|
||||
L"0123456789abcdef", //
|
||||
L"xyz" //
|
||||
}),
|
||||
&trunc, 8);
|
||||
do_test(format_layout() == L"[8,15,24],8,3");
|
||||
do_test(trunc == join({ellipsis + L"9ABCDEF", L"012345", ellipsis + L"9abcdef", L"xyz"}));
|
||||
|
||||
// Escape sequences are not truncated.
|
||||
layout =
|
||||
cache.calc_prompt_layout(L"\x1B]50;CurrentDir=test/foo\x07NOT_PART_OF_SEQUENCE", &trunc, 4);
|
||||
do_test(format_layout() == L"[],4,4");
|
||||
do_test(trunc == ellipsis + L"\x1B]50;CurrentDir=test/foo\x07NCE");
|
||||
|
||||
// Newlines in escape sequences are skipped.
|
||||
layout = cache.calc_prompt_layout(L"\x1B]50;CurrentDir=\ntest/foo\x07NOT_PART_OF_SEQUENCE",
|
||||
&trunc, 4);
|
||||
do_test(format_layout() == L"[],4,4");
|
||||
do_test(trunc == ellipsis + L"\x1B]50;CurrentDir=\ntest/foo\x07NCE");
|
||||
|
||||
// We will truncate down to one character if we have to.
|
||||
layout = cache.calc_prompt_layout(L"Yay", &trunc, 1);
|
||||
do_test(format_layout() == L"[],1,1");
|
||||
do_test(trunc == ellipsis);
|
||||
}
|
||||
|
||||
// todo!("already ported, delete this")
|
||||
void test_normalize_path() {
|
||||
say(L"Testing path normalization");
|
||||
|
@ -2649,7 +2493,6 @@ static const test_t s_tests[]{
|
|||
{TEST_GROUP("autosuggestion"), test_autosuggestion_combining},
|
||||
{TEST_GROUP("new_parser_ll2"), test_new_parser_ll2},
|
||||
{TEST_GROUP("test_abbreviations"), test_abbreviations},
|
||||
{TEST_GROUP("test_escape_sequences"), test_escape_sequences},
|
||||
{TEST_GROUP("new_parser_fuzzing"), test_new_parser_fuzzing},
|
||||
{TEST_GROUP("new_parser_correctness"), test_new_parser_correctness},
|
||||
{TEST_GROUP("new_parser_ad_hoc"), test_new_parser_ad_hoc},
|
||||
|
@ -2676,8 +2519,6 @@ static const test_t s_tests[]{
|
|||
{TEST_GROUP("completion_insertions"), test_completion_insertions},
|
||||
{TEST_GROUP("illegal_command_exit_code"), test_illegal_command_exit_code},
|
||||
{TEST_GROUP("maybe"), test_maybe},
|
||||
{TEST_GROUP("layout_cache"), test_layout_cache},
|
||||
{TEST_GROUP("prompt"), test_prompt_truncation},
|
||||
{TEST_GROUP("normalize"), test_normalize_path},
|
||||
{TEST_GROUP("dirname"), test_dirname_basename},
|
||||
{TEST_GROUP("pipes"), test_pipes},
|
||||
|
|
|
@ -28,6 +28,7 @@ struct highlight_spec_t;
|
|||
#else
|
||||
struct HighlightSpec;
|
||||
enum class HighlightRole : uint8_t;
|
||||
struct HighlightSpecListFFI;
|
||||
#endif
|
||||
|
||||
using highlight_role_t = HighlightRole;
|
||||
|
|
|
@ -134,14 +134,15 @@ static size_t print_max(const wcstring &str, highlight_spec_t color, size_t max,
|
|||
}
|
||||
|
||||
/// Print the specified item using at the specified amount of space.
|
||||
line_t pager_t::completion_print_item(const wcstring &prefix, const comp_t *c, size_t row,
|
||||
size_t column, size_t width, bool secondary, bool selected,
|
||||
page_rendering_t *rendering) const {
|
||||
rust::Box<Line> pager_t::completion_print_item(const wcstring &prefix, const comp_t *c, size_t row,
|
||||
size_t column, size_t width, bool secondary,
|
||||
bool selected, page_rendering_t *rendering) const {
|
||||
UNUSED(column);
|
||||
UNUSED(row);
|
||||
UNUSED(rendering);
|
||||
size_t comp_width;
|
||||
line_t line_data;
|
||||
rust::Box<Line> line_data_box = new_line();
|
||||
auto &line_data = *line_data_box;
|
||||
|
||||
if (c->preferred_width() <= width) {
|
||||
// The entry fits, we give it as much space as it wants.
|
||||
|
@ -228,7 +229,7 @@ line_t pager_t::completion_print_item(const wcstring &prefix, const comp_t *c, s
|
|||
print_max(wcstring(desc_remaining, L' '), bg, desc_remaining, false, &line_data);
|
||||
}
|
||||
|
||||
return line_data;
|
||||
return line_data_box;
|
||||
}
|
||||
|
||||
/// Print the specified part of the completion list, using the specified column offsets and quoting
|
||||
|
@ -261,16 +262,16 @@ void pager_t::completion_print(size_t cols, const size_t *width_by_column, size_
|
|||
bool is_selected = (idx == effective_selected_idx);
|
||||
|
||||
// Print this completion on its own "line".
|
||||
line_t line = completion_print_item(prefix, el, row, col, width_by_column[col], row % 2,
|
||||
is_selected, rendering);
|
||||
auto line = completion_print_item(prefix, el, row, col, width_by_column[col], row % 2,
|
||||
is_selected, rendering);
|
||||
|
||||
// If there's more to come, append two spaces.
|
||||
if (col + 1 < cols) {
|
||||
line.append(PAGER_SPACER_STRING, highlight_spec_t{});
|
||||
line->append_str(PAGER_SPACER_STRING, highlight_spec_t{});
|
||||
}
|
||||
|
||||
// Append this to the real line.
|
||||
rendering->screen_data.create_line(row - row_start).append_line(line);
|
||||
rendering->screen_data->create_line(row - row_start)->append_line(*line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -447,11 +448,19 @@ void pager_t::set_prefix(const wcstring &pref, bool highlight) {
|
|||
highlight_prefix = highlight;
|
||||
}
|
||||
|
||||
void pager_t::set_term_size(termsize_t ts) {
|
||||
void pager_t::set_term_size(const termsize_t &ts) {
|
||||
available_term_width = ts.width > 0 ? ts.width : 0;
|
||||
available_term_height = ts.height > 0 ? ts.height : 0;
|
||||
}
|
||||
|
||||
void pager_set_term_size_ffi(pager_t &pager, const void *ts) {
|
||||
pager.set_term_size(*reinterpret_cast<const termsize_t *>(ts));
|
||||
}
|
||||
|
||||
void pager_update_rendering_ffi(pager_t &pager, page_rendering_t &rendering) {
|
||||
pager.update_rendering(&rendering);
|
||||
}
|
||||
|
||||
/// Try to print the list of completions lst with the prefix prefix using cols as the number of
|
||||
/// columns. Return true if the completion list was printed, false if the terminal is too narrow for
|
||||
/// the specified number of columns. Always succeeds if cols is 1.
|
||||
|
@ -571,7 +580,7 @@ bool pager_t::completion_try_print(size_t cols, const wcstring &prefix, const co
|
|||
}
|
||||
|
||||
if (!progress_text.empty()) {
|
||||
line_t &line = rendering->screen_data.add_line();
|
||||
line_t &line = rendering->screen_data->add_line();
|
||||
highlight_spec_t spec = {highlight_role_t::pager_progress,
|
||||
highlight_role_t::pager_progress};
|
||||
print_max(progress_text, spec, term_width, true /* has_more */, &line);
|
||||
|
@ -587,7 +596,7 @@ bool pager_t::completion_try_print(size_t cols, const wcstring &prefix, const co
|
|||
if (search_field_text.size() < PAGER_SEARCH_FIELD_WIDTH) {
|
||||
search_field_text.append(PAGER_SEARCH_FIELD_WIDTH - search_field_text.size(), L' ');
|
||||
}
|
||||
line_t *search_field = &rendering->screen_data.insert_line_at_index(0);
|
||||
line_t *search_field = &rendering->screen_data->insert_line_at_index(0);
|
||||
|
||||
// We limit the width to term_width - 1.
|
||||
highlight_spec_t underline{};
|
||||
|
@ -613,7 +622,7 @@ page_rendering_t pager_t::render() const {
|
|||
|
||||
for (size_t cols = PAGER_MAX_COLS; cols > 0; cols--) {
|
||||
// Initially empty rendering.
|
||||
rendering.screen_data.resize(0);
|
||||
rendering.screen_data->resize(0);
|
||||
|
||||
// Determine how many rows we would need if we had 'cols' columns. Then determine how many
|
||||
// columns we want from that. For example, say we had 19 completions. We can fit them into 6
|
||||
|
@ -645,9 +654,9 @@ page_rendering_t pager_t::render() const {
|
|||
bool pager_t::rendering_needs_update(const page_rendering_t &rendering) const {
|
||||
if (have_unrendered_completions) return true;
|
||||
// Common case is no pager.
|
||||
if (this->empty() && rendering.screen_data.empty()) return false;
|
||||
if (this->empty() && rendering.screen_data->empty()) return false;
|
||||
|
||||
return (this->empty() && !rendering.screen_data.empty()) || // Do update after clear().
|
||||
return (this->empty() && !rendering.screen_data->empty()) || // Do update after clear().
|
||||
rendering.term_width != this->available_term_width || //
|
||||
rendering.term_height != this->available_term_height || //
|
||||
rendering.selected_completion_idx !=
|
||||
|
@ -956,4 +965,4 @@ size_t pager_t::cursor_position() const {
|
|||
return result;
|
||||
}
|
||||
|
||||
page_rendering_t::page_rendering_t() = default;
|
||||
page_rendering_t::page_rendering_t() : screen_data(new_screen_data()) {}
|
||||
|
|
20
src/pager.h
20
src/pager.h
|
@ -28,8 +28,9 @@ class page_rendering_t {
|
|||
size_t row_start{0};
|
||||
size_t row_end{0};
|
||||
size_t selected_completion_idx{size_t(-1)};
|
||||
screen_data_t screen_data{};
|
||||
rust::Box<ScreenData> screen_data;
|
||||
|
||||
const screen_data_t *screen_data_ffi() const { return &*screen_data; }
|
||||
size_t remaining_to_disclose{0};
|
||||
|
||||
bool search_field_shown{false};
|
||||
|
@ -37,6 +38,10 @@ class page_rendering_t {
|
|||
|
||||
// Returns a rendering with invalid data, useful to indicate "no rendering".
|
||||
page_rendering_t();
|
||||
page_rendering_t(const page_rendering_t &) = delete;
|
||||
page_rendering_t(page_rendering_t &&) = default;
|
||||
page_rendering_t &operator=(const page_rendering_t &) = delete;
|
||||
page_rendering_t &operator=(page_rendering_t &&) = default;
|
||||
};
|
||||
|
||||
enum class selection_motion_t {
|
||||
|
@ -87,8 +92,10 @@ class pager_t {
|
|||
std::vector<wcstring> comp{};
|
||||
/// The description.
|
||||
wcstring desc{};
|
||||
#if INCLUDE_RUST_HEADERS
|
||||
/// The representative completion.
|
||||
rust::Box<completion_t> representative = new_completion();
|
||||
#endif
|
||||
/// The per-character highlighting, used when this is a full shell command.
|
||||
std::vector<highlight_spec_t> colors{};
|
||||
/// On-screen width of the completion string.
|
||||
|
@ -144,9 +151,9 @@ class pager_t {
|
|||
void completion_print(size_t cols, const size_t *width_by_column, size_t row_start,
|
||||
size_t row_stop, const wcstring &prefix, const comp_info_list_t &lst,
|
||||
page_rendering_t *rendering) const;
|
||||
line_t completion_print_item(const wcstring &prefix, const comp_t *c, size_t row, size_t column,
|
||||
size_t width, bool secondary, bool selected,
|
||||
page_rendering_t *rendering) const;
|
||||
rust::Box<Line> completion_print_item(const wcstring &prefix, const comp_t *c, size_t row,
|
||||
size_t column, size_t width, bool secondary,
|
||||
bool selected, page_rendering_t *rendering) const;
|
||||
|
||||
public:
|
||||
// The text of the search field.
|
||||
|
@ -162,7 +169,7 @@ class pager_t {
|
|||
void set_prefix(const wcstring &pref, bool highlight = true);
|
||||
|
||||
// Sets the terminal size.
|
||||
void set_term_size(termsize_t ts);
|
||||
void set_term_size(const termsize_t &ts);
|
||||
|
||||
// Changes the selected completion in the given direction according to the layout of the given
|
||||
// rendering. Returns true if the selection changed.
|
||||
|
@ -218,4 +225,7 @@ class pager_t {
|
|||
~pager_t();
|
||||
};
|
||||
|
||||
void pager_set_term_size_ffi(pager_t &pager, const void *ts);
|
||||
void pager_update_rendering_ffi(pager_t &pager, page_rendering_t &rendering);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -740,7 +740,7 @@ class reader_data_t : public std::enable_shared_from_this<reader_data_t> {
|
|||
std::chrono::time_point<std::chrono::steady_clock> last_flash;
|
||||
|
||||
/// The representation of the current screen contents.
|
||||
screen_t screen;
|
||||
rust::Box<Screen> screen;
|
||||
|
||||
/// The source of input events.
|
||||
inputter_t inputter;
|
||||
|
@ -859,6 +859,7 @@ class reader_data_t : public std::enable_shared_from_this<reader_data_t> {
|
|||
reader_data_t(rust::Box<ParserRef> parser, HistorySharedPtr &hist, reader_config_t &&conf)
|
||||
: conf(std::move(conf)),
|
||||
parser_ref(std::move(parser)),
|
||||
screen(new_screen()),
|
||||
inputter(parser_ref->deref(), conf.in),
|
||||
history(hist.clone()) {}
|
||||
|
||||
|
@ -1239,10 +1240,12 @@ void reader_data_t::paint_layout(const wchar_t *reason) {
|
|||
std::vector<int> indents = parse_util_compute_indents(cmd_line->text());
|
||||
indents.resize(full_line.size(), 0);
|
||||
|
||||
auto ffi_colors = new_highlight_spec_list();
|
||||
for (auto color : colors) ffi_colors->push(color);
|
||||
// Prepend the mode prompt to the left prompt.
|
||||
screen.write(mode_prompt_buff + left_prompt_buff, right_prompt_buff, full_line,
|
||||
cmd_line->size(), colors, indents, data.position, parser().vars_boxed(), pager,
|
||||
current_page_rendering, data.focused_on_pager);
|
||||
screen->write(mode_prompt_buff + left_prompt_buff, right_prompt_buff, full_line,
|
||||
cmd_line->size(), *ffi_colors, indents, data.position, parser().vars_boxed(),
|
||||
pager, current_page_rendering, data.focused_on_pager);
|
||||
}
|
||||
|
||||
/// Internal helper function for handling killing parts of text.
|
||||
|
@ -3052,7 +3055,7 @@ void reader_pop() {
|
|||
reader_interactive_destroy();
|
||||
*commandline_state_snapshot() = commandline_state_t{};
|
||||
} else {
|
||||
new_reader->screen.reset_abandoning_line(termsize_last().width);
|
||||
new_reader->screen->reset_abandoning_line(termsize_last().width);
|
||||
new_reader->update_commandline_state();
|
||||
}
|
||||
}
|
||||
|
@ -3434,7 +3437,7 @@ maybe_t<char_event_t> reader_data_t::read_normal_chars(readline_loop_state_t &rl
|
|||
|
||||
if (last_exec_count != exec_count()) {
|
||||
last_exec_count = exec_count();
|
||||
screen.save_status();
|
||||
screen->save_status();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3453,7 +3456,7 @@ maybe_t<char_event_t> reader_data_t::read_normal_chars(readline_loop_state_t &rl
|
|||
|
||||
if (last_exec_count != exec_count()) {
|
||||
last_exec_count = exec_count();
|
||||
screen.save_status();
|
||||
screen->save_status();
|
||||
}
|
||||
|
||||
return event_needing_handling;
|
||||
|
@ -3512,7 +3515,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
|
|||
outp.push_back('\n');
|
||||
|
||||
set_command_line_and_position(&command_line, L"", 0);
|
||||
screen.reset_abandoning_line(termsize_last().width - command_line.size());
|
||||
screen->reset_abandoning_line(termsize_last().width - command_line.size());
|
||||
|
||||
// Post fish_cancel.
|
||||
event_fire_generic(parser(), L"fish_cancel");
|
||||
|
@ -3551,7 +3554,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
|
|||
exec_mode_prompt();
|
||||
if (!mode_prompt_buff.empty()) {
|
||||
if (this->is_repaint_needed()) {
|
||||
screen.reset_line(true /* redraw prompt */);
|
||||
screen->reset_line(true /* redraw prompt */);
|
||||
this->layout_and_repaint(L"mode");
|
||||
}
|
||||
parser().libdata_pods_mut().is_repaint = false;
|
||||
|
@ -3564,7 +3567,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
|
|||
case rl::repaint: {
|
||||
parser().libdata_pods_mut().is_repaint = true;
|
||||
exec_prompt();
|
||||
screen.reset_line(true /* redraw prompt */);
|
||||
screen->reset_line(true /* redraw prompt */);
|
||||
this->layout_and_repaint(L"readline");
|
||||
force_exec_prompt_and_repaint = false;
|
||||
parser().libdata_pods_mut().is_repaint = false;
|
||||
|
@ -3734,7 +3737,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
|
|||
case rl::execute: {
|
||||
if (!this->handle_execute(rls)) {
|
||||
event_fire_generic(parser(), L"fish_posterror", {command_line.text()});
|
||||
screen.reset_abandoning_line(termsize_last().width);
|
||||
screen->reset_abandoning_line(termsize_last().width);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -3776,7 +3779,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
|
|||
|
||||
// Skip the autosuggestion in the history unless it was truncated.
|
||||
const wcstring &suggest = autosuggestion.text;
|
||||
if (!suggest.empty() && !screen.autosuggestion_is_truncated &&
|
||||
if (!suggest.empty() && !screen->autosuggestion_is_truncated() &&
|
||||
mode != reader_history_search_t::prefix) {
|
||||
history_search.add_skip(suggest);
|
||||
}
|
||||
|
@ -4326,7 +4329,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
|
|||
}
|
||||
case rl::clear_screen_and_repaint: {
|
||||
parser().libdata_pods_mut().is_repaint = true;
|
||||
auto clear = screen_clear();
|
||||
auto clear = *screen_clear();
|
||||
if (!clear.empty()) {
|
||||
// Clear the screen if we can.
|
||||
// This is subtle: We first clear, draw the old prompt,
|
||||
|
@ -4335,11 +4338,11 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
|
|||
// while keeping the prompt up-to-date.
|
||||
outputter_t &outp = stdoutput();
|
||||
outp.writestr(clear.c_str());
|
||||
screen.reset_line(true /* redraw prompt */);
|
||||
screen->reset_line(true /* redraw prompt */);
|
||||
this->layout_and_repaint(L"readline");
|
||||
}
|
||||
exec_prompt();
|
||||
screen.reset_line(true /* redraw prompt */);
|
||||
screen->reset_line(true /* redraw prompt */);
|
||||
this->layout_and_repaint(L"readline");
|
||||
force_exec_prompt_and_repaint = false;
|
||||
parser().libdata_pods_mut().is_repaint = false;
|
||||
|
@ -4529,7 +4532,7 @@ maybe_t<wcstring> reader_data_t::readline(int nchars_or_0) {
|
|||
//
|
||||
// I can't see a good way around this.
|
||||
if (!first_prompt) {
|
||||
screen.reset_abandoning_line(termsize_last().width);
|
||||
screen->reset_abandoning_line(termsize_last().width);
|
||||
}
|
||||
first_prompt = false;
|
||||
|
||||
|
@ -4662,7 +4665,7 @@ maybe_t<wcstring> reader_data_t::readline(int nchars_or_0) {
|
|||
|
||||
// Emit a newline so that the output is on the line after the command.
|
||||
// But do not emit a newline if the cursor has wrapped onto a new line all its own - see #6826.
|
||||
if (!screen.cursor_is_wrapped_to_own_line()) {
|
||||
if (!screen->cursor_is_wrapped_to_own_line()) {
|
||||
ignore_result(write(STDOUT_FILENO, "\n", 1));
|
||||
}
|
||||
|
||||
|
|
1368
src/screen.cpp
1368
src/screen.cpp
File diff suppressed because it is too large
Load diff
351
src/screen.h
351
src/screen.h
|
@ -1,343 +1,24 @@
|
|||
// High level library for handling the terminal screen
|
||||
//
|
||||
// The screen library allows the interactive reader to write its output to screen efficiently by
|
||||
// keeping an internal representation of the current screen contents and trying to find a reasonably
|
||||
// efficient way for transforming that to the desired screen content.
|
||||
//
|
||||
// The current implementation is less smart than ncurses allows and can not for example move blocks
|
||||
// of text around to handle text insertion.
|
||||
#ifndef FISH_SCREEN_H
|
||||
#define FISH_SCREEN_H
|
||||
#include "config.h" // IWYU pragma: keep
|
||||
|
||||
#include <stddef.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common.h"
|
||||
#include "env.h"
|
||||
#include "highlight.h"
|
||||
#include "maybe.h"
|
||||
#include "wcstringutil.h"
|
||||
|
||||
class pager_t;
|
||||
class page_rendering_t;
|
||||
|
||||
/// A class representing a single line of a screen.
|
||||
struct line_t {
|
||||
struct highlighted_char_t {
|
||||
highlight_spec_t highlight;
|
||||
wchar_t character;
|
||||
};
|
||||
|
||||
/// A pair of a character, and the color with which to draw it.
|
||||
std::vector<highlighted_char_t> text{};
|
||||
bool is_soft_wrapped{false};
|
||||
size_t indentation{0};
|
||||
|
||||
line_t() = default;
|
||||
|
||||
/// Clear the line's contents.
|
||||
void clear(void) { text.clear(); }
|
||||
|
||||
/// Append a single character \p txt to the line with color \p c.
|
||||
void append(wchar_t c, highlight_spec_t color) { text.push_back({color, c}); }
|
||||
|
||||
/// Append a nul-terminated string \p txt to the line, giving each character \p color.
|
||||
void append(const wchar_t *txt, highlight_spec_t color) {
|
||||
for (size_t i = 0; txt[i]; i++) {
|
||||
text.push_back({color, txt[i]});
|
||||
}
|
||||
}
|
||||
|
||||
/// \return the number of characters.
|
||||
size_t size() const { return text.size(); }
|
||||
|
||||
/// \return the character at a char index.
|
||||
wchar_t char_at(size_t idx) const { return text.at(idx).character; }
|
||||
|
||||
/// \return the color at a char index.
|
||||
highlight_spec_t color_at(size_t idx) const { return text.at(idx).highlight; }
|
||||
|
||||
/// Append the contents of \p line to this line.
|
||||
void append_line(const line_t &line) {
|
||||
text.insert(text.end(), line.text.begin(), line.text.end());
|
||||
}
|
||||
|
||||
/// \return the width of this line, counting up to no more than \p max characters.
|
||||
/// This follows fish_wcswidth() semantics, except that characters whose width would be -1 are
|
||||
/// treated as 0.
|
||||
int wcswidth_min_0(size_t max = std::numeric_limits<size_t>::max()) const;
|
||||
};
|
||||
|
||||
/// A class representing screen contents.
|
||||
class screen_data_t {
|
||||
std::vector<line_t> line_datas;
|
||||
|
||||
public:
|
||||
/// The width of the screen in this rendering.
|
||||
/// -1 if not set, i.e. we have not rendered before.
|
||||
int screen_width{-1};
|
||||
|
||||
/// Where the cursor is in (x, y) coordinates.
|
||||
struct cursor_t {
|
||||
int x{0};
|
||||
int y{0};
|
||||
cursor_t() = default;
|
||||
cursor_t(int a, int b) : x(a), y(b) {}
|
||||
};
|
||||
cursor_t cursor;
|
||||
|
||||
line_t &add_line(void) {
|
||||
line_datas.resize(line_datas.size() + 1);
|
||||
return line_datas.back();
|
||||
}
|
||||
|
||||
void resize(size_t size) { line_datas.resize(size); }
|
||||
|
||||
line_t &create_line(size_t idx) {
|
||||
if (idx >= line_datas.size()) {
|
||||
line_datas.resize(idx + 1);
|
||||
}
|
||||
return line_datas.at(idx);
|
||||
}
|
||||
|
||||
line_t &insert_line_at_index(size_t idx) {
|
||||
assert(idx <= line_datas.size());
|
||||
return *line_datas.insert(line_datas.begin() + idx, line_t());
|
||||
}
|
||||
|
||||
line_t &line(size_t idx) { return line_datas.at(idx); }
|
||||
|
||||
const line_t &line(size_t idx) const { return line_datas.at(idx); }
|
||||
|
||||
size_t line_count() const { return line_datas.size(); }
|
||||
|
||||
void append_lines(const screen_data_t &d) {
|
||||
this->line_datas.insert(this->line_datas.end(), d.line_datas.begin(), d.line_datas.end());
|
||||
}
|
||||
|
||||
bool empty() const { return line_datas.empty(); }
|
||||
};
|
||||
|
||||
struct outputter_t;
|
||||
|
||||
/// The class representing the current and desired screen contents.
|
||||
class screen_t {
|
||||
public:
|
||||
screen_t();
|
||||
|
||||
/// This is the main function for the screen output library. It is used to define the desired
|
||||
/// contents of the screen. The screen command will use its knowledge of the current contents of
|
||||
/// the screen in order to render the desired output using as few terminal commands as possible.
|
||||
///
|
||||
/// \param left_prompt the prompt to prepend to the command line
|
||||
/// \param right_prompt the right prompt, or NULL if none
|
||||
/// \param commandline the command line
|
||||
/// \param explicit_len the number of characters of the "explicit" (non-autosuggestion) portion
|
||||
/// of the command line \param colors the colors to use for the commanad line \param indent the
|
||||
/// indent to use for the command line \param cursor_pos where the cursor is \param pager the
|
||||
/// pager to render below the command line \param page_rendering to cache the current pager view
|
||||
/// \param cursor_is_within_pager whether the position is within the pager line (first line)
|
||||
void write(const wcstring &left_prompt, const wcstring &right_prompt,
|
||||
const wcstring &commandline, size_t explicit_len,
|
||||
const std::vector<highlight_spec_t> &colors, const std::vector<int> &indent,
|
||||
size_t cursor_pos,
|
||||
// todo!("this should be environment_t")
|
||||
const env_stack_t &vars, pager_t &pager, page_rendering_t &page_rendering,
|
||||
bool cursor_is_within_pager);
|
||||
|
||||
/// Resets the screen buffer's internal knowledge about the contents of the screen,
|
||||
/// optionally repainting the prompt as well.
|
||||
/// This function assumes that the current line is still valid.
|
||||
void reset_line(bool repaint_prompt = false);
|
||||
|
||||
/// Resets the screen buffer's internal knowledge about the contents of the screen,
|
||||
/// abandoning the current line and going to the next line.
|
||||
/// If clear_to_eos is set,
|
||||
/// The screen width must be provided for the PROMPT_SP hack.
|
||||
void reset_abandoning_line(int screen_width);
|
||||
|
||||
/// Stat stdout and stderr and save result as the current timestamp.
|
||||
/// This is used to avoid reacting to changes that we ourselves made to the screen.
|
||||
void save_status();
|
||||
|
||||
/// \return whether we believe the cursor is wrapped onto the last line, and that line is
|
||||
/// otherwise empty. This includes both soft and hard wrapping.
|
||||
bool cursor_is_wrapped_to_own_line() const;
|
||||
|
||||
/// Whether the last-drawn autosuggestion (if any) is truncated, or hidden entirely.
|
||||
bool autosuggestion_is_truncated{false};
|
||||
|
||||
private:
|
||||
/// Appends a character to the end of the line that the output cursor is on. This function
|
||||
/// automatically handles linebreaks and lines longer than the screen width.
|
||||
void desired_append_char(wchar_t b, highlight_spec_t c, int indent, size_t prompt_width,
|
||||
size_t bwidth);
|
||||
|
||||
/// Stat stdout and stderr and compare result to previous result in reader_save_status. Repaint
|
||||
/// if modification time has changed.
|
||||
void check_status();
|
||||
|
||||
/// Write the bytes needed to move screen cursor to the specified position to the specified
|
||||
/// buffer. The actual_cursor field of the specified screen_t will be updated.
|
||||
///
|
||||
/// \param new_x the new x position
|
||||
/// \param new_y the new y position
|
||||
void move(int new_x, int new_y);
|
||||
|
||||
/// Convert a wide character to a multibyte string and append it to the buffer.
|
||||
void write_char(wchar_t c, size_t width);
|
||||
|
||||
/// Send the specified string through tputs and append the output to the screen's outputter.
|
||||
void write_mbs(const char *s);
|
||||
|
||||
/// Convert a wide string to a multibyte string and append it to the buffer.
|
||||
void write_str(const wchar_t *s);
|
||||
void write_str(const wcstring &s);
|
||||
|
||||
/// Update the cursor as if soft wrapping had been performed.
|
||||
bool handle_soft_wrap(int x, int y);
|
||||
|
||||
/// Receiver for our output.
|
||||
outputter_t &outp_;
|
||||
|
||||
/// The internal representation of the desired screen contents.
|
||||
screen_data_t desired{};
|
||||
/// The internal representation of the actual screen contents.
|
||||
screen_data_t actual{};
|
||||
/// A string containing the prompt which was last printed to the screen.
|
||||
wcstring actual_left_prompt{};
|
||||
/// Last right prompt width.
|
||||
size_t last_right_prompt_width{0};
|
||||
/// If we support soft wrapping, we can output to this location without any cursor motion.
|
||||
maybe_t<screen_data_t::cursor_t> soft_wrap_location{};
|
||||
/// This flag is set to true when there is reason to suspect that the parts of the screen lines
|
||||
/// where the actual content is not filled in may be non-empty. This means that a clr_eol
|
||||
/// command has to be sent to the terminal at the end of each line, including
|
||||
/// actual_lines_before_reset.
|
||||
bool need_clear_lines{false};
|
||||
/// Whether there may be yet more content after the lines, and we issue a clr_eos if possible.
|
||||
bool need_clear_screen{false};
|
||||
/// If we need to clear, this is how many lines the actual screen had, before we reset it. This
|
||||
/// is used when resizing the window larger: if the cursor jumps to the line above, we need to
|
||||
/// remember to clear the subsequent lines.
|
||||
size_t actual_lines_before_reset{0};
|
||||
/// These status buffers are used to check if any output has occurred other than from fish's
|
||||
/// main loop, in which case we need to redraw.
|
||||
struct stat prev_buff_1 {};
|
||||
struct stat prev_buff_2 {};
|
||||
|
||||
/// \return the outputter for this screen.
|
||||
outputter_t &outp() { return outp_; }
|
||||
|
||||
/// Update the screen to match the desired output.
|
||||
void update(const wcstring &left_prompt, const wcstring &right_prompt,
|
||||
// todo!("this should be environment_t")
|
||||
const env_stack_t &vars);
|
||||
};
|
||||
|
||||
/// Issues an immediate clr_eos.
|
||||
void screen_force_clear_to_end();
|
||||
|
||||
void screen_clear_layout_cache_ffi();
|
||||
|
||||
// Information about the layout of a prompt.
|
||||
struct prompt_layout_t {
|
||||
std::vector<size_t> line_breaks; // line breaks when rendering the prompt
|
||||
size_t max_line_width; // width of the longest line
|
||||
size_t last_line_width; // width of the last line
|
||||
};
|
||||
|
||||
// Maintain a mapping of escape sequences to their widths for fast lookup.
|
||||
class layout_cache_t : noncopyable_t {
|
||||
private:
|
||||
// Cached escape sequences we've already detected in the prompt and similar strings, ordered
|
||||
// lexicographically.
|
||||
std::vector<wcstring> esc_cache_;
|
||||
|
||||
// LRU-list of prompts and their layouts.
|
||||
// Use a list so we can promote to the front on a cache hit.
|
||||
struct prompt_cache_entry_t {
|
||||
wcstring text; // Original prompt string.
|
||||
size_t max_line_width; // Max line width when computing layout (for truncation).
|
||||
wcstring trunc_text; // Resulting truncated prompt string.
|
||||
prompt_layout_t layout; // Resulting layout.
|
||||
};
|
||||
std::list<prompt_cache_entry_t> prompt_cache_;
|
||||
|
||||
public:
|
||||
static constexpr size_t prompt_cache_max_size = 12;
|
||||
|
||||
/// \return the size of the escape code cache.
|
||||
size_t esc_cache_size() const { return esc_cache_.size(); }
|
||||
|
||||
/// Insert the entry \p str in its sorted position, if it is not already present in the cache.
|
||||
void add_escape_code(wcstring str) {
|
||||
auto where = std::upper_bound(esc_cache_.begin(), esc_cache_.end(), str);
|
||||
if (where == esc_cache_.begin() || where[-1] != str) {
|
||||
esc_cache_.emplace(where, std::move(str));
|
||||
}
|
||||
}
|
||||
|
||||
/// \return the length of an escape code, accessing and perhaps populating the cache.
|
||||
size_t escape_code_length(const wchar_t *code);
|
||||
|
||||
/// \return the length of a string that matches a prefix of \p entry.
|
||||
size_t find_escape_code(const wchar_t *entry) const {
|
||||
// Do a binary search and see if the escape code right before our entry is a prefix of our
|
||||
// entry. Note this assumes that escape codes are prefix-free: no escape code is a prefix of
|
||||
// another one. This seems like a safe assumption.
|
||||
auto where = std::upper_bound(esc_cache_.begin(), esc_cache_.end(), entry);
|
||||
// 'where' is now the first element that is greater than entry. Thus where-1 is the last
|
||||
// element that is less than or equal to entry.
|
||||
if (where != esc_cache_.begin()) {
|
||||
const wcstring &candidate = where[-1];
|
||||
if (string_prefixes_string(candidate.c_str(), entry)) return candidate.size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Computes a prompt layout for \p prompt_str, perhaps truncating it to \p max_line_width.
|
||||
/// \return the layout, and optionally the truncated prompt itself, by reference.
|
||||
prompt_layout_t calc_prompt_layout(const wcstring &prompt_str,
|
||||
wcstring *out_trunc_prompt = nullptr,
|
||||
size_t max_line_width = std::numeric_limits<size_t>::max());
|
||||
|
||||
void clear() {
|
||||
esc_cache_.clear();
|
||||
prompt_cache_.clear();
|
||||
}
|
||||
|
||||
// Singleton that is exposed so that the cache can be invalidated when terminal related
|
||||
// variables change by calling `cached_esc_sequences.clear()`.
|
||||
static layout_cache_t shared;
|
||||
|
||||
layout_cache_t() = default;
|
||||
|
||||
private:
|
||||
// Add a cache entry.
|
||||
void add_prompt_layout(prompt_cache_entry_t entry);
|
||||
|
||||
// Finds the layout for a prompt, promoting it to the front. Returns nullptr if not found.
|
||||
// Note this points into our cache; do not modify the cache while the pointer lives.
|
||||
const prompt_cache_entry_t *find_prompt_layout(
|
||||
const wcstring &input, size_t max_line_width = std::numeric_limits<size_t>::max());
|
||||
|
||||
friend void test_layout_cache();
|
||||
};
|
||||
|
||||
maybe_t<size_t> escape_code_length(const wchar_t *code);
|
||||
// Always return a value, by moving checking of sequence start to the caller.
|
||||
long escape_code_length_ffi(const wchar_t *code);
|
||||
|
||||
wcstring screen_clear();
|
||||
void screen_set_midnight_commander_hack();
|
||||
#if INCLUDE_RUST_HEADERS
|
||||
#include "screen.rs.h"
|
||||
#else
|
||||
struct Line;
|
||||
struct ScreenData;
|
||||
struct Screen;
|
||||
struct PromptLayout;
|
||||
struct LayoutCache;
|
||||
#endif
|
||||
|
||||
using line_t = Line;
|
||||
using screen_data_t = ScreenData;
|
||||
using screen_t = Screen;
|
||||
using prompt_layout_t = PromptLayout;
|
||||
using layout_cache_t = LayoutCache;
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in a new issue