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 a reasonably
|
|
|
|
// efficient way for transforming that to the desired screen content.
|
|
|
|
//
|
|
|
|
// The current implementation is less smart than ncurses allows and can not for example move blocks
|
|
|
|
// of text around to handle text insertion.
|
2006-10-02 00:02:58 +08:00
|
|
|
#ifndef FISH_SCREEN_H
|
|
|
|
#define FISH_SCREEN_H
|
2017-02-13 12:24:22 +08:00
|
|
|
#include "config.h" // IWYU pragma: keep
|
2006-10-02 00:02:58 +08:00
|
|
|
|
2015-07-25 23:14:25 +08:00
|
|
|
#include <stddef.h>
|
2013-12-08 04:43:40 +08:00
|
|
|
#include <sys/stat.h>
|
2017-02-11 10:47:02 +08:00
|
|
|
|
2017-02-04 11:20:21 +08:00
|
|
|
#include <algorithm>
|
2017-02-16 12:09:26 +08:00
|
|
|
#include <cstddef>
|
2019-10-14 06:50:48 +08:00
|
|
|
#include <cwchar>
|
2018-02-05 07:03:50 +08:00
|
|
|
#include <list>
|
2016-05-04 03:31:07 +08:00
|
|
|
#include <memory>
|
2017-08-20 00:55:06 +08:00
|
|
|
#include <unordered_map>
|
2018-02-05 07:03:50 +08:00
|
|
|
#include <unordered_set>
|
2020-06-08 04:53:41 +08:00
|
|
|
#include <utility>
|
2016-05-04 03:31:07 +08:00
|
|
|
#include <vector>
|
2017-02-04 11:20:21 +08:00
|
|
|
|
2015-07-25 23:14:25 +08:00
|
|
|
#include "common.h"
|
2014-01-15 17:01:25 +08:00
|
|
|
#include "highlight.h"
|
2020-01-16 05:16:43 +08:00
|
|
|
#include "wcstringutil.h"
|
2011-12-28 10:41:38 +08:00
|
|
|
|
2014-01-18 04:04:03 +08:00
|
|
|
class page_rendering_t;
|
2014-01-16 10:21:38 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
/// A class representing a single line of a screen.
|
|
|
|
struct line_t {
|
2020-06-08 04:53:41 +08:00
|
|
|
/// A pair of a character, and the color with which to draw it.
|
|
|
|
using highlighted_char_t = std::pair<wchar_t, highlight_spec_t>;
|
|
|
|
std::vector<highlighted_char_t> text{};
|
|
|
|
bool is_soft_wrapped{false};
|
|
|
|
size_t indentation{0};
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2020-06-08 04:53:41 +08:00
|
|
|
line_t() = default;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2020-06-08 04:53:41 +08:00
|
|
|
/// Clear the line's contents.
|
2016-05-04 03:31:07 +08:00
|
|
|
void clear(void) {
|
2012-07-16 01:45:18 +08:00
|
|
|
text.clear();
|
2011-12-28 10:41:38 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2020-06-08 04:53:41 +08:00
|
|
|
/// Append a single character \p txt to the line with color \p c.
|
|
|
|
void append(wchar_t c, highlight_spec_t color) { text.push_back({c, color}); }
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2020-06-08 04:53:41 +08:00
|
|
|
/// Append a nul-terminated string \p txt to the line, giving each character \p color.
|
2016-05-04 03:31:07 +08:00
|
|
|
void append(const wchar_t *txt, highlight_spec_t color) {
|
|
|
|
for (size_t i = 0; txt[i]; i++) {
|
2020-06-08 04:53:41 +08:00
|
|
|
text.push_back({txt[i], color});
|
2014-01-20 08:41:26 +08:00
|
|
|
}
|
|
|
|
}
|
2014-04-01 01:01:39 +08:00
|
|
|
|
2020-06-08 04:53:41 +08:00
|
|
|
/// \return the number of characters.
|
|
|
|
size_t size() const { return text.size(); }
|
2014-04-01 01:01:39 +08:00
|
|
|
|
2020-06-08 04:53:41 +08:00
|
|
|
/// \return the character at a char index.
|
|
|
|
wchar_t char_at(size_t idx) const { return text.at(idx).first; }
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2020-06-08 04:53:41 +08:00
|
|
|
/// \return the color at a char index.
|
|
|
|
highlight_spec_t color_at(size_t idx) const { return text.at(idx).second; }
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2020-06-08 04:53:41 +08:00
|
|
|
/// Append the contents of \p line to this line.
|
2016-05-04 03:31:07 +08:00
|
|
|
void append_line(const line_t &line) {
|
2014-01-14 08:41:22 +08:00
|
|
|
text.insert(text.end(), line.text.begin(), line.text.end());
|
|
|
|
}
|
2016-12-04 05:12:44 +08:00
|
|
|
|
2020-06-08 04:53:41 +08:00
|
|
|
/// \return the width of this line, counting up to no more than \p max characters.
|
|
|
|
/// This follows fish_wcswidth() semantics, except that characters whose width would be -1 are
|
|
|
|
/// treated as 0.
|
|
|
|
int wcswidth_min_0(size_t max = std::numeric_limits<size_t>::max()) const;
|
2011-12-28 10:41:38 +08:00
|
|
|
};
|
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
/// A class representing screen contents.
|
|
|
|
class screen_data_t {
|
2011-12-28 10:41:38 +08:00
|
|
|
std::vector<line_t> line_datas;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
public:
|
2020-06-08 05:53:42 +08:00
|
|
|
/// The width of the screen in this rendering.
|
|
|
|
/// -1 if not set, i.e. we have not rendered before.
|
|
|
|
int screen_width{-1};
|
|
|
|
|
|
|
|
/// Where the cursor is in (x, y) coordinates.
|
2016-05-04 03:31:07 +08:00
|
|
|
struct cursor_t {
|
2020-04-29 02:25:32 +08:00
|
|
|
int x{0};
|
|
|
|
int y{0};
|
|
|
|
cursor_t() = default;
|
2016-05-04 03:31:07 +08:00
|
|
|
cursor_t(int a, int b) : x(a), y(b) {}
|
2020-06-08 05:53:42 +08:00
|
|
|
};
|
|
|
|
cursor_t cursor;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
line_t &add_line(void) {
|
2011-12-28 10:41:38 +08:00
|
|
|
line_datas.resize(line_datas.size() + 1);
|
|
|
|
return line_datas.back();
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
void resize(size_t size) { line_datas.resize(size); }
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
line_t &create_line(size_t idx) {
|
|
|
|
if (idx >= line_datas.size()) {
|
2011-12-28 10:41:38 +08:00
|
|
|
line_datas.resize(idx + 1);
|
|
|
|
}
|
|
|
|
return line_datas.at(idx);
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
line_t &insert_line_at_index(size_t idx) {
|
2014-01-26 16:41:30 +08:00
|
|
|
assert(idx <= line_datas.size());
|
|
|
|
return *line_datas.insert(line_datas.begin() + idx, line_t());
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
line_t &line(size_t idx) { return line_datas.at(idx); }
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-12-04 05:12:44 +08:00
|
|
|
const line_t &line(size_t idx) const { return line_datas.at(idx); }
|
|
|
|
|
|
|
|
size_t line_count() const { return line_datas.size(); }
|
2014-04-01 01:01:39 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
void append_lines(const screen_data_t &d) {
|
2014-01-15 07:39:53 +08:00
|
|
|
this->line_datas.insert(this->line_datas.end(), d.line_datas.begin(), d.line_datas.end());
|
|
|
|
}
|
2014-04-01 01:01:39 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
bool empty() const { return line_datas.empty(); }
|
2011-12-28 10:41:38 +08:00
|
|
|
};
|
|
|
|
|
2018-10-07 04:32:08 +08:00
|
|
|
class outputter_t;
|
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
/// The class representing the current and desired screen contents.
|
|
|
|
class screen_t {
|
2018-10-07 04:32:08 +08:00
|
|
|
outputter_t &outp_;
|
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
public:
|
2012-10-01 18:29:18 +08:00
|
|
|
screen_t();
|
2006-10-05 05:45:02 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
/// The internal representation of the desired screen contents.
|
2020-04-29 02:25:32 +08:00
|
|
|
screen_data_t desired{};
|
2016-05-04 03:31:07 +08:00
|
|
|
/// The internal representation of the actual screen contents.
|
2020-04-29 02:25:32 +08:00
|
|
|
screen_data_t actual{};
|
2016-05-04 03:31:07 +08:00
|
|
|
/// A string containing the prompt which was last printed to the screen.
|
2020-04-29 02:25:32 +08:00
|
|
|
wcstring actual_left_prompt{};
|
2016-05-04 03:31:07 +08:00
|
|
|
/// Last right prompt width.
|
2020-04-29 02:25:32 +08:00
|
|
|
size_t last_right_prompt_width{0};
|
2016-05-04 03:31:07 +08:00
|
|
|
/// If we support soft wrapping, we can output to this location without any cursor motion.
|
2020-04-29 02:25:32 +08:00
|
|
|
maybe_t<screen_data_t::cursor_t> soft_wrap_location{};
|
2016-05-04 03:31:07 +08:00
|
|
|
/// Whether the last-drawn autosuggestion (if any) is truncated, or hidden entirely.
|
2020-04-29 02:25:32 +08:00
|
|
|
bool autosuggestion_is_truncated{false};
|
2016-05-04 03:31:07 +08:00
|
|
|
/// This flag is set to true when there is reason to suspect that the parts of the screen lines
|
|
|
|
/// where the actual content is not filled in may be non-empty. This means that a clr_eol
|
|
|
|
/// command has to be sent to the terminal at the end of each line, including
|
|
|
|
/// actual_lines_before_reset.
|
2020-04-29 02:25:32 +08:00
|
|
|
bool need_clear_lines{false};
|
2016-05-04 03:31:07 +08:00
|
|
|
/// Whether there may be yet more content after the lines, and we issue a clr_eos if possible.
|
2020-04-29 02:25:32 +08:00
|
|
|
bool need_clear_screen{false};
|
2016-05-04 03:31:07 +08:00
|
|
|
/// If we need to clear, this is how many lines the actual screen had, before we reset it. This
|
|
|
|
/// is used when resizing the window larger: if the cursor jumps to the line above, we need to
|
|
|
|
/// remember to clear the subsequent lines.
|
2020-04-29 02:25:32 +08:00
|
|
|
size_t actual_lines_before_reset{0};
|
2016-05-04 03:31:07 +08:00
|
|
|
/// These status buffers are used to check if any output has occurred other than from fish's
|
|
|
|
/// main loop, in which case we need to redraw.
|
2020-04-29 02:25:32 +08:00
|
|
|
struct stat prev_buff_1 {};
|
|
|
|
struct stat prev_buff_2 {};
|
2018-10-07 04:32:08 +08:00
|
|
|
|
|
|
|
/// \return the outputter for this screen.
|
|
|
|
outputter_t &outp() { return outp_; }
|
2020-04-29 02:49:26 +08:00
|
|
|
|
|
|
|
/// \return whether we believe the cursor is wrapped onto the last line, and that line is
|
|
|
|
/// otherwise empty. This includes both soft and hard wrapping.
|
|
|
|
bool cursor_is_wrapped_to_own_line() const;
|
2012-03-25 18:00:38 +08:00
|
|
|
};
|
2006-10-02 00:02:58 +08:00
|
|
|
|
2016-05-04 03:31:07 +08:00
|
|
|
/// This is the main function for the screen putput library. It is used to define the desired
|
2019-11-25 19:03:25 +08:00
|
|
|
/// contents of the screen. The screen command will use its knowledge of the current contents of the
|
2016-05-04 03:31:07 +08:00
|
|
|
/// screen in order to render the desired output using as few terminal commands as possible.
|
|
|
|
///
|
|
|
|
/// \param s the screen on which to write
|
2020-06-08 05:53:42 +08:00
|
|
|
/// \param int screen_width the width of the screen to render
|
2016-05-04 03:31:07 +08:00
|
|
|
/// \param left_prompt the prompt to prepend to the command line
|
|
|
|
/// \param right_prompt the right prompt, or NULL if none
|
|
|
|
/// \param commandline the command line
|
|
|
|
/// \param explicit_len the number of characters of the "explicit" (non-autosuggestion) portion of
|
|
|
|
/// the command line
|
|
|
|
/// \param colors the colors to use for the comand line
|
|
|
|
/// \param indent the indent to use for the command line
|
|
|
|
/// \param cursor_pos where the cursor is
|
|
|
|
/// \param pager_data any pager data, to append to the screen
|
2016-06-06 09:46:04 +08:00
|
|
|
/// \param cursor_is_within_pager whether the position is within the pager line (first line)
|
2020-06-08 05:53:42 +08:00
|
|
|
void s_write(screen_t *s, int screen_width, const wcstring &left_prompt,
|
|
|
|
const wcstring &right_prompt, const wcstring &commandline, size_t explicit_len,
|
2019-03-04 10:49:57 +08:00
|
|
|
const std::vector<highlight_spec_t> &colors, const std::vector<int> &indent,
|
|
|
|
size_t cursor_pos, const page_rendering_t &pager_data, bool cursor_is_within_pager);
|
2016-05-04 03:31:07 +08:00
|
|
|
|
2018-09-17 07:25:49 +08:00
|
|
|
|
2020-04-29 02:00:14 +08:00
|
|
|
enum class screen_reset_mode_t {
|
2016-05-04 03:31:07 +08:00
|
|
|
/// Do not make a new line, do not repaint the prompt.
|
2020-04-29 02:00:14 +08:00
|
|
|
current_line_contents,
|
2016-05-04 03:31:07 +08:00
|
|
|
/// Do not make a new line, do repaint the prompt.
|
2020-04-29 02:00:14 +08:00
|
|
|
current_line_and_prompt,
|
2016-05-04 03:31:07 +08:00
|
|
|
/// Abandon the current line, go to the next one, repaint the prompt.
|
2020-04-29 02:00:14 +08:00
|
|
|
abandon_line,
|
2016-05-04 03:31:07 +08:00
|
|
|
/// Abandon the current line, go to the next one, clear the rest of the screen.
|
2020-04-29 02:00:14 +08:00
|
|
|
abandon_line_and_clear_to_end_of_screen
|
2012-11-25 08:42:25 +08:00
|
|
|
};
|
|
|
|
|
2020-06-08 05:28:44 +08:00
|
|
|
/// This function resets the screen buffers internal knowledge about the contents of the screen. Use
|
|
|
|
/// this function when some other function than s_write has written to the screen.
|
|
|
|
///
|
|
|
|
/// \param s the screen to reset
|
2020-06-08 05:53:42 +08:00
|
|
|
/// \param screen_width the current width of the screen
|
2020-06-08 05:28:44 +08:00
|
|
|
/// \param mode the sort of screen reset to apply
|
|
|
|
///
|
|
|
|
/// If reset_cursor is incorrectly set to false, this may result in screen contents being erased. If
|
|
|
|
/// it is incorrectly set to true, it may result in one or more lines of garbage on screen on the
|
|
|
|
/// next repaint. If this happens during a loop, such as an interactive resizing, there will be one
|
|
|
|
/// line of garbage for every repaint, which will quickly fill the screen.
|
2020-06-08 05:53:42 +08:00
|
|
|
void s_reset(screen_t *s, int screen_width, screen_reset_mode_t mode);
|
2012-11-25 08:42:25 +08:00
|
|
|
|
2020-06-08 05:28:44 +08:00
|
|
|
/// Stat stdout and stderr and save result as the current timestamp.
|
|
|
|
void s_save_status(screen_t *s);
|
|
|
|
|
2018-10-07 04:32:08 +08:00
|
|
|
/// Issues an immediate clr_eos.
|
|
|
|
void screen_force_clear_to_end();
|
2014-01-16 10:21:38 +08:00
|
|
|
|
2018-02-05 07:03:50 +08:00
|
|
|
// Information about the layout of a prompt.
|
|
|
|
struct prompt_layout_t {
|
|
|
|
size_t line_count; // how many lines the prompt consumes
|
|
|
|
size_t max_line_width; // width of the longest line
|
|
|
|
size_t last_line_width; // width of the last line
|
|
|
|
};
|
|
|
|
|
2020-05-31 08:10:44 +08:00
|
|
|
// Maintain a mapping of escape sequences to their widths for fast lookup.
|
2018-02-05 07:03:50 +08:00
|
|
|
class layout_cache_t {
|
2017-02-04 11:20:21 +08:00
|
|
|
private:
|
2017-09-02 05:33:59 +08:00
|
|
|
// Cached escape sequences we've already detected in the prompt and similar strings, ordered
|
|
|
|
// lexicographically.
|
2019-03-15 02:15:50 +08:00
|
|
|
wcstring_list_t esc_cache_;
|
2018-02-05 07:03:50 +08:00
|
|
|
|
|
|
|
// LRU-list of prompts and their layouts.
|
|
|
|
// Use a list so we can promote to the front on a cache hit.
|
2020-05-31 08:10:44 +08:00
|
|
|
struct prompt_cache_entry_t {
|
|
|
|
wcstring text; // Original prompt string.
|
|
|
|
size_t max_line_width; // Max line width when computing layout (for truncation).
|
|
|
|
wcstring trunc_text; // Resulting truncated prompt string.
|
|
|
|
prompt_layout_t layout; // Resulting layout.
|
|
|
|
};
|
|
|
|
std::list<prompt_cache_entry_t> prompt_cache_;
|
2017-02-04 11:20:21 +08:00
|
|
|
|
|
|
|
public:
|
2020-05-31 08:10:44 +08:00
|
|
|
static constexpr size_t prompt_cache_max_size = 12;
|
2018-02-05 07:43:03 +08:00
|
|
|
|
2018-02-05 07:03:50 +08:00
|
|
|
/// \return the size of the escape code cache.
|
|
|
|
size_t esc_cache_size() const { return esc_cache_.size(); }
|
2017-09-02 05:33:59 +08:00
|
|
|
|
|
|
|
/// Insert the entry \p str in its sorted position, if it is not already present in the cache.
|
2018-02-05 07:03:50 +08:00
|
|
|
void add_escape_code(wcstring str) {
|
|
|
|
auto where = std::upper_bound(esc_cache_.begin(), esc_cache_.end(), str);
|
|
|
|
if (where == esc_cache_.begin() || where[-1] != str) {
|
|
|
|
esc_cache_.emplace(where, std::move(str));
|
2017-02-04 11:20:21 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-31 05:59:35 +08:00
|
|
|
/// \return the length of an escape code, accessing and perhaps populating the cache.
|
|
|
|
size_t escape_code_length(const wchar_t *code);
|
|
|
|
|
2017-09-02 05:33:59 +08:00
|
|
|
/// \return the length of a string that matches a prefix of \p entry.
|
2018-02-05 07:03:50 +08:00
|
|
|
size_t find_escape_code(const wchar_t *entry) const {
|
2017-09-02 05:33:59 +08:00
|
|
|
// Do a binary search and see if the escape code right before our entry is a prefix of our
|
|
|
|
// entry. Note this assumes that escape codes are prefix-free: no escape code is a prefix of
|
|
|
|
// another one. This seems like a safe assumption.
|
2018-02-05 07:03:50 +08:00
|
|
|
auto where = std::upper_bound(esc_cache_.begin(), esc_cache_.end(), entry);
|
2017-09-02 05:33:59 +08:00
|
|
|
// 'where' is now the first element that is greater than entry. Thus where-1 is the last
|
|
|
|
// element that is less than or equal to entry.
|
2018-02-05 07:03:50 +08:00
|
|
|
if (where != esc_cache_.begin()) {
|
2017-09-02 05:33:59 +08:00
|
|
|
const wcstring &candidate = where[-1];
|
|
|
|
if (string_prefixes_string(candidate.c_str(), entry)) return candidate.size();
|
2017-02-04 11:20:21 +08:00
|
|
|
}
|
2017-09-02 05:33:59 +08:00
|
|
|
return 0;
|
2017-02-04 11:20:21 +08:00
|
|
|
}
|
|
|
|
|
2020-06-08 03:56:14 +08:00
|
|
|
/// Computes a prompt layout for \p prompt_str, perhaps truncating it to \p max_line_width.
|
2020-05-31 08:10:44 +08:00
|
|
|
/// \return the layout, and optionally the truncated prompt itself, by reference.
|
2020-06-08 03:56:14 +08:00
|
|
|
prompt_layout_t calc_prompt_layout(const wcstring &prompt_str,
|
|
|
|
wcstring *out_trunc_prompt = nullptr,
|
|
|
|
size_t max_line_width = std::numeric_limits<size_t>::max());
|
2018-02-05 07:43:03 +08:00
|
|
|
|
2018-02-05 07:03:50 +08:00
|
|
|
void clear() {
|
|
|
|
esc_cache_.clear();
|
|
|
|
prompt_cache_.clear();
|
|
|
|
}
|
2017-02-04 11:20:21 +08:00
|
|
|
|
2020-05-31 05:59:35 +08:00
|
|
|
// Singleton that is exposed so that the cache can be invalidated when terminal related
|
|
|
|
// variables change by calling `cached_esc_sequences.clear()`.
|
|
|
|
static layout_cache_t shared;
|
|
|
|
|
|
|
|
layout_cache_t() = default;
|
|
|
|
layout_cache_t(const layout_cache_t &) = delete;
|
|
|
|
void operator=(const layout_cache_t &) = delete;
|
2020-05-31 08:10:44 +08:00
|
|
|
|
|
|
|
private:
|
|
|
|
// Add a cache entry.
|
|
|
|
void add_prompt_layout(prompt_cache_entry_t entry);
|
|
|
|
|
|
|
|
// Finds the layout for a prompt, promoting it to the front. Returns nullptr if not found.
|
|
|
|
// Note this points into our cache; do not modify the cache while the pointer lives.
|
|
|
|
const prompt_cache_entry_t *find_prompt_layout(
|
|
|
|
const wcstring &input, size_t max_line_width = std::numeric_limits<size_t>::max());
|
|
|
|
|
|
|
|
friend void test_layout_cache();
|
2020-05-31 05:59:35 +08:00
|
|
|
};
|
2017-02-04 11:20:21 +08:00
|
|
|
|
2006-10-02 00:02:58 +08:00
|
|
|
#endif
|