aboutsummaryrefslogtreecommitdiffhomepage
path: root/reader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'reader.cpp')
-rw-r--r--reader.cpp1694
1 files changed, 976 insertions, 718 deletions
diff --git a/reader.cpp b/reader.cpp
index 228fa918..f9299a3c 100644
--- a/reader.cpp
+++ b/reader.cpp
@@ -45,6 +45,8 @@ commence.
#if HAVE_NCURSES_H
#include <ncurses.h>
+#elif HAVE_NCURSES_CURSES_H
+#include <ncurses/curses.h>
#else
#include <curses.h>
#endif
@@ -99,6 +101,8 @@ commence.
#include "path.h"
#include "parse_util.h"
#include "parser_keywords.h"
+#include "parse_tree.h"
+#include "pager.h"
/**
Maximum length of prefix string when printing completion
@@ -179,10 +183,17 @@ static volatile unsigned int s_generation_count;
/* This pthreads generation count is set when an autosuggestion background thread starts up, so it can easily check if the work it is doing is no longer useful. */
static pthread_key_t generation_count_key;
-/* A color is an int */
-typedef int color_t;
+static void set_command_line_and_position(editable_line_t *el, const wcstring &new_str, size_t pos);
-static void set_command_line_and_position(const wcstring &new_str, size_t pos);
+void editable_line_t::insert_string(const wcstring &str, size_t start, size_t len)
+{
+ // Clamp the range to something valid
+ size_t string_length = str.size();
+ start = mini(start, string_length);
+ len = mini(len, string_length - start);
+ this->text.insert(this->position, str, start, len);
+ this->position += len;
+}
/**
A struct describing the state of the interactive reader. These
@@ -194,11 +205,17 @@ class reader_data_t
public:
/** String containing the whole current commandline */
- wcstring command_line;
+ editable_line_t command_line;
/** String containing the autosuggestion */
wcstring autosuggestion;
+ /** Current pager */
+ pager_t pager;
+
+ /** Current page rendering */
+ page_rendering_t current_page_rendering;
+
/** Whether autosuggesting is allowed at all */
bool allow_autosuggestion;
@@ -240,20 +257,44 @@ public:
/** The current position in search_prev */
size_t search_pos;
- /** Length of the command */
- size_t command_length() const
+ bool is_navigating_pager_contents() const
{
- return command_line.size();
+ return this->pager.is_navigating_contents();
+ }
+
+ /* The line that is currently being edited. Typically the command line, but may be the search field */
+ editable_line_t *active_edit_line()
+ {
+ if (this->is_navigating_pager_contents() && this->pager.is_search_field_shown())
+ {
+ return &this->pager.search_field_line;
+ }
+ else
+ {
+ return &this->command_line;
+ }
}
/** Do what we need to do whenever our command line changes */
- void command_line_changed(void);
+ void command_line_changed(const editable_line_t *el);
+
+ /** Do what we need to do whenever our pager selection */
+ void pager_selection_changed();
/** Expand abbreviations at the current cursor position, minus backtrack_amt. */
bool expand_abbreviation_as_necessary(size_t cursor_backtrack);
- /** The current position of the cursor in buff. */
- size_t buff_pos;
+ /** Indicates whether a selection is currently active */
+ bool sel_active;
+
+ /** The position of the cursor, when selection was initiated. */
+ size_t sel_begin_pos;
+
+ /** The start position of the current selection, if one. */
+ size_t sel_start_pos;
+
+ /** The stop position of the current selection, if one. */
+ size_t sel_stop_pos;
/** Name of the current application */
wcstring app_name;
@@ -268,12 +309,16 @@ public:
/** The output of the last evaluation of the right prompt command */
wcstring right_prompt_buff;
+ /* Completion support */
+ wcstring cycle_command_line;
+ size_t cycle_cursor_pos;
+
/**
Color is the syntax highlighting for buff. The format is that
color[i] is the classification (according to the enum in
highlight.h) of buff[i].
*/
- std::vector<color_t> colors;
+ std::vector<highlight_spec_t> colors;
/** An array defining the block level at each character. */
std::vector<int> indents;
@@ -338,7 +383,11 @@ public:
history(0),
token_history_pos(0),
search_pos(0),
- buff_pos(0),
+ sel_active(0),
+ sel_begin_pos(0),
+ sel_start_pos(0),
+ sel_stop_pos(0),
+ cycle_cursor_pos(0),
complete_func(0),
highlight_function(0),
test_func(0),
@@ -353,6 +402,12 @@ public:
}
};
+/* Sets the command line contents, without clearing the pager */
+static void reader_set_buffer_maintaining_pager(const wcstring &b, size_t pos);
+
+/* Clears the pager */
+static void clear_pager();
+
/**
The current interactive reading context
*/
@@ -399,7 +454,7 @@ static struct termios terminal_mode_on_startup;
static struct termios terminal_mode_for_executing_programs;
-static void reader_super_highlight_me_plenty(size_t pos);
+static void reader_super_highlight_me_plenty(int highlight_pos_adjust = 0, bool no_io = false);
/**
Variable to keep track of forced exits - see \c reader_exit_forced();
@@ -432,6 +487,29 @@ static void term_donate()
}
+
+/**
+ Update the cursor position
+*/
+static void update_buff_pos(editable_line_t *el, size_t buff_pos)
+{
+ el->position = buff_pos;
+ if (el == &data->command_line && data->sel_active)
+ {
+ if (data->sel_begin_pos <= buff_pos)
+ {
+ data->sel_start_pos = data->sel_begin_pos;
+ data->sel_stop_pos = buff_pos;
+ }
+ else
+ {
+ data->sel_start_pos = buff_pos;
+ data->sel_stop_pos = data->sel_begin_pos;
+ }
+ }
+}
+
+
/**
Grab control of terminal
*/
@@ -514,50 +592,62 @@ wcstring combine_command_and_autosuggestion(const wcstring &cmdline, const wcstr
commandline, write the prompt, perform syntax highlighting, write
the commandline and move the cursor.
*/
-
static void reader_repaint()
{
+ editable_line_t *cmd_line = &data->command_line;
// Update the indentation
- parser_t::principal_parser().test(data->command_line.c_str(), &data->indents[0]);
+ data->indents = parse_util_compute_indents(cmd_line->text);
// Combine the command and autosuggestion into one string
- wcstring full_line = combine_command_and_autosuggestion(data->command_line, data->autosuggestion);
+ wcstring full_line = combine_command_and_autosuggestion(cmd_line->text, data->autosuggestion);
size_t len = full_line.size();
if (len < 1)
len = 1;
- std::vector<color_t> colors = data->colors;
- colors.resize(len, HIGHLIGHT_AUTOSUGGESTION);
+ std::vector<highlight_spec_t> colors = data->colors;
+ colors.resize(len, highlight_spec_autosuggestion);
+
+ if (data->sel_active)
+ {
+ highlight_spec_t selection_color = highlight_make_background(highlight_spec_selection);
+ for (size_t i = data->sel_start_pos; i <= std::min(len - 1, data->sel_stop_pos); i++)
+ {
+ colors[i] = selection_color;
+ }
+ }
std::vector<int> indents = data->indents;
indents.resize(len);
+ // Re-render our completions page if necessary
+ // We set the term size to 1 less than the true term height. This means we will always show the (bottom) line of the prompt.
+ data->pager.set_term_size(maxi(1, common_get_width()), maxi(1, common_get_height() - 1));
+ data->pager.update_rendering(&data->current_page_rendering);
+
+ bool focused_on_pager = data->active_edit_line() == &data->pager.search_field_line;
+ size_t cursor_position = focused_on_pager ? data->pager.cursor_position() : cmd_line->position;
+
s_write(&data->screen,
data->left_prompt_buff,
data->right_prompt_buff,
full_line,
- data->command_length(),
+ cmd_line->size(),
&colors[0],
&indents[0],
- data->buff_pos);
+ cursor_position,
+ data->sel_start_pos,
+ data->sel_stop_pos,
+ data->current_page_rendering,
+ focused_on_pager);
data->repaint_needed = false;
}
-static void reader_repaint_without_autosuggestion()
-{
- // Swap in an empty autosuggestion, repaint, then swap it out
- wcstring saved_autosuggestion;
- data->autosuggestion.swap(saved_autosuggestion);
- reader_repaint();
- data->autosuggestion.swap(saved_autosuggestion);
-}
-
/** Internal helper function for handling killing parts of text. */
-static void reader_kill(size_t begin_idx, size_t length, int mode, int newv)
+static void reader_kill(editable_line_t *el, size_t begin_idx, size_t length, int mode, int newv)
{
- const wchar_t *begin = data->command_line.c_str() + begin_idx;
+ const wchar_t *begin = el->text.c_str() + begin_idx;
if (newv)
{
data->kill_item = wcstring(begin, length);
@@ -580,19 +670,18 @@ static void reader_kill(size_t begin_idx, size_t length, int mode, int newv)
kill_replace(old, data->kill_item);
}
- if (data->buff_pos > begin_idx)
+ if (el->position > begin_idx)
{
/* Move the buff position back by the number of characters we deleted, but don't go past buff_pos */
- size_t backtrack = mini(data->buff_pos - begin_idx, length);
- data->buff_pos -= backtrack;
+ size_t backtrack = mini(el->position - begin_idx, length);
+ update_buff_pos(el, el->position - backtrack);
}
- data->command_line.erase(begin_idx, length);
- data->command_line_changed();
+ el->text.erase(begin_idx, length);
+ data->command_line_changed(el);
- reader_super_highlight_me_plenty(data->buff_pos);
+ reader_super_highlight_me_plenty();
reader_repaint();
-
}
@@ -630,19 +719,54 @@ void reader_pop_current_filename()
/** Make sure buffers are large enough to hold the current string length */
-void reader_data_t::command_line_changed()
+void reader_data_t::command_line_changed(const editable_line_t *el)
{
ASSERT_IS_MAIN_THREAD();
- size_t len = command_length();
+ if (el == &this->command_line)
+ {
+ size_t len = this->command_line.size();
- /* When we grow colors, propagate the last color (if any), under the assumption that usually it will be correct. If it is, it avoids a repaint. */
- color_t last_color = colors.empty() ? color_t() : colors.back();
- colors.resize(len, last_color);
+ /* When we grow colors, propagate the last color (if any), under the assumption that usually it will be correct. If it is, it avoids a repaint. */
+ highlight_spec_t last_color = colors.empty() ? highlight_spec_t() : colors.back();
+ colors.resize(len, last_color);
- indents.resize(len);
+ indents.resize(len);
- /* Update the gen count */
- s_generation_count++;
+ /* Update the gen count */
+ s_generation_count++;
+ }
+ else if (el == &this->pager.search_field_line)
+ {
+ this->pager.refilter_completions();
+ this->pager_selection_changed();
+ }
+}
+
+void reader_data_t::pager_selection_changed()
+{
+ ASSERT_IS_MAIN_THREAD();
+
+ const completion_t *completion = this->pager.selected_completion(this->current_page_rendering);
+
+ /* Update the cursor and command line */
+ size_t cursor_pos = this->cycle_cursor_pos;
+ wcstring new_cmd_line;
+
+ if (completion == NULL)
+ {
+ new_cmd_line = this->cycle_command_line;
+ }
+ else
+ {
+ new_cmd_line = completion_apply_to_command_line(completion->completion, completion->flags, this->cycle_command_line, &cursor_pos, false);
+ }
+ reader_set_buffer_maintaining_pager(new_cmd_line, cursor_pos);
+
+ /* Since we just inserted a completion, don't immediately do a new autosuggestion */
+ this->suppress_autosuggestion = true;
+
+ /* Trigger repaint (see #765) */
+ reader_repaint_needed();
}
/* Expand abbreviations at the given cursor position. Does NOT inspect 'data'. */
@@ -659,117 +783,55 @@ bool reader_expand_abbreviation_in_command(const wcstring &cmdline, size_t curso
const size_t subcmd_offset = cmdsub_begin - buff;
const wcstring subcmd = wcstring(cmdsub_begin, cmdsub_end - cmdsub_begin);
- const wchar_t *subcmd_cstr = subcmd.c_str();
+ const size_t subcmd_cursor_pos = cursor_pos - subcmd_offset;
- /* Get the token containing the cursor */
- const wchar_t *subcmd_tok_begin = NULL, *subcmd_tok_end = NULL;
- assert(cursor_pos >= subcmd_offset);
- size_t subcmd_cursor_pos = cursor_pos - subcmd_offset;
- parse_util_token_extent(subcmd_cstr, subcmd_cursor_pos, &subcmd_tok_begin, &subcmd_tok_end, NULL, NULL);
+ /* Parse this subcmd */
+ parse_node_tree_t parse_tree;
+ parse_tree_from_string(subcmd, parse_flag_continue_after_error | parse_flag_accept_incomplete_tokens, &parse_tree, NULL);
- /* Compute the offset of the token before the cursor within the subcmd */
- assert(subcmd_tok_begin >= subcmd_cstr);
- assert(subcmd_tok_end >= subcmd_tok_begin);
- const size_t subcmd_tok_begin_offset = subcmd_tok_begin - subcmd_cstr;
- const size_t subcmd_tok_length = subcmd_tok_end - subcmd_tok_begin;
-
- /* Now parse the subcmd, looking for commands */
- bool had_cmd = false, previous_token_is_cmd = false;
- tokenizer_t tok(subcmd_cstr, TOK_ACCEPT_UNFINISHED | TOK_SQUASH_ERRORS);
- for (; tok_has_next(&tok); tok_next(&tok))
+ /* Look for plain statements where the cursor is at the end of the command */
+ const parse_node_t *matching_cmd_node = NULL;
+ const size_t len = parse_tree.size();
+ for (size_t i=0; i < len; i++)
{
- size_t tok_pos = static_cast<size_t>(tok_get_pos(&tok));
- if (tok_pos > subcmd_tok_begin_offset)
- {
- /* We've passed the token we're interested in */
- break;
- }
+ const parse_node_t &node = parse_tree.at(i);
- int last_type = tok_last_type(&tok);
+ /* Only interested in plain statements with source */
+ if (node.type != symbol_plain_statement || ! node.has_source())
+ continue;
- switch (last_type)
- {
- case TOK_STRING:
- {
- if (had_cmd)
- {
- /* Parameter to the command. */
- }
- else
- {
- const wcstring potential_cmd = tok_last(&tok);
- if (parser_keywords_is_subcommand(potential_cmd))
- {
- if (potential_cmd == L"command" || potential_cmd == L"builtin")
- {
- /* 'command' and 'builtin' defeat abbreviation expansion. Skip this command. */
- had_cmd = true;
- }
- else
- {
- /* Other subcommand. Pretend it doesn't exist so that we can expand the following command */
- had_cmd = false;
- }
- }
- else
- {
- /* It's a normal command */
- had_cmd = true;
- if (tok_pos == subcmd_tok_begin_offset)
- {
- /* This is the token we care about! */
- previous_token_is_cmd = true;
- }
- }
- }
- break;
- }
+ /* Skip decorated statements */
+ if (parse_tree.decoration_for_plain_statement(node) != parse_statement_decoration_none)
+ continue;
- case TOK_REDIRECT_NOCLOB:
- case TOK_REDIRECT_OUT:
- case TOK_REDIRECT_IN:
- case TOK_REDIRECT_APPEND:
- case TOK_REDIRECT_FD:
- {
- if (!had_cmd)
- {
- break;
- }
- tok_next(&tok);
- break;
- }
+ /* Get the command node. Skip it if we can't or it has no source */
+ const parse_node_t *cmd_node = parse_tree.get_child(node, 0, parse_token_type_string);
+ if (cmd_node == NULL || ! cmd_node->has_source())
+ continue;
- case TOK_PIPE:
- case TOK_BACKGROUND:
- case TOK_END:
- {
- had_cmd = false;
- break;
- }
-
- case TOK_COMMENT:
- case TOK_ERROR:
- default:
- {
- break;
- }
+ /* Now see if its source range contains our cursor, including at the end */
+ if (subcmd_cursor_pos >= cmd_node->source_start && subcmd_cursor_pos <= cmd_node->source_start + cmd_node->source_length)
+ {
+ /* Success! */
+ matching_cmd_node = cmd_node;
+ break;
}
}
+ /* Now if we found a command node, expand it */
bool result = false;
- if (previous_token_is_cmd)
+ if (matching_cmd_node != NULL)
{
- /* The token is a command. Try expanding it as an abbreviation. */
- const wcstring token = wcstring(subcmd, subcmd_tok_begin_offset, subcmd_tok_length);
+ assert(matching_cmd_node->type == parse_token_type_string);
+ const wcstring token = matching_cmd_node->get_source(subcmd);
wcstring abbreviation;
if (expand_abbreviation(token, &abbreviation))
{
/* There was an abbreviation! Replace the token in the full command. Maintain the relative position of the cursor. */
if (output != NULL)
{
- size_t cmd_tok_begin_offset = subcmd_tok_begin_offset + subcmd_offset;
output->assign(cmdline);
- output->replace(cmd_tok_begin_offset, subcmd_tok_length, abbreviation);
+ output->replace(subcmd_offset + matching_cmd_node->source_start, matching_cmd_node->source_length, abbreviation);
}
result = true;
}
@@ -781,19 +843,21 @@ bool reader_expand_abbreviation_in_command(const wcstring &cmdline, size_t curso
bool reader_data_t::expand_abbreviation_as_necessary(size_t cursor_backtrack)
{
bool result = false;
- if (this->expand_abbreviations)
+ editable_line_t *el = data->active_edit_line();
+ if (this->expand_abbreviations && el == &data->command_line)
{
/* Try expanding abbreviations */
wcstring new_cmdline;
- size_t cursor_pos = this->buff_pos - mini(this->buff_pos, cursor_backtrack);
- if (reader_expand_abbreviation_in_command(this->command_line, cursor_pos, &new_cmdline))
+ size_t cursor_pos = el->position - mini(el->position, cursor_backtrack);
+ if (reader_expand_abbreviation_in_command(el->text, cursor_pos, &new_cmdline))
{
/* We expanded an abbreviation! The cursor moves by the difference in the command line lengths. */
- size_t new_buff_pos = this->buff_pos + new_cmdline.size() - this->command_line.size();
+ size_t new_buff_pos = el->position + new_cmdline.size() - el->text.size();
- this->command_line.swap(new_cmdline);
- data->buff_pos = new_buff_pos;
- data->command_line_changed();
+
+ el->text.swap(new_cmdline);
+ update_buff_pos(el, new_buff_pos);
+ data->command_line_changed(el);
result = true;
}
}
@@ -843,9 +907,8 @@ bool reader_thread_job_is_stale()
return (void*)(uintptr_t) s_generation_count != pthread_getspecific(generation_count_key);
}
-void reader_write_title()
+void reader_write_title(const wcstring &cmd)
{
- const wchar_t *title;
const env_var_t term_str = env_get_string(L"TERM");
/*
@@ -875,27 +938,33 @@ void reader_write_title()
{
char *n = ttyname(STDIN_FILENO);
-
if (contains(term, L"linux"))
{
return;
}
- if (strstr(n, "tty") || strstr(n, "/vc/"))
+ if (contains(term, L"dumb"))
return;
-
+ if (strstr(n, "tty") || strstr(n, "/vc/"))
+ return;
}
- title = function_exists(L"fish_title")?L"fish_title":DEFAULT_TITLE;
-
- if (wcslen(title) ==0)
- return;
+ wcstring fish_title_command = DEFAULT_TITLE;
+ if (function_exists(L"fish_title"))
+ {
+ fish_title_command = L"fish_title";
+ if (! cmd.empty())
+ {
+ fish_title_command.append(L" ");
+ fish_title_command.append(parse_util_escape_string_with_quote(cmd, L'\0'));
+ }
+ }
wcstring_list_t lst;
proc_push_interactive(0);
- if (exec_subshell(title, lst, false /* do not apply exit status */) != -1)
+ if (exec_subshell(fish_title_command, lst, false /* do not apply exit status */) != -1)
{
if (! lst.empty())
{
@@ -956,7 +1025,7 @@ static void exec_prompt()
}
/* Write the screen title */
- reader_write_title();
+ reader_write_title(L"");
}
void reader_init()
@@ -984,19 +1053,27 @@ void reader_init()
// PCA disable VDSUSP (typically control-Y), which is a funny job control
// function available only on OS X and BSD systems
// This lets us use control-Y for yank instead
- #ifdef VDSUSP
+#ifdef VDSUSP
shell_modes.c_cc[VDSUSP] = _POSIX_VDISABLE;
- #endif
+#endif
#endif
}
void reader_destroy()
{
- tcsetattr(0, TCSANOW, &terminal_mode_on_startup);
pthread_key_delete(generation_count_key);
}
+void restore_term_mode()
+{
+ // Restore the term mode if we own the terminal
+ // It's important we do this before restore_foreground_process_group, otherwise we won't think we own the terminal
+ if (getpid() == tcgetpgrp(STDIN_FILENO))
+ {
+ tcsetattr(STDIN_FILENO, TCSANOW, &terminal_mode_on_startup);
+ }
+}
void reader_exit(int do_exit, int forced)
{
@@ -1057,29 +1134,92 @@ void reader_react_to_color_change()
}
+/* Indicates if the given command char ends paging */
+static bool command_ends_paging(wchar_t c, bool focused_on_search_field)
+{
+ switch (c)
+ {
+ /* These commands always end paging */
+ case R_HISTORY_SEARCH_BACKWARD:
+ case R_HISTORY_SEARCH_FORWARD:
+ case R_BEGINNING_OF_HISTORY:
+ case R_END_OF_HISTORY:
+ case R_HISTORY_TOKEN_SEARCH_BACKWARD:
+ case R_HISTORY_TOKEN_SEARCH_FORWARD:
+ case R_ACCEPT_AUTOSUGGESTION:
+ case R_CANCEL:
+ return true;
+
+ /* These commands never do */
+ case R_COMPLETE:
+ case R_COMPLETE_AND_SEARCH:
+ case R_BACKWARD_CHAR:
+ case R_FORWARD_CHAR:
+ case R_UP_LINE:
+ case R_DOWN_LINE:
+ case R_NULL:
+ case R_REPAINT:
+ case R_SUPPRESS_AUTOSUGGESTION:
+ default:
+ return false;
+
+ /* R_EXECUTE does end paging, but only executes if it was not paging. So it's handled specially */
+ case R_EXECUTE:
+ return false;
+
+ /* These commands operate on the search field if that's where the focus is */
+ case R_BEGINNING_OF_LINE:
+ case R_END_OF_LINE:
+ case R_FORWARD_WORD:
+ case R_BACKWARD_WORD:
+ case R_DELETE_CHAR:
+ case R_BACKWARD_DELETE_CHAR:
+ case R_KILL_LINE:
+ case R_YANK:
+ case R_YANK_POP:
+ case R_BACKWARD_KILL_LINE:
+ case R_KILL_WHOLE_LINE:
+ case R_KILL_WORD:
+ case R_BACKWARD_KILL_WORD:
+ case R_BACKWARD_KILL_PATH_COMPONENT:
+ case R_SELF_INSERT:
+ case R_TRANSPOSE_CHARS:
+ case R_TRANSPOSE_WORDS:
+ case R_UPCASE_WORD:
+ case R_DOWNCASE_WORD:
+ case R_CAPITALIZE_WORD:
+ case R_VI_ARG_DIGIT:
+ case R_VI_DELETE_TO:
+ case R_BEGINNING_OF_BUFFER:
+ case R_END_OF_BUFFER:
+ return ! focused_on_search_field;
+ }
+}
+
/**
Remove the previous character in the character buffer and on the
screen using syntax highlighting, etc.
*/
static void remove_backward()
{
+ editable_line_t *el = data->active_edit_line();
- if (data->buff_pos <= 0)
+ if (el->position <= 0)
return;
/* Fake composed character sequences by continuing to delete until we delete a character of width at least 1. */
int width;
do
{
- data->buff_pos -= 1;
- width = fish_wcwidth(data->command_line.at(data->buff_pos));
- data->command_line.erase(data->buff_pos, 1);
+ update_buff_pos(el, el->position - 1);
+ width = fish_wcwidth(el->text.at(el->position));
+ el->text.erase(el->position, 1);
}
- while (width == 0 && data->buff_pos > 0);
- data->command_line_changed();
+ while (width == 0 && el->position > 0);
+ data->command_line_changed(el);
data->suppress_autosuggestion = true;
- reader_super_highlight_me_plenty(data->buff_pos);
+ reader_super_highlight_me_plenty();
reader_repaint();
@@ -1089,26 +1229,51 @@ static void remove_backward()
/**
Insert the characters of the string into the command line buffer
and print them to the screen using syntax highlighting, etc.
- Optionally also expand abbreviations.
+ Optionally also expand abbreviations, after space characters.
Returns true if the string changed.
*/
-static bool insert_string(const wcstring &str, bool should_expand_abbreviations = false)
+static bool insert_string(editable_line_t *el, const wcstring &str, bool allow_expand_abbreviations = false)
{
size_t len = str.size();
if (len == 0)
return false;
-
- data->command_line.insert(data->buff_pos, str);
- data->buff_pos += len;
- data->command_line_changed();
- data->suppress_autosuggestion = false;
-
- if (should_expand_abbreviations)
- data->expand_abbreviation_as_necessary(1);
-
- /* Syntax highlight. Note we must have that buff_pos > 0 because we just added something nonzero to its length */
- assert(data->buff_pos > 0);
- reader_super_highlight_me_plenty(data->buff_pos-1);
+
+ /* Start inserting. If we are expanding abbreviations, we have to do this after every space (see #1434), so look for spaces. We try to do this efficiently (rather than the simpler character at a time) to avoid expensive work in command_line_changed() */
+ size_t cursor = 0;
+ while (cursor < len)
+ {
+ /* Determine the position of the next expansion-triggering char (possibly none), and the end of the range we wish to insert */
+ const wchar_t *expansion_triggering_chars = L" ;|&^><";
+ size_t char_triggering_expansion_pos = allow_expand_abbreviations ? str.find_first_of(expansion_triggering_chars, cursor) : wcstring::npos;
+ bool has_expansion_triggering_char = (char_triggering_expansion_pos != wcstring::npos);
+ size_t range_end = (has_expansion_triggering_char ? char_triggering_expansion_pos + 1 : len);
+
+ /* Insert from the cursor up to but not including the range end */
+ assert(range_end > cursor);
+ el->insert_string(str, cursor, range_end - cursor);
+
+ update_buff_pos(el, el->position);
+ data->command_line_changed(el);
+
+ /* If we got an expansion trigger, then the last character we inserted was it (i.e. was a space). Expand abbreviations. */
+ if (has_expansion_triggering_char && allow_expand_abbreviations)
+ {
+ assert(range_end > 0);
+ assert(wcschr(expansion_triggering_chars, str.at(range_end - 1)));
+ data->expand_abbreviation_as_necessary(1);
+ }
+ cursor = range_end;
+ }
+
+ if (el == &data->command_line)
+ {
+ data->suppress_autosuggestion = false;
+
+ /* Syntax highlight. Note we must have that buff_pos > 0 because we just added something nonzero to its length */
+ assert(el->position > 0);
+ reader_super_highlight_me_plenty(-1);
+ }
+
reader_repaint();
return true;
@@ -1118,9 +1283,9 @@ static bool insert_string(const wcstring &str, bool should_expand_abbreviations
Insert the character into the command line buffer and print it to
the screen using syntax highlighting, etc.
*/
-static bool insert_char(wchar_t c, bool should_expand_abbreviations = false)
+static bool insert_char(editable_line_t *el, wchar_t c, bool allow_expand_abbreviations = false)
{
- return insert_string(wcstring(1, c), should_expand_abbreviations);
+ return insert_string(el, wcstring(1, c), allow_expand_abbreviations);
}
@@ -1149,7 +1314,6 @@ wcstring completion_apply_to_command_line(const wcstring &val_str, complete_flag
{
size_t move_cursor;
const wchar_t *begin, *end;
- wchar_t *escaped;
const wchar_t *buff = command_line.c_str();
parse_util_token_extent(buff, cursor_pos, &begin, 0, 0, 0);
@@ -1161,10 +1325,9 @@ wcstring completion_apply_to_command_line(const wcstring &val_str, complete_flag
{
/* Respect COMPLETE_DONT_ESCAPE_TILDES */
bool no_tilde = !!(flags & COMPLETE_DONT_ESCAPE_TILDES);
- escaped = escape(val, ESCAPE_ALL | ESCAPE_NO_QUOTED | (no_tilde ? ESCAPE_NO_TILDE : 0));
+ wcstring escaped = escape(val, ESCAPE_ALL | ESCAPE_NO_QUOTED | (no_tilde ? ESCAPE_NO_TILDE : 0));
sb.append(escaped);
- move_cursor = wcslen(escaped);
- free(escaped);
+ move_cursor = escaped.size();
}
else
{
@@ -1249,150 +1412,15 @@ wcstring completion_apply_to_command_line(const wcstring &val_str, complete_flag
*/
static void completion_insert(const wchar_t *val, complete_flags_t flags)
{
- size_t cursor = data->buff_pos;
- wcstring new_command_line = completion_apply_to_command_line(val, flags, data->command_line, &cursor, false /* not append only */);
- reader_set_buffer(new_command_line, cursor);
+ editable_line_t *el = data->active_edit_line();
+ size_t cursor = el->position;
+ wcstring new_command_line = completion_apply_to_command_line(val, flags, el->text, &cursor, false /* not append only */);
+ reader_set_buffer_maintaining_pager(new_command_line, cursor);
/* Since we just inserted a completion, don't immediately do a new autosuggestion */
data->suppress_autosuggestion = true;
}
-/* Return an escaped path to fish_pager */
-static wcstring escaped_fish_pager_path(void)
-{
- wcstring result;
- const env_var_t bin_dir = env_get_string(L"__fish_bin_dir");
- if (bin_dir.missing_or_empty())
- {
- /* This isn't good, hope our normal command stuff can find it */
- result = L"fish_pager";
- }
- else
- {
- result = escape_string(bin_dir + L"/fish_pager", ESCAPE_ALL);
- }
- return result;
-}
-
-/**
- Run the fish_pager command to display the completion list. If the
- fish_pager outputs any text, it is inserted into the input
- backbuffer.
-
- \param prefix the string to display before every completion.
- \param is_quoted should be set if the argument is quoted. This will change the display style.
- \param comp the list of completions to display
-*/
-static void run_pager(const wcstring &prefix, int is_quoted, const std::vector<completion_t> &comp)
-{
- wcstring msg;
- wcstring prefix_esc;
- char *foo;
-
- shared_ptr<io_buffer_t> in_buff(io_buffer_t::create(true, 3));
- shared_ptr<io_buffer_t> out_buff(io_buffer_t::create(false, 4));
-
- // The above may fail e.g. if we have too many open fds
- if (in_buff.get() == NULL || out_buff.get() == NULL)
- return;
-
- wchar_t *escaped_separator;
-
- if (prefix.empty())
- {
- prefix_esc = L"\"\"";
- }
- else
- {
- prefix_esc = escape_string(prefix, 1);
- }
-
-
- const wcstring pager_path = escaped_fish_pager_path();
- const wcstring cmd = format_string(L"%ls -c 3 -r 4 %ls -p %ls",
- // L"valgrind --track-fds=yes --log-file=pager.txt --leak-check=full ./%ls %d %ls",
- pager_path.c_str(),
- is_quoted?L"-q":L"",
- prefix_esc.c_str());
-
- escaped_separator = escape(COMPLETE_SEP_STR, 1);
-
- for (size_t i=0; i< comp.size(); i++)
- {
- long base_len=-1;
- const completion_t &el = comp.at(i);
-
- wcstring completion_text;
- wcstring description_text;
-
- // Note that an empty completion is perfectly sensible here, e.g. tab-completing 'foo' with a file called 'foo' and another called 'foobar'
- if ((el.flags & COMPLETE_REPLACES_TOKEN) && match_type_shares_prefix(el.match.type))
- {
- // Compute base_len if we have not yet
- if (base_len == -1)
- {
- const wchar_t *begin, *buff = data->command_line.c_str();
-
- parse_util_token_extent(buff, data->buff_pos, &begin, 0, 0, 0);
- base_len = data->buff_pos - (begin-buff);
- }
-
- completion_text = escape_string(el.completion.c_str() + base_len, ESCAPE_ALL | ESCAPE_NO_QUOTED);
- }
- else
- {
- completion_text = escape_string(el.completion, ESCAPE_ALL | ESCAPE_NO_QUOTED);
- }
-
-
- if (! el.description.empty())
- {
- description_text = escape_string(el.description, true);
- }
-
- /* It's possible (even common) to have an empty completion with no description. An example would be completing 'foo' with extant files 'foo' and 'foobar'. But fish_pager ignores blank lines. So if our completion text is empty, always include a description, even if it's empty.
- */
- msg.reserve(msg.size() + completion_text.size() + description_text.size() + 2);
- msg.append(completion_text);
- if (! description_text.empty() || completion_text.empty())
- {
- msg.append(escaped_separator);
- msg.append(description_text);
- }
- msg.push_back(L'\n');
- }
-
- free(escaped_separator);
-
- foo = wcs2str(msg.c_str());
- in_buff->out_buffer_append(foo, strlen(foo));
- free(foo);
-
- term_donate();
- parser_t &parser = parser_t::principal_parser();
- io_chain_t io_chain;
- io_chain.push_back(out_buff);
- io_chain.push_back(in_buff);
- parser.eval(cmd, io_chain, TOP);
- term_steal();
-
- out_buff->read();
-
- const char zero = 0;
- out_buff->out_buffer_append(&zero, 1);
-
- const char *out_data = out_buff->out_buffer_ptr();
- if (out_data)
- {
- const wcstring str = str2wcstring(out_data);
- size_t idx = str.size();
- while (idx--)
- {
- input_unreadch(str.at(idx));
- }
- }
-}
-
struct autosuggestion_context_t
{
wcstring search_string;
@@ -1402,21 +1430,16 @@ struct autosuggestion_context_t
file_detection_context_t detector;
const wcstring working_directory;
const env_vars_snapshot_t vars;
- wcstring_list_t commands_to_load;
const unsigned int generation_count;
- // don't reload more than once
- bool has_tried_reloading;
-
autosuggestion_context_t(history_t *history, const wcstring &term, size_t pos) :
search_string(term),
cursor_pos(pos),
searcher(*history, term, HISTORY_SEARCH_TYPE_PREFIX),
- detector(history, term),
+ detector(history),
working_directory(env_get_pwd_slash()),
vars(env_vars_snapshot_t::highlighting_keys),
- generation_count(s_generation_count),
- has_tried_reloading(false)
+ generation_count(s_generation_count)
{
}
@@ -1486,12 +1509,12 @@ struct autosuggestion_context_t
/* Try normal completions */
std::vector<completion_t> completions;
- complete(search_string, completions, COMPLETION_REQUEST_AUTOSUGGESTION, &this->commands_to_load);
+ complete(search_string, completions, COMPLETION_REQUEST_AUTOSUGGESTION);
if (! completions.empty())
{
const completion_t &comp = completions.at(0);
size_t cursor = this->cursor_pos;
- this->autosuggestion = completion_apply_to_command_line(comp.completion.c_str(), comp.flags, this->search_string, &cursor, true /* append only */);
+ this->autosuggestion = completion_apply_to_command_line(comp.completion, comp.flags, this->search_string, &cursor, true /* append only */);
return 1;
}
@@ -1507,34 +1530,19 @@ static int threaded_autosuggest(autosuggestion_context_t *ctx)
static bool can_autosuggest(void)
{
/* We autosuggest if suppress_autosuggestion is not set, if we're not doing a history search, and our command line contains a non-whitespace character. */
+ const editable_line_t *el = data->active_edit_line();
const wchar_t *whitespace = L" \t\r\n\v";
return ! data->suppress_autosuggestion &&
data->history_search.is_at_end() &&
- data->command_line.find_first_not_of(whitespace) != wcstring::npos;
+ el == &data->command_line &&
+ el->text.find_first_not_of(whitespace) != wcstring::npos;
}
static void autosuggest_completed(autosuggestion_context_t *ctx, int result)
{
-
- /* Extract the commands to load */
- wcstring_list_t commands_to_load;
- ctx->commands_to_load.swap(commands_to_load);
-
- /* If we have autosuggestions to load, load them and try again */
- if (! result && ! commands_to_load.empty() && ! ctx->has_tried_reloading)
- {
- ctx->has_tried_reloading = true;
- for (wcstring_list_t::const_iterator iter = commands_to_load.begin(); iter != commands_to_load.end(); ++iter)
- {
- complete_load(*iter, false);
- }
- iothread_perform(threaded_autosuggest, autosuggest_completed, ctx);
- return;
- }
-
if (result &&
can_autosuggest() &&
- ctx->search_string == data->command_line &&
+ ctx->search_string == data->command_line.text &&
string_prefixes_string_case_insensitive(ctx->search_string, ctx->autosuggestion))
{
/* Autosuggestion is active and the search term has not changed, so we're good to go */
@@ -1552,7 +1560,8 @@ static void update_autosuggestion(void)
data->autosuggestion.clear();
if (data->allow_autosuggestion && ! data->suppress_autosuggestion && ! data->command_line.empty() && data->history_search.is_at_end())
{
- autosuggestion_context_t *ctx = new autosuggestion_context_t(data->history, data->command_line, data->buff_pos);
+ const editable_line_t *el = data->active_edit_line();
+ autosuggestion_context_t *ctx = new autosuggestion_context_t(data->history, el->text, el->position);
iothread_perform(threaded_autosuggest, autosuggest_completed, ctx);
}
}
@@ -1562,11 +1571,14 @@ static void accept_autosuggestion(bool full)
{
if (! data->autosuggestion.empty())
{
+ /* Accepting an autosuggestion clears the pager */
+ clear_pager();
+
/* Accept the autosuggestion */
if (full)
{
/* Just take the whole thing */
- data->command_line = data->autosuggestion;
+ data->command_line.text = data->autosuggestion;
}
else
{
@@ -1577,16 +1589,37 @@ static void accept_autosuggestion(bool full)
wchar_t wc = data->autosuggestion.at(idx);
if (! state.consume_char(wc))
break;
- data->command_line.push_back(wc);
+ data->command_line.text.push_back(wc);
}
}
- data->buff_pos = data->command_line.size();
- data->command_line_changed();
- reader_super_highlight_me_plenty(data->buff_pos);
+ update_buff_pos(&data->command_line, data->command_line.size());
+ data->command_line_changed(&data->command_line);
+ reader_super_highlight_me_plenty();
reader_repaint();
}
}
+/* Ensure we have no pager contents */
+static void clear_pager()
+{
+ if (data)
+ {
+ data->pager.clear();
+ data->current_page_rendering = page_rendering_t();
+ reader_repaint_needed();
+ }
+}
+
+static void select_completion_in_direction(enum selection_direction_t dir)
+{
+ assert(data != NULL);
+ bool selection_changed = data->pager.select_next_completion_in_direction(dir, data->current_page_rendering);
+ if (selection_changed)
+ {
+ data->pager_selection_changed();
+ }
+}
+
/**
Flash the screen. This function only changed the color of the
current line, since the flash_screen sequnce is rather painful to
@@ -1596,9 +1629,10 @@ static void reader_flash()
{
struct timespec pollint;
- for (size_t i=0; i<data->buff_pos; i++)
+ editable_line_t *el = &data->command_line;
+ for (size_t i=0; i<el->position; i++)
{
- data->colors.at(i) = HIGHLIGHT_SEARCH_MATCH<<16;
+ data->colors.at(i) = highlight_spec_search_match<<16;
}
reader_repaint();
@@ -1607,7 +1641,7 @@ static void reader_flash()
pollint.tv_nsec = 100 * 1000000;
nanosleep(&pollint, NULL);
- reader_super_highlight_me_plenty(data->buff_pos);
+ reader_super_highlight_me_plenty();
reader_repaint();
}
@@ -1705,40 +1739,6 @@ static void prioritize_completions(std::vector<completion_t> &comp)
sort(comp.begin(), comp.end(), compare_completions_by_match_type);
}
-/* Given a list of completions, get the completion at an index past *inout_idx, and then increment it. inout_idx should be initialized to (size_t)(-1) for the first call. */
-static const completion_t *cycle_competions(const std::vector<completion_t> &comp, const wcstring &command_line, size_t *inout_idx)
-{
- const size_t size = comp.size();
- if (size == 0)
- return NULL;
-
- // note start_idx will be set to -1 initially, so that when it gets incremented we start at 0
- const size_t start_idx = *inout_idx;
- size_t idx = start_idx;
-
- const completion_t *result = NULL;
- size_t remaining = comp.size();
- while (remaining--)
- {
- /* Bump the index */
- idx = (idx + 1) % size;
-
- /* Get the completion */
- const completion_t &c = comp.at(idx);
-
- /* Try this completion */
- if (!(c.flags & COMPLETE_REPLACES_TOKEN) || reader_can_replace(command_line, c.flags))
- {
- /* Success */
- result = &c;
- break;
- }
- }
-
- *inout_idx = idx;
- return result;
-}
-
/**
Handle the list of completions. This means the following:
@@ -1755,18 +1755,20 @@ static const completion_t *cycle_competions(const std::vector<completion_t> &com
completions.
\param comp the list of completion strings
+ \param continue_after_prefix_insertion If we have a shared prefix, whether to print the list of completions after inserting it.
Return true if we inserted text into the command line, false if we did not.
*/
-static bool handle_completions(const std::vector<completion_t> &comp)
+static bool handle_completions(const std::vector<completion_t> &comp, bool continue_after_prefix_insertion)
{
bool done = false;
bool success = false;
- const wchar_t *begin, *end, *buff = data->command_line.c_str();
+ const editable_line_t *el = &data->command_line;
+ const wchar_t *begin, *end, *buff = el->text.c_str();
- parse_util_token_extent(buff, data->buff_pos, &begin, 0, 0, 0);
- end = buff+data->buff_pos;
+ parse_util_token_extent(buff, el->position, &begin, 0, 0, 0);
+ end = buff+el->position;
const wcstring tok(begin, end - begin);
@@ -1886,8 +1888,12 @@ static bool handle_completions(const std::vector<completion_t> &comp)
break;
}
}
+
+ /* Determine if we use the prefix. We use it if it's non-empty and it will actually make the command line longer. It may make the command line longer by virtue of not using REPLACE_TOKEN (so it always appends to the command line), or by virtue of replacing the token but being longer than it. */
+ bool use_prefix = common_prefix.size() > (will_replace_token ? tok.size() : 0);
+ assert(! use_prefix || ! common_prefix.empty());
- if (! common_prefix.empty())
+ if (use_prefix)
{
/* We got something. If more than one completion contributed, then it means we have a prefix; don't insert a space after it */
if (prefix_is_partial_completion)
@@ -1895,15 +1901,16 @@ static bool handle_completions(const std::vector<completion_t> &comp)
completion_insert(common_prefix.c_str(), flags);
success = true;
}
- else
+
+ if (continue_after_prefix_insertion || ! use_prefix)
{
- /* We didn't get a common prefix. Print the list. */
+ /* We didn't get a common prefix, or we want to print the list anyways. */
size_t len, prefix_start = 0;
wcstring prefix;
- parse_util_get_parameter_info(data->command_line, data->buff_pos, NULL, &prefix_start, NULL);
+ parse_util_get_parameter_info(el->text, el->position, NULL, &prefix_start, NULL);
- assert(data->buff_pos >= prefix_start);
- len = data->buff_pos - prefix_start;
+ assert(el->position >= prefix_start);
+ len = el->position - prefix_start;
if (match_type_requires_full_replacement(best_match_type))
{
@@ -1912,32 +1919,30 @@ static bool handle_completions(const std::vector<completion_t> &comp)
}
else if (len <= PREFIX_MAX_LEN)
{
- prefix.append(data->command_line, prefix_start, len);
+ prefix.append(el->text, prefix_start, len);
}
else
{
// append just the end of the string
prefix = wcstring(&ellipsis_char, 1);
- prefix.append(data->command_line, prefix_start + len - PREFIX_MAX_LEN, PREFIX_MAX_LEN);
+ prefix.append(el->text, prefix_start + len - PREFIX_MAX_LEN, PREFIX_MAX_LEN);
}
- {
- int is_quoted;
+ wchar_t quote;
+ parse_util_get_parameter_info(el->text, el->position, &quote, NULL, NULL);
- wchar_t quote;
- parse_util_get_parameter_info(data->command_line, data->buff_pos, &quote, NULL, NULL);
- is_quoted = (quote != L'\0');
+ /* Update the pager data */
+ data->pager.set_prefix(prefix);
+ data->pager.set_completions(surviving_completions);
- /* Clear the autosuggestion from the old commandline before abandoning it (see #561) */
- if (! data->autosuggestion.empty())
- reader_repaint_without_autosuggestion();
+ /* Invalidate our rendering */
+ data->current_page_rendering = page_rendering_t();
- write_loop(1, "\n", 1);
+ /* Modify the command line to reflect the new pager */
+ data->pager_selection_changed();
+
+ reader_repaint_needed();
- run_pager(prefix, is_quoted, surviving_completions);
- }
- s_reset(&data->screen, screen_reset_abandon_line);
- reader_repaint();
success = false;
}
}
@@ -2143,18 +2148,16 @@ static void reader_interactive_destroy()
void reader_sanity_check()
{
- if (get_is_interactive())
+ /* Note: 'data' is non-null if we are interactive, except in the testing environment */
+ if (get_is_interactive() && data != NULL)
{
- if (!data)
- sanity_lose();
-
- if (!(data->buff_pos <= data->command_length()))
+ if (data->command_line.position > data->command_line.size())
sanity_lose();
- if (data->colors.size() != data->command_length())
+ if (data->colors.size() != data->command_line.size())
sanity_lose();
- if (data->indents.size() != data->command_length())
+ if (data->indents.size() != data->command_line.size())
sanity_lose();
}
@@ -2163,13 +2166,13 @@ void reader_sanity_check()
/**
Set the specified string as the current buffer.
*/
-static void set_command_line_and_position(const wcstring &new_str, size_t pos)
+static void set_command_line_and_position(editable_line_t *el, const wcstring &new_str, size_t pos)
{
- data->command_line = new_str;
- data->command_line_changed();
- data->buff_pos = pos;
- reader_super_highlight_me_plenty(data->buff_pos);
- reader_repaint();
+ el->text = new_str;
+ update_buff_pos(el, pos);
+ data->command_line_changed(el);
+ reader_super_highlight_me_plenty();
+ reader_repaint_needed();
}
static void reader_replace_current_token(const wchar_t *new_token)
@@ -2179,8 +2182,9 @@ static void reader_replace_current_token(const wchar_t *new_token)
size_t new_pos;
/* Find current token */
- const wchar_t *buff = data->command_line.c_str();
- parse_util_token_extent(buff, data->buff_pos, &begin, &end, 0, 0);
+ editable_line_t *el = data->active_edit_line();
+ const wchar_t *buff = el->text.c_str();
+ parse_util_token_extent(buff, el->position, &begin, &end, 0, 0);
if (!begin || !end)
return;
@@ -2191,7 +2195,7 @@ static void reader_replace_current_token(const wchar_t *new_token)
new_buff.append(end);
new_pos = (begin-buff) + wcslen(new_token);
- set_command_line_and_position(new_buff, new_pos);
+ set_command_line_and_position(el, new_buff, new_pos);
}
@@ -2200,9 +2204,10 @@ static void reader_replace_current_token(const wchar_t *new_token)
*/
static void reset_token_history()
{
+ const editable_line_t *el = data->active_edit_line();
const wchar_t *begin, *end;
- const wchar_t *buff = data->command_line.c_str();
- parse_util_token_extent((wchar_t *)buff, data->buff_pos, &begin, &end, 0, 0);
+ const wchar_t *buff = el->text.c_str();
+ parse_util_token_extent((wchar_t *)buff, el->position, &begin, &end, 0, 0);
data->search_buff.clear();
if (begin)
@@ -2263,7 +2268,7 @@ static void handle_token_history(int forward, int reset)
}
reader_replace_current_token(str);
- reader_super_highlight_me_plenty(data->buff_pos);
+ reader_super_highlight_me_plenty();
reader_repaint();
}
else
@@ -2277,7 +2282,6 @@ static void handle_token_history(int forward, int reset)
*/
if (data->history_search.go_backwards())
{
- wcstring item = data->history_search.current_string();
data->token_history_buff = data->history_search.current_string();
}
current_pos = data->token_history_buff.size();
@@ -2332,7 +2336,7 @@ static void handle_token_history(int forward, int reset)
}
}
break;
-
+
default:
{
break;
@@ -2345,7 +2349,7 @@ static void handle_token_history(int forward, int reset)
if (str)
{
reader_replace_current_token(str);
- reader_super_highlight_me_plenty(data->buff_pos);
+ reader_super_highlight_me_plenty();
reader_repaint();
data->search_pos = data->search_prev.size();
data->search_prev.push_back(str);
@@ -2373,19 +2377,19 @@ enum move_word_dir_t
MOVE_DIR_RIGHT
};
-static void move_word(bool move_right, bool erase, enum move_word_style_t style, bool newv)
+static void move_word(editable_line_t *el, bool move_right, bool erase, enum move_word_style_t style, bool newv)
{
/* Return if we are already at the edge */
- const size_t boundary = move_right ? data->command_length() : 0;
- if (data->buff_pos == boundary)
+ const size_t boundary = move_right ? el->size() : 0;
+ if (el->position == boundary)
return;
/* When moving left, a value of 1 means the character at index 0. */
move_word_state_machine_t state(style);
- const wchar_t * const command_line = data->command_line.c_str();
- const size_t start_buff_pos = data->buff_pos;
+ const wchar_t * const command_line = el->text.c_str();
+ const size_t start_buff_pos = el->position;
- size_t buff_pos = data->buff_pos;
+ size_t buff_pos = el->position;
while (buff_pos != boundary)
{
size_t idx = (move_right ? buff_pos : buff_pos - 1);
@@ -2399,24 +2403,27 @@ static void move_word(bool move_right, bool erase, enum move_word_style_t style,
if (buff_pos == start_buff_pos)
buff_pos = (move_right ? buff_pos + 1 : buff_pos - 1);
- /* If we are moving left, buff_pos-1 is the index of the first character we do not delete (possibly -1). If we are moving right, then buff_pos is that index - possibly data->command_length(). */
+ /* If we are moving left, buff_pos-1 is the index of the first character we do not delete (possibly -1). If we are moving right, then buff_pos is that index - possibly el->size(). */
if (erase)
{
/* Don't autosuggest after a kill */
- data->suppress_autosuggestion = true;
+ if (el == &data->command_line)
+ {
+ data->suppress_autosuggestion = true;
+ }
if (move_right)
{
- reader_kill(start_buff_pos, buff_pos - start_buff_pos, KILL_APPEND, newv);
+ reader_kill(el, start_buff_pos, buff_pos - start_buff_pos, KILL_APPEND, newv);
}
else
{
- reader_kill(buff_pos, start_buff_pos - buff_pos, KILL_PREPEND, newv);
+ reader_kill(el, buff_pos, start_buff_pos - buff_pos, KILL_PREPEND, newv);
}
}
else
{
- data->buff_pos = buff_pos;
+ update_buff_pos(el, buff_pos);
reader_repaint();
}
@@ -2425,7 +2432,7 @@ static void move_word(bool move_right, bool erase, enum move_word_style_t style,
const wchar_t *reader_get_buffer(void)
{
ASSERT_IS_MAIN_THREAD();
- return data?data->command_line.c_str():NULL;
+ return data ? data->command_line.text.c_str() : NULL;
}
history_t *reader_get_history(void)
@@ -2434,39 +2441,61 @@ history_t *reader_get_history(void)
return data ? data->history : NULL;
}
-void reader_set_buffer(const wcstring &b, size_t pos)
+/* Sets the command line contents, without clearing the pager */
+static void reader_set_buffer_maintaining_pager(const wcstring &b, size_t pos)
{
- if (!data)
- return;
-
/* Callers like to pass us pointers into ourselves, so be careful! I don't know if we can use operator= with a pointer to our interior, so use an intermediate. */
size_t command_line_len = b.size();
- data->command_line = b;
- data->command_line_changed();
+ data->command_line.text = b;
+ data->command_line_changed(&data->command_line);
/* Don't set a position past the command line length */
if (pos > command_line_len)
pos = command_line_len;
- data->buff_pos = pos;
+ update_buff_pos(&data->command_line, pos);
+ /* Clear history search and pager contents */
data->search_mode = NO_SEARCH;
data->search_buff.clear();
data->history_search.go_to_end();
- reader_super_highlight_me_plenty(data->buff_pos);
+ reader_super_highlight_me_plenty();
reader_repaint_needed();
}
+/* Sets the command line contents, clearing the pager */
+void reader_set_buffer(const wcstring &b, size_t pos)
+{
+ if (!data)
+ return;
+
+ clear_pager();
+ reader_set_buffer_maintaining_pager(b, pos);
+}
+
size_t reader_get_cursor_pos()
{
if (!data)
return (size_t)(-1);
- return data->buff_pos;
+ return data->command_line.position;
}
+bool reader_get_selection(size_t *start, size_t *len)
+{
+ bool result = false;
+ if (data != NULL && data->sel_active)
+ {
+ *start = data->sel_start_pos;
+ *len = std::min(data->sel_stop_pos - data->sel_start_pos + 1, data->command_line.size());
+ result = true;
+ }
+ return result;
+}
+
+
#define ENV_CMD_DURATION L"CMD_DURATION"
void set_env_cmd_duration(struct timeval *after, struct timeval *before)
@@ -2481,34 +2510,8 @@ void set_env_cmd_duration(struct timeval *after, struct timeval *before)
secs -= 1;
}
- if (secs < 1)
- {
- env_remove(ENV_CMD_DURATION, 0);
- }
- else
- {
- if (secs < 10) // 10 secs
- {
- swprintf(buf, 16, L"%lu.%02us", secs, usecs / 10000);
- }
- else if (secs < 60) // 1 min
- {
- swprintf(buf, 16, L"%lu.%01us", secs, usecs / 100000);
- }
- else if (secs < 600) // 10 mins
- {
- swprintf(buf, 16, L"%lum %lu.%01us", secs / 60, secs % 60, usecs / 100000);
- }
- else if (secs < 5400) // 1.5 hours
- {
- swprintf(buf, 16, L"%lum %lus", secs / 60, secs % 60);
- }
- else
- {
- swprintf(buf, 16, L"%.1fh", secs / 3600.0);
- }
- env_set(ENV_CMD_DURATION, buf, ENV_EXPORT);
- }
+ swprintf(buf, 16, L"%d", (secs * 1000) + (usecs / 1000));
+ env_set(ENV_CMD_DURATION, buf, ENV_UNEXPORT);
}
void reader_run_command(parser_t &parser, const wcstring &cmd)
@@ -2521,7 +2524,7 @@ void reader_run_command(parser_t &parser, const wcstring &cmd)
if (! ft.empty())
env_set(L"_", ft.c_str(), ENV_GLOBAL);
- reader_write_title();
+ reader_write_title(cmd);
term_donate();
@@ -2547,28 +2550,26 @@ void reader_run_command(parser_t &parser, const wcstring &cmd)
int reader_shell_test(const wchar_t *b)
{
- int res = parser_t::principal_parser().test(b);
+ assert(b != NULL);
+ wcstring bstr = b;
- if (res & PARSER_TEST_ERROR)
- {
- wcstring sb;
+ /* Append a newline, to act as a statement terminator */
+ bstr.push_back(L'\n');
- const int tmp[1] = {0};
- const int tmp2[1] = {0};
- const wcstring empty;
-
- s_write(&data->screen,
- empty,
- empty,
- empty,
- 0,
- tmp,
- tmp2,
- 0);
+ parse_error_list_t errors;
+ int res = parse_util_detect_errors(bstr, &errors, true /* do accept incomplete */);
+ if (res & PARSER_TEST_ERROR)
+ {
+ wcstring error_desc;
+ parser_t::principal_parser().get_backtrace(bstr, errors, &error_desc);
- parser_t::principal_parser().test(b, NULL, &sb, L"fish");
- fwprintf(stderr, L"%ls", sb.c_str());
+ // ensure we end with a newline. Also add an initial newline, because it's likely the user just hit enter and so there's junk on the current line
+ if (! string_suffixes_string(L"\n", error_desc))
+ {
+ error_desc.push_back(L'\n');
+ }
+ fwprintf(stderr, L"\n%ls", error_desc.c_str());
}
return res;
}
@@ -2593,7 +2594,7 @@ void reader_push(const wchar_t *name)
data=n;
- data->command_line_changed();
+ data->command_line_changed(&data->command_line);
if (data->next == 0)
{
@@ -2701,7 +2702,7 @@ public:
const wcstring string_to_highlight;
/** Color buffer */
- std::vector<color_t> colors;
+ std::vector<highlight_spec_t> colors;
/** The position to use for bracket matching */
const size_t match_highlight_pos;
@@ -2729,7 +2730,7 @@ public:
{
}
- int threaded_highlight()
+ int perform_highlight()
{
if (generation_count != s_generation_count)
{
@@ -2749,14 +2750,15 @@ static void highlight_search(void)
{
if (! data->search_buff.empty() && ! data->history_search.is_at_end())
{
+ const editable_line_t *el = &data->command_line;
const wcstring &needle = data->search_buff;
- size_t match_pos = data->command_line.find(needle);
+ size_t match_pos = el->text.find(needle);
if (match_pos != wcstring::npos)
{
size_t end = match_pos + needle.size();
for (size_t i=match_pos; i < end; i++)
{
- data->colors.at(i) |= (HIGHLIGHT_SEARCH_MATCH<<16);
+ data->colors.at(i) |= (highlight_spec_search_match<<16);
}
}
}
@@ -2765,10 +2767,10 @@ static void highlight_search(void)
static void highlight_complete(background_highlight_context_t *ctx, int result)
{
ASSERT_IS_MAIN_THREAD();
- if (ctx->string_to_highlight == data->command_line)
+ if (ctx->string_to_highlight == data->command_line.text)
{
/* The data hasn't changed, so swap in our colors. The colors may not have changed, so do nothing if they have not. */
- assert(ctx->colors.size() == data->command_length());
+ assert(ctx->colors.size() == data->command_line.size());
if (data->colors != ctx->colors)
{
data->colors.swap(ctx->colors);
@@ -2784,7 +2786,7 @@ static void highlight_complete(background_highlight_context_t *ctx, int result)
static int threaded_highlight(background_highlight_context_t *ctx)
{
- return ctx->threaded_highlight();
+ return ctx->perform_highlight();
}
@@ -2794,19 +2796,36 @@ static int threaded_highlight(background_highlight_context_t *ctx)
to avoid repaint issues on terminals where e.g. syntax highlighting
maykes characters under the sursor unreadable.
- \param match_highlight_pos the position to use for bracket matching. This need not be the same as the surrent cursor position
+ \param match_highlight_pos_adjust the adjustment to the position to use for bracket matching. This is added to the current cursor position and may be negative.
\param error if non-null, any possible errors in the buffer are further descibed by the strings inserted into the specified arraylist
+ \param no_io if true, do a highlight that does not perform I/O, synchronously. If false, perform an asynchronous highlight in the background, which may perform disk I/O.
*/
-static void reader_super_highlight_me_plenty(size_t match_highlight_pos)
+static void reader_super_highlight_me_plenty(int match_highlight_pos_adjust, bool no_io)
{
+ const editable_line_t *el = &data->command_line;
+ long match_highlight_pos = (long)el->position + match_highlight_pos_adjust;
+ assert(match_highlight_pos >= 0);
+
reader_sanity_check();
- background_highlight_context_t *ctx = new background_highlight_context_t(data->command_line, match_highlight_pos, data->highlight_function);
- iothread_perform(threaded_highlight, highlight_complete, ctx);
+ highlight_function_t highlight_func = no_io ? highlight_shell_no_io : data->highlight_function;
+ background_highlight_context_t *ctx = new background_highlight_context_t(el->text, match_highlight_pos, highlight_func);
+ if (no_io)
+ {
+ // Highlighting without IO, we just do it
+ // Note that highlight_complete deletes ctx.
+ int result = ctx->perform_highlight();
+ highlight_complete(ctx, result);
+ }
+ else
+ {
+ // Highlighting including I/O proceeds in the background
+ iothread_perform(threaded_highlight, highlight_complete, ctx);
+ }
highlight_search();
/* Here's a hack. Check to see if our autosuggestion still applies; if so, don't recompute it. Since the autosuggestion computation is asynchronous, this avoids "flashing" as you type into the autosuggestion. */
- const wcstring &cmd = data->command_line, &suggest = data->autosuggestion;
+ const wcstring &cmd = el->text, &suggest = data->autosuggestion;
if (can_autosuggest() && ! suggest.empty() && string_prefixes_string_case_insensitive(cmd, suggest))
{
/* The autosuggestion is still reasonable, so do nothing */
@@ -2818,10 +2837,10 @@ static void reader_super_highlight_me_plenty(size_t match_highlight_pos)
}
-int exit_status()
+bool shell_is_exiting()
{
if (get_is_interactive())
- return job_list_is_empty() && data->end_loop;
+ return job_list_is_empty() && data != NULL && data->end_loop;
else
return end_loop;
}
@@ -2837,14 +2856,11 @@ static void handle_end_loop()
job_t *j;
int stopped_jobs_count=0;
int is_breakpoint=0;
- block_t *b;
- parser_t &parser = parser_t::principal_parser();
+ const parser_t &parser = parser_t::principal_parser();
- for (b = parser.current_block;
- b;
- b = b->outer)
+ for (size_t i = 0; i < parser.block_count(); i++)
{
- if (b->type() == BREAKPOINT)
+ if (parser.block_at_index(i)->type() == BREAKPOINT)
{
is_breakpoint = 1;
break;
@@ -2893,6 +2909,21 @@ static void handle_end_loop()
}
}
+static bool selection_is_at_top()
+{
+ const pager_t *pager = &data->pager;
+ size_t row = pager->get_selected_row(data->current_page_rendering);
+ if (row != 0 && row != PAGER_SELECTION_NONE)
+ return false;
+
+ size_t col = pager->get_selected_column(data->current_page_rendering);
+ if (col != 0 && col != PAGER_SELECTION_NONE)
+ return false;
+
+ return true;
+}
+
+
/**
Read interactively. Read input from stdin while providing editing
facilities.
@@ -2931,7 +2962,7 @@ static int read_i(void)
during evaluation.
*/
- const wchar_t *tmp = reader_readline();
+ const wchar_t *tmp = reader_readline(0);
if (data->end_loop)
{
@@ -2939,11 +2970,14 @@ static int read_i(void)
}
else if (tmp)
{
- wcstring command = tmp;
- data->buff_pos=0;
- data->command_line.clear();
- data->command_line_changed();
+ const wcstring command = tmp;
+ update_buff_pos(&data->command_line, 0);
+ data->command_line.text.clear();
+ data->command_line_changed(&data->command_line);
+ wcstring_list_t argv(1, command);
+ event_fire_generic(L"fish_preexec", &argv);
reader_run_command(parser, command);
+ event_fire_generic(L"fish_postexec", &argv);
if (data->end_loop)
{
handle_end_loop();
@@ -2977,10 +3011,13 @@ static int can_read(int fd)
/**
Test if the specified character is in the private use area that
fish uses to store internal characters
+
+ Note: Allow U+F8FF because that's the Apple symbol, which is in the
+ OS X US keyboard layout.
*/
static int wchar_private(wchar_t c)
{
- return ((c >= 0xe000) && (c <= 0xf8ff));
+ return ((c >= 0xe000) && (c < 0xf8ff));
}
/**
@@ -3020,7 +3057,7 @@ static wchar_t unescaped_quote(const wcstring &str, size_t pos)
}
-const wchar_t *reader_readline(void)
+const wchar_t *reader_readline(int nchars)
{
wint_t c;
int last_char=0;
@@ -3034,19 +3071,16 @@ const wchar_t *reader_readline(void)
/* Coalesce redundant repaints. When we get a repaint, we set this to true, and skip repaints until we get something else. */
bool coalescing_repaints = false;
- /* The cycle index in our completion list */
- size_t completion_cycle_idx = (size_t)(-1);
-
/* The command line before completion */
- wcstring cycle_command_line;
- size_t cycle_cursor_pos = 0;
+ data->cycle_command_line.clear();
+ data->cycle_cursor_pos = 0;
data->search_buff.clear();
data->search_mode = NO_SEARCH;
exec_prompt();
- reader_super_highlight_me_plenty(data->buff_pos);
+ reader_super_highlight_me_plenty();
s_reset(&data->screen, screen_reset_abandon_line);
reader_repaint();
@@ -3063,6 +3097,13 @@ const wchar_t *reader_readline(void)
while (!finished && !data->end_loop)
{
+ if (0 < nchars && (size_t)nchars <= data->command_line.size())
+ {
+ // we've already hit the specified character limit
+ finished = 1;
+ break;
+ }
+
/*
Sometimes strange input sequences seem to generate a zero
byte. I believe these simply mean a character was pressed
@@ -3075,6 +3116,7 @@ const wchar_t *reader_readline(void)
is_interactive_read = 1;
c=input_readch();
is_interactive_read = was_interactive_read;
+ //fprintf(stderr, "C: %lx\n", (long)c);
if (((!wchar_private(c))) && (c>31) && (c != 127))
{
@@ -3082,12 +3124,14 @@ const wchar_t *reader_readline(void)
{
wchar_t arr[READAHEAD_MAX+1];
- int i;
+ size_t i;
+ size_t limit = 0 < nchars ? std::min((size_t)nchars - data->command_line.size(), (size_t)READAHEAD_MAX)
+ : READAHEAD_MAX;
memset(arr, 0, sizeof(arr));
arr[0] = c;
- for (i=1; i<READAHEAD_MAX; i++)
+ for (i = 1; i < limit; ++i)
{
if (!can_read(0))
@@ -3095,7 +3139,10 @@ const wchar_t *reader_readline(void)
c = 0;
break;
}
- c = input_readch();
+ // only allow commands on the first key; otherwise, we might
+ // have data we need to insert on the commandline that the
+ // commmand might need to be able to see.
+ c = input_readch(false);
if ((!wchar_private(c)) && (c>31) && (c != 127))
{
arr[i]=c;
@@ -3105,13 +3152,19 @@ const wchar_t *reader_readline(void)
break;
}
- insert_string(arr);
+ insert_string(&data->command_line, arr, true);
}
}
if (c != 0)
break;
+
+ if (0 < nchars && (size_t)nchars <= data->command_line.size())
+ {
+ c = R_NULL;
+ break;
+ }
}
/* If we get something other than a repaint, then stop coalescing them */
@@ -3121,31 +3174,47 @@ const wchar_t *reader_readline(void)
if (last_char != R_YANK && last_char != R_YANK_POP)
yank_len=0;
- const wchar_t *buff = data->command_line.c_str();
- switch (c)
+ /* Restore the text */
+ if (c == R_CANCEL && data->is_navigating_pager_contents())
+ {
+ set_command_line_and_position(&data->command_line, data->cycle_command_line, data->cycle_cursor_pos);
+ }
+
+
+ /* Clear the pager if necessary */
+ bool focused_on_search_field = (data->active_edit_line() == &data->pager.search_field_line);
+ if (command_ends_paging(c, focused_on_search_field))
{
+ clear_pager();
+ }
+ //fprintf(stderr, "\n\nchar: %ls\n\n", describe_char(c).c_str());
+
+ switch (c)
+ {
/* go to beginning of line*/
case R_BEGINNING_OF_LINE:
{
- while ((data->buff_pos>0) &&
- (buff[data->buff_pos-1] != L'\n'))
+ editable_line_t *el = data->active_edit_line();
+ while (el->position > 0 && el->text.at(el->position - 1) != L'\n')
{
- data->buff_pos--;
+ update_buff_pos(el, el->position - 1);
}
- reader_repaint();
+ reader_repaint_needed();
break;
}
case R_END_OF_LINE:
{
- if (data->buff_pos < data->command_length())
+ editable_line_t *el = data->active_edit_line();
+ if (el->position < el->size())
{
- while (buff[data->buff_pos] &&
- buff[data->buff_pos] != L'\n')
+ const wchar_t *buff = el->text.c_str();
+ while (buff[el->position] &&
+ buff[el->position] != L'\n')
{
- data->buff_pos++;
+ update_buff_pos(el, el->position + 1);
}
}
else
@@ -3153,34 +3222,39 @@ const wchar_t *reader_readline(void)
accept_autosuggestion(true);
}
- reader_repaint();
+ reader_repaint_needed();
break;
}
case R_BEGINNING_OF_BUFFER:
{
- data->buff_pos = 0;
-
- reader_repaint();
+ update_buff_pos(&data->command_line, 0);
+ reader_repaint_needed();
break;
}
/* go to EOL*/
case R_END_OF_BUFFER:
{
- data->buff_pos = data->command_length();
+ update_buff_pos(&data->command_line, data->command_line.size());
- reader_repaint();
+ reader_repaint_needed();
break;
}
case R_NULL:
{
- reader_repaint_if_needed();
break;
}
+ case R_CANCEL:
+ {
+ // The only thing we can cancel right now is paging, which we handled up above
+ break;
+ }
+
+ case R_FORCE_REPAINT:
case R_REPAINT:
{
if (! coalescing_repaints)
@@ -3203,26 +3277,25 @@ const wchar_t *reader_readline(void)
/* complete */
case R_COMPLETE:
+ case R_COMPLETE_AND_SEARCH:
{
if (!data->complete_func)
break;
- if (! comp_empty && last_char == R_COMPLETE)
+ /* Use the command line only; it doesn't make sense to complete in any other line */
+ editable_line_t *el = &data->command_line;
+ if (data->is_navigating_pager_contents() || (! comp_empty && last_char == R_COMPLETE))
{
- /* The user typed R_COMPLETE more than once in a row. Cycle through our available completions */
- const completion_t *next_comp = cycle_competions(comp, cycle_command_line, &completion_cycle_idx);
- if (next_comp != NULL)
+ /* The user typed R_COMPLETE more than once in a row. If we are not yet fully disclosed, then become so; otherwise cycle through our available completions. */
+ if (data->current_page_rendering.remaining_to_disclose > 0)
{
- size_t cursor_pos = cycle_cursor_pos;
- const wcstring new_cmd_line = completion_apply_to_command_line(next_comp->completion, next_comp->flags, cycle_command_line, &cursor_pos, false);
- reader_set_buffer(new_cmd_line, cursor_pos);
-
- /* Since we just inserted a completion, don't immediately do a new autosuggestion */
- data->suppress_autosuggestion = true;
-
- /* Trigger repaint (see #765) */
- reader_repaint_if_needed();
+ data->pager.set_fully_disclosed(true);
+ reader_repaint_needed();
+ }
+ else
+ {
+ select_completion_in_direction(c == R_COMPLETE ? direction_next : direction_prev);
}
}
else
@@ -3230,32 +3303,35 @@ const wchar_t *reader_readline(void)
/* Either the user hit tab only once, or we had no visible completion list. */
/* Remove a trailing backslash. This may trigger an extra repaint, but this is rare. */
- if (is_backslashed(data->command_line, data->buff_pos))
+ if (is_backslashed(el->text, el->position))
{
remove_backward();
}
/* Get the string; we have to do this after removing any trailing backslash */
- const wchar_t * const buff = data->command_line.c_str();
+ const wchar_t * const buff = el->text.c_str();
/* Clear the completion list */
comp.clear();
/* Figure out the extent of the command substitution surrounding the cursor. This is because we only look at the current command substitution to form completions - stuff happening outside of it is not interesting. */
const wchar_t *cmdsub_begin, *cmdsub_end;
- parse_util_cmdsubst_extent(buff, data->buff_pos, &cmdsub_begin, &cmdsub_end);
+ parse_util_cmdsubst_extent(buff, el->position, &cmdsub_begin, &cmdsub_end);
/* Figure out the extent of the token within the command substitution. Note we pass cmdsub_begin here, not buff */
const wchar_t *token_begin, *token_end;
- parse_util_token_extent(cmdsub_begin, data->buff_pos - (cmdsub_begin-buff), &token_begin, &token_end, 0, 0);
+ parse_util_token_extent(cmdsub_begin, el->position - (cmdsub_begin-buff), &token_begin, &token_end, 0, 0);
+
+ /* Hack: the token may extend past the end of the command substitution, e.g. in (echo foo) the last token is 'foo)'. Don't let that happen. */
+ if (token_end > cmdsub_end) token_end = cmdsub_end;
/* Figure out how many steps to get from the current position to the end of the current token. */
size_t end_of_token_offset = token_end - buff;
/* Move the cursor to the end */
- if (data->buff_pos != end_of_token_offset)
+ if (el->position != end_of_token_offset)
{
- data->buff_pos = end_of_token_offset;
+ update_buff_pos(el, end_of_token_offset);
reader_repaint();
}
@@ -3263,23 +3339,27 @@ const wchar_t *reader_readline(void)
const wcstring buffcpy = wcstring(cmdsub_begin, token_end);
//fprintf(stderr, "Complete (%ls)\n", buffcpy.c_str());
- data->complete_func(buffcpy, comp, COMPLETION_REQUEST_DEFAULT | COMPLETION_REQUEST_DESCRIPTIONS | COMPLETION_REQUEST_FUZZY_MATCH, NULL);
+ data->complete_func(buffcpy, comp, COMPLETION_REQUEST_DEFAULT | COMPLETION_REQUEST_DESCRIPTIONS | COMPLETION_REQUEST_FUZZY_MATCH);
/* Munge our completions */
sort_and_make_unique(comp);
prioritize_completions(comp);
/* Record our cycle_command_line */
- cycle_command_line = data->command_line;
- cycle_cursor_pos = data->buff_pos;
+ data->cycle_command_line = el->text;
+ data->cycle_cursor_pos = el->position;
- comp_empty = handle_completions(comp);
+ bool continue_after_prefix_insertion = (c == R_COMPLETE_AND_SEARCH);
+ comp_empty = handle_completions(comp, continue_after_prefix_insertion);
- /* Start the cycle at the beginning */
- completion_cycle_idx = (size_t)(-1);
+ /* Show the search field if requested and if we printed a list of completions */
+ if (c == R_COMPLETE_AND_SEARCH && ! comp_empty && ! data->pager.empty())
+ {
+ data->pager.set_search_field_shown(true);
+ select_completion_in_direction(direction_next);
+ reader_repaint_needed();
+ }
- /* Repaint */
- reader_repaint_if_needed();
}
break;
@@ -3288,8 +3368,9 @@ const wchar_t *reader_readline(void)
/* kill */
case R_KILL_LINE:
{
- const wchar_t *buff = data->command_line.c_str();
- const wchar_t *begin = &buff[data->buff_pos];
+ editable_line_t *el = data->active_edit_line();
+ const wchar_t *buff = el->text.c_str();
+ const wchar_t *begin = &buff[el->position];
const wchar_t *end = begin;
while (*end && *end != L'\n')
@@ -3301,7 +3382,7 @@ const wchar_t *reader_readline(void)
size_t len = end-begin;
if (len)
{
- reader_kill(begin - buff, len, KILL_APPEND, last_char!=R_KILL_LINE);
+ reader_kill(el, begin - buff, len, KILL_APPEND, last_char!=R_KILL_LINE);
}
break;
@@ -3309,10 +3390,11 @@ const wchar_t *reader_readline(void)
case R_BACKWARD_KILL_LINE:
{
- if (data->buff_pos > 0)
+ editable_line_t *el = data->active_edit_line();
+ if (el->position > 0)
{
- const wchar_t *buff = data->command_line.c_str();
- const wchar_t *end = &buff[data->buff_pos];
+ const wchar_t *buff = el->text.c_str();
+ const wchar_t *end = &buff[el->position];
const wchar_t *begin = end;
/* Make sure we delete at least one character (see #580) */
@@ -3330,7 +3412,7 @@ const wchar_t *reader_readline(void)
size_t len = maxi<size_t>(end-begin, 1);
begin = end - len;
- reader_kill(begin - buff, len, KILL_PREPEND, last_char!=R_BACKWARD_KILL_LINE);
+ reader_kill(el, begin - buff, len, KILL_PREPEND, last_char!=R_BACKWARD_KILL_LINE);
}
break;
@@ -3338,33 +3420,33 @@ const wchar_t *reader_readline(void)
case R_KILL_WHOLE_LINE:
{
- const wchar_t *buff = data->command_line.c_str();
- const wchar_t *end = &buff[data->buff_pos];
- const wchar_t *begin = end;
- size_t len;
+ /* We match the emacs behavior here: "kills the entire line including the following newline" */
+ editable_line_t *el = data->active_edit_line();
+ const wchar_t *buff = el->text.c_str();
- while (begin > buff && *begin != L'\n')
+ /* Back up to the character just past the previous newline, or go to the beginning of the command line. Note that if the position is on a newline, visually this looks like the cursor is at the end of a line. Therefore that newline is NOT the beginning of a line; this justifies the -1 check. */
+ size_t begin = el->position;
+ while (begin > 0 && buff[begin-1] != L'\n')
+ {
begin--;
-
- if (*begin == L'\n')
- begin++;
-
- len = maxi<size_t>(end-begin, 0);
- begin = end - len;
-
- while (*end && *end != L'\n')
- end++;
-
- if (begin == end && *end)
+ }
+
+ /* Push end forwards to just past the next newline, or just past the last char. */
+ size_t end = el->position;
+ while (buff[end] != L'\0')
+ {
end++;
+ if (buff[end-1] == L'\n')
+ {
+ break;
+ }
+ }
+ assert(end >= begin);
- len = end-begin;
-
- if (len)
+ if (end > begin)
{
- reader_kill(begin - buff, len, KILL_APPEND, last_char!=R_KILL_WHOLE_LINE);
+ reader_kill(el, begin, end - begin, KILL_APPEND, last_char!=R_KILL_WHOLE_LINE);
}
-
break;
}
@@ -3372,7 +3454,7 @@ const wchar_t *reader_readline(void)
case R_YANK:
{
yank_str = kill_yank();
- insert_string(yank_str);
+ insert_string(data->active_edit_line(), yank_str);
yank_len = wcslen(yank_str);
break;
}
@@ -3386,7 +3468,7 @@ const wchar_t *reader_readline(void)
remove_backward();
yank_str = kill_yank_rotate();
- insert_string(yank_str);
+ insert_string(data->active_edit_line(), yank_str);
yank_len = wcslen(yank_str);
}
break;
@@ -3403,16 +3485,15 @@ const wchar_t *reader_readline(void)
{
//history_reset();
data->history_search.go_to_end();
- reader_set_buffer(data->search_buff.c_str(), data->search_buff.size());
+ reader_set_buffer(data->search_buff, data->search_buff.size());
}
else
{
reader_replace_current_token(data->search_buff.c_str());
}
data->search_buff.clear();
- reader_super_highlight_me_plenty(data->buff_pos);
- reader_repaint();
-
+ reader_super_highlight_me_plenty();
+ reader_repaint_needed();
}
break;
@@ -3432,9 +3513,10 @@ const wchar_t *reader_readline(void)
Remove the current character in the character buffer and on the
screen using syntax highlighting, etc.
*/
- if (data->buff_pos < data->command_length())
+ editable_line_t *el = data->active_edit_line();
+ if (el->position < el->size())
{
- data->buff_pos++;
+ update_buff_pos(el, el->position + 1);
remove_backward();
}
break;
@@ -3450,18 +3532,31 @@ const wchar_t *reader_readline(void)
/* Delete any autosuggestion */
data->autosuggestion.clear();
+ /* If the user hits return while navigating the pager, it only clears the pager */
+ if (data->is_navigating_pager_contents())
+ {
+ clear_pager();
+ break;
+ }
+
+ /* The user may have hit return with pager contents, but while not navigating them. Clear the pager in that event. */
+ clear_pager();
+
+ /* We only execute the command line */
+ editable_line_t *el = &data->command_line;
+
/* Allow backslash-escaped newlines, but only if the following character is whitespace, or we're at the end of the text (see issue #163) */
- if (is_backslashed(data->command_line, data->buff_pos))
+ if (is_backslashed(el->text, el->position))
{
- if (data->buff_pos >= data->command_length() || iswspace(data->command_line.at(data->buff_pos)))
+ if (el->position >= el->size() || iswspace(el->text.at(el->position)))
{
- insert_char('\n');
+ insert_char(el, '\n');
break;
}
}
/* See if this command is valid */
- int command_test_result = data->test_func(data->command_line.c_str());
+ int command_test_result = data->test_func(el->text.c_str());
if (command_test_result == 0 || command_test_result == PARSER_TEST_INCOMPLETE)
{
/* This command is valid, but an abbreviation may make it invalid. If so, we will have to test again. */
@@ -3469,8 +3564,11 @@ const wchar_t *reader_readline(void)
if (abbreviation_expanded)
{
/* It's our reponsibility to rehighlight and repaint. But everything we do below triggers a repaint. */
- reader_super_highlight_me_plenty(data->buff_pos);
- command_test_result = data->test_func(data->command_line.c_str());
+ command_test_result = data->test_func(el->text.c_str());
+
+ /* If the command is OK, then we're going to execute it. We still want to do syntax highlighting, but a synchronous variant that performs no I/O, so as not to block the user */
+ bool skip_io = (command_test_result == 0);
+ reader_super_highlight_me_plenty(0, skip_io);
}
}
@@ -3480,15 +3578,13 @@ const wchar_t *reader_readline(void)
case 0:
{
/* Finished command, execute it. Don't add items that start with a leading space. */
- if (! data->command_line.empty() && data->command_line.at(0) != L' ')
+ const editable_line_t *el = &data->command_line;
+ if (data->history != NULL && ! el->empty() && el->text.at(0) != L' ')
{
- if (data->history != NULL)
- {
- data->history->add_with_file_detection(data->command_line);
- }
+ data->history->add_with_file_detection(el->text);
}
finished=1;
- data->buff_pos=data->command_length();
+ update_buff_pos(&data->command_line, data->command_line.size());
reader_repaint();
break;
}
@@ -3498,7 +3594,7 @@ const wchar_t *reader_readline(void)
*/
case PARSER_TEST_INCOMPLETE:
{
- insert_char('\n');
+ insert_char(el, '\n');
break;
}
@@ -3510,7 +3606,7 @@ const wchar_t *reader_readline(void)
default:
{
s_reset(&data->screen, screen_reset_abandon_line);
- reader_repaint();
+ reader_repaint_needed();
break;
}
@@ -3526,7 +3622,6 @@ const wchar_t *reader_readline(void)
case R_HISTORY_TOKEN_SEARCH_FORWARD:
{
int reset = 0;
-
if (data->search_mode == NO_SEARCH)
{
reset = 1;
@@ -3540,7 +3635,8 @@ const wchar_t *reader_readline(void)
data->search_mode = TOKEN_SEARCH;
}
- data->search_buff.append(data->command_line);
+ const editable_line_t *el = &data->command_line;
+ data->search_buff.append(el->text);
data->history_search = history_search_t(*data->history, data->search_buff, HISTORY_SEARCH_TYPE_CONTAINS);
/* Skip the autosuggestion as history unless it was truncated */
@@ -3553,7 +3649,6 @@ const wchar_t *reader_readline(void)
switch (data->search_mode)
{
-
case LINE_SEARCH:
{
if ((c == R_HISTORY_SEARCH_BACKWARD) ||
@@ -3579,7 +3674,7 @@ const wchar_t *reader_readline(void)
{
new_text = data->history_search.current_string();
}
- set_command_line_and_position(new_text, new_text.size());
+ set_command_line_and_position(&data->command_line, new_text, new_text.size());
break;
}
@@ -3607,10 +3702,15 @@ const wchar_t *reader_readline(void)
/* Move left*/
case R_BACKWARD_CHAR:
{
- if (data->buff_pos > 0)
+ editable_line_t *el = data->active_edit_line();
+ if (data->is_navigating_pager_contents())
{
- data->buff_pos--;
- reader_repaint();
+ select_completion_in_direction(direction_west);
+ }
+ else if (el->position > 0)
+ {
+ update_buff_pos(el, el->position-1);
+ reader_repaint_needed();
}
break;
}
@@ -3618,10 +3718,15 @@ const wchar_t *reader_readline(void)
/* Move right*/
case R_FORWARD_CHAR:
{
- if (data->buff_pos < data->command_length())
+ editable_line_t *el = data->active_edit_line();
+ if (data->is_navigating_pager_contents())
{
- data->buff_pos++;
- reader_repaint();
+ select_completion_in_direction(direction_east);
+ }
+ else if (el->position < el->size())
+ {
+ update_buff_pos(el, el->position + 1);
+ reader_repaint_needed();
}
else
{
@@ -3636,30 +3741,31 @@ const wchar_t *reader_readline(void)
{
move_word_style_t style = (c == R_BACKWARD_KILL_PATH_COMPONENT ? move_word_style_path_components : move_word_style_punctuation);
bool newv = (last_char != R_BACKWARD_KILL_WORD && last_char != R_BACKWARD_KILL_PATH_COMPONENT);
- move_word(MOVE_DIR_LEFT, true /* erase */, style, newv);
+ move_word(data->active_edit_line(), MOVE_DIR_LEFT, true /* erase */, style, newv);
break;
}
/* kill one word right */
case R_KILL_WORD:
{
- move_word(MOVE_DIR_RIGHT, true /* erase */, move_word_style_punctuation, last_char!=R_KILL_WORD);
+ move_word(data->active_edit_line(), MOVE_DIR_RIGHT, true /* erase */, move_word_style_punctuation, last_char!=R_KILL_WORD);
break;
}
/* move one word left*/
case R_BACKWARD_WORD:
{
- move_word(MOVE_DIR_LEFT, false /* do not erase */, move_word_style_punctuation, false);
+ move_word(data->active_edit_line(), MOVE_DIR_LEFT, false /* do not erase */, move_word_style_punctuation, false);
break;
}
/* move one word right*/
case R_FORWARD_WORD:
{
- if (data->buff_pos < data->command_length())
+ editable_line_t *el = data->active_edit_line();
+ if (el->position < el->size())
{
- move_word(MOVE_DIR_RIGHT, false /* do not erase */, move_word_style_punctuation, false);
+ move_word(el, MOVE_DIR_RIGHT, false /* do not erase */, move_word_style_punctuation, false);
}
else
{
@@ -3670,12 +3776,13 @@ const wchar_t *reader_readline(void)
case R_BEGINNING_OF_HISTORY:
{
- data->history_search = history_search_t(*data->history, data->command_line, HISTORY_SEARCH_TYPE_PREFIX);
+ const editable_line_t *el = &data->command_line;
+ data->history_search = history_search_t(*data->history, el->text, HISTORY_SEARCH_TYPE_PREFIX);
data->history_search.go_to_beginning();
if (! data->history_search.is_at_end())
{
wcstring new_text = data->history_search.current_string();
- set_command_line_and_position(new_text, new_text.size());
+ set_command_line_and_position(&data->command_line, new_text, new_text.size());
}
break;
@@ -3690,38 +3797,71 @@ const wchar_t *reader_readline(void)
case R_UP_LINE:
case R_DOWN_LINE:
{
- int line_old = parse_util_get_line_from_offset(data->command_line, data->buff_pos);
- int line_new;
+ if (data->is_navigating_pager_contents())
+ {
+ /* We are already navigating pager contents. */
+ selection_direction_t direction;
+ if (c == R_DOWN_LINE)
+ {
+ /* Down arrow is always south */
+ direction = direction_south;
+ }
+ else if (selection_is_at_top())
+ {
+ /* Up arrow, but we are in the first column and first row. End navigation */
+ direction = direction_deselect;
+ }
+ else
+ {
+ /* Up arrow, go north */
+ direction = direction_north;
+ }
- if (c == R_UP_LINE)
- line_new = line_old-1;
+ /* Now do the selection */
+ select_completion_in_direction(direction);
+ }
+ else if (c == R_DOWN_LINE && ! data->pager.empty())
+ {
+ /* We pressed down with a non-empty pager contents, begin navigation */
+ select_completion_in_direction(direction_south);
+ }
else
- line_new = line_old+1;
+ {
+ /* Not navigating the pager contents */
+ editable_line_t *el = data->active_edit_line();
+ int line_old = parse_util_get_line_from_offset(el->text, el->position);
+ int line_new;
- int line_count = parse_util_lineno(data->command_line.c_str(), data->command_length())-1;
+ if (c == R_UP_LINE)
+ line_new = line_old-1;
+ else
+ line_new = line_old+1;
- if (line_new >= 0 && line_new <= line_count)
- {
- size_t base_pos_new;
- size_t base_pos_old;
+ int line_count = parse_util_lineno(el->text.c_str(), el->size())-1;
+
+ if (line_new >= 0 && line_new <= line_count)
+ {
+ size_t base_pos_new;
+ size_t base_pos_old;
- int indent_old;
- int indent_new;
- size_t line_offset_old;
- size_t total_offset_new;
+ int indent_old;
+ int indent_new;
+ size_t line_offset_old;
+ size_t total_offset_new;
- base_pos_new = parse_util_get_offset_from_line(data->command_line, line_new);
+ base_pos_new = parse_util_get_offset_from_line(el->text, line_new);
- base_pos_old = parse_util_get_offset_from_line(data->command_line, line_old);
+ base_pos_old = parse_util_get_offset_from_line(el->text, line_old);
- assert(base_pos_new != (size_t)(-1) && base_pos_old != (size_t)(-1));
- indent_old = data->indents.at(base_pos_old);
- indent_new = data->indents.at(base_pos_new);
+ assert(base_pos_new != (size_t)(-1) && base_pos_old != (size_t)(-1));
+ indent_old = data->indents.at(base_pos_old);
+ indent_new = data->indents.at(base_pos_new);
- line_offset_old = data->buff_pos - parse_util_get_offset_from_line(data->command_line, line_old);
- total_offset_new = parse_util_get_offset(data->command_line, line_new, line_offset_old - 4*(indent_new-indent_old));
- data->buff_pos = total_offset_new;
- reader_repaint();
+ line_offset_old = el->position - parse_util_get_offset_from_line(el->text, line_old);
+ total_offset_new = parse_util_get_offset(el->text, line_new, line_offset_old - 4*(indent_new-indent_old));
+ update_buff_pos(el, total_offset_new);
+ reader_repaint_needed();
+ }
}
break;
@@ -3731,7 +3871,7 @@ const wchar_t *reader_readline(void)
{
data->suppress_autosuggestion = true;
data->autosuggestion.clear();
- reader_repaint();
+ reader_repaint_needed();
break;
}
@@ -3743,41 +3883,46 @@ const wchar_t *reader_readline(void)
case R_TRANSPOSE_CHARS:
{
- if (data->command_length() < 2)
+ editable_line_t *el = data->active_edit_line();
+ if (el->size() < 2)
{
break;
}
/* If the cursor is at the end, transpose the last two characters of the line */
- if (data->buff_pos == data->command_length())
+ if (el->position == el->size())
{
- data->buff_pos--;
+ update_buff_pos(el, el->position - 1);
}
/*
Drag the character before the cursor forward over the character at the cursor, moving
the cursor forward as well.
*/
- if (data->buff_pos > 0)
+ if (el->position > 0)
{
- wcstring local_cmd = data->command_line;
- std::swap(local_cmd.at(data->buff_pos), local_cmd.at(data->buff_pos-1));
- set_command_line_and_position(local_cmd, data->buff_pos + 1);
+ wcstring local_cmd = el->text;
+ std::swap(local_cmd.at(el->position), local_cmd.at(el->position-1));
+ set_command_line_and_position(el, local_cmd, el->position + 1);
}
break;
}
case R_TRANSPOSE_WORDS:
{
- size_t len = data->command_length();
- const wchar_t *buff = data->command_line.c_str();
+ editable_line_t *el = data->active_edit_line();
+ size_t len = el->size();
+ const wchar_t *buff = el->text.c_str();
const wchar_t *tok_begin, *tok_end, *prev_begin, *prev_end;
/* If we are not in a token, look for one ahead */
- while (data->buff_pos != len && !iswalnum(buff[data->buff_pos]))
- data->buff_pos++;
+ size_t buff_pos = el->position;
+ while (buff_pos != len && !iswalnum(buff[buff_pos]))
+ buff_pos++;
+
+ update_buff_pos(el, buff_pos);
- parse_util_token_extent(buff, data->buff_pos, &tok_begin, &tok_end, &prev_begin, &prev_end);
+ parse_util_token_extent(buff, el->position, &tok_begin, &tok_end, &prev_begin, &prev_end);
/* In case we didn't find a token at or after the cursor... */
if (tok_begin == &buff[len])
@@ -3802,57 +3947,143 @@ const wchar_t *reader_readline(void)
new_buff.append(prev);
new_buff.append(trail);
/* Put cursor right after the second token */
- set_command_line_and_position(new_buff, tok_end - buff);
+ set_command_line_and_position(el, new_buff, tok_end - buff);
}
break;
}
-
+
case R_UPCASE_WORD:
case R_DOWNCASE_WORD:
case R_CAPITALIZE_WORD:
{
+ editable_line_t *el = data->active_edit_line();
// For capitalize_word, whether we've capitalized a character so far
bool capitalized_first = false;
-
+
// We apply the operation from the current location to the end of the word
- size_t pos = data->buff_pos;
- move_word(MOVE_DIR_RIGHT, false, move_word_style_punctuation, false);
- for (; pos < data->buff_pos; pos++)
+ size_t pos = el->position;
+ move_word(el, MOVE_DIR_RIGHT, false, move_word_style_punctuation, false);
+ for (; pos < el->position; pos++)
{
- wchar_t chr = data->command_line.at(pos);
-
+ wchar_t chr = el->text.at(pos);
+
// We always change the case; this decides whether we go uppercase (true) or lowercase (false)
bool make_uppercase;
if (c == R_CAPITALIZE_WORD)
make_uppercase = ! capitalized_first && iswalnum(chr);
else
make_uppercase = (c == R_UPCASE_WORD);
-
+
// Apply the operation and then record what we did
if (make_uppercase)
chr = towupper(chr);
else
chr = towlower(chr);
-
- data->command_line.at(pos) = chr;
+
+ data->command_line.text.at(pos) = chr;
capitalized_first = capitalized_first || make_uppercase;
}
- data->command_line_changed();
- reader_super_highlight_me_plenty(data->buff_pos);
- reader_repaint();
+ data->command_line_changed(el);
+ reader_super_highlight_me_plenty();
+ reader_repaint_needed();
break;
}
-
+
+ case R_BEGIN_SELECTION:
+ {
+ data->sel_active = true;
+ data->sel_begin_pos = data->command_line.position;
+ data->sel_start_pos = data->command_line.position;
+ data->sel_stop_pos = data->command_line.position;
+ break;
+ }
+
+ case R_END_SELECTION:
+ {
+ data->sel_active = false;
+ data->sel_start_pos = data->command_line.position;
+ data->sel_stop_pos = data->command_line.position;
+ break;
+ }
+
+ case R_KILL_SELECTION:
+ {
+ bool newv = (last_char != R_KILL_SELECTION);
+ size_t start, len;
+ if (reader_get_selection(&start, &len))
+ {
+ reader_kill(&data->command_line, start, len, KILL_APPEND, newv);
+ }
+ break;
+ }
+
+ case R_FORWARD_JUMP:
+ {
+ editable_line_t *el = data->active_edit_line();
+ wchar_t target = input_function_pop_arg();
+ bool status = false;
+
+ for (size_t i = el->position + 1; i < el->size(); i++)
+ {
+ if (el->at(i) == target)
+ {
+ update_buff_pos(el, i);
+ status = true;
+ break;
+ }
+ }
+ input_function_set_status(status);
+ reader_repaint_needed();
+ break;
+ }
+
+ case R_BACKWARD_JUMP:
+ {
+ editable_line_t *el = data->active_edit_line();
+ wchar_t target = input_function_pop_arg();
+ bool status = false;
+
+ size_t tmp_pos = el->position;
+ while (tmp_pos--)
+ {
+ if (el->at(tmp_pos) == target)
+ {
+ update_buff_pos(el, tmp_pos);
+ status = true;
+ break;
+ }
+ }
+ input_function_set_status(status);
+ reader_repaint_needed();
+ break;
+ }
+
/* Other, if a normal character, we add it to the command */
default:
{
if ((!wchar_private(c)) && (((c>31) || (c==L'\n'))&& (c != 127)))
{
- /* Expand abbreviations after space */
- bool should_expand_abbreviations = (c == L' ');
+ bool allow_expand_abbreviations = false;
+ if (data->is_navigating_pager_contents())
+ {
+ data->pager.set_search_field_shown(true);
+ }
+ else
+ {
+ allow_expand_abbreviations = true;
+ }
/* Regular character */
- insert_char(c, should_expand_abbreviations);
+ editable_line_t *el = data->active_edit_line();
+ insert_char(data->active_edit_line(), c, allow_expand_abbreviations);
+
+ /* End paging upon inserting into the normal command line */
+ if (el == &data->command_line)
+ {
+ clear_pager();
+ }
+
+
}
else
{
@@ -3881,10 +4112,20 @@ const wchar_t *reader_readline(void)
}
last_char = c;
+
+ reader_repaint_if_needed();
}
writestr(L"\n");
+ /* Ensure we have no pager contents when we exit */
+ if (! data->pager.empty())
+ {
+ /* Clear to end of screen to erase the pager contents. TODO: this may fail if eos doesn't exist, in which case we should emit newlines */
+ screen_force_clear_to_end();
+ data->pager.clear();
+ }
+
if (!reader_exit_forced())
{
if (tcsetattr(0,TCSANOW,&old_modes)) /* return to previous mode */
@@ -3895,7 +4136,7 @@ const wchar_t *reader_readline(void)
set_color(rgb_color_t::reset(), rgb_color_t::reset());
}
- return finished ? data->command_line.c_str() : 0;
+ return finished ? data->command_line.text.c_str() : NULL;
}
int reader_search_mode()
@@ -3908,6 +4149,15 @@ int reader_search_mode()
return !!data->search_mode;
}
+int reader_has_pager_contents()
+{
+ if (!data)
+ {
+ return -1;
+ }
+
+ return ! data->current_page_rendering.screen_data.empty();
+}
/**
Read non-interactively. Read input from stdin without displaying
@@ -3964,7 +4214,7 @@ static int read_ni(int fd, const io_chain_t &io)
acc.insert(acc.end(), buff, buff + c);
}
- const wcstring str = acc.empty() ? wcstring() : str2wcstring(&acc.at(0), acc.size());
+ wcstring str = acc.empty() ? wcstring() : str2wcstring(&acc.at(0), acc.size());
acc.clear();
if (fclose(in_stream))
@@ -3975,13 +4225,21 @@ static int read_ni(int fd, const io_chain_t &io)
res = 1;
}
- wcstring sb;
- if (! parser.test(str.c_str(), 0, &sb, L"fish"))
+ /* Swallow a BOM (#1518) */
+ if (! str.empty() && str.at(0) == UTF8_BOM_WCHAR)
+ {
+ str.erase(0, 1);
+ }
+
+ parse_error_list_t errors;
+ if (! parse_util_detect_errors(str, &errors, false /* do not accept incomplete */))
{
parser.eval(str, io, TOP);
}
else
{
+ wcstring sb;
+ parser.get_backtrace(str, errors, &sb);
fwprintf(stderr, L"%ls", sb.c_str());
res = 1;
}