diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index 648d6255d..3dc3c9c2a 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -1679,6 +1679,60 @@ static bool guac_terminal_is_blank(int ascii_char) { return (ascii_char == '\0' || ascii_char == ' '); } +/** + * Set the row tail/head and col tail/head according to the value of current + * row/col and selection_initial_row/selection_initial_column. This aims to + * keep the initially selected word when dragging mouse after a double click. + * + * @param terminal + * The terminal that received a double click event. + * + * @param col + * The column where is currently the mouse. + * + * @param row + * The row where is currently the mouse. + * + * @param col_head + * Pointer where to write the calculated head column. + * + * @param col_tail + * Pointer where to write the calculated tail column. + * + * @param row_head + * Pointer where to write the calculated head row. + * + * @param row_tail + * Pointer where to write the calculated tail row. + */ +static void guac_terminal_word_initial_position(guac_terminal* terminal, + int col, int row, int* col_head, int* col_tail, int* row_head, int* row_tail) { + + /* The mouse is still in the intial row */ + if (row == terminal->selection_initial_row) { + + /* Mouse on the right of initial column */ + if (col > terminal->selection_initial_column) + *col_head = terminal->selection_initial_column; + + /* Mouse on the left of initial column */ + else + *col_tail = terminal->selection_initial_column; + } + + /* Use initial row as bottom-right of the selection and go up/left */ + if (row < terminal->selection_initial_row) { + *row_tail = terminal->selection_initial_row; + *col_tail = terminal->selection_initial_column; + } + + /* Use initial row/col as top-left of the selection and go down/right */ + if (row > terminal->selection_initial_row) { + *row_head = terminal->selection_initial_row; + *col_head = terminal->selection_initial_column; + } +} + /** * Selection of a word during a double click event. * - Fetching the character under the mouse cursor. @@ -1698,53 +1752,96 @@ static bool guac_terminal_is_blank(int ascii_char) { * * @param col * The column where is the mouse at the double click event. + * + * @param hold + * True when user hold left click, false otherwise. */ -static void guac_terminal_double_click(guac_terminal* terminal, int row, int col) { +static void guac_terminal_double_click(guac_terminal* terminal, int row, int col, bool hold) { + /* To store buffer row characters */ guac_terminal_char* characters; + /* To get wrapped buffer row status */ bool is_wrapped; - int length = guac_terminal_buffer_get_columns(terminal->current_buffer, &characters, &is_wrapped, row); + /* Length of the buffer row */ + int length; + /* Character read at a position */ + int current_char; - if (col >= length) - return; + /* Position of the word behind cursor */ + int word_col_head; + int word_col_tail; + int word_row_head; + int word_row_tail; - /* Get buffer row and char behind cursor */ - int current_char = characters[col].value; + /* The function used to calculate the word borders */ + bool (*is_part_of_word_top_left)(int) = NULL; + bool (*is_part_of_word_bottom_right)(int) = NULL; - /* Position of the word behind cursor. - * Default = col/row required to select a char if not a word and not blank. */ - int word_col_head = col; - int word_col_tail = col; - int word_row_head = row; - int word_row_tail = row; - int flag; + /* Event to exit main loop */ + bool exit_loop = false; - /* The function used to calculate the word borders */ - bool (*is_part_of_word)(int) = NULL; + do { + /* Default = col/row required to select a char if not a word and not blank */ + word_col_head = col; + word_col_tail = col; + word_row_head = row; + word_row_tail = row; + + /* User holds left click: update default selection boundaries */ + if (hold) + guac_terminal_word_initial_position(terminal, col, row, &word_col_head, + &word_col_tail, &word_row_head, &word_row_tail); + + /* Get the right function when is_part_of_word_top_left is NULL */ + if (is_part_of_word_top_left == NULL) { + + /* Get top row */ + length = guac_terminal_buffer_get_columns(terminal->current_buffer, + &characters, &is_wrapped, word_row_head); + + /* Out of bounds */ + if (col >= length) + return; + + /* Get char on top-left of default selection */ + current_char = characters[word_col_head].value; + + /* If selection is on a word, get its borders */ + if (guac_terminal_is_part_of_word_or_uri(current_char)) + is_part_of_word_top_left = guac_terminal_is_part_of_word_or_uri; + + /* If selection is on a blank, get its borders */ + else if (guac_terminal_is_blank(current_char)) + is_part_of_word_top_left = guac_terminal_is_blank; + } - /* If selection is on a word, get its borders */ - if (guac_terminal_is_part_of_word_or_uri(current_char)) - is_part_of_word = guac_terminal_is_part_of_word_or_uri; + /* Get the right function when is_part_of_word_bottom_right is NULL */ + if (is_part_of_word_bottom_right == NULL) { - /* If selection is on a blank, get its borders */ - else if (guac_terminal_is_blank(current_char)) - is_part_of_word = guac_terminal_is_blank; + /* Get top row */ + length = guac_terminal_buffer_get_columns(terminal->current_buffer, + &characters, &is_wrapped, word_row_tail); - if (is_part_of_word != NULL) { + /* Out of bounds */ + if (col >= length) + return; - /* Event to exit loop */ - bool exit_loop = false; + /* Get char on top-left of default selection */ + current_char = characters[word_col_tail].value; - do { + /* If selection is on a word, get its borders */ + if (guac_terminal_is_part_of_word_or_uri(current_char)) + is_part_of_word_bottom_right = guac_terminal_is_part_of_word_or_uri; + + /* If selection is on a blank, get its borders */ + else if (guac_terminal_is_blank(current_char)) + is_part_of_word_bottom_right = guac_terminal_is_blank; + } - /* Position of the word behind cursor. - * Default = col/row required to select a char if not a word and not blank. */ - word_col_head = col; - word_col_tail = col; - word_row_head = row; - word_row_tail = row; + /* Get top left word bounds whether current char is a part of word or URI */ + if (is_part_of_word_top_left != NULL) { - /* Get word head*/ + /* Get word head */ do { /* Buffer row to get */ @@ -1756,7 +1853,8 @@ static void guac_terminal_double_click(guac_terminal* terminal, int row, int col current_row--; /* Get current buffer row */ - length = guac_terminal_buffer_get_columns(terminal->current_buffer, &characters, &is_wrapped, current_row); + length = guac_terminal_buffer_get_columns(terminal->current_buffer, + &characters, &is_wrapped, current_row); /* If we are on the previous row */ if (current_row < word_row_head) { @@ -1770,10 +1868,10 @@ static void guac_terminal_double_click(guac_terminal* terminal, int row, int col } /* Get char of the current row/column */ - flag = characters[current_col-1].value; + current_char = characters[current_col-1].value; /* Word boundary reached, stop */ - if (!is_part_of_word(flag)) + if (!is_part_of_word_top_left(current_char)) break; /* Store new position on previous row */ @@ -1783,12 +1881,17 @@ static void guac_terminal_double_click(guac_terminal* terminal, int row, int col } } while (word_col_head >= 0 && word_col_head--); + } + + /* Get bottom right word bounds whether current char is a part of word or URI */ + if (is_part_of_word_bottom_right != NULL) { /* Get word tail */ do { /* Get current buffer row */ - length = guac_terminal_buffer_get_columns(terminal->current_buffer, &characters, &is_wrapped, word_row_tail); + length = guac_terminal_buffer_get_columns(terminal->current_buffer, + &characters, &is_wrapped, word_row_tail); /* Bound of screen reached and row is wrapped: get next row */ if (word_col_tail == length - 1 && is_wrapped) { @@ -1796,19 +1899,20 @@ static void guac_terminal_double_click(guac_terminal* terminal, int row, int col /* Get next buffer row */ bool next_is_wrapped; guac_terminal_char* next_characters; - guac_terminal_buffer_get_columns(terminal->current_buffer, &next_characters, &next_is_wrapped, word_row_tail + 1); + guac_terminal_buffer_get_columns(terminal->current_buffer, + &next_characters, &next_is_wrapped, word_row_tail + 1); /* Get first char of the next row */ - flag = next_characters[0].value; + current_char = next_characters[0].value; } /* Otherwise, get char of next column on current row */ else - flag = characters[word_col_tail+1].value; + current_char = characters[word_col_tail+1].value; /* Word boundary reached, stop */ - if (!is_part_of_word(flag)) + if (!is_part_of_word_bottom_right(current_char)) break; /* Store new position on next row */ @@ -1822,80 +1926,82 @@ static void guac_terminal_double_click(guac_terminal* terminal, int row, int col word_col_tail++; } while (word_col_tail <= length); + } - /* The following is only for URL scheme validation */ - if (is_part_of_word != guac_terminal_is_part_of_word_or_uri) - break; - - /* Temp vars to avoid overwrite word_row_head and word_col_head */ - int tmp_row = word_row_head; - int tmp_col = word_col_head; + /* The following is only for URL scheme validation */ + if (is_part_of_word_top_left != guac_terminal_is_part_of_word_or_uri + && is_part_of_word_bottom_right != guac_terminal_is_part_of_word_or_uri) + break; - /* Check for the presence of a uri scheme like /^[a-z]+\:\/{2}/ */ - do { + /* Temp vars to avoid overwrite word_row_head and word_col_head */ + int tmp_row = word_row_head; + int tmp_col = word_col_head; - /* Get first char of first row */ - length = guac_terminal_buffer_get_columns(terminal->current_buffer, &characters, &is_wrapped, tmp_row); - current_char = characters[tmp_col].value; + /* Check for the presence of a uri scheme like /^[a-z]+\:\/{2}/ */ + do { - /* End of [a-z]+ part */ - if (current_char < 'a' || current_char > 'z') { + /* Get first char of first row */ + length = guac_terminal_buffer_get_columns(terminal->current_buffer, + &characters, &is_wrapped, tmp_row); + current_char = characters[tmp_col].value; - /* URI scheme delimiter :// foud */ - if (current_char == ':' && - characters[tmp_col+1].value == '/' && - characters[tmp_col+2].value == '/') { + /* End of [a-z]+ part */ + if (current_char < 'a' || current_char > 'z') { - /* Set exit event */ - exit_loop = true; - break; - } + /* URI scheme delimiter :// foud */ + if (current_char == ':' && + characters[tmp_col+1].value == '/' && + characters[tmp_col+2].value == '/') { - /* Not URI scheme */ - else - break; + /* Set exit event */ + exit_loop = true; + break; } - /* End of buffer row */ - else if (tmp_col == length-1) { - - /* Confinue only if current buffer row is wrapped */ - if (is_wrapped) { + /* Not URI scheme */ + else + break; + } - /* Stop if latest row */ - if (tmp_row == word_row_tail) - break; + /* End of buffer row */ + else if (tmp_col == length-1) { - /* Go to next row */ - tmp_row++; + /* Confinue only if current buffer row is wrapped */ + if (is_wrapped) { - /* Go to first row (-1 for auto increment on next iteration) */ - tmp_col = 0; + /* Stop if latest row */ + if (tmp_row == word_row_tail) + break; - /* Don't do further tests for this iteration */ - continue; + /* Go to next row */ + tmp_row++; - } + /* Go to first row (-1 for auto increment on next iteration) */ + tmp_col = 0; - /* End of selection without matching uri scheme */ - else - break; + /* Don't do further tests for this iteration */ + continue; } /* End of selection without matching uri scheme */ - else if (tmp_row == word_row_tail && tmp_col == word_col_tail) + else break; - tmp_col++; - //printf("tmp_row = %d | word_row_tail = %d | tmp_col = %d | word_col_tail = %d\n", tmp_row, word_row_tail, tmp_col, word_col_tail); - } while (true); + } - /* Get word boundaries instead of URI */ - is_part_of_word = guac_terminal_is_part_of_word; + /* End of selection without matching uri scheme */ + else if (tmp_row == word_row_tail && tmp_col == word_col_tail) + break; - } while (!exit_loop); - } + tmp_col++; + } while (true); + + /* Get word boundaries instead of URI */ + is_part_of_word_top_left = + is_part_of_word_bottom_right = guac_terminal_is_part_of_word; + + } while (!exit_loop); /* Select and add to clipboard the "word" */ guac_terminal_select_start(terminal, word_row_head, word_col_head); @@ -1917,24 +2023,39 @@ static void guac_terminal_double_click(guac_terminal* terminal, int row, int col * * @param col * The column where is the mouse at the triple click event. + * + * @param hold + * True when user hold left click. */ -static void guac_terminal_triple_click(guac_terminal* terminal, int row, int col) { +static void guac_terminal_triple_click(guac_terminal* terminal, int row, int col, bool hold) { /* Temporarily reading previous and next lines */ guac_terminal_char* characters; bool is_wrapped; int length; - /* Final boundary rows */ int top_row = row; int bottom_row = row; + /* User holds left click */ + if (hold) { + + /* Use initial row as bottom of the selection and go up */ + if (row <= terminal->selection_initial_row) + bottom_row = terminal->selection_initial_row; + + /* Use initial row as top of the selection and go down */ + if (row > terminal->selection_initial_row) + top_row = terminal->selection_initial_row; + } + /* Get top boundary */ do { /* Read previous buffer row */ - length = guac_terminal_buffer_get_columns(terminal->current_buffer, &characters, &is_wrapped, top_row - 1); + length = guac_terminal_buffer_get_columns(terminal->current_buffer, + &characters, &is_wrapped, top_row - 1); /* Go to the previous row if it is wrapped */ } while (is_wrapped && top_row--); @@ -1943,7 +2064,8 @@ static void guac_terminal_triple_click(guac_terminal* terminal, int row, int col do { /* Read current buffer row */ - length = guac_terminal_buffer_get_columns(terminal->current_buffer, &characters, &is_wrapped, bottom_row); + length = guac_terminal_buffer_get_columns(terminal->current_buffer, + &characters, &is_wrapped, bottom_row); /* Go to the next row if current row is wrapped */ } while (is_wrapped && bottom_row++); @@ -2037,27 +2159,42 @@ static int __guac_terminal_send_mouse(guac_terminal* term, guac_user* user, * the ALT key at the start of the selection */ term->rectangle_selection = term->mod_alt; + /* Save initial mouse position */ + term->selection_initial_row = row; + term->selection_initial_column = col; + /* Start selection */ guac_terminal_select_start(term, row, col); break; /* Second click = word selection */ case 1: - guac_terminal_double_click(term, row, col); + guac_terminal_double_click(term, row, col, false); break; /* third click or more = line selection */ default: - guac_terminal_triple_click(term, row, col); + guac_terminal_triple_click(term, row, col, false); break; } } } - /* In all other cases, simply update the existing selection as long as - * the mouse button is pressed */ + /* Hold left click */ else - guac_terminal_select_update(term, row, col); + + /* Simply update the existing selection as long as + * the mouse button is pressed */ + if (term->click_counter <= 1) + guac_terminal_select_update(term, row, col); + + /* Second click + hold = word selection update */ + else if (term->click_counter == 2) + guac_terminal_double_click(term, row, col, true); + + /* third click or more + hold = line selection update */ + else + guac_terminal_triple_click(term, row, col, true); } diff --git a/src/terminal/terminal/terminal-priv.h b/src/terminal/terminal/terminal-priv.h index 55afa2ea1..8ff8177e4 100644 --- a/src/terminal/terminal/terminal-priv.h +++ b/src/terminal/terminal/terminal-priv.h @@ -365,6 +365,16 @@ struct guac_terminal { */ bool selection_committed; + /** + * The row where the first click of selection was made. + */ + int selection_initial_row; + + /** + * The column where the first click of selection was made. + */ + int selection_initial_column; + /** * The row that the selection starts at. */