use std::io::BufRead;
use std::io::Write;
use crate::libc::strncmp;
use crate::*;
use crate::options_::*;
#[repr(C)]
struct status_prompt_menu {
c: *mut client,
start: u32,
list: Vec<String>,
flag: u8,
}
pub static PROMPT_TYPE_STRINGS: [&str; 4] = ["command", "search", "target", "window-target"];
pub static mut STATUS_PROMPT_HLIST: [*mut *mut u8; PROMPT_NTYPES as usize] =
[null_mut(); PROMPT_NTYPES as usize];
pub static mut STATUS_PROMPT_HSIZE: [u32; PROMPT_NTYPES as usize] = [0; PROMPT_NTYPES as usize];
unsafe fn status_prompt_find_history_file() -> Option<String> {
unsafe {
let history_file = options_get_string_(GLOBAL_OPTIONS, "history-file");
if *history_file == b'\0' {
return None;
}
if *history_file == b'/' {
return Some(
std::ffi::CStr::from_ptr(history_file.cast())
.to_string_lossy()
.into_owned(),
);
}
if *history_file != b'~' || *history_file.add(1) != b'/' {
return None;
}
let home = find_home()?;
let str = format_nul!("{}{}", home.to_str().unwrap(), _s(history_file.add(1)));
Some(
std::ffi::CStr::from_ptr(str.cast())
.to_string_lossy()
.into_owned(),
)
}
}
unsafe fn status_prompt_add_typed_history(mut line: *mut u8) {
unsafe {
let mut type_ = prompt_type::PROMPT_TYPE_INVALID;
let typestr: *mut u8 = strsep(&raw mut line, c!(":"));
if !line.is_null() {
type_ = status_prompt_type(typestr);
}
if type_ == prompt_type::PROMPT_TYPE_INVALID {
if !line.is_null() {
line = line.sub(1);
*(line) = b':';
}
status_prompt_add_history(typestr, prompt_type::PROMPT_TYPE_COMMAND as u32);
} else {
status_prompt_add_history(line, type_ as u32);
}
}
}
pub fn status_prompt_load_history() {
unsafe {
let Some(history_file) = status_prompt_find_history_file() else {
return;
};
log_debug!("loading history from {}", &history_file);
let Ok(file) = std::fs::OpenOptions::new().read(true).open(&history_file) else {
log_debug!("{}: failed to open file", &history_file);
return;
};
let reader = std::io::BufReader::new(file);
for line in reader.lines() {
if let Ok(line) = line {
let mut line_bytes = line.into_bytes();
line_bytes.push(b'\0');
status_prompt_add_typed_history(line_bytes.as_mut_ptr());
} else {
break;
}
}
}
}
pub unsafe fn status_prompt_save_history() {
unsafe {
let Some(history_file) = status_prompt_find_history_file() else {
return;
};
log_debug!("saving history to {}", &history_file);
let Ok(mut file) = std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&history_file)
else {
log_debug!("{}: failed to open file for writing", &history_file);
return;
};
for type_ in 0..PROMPT_NTYPES {
for i in 0..STATUS_PROMPT_HSIZE[type_ as usize] {
_ = writeln!(
file,
"{}:{}",
PROMPT_TYPE_STRINGS[type_ as usize],
_s(*STATUS_PROMPT_HLIST[type_ as usize].add(i as usize))
);
}
}
}
}
unsafe extern "C-unwind" fn status_timer_callback(_fd: i32, _events: i16, c: NonNull<client>) {
unsafe {
let c = c.as_ptr();
let s: *mut session = (*c).session;
evtimer_del(&raw mut (*c).status.timer);
if s.is_null() {
return;
}
if (*c).message_string.is_none() && (*c).prompt_string.is_none() {
(*c).flags |= client_flag::REDRAWSTATUS;
}
let mut tv: timeval = zeroed();
timerclear(&raw mut tv);
tv.tv_sec = options_get_number_((*s).options, "status-interval");
if tv.tv_sec != 0 {
evtimer_add(&raw mut (*c).status.timer, &raw const tv);
}
log_debug!("client {:p}, status interval {}", c, tv.tv_sec);
}
}
pub unsafe fn status_timer_start(c: NonNull<client>) {
unsafe {
let s: *mut session = (*c.as_ptr()).session;
if event_initialized(&raw mut (*c.as_ptr()).status.timer) != 0 {
evtimer_del(&raw mut (*c.as_ptr()).status.timer);
} else {
evtimer_set(
&raw mut (*c.as_ptr()).status.timer,
status_timer_callback,
c,
);
}
if !s.is_null() && options_get_number_((*s).options, "status") != 0 {
status_timer_callback(-1, 0, c);
}
}
}
pub unsafe fn status_timer_start_all() {
unsafe {
for c in tailq_foreach(&raw mut CLIENTS) {
status_timer_start(c);
}
}
}
pub unsafe fn status_update_cache(s: *mut session) {
unsafe {
(*s).statuslines = options_get_number_((*s).options, "status") as u32;
if (*s).statuslines == 0 {
(*s).statusat = -1;
} else if options_get_number_((*s).options, "status-position") == 0 {
(*s).statusat = 0;
} else {
(*s).statusat = 1;
}
}
}
pub unsafe fn status_at_line(c: *mut client) -> i32 {
unsafe {
let s: *mut session = (*c).session;
if (*c)
.flags
.intersects(client_flag::STATUSOFF | client_flag::CONTROL)
{
return -1;
}
if (*s).statusat != 1 {
return (*s).statusat;
}
(*c).tty.sy as i32 - status_line_size(c) as i32
}
}
pub unsafe fn status_line_size(c: *mut client) -> u32 {
unsafe {
let s: *mut session = (*c).session;
if (*c)
.flags
.intersects(client_flag::STATUSOFF | client_flag::CONTROL)
{
return 0;
}
if s.is_null() {
return options_get_number_(GLOBAL_S_OPTIONS, "status") as u32;
}
(*s).statuslines
}
}
pub(crate) unsafe fn status_prompt_line_at(c: *mut client) -> u32 {
unsafe {
let s = (*c).session;
if (*c)
.flags
.intersects(client_flag::STATUSOFF | client_flag::CONTROL)
{
return 1;
}
options_get_number_((*s).options, "message-line") as u32
}
}
pub unsafe fn status_get_range(c: *mut client, x: u32, y: u32) -> *mut style_range {
unsafe {
let sl = &raw mut (*c).status;
if y >= (*sl).entries.len() as u32 {
return null_mut();
}
for sr in tailq_foreach(&raw mut (*sl).entries[y as usize].ranges).map(NonNull::as_ptr) {
if x >= (*sr).start && x < (*sr).end {
return sr;
}
}
null_mut()
}
}
unsafe fn status_free_ranges(srs: *mut style_ranges) {
unsafe {
for sr in tailq_foreach(srs).map(NonNull::as_ptr) {
tailq_remove(srs, sr);
free_(sr);
}
}
}
unsafe fn status_push_screen(c: *mut client) {
unsafe {
let sl = &raw mut (*c).status;
if (*sl).active == &raw mut (*sl).screen {
(*sl).active = Box::leak(Box::new(zeroed())) as *mut screen;
screen_init((*sl).active, (*c).tty.sx, status_line_size(c), 0);
}
(*sl).references += 1;
}
}
unsafe fn status_pop_screen(c: *mut client) {
unsafe {
let sl = &raw mut (*c).status;
(*sl).references -= 1;
if (*sl).references == 0 {
screen_free((*sl).active);
free_((*sl).active);
(*sl).active = &raw mut (*sl).screen;
}
}
}
pub unsafe fn status_init(c: *mut client) {
unsafe {
let sl = &raw mut (*c).status;
for i in 0..(*sl).entries.len() {
tailq_init(&raw mut (*sl).entries[i].ranges);
}
screen_init(&raw mut (*sl).screen, (*c).tty.sx, 1, 0);
(*sl).active = &raw mut (*sl).screen;
}
}
pub unsafe fn status_free(c: *mut client) {
unsafe {
let sl = &raw mut (*c).status;
for i in 0..(*sl).entries.len() {
status_free_ranges(&raw mut (*sl).entries[i].ranges);
(*sl).entries[i].expanded = None;
}
if event_initialized(&raw mut (*sl).timer) != 0 {
evtimer_del(&raw mut (*sl).timer);
}
if (*sl).active != &raw mut (*sl).screen {
screen_free((*sl).active);
free_((*sl).active);
}
screen_free(&raw mut (*sl).screen);
}
}
pub unsafe fn status_redraw(c: *mut client) -> i32 {
unsafe {
let sl = &raw mut (*c).status;
let s = (*c).session;
let mut ctx: screen_write_ctx = zeroed();
let mut gc: grid_cell = zeroed();
let width = (*c).tty.sx;
let mut force = false;
let mut changed = false;
log_debug!("status_redraw enter");
if (*sl).active != &raw mut (*sl).screen {
fatalx("not the active screen");
}
let lines = status_line_size(c);
if (*c).tty.sy == 0 || lines == 0 {
return 1;
}
let mut flags = format_flags::FORMAT_STATUS;
if (*c).flags.intersects(client_flag::STATUSFORCE) {
flags |= format_flags::FORMAT_FORCE;
}
let ft = format_create(c, null_mut(), FORMAT_NONE, flags);
format_defaults(ft, c, None, None, None);
style_apply(&raw mut gc, (*s).options, c!("status-style"), ft);
let fg = options_get_number_((*s).options, "status-fg") as i32;
if !COLOUR_DEFAULT(fg) {
gc.fg = fg;
}
let bg = options_get_number_((*s).options, "status-bg") as i32;
if !COLOUR_DEFAULT(bg) {
gc.bg = bg;
}
if !grid_cells_equal(&raw const gc, &raw const (*sl).style) {
force = true;
memcpy__(&raw mut (*sl).style, &raw mut gc);
}
if screen_size_x(&raw mut (*sl).screen) != width
|| screen_size_y(&raw mut (*sl).screen) != lines
{
screen_resize(&raw mut (*sl).screen, width, lines, 0);
changed = true;
force = true;
}
screen_write_start(&raw mut ctx, &raw mut (*sl).screen);
let o = options_get(&mut *(*s).options, "status-format");
if o.is_null() {
for _ in 0..(width * lines) {
screen_write_putc(&raw mut ctx, &raw mut gc, b' ');
}
} else {
for i in 0..lines {
screen_write_cursormove(&raw mut ctx, 0, i as i32, 0);
let ov = options_array_get(o, i);
if ov.is_null() {
for _ in 0..width {
screen_write_putc(&raw mut ctx, &raw mut gc, b' ');
}
continue;
}
let sle = &raw mut (*sl).entries[i as usize];
let expanded = format_expand_time(ft, (*ov).string);
if !force
&& (*sle).expanded.is_some()
&& libc::strcmp(expanded, (*sle).expanded_ptr()) == 0
{
free_(expanded);
continue;
}
changed = true;
for _ in 0..width {
screen_write_putc(&raw mut ctx, &raw mut gc, b' ');
}
screen_write_cursormove(&raw mut ctx, 0, i as i32, 0);
status_free_ranges(&raw mut (*sle).ranges);
format_draw(
&raw mut ctx,
&raw mut gc,
width,
cstr_to_str(expanded),
&raw mut (*sle).ranges,
0,
);
(*sle).expanded = Some(std::ffi::CString::from_raw(expanded.cast()));
}
}
screen_write_stop(&raw mut ctx);
format_free(ft);
(force || changed) as i32
}
}
macro_rules! status_message_set {
($c:expr, $delay:expr, $ignore_styles:expr, $ignore_keys:expr, $no_freeze:expr, $fmt:literal $(, $args:expr)* $(,)?) => {
crate::status::status_message_set_($c, $delay, $ignore_styles, $ignore_keys, $no_freeze, format_args!($fmt $(, $args)*))
};
}
pub(crate) use status_message_set;
pub unsafe fn status_message_set_(
c: *mut client,
mut delay: i32,
ignore_styles: i32,
ignore_keys: bool,
no_freeze: i32,
args: std::fmt::Arguments,
) {
unsafe {
let mut tv: timeval = zeroed();
let s = args.to_string();
if c.is_null() {
server_add_message!("message: {}", s);
return;
}
status_message_clear(NonNull::new_unchecked(c));
if !crate::extensions::ratatui_ui::enabled() {
status_push_screen(c);
}
let cs = crate::cstring_truncating(s);
server_add_message!(
"{} message: {}",
_s((*c).name),
_s(cs.as_ptr().cast::<u8>())
);
(*c).message_string = Some(cs);
if delay == -1 {
delay = options_get_number_((*(*c).session).options, "display-time") as i32;
}
if delay > 0 {
tv.tv_sec = (delay / 1000) as libc::time_t;
tv.tv_usec = (delay as libc::suseconds_t % 1000) * (1000 as libc::suseconds_t);
if event_initialized(&raw mut (*c).message_timer) != 0 {
evtimer_del(&raw mut (*c).message_timer);
}
evtimer_set(
&raw mut (*c).message_timer,
status_message_callback,
NonNull::new_unchecked(c),
);
evtimer_add(&raw mut (*c).message_timer, &raw mut tv);
}
if delay != 0 {
(*c).message_ignore_keys = ignore_keys as i32;
}
(*c).message_ignore_styles = ignore_styles;
if crate::extensions::ratatui_ui::enabled() {
(*c).tty.flags |= tty_flags::TTY_NOCURSOR;
(*c).flags |= client_flag::REDRAWSTATUS;
crate::extensions::ratatui_ui::set_message_overlay(c);
return;
}
if no_freeze == 0 {
(*c).tty.flags |= tty_flags::TTY_FREEZE;
}
(*c).tty.flags |= tty_flags::TTY_NOCURSOR;
(*c).flags |= client_flag::REDRAWSTATUS;
}
}
pub unsafe fn status_message_clear(c: NonNull<client>) {
unsafe {
let c = c.as_ptr();
if (*c).message_string.is_none() {
return;
}
if crate::extensions::ratatui_ui::enabled() {
crate::extensions::ratatui_ui::clear_message_overlay(c);
}
(*c).message_string = None;
(*c).message_string = None;
if (*c).prompt_string.is_none() {
(*c).tty.flags &= !(tty_flags::TTY_NOCURSOR | tty_flags::TTY_FREEZE);
}
(*c).flags |= CLIENT_ALLREDRAWFLAGS;
if !crate::extensions::ratatui_ui::enabled() {
status_pop_screen(c);
}
}
}
unsafe extern "C-unwind" fn status_message_callback(_fd: i32, _event: i16, data: NonNull<client>) {
unsafe {
status_message_clear(data);
}
}
pub unsafe fn status_message_redraw(c: *mut client) -> i32 {
unsafe {
if crate::extensions::ratatui_ui::enabled() {
return 0;
}
let sl = &raw mut (*c).status;
let mut ctx: screen_write_ctx = zeroed();
let s = (*c).session;
let mut gc: grid_cell = zeroed();
if (*c).tty.sx == 0 || (*c).tty.sy == 0 {
return 0;
}
let mut old_screen = (*(*sl).active).clone();
let mut lines = status_line_size(c);
if lines <= 1 {
lines = 1;
}
screen_init((*sl).active, (*c).tty.sx, lines, 0);
let mut messageline = status_prompt_line_at(c);
if messageline > lines - 1 {
messageline = lines - 1;
}
let mut len = screen_write_strlen!("{}", _s((*c).message_string_ptr()));
if len > (*c).tty.sx as usize {
len = (*c).tty.sx as usize;
}
let ft = format_create_defaults(null_mut(), c, null_mut(), null_mut(), null_mut());
style_apply(&raw mut gc, (*s).options, c!("message-style"), ft);
format_free(ft);
screen_write_start(&raw mut ctx, (*sl).active);
screen_write_fast_copy(
&raw mut ctx,
&raw mut (*sl).screen,
0,
0,
(*c).tty.sx,
lines,
);
screen_write_cursormove(&raw mut ctx, 0, messageline as i32, 0);
for _ in 0..(*c).tty.sx {
screen_write_putc(&raw mut ctx, &raw const gc, b' ');
}
screen_write_cursormove(&raw mut ctx, 0, messageline as i32, 0);
if (*c).message_ignore_styles != 0 {
screen_write_nputs!(
&raw mut ctx,
len as isize,
&raw mut gc,
"{}",
_s((*c).message_string_ptr()),
);
} else {
format_draw(
&raw mut ctx,
&raw const gc,
(*c).tty.sx,
cstr_to_str((*c).message_string_ptr()),
null_mut(),
0,
);
}
screen_write_stop(&raw mut ctx);
if grid_compare((*(*sl).active).grid, old_screen.grid) == 0 {
screen_free(&raw mut old_screen);
return 0;
}
screen_free(&raw mut old_screen);
1
}
}
pub unsafe fn status_prompt_set<T>(
c: *mut client,
fs: *mut cmd_find_state,
msg: *const u8,
mut input: *const u8,
inputcb: unsafe fn(*mut client, NonNull<T>, *const u8, i32) -> i32,
freecb: unsafe fn(NonNull<T>),
data: *mut T,
flags: prompt_flags,
prompt_type: prompt_type,
) {
unsafe {
server_client_clear_overlay(c);
let ft = if !fs.is_null() {
format_create_from_state(null_mut(), c, fs)
} else {
format_create_defaults(null_mut(), c, null_mut(), null_mut(), null_mut())
};
if input.is_null() {
input = c!("");
}
let tmp = if flags.intersects(prompt_flags::PROMPT_NOFORMAT) {
xstrdup(input).as_ptr()
} else {
format_expand_time(ft, input)
};
status_message_clear(NonNull::new_unchecked(c));
status_prompt_clear(c);
if !crate::extensions::ratatui_ui::enabled() {
status_push_screen(c);
}
(*c).prompt_string = Some(std::ffi::CString::from_raw(format_expand_time(ft, msg).cast()));
if flags.intersects(prompt_flags::PROMPT_INCREMENTAL) {
(*c).prompt_last = Some(std::ffi::CStr::from_ptr((tmp) as *const std::ffi::c_char).to_owned());
(*c).prompt_buffer = utf8_fromcstr(c!(""));
} else {
(*c).prompt_last = None;
(*c).prompt_buffer = utf8_fromcstr(tmp);
}
(*c).prompt_index = utf8_strlen((*c).prompt_buffer);
(*c).prompt_inputcb = Some(std::mem::transmute::<
unsafe fn(*mut client, NonNull<T>, *const u8, i32) -> i32,
unsafe fn(*mut client, NonNull<c_void>, *const u8, i32) -> i32,
>(inputcb));
(*c).prompt_freecb = Some(std::mem::transmute::<
unsafe fn(NonNull<T>),
unsafe fn(NonNull<c_void>),
>(freecb));
(*c).prompt_data = data.cast();
libc::memset(
(&raw mut (*c).prompt_hindex).cast(),
0,
size_of::<[u32; 4]>(),
);
(*c).prompt_flags = flags;
(*c).prompt_type = prompt_type;
(*c).prompt_mode = prompt_mode::PROMPT_ENTRY;
if !flags.intersects(prompt_flags::PROMPT_INCREMENTAL) {
(*c).tty.flags |= tty_flags::TTY_NOCURSOR;
if !crate::extensions::ratatui_ui::enabled() {
(*c).tty.flags |= tty_flags::TTY_FREEZE;
}
}
(*c).flags |= client_flag::REDRAWSTATUS;
if crate::extensions::ratatui_ui::enabled() {
crate::extensions::ratatui_ui::set_prompt_overlay(c);
}
if flags.intersects(prompt_flags::PROMPT_INCREMENTAL) {
(*c).prompt_inputcb.unwrap()(c, NonNull::new((*c).prompt_data).unwrap(), c!("="), 0);
}
free_(tmp);
format_free(ft);
}
}
pub unsafe fn status_prompt_clear(c: *mut client) {
unsafe {
if (*c).prompt_string.is_none() {
return;
}
if crate::extensions::ratatui_ui::enabled() {
crate::extensions::ratatui_ui::clear_prompt_overlay(c);
}
if let (Some(prompt_freecb), Some(prompt_data)) =
((*c).prompt_freecb, NonNull::new((*c).prompt_data))
{
prompt_freecb(prompt_data);
}
(*c).prompt_last = None;
(*c).prompt_string = None;
free_((*c).prompt_buffer);
(*c).prompt_buffer = null_mut();
free_((*c).prompt_saved);
(*c).prompt_saved = null_mut();
(*c).tty.flags &= !(tty_flags::TTY_NOCURSOR | tty_flags::TTY_FREEZE);
(*c).flags |= CLIENT_ALLREDRAWFLAGS;
if !crate::extensions::ratatui_ui::enabled() {
status_pop_screen(c);
}
}
}
pub unsafe fn status_prompt_update(c: *mut client, msg: *const u8, input: *const u8) {
unsafe {
let ft = format_create(c, null_mut(), FORMAT_NONE, format_flags::empty());
format_defaults(ft, c, None, None, None);
let tmp = format_expand_time(ft, input);
(*c).prompt_string = None;
(*c).prompt_string = Some(std::ffi::CString::from_raw(format_expand_time(ft, msg).cast()));
free_((*c).prompt_buffer);
(*c).prompt_buffer = utf8_fromcstr(tmp);
(*c).prompt_index = utf8_strlen((*c).prompt_buffer);
libc::memset(
(&raw mut (*c).prompt_hindex).cast(),
0,
size_of::<[u32; 4]>(),
);
(*c).flags |= client_flag::REDRAWSTATUS;
free_(tmp);
format_free(ft);
}
}
pub unsafe fn status_prompt_redraw(c: *mut client) -> i32 {
unsafe {
if crate::extensions::ratatui_ui::enabled() {
return 0;
}
let sl = &raw mut (*c).status;
let mut ctx: screen_write_ctx = zeroed();
let s = (*c).session;
let offset: u32;
let mut gc: grid_cell = zeroed();
let mut cursorgc: grid_cell = zeroed();
let mut old_screen: screen;
'finished: {
if (*c).tty.sx == 0 || (*c).tty.sy == 0 {
return 0;
}
old_screen = (*(*sl).active).clone();
let mut lines = status_line_size(c);
if lines <= 1 {
lines = 1;
}
screen_init((*sl).active, (*c).tty.sx, lines, 0);
let mut promptline = status_prompt_line_at(c);
if promptline > lines - 1 {
promptline = lines - 1;
}
let ft = format_create_defaults(null_mut(), c, null_mut(), null_mut(), null_mut());
if (*c).prompt_mode == prompt_mode::PROMPT_COMMAND {
style_apply(&raw mut gc, (*s).options, c!("message-command-style"), ft);
} else {
style_apply(&raw mut gc, (*s).options, c!("message-style"), ft);
}
format_free(ft);
memcpy__(&raw mut cursorgc, &raw const gc);
cursorgc.attr ^= grid_attr::GRID_ATTR_REVERSE;
let mut start = format_width(cstr_to_str((*c).prompt_string_ptr()));
if start > (*c).tty.sx {
start = (*c).tty.sx;
}
screen_write_start(&raw mut ctx, (*sl).active);
screen_write_fast_copy(
&raw mut ctx,
&raw mut (*sl).screen,
0,
0,
(*c).tty.sx,
lines,
);
screen_write_cursormove(&raw mut ctx, 0, promptline as i32, 0);
for _ in 0..(*c).tty.sx {
screen_write_putc(&raw mut ctx, &raw const gc, b' ');
}
screen_write_cursormove(&raw mut ctx, 0, promptline as i32, 0);
format_draw(
&raw mut ctx,
&raw const gc,
start,
cstr_to_str((*c).prompt_string_ptr()),
null_mut(),
0,
);
screen_write_cursormove(&raw mut ctx, start as i32, promptline as i32, 0);
let left = (*c).tty.sx - start;
if left == 0 {
break 'finished;
}
let pcursor = utf8_strwidth((*c).prompt_buffer, (*c).prompt_index as isize);
let mut pwidth = utf8_strwidth((*c).prompt_buffer, -1);
if pcursor >= left {
offset = (pcursor - left) + 1;
pwidth = left;
} else {
offset = 0;
}
if pwidth > left {
pwidth = left;
}
(*c).prompt_cursor =
(start as isize + (*c).prompt_index as isize - offset as isize) as i32;
let mut width = 0;
let mut i = 0;
while (*(*c).prompt_buffer.add(i)).size != 0 {
if width < offset {
width += (*(*c).prompt_buffer.add(i)).width as u32;
i += 1;
continue;
}
if width >= offset + pwidth {
break;
}
width += (*(*c).prompt_buffer.add(i)).width as u32;
if width > offset + pwidth {
break;
}
if i != (*c).prompt_index {
utf8_copy(&raw mut gc.data, (*c).prompt_buffer.add(i));
screen_write_cell(&raw mut ctx, &raw const gc);
} else {
utf8_copy(&raw mut cursorgc.data, (*c).prompt_buffer.add(i));
screen_write_cell(&raw mut ctx, &raw const cursorgc);
}
i += 1;
}
if (*(*sl).active).cx < screen_size_x((*sl).active) && (*c).prompt_index >= i {
screen_write_putc(&raw mut ctx, &raw const cursorgc, b' ');
}
}
screen_write_stop(&raw mut ctx);
if grid_compare((*(*sl).active).grid, old_screen.grid) == 0 {
screen_free(&raw mut old_screen);
return 0;
}
screen_free(&raw mut old_screen);
1
}
}
unsafe fn status_prompt_in_list(ws: *const u8, ud: *const utf8_data) -> i32 {
unsafe {
if (*ud).size != 1 || (*ud).width != 1 {
return 0;
}
!libc::strchr(ws, (*ud).data[0] as i32).is_null() as i32
}
}
unsafe fn status_prompt_space(ud: *const utf8_data) -> i32 {
unsafe {
if (*ud).size != 1 || (*ud).width != 1 {
return 0;
}
((*ud).data[0] == b' ') as i32
}
}
unsafe fn status_prompt_translate_key(
c: *mut client,
key: key_code,
new_key: *mut key_code,
) -> i32 {
unsafe {
if (*c).prompt_mode == prompt_mode::PROMPT_ENTRY {
match key {
code::A_CTRL
| code::C_CTRL
| code::E_CTRL
| code::G_CTRL
| code::H_CTRL
| code::TAB
| code::K_CTRL
| code::N_CTRL
| code::P_CTRL
| code::T_CTRL
| code::W_CTRL
| code::Y_CTRL
| code::LF
| code::CR
| code::LEFT_CTRL
| code::RIGHT_CTRL
| code::KEYC_BSPACE
| code::KEYC_DC
| code::KEYC_DOWN
| code::KEYC_END
| code::KEYC_HOME
| code::KEYC_LEFT
| code::KEYC_RIGHT
| code::KEYC_UP => {
*new_key = key;
return 1;
}
code::ESC => {
(*c).prompt_mode = prompt_mode::PROMPT_COMMAND;
(*c).flags |= client_flag::REDRAWSTATUS;
return 0;
}
_ => (),
}
*new_key = key;
return 2;
}
match key {
code::KEYC_BSPACE => {
*new_key = keyc::KEYC_LEFT as u64;
return 1;
}
code::A_UPPER | code::I_UPPER | code::C_UPPER | code::S | code::A => {
(*c).prompt_mode = prompt_mode::PROMPT_ENTRY;
(*c).flags |= client_flag::REDRAWSTATUS;
}
code::S_UPPER => {
(*c).prompt_mode = prompt_mode::PROMPT_ENTRY;
(*c).flags |= client_flag::REDRAWSTATUS;
*new_key = b'u' as u64 | KEYC_CTRL;
return 1;
}
code::I | code::ESC => {
(*c).prompt_mode = prompt_mode::PROMPT_ENTRY;
(*c).flags |= client_flag::REDRAWSTATUS;
return 0;
}
_ => (),
}
match key {
code::A_UPPER | code::DOLLAR => {
*new_key = keyc::KEYC_END as u64;
return 1;
}
code::I_UPPER | code::ZERO | code::CARET => {
*new_key = keyc::KEYC_HOME as u64;
return 1;
}
code::C_UPPER | code::D_UPPER => {
*new_key = b'k' as u64 | KEYC_CTRL;
return 1;
}
code::KEYC_BSPACE | code::X_UPPER => {
*new_key = keyc::KEYC_BSPACE as u64;
return 1;
}
code::B => {
*new_key = b'b' as u64 | KEYC_META;
return 1;
}
code::B_UPPER => {
*new_key = b'B' as u64 | KEYC_VI;
return 1;
}
code::D => {
*new_key = b'u' as u64 | KEYC_CTRL;
return 1;
}
code::E => {
*new_key = b'e' as u64 | KEYC_VI;
return 1;
}
code::E_UPPER => {
*new_key = b'E' as u64 | KEYC_VI;
return 1;
}
code::W => {
*new_key = b'w' as u64 | KEYC_VI;
return 1;
}
code::W_UPPER => {
*new_key = b'W' as u64 | KEYC_VI;
return 1;
}
code::P => {
*new_key = b'y' as u64 | KEYC_CTRL;
return 1;
}
code::Q => {
*new_key = b'c' as u64 | KEYC_CTRL;
return 1;
}
code::S | code::KEYC_DC | code::X => {
*new_key = keyc::KEYC_DC as u64;
return 1;
}
code::KEYC_DOWN | code::J => {
*new_key = keyc::KEYC_DOWN as u64;
return 1;
}
code::KEYC_LEFT | code::H => {
*new_key = keyc::KEYC_LEFT as u64;
return 1;
}
code::A | code::KEYC_RIGHT | code::L => {
*new_key = keyc::KEYC_RIGHT as u64;
return 1;
}
code::KEYC_UP | code::K => {
*new_key = keyc::KEYC_UP as u64;
return 1;
}
code::H_CTRL | code::C_CTRL | code::CR | code::LF => {
return 1;
}
_ => (),
}
0
}
}
unsafe fn status_prompt_paste(c: *mut client) -> i32 {
unsafe {
let mut bufsize: usize = 0;
let n: usize;
let ud: *mut utf8_data;
let size = utf8_strlen((*c).prompt_buffer);
if !(*c).prompt_saved.is_null() {
ud = (*c).prompt_saved;
n = utf8_strlen((*c).prompt_saved);
} else {
let pb = paste_get_top(null_mut());
if pb.is_null() {
return 0;
}
let bufdata: *const u8 = paste_buffer_data(pb, &raw mut bufsize).cast();
let mut udp = xreallocarray_::<utf8_data>(null_mut(), bufsize + 1).as_ptr();
ud = udp;
let mut i: u32 = 0;
while i as usize != bufsize {
let mut more = utf8_open(udp, *bufdata.add(i as usize));
if more == utf8_state::UTF8_MORE {
while {
i += 1;
i as usize != bufsize && more == utf8_state::UTF8_MORE
} {
more = utf8_append(udp, *bufdata.add(i as usize));
}
if more == utf8_state::UTF8_DONE {
udp = udp.add(1);
continue;
}
i -= (*udp).have as u32;
}
if *bufdata.add(i as usize) <= 31 || *bufdata.add(i as usize) >= 127 {
break;
}
utf8_set(udp, *bufdata.add(i as usize));
udp = udp.add(1);
i += 1;
}
(*udp).size = 0;
n = udp.offset_from_unsigned(ud);
}
if n != 0 {
(*c).prompt_buffer =
xreallocarray_::<utf8_data>((*c).prompt_buffer, size + n + 1).as_ptr();
if (*c).prompt_index == size {
libc::memcpy(
(*c).prompt_buffer.add((*c).prompt_index).cast(),
ud.cast(),
n * size_of::<utf8_data>(),
);
(*c).prompt_index += n;
(*(*c).prompt_buffer.add((*c).prompt_index)).size = 0;
} else {
libc::memmove(
(*c).prompt_buffer.add((*c).prompt_index + n).cast(),
(*c).prompt_buffer.add((*c).prompt_index).cast(),
(size + 1 - (*c).prompt_index) * size_of::<utf8_data>(),
);
libc::memcpy(
(*c).prompt_buffer.add((*c).prompt_index).cast(),
ud.cast(),
n * size_of::<utf8_data>(),
);
(*c).prompt_index += n;
}
}
if ud != (*c).prompt_saved {
free_(ud);
}
1
}
}
pub(crate) unsafe fn status_prompt_replace_complete(c: *mut client, s: Option<&str>) -> i32 {
unsafe {
let mut word: [u8; 64] = [0; 64];
let completion: Option<String>;
let mut used: usize;
let mut last: *mut utf8_data;
let mut ud: *mut utf8_data;
let idx = (*c).prompt_index.saturating_sub(1);
let mut size = utf8_strlen((*c).prompt_buffer);
let mut first = (*c).prompt_buffer.add(idx);
while first.addr() > (*c).prompt_buffer.addr() && status_prompt_space(first) == 0 {
first = first.sub(1);
}
while (*first).size != 0 && status_prompt_space(first) != 0 {
first = first.add(1);
}
last = (*c).prompt_buffer.add(idx);
while (*last).size != 0 && status_prompt_space(last) == 0 {
last = last.add(1);
}
while last > (*c).prompt_buffer && status_prompt_space(last) != 0 {
last = last.sub(1);
}
if (*last).size != 0 {
last = last.add(1);
}
if last < first {
return 0;
}
let s_str = if let Some(s) = s {
s
} else {
used = 0;
ud = first;
while ud < last {
if used + (*ud).size as usize >= word.len() {
break;
}
libc::memcpy(
(&raw mut word as *mut i8).add(used).cast(),
(&raw mut (*ud).data).cast(),
(*ud).size as usize,
);
used += (*ud).size as usize;
ud = ud.add(1);
}
if ud != last {
return 0;
}
word[used] = b'\0';
completion = status_prompt_complete(
c,
(&raw const word).cast(),
first.offset_from_unsigned((*c).prompt_buffer) as u32,
);
if completion.is_none() {
return 0;
}
completion.as_ref().unwrap().as_str()
};
let n: usize = size - last.offset_from_unsigned((*c).prompt_buffer) + 1;
libc::memmove(first.cast(), last.cast(), n * size_of::<utf8_data>());
size -= last.offset_from_unsigned(first);
size += s_str.len();
let off: usize = first.offset_from_unsigned((*c).prompt_buffer);
(*c).prompt_buffer = xreallocarray_::<utf8_data>((*c).prompt_buffer, size + 1).as_ptr();
first = (*c).prompt_buffer.add(off);
libc::memmove(
first.add(s_str.len()).cast(),
first.cast(),
n * size_of::<utf8_data>(),
);
for (idx, &byte) in s_str.as_bytes().iter().enumerate() {
utf8_set(first.add(idx), byte);
}
(*c).prompt_index = first.offset_from_unsigned((*c).prompt_buffer) + s_str.len();
1
}
}
unsafe fn status_prompt_forward_word(c: *mut client, size: usize, vi: i32, separators: *const u8) {
unsafe {
let mut idx = (*c).prompt_index;
if vi == 0 {
while idx != size && status_prompt_space((*c).prompt_buffer.add(idx)) != 0 {
idx += 1;
}
}
if idx == size {
(*c).prompt_index = idx;
return;
}
let word_is_separators =
(status_prompt_in_list(separators, (*c).prompt_buffer.add(idx)) != 0
&& status_prompt_space((*c).prompt_buffer.add(idx)) == 0) as i32;
loop {
idx += 1;
if status_prompt_space((*c).prompt_buffer.add(idx)) != 0 {
if vi != 0 {
while idx != size && status_prompt_space((*c).prompt_buffer.add(idx)) != 0 {
idx += 1;
}
}
break;
}
if !(idx != size
&& word_is_separators
== status_prompt_in_list(separators, (*c).prompt_buffer.add(idx)))
{
break;
}
}
(*c).prompt_index = idx;
}
}
unsafe fn status_prompt_end_word(c: *mut client, size: usize, separators: *const u8) {
unsafe {
let mut idx = (*c).prompt_index;
if idx == size {
return;
}
loop {
idx += 1;
if idx == size {
(*c).prompt_index = idx;
return;
}
if status_prompt_space((*c).prompt_buffer.add(idx)) == 0 {
break;
}
}
let word_is_separators = status_prompt_in_list(separators, (*c).prompt_buffer.add(idx));
loop {
idx += 1;
if idx == size {
break;
}
if !(status_prompt_space((*c).prompt_buffer.add(idx)) == 0
&& word_is_separators
== status_prompt_in_list(separators, (*c).prompt_buffer.add(idx)))
{
break;
}
}
(*c).prompt_index = idx - 1;
}
}
unsafe fn status_prompt_backward_word(c: *mut client, separators: *const u8) {
unsafe {
let mut idx = (*c).prompt_index;
while idx != 0 {
idx -= 1;
if status_prompt_space((*c).prompt_buffer.add(idx)) == 0 {
break;
}
}
let word_is_separators = status_prompt_in_list(separators, (*c).prompt_buffer.add(idx));
while idx != 0 {
idx -= 1;
if status_prompt_space((*c).prompt_buffer.add(idx)) != 0
|| word_is_separators
!= status_prompt_in_list(separators, (*c).prompt_buffer.add(idx))
{
idx += 1;
break;
}
}
(*c).prompt_index = idx;
}
}
pub unsafe fn status_prompt_key(c: *mut client, mut key: key_code) -> i32 {
unsafe {
let oo = (*(*c).session).options;
let mut s;
let cp;
let mut prefix = b'=';
let histstr: *const u8;
let separators: *const u8;
let keystring: *const u8;
let mut idx: usize;
let mut tmp: utf8_data = zeroed();
let word_is_separators: i32;
if (*c).prompt_flags.intersects(prompt_flags::PROMPT_KEY) {
keystring = key_string_lookup_key(key, 0);
(*c).prompt_inputcb.unwrap()(c, NonNull::new((*c).prompt_data).unwrap(), keystring, 1);
status_prompt_clear(c);
return 0;
}
let size: usize = utf8_strlen((*c).prompt_buffer);
'changed: {
'append_key: {
'process_key: {
if (*c).prompt_flags.intersects(prompt_flags::PROMPT_NUMERIC) {
if key >= b'0' as u64 && key <= b'9' as u64 {
break 'append_key;
}
s = utf8_tocstr((*c).prompt_buffer);
(*c).prompt_inputcb.unwrap()(
c,
NonNull::new((*c).prompt_data).unwrap(),
s,
1,
);
status_prompt_clear(c);
free_(s);
return 1;
}
key &= !KEYC_MASK_FLAGS;
let keys = modekey::try_from(options_get_number_(
(*(*c).session).options,
"status-keys",
) as i32);
if keys == Ok(modekey::MODEKEY_VI) {
match status_prompt_translate_key(c, key, &raw mut key) {
1 => break 'process_key,
2 => break 'append_key,
_ => return 0,
}
}
}
match key {
code::KEYC_LEFT | code::B_CTRL => {
if (*c).prompt_index > 0 {
(*c).prompt_index -= 1;
}
}
code::KEYC_RIGHT | code::F_CTRL => {
if (*c).prompt_index < size {
(*c).prompt_index += 1;
}
}
code::KEYC_HOME | code::A_CTRL => {
if (*c).prompt_index != 0 {
(*c).prompt_index = 0;
}
}
code::KEYC_END | code::E_CTRL => {
if (*c).prompt_index != size {
(*c).prompt_index = size;
}
}
code::TAB => {
if status_prompt_replace_complete(c, None) != 0 {
break 'changed;
}
}
code::KEYC_BSPACE | code::H_CTRL => {
if (*c).prompt_index != 0 {
if (*c).prompt_index == size {
(*c).prompt_index -= 1;
(*(*c).prompt_buffer.add((*c).prompt_index)).size = 0;
} else {
libc::memmove(
(*c).prompt_buffer.add((*c).prompt_index - 1).cast(),
(*c).prompt_buffer.add((*c).prompt_index).cast(),
(size + 1 - (*c).prompt_index) * size_of::<utf8_data>(),
);
(*c).prompt_index -= 1;
}
break 'changed;
}
}
code::KEYC_DC | code::D_CTRL => {
if (*c).prompt_index != size {
libc::memmove(
(*c).prompt_buffer.add((*c).prompt_index).cast(),
(*c).prompt_buffer.add((*c).prompt_index + 1).cast(),
(size + 1 - (*c).prompt_index) * size_of::<utf8_data>(),
);
break 'changed;
}
}
code::U_CTRL => {
(*(*c).prompt_buffer).size = 0;
(*c).prompt_index = 0;
break 'changed;
}
code::K_CTRL => {
if (*c).prompt_index < size {
(*(*c).prompt_buffer.add((*c).prompt_index)).size = 0;
break 'changed;
}
}
code::W_CTRL => {
separators = options_get_string_(oo, "word-separators");
idx = (*c).prompt_index;
while idx != 0 {
idx -= 1;
if status_prompt_space((*c).prompt_buffer.add(idx)) == 0 {
break;
}
}
word_is_separators =
status_prompt_in_list(separators, (*c).prompt_buffer.add(idx));
while idx != 0 {
idx -= 1;
if status_prompt_space((*c).prompt_buffer.add(idx)) != 0
|| word_is_separators
!= status_prompt_in_list(
separators,
(*c).prompt_buffer.add(idx),
)
{
idx += 1;
break;
}
}
free_((*c).prompt_saved);
(*c).prompt_saved =
xcalloc_::<utf8_data>(((*c).prompt_index - idx) + 1).as_ptr();
libc::memcpy(
(*c).prompt_saved.cast(),
(*c).prompt_buffer.add(idx).cast(),
((*c).prompt_index - idx) * size_of::<utf8_data>(),
);
libc::memmove(
(*c).prompt_buffer.add(idx).cast(),
(*c).prompt_buffer.add((*c).prompt_index).cast(),
(size + 1 - (*c).prompt_index) * size_of::<utf8_data>(),
);
libc::memset(
(*c).prompt_buffer
.add(size - ((*c).prompt_index - idx))
.cast(),
b'\0' as i32,
((*c).prompt_index - idx) * size_of::<utf8_data>(),
);
(*c).prompt_index = idx;
break 'changed;
}
code::RIGHT_CTRL | code::F_META => {
separators = options_get_string_(oo, "word-separators");
status_prompt_forward_word(c, size, 0, separators);
break 'changed;
}
code::E_UPPER_VI => {
status_prompt_end_word(c, size, c!(""));
break 'changed;
}
code::E_VI => {
separators = options_get_string_(oo, "word-separators");
status_prompt_end_word(c, size, separators);
break 'changed;
}
code::W_UPPER_VI => {
status_prompt_forward_word(c, size, 1, c!(""));
break 'changed;
}
code::W_VI => {
separators = options_get_string_(oo, "word-separators");
status_prompt_forward_word(c, size, 1, separators);
break 'changed;
}
code::B_VI => {
status_prompt_backward_word(c, c!(""));
break 'changed;
}
code::LEFT_CTRL | code::B_META => {
separators = options_get_string_(oo, "word-separators");
status_prompt_backward_word(c, separators);
break 'changed;
}
code::KEYC_UP | code::P_CTRL => {
histstr = status_prompt_up_history(
(&raw mut (*c).prompt_hindex).cast(),
(*c).prompt_type as u32,
);
if !histstr.is_null() {
free_((*c).prompt_buffer);
(*c).prompt_buffer = utf8_fromcstr(histstr);
(*c).prompt_index = utf8_strlen((*c).prompt_buffer);
break 'changed;
}
}
code::KEYC_DOWN | code::N_CTRL => {
histstr = status_prompt_down_history(
(&raw mut (*c).prompt_hindex).cast(),
(*c).prompt_type as u32,
);
if !histstr.is_null() {
free_((*c).prompt_buffer);
(*c).prompt_buffer = utf8_fromcstr(histstr);
(*c).prompt_index = utf8_strlen((*c).prompt_buffer);
break 'changed;
}
}
code::Y_CTRL => {
if status_prompt_paste(c) != 0 {
break 'changed;
}
}
code::T_CTRL => {
idx = (*c).prompt_index;
if idx < size {
idx += 1;
}
if idx >= 2 {
utf8_copy(&raw mut tmp, (*c).prompt_buffer.add(idx - 2));
utf8_copy(
(*c).prompt_buffer.add(idx - 2),
(*c).prompt_buffer.add(idx - 1),
);
utf8_copy((*c).prompt_buffer.add(idx - 1), &raw const tmp);
(*c).prompt_index = idx;
break 'changed;
}
}
code::CR | code::LF => {
s = utf8_tocstr((*c).prompt_buffer);
if *s != b'\0' {
status_prompt_add_history(s, (*c).prompt_type as u32);
}
if (*c).prompt_inputcb.unwrap()(
c,
NonNull::new((*c).prompt_data).unwrap(),
s,
1,
) == 0
{
status_prompt_clear(c);
}
free_(s);
}
code::ESC | code::C_CTRL | code::G_CTRL => {
if (*c).prompt_inputcb.unwrap()(
c,
NonNull::new((*c).prompt_data).unwrap(),
null_mut(),
1,
) == 0
{
status_prompt_clear(c);
}
}
code::R_CTRL => {
if (*c)
.prompt_flags
.intersects(prompt_flags::PROMPT_INCREMENTAL)
{
if (*(*c).prompt_buffer).size == 0 {
prefix = b'=';
free_((*c).prompt_buffer);
(*c).prompt_buffer = utf8_fromcstr((*c).prompt_last_ptr());
(*c).prompt_index = utf8_strlen((*c).prompt_buffer);
} else {
prefix = b'-';
}
break 'changed;
}
}
code::S_CTRL => {
if (*c)
.prompt_flags
.intersects(prompt_flags::PROMPT_INCREMENTAL)
{
if (*(*c).prompt_buffer).size == 0 {
prefix = b'=';
free_((*c).prompt_buffer);
(*c).prompt_buffer = utf8_fromcstr((*c).prompt_last_ptr());
(*c).prompt_index = utf8_strlen((*c).prompt_buffer);
} else {
prefix = b'+';
}
break 'changed;
}
}
_ => break 'append_key,
}
(*c).flags |= client_flag::REDRAWSTATUS;
return 0;
} if key <= 0x7f {
utf8_set(&raw mut tmp, key as u8);
} else if KEYC_IS_UNICODE(key) {
tmp = utf8_to_data(key as u32);
} else {
return 0;
}
(*c).prompt_buffer = xreallocarray_((*c).prompt_buffer, size + 2).as_ptr();
if (*c).prompt_index == size {
utf8_copy((*c).prompt_buffer.add((*c).prompt_index), &raw const tmp);
(*c).prompt_index += 1;
(*(*c).prompt_buffer.add((*c).prompt_index)).size = 0;
} else {
libc::memmove(
(*c).prompt_buffer.add((*c).prompt_index + 1).cast(),
(*c).prompt_buffer.add((*c).prompt_index).cast(),
(size + 1 - (*c).prompt_index) * size_of::<utf8_data>(),
);
utf8_copy((*c).prompt_buffer.add((*c).prompt_index), &raw const tmp);
(*c).prompt_index += 1;
}
if (*c).prompt_flags.intersects(prompt_flags::PROMPT_SINGLE) {
if utf8_strlen((*c).prompt_buffer) != 1 {
status_prompt_clear(c);
} else {
s = utf8_tocstr((*c).prompt_buffer);
if (*c).prompt_inputcb.unwrap()(
c,
NonNull::new((*c).prompt_data).unwrap(),
s,
1,
) == 0
{
status_prompt_clear(c);
}
free_(s);
}
}
} (*c).flags |= client_flag::REDRAWSTATUS;
if (*c)
.prompt_flags
.intersects(prompt_flags::PROMPT_INCREMENTAL)
{
s = utf8_tocstr((*c).prompt_buffer);
cp = format_nul!("{}{}", prefix as char, _s(s));
(*c).prompt_inputcb.unwrap()(c, NonNull::new((*c).prompt_data).unwrap(), cp, 0);
free_(cp);
free_(s);
}
0
}
}
unsafe fn status_prompt_up_history(idx: *mut u32, type_: u32) -> *mut u8 {
unsafe {
if STATUS_PROMPT_HSIZE[type_ as usize] == 0
|| *idx.add(type_ as usize) == STATUS_PROMPT_HSIZE[type_ as usize]
{
return null_mut();
}
*idx.add(type_ as usize) += 1;
*STATUS_PROMPT_HLIST[type_ as usize]
.add((STATUS_PROMPT_HSIZE[type_ as usize] - *idx.add(type_ as usize)) as usize)
}
}
unsafe fn status_prompt_down_history(idx: *mut u32, type_: u32) -> *const u8 {
unsafe {
if STATUS_PROMPT_HSIZE[type_ as usize] == 0 || *idx.add(type_ as usize) == 0 {
return c!("");
}
*idx.add(type_ as usize) -= 1;
if *idx.add(type_ as usize) == 0 {
return c!("");
}
*STATUS_PROMPT_HLIST[type_ as usize]
.add((STATUS_PROMPT_HSIZE[type_ as usize] - *idx.add(type_ as usize)) as usize)
}
}
unsafe fn status_prompt_add_history(line: *const u8, type_: u32) {
unsafe {
let mut new: u32 = 1;
let newsize: u32;
let mut freecount: u32;
let movesize: usize;
let oldsize = STATUS_PROMPT_HSIZE[type_ as usize];
if oldsize > 0
&& libc::strcmp(
*STATUS_PROMPT_HLIST[type_ as usize].add(oldsize as usize - 1),
line,
) == 0
{
new = 0;
}
let hlimit = options_get_number_(GLOBAL_OPTIONS, "prompt-history-limit") as u32;
if hlimit > oldsize {
if new == 0 {
return;
}
newsize = oldsize + new;
} else {
newsize = hlimit;
freecount = oldsize + new - newsize;
if freecount > oldsize {
freecount = oldsize;
}
if freecount == 0 {
return;
}
for i in 0..freecount {
free_(*STATUS_PROMPT_HLIST[type_ as usize].add(i as usize));
}
movesize = (oldsize as isize - freecount as isize) as usize * size_of::<*mut u8>();
if movesize > 0 {
libc::memmove(
STATUS_PROMPT_HLIST[type_ as usize].cast(),
STATUS_PROMPT_HLIST[type_ as usize]
.add(freecount as usize)
.cast(),
movesize,
);
}
}
if newsize == 0 {
free_(STATUS_PROMPT_HLIST[type_ as usize]);
STATUS_PROMPT_HLIST[type_ as usize] = null_mut();
} else if newsize != oldsize {
STATUS_PROMPT_HLIST[type_ as usize] =
xreallocarray_(STATUS_PROMPT_HLIST[type_ as usize], newsize as usize).as_ptr();
}
if new == 1 && newsize > 0 {
*STATUS_PROMPT_HLIST[type_ as usize].add(newsize as usize - 1) = xstrdup(line).as_ptr();
}
STATUS_PROMPT_HSIZE[type_ as usize] = newsize;
}
}
fn status_prompt_add_list(list: &mut Vec<String>, s: &str) {
if !list.iter().any(|item| item.as_str() == s) {
list.push(s.to_string());
}
}
pub(crate) unsafe fn status_prompt_complete_list(s: *const u8, at_start: i32) -> Vec<String> {
unsafe {
let mut list = Vec::new();
let s = cstr_to_str(s);
let layouts: [&str; 7] = [
"even-horizontal",
"even-vertical",
"main-horizontal",
"main-horizontal-mirrored",
"main-vertical",
"main-vertical-mirrored",
"tiled",
];
for cmdent in CMD_TABLE {
if cmdent.name.starts_with(s) {
status_prompt_add_list(&mut list, cmdent.name);
}
if let Some(alias) = cmdent.alias
&& alias.starts_with(s)
{
status_prompt_add_list(&mut list, alias);
}
}
for &name in crate::extensions::EXTENSION_COMMANDS {
if name.starts_with(s) {
status_prompt_add_list(&mut list, name);
}
}
let o = options_get_only(GLOBAL_OPTIONS, "command-alias");
if !o.is_null() {
let mut a = options_array_first(o);
while !a.is_null() {
'next: {
let value = (*options_array_item_value(a)).string;
let cp = libc::strchr(value, b'=' as i32);
if cp.is_null() {
break 'next;
}
let valuelen = cp.offset_from_unsigned(value);
if s.len() > valuelen || !cstr_to_str(value).starts_with(s) {
break 'next;
}
let tmp = format!("{:.*}", valuelen, _s(value));
status_prompt_add_list(&mut list, &tmp);
} a = options_array_next(a);
}
}
if at_start != 0 {
return list;
}
for oe in &OPTIONS_TABLE {
if oe.name.starts_with(s) {
status_prompt_add_list(&mut list, oe.name);
}
}
for layout in layouts {
if layout.starts_with(s) {
status_prompt_add_list(&mut list, layout);
}
}
list
}
}
fn status_prompt_complete_prefix(list: &[String]) -> String {
if list.is_empty() {
return String::new();
}
let first = &list[0];
let mut prefix_len = first.len();
for item in &list[1..] {
prefix_len = prefix_len
.min(item.len())
.min(
first
.bytes()
.zip(item.bytes())
.take_while(|(a, b)| a == b)
.count()
);
}
first[..prefix_len].to_string()
}
unsafe fn status_prompt_menu_callback(
_menu: *mut menu,
mut idx: u32,
key: key_code,
data: *mut c_void,
) {
unsafe {
let spm: *mut status_prompt_menu = data.cast();
let c = (*spm).c;
if key != KEYC_NONE {
idx += (*spm).start;
let selected = &(&(*spm).list)[idx as usize];
let completion = if (*spm).flag == b'\0' {
selected.clone()
} else {
format!("-{}{}", (*spm).flag as char, selected)
};
if (*c).prompt_type == prompt_type::PROMPT_TYPE_WINDOW_TARGET {
free_((*c).prompt_buffer);
let s = format_nul!("{}", completion);
(*c).prompt_buffer = utf8_fromcstr(s);
(*c).prompt_index = utf8_strlen((*c).prompt_buffer);
free_(s);
(*c).flags |= client_flag::REDRAWSTATUS;
} else if status_prompt_replace_complete(c, Some(&completion)) != 0 {
(*c).flags |= client_flag::REDRAWSTATUS;
}
}
(*spm).list = Vec::new();
free_(spm);
}
}
pub(crate) unsafe fn status_prompt_complete_list_menu(
c: *mut client,
list: Vec<String>,
mut offset: u32,
flag: u8,
) -> i32 {
unsafe {
let mut item: menu_item = zeroed();
let lines = status_line_size(c);
let size = list.len() as u32;
if size <= 1 {
return 0;
}
if (*c).tty.sy - lines < 3 {
return 0;
}
let spm: *mut status_prompt_menu = Box::leak(Box::new(status_prompt_menu {
c,
start: 0,
list,
flag,
})) as *mut status_prompt_menu;
let mut height = (*c).tty.sy - lines - 2;
if height > 10 {
height = 10;
}
if height > size {
height = size;
}
(*spm).start = size - height;
let menu = Box::leak(menu_create(""));
for i in (*spm).start..size {
item.name = Cow::Owned((&(*spm).list)[i as usize].to_string());
item.key = b'0' as u64 + (i as i64 - (*spm).start as i64) as u64;
item.command = SyncCharPtr::null();
menu_add_item(menu, Some(&item), null_mut(), c, null_mut());
drop(item.name);
}
let py = if options_get_number_((*(*c).session).options, "status-position") == 0 {
lines
} else {
(*c).tty.sy - 3 - height
};
offset += utf8_cstrwidth((*c).prompt_string_ptr());
if offset > 2 {
offset -= 2;
} else {
offset = 0;
}
if menu_display(
menu,
MENU_NOMOUSE | MENU_TAB,
0,
null_mut(),
offset,
py,
c,
box_lines::BOX_LINES_DEFAULT,
null_mut(),
null_mut(),
null_mut(),
null_mut(),
Some(status_prompt_menu_callback),
spm.cast(),
) != 0
{
menu_free(menu);
(*spm).list = Vec::new();
free_(spm);
return 0;
}
1
}
}
unsafe fn status_prompt_complete_window_menu(
c: *mut client,
s: *mut session,
word: *const u8,
mut offset: u32,
flag: u8,
) -> Option<String> {
unsafe {
let mut item: menu_item = zeroed();
let mut list = Vec::new();
let lines = status_line_size(c);
if (*c).tty.sy - lines < 3 {
return None;
}
let mut height = (*c).tty.sy - lines - 2;
if height > 10 {
height = 10;
}
let spm = Box::leak(Box::new(status_prompt_menu {
c,
start: 0,
list: Vec::new(),
flag,
})) as *mut status_prompt_menu;
let menu = Box::leak(menu_create(""));
for wl in rb_foreach(&raw mut (*s).windows).map(NonNull::as_ptr) {
let mut tmp;
if !word.is_null() && *word != b'\0' {
tmp = format!("{}", (*wl).idx);
if !tmp.starts_with(cstr_to_str(word)) {
continue;
}
}
if (*c).prompt_type == prompt_type::PROMPT_TYPE_WINDOW_TARGET {
tmp = format!("{} ({})", (*wl).idx, _s((*(*wl).window).name_ptr()),);
list.push(format!("{}", (*wl).idx));
} else {
tmp = format!("{}:{} ({})", (*s).name, (*wl).idx, _s((*(*wl).window).name_ptr()),);
list.push(format!("{}:{}", (*s).name, (*wl).idx));
}
item.name = Cow::Owned(tmp);
item.key = (b'0' as u64) + list.len() as u64 - 1;
item.command = SyncCharPtr::null();
menu_add_item(menu, Some(&item), null_mut(), c, null_mut());
drop(item.name);
if list.len() == height as usize {
break;
}
}
if list.is_empty() {
menu_free(menu);
free_(spm);
return None;
}
if list.len() == 1 {
menu_free(menu);
let result = if flag != b'\0' {
format!("-{}{}", flag as char, list[0])
} else {
list[0].clone()
};
free_(spm);
return Some(result);
}
if height as usize > list.len() {
height = list.len() as u32;
}
(*spm).list = list;
let py = if options_get_number_((*(*c).session).options, "status-position") == 0 {
lines
} else {
(*c).tty.sy - 3 - height
};
offset += utf8_cstrwidth((*c).prompt_string_ptr());
if offset > 2 {
offset -= 2;
} else {
offset = 0;
}
if menu_display(
menu,
MENU_NOMOUSE | MENU_TAB,
0,
null_mut(),
offset,
py,
c,
box_lines::BOX_LINES_DEFAULT,
null_mut(),
null_mut(),
null_mut(),
null_mut(),
Some(status_prompt_menu_callback),
spm.cast(),
) != 0
{
menu_free(menu);
(*spm).list = Vec::new();
free_(spm);
return None;
}
None
}
}
unsafe fn status_prompt_complete_session(
list: &mut Vec<String>,
s: *const u8,
flag: u8,
) -> Option<String> {
unsafe {
let mut n: [u8; 11] = [0; 11];
for loop_ in rb_foreach(&raw mut SESSIONS).map(NonNull::as_ptr) {
if *s == b'\0'
|| strncmp(
CString::new((*loop_).name.to_string())
.expect("TODO remove this allocation")
.as_ptr()
.cast(),
s,
strlen(s),
) == 0
{
list.push(format!("{}:", (*loop_).name));
} else if *s == b'$' {
_ = xsnprintf_!((&raw mut n).cast(), n.len(), "{}", (*loop_).id);
if *s.add(1) == b'\0' || strncmp((&raw mut n).cast(), s.add(1), strlen(s) - 1) == 0
{
list.push(format!("${}:", _s((&raw const n).cast::<u8>())));
}
}
}
let prefix = status_prompt_complete_prefix(list);
if prefix.is_empty() {
None
} else if flag != b'\0' {
Some(format!("-{}{}", flag as char, prefix))
} else {
Some(prefix)
}
}
}
unsafe fn status_prompt_complete(c: *mut client, word: *const u8, mut offset: u32) -> Option<String> {
unsafe {
let session: *mut session;
let s: *const u8;
let colon: *mut u8;
let mut flag: u8 = b'\0';
let mut list: Vec<String> = Vec::new();
let copy: *mut u8;
let mut out: Option<String> = None;
let word_str = cstr_to_str(word);
if *word == b'\0'
&& (*c).prompt_type != prompt_type::PROMPT_TYPE_TARGET
&& (*c).prompt_type != prompt_type::PROMPT_TYPE_WINDOW_TARGET
{
return None;
}
'found: {
if (*c).prompt_type != prompt_type::PROMPT_TYPE_TARGET
&& (*c).prompt_type != prompt_type::PROMPT_TYPE_WINDOW_TARGET
&& strncmp(word, c!("-t"), 2) != 0
&& strncmp(word, c!("-s"), 2) != 0
{
list = status_prompt_complete_list(word, (offset == 0) as i32);
out = if list.is_empty() {
None
} else if list.len() == 1 {
Some(format!("{} ", list[0]))
} else {
Some(status_prompt_complete_prefix(&list))
};
break 'found;
}
if (*c).prompt_type == prompt_type::PROMPT_TYPE_TARGET
|| (*c).prompt_type == prompt_type::PROMPT_TYPE_WINDOW_TARGET
{
s = word;
flag = b'\0';
} else {
s = word.add(2);
flag = *word.add(1);
offset += 2;
}
if (*c).prompt_type == prompt_type::PROMPT_TYPE_WINDOW_TARGET {
out = status_prompt_complete_window_menu(c, (*c).session, s, offset, b'\0');
break 'found;
}
colon = libc::strchr(s, b':' as i32);
if colon.is_null() {
out = status_prompt_complete_session(&mut list, s, flag);
break 'found;
}
if libc::strchr(colon.add(1), b'.' as i32).is_null() {
if *s == b':' {
session = (*c).session;
} else {
copy = xstrdup(s).as_ptr();
*libc::strchr(copy, b':' as i32) = b'\0';
session = session_find(cstr_to_str(copy));
free_(copy);
if session.is_null() {
break 'found;
}
}
out = Some(status_prompt_complete_window_menu(c, session, colon.add(1), offset, flag)?);
}
} if !list.is_empty() {
list.sort_unstable();
for (i, item) in list.iter().enumerate() {
log_debug!("complete {i}: {}", item);
}
}
if out.as_ref().is_some_and(|result| result == word_str) {
out = None;
}
if out.is_some() {
} else {
status_prompt_complete_list_menu(c, list, offset, flag);
}
out
}
}
pub unsafe fn status_prompt_type(type_: *const u8) -> prompt_type {
unsafe {
for i in 0..PROMPT_NTYPES {
if libc::streq_(type_, status_prompt_type_string(i)) {
return prompt_type::try_from(i).unwrap();
}
}
prompt_type::PROMPT_TYPE_INVALID
}
}
pub fn status_prompt_type_string(type_: u32) -> &'static str {
if type_ >= PROMPT_NTYPES {
return "invalid";
}
PROMPT_TYPE_STRINGS[type_ as usize]
}
mod code {
use super::*;
pub const A: u64 = 'a' as u64;
pub const B: u64 = 'b' as u64;
pub const D: u64 = 'd' as u64;
pub const E: u64 = 'e' as u64;
pub const H: u64 = 'h' as u64;
pub const I: u64 = 'i' as u64;
pub const J: u64 = 'j' as u64;
pub const K: u64 = 'k' as u64;
pub const L: u64 = 'l' as u64;
pub const P: u64 = 'p' as u64;
pub const Q: u64 = 'q' as u64;
pub const S: u64 = 's' as u64;
pub const W: u64 = 'w' as u64;
pub const X: u64 = 'x' as u64;
pub const DOLLAR: u64 = '$' as u64;
pub const ZERO: u64 = '0' as u64;
pub const CARET: u64 = '^' as u64;
pub const A_UPPER: u64 = 'A' as u64;
pub const B_UPPER: u64 = 'B' as u64;
pub const C_UPPER: u64 = 'C' as u64;
pub const D_UPPER: u64 = 'D' as u64;
pub const E_UPPER: u64 = 'E' as u64;
pub const I_UPPER: u64 = 'I' as u64;
pub const S_UPPER: u64 = 'S' as u64;
pub const W_UPPER: u64 = 'W' as u64;
pub const X_UPPER: u64 = 'X' as u64;
pub const TAB: u64 = b'\x09' as u64;
pub const KEYC_HOME: u64 = keyc::KEYC_HOME as u64;
pub const KEYC_END: u64 = keyc::KEYC_END as u64;
pub const KEYC_UP: u64 = keyc::KEYC_UP as u64;
pub const KEYC_DOWN: u64 = keyc::KEYC_DOWN as u64;
pub const KEYC_LEFT: u64 = keyc::KEYC_LEFT as u64;
pub const KEYC_RIGHT: u64 = keyc::KEYC_RIGHT as u64;
pub const KEYC_BSPACE: u64 = keyc::KEYC_BSPACE as u64;
pub const KEYC_DC: u64 = keyc::KEYC_DC as u64;
pub const A_CTRL: u64 = 'a' as u64 | KEYC_CTRL;
pub const B_CTRL: u64 = 'b' as u64 | KEYC_CTRL;
pub const C_CTRL: u64 = 'c' as u64 | KEYC_CTRL;
pub const D_CTRL: u64 = 'd' as u64 | KEYC_CTRL;
pub const E_CTRL: u64 = 'e' as u64 | KEYC_CTRL;
pub const F_CTRL: u64 = 'f' as u64 | KEYC_CTRL;
pub const G_CTRL: u64 = 'g' as u64 | KEYC_CTRL;
pub const H_CTRL: u64 = 'h' as u64 | KEYC_CTRL;
pub const K_CTRL: u64 = 'k' as u64 | KEYC_CTRL;
pub const N_CTRL: u64 = 'n' as u64 | KEYC_CTRL;
pub const P_CTRL: u64 = 'p' as u64 | KEYC_CTRL;
pub const R_CTRL: u64 = 'r' as u64 | KEYC_CTRL;
pub const S_CTRL: u64 = 's' as u64 | KEYC_CTRL;
pub const T_CTRL: u64 = 't' as u64 | KEYC_CTRL;
pub const U_CTRL: u64 = 'u' as u64 | KEYC_CTRL;
pub const W_CTRL: u64 = 'w' as u64 | KEYC_CTRL;
pub const Y_CTRL: u64 = 'y' as u64 | KEYC_CTRL;
pub const LEFT_CTRL: u64 = keyc::KEYC_LEFT as u64 | KEYC_CTRL;
pub const RIGHT_CTRL: u64 = keyc::KEYC_RIGHT as u64 | KEYC_CTRL;
pub const B_META: u64 = 'b' as u64 | KEYC_META;
pub const F_META: u64 = 'f' as u64 | KEYC_META;
pub const E_UPPER_VI: u64 = 'E' as u64 | KEYC_VI;
pub const E_VI: u64 = 'e' as u64 | KEYC_VI;
pub const W_UPPER_VI: u64 = 'W' as u64 | KEYC_VI;
pub const W_VI: u64 = 'w' as u64 | KEYC_VI;
pub const B_VI: u64 = 'b' as u64 | KEYC_VI;
pub const CR: u64 = '\r' as u64;
pub const LF: u64 = '\n' as u64;
pub const ESC: u64 = '\x1b' as u64;
}