fish-shell/src/reader.h
ridiculousfish a32248277f Make commandline state thread safe
Today the reader exposes its internals directly, e.g. to the commandline
builtin. This is of course not thread safe. For example in concurrent
execution, running `commandline` twice in separate threads would cause a
race and likely a crash.

Fix this by factoring all the commandline state into a new type
'commandline_state_t'. Make it a singleton (there is only one command
line
after all) and protect it with a lock.

No user visible change here.
2021-07-21 11:51:46 -07:00

276 lines
11 KiB
C++

// Prototypes for functions for reading data from stdin and passing to the parser. If stdin is a
// keyboard, it supplies a killring, history, syntax highlighting, tab-completion and various other
// features.
#ifndef FISH_READER_H
#define FISH_READER_H
#include <stddef.h>
#include <string>
#include <vector>
#include "common.h"
#include "complete.h"
#include "highlight.h"
#include "parse_constants.h"
class environment_t;
class history_t;
class io_chain_t;
class operation_context_t;
class parser_t;
/// An edit action that can be undone.
struct edit_t {
/// When undoing the edit we use this to restore the previous cursor position.
size_t cursor_position_before_edit = 0;
/// The span of text that is replaced by this edit.
size_t offset, length;
/// The strings that are removed and added by this edit, respectively.
wcstring old, replacement;
/// edit_t is only for contiguous changes, so to restore a group of arbitrary changes to the
/// command line we need to have a group id as forcibly coalescing changes is not enough.
maybe_t<int> group_id;
explicit edit_t(size_t offset, size_t length, wcstring replacement)
: offset(offset), length(length), replacement(std::move(replacement)) {}
/// Used for testing.
bool operator==(const edit_t &other) const;
};
/// Modify a string according to the given edit.
/// Currently exposed for testing only.
void apply_edit(wcstring *target, const edit_t &edit);
/// The history of all edits to some command line.
struct undo_history_t {
/// The stack of edits that can be undone or redone atomically.
std::vector<edit_t> edits;
/// The position in the undo stack that corresponds to the current
/// state of the input line.
/// Invariants:
/// edits_applied - 1 is the index of the next edit to undo.
/// edits_applied is the index of the next edit to redo.
///
/// For example, if nothing was undone, edits_applied is edits.size().
/// If every single edit was undone, edits_applied is 0.
size_t edits_applied = 0;
/// Whether we allow the next edit to be grouped together with the
/// last one.
bool may_coalesce = false;
/// Whether to be more aggressive in coalescing edits. Ideally, it would be "force coalesce"
/// with guaranteed atomicity but as `edit_t` is strictly for contiguous changes, that guarantee
/// can't be made at this time.
bool try_coalesce = false;
/// Empty the history.
void clear();
};
/// Helper class for storing a command line.
class editable_line_t {
/// The command line.
wcstring text_;
/// The current position of the cursor in the command line.
size_t position_ = 0;
/// The nesting level for atomic edits, so that recursive invocations of start_edit_group()
/// are not ended by one end_edit_group() call.
int32_t edit_group_level_ = -1;
/// Monotonically increasing edit group, ignored when edit_group_level_ is -1. Allowed to wrap.
uint32_t edit_group_id_ = -1;
public:
undo_history_t undo_history;
const wcstring &text() const { return text_; }
/// Set the text directly without maintaining undo invariants. Use with caution.
void set_text_bypassing_undo_history(wcstring &&text) { text_ = text; }
size_t position() const { return position_; }
void set_position(size_t position) { position_ = position; }
// Gets the length of the text.
size_t size() const { return text().size(); }
bool empty() const { return text().empty(); }
wchar_t at(size_t idx) const { return text().at(idx); }
void clear() {
undo_history.clear();
if (empty()) return;
set_text_bypassing_undo_history(L"");
set_position(0);
}
/// Modify the commandline according to @edit. Most modifications to the
/// text should pass through this function.
void push_edit(edit_t &&edit);
/// Modify the commandline by inserting a string at the cursor.
/// Does not create a new undo point, but adds to the last edit which
/// must be an insertion, too.
void insert_coalesce(const wcstring &str);
/// Undo the most recent edit that was not yet undone. Returns true on success.
bool undo();
/// Redo the most recent undo. Returns true on success.
bool redo();
/// Start a logical grouping of command line edits that should be undone/redone together.
void begin_edit_group();
/// End a logical grouping of command line edits that should be undone/redone together.
void end_edit_group();
};
/// Read commands from \c fd until encountering EOF.
/// The fd is not closed.
int reader_read(parser_t &parser, int fd, const io_chain_t &io);
/// Mark that we encountered SIGHUP and must (soon) exit. This is invoked from a signal handler.
void reader_sighup();
/// Initialize the reader.
void reader_init();
void term_copy_modes();
/// Restore the term mode at startup.
void restore_term_mode();
/// Change the history file for the current command reading context.
void reader_change_history(const wcstring &name);
/// Write the title to the titlebar. This function is called just before a new application starts
/// executing and just after it finishes.
///
/// \param cmd Command line string passed to \c fish_title if is defined.
/// \param parser The parser to use for autoloading fish_title.
/// \param reset_cursor_position If set, issue a \r so the line driver knows where we are
void reader_write_title(const wcstring &cmd, parser_t &parser, bool reset_cursor_position = true);
/// Tell the reader that it needs to re-exec the prompt and repaint.
/// This may be called in response to e.g. a color variable change.
void reader_schedule_prompt_repaint();
/// Enqueue an event to the back of the reader's input queue.
class char_event_t;
void reader_queue_ch(const char_event_t &ch);
/// Return the value of the interrupted flag, which is set by the sigint handler, and clear it if it
/// was set. In practice this will return 0 or SIGINT.
int reader_test_and_clear_interrupted();
/// Clear the interrupted flag unconditionally without handling anything. The flag could have been
/// set e.g. when an interrupt arrived just as we were ending an earlier \c reader_readline
/// invocation but before the \c is_interactive_read flag was cleared.
void reader_reset_interrupted();
/// Return the value of the interrupted flag, which is set by the sigint handler, and clear it if it
/// was set. If the current reader is interruptible, call \c reader_exit().
int reader_reading_interrupted();
/// Read one line of input. Before calling this function, reader_push() must have been called in
/// order to set up a valid reader environment. If nchars > 0, return after reading that many
/// characters even if a full line has not yet been read. Note: the returned value may be longer
/// than nchars if a single keypress resulted in multiple characters being inserted into the
/// commandline.
maybe_t<wcstring> reader_readline(int nchars);
/// Configuration that we provide to a reader.
struct reader_config_t {
/// Left prompt command, typically fish_prompt.
wcstring left_prompt_cmd{};
/// Right prompt command, typically fish_right_prompt.
wcstring right_prompt_cmd{};
/// Whether tab completion is OK.
bool complete_ok{false};
/// Whether to perform syntax highlighting.
bool highlight_ok{false};
/// Whether to perform syntax checking before returning.
bool syntax_check_ok{false};
/// Whether to allow autosuggestions.
bool autosuggest_ok{false};
/// Whether to expand abbreviations.
bool expand_abbrev_ok{false};
/// Whether to exit on interrupt (^C).
bool exit_on_interrupt{false};
/// If set, do not show what is typed.
bool in_silent_mode{false};
/// The fd for stdin, default to actual stdin.
int in{0};
};
/// Push a new reader environment controlled by \p conf, using the given history name.
/// If \p history_name is empty, then save history in-memory only; do not write it to disk.
void reader_push(parser_t &parser, const wcstring &history_name, reader_config_t &&conf);
/// Return to previous reader environment.
void reader_pop();
/// The readers interrupt signal handler. Cancels all currently running blocks.
void reader_handle_sigint();
/// \return whether we should cancel fish script due to fish itself receiving a signal.
/// TODO: this doesn't belong in reader.
bool check_cancel_from_fish_signal();
/// Given a command line and an autosuggestion, return the string that gets shown to the user.
/// Exposed for testing purposes only.
wcstring combine_command_and_autosuggestion(const wcstring &cmdline,
const wcstring &autosuggestion);
/// Expand abbreviations at the given cursor position. Exposed for testing purposes only.
/// \return none if no abbreviations were expanded, otherwise the new command line.
maybe_t<edit_t> reader_expand_abbreviation_in_command(const wcstring &cmdline, size_t cursor_pos,
const environment_t &vars);
/// Apply a completion string. Exposed for testing only.
wcstring completion_apply_to_command_line(const wcstring &val_str, complete_flags_t flags,
const wcstring &command_line, size_t *inout_cursor_pos,
bool append_only);
/// Snapshotted state from the reader.
struct commandline_state_t {
wcstring text; // command line text, or empty if not interactive
size_t cursor_pos{0}; // position of the cursor, may be as large as text.size()
maybe_t<source_range_t> selection{}; // visual selection, or none if none
std::shared_ptr<history_t> history{}; // current reader history, or null if not interactive
bool pager_mode{false}; // pager is visible
bool search_mode{false}; // pager is visible and search is active
bool initialized{false}; // if false, the reader has not yet been entered
};
/// Get the command line state. This may be fetched on a background thread.
commandline_state_t commandline_get_state();
/// Set the command line text and position. This may be called on a background thread; the reader
/// will pick it up when it is done executing.
void commandline_set_buffer(wcstring text, size_t cursor_pos = -1);
/// Return the current interactive reads loop count. Useful for determining how many commands have
/// been executed between invocations of code.
uint64_t reader_run_count();
/// Returns the current "generation" of interactive status. Useful for determining whether the
/// previous command produced a status.
uint64_t reader_status_count();
#endif