2016-05-04 03:31:07 +08:00
|
|
|
// 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 the most
|
|
|
|
// efficient way for transforming that to the desired screen content.
|
|
|
|
//
|
2016-04-21 14:00:54 +08:00
|
|
|
// IWYU pragma: no_include <cstddef>
|
2006-10-02 00:02:58 +08:00
|
|
|
#include "config.h"
|
|
|
|
|
2017-02-16 12:09:26 +08:00
|
|
|
#include <stddef.h>
|
2006-10-02 00:02:58 +08:00
|
|
|
#include <stdio.h>
|
2016-05-04 03:31:07 +08:00
|
|
|
#include <stdlib.h>
|
2019-06-11 17:34:14 +08:00
|
|
|
#include <termios.h>
|
2017-02-14 12:37:27 +08:00
|
|
|
#include <time.h>
|
2006-10-02 00:02:58 +08:00
|
|
|
#include <unistd.h>
|
2019-10-14 06:50:48 +08:00
|
|
|
|
2019-05-05 18:09:25 +08:00
|
|
|
#include <cstring>
|
2019-03-13 05:06:01 +08:00
|
|
|
#include <cwchar>
|
2017-02-14 12:37:27 +08:00
|
|
|
|
2018-02-04 16:59:37 +08:00
|
|
|
#if HAVE_CURSES_H
|
|
|
|
#include <curses.h>
|
|
|
|
#elif HAVE_NCURSES_H
|
2006-10-02 00:02:58 +08:00
|
|
|
#include <ncurses.h>
|
2014-12-07 16:41:15 +08:00
|
|
|
#elif HAVE_NCURSES_CURSES_H
|
|
|
|
#include <ncurses/curses.h>
|
2006-10-02 00:02:58 +08:00
|
|
|
#endif
|
|
|
|
#if HAVE_TERM_H
|
|
|
|
#include <term.h>
|
|
|
|
#elif HAVE_NCURSES_TERM_H
|
|
|
|
#include <ncurses/term.h>
|
|
|
|
#endif
|
2017-02-14 12:37:27 +08:00
|
|
|
|
2015-07-25 23:14:25 +08:00
|
|
|
#include <algorithm>
|
|
|
|
#include <string>
|
2012-03-04 18:45:51 +08:00
|
|
|
#include <vector>
|
2006-10-02 00:02:58 +08:00
|
|
|
|
|
|
|
#include "common.h"
|
2016-12-24 05:08:45 +08:00
|
|
|
#include "env.h"
|
2016-05-04 03:31:07 +08:00
|
|
|
#include "fallback.h" // IWYU pragma: keep
|
2019-08-11 05:32:55 +08:00
|
|
|
#include "flog.h"
|
2016-05-04 03:31:07 +08:00
|
|
|
#include "highlight.h"
|
|
|
|
#include "output.h"
|
2014-01-16 10:21:38 +08:00
|
|
|
#include "pager.h"
|
2016-05-04 03:31:07 +08:00
|
|
|
#include "screen.h"
|
2006-10-02 00:02:58 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
/// The number of characters to indent new blocks.
|
2014-04-28 08:23:19 +08:00
|
|
|
#define INDENT_STEP 4u
|
2007-09-24 16:49:33 +08:00
|
|
|
|
2012-10-03 08:30:07 +08:00
|
|
|
static void invalidate_soft_wrap(screen_t *scr);
|
|
|
|
|
2018-10-07 04:32:08 +08:00
|
|
|
/// RAII class to begin and end buffering around stdoutput().
|
2016-05-04 03:31:07 +08:00
|
|
|
class scoped_buffer_t {
|
2018-10-07 04:32:08 +08:00
|
|
|
screen_t &screen_;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
public:
|
2020-02-21 14:54:30 +08:00
|
|
|
explicit scoped_buffer_t(screen_t &s) : screen_(s) { screen_.outp().beginBuffering(); }
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2018-10-07 04:32:08 +08:00
|
|
|
~scoped_buffer_t() { screen_.outp().endBuffering(); }
|
2012-03-26 16:21:10 +08:00
|
|
|
};
|
|
|
|
|
2017-02-04 11:20:21 +08:00
|
|
|
// Singleton of the cached escape sequences seen in prompts and similar strings.
|
2017-09-02 05:33:59 +08:00
|
|
|
// Note this is deliberately exported so that init_curses can clear it.
|
2018-02-05 07:03:50 +08:00
|
|
|
layout_cache_t cached_layouts;
|
2017-02-04 11:20:21 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
/// Tests if the specified narrow character sequence is present at the specified position of the
|
|
|
|
/// specified wide character string. All of \c seq must match, but str may be longer than seq.
|
|
|
|
static size_t try_sequence(const char *seq, const wchar_t *str) {
|
|
|
|
for (size_t i = 0;; i++) {
|
|
|
|
if (!seq[i]) return i;
|
|
|
|
if (seq[i] != str[i]) return 0;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2006-10-02 00:02:58 +08:00
|
|
|
|
2017-02-04 11:20:21 +08:00
|
|
|
DIE("unexpectedly fell off end of try_sequence()");
|
|
|
|
return 0; // this should never be executed
|
2006-10-02 00:02:58 +08:00
|
|
|
}
|
|
|
|
|
2019-11-25 19:03:25 +08:00
|
|
|
/// Returns the number of columns left until the next tab stop, given the current cursor position.
|
2016-05-05 09:14:04 +08:00
|
|
|
static size_t next_tab_stop(size_t current_line_width) {
|
2016-05-04 03:31:07 +08:00
|
|
|
// Assume tab stops every 8 characters if undefined.
|
2019-11-19 09:08:16 +08:00
|
|
|
size_t tab_width = init_tabs > 0 ? static_cast<size_t>(init_tabs) : 8;
|
2016-05-05 09:14:04 +08:00
|
|
|
return ((current_line_width / tab_width) + 1) * tab_width;
|
2006-11-11 18:54:52 +08:00
|
|
|
}
|
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
/// Like fish_wcwidth, but returns 0 for control characters instead of -1.
|
2019-03-13 13:38:42 +08:00
|
|
|
static int fish_wcwidth_min_0(wchar_t widechar) { return std::max(0, fish_wcwidth(widechar)); }
|
2012-11-25 08:42:25 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
/// Whether we permit soft wrapping. If so, in some cases we don't explicitly move to the second
|
|
|
|
/// physical line on a wrapped logical line; instead we just output it.
|
2018-02-19 10:33:04 +08:00
|
|
|
static bool allow_soft_wrap() {
|
2012-10-01 18:29:18 +08:00
|
|
|
// Should we be looking at eat_newline_glitch as well?
|
2016-05-05 09:14:04 +08:00
|
|
|
return auto_right_margin;
|
|
|
|
}
|
|
|
|
|
2018-09-25 12:17:05 +08:00
|
|
|
/// Does this look like the escape sequence for setting a screen name?
|
2016-05-05 09:14:04 +08:00
|
|
|
static bool is_screen_name_escape_seq(const wchar_t *code, size_t *resulting_length) {
|
2016-10-31 11:21:26 +08:00
|
|
|
if (code[1] != L'k') {
|
|
|
|
return false;
|
2016-05-05 09:14:04 +08:00
|
|
|
}
|
2018-06-18 13:01:32 +08:00
|
|
|
const wchar_t *const screen_name_end_sentinel = L"\x1B\\";
|
2019-03-13 05:06:01 +08:00
|
|
|
const wchar_t *screen_name_end = std::wcsstr(&code[2], screen_name_end_sentinel);
|
2019-11-19 10:34:50 +08:00
|
|
|
if (screen_name_end == nullptr) {
|
2016-10-31 11:21:26 +08:00
|
|
|
// Consider just <esc>k to be the code.
|
|
|
|
*resulting_length = 2;
|
|
|
|
} else {
|
2019-05-05 18:09:25 +08:00
|
|
|
const wchar_t *escape_sequence_end =
|
|
|
|
screen_name_end + std::wcslen(screen_name_end_sentinel);
|
2016-10-31 11:21:26 +08:00
|
|
|
*resulting_length = escape_sequence_end - code;
|
|
|
|
}
|
|
|
|
return true;
|
2016-05-05 09:14:04 +08:00
|
|
|
}
|
|
|
|
|
2019-04-20 00:29:35 +08:00
|
|
|
/// Operating System Command (OSC) escape codes, used by iTerm2 and others:
|
|
|
|
/// ESC followed by ], terminated by either BEL or escape + backslash.
|
|
|
|
/// See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
|
|
|
/// and https://iterm2.com/documentation-escape-codes.html .
|
|
|
|
static bool is_osc_escape_seq(const wchar_t *code, size_t *resulting_length) {
|
2016-05-05 09:14:04 +08:00
|
|
|
bool found = false;
|
|
|
|
if (code[1] == ']') {
|
|
|
|
// Start at 2 to skip over <esc>].
|
|
|
|
size_t cursor = 2;
|
|
|
|
for (; code[cursor] != L'\0'; cursor++) {
|
|
|
|
// Consume a sequence of characters up to <esc>\ or <bel>.
|
2018-06-18 13:01:32 +08:00
|
|
|
if (code[cursor] == '\x07' || (code[cursor] == '\\' && code[cursor - 1] == '\x1B')) {
|
2016-05-05 09:14:04 +08:00
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (found) {
|
|
|
|
*resulting_length = cursor + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return found;
|
|
|
|
}
|
|
|
|
|
2019-06-07 10:42:34 +08:00
|
|
|
/// Generic VT100 three byte sequence: CSI followed by something in the range @ through _.
|
2019-06-07 12:45:40 +08:00
|
|
|
static bool is_three_byte_escape_seq(const wchar_t *code, size_t *resulting_length) {
|
2016-05-05 09:14:04 +08:00
|
|
|
bool found = false;
|
|
|
|
if (code[1] == L'[' && (code[2] >= L'@' && code[2] <= L'_')) {
|
|
|
|
*resulting_length = 3;
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
return found;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Generic VT100 two byte sequence: <esc> followed by something in the range @ through _.
|
|
|
|
static bool is_two_byte_escape_seq(const wchar_t *code, size_t *resulting_length) {
|
|
|
|
bool found = false;
|
|
|
|
if (code[1] >= L'@' && code[1] <= L'_') {
|
|
|
|
*resulting_length = 2;
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
return found;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Generic VT100 CSI-style sequence. <esc>, followed by zero or more ASCII characters NOT in
|
|
|
|
/// the range [@,_], followed by one character in that range.
|
|
|
|
static bool is_csi_style_escape_seq(const wchar_t *code, size_t *resulting_length) {
|
2016-10-31 11:21:26 +08:00
|
|
|
if (code[1] != L'[') {
|
|
|
|
return false;
|
|
|
|
}
|
2016-05-05 09:14:04 +08:00
|
|
|
|
2016-10-31 11:21:26 +08:00
|
|
|
// Start at 2 to skip over <esc>[
|
|
|
|
size_t cursor = 2;
|
|
|
|
for (; code[cursor] != L'\0'; cursor++) {
|
|
|
|
// Consume a sequence of ASCII characters not in the range [@, ~].
|
|
|
|
wchar_t widechar = code[cursor];
|
2016-05-05 09:14:04 +08:00
|
|
|
|
2016-10-31 11:21:26 +08:00
|
|
|
// If we're not in ASCII, just stop.
|
|
|
|
if (widechar > 127) break;
|
|
|
|
|
|
|
|
// If we're the end character, then consume it and then stop.
|
|
|
|
if (widechar >= L'@' && widechar <= L'~') {
|
|
|
|
cursor++;
|
|
|
|
break;
|
2016-05-05 09:14:04 +08:00
|
|
|
}
|
|
|
|
}
|
2016-10-31 11:21:26 +08:00
|
|
|
// cursor now indexes just beyond the end of the sequence (or at the terminating zero).
|
|
|
|
*resulting_length = cursor;
|
|
|
|
return true;
|
2012-10-01 18:29:18 +08:00
|
|
|
}
|
|
|
|
|
2017-02-04 11:20:21 +08:00
|
|
|
/// Detect whether the escape sequence sets foreground/background color. Note that 24-bit color
|
|
|
|
/// sequences are detected by `is_csi_style_escape_seq()` if they use the ANSI X3.64 pattern for
|
|
|
|
/// such sequences. This function only handles those escape sequences for setting color that rely on
|
|
|
|
/// the terminfo definition and which might use a different pattern.
|
2017-02-04 06:41:29 +08:00
|
|
|
static bool is_color_escape_seq(const wchar_t *code, size_t *resulting_length) {
|
|
|
|
if (!cur_term) return false;
|
|
|
|
|
|
|
|
// Detect these terminfo color escapes with parameter value up to max_colors, all of which
|
|
|
|
// don't move the cursor.
|
2018-12-12 18:42:29 +08:00
|
|
|
const char *const esc[] = {
|
2019-05-05 18:09:25 +08:00
|
|
|
set_a_foreground,
|
|
|
|
set_a_background,
|
|
|
|
set_foreground,
|
|
|
|
set_background,
|
2017-02-04 06:41:29 +08:00
|
|
|
};
|
|
|
|
|
2019-11-20 05:46:47 +08:00
|
|
|
for (auto p : esc) {
|
|
|
|
if (!p) continue;
|
2017-02-04 06:41:29 +08:00
|
|
|
|
2017-02-04 11:20:21 +08:00
|
|
|
for (int k = 0; k < max_colors; k++) {
|
2019-11-20 05:46:47 +08:00
|
|
|
size_t esc_seq_len = try_sequence(tparm(const_cast<char *>(p), k), code);
|
2017-02-04 06:41:29 +08:00
|
|
|
if (esc_seq_len) {
|
|
|
|
*resulting_length = esc_seq_len;
|
|
|
|
return true;
|
2013-09-29 17:48:35 +08:00
|
|
|
}
|
|
|
|
}
|
2017-02-04 06:41:29 +08:00
|
|
|
}
|
2013-10-27 06:27:39 +08:00
|
|
|
|
2017-02-04 06:41:29 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-02-04 11:20:21 +08:00
|
|
|
/// Detect whether the escape sequence sets one of the terminal attributes that affects how text is
|
|
|
|
/// displayed other than the color.
|
2017-02-04 06:41:29 +08:00
|
|
|
static bool is_visual_escape_seq(const wchar_t *code, size_t *resulting_length) {
|
|
|
|
if (!cur_term) return false;
|
2018-12-12 18:42:29 +08:00
|
|
|
const char *const esc2[] = {
|
2019-05-05 18:09:25 +08:00
|
|
|
enter_bold_mode, exit_attribute_mode, enter_underline_mode, exit_underline_mode,
|
|
|
|
enter_standout_mode, exit_standout_mode, enter_blink_mode, enter_protected_mode,
|
|
|
|
enter_italics_mode, exit_italics_mode, enter_reverse_mode, enter_shadow_mode,
|
|
|
|
exit_shadow_mode, enter_standout_mode, exit_standout_mode, enter_secure_mode,
|
|
|
|
enter_dim_mode, enter_blink_mode, enter_alt_charset_mode, exit_alt_charset_mode};
|
2017-02-04 06:41:29 +08:00
|
|
|
|
2019-11-20 05:46:47 +08:00
|
|
|
for (auto p : esc2) {
|
|
|
|
if (!p) continue;
|
2017-02-04 06:41:29 +08:00
|
|
|
// Test both padded and unpadded version, just to be safe. Most versions of tparm don't
|
|
|
|
// actually seem to do anything these days.
|
2019-11-20 05:46:47 +08:00
|
|
|
size_t esc_seq_len =
|
|
|
|
std::max(try_sequence(tparm(const_cast<char *>(p)), code), try_sequence(p, code));
|
2017-02-04 06:41:29 +08:00
|
|
|
if (esc_seq_len) {
|
|
|
|
*resulting_length = esc_seq_len;
|
|
|
|
return true;
|
2013-09-29 17:48:35 +08:00
|
|
|
}
|
|
|
|
}
|
2016-06-24 13:44:58 +08:00
|
|
|
|
2017-02-04 06:41:29 +08:00
|
|
|
return false;
|
|
|
|
}
|
2013-10-27 06:27:39 +08:00
|
|
|
|
2017-02-04 06:41:29 +08:00
|
|
|
/// Returns the number of characters in the escape code starting at 'code'. We only handle sequences
|
2019-05-05 18:09:25 +08:00
|
|
|
/// that begin with \x1B. If it doesn't we return zero. We also return zero if we don't recognize
|
|
|
|
/// the escape sequence based on querying terminfo and other heuristics.
|
2017-02-04 06:41:29 +08:00
|
|
|
size_t escape_code_length(const wchar_t *code) {
|
2019-11-19 10:34:50 +08:00
|
|
|
assert(code != nullptr);
|
2018-06-18 13:01:32 +08:00
|
|
|
if (*code != L'\x1B') return 0;
|
2017-02-04 06:41:29 +08:00
|
|
|
|
2018-02-05 07:03:50 +08:00
|
|
|
size_t esc_seq_len = cached_layouts.find_escape_code(code);
|
2017-02-04 11:20:21 +08:00
|
|
|
if (esc_seq_len) return esc_seq_len;
|
|
|
|
|
|
|
|
bool found = is_color_escape_seq(code, &esc_seq_len);
|
|
|
|
if (!found) found = is_visual_escape_seq(code, &esc_seq_len);
|
|
|
|
if (!found) found = is_screen_name_escape_seq(code, &esc_seq_len);
|
2019-04-20 00:29:35 +08:00
|
|
|
if (!found) found = is_osc_escape_seq(code, &esc_seq_len);
|
2019-06-07 12:45:40 +08:00
|
|
|
if (!found) found = is_three_byte_escape_seq(code, &esc_seq_len);
|
2017-02-04 11:20:21 +08:00
|
|
|
if (!found) found = is_csi_style_escape_seq(code, &esc_seq_len);
|
|
|
|
if (!found) found = is_two_byte_escape_seq(code, &esc_seq_len);
|
2018-02-05 07:03:50 +08:00
|
|
|
if (found) cached_layouts.add_escape_code(wcstring(code, esc_seq_len));
|
2017-02-04 11:20:21 +08:00
|
|
|
return esc_seq_len;
|
2013-09-29 17:48:35 +08:00
|
|
|
}
|
|
|
|
|
2018-02-05 07:43:03 +08:00
|
|
|
maybe_t<prompt_layout_t> layout_cache_t::find_prompt_layout(const wcstring &input) {
|
|
|
|
auto start = prompt_cache_.begin();
|
|
|
|
auto end = prompt_cache_.end();
|
|
|
|
for (auto iter = start; iter != end; ++iter) {
|
|
|
|
if (iter->first == input) {
|
|
|
|
// Found it. Move it to the front if not already there.
|
|
|
|
if (iter != start) prompt_cache_.splice(start, prompt_cache_, iter);
|
|
|
|
return iter->second;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return none();
|
|
|
|
}
|
|
|
|
|
|
|
|
void layout_cache_t::add_prompt_layout(wcstring input, prompt_layout_t layout) {
|
|
|
|
assert(!find_prompt_layout(input) && "Should not have a prompt layout for this input");
|
2019-12-22 05:53:48 +08:00
|
|
|
prompt_cache_.emplace_front(std::move(input), layout);
|
2018-02-05 07:43:03 +08:00
|
|
|
if (prompt_cache_.size() > prompt_cache_max_size) {
|
|
|
|
prompt_cache_.pop_back();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
/// Calculate layout information for the given prompt. Does some clever magic to detect common
|
2019-10-22 08:21:40 +08:00
|
|
|
/// escape sequences that may be embedded in a prompt, such as those to set visual attributes.
|
2019-11-25 19:03:25 +08:00
|
|
|
/// escape sequences that may be embedded in a prompt, such as those to set visual attributes.
|
2019-08-01 02:06:39 +08:00
|
|
|
static prompt_layout_t calc_prompt_layout(const wcstring &prompt_str, layout_cache_t &cache) {
|
|
|
|
if (auto cached_layout = cache.find_prompt_layout(prompt_str)) {
|
2018-02-05 08:17:57 +08:00
|
|
|
return *cached_layout;
|
2017-02-04 11:20:21 +08:00
|
|
|
}
|
2012-12-03 18:25:08 +08:00
|
|
|
|
2017-02-04 11:20:21 +08:00
|
|
|
prompt_layout_t prompt_layout = {1, 0, 0};
|
|
|
|
size_t current_line_width = 0;
|
2012-11-25 08:58:30 +08:00
|
|
|
|
2019-08-01 02:06:39 +08:00
|
|
|
const wchar_t *prompt = prompt_str.c_str();
|
|
|
|
for (size_t j = 0; prompt[j]; j++) {
|
2018-06-18 13:01:32 +08:00
|
|
|
if (prompt[j] == L'\x1B') {
|
2017-02-04 11:20:21 +08:00
|
|
|
// This is the start of an escape code. Skip over it if it's at least one char long.
|
|
|
|
size_t len = escape_code_length(&prompt[j]);
|
|
|
|
if (len > 0) j += len - 1;
|
2016-05-04 03:31:07 +08:00
|
|
|
} else if (prompt[j] == L'\t') {
|
2012-11-25 17:26:58 +08:00
|
|
|
current_line_width = next_tab_stop(current_line_width);
|
2016-05-04 03:31:07 +08:00
|
|
|
} else if (prompt[j] == L'\n' || prompt[j] == L'\f') {
|
|
|
|
// PCA: At least one prompt uses \f\r as a newline. It's unclear to me what this is
|
|
|
|
// meant to do, but terminals seem to treat it as a newline so we do the same.
|
2012-11-25 17:26:58 +08:00
|
|
|
current_line_width = 0;
|
|
|
|
prompt_layout.line_count += 1;
|
2016-05-04 03:31:07 +08:00
|
|
|
} else if (prompt[j] == L'\r') {
|
2012-11-25 17:26:58 +08:00
|
|
|
current_line_width = 0;
|
2016-05-04 03:31:07 +08:00
|
|
|
} else {
|
2017-02-04 11:20:21 +08:00
|
|
|
// Ordinary char. Add its width with care to ignore control chars which have width -1.
|
2012-11-25 17:26:58 +08:00
|
|
|
current_line_width += fish_wcwidth_min_0(prompt[j]);
|
2017-02-04 11:20:21 +08:00
|
|
|
if (current_line_width > prompt_layout.max_line_width) {
|
|
|
|
prompt_layout.max_line_width = current_line_width;
|
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
}
|
2012-11-25 17:26:58 +08:00
|
|
|
prompt_layout.last_line_width = current_line_width;
|
2018-02-05 08:17:57 +08:00
|
|
|
cache.add_prompt_layout(prompt, prompt_layout);
|
2012-11-25 17:26:58 +08:00
|
|
|
return prompt_layout;
|
2012-10-16 10:25:56 +08:00
|
|
|
}
|
|
|
|
|
2018-02-05 06:14:37 +08:00
|
|
|
static size_t calc_prompt_lines(const wcstring &prompt) {
|
2016-05-04 03:31:07 +08:00
|
|
|
// Hack for the common case where there's no newline at all. I don't know if a newline can
|
|
|
|
// appear in an escape sequence, so if we detect a newline we have to defer to
|
|
|
|
// calc_prompt_width_and_lines.
|
2012-10-16 10:25:56 +08:00
|
|
|
size_t result = 1;
|
2016-05-04 03:31:07 +08:00
|
|
|
if (prompt.find(L'\n') != wcstring::npos || prompt.find(L'\f') != wcstring::npos) {
|
2018-02-05 08:17:57 +08:00
|
|
|
result = calc_prompt_layout(prompt, cached_layouts).line_count;
|
2012-10-16 10:25:56 +08:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2006-10-05 07:33:12 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
/// Stat stdout and stderr and save result. This should be done before calling a function that may
|
|
|
|
/// cause output.
|
2018-09-17 07:25:49 +08:00
|
|
|
void s_save_status(screen_t *s) {
|
2020-04-29 02:25:32 +08:00
|
|
|
fstat(STDOUT_FILENO, &s->prev_buff_1);
|
|
|
|
fstat(STDERR_FILENO, &s->prev_buff_2);
|
2006-10-05 07:33:12 +08:00
|
|
|
}
|
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
/// Stat stdout and stderr and compare result to previous result in reader_save_status. Repaint if
|
|
|
|
/// modification time has changed.
|
|
|
|
///
|
|
|
|
/// Unfortunately, for some reason this call seems to give a lot of false positives, at least under
|
|
|
|
/// Linux.
|
|
|
|
static void s_check_status(screen_t *s) {
|
2012-11-19 08:30:30 +08:00
|
|
|
fflush(stdout);
|
|
|
|
fflush(stderr);
|
2016-06-18 04:08:25 +08:00
|
|
|
if (!has_working_tty_timestamps) {
|
|
|
|
// We can't reliably determine if the terminal has been written to behind our back so we
|
|
|
|
// just assume that hasn't happened and hope for the best. This is important for multi-line
|
|
|
|
// prompts to work correctly.
|
|
|
|
return;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2020-04-29 02:25:32 +08:00
|
|
|
struct stat post_buff_1 {};
|
|
|
|
struct stat post_buff_2 {};
|
|
|
|
fstat(STDOUT_FILENO, &post_buff_1);
|
|
|
|
fstat(STDERR_FILENO, &post_buff_2);
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2020-04-29 02:25:32 +08:00
|
|
|
bool changed = (s->prev_buff_1.st_mtime != post_buff_1.st_mtime) ||
|
|
|
|
(s->prev_buff_2.st_mtime != post_buff_2.st_mtime);
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
#if defined HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC
|
2020-04-29 02:25:32 +08:00
|
|
|
changed = changed || s->prev_buff_1.st_mtimespec.tv_nsec != post_buff_1.st_mtimespec.tv_nsec ||
|
|
|
|
s->prev_buff_2.st_mtimespec.tv_nsec != post_buff_2.st_mtimespec.tv_nsec;
|
2016-05-04 03:31:07 +08:00
|
|
|
#elif defined HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
|
2020-04-29 02:25:32 +08:00
|
|
|
changed = changed || s->prev_buff_1.st_mtim.tv_nsec != post_buff_1.st_mtim.tv_nsec ||
|
|
|
|
s->prev_buff_2.st_mtim.tv_nsec != post_buff_2.st_mtim.tv_nsec;
|
2016-05-04 03:31:07 +08:00
|
|
|
#endif
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
if (changed) {
|
|
|
|
// Ok, someone has been messing with our screen. We will want to repaint. However, we do not
|
|
|
|
// know where the cursor is. It is our best bet that we are still on the same line, so we
|
|
|
|
// move to the beginning of the line, reset the modelled screen contents, and then set the
|
|
|
|
// modeled cursor y-pos to its earlier value.
|
2012-11-19 08:30:30 +08:00
|
|
|
int prev_line = s->actual.cursor.y;
|
2012-12-02 07:44:09 +08:00
|
|
|
write_loop(STDOUT_FILENO, "\r", 1);
|
2020-04-29 02:00:14 +08:00
|
|
|
s_reset(s, screen_reset_mode_t::current_line_and_prompt);
|
2012-11-19 08:30:30 +08:00
|
|
|
s->actual.cursor.y = prev_line;
|
|
|
|
}
|
2006-10-05 07:33:12 +08:00
|
|
|
}
|
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
/// 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.
|
2019-03-04 09:34:00 +08:00
|
|
|
static void s_desired_append_char(screen_t *s, wchar_t b, highlight_spec_t c, int indent,
|
2019-05-11 16:16:22 +08:00
|
|
|
size_t prompt_width, size_t bwidth) {
|
2012-11-19 08:30:30 +08:00
|
|
|
int line_no = s->desired.cursor.y;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-05 09:14:04 +08:00
|
|
|
if (b == L'\n') {
|
|
|
|
// Current line is definitely hard wrapped.
|
2019-08-11 05:32:55 +08:00
|
|
|
// Create the next line.
|
|
|
|
s->desired.create_line(s->desired.cursor.y + 1);
|
2016-05-05 09:14:04 +08:00
|
|
|
s->desired.line(s->desired.cursor.y).is_soft_wrapped = false;
|
2019-06-07 12:27:13 +08:00
|
|
|
int line_no = ++s->desired.cursor.y;
|
2016-05-05 09:14:04 +08:00
|
|
|
s->desired.cursor.x = 0;
|
2019-06-07 12:27:13 +08:00
|
|
|
size_t indentation = prompt_width + indent * INDENT_STEP;
|
|
|
|
line_t &line = s->desired.line(line_no);
|
|
|
|
line.indentation = indentation;
|
|
|
|
for (size_t i = 0; i < indentation; i++) {
|
2019-05-11 16:16:22 +08:00
|
|
|
s_desired_append_char(s, L' ', highlight_spec_t{}, indent, prompt_width, 1);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-05-05 09:14:04 +08:00
|
|
|
} else if (b == L'\r') {
|
|
|
|
line_t ¤t = s->desired.line(line_no);
|
|
|
|
current.clear();
|
|
|
|
s->desired.cursor.x = 0;
|
|
|
|
} else {
|
|
|
|
int screen_width = common_get_width();
|
2019-05-11 16:16:22 +08:00
|
|
|
int cw = bwidth;
|
2016-05-05 09:14:04 +08:00
|
|
|
|
|
|
|
s->desired.create_line(line_no);
|
|
|
|
|
|
|
|
// Check if we are at the end of the line. If so, continue on the next line.
|
|
|
|
if ((s->desired.cursor.x + cw) > screen_width) {
|
|
|
|
// Current line is soft wrapped (assuming we support it).
|
|
|
|
s->desired.line(s->desired.cursor.y).is_soft_wrapped = true;
|
|
|
|
|
2019-11-19 09:08:16 +08:00
|
|
|
line_no = static_cast<int>(s->desired.line_count());
|
2016-05-05 09:14:04 +08:00
|
|
|
s->desired.add_line();
|
|
|
|
s->desired.cursor.y++;
|
2012-11-19 08:30:30 +08:00
|
|
|
s->desired.cursor.x = 0;
|
2012-11-19 16:31:03 +08:00
|
|
|
}
|
|
|
|
|
2016-05-05 09:14:04 +08:00
|
|
|
line_t &line = s->desired.line(line_no);
|
|
|
|
line.append(b, c);
|
|
|
|
s->desired.cursor.x += cw;
|
2012-11-19 16:31:03 +08:00
|
|
|
|
2016-05-05 09:14:04 +08:00
|
|
|
// Maybe wrap the cursor to the next line, even if the line itself did not wrap. This
|
|
|
|
// avoids wonkiness in the last column.
|
|
|
|
if (s->desired.cursor.x >= screen_width) {
|
|
|
|
line.is_soft_wrapped = true;
|
|
|
|
s->desired.cursor.x = 0;
|
|
|
|
s->desired.cursor.y++;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
}
|
2006-10-02 00:02:58 +08:00
|
|
|
}
|
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
/// 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 s the screen to operate on
|
|
|
|
/// \param new_x the new x position
|
|
|
|
/// \param new_y the new y position
|
2018-10-07 04:32:08 +08:00
|
|
|
static void s_move(screen_t *s, int new_x, int new_y) {
|
2016-05-04 03:31:07 +08:00
|
|
|
if (s->actual.cursor.x == new_x && s->actual.cursor.y == new_y) return;
|
|
|
|
|
2018-10-07 04:32:08 +08:00
|
|
|
const scoped_buffer_t buffering(*s);
|
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// If we are at the end of our window, then either the cursor stuck to the edge or it didn't. We
|
|
|
|
// don't know! We can fix it up though.
|
|
|
|
if (s->actual.cursor.x == common_get_width()) {
|
|
|
|
// Either issue a cr to go back to the beginning of this line, or a nl to go to the
|
|
|
|
// beginning of the next one, depending on what we think is more efficient.
|
|
|
|
if (new_y <= s->actual.cursor.y) {
|
2018-10-07 04:32:08 +08:00
|
|
|
s->outp().push_back('\r');
|
2016-05-04 03:31:07 +08:00
|
|
|
} else {
|
2018-10-07 04:32:08 +08:00
|
|
|
s->outp().push_back('\n');
|
2012-12-06 18:54:25 +08:00
|
|
|
s->actual.cursor.y++;
|
|
|
|
}
|
2016-05-04 03:31:07 +08:00
|
|
|
// Either way we're not in the first column.
|
2012-12-06 18:54:25 +08:00
|
|
|
s->actual.cursor.x = 0;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2012-11-19 08:30:30 +08:00
|
|
|
int i;
|
|
|
|
int x_steps, y_steps;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2018-12-12 18:42:29 +08:00
|
|
|
const char *str;
|
2018-10-07 04:32:08 +08:00
|
|
|
auto &outp = s->outp();
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2012-11-19 08:30:30 +08:00
|
|
|
y_steps = new_y - s->actual.cursor.y;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
if (y_steps < 0) {
|
2012-11-19 08:30:30 +08:00
|
|
|
str = cursor_up;
|
2019-04-20 00:40:30 +08:00
|
|
|
} else if (y_steps > 0) {
|
2012-11-19 08:30:30 +08:00
|
|
|
str = cursor_down;
|
2019-05-28 10:47:13 +08:00
|
|
|
if ((shell_modes.c_oflag & ONLCR) != 0 &&
|
|
|
|
std::strcmp(str, "\n") == 0) { // See GitHub issue #4505.
|
2019-04-19 10:42:18 +08:00
|
|
|
// Most consoles use a simple newline as the cursor down escape.
|
|
|
|
// If ONLCR is enabled (which it normally is) this will of course
|
|
|
|
// also move the cursor to the beginning of the line.
|
2019-04-20 00:40:30 +08:00
|
|
|
// We could do:
|
|
|
|
// if (std::strcmp(cursor_up, "\x1B[A") == 0) str = "\x1B[B";
|
|
|
|
// else ... but that doesn't work for unknown reasons.
|
|
|
|
s->actual.cursor.x = 0;
|
2019-04-19 10:42:18 +08:00
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
for (i = 0; i < abs(y_steps); i++) {
|
2018-10-07 04:32:08 +08:00
|
|
|
writembs(outp, str);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2012-11-19 08:30:30 +08:00
|
|
|
x_steps = new_x - s->actual.cursor.x;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
if (x_steps && new_x == 0) {
|
2018-10-07 04:32:08 +08:00
|
|
|
outp.push_back('\r');
|
2012-11-19 08:30:30 +08:00
|
|
|
x_steps = 0;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2019-11-19 10:34:50 +08:00
|
|
|
const char *multi_str = nullptr;
|
2016-05-04 03:31:07 +08:00
|
|
|
if (x_steps < 0) {
|
2012-11-19 08:30:30 +08:00
|
|
|
str = cursor_left;
|
2014-05-10 05:37:23 +08:00
|
|
|
multi_str = parm_left_cursor;
|
2016-05-04 03:31:07 +08:00
|
|
|
} else {
|
2012-11-19 08:30:30 +08:00
|
|
|
str = cursor_right;
|
2014-05-10 05:37:23 +08:00
|
|
|
multi_str = parm_right_cursor;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-05-04 03:31:07 +08:00
|
|
|
|
2014-05-10 05:37:23 +08:00
|
|
|
// Use the bulk ('multi') output for cursor movement if it is supported and it would be shorter
|
2016-05-04 03:31:07 +08:00
|
|
|
// Note that this is required to avoid some visual glitches in iTerm (issue #1448).
|
2019-11-19 10:34:50 +08:00
|
|
|
bool use_multi = multi_str != nullptr && multi_str[0] != '\0' &&
|
2019-05-05 18:09:25 +08:00
|
|
|
abs(x_steps) * std::strlen(str) > std::strlen(multi_str);
|
2017-02-16 12:09:26 +08:00
|
|
|
if (use_multi && cur_term) {
|
2019-11-19 09:08:16 +08:00
|
|
|
char *multi_param = tparm(const_cast<char *>(multi_str), abs(x_steps));
|
2018-10-07 04:32:08 +08:00
|
|
|
writembs(outp, multi_param);
|
2016-05-04 03:31:07 +08:00
|
|
|
} else {
|
|
|
|
for (i = 0; i < abs(x_steps); i++) {
|
2018-10-07 04:32:08 +08:00
|
|
|
writembs(outp, str);
|
2014-05-10 05:37:23 +08:00
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2012-11-19 08:30:30 +08:00
|
|
|
s->actual.cursor.x = new_x;
|
|
|
|
s->actual.cursor.y = new_y;
|
2006-10-02 00:02:58 +08:00
|
|
|
}
|
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
/// Set the pen color for the terminal.
|
2018-10-07 04:32:08 +08:00
|
|
|
static void s_set_color(screen_t *s, const environment_t &vars, highlight_spec_t c) {
|
2016-10-10 05:38:26 +08:00
|
|
|
UNUSED(s);
|
2019-05-13 09:23:00 +08:00
|
|
|
UNUSED(vars);
|
2019-03-04 09:34:00 +08:00
|
|
|
s->outp().set_color(highlight_get_color(c, false), highlight_get_color(c, true));
|
2006-10-02 00:02:58 +08:00
|
|
|
}
|
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
/// Convert a wide character to a multibyte string and append it to the buffer.
|
2019-05-11 16:16:22 +08:00
|
|
|
static void s_write_char(screen_t *s, wchar_t c, size_t width) {
|
2018-10-07 04:32:08 +08:00
|
|
|
scoped_buffer_t outp(*s);
|
2019-05-11 16:16:22 +08:00
|
|
|
s->actual.cursor.x += width;
|
2018-10-07 04:32:08 +08:00
|
|
|
s->outp().writech(c);
|
2016-05-04 03:31:07 +08:00
|
|
|
if (s->actual.cursor.x == s->actual_width && allow_soft_wrap()) {
|
2020-04-29 02:25:32 +08:00
|
|
|
s->soft_wrap_location = screen_data_t::cursor_t{0, s->actual.cursor.y + 1};
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Note that our cursor position may be a lie: Apple Terminal makes the right cursor stick
|
|
|
|
// to the margin, while Ubuntu makes it "go off the end" (but still doesn't wrap). We rely
|
|
|
|
// on s_move to fix this up.
|
|
|
|
} else {
|
2012-10-03 08:30:07 +08:00
|
|
|
invalidate_soft_wrap(s);
|
2012-10-01 18:29:18 +08:00
|
|
|
}
|
2012-07-16 01:45:18 +08:00
|
|
|
}
|
|
|
|
|
2018-10-07 04:32:08 +08:00
|
|
|
/// Send the specified string through tputs and append the output to the screen's outputter.
|
|
|
|
static void s_write_mbs(screen_t *screen, const char *s) { writembs(screen->outp(), s); }
|
2006-10-02 00:02:58 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
/// Convert a wide string to a multibyte string and append it to the buffer.
|
2018-10-07 04:32:08 +08:00
|
|
|
static void s_write_str(screen_t *screen, const wchar_t *s) { screen->outp().writestr(s); }
|
2006-10-02 00:02:58 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
/// Returns the length of the "shared prefix" of the two lines, which is the run of matching text
|
|
|
|
/// and colors. If the prefix ends on a combining character, do not include the previous character
|
|
|
|
/// in the prefix.
|
|
|
|
static size_t line_shared_prefix(const line_t &a, const line_t &b) {
|
2012-07-16 01:45:18 +08:00
|
|
|
size_t idx, max = std::min(a.size(), b.size());
|
2016-05-04 03:31:07 +08:00
|
|
|
for (idx = 0; idx < max; idx++) {
|
2012-07-16 01:45:18 +08:00
|
|
|
wchar_t ac = a.char_at(idx), bc = b.char_at(idx);
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// We're done if the text or colors are different.
|
2019-05-10 23:21:06 +08:00
|
|
|
if (ac != bc || a.color_at(idx) != b.color_at(idx)) {
|
|
|
|
if (idx > 0) {
|
|
|
|
const line_t *c = nullptr;
|
2019-05-28 10:47:13 +08:00
|
|
|
// Possible combining mark, go back until we hit _two_ printable characters or idx
|
|
|
|
// of 0.
|
2019-05-10 23:21:06 +08:00
|
|
|
if (fish_wcwidth(a.char_at(idx)) < 1) {
|
|
|
|
c = &a;
|
|
|
|
} else if (fish_wcwidth(b.char_at(idx)) < 1) {
|
|
|
|
c = &b;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c) {
|
2019-05-28 10:47:13 +08:00
|
|
|
while (idx > 1 && (fish_wcwidth(c->char_at(idx - 1)) < 1 ||
|
|
|
|
fish_wcwidth(c->char_at(idx)) < 1))
|
|
|
|
idx--;
|
2019-05-10 23:21:06 +08:00
|
|
|
if (idx == 1 && fish_wcwidth(c->char_at(idx)) < 1) idx = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2012-07-16 01:45:18 +08:00
|
|
|
}
|
|
|
|
return idx;
|
|
|
|
}
|
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// We are about to output one or more characters onto the screen at the given x, y. If we are at the
|
|
|
|
// end of previous line, and the previous line is marked as soft wrapping, then tweak the screen so
|
|
|
|
// we believe we are already in the target position. This lets the terminal take care of wrapping,
|
|
|
|
// which means that if you copy and paste the text, it won't have an embedded newline.
|
|
|
|
static bool perform_any_impending_soft_wrap(screen_t *scr, int x, int y) {
|
2020-04-29 02:25:32 +08:00
|
|
|
if (scr->soft_wrap_location && x == scr->soft_wrap_location->x &&
|
|
|
|
y == scr->soft_wrap_location->y) { //!OCLINT
|
2016-05-04 03:31:07 +08:00
|
|
|
// We can soft wrap; but do we want to?
|
|
|
|
if (scr->desired.line(y - 1).is_soft_wrapped && allow_soft_wrap()) {
|
|
|
|
// Yes. Just update the actual cursor; that will cause us to elide emitting the commands
|
|
|
|
// to move here, so we will just output on "one big line" (which the terminal soft
|
|
|
|
// wraps.
|
2020-04-29 02:25:32 +08:00
|
|
|
scr->actual.cursor = scr->soft_wrap_location.value();
|
2012-10-01 18:29:18 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
/// Make sure we don't soft wrap.
|
2020-04-29 02:25:32 +08:00
|
|
|
static void invalidate_soft_wrap(screen_t *scr) { scr->soft_wrap_location = none(); }
|
2016-05-04 03:31:07 +08:00
|
|
|
|
|
|
|
/// Update the screen to match the desired output.
|
2018-02-05 08:17:57 +08:00
|
|
|
static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring &right_prompt) {
|
2018-10-07 04:32:08 +08:00
|
|
|
const environment_t &vars = env_stack_t::principal();
|
|
|
|
const scoped_buffer_t buffering(*scr);
|
2018-02-05 08:17:57 +08:00
|
|
|
const size_t left_prompt_width =
|
|
|
|
calc_prompt_layout(left_prompt, cached_layouts).last_line_width;
|
2017-02-04 11:20:21 +08:00
|
|
|
const size_t right_prompt_width =
|
2018-02-05 08:17:57 +08:00
|
|
|
calc_prompt_layout(right_prompt, cached_layouts).last_line_width;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2012-11-19 08:30:30 +08:00
|
|
|
int screen_width = common_get_width();
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Figure out how many following lines we need to clear (probably 0).
|
2012-10-03 08:30:07 +08:00
|
|
|
size_t actual_lines_before_reset = scr->actual_lines_before_reset;
|
|
|
|
scr->actual_lines_before_reset = 0;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2012-11-25 17:26:58 +08:00
|
|
|
bool need_clear_lines = scr->need_clear_lines;
|
|
|
|
bool need_clear_screen = scr->need_clear_screen;
|
|
|
|
bool has_cleared_screen = false;
|
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
if (scr->actual_width != screen_width) {
|
|
|
|
// Ensure we don't issue a clear screen for the very first output, to avoid issue #402.
|
2020-04-29 02:25:32 +08:00
|
|
|
if (scr->actual_width > 0) {
|
2012-11-25 08:42:25 +08:00
|
|
|
need_clear_screen = true;
|
2018-10-07 04:32:08 +08:00
|
|
|
s_move(scr, 0, 0);
|
2020-04-29 02:00:14 +08:00
|
|
|
s_reset(scr, screen_reset_mode_t::current_line_contents);
|
2012-12-03 18:25:08 +08:00
|
|
|
|
2012-11-25 17:26:58 +08:00
|
|
|
need_clear_lines = need_clear_lines || scr->need_clear_lines;
|
|
|
|
need_clear_screen = need_clear_screen || scr->need_clear_screen;
|
2012-11-25 08:42:25 +08:00
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
scr->actual_width = screen_width;
|
|
|
|
}
|
2012-12-03 18:25:08 +08:00
|
|
|
|
2012-11-25 17:26:58 +08:00
|
|
|
scr->need_clear_lines = false;
|
|
|
|
scr->need_clear_screen = false;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Determine how many lines have stuff on them; we need to clear lines with stuff that we don't
|
|
|
|
// want.
|
2019-03-13 13:38:42 +08:00
|
|
|
const size_t lines_with_stuff = std::max(actual_lines_before_reset, scr->actual.line_count());
|
2019-11-10 08:07:33 +08:00
|
|
|
if (scr->desired.line_count() < lines_with_stuff) need_clear_screen = true;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2018-02-05 08:17:57 +08:00
|
|
|
if (left_prompt != scr->actual_left_prompt) {
|
2018-10-07 04:32:08 +08:00
|
|
|
s_move(scr, 0, 0);
|
|
|
|
s_write_str(scr, left_prompt.c_str());
|
2012-11-05 15:21:37 +08:00
|
|
|
scr->actual_left_prompt = left_prompt;
|
2019-11-19 09:08:16 +08:00
|
|
|
scr->actual.cursor.x = static_cast<int>(left_prompt_width);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
for (size_t i = 0; i < scr->desired.line_count(); i++) {
|
2012-11-19 08:30:30 +08:00
|
|
|
const line_t &o_line = scr->desired.line(i);
|
|
|
|
line_t &s_line = scr->actual.create_line(i);
|
2016-05-04 12:31:32 +08:00
|
|
|
size_t start_pos = i == 0 ? left_prompt_width : 0;
|
2012-07-16 01:45:18 +08:00
|
|
|
int current_width = 0;
|
2019-06-07 12:27:13 +08:00
|
|
|
bool has_cleared_line = false;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// If this is the last line, maybe we should clear the screen.
|
|
|
|
const bool should_clear_screen_this_line =
|
2019-11-19 10:34:50 +08:00
|
|
|
need_clear_screen && i + 1 == scr->desired.line_count() && clr_eos != nullptr;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Note that skip_remaining is a width, not a character count.
|
2012-08-02 07:32:52 +08:00
|
|
|
size_t skip_remaining = start_pos;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2019-05-11 16:25:27 +08:00
|
|
|
const size_t shared_prefix = line_shared_prefix(o_line, s_line);
|
2019-06-07 12:27:13 +08:00
|
|
|
size_t skip_prefix = shared_prefix;
|
|
|
|
if (shared_prefix < o_line.indentation) {
|
2019-10-14 06:50:48 +08:00
|
|
|
if (o_line.indentation > s_line.indentation && !has_cleared_screen && clr_eol &&
|
|
|
|
clr_eos) {
|
2019-06-07 12:27:13 +08:00
|
|
|
s_set_color(scr, vars, highlight_spec_t{});
|
2019-11-19 09:08:16 +08:00
|
|
|
s_move(scr, 0, static_cast<int>(i));
|
2019-10-14 06:50:48 +08:00
|
|
|
s_write_mbs(scr, should_clear_screen_this_line ? clr_eos : clr_eol);
|
2019-06-07 12:27:13 +08:00
|
|
|
has_cleared_screen = should_clear_screen_this_line;
|
|
|
|
has_cleared_line = true;
|
|
|
|
}
|
|
|
|
skip_prefix = o_line.indentation;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2019-10-03 02:45:01 +08:00
|
|
|
// Compute how much we should skip. At a minimum we skip over the prompt. But also skip
|
|
|
|
// over the shared prefix of what we want to output now, and what we output before, to
|
|
|
|
// avoid repeatedly outputting it.
|
|
|
|
if (skip_prefix > 0) {
|
2019-10-14 06:50:48 +08:00
|
|
|
size_t skip_width = shared_prefix < skip_prefix
|
|
|
|
? skip_prefix
|
|
|
|
: fish_wcswidth(&o_line.text.at(0), shared_prefix);
|
2019-10-03 02:45:01 +08:00
|
|
|
if (skip_width > skip_remaining) skip_remaining = skip_width;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!should_clear_screen_this_line) {
|
2016-05-04 03:31:07 +08:00
|
|
|
// If we're soft wrapped, and if we're going to change the first character of the next
|
|
|
|
// line, don't skip over the last two characters so that we maintain soft-wrapping.
|
|
|
|
if (o_line.is_soft_wrapped && i + 1 < scr->desired.line_count()) {
|
2016-05-05 09:14:04 +08:00
|
|
|
bool next_line_will_change = true;
|
|
|
|
if (i + 1 < scr->actual.line_count()) { //!OCLINT
|
2016-05-04 03:31:07 +08:00
|
|
|
if (line_shared_prefix(scr->desired.line(i + 1), scr->actual.line(i + 1)) > 0) {
|
2016-05-05 09:14:04 +08:00
|
|
|
next_line_will_change = false;
|
2012-12-06 18:54:25 +08:00
|
|
|
}
|
|
|
|
}
|
2016-05-05 09:14:04 +08:00
|
|
|
if (next_line_will_change) {
|
2019-11-19 09:08:16 +08:00
|
|
|
skip_remaining =
|
|
|
|
std::min(skip_remaining, static_cast<size_t>(scr->actual_width - 2));
|
2012-12-06 18:54:25 +08:00
|
|
|
}
|
|
|
|
}
|
2012-07-16 01:45:18 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Skip over skip_remaining width worth of characters.
|
2012-07-16 01:45:18 +08:00
|
|
|
size_t j = 0;
|
2016-05-04 03:31:07 +08:00
|
|
|
for (; j < o_line.size(); j++) {
|
2016-10-10 05:36:08 +08:00
|
|
|
size_t width = fish_wcwidth_min_0(o_line.char_at(j));
|
2016-05-04 03:31:07 +08:00
|
|
|
if (skip_remaining < width) break;
|
2012-08-05 09:32:15 +08:00
|
|
|
skip_remaining -= width;
|
2012-07-16 01:45:18 +08:00
|
|
|
current_width += width;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Skip over zero-width characters (e.g. combining marks at the end of the prompt).
|
|
|
|
for (; j < o_line.size(); j++) {
|
2012-11-25 08:42:25 +08:00
|
|
|
int width = fish_wcwidth_min_0(o_line.char_at(j));
|
2016-05-04 03:31:07 +08:00
|
|
|
if (width > 0) break;
|
2012-07-16 01:45:18 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Now actually output stuff.
|
2019-10-14 06:50:48 +08:00
|
|
|
for (;; j++) {
|
2019-06-07 12:18:09 +08:00
|
|
|
bool done = j >= o_line.size();
|
|
|
|
// Clear the screen if we have not done so yet.
|
2016-05-04 03:31:07 +08:00
|
|
|
// If we are about to output into the last column, clear the screen first. If we clear
|
|
|
|
// the screen after we output into the last column, it can erase the last character due
|
|
|
|
// to the sticky right cursor. If we clear the screen too early, we can defeat soft
|
|
|
|
// wrapping.
|
2019-10-14 06:50:48 +08:00
|
|
|
if (should_clear_screen_this_line && !has_cleared_screen &&
|
2019-11-19 09:08:16 +08:00
|
|
|
(done || j + 1 == static_cast<size_t>(screen_width))) {
|
2019-11-02 00:28:41 +08:00
|
|
|
s_set_color(scr, vars, highlight_spec_t{});
|
2019-11-19 09:08:16 +08:00
|
|
|
s_move(scr, current_width, static_cast<int>(i));
|
2018-10-07 04:32:08 +08:00
|
|
|
s_write_mbs(scr, clr_eos);
|
2012-11-25 17:26:58 +08:00
|
|
|
has_cleared_screen = true;
|
|
|
|
}
|
2019-10-14 06:50:48 +08:00
|
|
|
if (done) break;
|
2012-12-03 18:25:08 +08:00
|
|
|
|
2019-11-19 09:08:16 +08:00
|
|
|
perform_any_impending_soft_wrap(scr, current_width, static_cast<int>(i));
|
|
|
|
s_move(scr, current_width, static_cast<int>(i));
|
2018-10-07 04:32:08 +08:00
|
|
|
s_set_color(scr, vars, o_line.color_at(j));
|
2019-05-11 16:16:22 +08:00
|
|
|
auto width = fish_wcwidth_min_0(o_line.char_at(j));
|
|
|
|
s_write_char(scr, o_line.char_at(j), width);
|
|
|
|
current_width += width;
|
2012-07-16 01:45:18 +08:00
|
|
|
}
|
2012-12-03 18:25:08 +08:00
|
|
|
|
2012-10-03 08:30:07 +08:00
|
|
|
bool clear_remainder = false;
|
2016-05-04 03:31:07 +08:00
|
|
|
// Clear the remainder of the line if we need to clear and if we didn't write to the end of
|
|
|
|
// the line. If we did write to the end of the line, the "sticky right edge" (as part of
|
|
|
|
// auto_right_margin) means that we'll be clearing the last character we wrote!
|
2019-06-07 12:27:13 +08:00
|
|
|
if (has_cleared_screen || has_cleared_line) {
|
2016-05-04 03:31:07 +08:00
|
|
|
// Already cleared everything.
|
2012-11-08 11:59:20 +08:00
|
|
|
clear_remainder = false;
|
2016-05-04 03:31:07 +08:00
|
|
|
} else if (need_clear_lines && current_width < screen_width) {
|
2012-11-08 11:59:20 +08:00
|
|
|
clear_remainder = true;
|
2016-05-04 03:31:07 +08:00
|
|
|
} else if (right_prompt_width < scr->last_right_prompt_width) {
|
2012-10-03 08:30:07 +08:00
|
|
|
clear_remainder = true;
|
2016-05-04 03:31:07 +08:00
|
|
|
} else {
|
2019-05-11 16:25:27 +08:00
|
|
|
// This wcswidth shows up strong in the profile.
|
|
|
|
// Only do it if the previous line could conceivably be wider.
|
|
|
|
// That means if it is a prefix of the current one we can skip it.
|
|
|
|
if (s_line.text.size() != shared_prefix) {
|
|
|
|
int prev_width = fish_wcswidth(&s_line.text.at(0), s_line.text.size());
|
|
|
|
clear_remainder = prev_width > current_width;
|
|
|
|
}
|
2012-10-03 08:30:07 +08:00
|
|
|
}
|
2018-11-28 20:08:48 +08:00
|
|
|
if (clear_remainder && clr_eol) {
|
2019-03-04 09:34:00 +08:00
|
|
|
s_set_color(scr, vars, highlight_spec_t{});
|
2019-11-19 09:08:16 +08:00
|
|
|
s_move(scr, current_width, static_cast<int>(i));
|
2018-10-07 04:32:08 +08:00
|
|
|
s_write_mbs(scr, clr_eol);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Output any rprompt if this is the first line.
|
2016-10-31 11:21:26 +08:00
|
|
|
if (i == 0 && right_prompt_width > 0) { //!OCLINT(Use early exit/continue)
|
2019-11-19 09:08:16 +08:00
|
|
|
s_move(scr, static_cast<int>(screen_width - right_prompt_width), static_cast<int>(i));
|
2019-03-04 09:34:00 +08:00
|
|
|
s_set_color(scr, vars, highlight_spec_t{});
|
2018-10-07 04:32:08 +08:00
|
|
|
s_write_str(scr, right_prompt.c_str());
|
2012-11-27 04:31:44 +08:00
|
|
|
scr->actual.cursor.x += right_prompt_width;
|
2012-12-03 18:25:08 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// We output in the last column. Some terms (Linux) push the cursor further right, past
|
|
|
|
// the window. Others make it "stick." Since we don't really know which is which, issue
|
|
|
|
// a cr so it goes back to the left.
|
|
|
|
//
|
|
|
|
// However, if the user is resizing the window smaller, then it's possible the cursor
|
|
|
|
// wrapped. If so, then a cr will go to the beginning of the following line! So instead
|
|
|
|
// issue a bunch of "move left" commands to get back onto the line, and then jump to the
|
|
|
|
// front of it.
|
2019-11-19 09:08:16 +08:00
|
|
|
s_move(scr, scr->actual.cursor.x - static_cast<int>(right_prompt_width),
|
|
|
|
scr->actual.cursor.y);
|
2018-10-07 04:32:08 +08:00
|
|
|
s_write_str(scr, L"\r");
|
2012-11-10 07:21:08 +08:00
|
|
|
scr->actual.cursor.x = 0;
|
2012-11-05 15:21:37 +08:00
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Clear remaining lines (if any) if we haven't cleared the screen.
|
2019-11-02 00:28:41 +08:00
|
|
|
if (!has_cleared_screen && need_clear_screen && clr_eol) {
|
2019-03-04 09:34:00 +08:00
|
|
|
s_set_color(scr, vars, highlight_spec_t{});
|
2016-05-04 03:31:07 +08:00
|
|
|
for (size_t i = scr->desired.line_count(); i < lines_with_stuff; i++) {
|
2019-11-19 09:08:16 +08:00
|
|
|
s_move(scr, 0, static_cast<int>(i));
|
2018-10-07 04:32:08 +08:00
|
|
|
s_write_mbs(scr, clr_eol);
|
2012-11-08 11:59:20 +08:00
|
|
|
}
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2018-10-07 04:32:08 +08:00
|
|
|
s_move(scr, scr->desired.cursor.x, scr->desired.cursor.y);
|
2019-03-04 09:34:00 +08:00
|
|
|
s_set_color(scr, vars, highlight_spec_t{});
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// We have now synced our actual screen against our desired screen. Note that this is a big
|
|
|
|
// assignment!
|
2012-07-16 01:45:18 +08:00
|
|
|
scr->actual = scr->desired;
|
2012-11-08 11:59:20 +08:00
|
|
|
scr->last_right_prompt_width = right_prompt_width;
|
2006-10-02 00:02:58 +08:00
|
|
|
}
|
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
/// Returns true if we are using a dumb terminal.
|
2018-02-19 10:33:04 +08:00
|
|
|
static bool is_dumb() {
|
2017-02-16 12:09:26 +08:00
|
|
|
if (!cur_term) return true;
|
|
|
|
return !cursor_up || !cursor_down || !cursor_left || !cursor_right;
|
|
|
|
}
|
2007-08-02 03:07:54 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
struct screen_layout_t {
|
|
|
|
// The left prompt that we're going to use.
|
2012-11-08 11:59:20 +08:00
|
|
|
wcstring left_prompt;
|
2016-05-04 03:31:07 +08:00
|
|
|
// How much space to leave for it.
|
2012-11-08 11:59:20 +08:00
|
|
|
size_t left_prompt_space;
|
2016-05-04 03:31:07 +08:00
|
|
|
// The right prompt.
|
2012-11-08 11:59:20 +08:00
|
|
|
wcstring right_prompt;
|
2016-05-04 03:31:07 +08:00
|
|
|
// The autosuggestion.
|
2012-11-08 11:59:20 +08:00
|
|
|
wcstring autosuggestion;
|
2016-05-04 03:31:07 +08:00
|
|
|
// Whether the prompts get their own line or not.
|
2013-10-27 06:27:39 +08:00
|
|
|
bool prompts_get_own_line;
|
2012-11-08 11:59:20 +08:00
|
|
|
};
|
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Given a vector whose indexes are offsets and whose values are the widths of the string if
|
|
|
|
// truncated at that offset, return the offset that fits in the given width. Returns
|
|
|
|
// width_by_offset.size() - 1 if they all fit. The first value in width_by_offset is assumed to be
|
|
|
|
// 0.
|
|
|
|
static size_t truncation_offset_for_width(const std::vector<size_t> &width_by_offset,
|
|
|
|
size_t max_width) {
|
|
|
|
assert(!width_by_offset.empty() && width_by_offset.at(0) == 0);
|
2012-11-08 11:59:20 +08:00
|
|
|
size_t i;
|
2016-05-04 03:31:07 +08:00
|
|
|
for (i = 1; i < width_by_offset.size(); i++) {
|
|
|
|
if (width_by_offset.at(i) > max_width) break;
|
2012-11-08 11:59:20 +08:00
|
|
|
}
|
2016-05-04 03:31:07 +08:00
|
|
|
// i is the first index that did not fit; i-1 is therefore the last that did.
|
2012-11-08 11:59:20 +08:00
|
|
|
return i - 1;
|
|
|
|
}
|
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
static screen_layout_t compute_layout(screen_t *s, size_t screen_width,
|
2019-05-22 05:34:05 +08:00
|
|
|
const wcstring &left_prompt_str,
|
|
|
|
const wcstring &right_prompt_str, const wcstring &commandline,
|
|
|
|
const wcstring &autosuggestion_str) {
|
2016-10-10 05:38:26 +08:00
|
|
|
UNUSED(s);
|
2012-11-08 11:59:20 +08:00
|
|
|
screen_layout_t result = {};
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Start by ensuring that the prompts themselves can fit.
|
2019-05-22 05:34:05 +08:00
|
|
|
const wchar_t *left_prompt = left_prompt_str.c_str();
|
|
|
|
const wchar_t *right_prompt = right_prompt_str.c_str();
|
|
|
|
const wchar_t *autosuggestion = autosuggestion_str.c_str();
|
|
|
|
|
|
|
|
prompt_layout_t left_prompt_layout = calc_prompt_layout(left_prompt_str, cached_layouts);
|
|
|
|
prompt_layout_t right_prompt_layout = calc_prompt_layout(right_prompt_str, cached_layouts);
|
2012-12-03 18:25:08 +08:00
|
|
|
|
2012-11-25 17:26:58 +08:00
|
|
|
size_t left_prompt_width = left_prompt_layout.last_line_width;
|
|
|
|
size_t right_prompt_width = right_prompt_layout.last_line_width;
|
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
if (left_prompt_layout.max_line_width > screen_width) {
|
|
|
|
// If we have a multi-line prompt, see if the longest line fits; if not neuter the whole
|
|
|
|
// left prompt.
|
2012-11-25 17:26:58 +08:00
|
|
|
left_prompt = L"> ";
|
|
|
|
left_prompt_width = 2;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
if (left_prompt_width + right_prompt_width >= screen_width) {
|
|
|
|
// Nix right_prompt.
|
2012-11-08 11:59:20 +08:00
|
|
|
right_prompt = L"";
|
|
|
|
right_prompt_width = 0;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
if (left_prompt_width + right_prompt_width >= screen_width) {
|
|
|
|
// Still doesn't fit, neuter left_prompt.
|
2012-11-08 11:59:20 +08:00
|
|
|
left_prompt = L"> ";
|
|
|
|
left_prompt_width = 2;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Now we should definitely fit.
|
2012-11-08 11:59:20 +08:00
|
|
|
assert(left_prompt_width + right_prompt_width < screen_width);
|
|
|
|
|
2019-05-10 23:38:56 +08:00
|
|
|
// Get the width of the first line, and if there is more than one line.
|
|
|
|
bool multiline = false;
|
|
|
|
size_t first_line_width = 0;
|
2019-11-20 05:46:47 +08:00
|
|
|
for (auto c : commandline) {
|
2016-05-04 03:31:07 +08:00
|
|
|
if (c == L'\n') {
|
2019-05-10 23:38:56 +08:00
|
|
|
multiline = true;
|
|
|
|
break;
|
2016-05-04 03:31:07 +08:00
|
|
|
} else {
|
2019-05-10 23:38:56 +08:00
|
|
|
first_line_width += fish_wcwidth_min_0(c);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
}
|
2019-05-10 23:38:56 +08:00
|
|
|
const size_t first_command_line_width = first_line_width;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// If we have more than one line, ensure we have no autosuggestion.
|
2016-05-05 09:14:04 +08:00
|
|
|
size_t autosuggest_total_width = 0;
|
2019-05-10 23:38:56 +08:00
|
|
|
std::vector<size_t> autosuggest_truncated_widths;
|
|
|
|
if (multiline) {
|
|
|
|
autosuggestion = L"";
|
|
|
|
} else {
|
2019-05-22 05:34:05 +08:00
|
|
|
autosuggest_truncated_widths.reserve(1 + autosuggestion_str.size());
|
2019-05-10 23:38:56 +08:00
|
|
|
for (size_t i = 0; autosuggestion[i] != L'\0'; i++) {
|
|
|
|
autosuggest_truncated_widths.push_back(autosuggest_total_width);
|
|
|
|
autosuggest_total_width += fish_wcwidth_min_0(autosuggestion[i]);
|
|
|
|
}
|
2012-11-08 11:59:20 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Here are the layouts we try in turn:
|
|
|
|
//
|
|
|
|
// 1. Left prompt visible, right prompt visible, command line visible, autosuggestion visible.
|
|
|
|
//
|
|
|
|
// 2. Left prompt visible, right prompt visible, command line visible, autosuggestion truncated
|
|
|
|
// (possibly to zero).
|
|
|
|
//
|
|
|
|
// 3. Left prompt visible, right prompt hidden, command line visible, autosuggestion hidden.
|
|
|
|
//
|
|
|
|
// 4. Newline separator (left prompt visible, right prompt hidden, command line visible,
|
|
|
|
// autosuggestion visible).
|
|
|
|
//
|
|
|
|
// A remark about layout #4: if we've pushed the command line to a new line, why can't we draw
|
|
|
|
// the right prompt? The issue is resizing: if you resize the window smaller, then the right
|
|
|
|
// prompt will wrap to the next line. This means that we can't go back to the line that we were
|
|
|
|
// on, and things turn to chaos very quickly.
|
2017-05-02 12:44:30 +08:00
|
|
|
size_t calculated_width;
|
2012-11-08 11:59:20 +08:00
|
|
|
bool done = false;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Case 1
|
2017-05-02 12:44:30 +08:00
|
|
|
if (!done) {
|
|
|
|
calculated_width = left_prompt_width + right_prompt_width + first_command_line_width +
|
|
|
|
autosuggest_total_width;
|
|
|
|
if (calculated_width < screen_width) {
|
2019-05-22 05:34:05 +08:00
|
|
|
result.left_prompt = left_prompt;
|
2017-05-02 12:44:30 +08:00
|
|
|
result.left_prompt_space = left_prompt_width;
|
2019-05-22 05:34:05 +08:00
|
|
|
result.right_prompt = right_prompt;
|
|
|
|
result.autosuggestion = autosuggestion;
|
2017-05-02 12:44:30 +08:00
|
|
|
done = true;
|
|
|
|
}
|
2012-11-08 11:59:20 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Case 2. Note that we require strict inequality so that there's always at least one space
|
|
|
|
// between the left edge and the rprompt.
|
2017-05-02 12:44:30 +08:00
|
|
|
if (!done) {
|
|
|
|
calculated_width = left_prompt_width + right_prompt_width + first_command_line_width;
|
|
|
|
if (calculated_width < screen_width) {
|
2019-05-22 05:34:05 +08:00
|
|
|
result.left_prompt = left_prompt;
|
2017-05-02 12:44:30 +08:00
|
|
|
result.left_prompt_space = left_prompt_width;
|
2019-05-22 05:34:05 +08:00
|
|
|
result.right_prompt = right_prompt;
|
2017-05-02 12:44:30 +08:00
|
|
|
|
|
|
|
// Need at least two characters to show an autosuggestion.
|
|
|
|
size_t available_autosuggest_space =
|
|
|
|
screen_width - (left_prompt_width + right_prompt_width + first_command_line_width);
|
|
|
|
if (autosuggest_total_width > 0 && available_autosuggest_space > 2) {
|
|
|
|
size_t truncation_offset = truncation_offset_for_width(
|
|
|
|
autosuggest_truncated_widths, available_autosuggest_space - 2);
|
|
|
|
result.autosuggestion = wcstring(autosuggestion, truncation_offset);
|
2019-04-29 06:00:36 +08:00
|
|
|
result.autosuggestion.push_back(get_ellipsis_char());
|
2017-05-02 12:44:30 +08:00
|
|
|
}
|
|
|
|
done = true;
|
2012-11-08 11:59:20 +08:00
|
|
|
}
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Case 3
|
2017-05-02 12:44:30 +08:00
|
|
|
if (!done) {
|
|
|
|
calculated_width = left_prompt_width + first_command_line_width;
|
|
|
|
if (calculated_width < screen_width) {
|
2019-05-22 05:34:05 +08:00
|
|
|
result.left_prompt = left_prompt;
|
2017-05-02 12:44:30 +08:00
|
|
|
result.left_prompt_space = left_prompt_width;
|
|
|
|
done = true;
|
|
|
|
}
|
2012-11-08 11:59:20 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Case 4
|
|
|
|
if (!done) {
|
2019-05-22 05:34:05 +08:00
|
|
|
result.left_prompt = left_prompt;
|
2012-11-08 11:59:20 +08:00
|
|
|
result.left_prompt_space = left_prompt_width;
|
2016-05-04 03:31:07 +08:00
|
|
|
// See remark about for why we can't use the right prompt here result.right_prompt =
|
|
|
|
// right_prompt. If the command wraps, and the prompt is not short, place the command on its
|
|
|
|
// own line. A short prompt is 33% or less of the terminal's width.
|
2013-11-17 20:49:48 +08:00
|
|
|
const size_t prompt_percent_width = (100 * left_prompt_width) / screen_width;
|
2016-05-04 03:31:07 +08:00
|
|
|
if (left_prompt_width + first_command_line_width + 1 > screen_width &&
|
|
|
|
prompt_percent_width > 33) {
|
2013-11-17 20:49:48 +08:00
|
|
|
result.prompts_get_own_line = true;
|
|
|
|
}
|
2012-11-08 11:59:20 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2012-11-08 11:59:20 +08:00
|
|
|
return result;
|
|
|
|
}
|
2007-08-02 03:07:54 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
void s_write(screen_t *s, const wcstring &left_prompt, const wcstring &right_prompt,
|
2019-03-04 10:49:57 +08:00
|
|
|
const wcstring &commandline, size_t explicit_len,
|
|
|
|
const std::vector<highlight_spec_t> &colors, const std::vector<int> &indent,
|
|
|
|
size_t cursor_pos, const page_rendering_t &pager, bool cursor_is_within_pager) {
|
2020-03-03 10:22:18 +08:00
|
|
|
static relaxed_atomic_t<uint32_t> s_repaints{0};
|
|
|
|
FLOGF(screen, "Repaint %u", (unsigned)++s_repaints);
|
2012-11-19 08:30:30 +08:00
|
|
|
screen_data_t::cursor_t cursor_arr;
|
2012-11-08 11:59:20 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Turn the command line into the explicit portion and the autosuggestion.
|
2012-11-08 11:59:20 +08:00
|
|
|
const wcstring explicit_command_line = commandline.substr(0, explicit_len);
|
|
|
|
const wcstring autosuggestion = commandline.substr(explicit_len);
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// If we are using a dumb terminal, don't try any fancy stuff, just print out the text.
|
|
|
|
// right_prompt not supported.
|
|
|
|
if (is_dumb()) {
|
2012-11-19 08:30:30 +08:00
|
|
|
const std::string prompt_narrow = wcs2string(left_prompt);
|
|
|
|
const std::string command_line_narrow = wcs2string(explicit_command_line);
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2012-12-02 07:44:09 +08:00
|
|
|
write_loop(STDOUT_FILENO, "\r", 1);
|
|
|
|
write_loop(STDOUT_FILENO, prompt_narrow.c_str(), prompt_narrow.size());
|
|
|
|
write_loop(STDOUT_FILENO, command_line_narrow.c_str(), command_line_narrow.size());
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2012-11-19 08:30:30 +08:00
|
|
|
return;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2012-11-19 08:30:30 +08:00
|
|
|
s_check_status(s);
|
2012-11-08 11:59:20 +08:00
|
|
|
const size_t screen_width = common_get_width();
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Completely ignore impossibly small screens.
|
|
|
|
if (screen_width < 4) {
|
2012-11-19 08:30:30 +08:00
|
|
|
return;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Compute a layout.
|
|
|
|
const screen_layout_t layout = compute_layout(s, screen_width, left_prompt, right_prompt,
|
2019-05-13 09:23:00 +08:00
|
|
|
explicit_command_line, autosuggestion);
|
2012-11-08 11:59:20 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Determine whether, if we have an autosuggestion, it was truncated.
|
|
|
|
s->autosuggestion_is_truncated =
|
|
|
|
!autosuggestion.empty() && autosuggestion != layout.autosuggestion;
|
2013-08-21 11:08:56 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Clear the desired screen.
|
2012-11-08 11:59:20 +08:00
|
|
|
s->desired.resize(0);
|
2012-11-19 08:30:30 +08:00
|
|
|
s->desired.cursor.x = s->desired.cursor.y = 0;
|
2012-11-08 11:59:20 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Append spaces for the left prompt.
|
|
|
|
for (size_t i = 0; i < layout.left_prompt_space; i++) {
|
2019-05-11 16:16:22 +08:00
|
|
|
s_desired_append_char(s, L' ', highlight_spec_t{}, 0, layout.left_prompt_space, 1);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// If overflowing, give the prompt its own line to improve the situation.
|
2012-11-08 11:59:20 +08:00
|
|
|
size_t first_line_prompt_space = layout.left_prompt_space;
|
2016-05-04 03:31:07 +08:00
|
|
|
if (layout.prompts_get_own_line) {
|
2019-05-11 16:16:22 +08:00
|
|
|
s_desired_append_char(s, L'\n', highlight_spec_t{}, 0, 0, 0);
|
2012-11-08 11:59:20 +08:00
|
|
|
first_line_prompt_space = 0;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Reconstruct the command line.
|
2012-11-08 11:59:20 +08:00
|
|
|
wcstring effective_commandline = explicit_command_line + layout.autosuggestion;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Output the command line.
|
2012-11-08 11:59:20 +08:00
|
|
|
size_t i;
|
2016-05-04 03:31:07 +08:00
|
|
|
for (i = 0; i < effective_commandline.size(); i++) {
|
|
|
|
// Grab the current cursor's x,y position if this character matches the cursor's offset.
|
2016-05-05 09:14:04 +08:00
|
|
|
if (!cursor_is_within_pager && i == cursor_pos) {
|
2012-11-08 11:59:20 +08:00
|
|
|
cursor_arr = s->desired.cursor;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-05-04 03:31:07 +08:00
|
|
|
s_desired_append_char(s, effective_commandline.at(i), colors[i], indent[i],
|
2019-05-28 10:47:13 +08:00
|
|
|
first_line_prompt_space,
|
|
|
|
fish_wcwidth_min_0(effective_commandline.at(i)));
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-05-04 03:31:07 +08:00
|
|
|
|
|
|
|
// Cursor may have been at the end too.
|
2016-05-05 09:14:04 +08:00
|
|
|
if (!cursor_is_within_pager && i == cursor_pos) {
|
2012-11-08 11:59:20 +08:00
|
|
|
cursor_arr = s->desired.cursor;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-05-04 03:31:07 +08:00
|
|
|
|
|
|
|
// Now that we've output everything, set the cursor to the position that we saved in the loop
|
|
|
|
// above.
|
2012-11-08 11:59:20 +08:00
|
|
|
s->desired.cursor = cursor_arr;
|
2014-04-01 01:01:39 +08:00
|
|
|
|
2016-05-05 09:14:04 +08:00
|
|
|
if (cursor_is_within_pager) {
|
2019-11-19 09:08:16 +08:00
|
|
|
s->desired.cursor.x = static_cast<int>(cursor_pos);
|
|
|
|
s->desired.cursor.y = static_cast<int>(s->desired.line_count());
|
2014-01-27 16:56:13 +08:00
|
|
|
}
|
2014-04-01 01:01:39 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// Append pager_data (none if empty).
|
2014-01-18 04:04:03 +08:00
|
|
|
s->desired.append_lines(pager.screen_data);
|
2014-04-01 01:01:39 +08:00
|
|
|
|
2018-02-05 08:17:57 +08:00
|
|
|
s_update(s, layout.left_prompt, layout.right_prompt);
|
2012-11-19 08:30:30 +08:00
|
|
|
s_save_status(s);
|
2012-11-08 11:59:20 +08:00
|
|
|
}
|
2016-05-04 03:31:07 +08:00
|
|
|
void s_reset(screen_t *s, screen_reset_mode_t mode) {
|
2019-05-28 08:24:19 +08:00
|
|
|
assert(s && "Null screen");
|
2012-11-25 08:58:30 +08:00
|
|
|
|
2012-11-25 08:42:25 +08:00
|
|
|
bool abandon_line = false, repaint_prompt = false, clear_to_eos = false;
|
2016-05-04 03:31:07 +08:00
|
|
|
switch (mode) {
|
2020-04-29 02:00:14 +08:00
|
|
|
case screen_reset_mode_t::current_line_contents: {
|
2012-11-25 08:42:25 +08:00
|
|
|
break;
|
2016-05-04 03:31:07 +08:00
|
|
|
}
|
2020-04-29 02:00:14 +08:00
|
|
|
case screen_reset_mode_t::current_line_and_prompt: {
|
2012-11-25 08:42:25 +08:00
|
|
|
repaint_prompt = true;
|
|
|
|
break;
|
2016-05-04 03:31:07 +08:00
|
|
|
}
|
2020-04-29 02:00:14 +08:00
|
|
|
case screen_reset_mode_t::abandon_line: {
|
2012-11-25 08:42:25 +08:00
|
|
|
abandon_line = true;
|
|
|
|
repaint_prompt = true;
|
|
|
|
break;
|
2016-05-04 03:31:07 +08:00
|
|
|
}
|
2020-04-29 02:00:14 +08:00
|
|
|
case screen_reset_mode_t::abandon_line_and_clear_to_end_of_screen: {
|
2012-11-25 08:42:25 +08:00
|
|
|
abandon_line = true;
|
|
|
|
repaint_prompt = true;
|
|
|
|
clear_to_eos = true;
|
|
|
|
break;
|
2016-05-04 03:31:07 +08:00
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-25 08:58:30 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// If we're abandoning the line, we must also be repainting the prompt.
|
|
|
|
assert(!abandon_line || repaint_prompt);
|
2012-11-25 08:58:30 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
// If we are not abandoning the line, we need to remember how many lines we had output to, so we
|
|
|
|
// can clear the remaining lines in the next call to s_update. This prevents leaving junk
|
|
|
|
// underneath the cursor when resizing a window wider such that it reduces our desired line
|
|
|
|
// count.
|
|
|
|
if (!abandon_line) {
|
2019-03-13 13:38:42 +08:00
|
|
|
s->actual_lines_before_reset =
|
|
|
|
std::max(s->actual_lines_before_reset, s->actual.line_count());
|
2012-11-08 11:59:20 +08:00
|
|
|
}
|
2012-12-03 18:25:08 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
if (repaint_prompt && !abandon_line) {
|
|
|
|
// If the prompt is multi-line, we need to move up to the prompt's initial line. We do this
|
|
|
|
// by lying to ourselves and claiming that we're really below what we consider "line 0"
|
|
|
|
// (which is the last line of the prompt). This will cause us to move up to try to get back
|
|
|
|
// to line 0, but really we're getting back to the initial line of the prompt.
|
2012-11-25 13:06:42 +08:00
|
|
|
const size_t prompt_line_count = calc_prompt_lines(s->actual_left_prompt);
|
|
|
|
assert(prompt_line_count >= 1);
|
|
|
|
s->actual.cursor.y += (prompt_line_count - 1);
|
2016-05-04 03:31:07 +08:00
|
|
|
} else if (abandon_line) {
|
2012-11-25 13:06:42 +08:00
|
|
|
s->actual.cursor.y = 0;
|
|
|
|
}
|
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
if (repaint_prompt) s->actual_left_prompt.clear();
|
2011-12-28 10:41:38 +08:00
|
|
|
s->actual.resize(0);
|
2012-11-25 08:42:25 +08:00
|
|
|
s->need_clear_lines = true;
|
|
|
|
s->need_clear_screen = s->need_clear_screen || clear_to_eos;
|
2012-11-25 08:58:30 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
if (abandon_line) {
|
|
|
|
// Do the PROMPT_SP hack.
|
2012-12-02 07:44:09 +08:00
|
|
|
int screen_width = common_get_width();
|
|
|
|
wcstring abandon_line_string;
|
2016-05-04 03:31:07 +08:00
|
|
|
abandon_line_string.reserve(screen_width + 32); // should be enough
|
2012-12-03 18:25:08 +08:00
|
|
|
|
2016-06-15 08:16:18 +08:00
|
|
|
// Don't need to check for fish_wcwidth errors; this is done when setting up
|
|
|
|
// omitted_newline_char in common.cpp.
|
2019-04-29 06:00:36 +08:00
|
|
|
int non_space_width = get_omitted_newline_width();
|
2016-12-24 05:08:45 +08:00
|
|
|
// We do `>` rather than `>=` because the code below might require one extra space.
|
|
|
|
if (screen_width > non_space_width) {
|
2016-10-03 20:35:08 +08:00
|
|
|
bool justgrey = true;
|
2017-02-16 12:09:26 +08:00
|
|
|
if (cur_term && enter_dim_mode) {
|
2020-01-06 01:51:33 +08:00
|
|
|
std::string dim = tparm((char *)enter_dim_mode);
|
2016-10-03 20:35:08 +08:00
|
|
|
if (!dim.empty()) {
|
|
|
|
// Use dim if they have it, so the color will be based on their actual normal
|
|
|
|
// color and the background of the termianl.
|
|
|
|
abandon_line_string.append(str2wcstring(dim));
|
|
|
|
justgrey = false;
|
|
|
|
}
|
2016-09-11 11:28:06 +08:00
|
|
|
}
|
2017-02-16 12:09:26 +08:00
|
|
|
if (cur_term && justgrey && set_a_foreground) {
|
2016-10-04 03:41:53 +08:00
|
|
|
if (max_colors >= 238) {
|
|
|
|
// draw the string in a particular grey
|
2020-01-06 01:51:33 +08:00
|
|
|
abandon_line_string.append(str2wcstring(tparm((char *)set_a_foreground, 237)));
|
2016-10-04 03:41:53 +08:00
|
|
|
} else if (max_colors >= 9) {
|
|
|
|
// bright black (the ninth color, looks grey)
|
2020-01-06 01:51:33 +08:00
|
|
|
abandon_line_string.append(str2wcstring(tparm((char *)set_a_foreground, 8)));
|
2016-10-04 03:41:53 +08:00
|
|
|
} else if (max_colors >= 2 && enter_bold_mode) {
|
|
|
|
// we might still get that color by setting black and going bold for bright
|
2020-01-06 01:51:33 +08:00
|
|
|
abandon_line_string.append(str2wcstring(tparm((char *)enter_bold_mode)));
|
|
|
|
abandon_line_string.append(str2wcstring(tparm((char *)set_a_foreground, 0)));
|
2016-10-04 03:41:53 +08:00
|
|
|
}
|
2013-01-25 04:07:57 +08:00
|
|
|
}
|
2017-02-16 12:09:26 +08:00
|
|
|
|
2019-04-29 06:00:36 +08:00
|
|
|
abandon_line_string.append(get_omitted_newline_str());
|
2016-10-04 03:41:53 +08:00
|
|
|
|
2017-02-16 12:09:26 +08:00
|
|
|
if (cur_term && exit_attribute_mode) {
|
2020-01-17 07:14:21 +08:00
|
|
|
abandon_line_string.append(str2wcstring(
|
|
|
|
tparm((char *)exit_attribute_mode))); // normal text ANSI escape sequence
|
2016-10-03 20:35:08 +08:00
|
|
|
}
|
2017-02-16 12:09:26 +08:00
|
|
|
|
2016-12-24 05:08:45 +08:00
|
|
|
int newline_glitch_width = term_has_xn ? 0 : 1;
|
|
|
|
abandon_line_string.append(screen_width - non_space_width - newline_glitch_width, L' ');
|
2012-12-02 07:44:09 +08:00
|
|
|
}
|
2017-02-16 12:09:26 +08:00
|
|
|
|
2012-12-02 07:44:09 +08:00
|
|
|
abandon_line_string.push_back(L'\r');
|
2019-04-29 06:00:36 +08:00
|
|
|
abandon_line_string.append(get_omitted_newline_str());
|
2016-05-04 03:31:07 +08:00
|
|
|
// Now we are certainly on a new line. But we may have dropped the omitted newline char on
|
|
|
|
// it. So append enough spaces to overwrite the omitted newline char, and then clear all the
|
2016-12-24 05:08:45 +08:00
|
|
|
// spaces from the new line.
|
2013-01-25 04:07:57 +08:00
|
|
|
abandon_line_string.append(non_space_width, L' ');
|
|
|
|
abandon_line_string.push_back(L'\r');
|
2016-12-24 05:08:45 +08:00
|
|
|
// Clear entire line. Zsh doesn't do this. Fish added this with commit 4417a6ee: If you have
|
|
|
|
// a prompt preceded by a new line, you'll get a line full of spaces instead of an empty
|
|
|
|
// line above your prompt. This doesn't make a difference in normal usage, but copying and
|
|
|
|
// pasting your terminal log becomes a pain. This commit clears that line, making it an
|
|
|
|
// actual empty line.
|
2018-11-28 20:08:48 +08:00
|
|
|
if (!is_dumb() && clr_eol) {
|
2018-05-08 18:39:58 +08:00
|
|
|
abandon_line_string.append(str2wcstring(clr_eol));
|
|
|
|
}
|
2013-02-01 07:57:08 +08:00
|
|
|
|
2012-12-02 07:44:09 +08:00
|
|
|
const std::string narrow_abandon_line_string = wcs2string(abandon_line_string);
|
2016-05-04 03:31:07 +08:00
|
|
|
write_loop(STDOUT_FILENO, narrow_abandon_line_string.c_str(),
|
|
|
|
narrow_abandon_line_string.size());
|
2012-12-02 07:44:09 +08:00
|
|
|
s->actual.cursor.x = 0;
|
|
|
|
}
|
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
if (!abandon_line) {
|
|
|
|
// This should prevent resetting the cursor position during the next repaint.
|
2012-12-02 07:44:09 +08:00
|
|
|
write_loop(STDOUT_FILENO, "\r", 1);
|
2012-11-25 13:06:42 +08:00
|
|
|
s->actual.cursor.x = 0;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-25 08:58:30 +08:00
|
|
|
|
2012-11-19 08:30:30 +08:00
|
|
|
fstat(1, &s->prev_buff_1);
|
|
|
|
fstat(2, &s->prev_buff_2);
|
2006-10-02 00:02:58 +08:00
|
|
|
}
|
|
|
|
|
2018-10-07 04:32:08 +08:00
|
|
|
void screen_force_clear_to_end() {
|
2016-05-04 03:31:07 +08:00
|
|
|
if (clr_eos) {
|
2018-10-07 04:32:08 +08:00
|
|
|
writembs(outputter_t::stdoutput(), clr_eos);
|
2014-01-16 10:21:38 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-29 02:25:32 +08:00
|
|
|
screen_t::screen_t() : outp_(outputter_t::stdoutput()) {}
|
2020-04-29 02:49:26 +08:00
|
|
|
|
|
|
|
bool screen_t::cursor_is_wrapped_to_own_line() const {
|
|
|
|
// Note == comparison against the line count is correct: we do not create a line just for the
|
|
|
|
// cursor. If there is a line containing the cursor, then it means that line has contents and we
|
|
|
|
// should return false.
|
|
|
|
return actual.cursor.x == 0 && static_cast<size_t>(actual.cursor.y) == actual.line_count();
|
|
|
|
}
|