From c8d5131a42b8117987d2a278d69edb21a7cf9383 Mon Sep 17 00:00:00 2001 From: Julian Aron Prenner Date: Wed, 15 Jan 2014 15:07:22 +0100 Subject: [PATCH] Add experimental support for selection and visual mode --- builtin_commandline.cpp | 29 ++++++ highlight.h | 5 + input.cpp | 10 +- input.h | 4 +- reader.cpp | 111 ++++++++++++++++++---- reader.h | 7 ++ screen.cpp | 4 +- screen.h | 6 +- share/functions/fish_vi_key_bindings.fish | 17 ++++ share/functions/fish_vi_prompt.fish | 3 + 10 files changed, 172 insertions(+), 24 deletions(-) diff --git a/builtin_commandline.cpp b/builtin_commandline.cpp index f6dc2b031..39a5037bd 100644 --- a/builtin_commandline.cpp +++ b/builtin_commandline.cpp @@ -211,6 +211,7 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv) int append_mode=0; int function_mode = 0; + int selection_mode = 0; int tokenize = 0; @@ -315,6 +316,10 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv) L"search-mode", no_argument, 0, 'S' } , + { + L"selection", no_argument, 0, 's' + } + , { 0, 0, 0, 0 } @@ -402,6 +407,10 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv) search_mode = 1; break; + case 's': + selection_mode = 1; + break; + case 'h': builtin_print_help(parser, argv[0], stdout_buffer); return 0; @@ -465,6 +474,26 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv) return 0; } + if (selection_mode) + { + size_t sel_start, sel_stop; + const wchar_t *buffer = reader_get_buffer(); + if(reader_get_selection_pos(sel_start, sel_stop)) + { + size_t len = std::min(sel_stop - sel_start + 1, wcslen(buffer)); + wchar_t *selection = new wchar_t[len]; + selection = wcsncpy(selection, current_buffer + sel_start, len); + + append_format(stdout_buffer, selection); + delete selection; + } + else + { + append_format(stdout_buffer, L""); + } + return 0; + } + /* Check for invalid switch combinations */ diff --git a/highlight.h b/highlight.h index 6747bba51..00b4d4b9b 100644 --- a/highlight.h +++ b/highlight.h @@ -70,6 +70,11 @@ */ #define HIGHLIGHT_AUTOSUGGESTION 0x2000 +/** + Internal value representing highlighting an active selection +*/ +#define HIGHLIGHT_SELECTION 0x80 + class history_item_t; struct file_detection_context_t; diff --git a/input.cpp b/input.cpp index ba0f6035a..75e0b33c2 100644 --- a/input.cpp +++ b/input.cpp @@ -137,7 +137,9 @@ static const wchar_t * const name_arr[] = L"up-line", L"down-line", L"suppress-autosuggestion", - L"accept-autosuggestion" + L"accept-autosuggestion", + L"begin-selection", + L"end-selection", } ; @@ -227,7 +229,9 @@ static const wchar_t code_arr[] = R_UP_LINE, R_DOWN_LINE, R_SUPPRESS_AUTOSUGGESTION, - R_ACCEPT_AUTOSUGGESTION + R_ACCEPT_AUTOSUGGESTION, + R_BEGIN_SELECTION, + R_END_SELECTION } ; @@ -265,7 +269,7 @@ const wchar_t *input_get_bind_mode() const env_var_t bind_mode_var = env_get_string(FISH_BIND_MODE_VAR); if(!bind_mode_var.missing()) { - bind_mode = bind_mode_var.c_str(); + bind_mode = bind_mode_var.c_str(); } return bind_mode; } diff --git a/input.h b/input.h index fdcb82b45..78f7ec72c 100644 --- a/input.h +++ b/input.h @@ -61,11 +61,13 @@ enum R_DOWN_LINE, R_SUPPRESS_AUTOSUGGESTION, R_ACCEPT_AUTOSUGGESTION, + R_BEGIN_SELECTION, + R_END_SELECTION } ; #define R_MIN R_NULL -#define R_MAX R_ACCEPT_AUTOSUGGESTION +#define R_MAX R_END_SELECTION /** Initialize the terminal by calling setupterm, and set up arrays diff --git a/reader.cpp b/reader.cpp index 37f35fac7..34fe0d23a 100644 --- a/reader.cpp +++ b/reader.cpp @@ -172,6 +172,13 @@ commence. History search mode. This value means we are searching forwards. */ #define SEARCH_FORWARD 1 +/** + The color used to display an active selection + */ +#define FISH_SELECTION_COLOR_VAR L"fish-selection-color" + +/** The color used if the variable above is not set */ +#define DEFAULT_SELECTION_COLOR L"gray" /* Any time the contents of a buffer changes, we update the generation count. This allows for our background highlighting thread to notice it and skip doing work that it would otherwise have to do. This variable should really be of some kind of interlocked or atomic type that guarantees we're not reading stale cache values. With C++11 we should use atomics, but until then volatile should work as well, at least on x86.*/ static volatile unsigned int s_generation_count; @@ -258,6 +265,9 @@ public: /** 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; @@ -349,6 +359,7 @@ public: search_pos(0), buff_pos(0), sel_active(0), + sel_begin_pos(0), sel_start_pos(0), sel_stop_pos(0), complete_func(0), @@ -448,9 +459,22 @@ static void term_donate() /** Update the cursor position */ -static void update_buff_pos(int buff_pos) +static void update_buff_pos(size_t buff_pos) { data->buff_pos = buff_pos; + if(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; + } + } } @@ -536,7 +560,6 @@ 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() { // Update the indentation @@ -552,6 +575,15 @@ static void reader_repaint() std::vector colors = data->colors; colors.resize(len, HIGHLIGHT_AUTOSUGGESTION); + if(data->sel_active) + { + int selection_color = HIGHLIGHT_SELECTION << 16; + for(int i = data->sel_start_pos; i <= std::min(len - 1, data->sel_stop_pos); i++) + { + colors[i] = selection_color; + } + } + std::vector indents = data->indents; indents.resize(len); @@ -562,11 +594,14 @@ static void reader_repaint() data->command_length(), &colors[0], &indents[0], - data->buff_pos); + data->buff_pos, + data->sel_start_pos, + data->sel_stop_pos); data->repaint_needed = false; } + static void reader_repaint_without_autosuggestion() { // Swap in an empty autosuggestion, repaint, then swap it out @@ -606,7 +641,7 @@ static void reader_kill(size_t begin_idx, size_t length, int mode, int newv) { /* 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; + update_buff_pos(data->buff_pos - backtrack); } data->command_line.erase(begin_idx, length); @@ -1101,7 +1136,7 @@ static void remove_backward() int width; do { - data->buff_pos -= 1; + update_buff_pos(data->buff_pos - 1); width = fish_wcwidth(data->command_line.at(data->buff_pos)); data->command_line.erase(data->buff_pos, 1); } @@ -1129,7 +1164,7 @@ static bool insert_string(const wcstring &str, bool should_expand_abbreviations return false; data->command_line.insert(data->buff_pos, str); - data->buff_pos += len; + update_buff_pos(data->buff_pos + len); data->command_line_changed(); data->suppress_autosuggestion = false; @@ -2475,6 +2510,26 @@ size_t reader_get_cursor_pos() return data->buff_pos; } +bool reader_get_selection_pos(size_t &start, size_t &stop) +{ + if (!data) + { + return false; + } + else + { + if(!data->sel_active) + return false; + else + { + start = data->sel_start_pos; + stop = data->sel_stop_pos; + return true; + } + } +} + + #define ENV_CMD_DURATION L"CMD_DURATION" void set_env_cmd_duration(struct timeval *after, struct timeval *before) @@ -2572,6 +2627,8 @@ int reader_shell_test(const wchar_t *b) 0, tmp, tmp2, + 0, + 0, 0); @@ -2945,7 +3002,7 @@ static int read_i(void) else if (tmp) { wcstring command = tmp; - data->buff_pos=0; + update_buff_pos(0); data->command_line.clear(); data->command_line_changed(); reader_run_command(parser, command); @@ -3136,7 +3193,7 @@ const wchar_t *reader_readline(void) while ((data->buff_pos>0) && (buff[data->buff_pos-1] != L'\n')) { - data->buff_pos--; + update_buff_pos(data->buff_pos - 1); } reader_repaint(); @@ -3150,7 +3207,7 @@ const wchar_t *reader_readline(void) while (buff[data->buff_pos] && buff[data->buff_pos] != L'\n') { - data->buff_pos++; + update_buff_pos(data->buff_pos + 1); } } else @@ -3174,8 +3231,6 @@ const wchar_t *reader_readline(void) /* go to EOL*/ case R_END_OF_BUFFER: { - data->buff_pos = data->command_length(); - update_buff_pos(data->command_length()); reader_repaint(); @@ -3442,7 +3497,7 @@ const wchar_t *reader_readline(void) */ if (data->buff_pos < data->command_length()) { - data->buff_pos++; + update_buff_pos(data->buff_pos + 1); remove_backward(); } break; @@ -3496,7 +3551,7 @@ const wchar_t *reader_readline(void) } } finished=1; - data->buff_pos=data->command_length(); + update_buff_pos(data->command_length()); reader_repaint(); break; } @@ -3617,7 +3672,7 @@ const wchar_t *reader_readline(void) { if (data->buff_pos > 0) { - data->buff_pos--; + update_buff_pos(data->buff_pos - 1); reader_repaint(); } break; @@ -3628,7 +3683,7 @@ const wchar_t *reader_readline(void) { if (data->buff_pos < data->command_length()) { - data->buff_pos++; + update_buff_pos(data->buff_pos + 1); reader_repaint(); } else @@ -3760,7 +3815,7 @@ const wchar_t *reader_readline(void) /* If the cursor is at the end, transpose the last two characters of the line */ if (data->buff_pos == data->command_length()) { - data->buff_pos--; + update_buff_pos(data->buff_pos - 1); } /* @@ -3783,8 +3838,11 @@ const wchar_t *reader_readline(void) 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 = data->buff_pos; + while (buff_pos != len && !iswalnum(buff[buff_pos])) + buff_pos++; + + update_buff_pos(buff_pos); parse_util_token_extent(buff, data->buff_pos, &tok_begin, &tok_end, &prev_begin, &prev_end); @@ -3852,6 +3910,23 @@ const wchar_t *reader_readline(void) break; } + case R_BEGIN_SELECTION: + { + data->sel_active = true; + data->sel_begin_pos = data->buff_pos; + data->sel_start_pos = data->buff_pos; + data->sel_stop_pos = data->buff_pos; + break; + } + + case R_END_SELECTION: + { + data->sel_active = false; + data->sel_start_pos = data->buff_pos; + data->sel_stop_pos = data->buff_pos; + break; + } + /* Other, if a normal character, we add it to the command */ default: { diff --git a/reader.h b/reader.h index b954c1bea..7e1cadf68 100644 --- a/reader.h +++ b/reader.h @@ -114,6 +114,13 @@ void reader_set_buffer(const wcstring &b, size_t p); */ size_t reader_get_cursor_pos(); + +/** + Get the current selection range in the command line. + Returns false if there is no active selection, true otherwise. +*/ +bool reader_get_selection_pos(size_t &start, size_t &stop); + /** Return the value of the interrupted flag, which is set by the sigint handler, and clear it if it was set. diff --git a/screen.cpp b/screen.cpp index 711aebabd..9db678ffc 100644 --- a/screen.cpp +++ b/screen.cpp @@ -1234,7 +1234,9 @@ void s_write(screen_t *s, size_t explicit_len, const int *colors, const int *indent, - size_t cursor_pos) + size_t cursor_pos, + size_t sel_start_pos, + size_t sel_stop_pos) { screen_data_t::cursor_t cursor_arr; diff --git a/screen.h b/screen.h index ef74383d7..3b2421472 100644 --- a/screen.h +++ b/screen.h @@ -181,6 +181,8 @@ public: \param colors the colors to use for the comand line \param indent the indent to use for the command line \param cursor_pos where the cursor is + \param sel_start_pos where the selections starts (inclusive) + \param sel_stop_pos where the selections ends (inclusive) */ void s_write(screen_t *s, const wcstring &left_prompt, @@ -189,7 +191,9 @@ void s_write(screen_t *s, size_t explicit_len, const int *colors, const int *indent, - size_t cursor_pos); + size_t cursor_pos, + size_t sel_start_pos, + size_t sel_stop_pos); /** This function resets the screen buffers internal knowledge about diff --git a/share/functions/fish_vi_key_bindings.fish b/share/functions/fish_vi_key_bindings.fish index 9d8f3f5b1..bd0309673 100644 --- a/share/functions/fish_vi_key_bindings.fish +++ b/share/functions/fish_vi_key_bindings.fish @@ -28,6 +28,7 @@ function fish_vi_key_bindings -d "vi-like key bindings for fish" bind -m insert I beginning-of-line force-repaint bind -m insert a forward-char force-repaint bind -m insert A end-of-line force-repaint + bind -m visual v begin-selection force-repaint bind -m insert o "commandline -a \n" down-line force-repaint #bind -m insert O beginning-of-line "commandline -i \n" up-line force-repaint # doesn't work @@ -148,4 +149,20 @@ function fish_vi_key_bindings -d "vi-like key bindings for fish" bind -M insert \cd exit bind -M insert \ef forward-word + + + + # + # visual mode + # + + bind -M visual \e\[C forward-char + bind -M visual \e\[D backward-char + bind -M visual -k right forward-char + bind -M visual -k left backward-char + bind -M visual h backward-char + bind -M visual l forward-char + + bind -M visual -m default \cc end-selection force-repaint + bind -M visual -m default \e end-selection force-repaint end diff --git a/share/functions/fish_vi_prompt.fish b/share/functions/fish_vi_prompt.fish index bae594cf6..fe780e406 100644 --- a/share/functions/fish_vi_prompt.fish +++ b/share/functions/fish_vi_prompt.fish @@ -7,6 +7,9 @@ function fish_vi_prompt_cm --description "Displays the current mode" case insert set_color --bold --background red white echo "[I]" + case visual + set_color --bold --background magenta white + echo "[V]" end set_color normal end