diff options
author | Bram Moolenaar <Bram@vim.org> | 2020-03-24 21:42:01 +0100 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2020-03-24 21:42:01 +0100 |
commit | 45fffdf10b7cb6e59794e76e9b8a2930fcb4b192 (patch) | |
tree | 9615bfdb0787577637e8d25b4049a31de10e52cd /src/clipboard.c | |
parent | 71658f74ae64c366b2d35b82c0a2eadb1317f028 (diff) |
patch 8.2.0443: clipboard code is spread outv8.2.0443
Problem: Clipboard code is spread out.
Solution: Move clipboard code to its own file. (Yegappan Lakshmanan,
closes #5827)
Diffstat (limited to 'src/clipboard.c')
-rw-r--r-- | src/clipboard.c | 2212 |
1 files changed, 2212 insertions, 0 deletions
diff --git a/src/clipboard.c b/src/clipboard.c new file mode 100644 index 0000000000..e9e426f52d --- /dev/null +++ b/src/clipboard.c @@ -0,0 +1,2212 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * clipboard.c: Functions to handle the clipboard + */ + +#include "vim.h" + +#ifdef FEAT_CYGWIN_WIN32_CLIPBOARD +# define WIN32_LEAN_AND_MEAN +# include <windows.h> +# include "winclip.pro" +#endif + +// Functions for copying and pasting text between applications. +// This is always included in a GUI version, but may also be included when the +// clipboard and mouse is available to a terminal version such as xterm. +// Note: there are some more functions in ops.c that handle selection stuff. +// +// Also note that the majority of functions here deal with the X 'primary' +// (visible - for Visual mode use) selection, and only that. There are no +// versions of these for the 'clipboard' selection, as Visual mode has no use +// for them. + +#if defined(FEAT_CLIPBOARD) || defined(PROTO) + +/* + * Selection stuff using Visual mode, for cutting and pasting text to other + * windows. + */ + +/* + * Call this to initialise the clipboard. Pass it FALSE if the clipboard code + * is included, but the clipboard can not be used, or TRUE if the clipboard can + * be used. Eg unix may call this with FALSE, then call it again with TRUE if + * the GUI starts. + */ + void +clip_init(int can_use) +{ + Clipboard_T *cb; + + cb = &clip_star; + for (;;) + { + cb->available = can_use; + cb->owned = FALSE; + cb->start.lnum = 0; + cb->start.col = 0; + cb->end.lnum = 0; + cb->end.col = 0; + cb->state = SELECT_CLEARED; + + if (cb == &clip_plus) + break; + cb = &clip_plus; + } +} + +/* + * Check whether the VIsual area has changed, and if so try to become the owner + * of the selection, and free any old converted selection we may still have + * lying around. If the VIsual mode has ended, make a copy of what was + * selected so we can still give it to others. Will probably have to make sure + * this is called whenever VIsual mode is ended. + */ + void +clip_update_selection(Clipboard_T *clip) +{ + pos_T start, end; + + // If visual mode is only due to a redo command ("."), then ignore it + if (!redo_VIsual_busy && VIsual_active && (State & NORMAL)) + { + if (LT_POS(VIsual, curwin->w_cursor)) + { + start = VIsual; + end = curwin->w_cursor; + if (has_mbyte) + end.col += (*mb_ptr2len)(ml_get_cursor()) - 1; + } + else + { + start = curwin->w_cursor; + end = VIsual; + } + if (!EQUAL_POS(clip->start, start) + || !EQUAL_POS(clip->end, end) + || clip->vmode != VIsual_mode) + { + clip_clear_selection(clip); + clip->start = start; + clip->end = end; + clip->vmode = VIsual_mode; + clip_free_selection(clip); + clip_own_selection(clip); + clip_gen_set_selection(clip); + } + } +} + + static int +clip_gen_own_selection(Clipboard_T *cbd) +{ +#ifdef FEAT_XCLIPBOARD +# ifdef FEAT_GUI + if (gui.in_use) + return clip_mch_own_selection(cbd); + else +# endif + return clip_xterm_own_selection(cbd); +#else + return clip_mch_own_selection(cbd); +#endif +} + + void +clip_own_selection(Clipboard_T *cbd) +{ + /* + * Also want to check somehow that we are reading from the keyboard rather + * than a mapping etc. + */ +#ifdef FEAT_X11 + // Always own the selection, we might have lost it without being + // notified, e.g. during a ":sh" command. + if (cbd->available) + { + int was_owned = cbd->owned; + + cbd->owned = (clip_gen_own_selection(cbd) == OK); + if (!was_owned && (cbd == &clip_star || cbd == &clip_plus)) + { + // May have to show a different kind of highlighting for the + // selected area. There is no specific redraw command for this, + // just redraw all windows on the current buffer. + if (cbd->owned + && (get_real_state() == VISUAL + || get_real_state() == SELECTMODE) + && (cbd == &clip_star ? clip_isautosel_star() + : clip_isautosel_plus()) + && HL_ATTR(HLF_V) != HL_ATTR(HLF_VNC)) + redraw_curbuf_later(INVERTED_ALL); + } + } +#else + // Only own the clipboard when we didn't own it yet. + if (!cbd->owned && cbd->available) + cbd->owned = (clip_gen_own_selection(cbd) == OK); +#endif +} + + static void +clip_gen_lose_selection(Clipboard_T *cbd) +{ +#ifdef FEAT_XCLIPBOARD +# ifdef FEAT_GUI + if (gui.in_use) + clip_mch_lose_selection(cbd); + else +# endif + clip_xterm_lose_selection(cbd); +#else + clip_mch_lose_selection(cbd); +#endif +} + + void +clip_lose_selection(Clipboard_T *cbd) +{ +#ifdef FEAT_X11 + int was_owned = cbd->owned; +#endif + int visual_selection = FALSE; + + if (cbd == &clip_star || cbd == &clip_plus) + visual_selection = TRUE; + + clip_free_selection(cbd); + cbd->owned = FALSE; + if (visual_selection) + clip_clear_selection(cbd); + clip_gen_lose_selection(cbd); +#ifdef FEAT_X11 + if (visual_selection) + { + // May have to show a different kind of highlighting for the selected + // area. There is no specific redraw command for this, just redraw all + // windows on the current buffer. + if (was_owned + && (get_real_state() == VISUAL + || get_real_state() == SELECTMODE) + && (cbd == &clip_star ? + clip_isautosel_star() : clip_isautosel_plus()) + && HL_ATTR(HLF_V) != HL_ATTR(HLF_VNC)) + { + update_curbuf(INVERTED_ALL); + setcursor(); + cursor_on(); + out_flush_cursor(TRUE, FALSE); + } + } +#endif +} + + static void +clip_copy_selection(Clipboard_T *clip) +{ + if (VIsual_active && (State & NORMAL) && clip->available) + { + clip_update_selection(clip); + clip_free_selection(clip); + clip_own_selection(clip); + if (clip->owned) + clip_get_selection(clip); + clip_gen_set_selection(clip); + } +} + +/* + * Save and restore clip_unnamed before doing possibly many changes. This + * prevents accessing the clipboard very often which might slow down Vim + * considerably. + */ +static int global_change_count = 0; // if set, inside a start_global_changes +static int clipboard_needs_update = FALSE; // clipboard needs to be updated +static int clip_did_set_selection = TRUE; + +/* + * Save clip_unnamed and reset it. + */ + void +start_global_changes(void) +{ + if (++global_change_count > 1) + return; + clip_unnamed_saved = clip_unnamed; + clipboard_needs_update = FALSE; + + if (clip_did_set_selection) + { + clip_unnamed = 0; + clip_did_set_selection = FALSE; + } +} + +/* + * Return TRUE if setting the clipboard was postponed, it already contains the + * right text. + */ + static int +is_clipboard_needs_update() +{ + return clipboard_needs_update; +} + +/* + * Restore clip_unnamed and set the selection when needed. + */ + void +end_global_changes(void) +{ + if (--global_change_count > 0) + // recursive + return; + if (!clip_did_set_selection) + { + clip_did_set_selection = TRUE; + clip_unnamed = clip_unnamed_saved; + clip_unnamed_saved = 0; + if (clipboard_needs_update) + { + // only store something in the clipboard, + // if we have yanked anything to it + if (clip_unnamed & CLIP_UNNAMED) + { + clip_own_selection(&clip_star); + clip_gen_set_selection(&clip_star); + } + if (clip_unnamed & CLIP_UNNAMED_PLUS) + { + clip_own_selection(&clip_plus); + clip_gen_set_selection(&clip_plus); + } + } + } + clipboard_needs_update = FALSE; +} + +/* + * Called when Visual mode is ended: update the selection. + */ + void +clip_auto_select(void) +{ + if (clip_isautosel_star()) + clip_copy_selection(&clip_star); + if (clip_isautosel_plus()) + clip_copy_selection(&clip_plus); +} + +/* + * Return TRUE if automatic selection of Visual area is desired for the * + * register. + */ + int +clip_isautosel_star(void) +{ + return ( +#ifdef FEAT_GUI + gui.in_use ? (vim_strchr(p_go, GO_ASEL) != NULL) : +#endif + clip_autoselect_star); +} + +/* + * Return TRUE if automatic selection of Visual area is desired for the + + * register. + */ + int +clip_isautosel_plus(void) +{ + return ( +#ifdef FEAT_GUI + gui.in_use ? (vim_strchr(p_go, GO_ASELPLUS) != NULL) : +#endif + clip_autoselect_plus); +} + + +/* + * Stuff for general mouse selection, without using Visual mode. + */ + +/* + * Compare two screen positions ala strcmp() + */ + static int +clip_compare_pos( + int row1, + int col1, + int row2, + int col2) +{ + if (row1 > row2) return(1); + if (row1 < row2) return(-1); + if (col1 > col2) return(1); + if (col1 < col2) return(-1); + return(0); +} + +// "how" flags for clip_invert_area() +#define CLIP_CLEAR 1 +#define CLIP_SET 2 +#define CLIP_TOGGLE 3 + +/* + * Invert or un-invert a rectangle of the screen. + * "invert" is true if the result is inverted. + */ + static void +clip_invert_rectangle( + Clipboard_T *cbd UNUSED, + int row_arg, + int col_arg, + int height_arg, + int width_arg, + int invert) +{ + int row = row_arg; + int col = col_arg; + int height = height_arg; + int width = width_arg; + +#ifdef FEAT_PROP_POPUP + // this goes on top of all popup windows + screen_zindex = CLIP_ZINDEX; + + if (col < cbd->min_col) + { + width -= cbd->min_col - col; + col = cbd->min_col; + } + if (width > cbd->max_col - col) + width = cbd->max_col - col; + if (row < cbd->min_row) + { + height -= cbd->min_row - row; + row = cbd->min_row; + } + if (height > cbd->max_row - row + 1) + height = cbd->max_row - row + 1; +#endif +#ifdef FEAT_GUI + if (gui.in_use) + gui_mch_invert_rectangle(row, col, height, width); + else +#endif + screen_draw_rectangle(row, col, height, width, invert); +#ifdef FEAT_PROP_POPUP + screen_zindex = 0; +#endif +} + +/* + * Invert a region of the display between a starting and ending row and column + * Values for "how": + * CLIP_CLEAR: undo inversion + * CLIP_SET: set inversion + * CLIP_TOGGLE: set inversion if pos1 < pos2, undo inversion otherwise. + * 0: invert (GUI only). + */ + static void +clip_invert_area( + Clipboard_T *cbd, + int row1, + int col1, + int row2, + int col2, + int how) +{ + int invert = FALSE; + int max_col; + +#ifdef FEAT_PROP_POPUP + max_col = cbd->max_col - 1; +#else + max_col = Columns - 1; +#endif + + if (how == CLIP_SET) + invert = TRUE; + + // Swap the from and to positions so the from is always before + if (clip_compare_pos(row1, col1, row2, col2) > 0) + { + int tmp_row, tmp_col; + + tmp_row = row1; + tmp_col = col1; + row1 = row2; + col1 = col2; + row2 = tmp_row; + col2 = tmp_col; + } + else if (how == CLIP_TOGGLE) + invert = TRUE; + + // If all on the same line, do it the easy way + if (row1 == row2) + { + clip_invert_rectangle(cbd, row1, col1, 1, col2 - col1, invert); + } + else + { + // Handle a piece of the first line + if (col1 > 0) + { + clip_invert_rectangle(cbd, row1, col1, 1, + (int)Columns - col1, invert); + row1++; + } + + // Handle a piece of the last line + if (col2 < max_col) + { + clip_invert_rectangle(cbd, row2, 0, 1, col2, invert); + row2--; + } + + // Handle the rectangle that's left + if (row2 >= row1) + clip_invert_rectangle(cbd, row1, 0, row2 - row1 + 1, + (int)Columns, invert); + } +} + +/* + * Start, continue or end a modeless selection. Used when editing the + * command-line, in the cmdline window and when the mouse is in a popup window. + */ + void +clip_modeless(int button, int is_click, int is_drag) +{ + int repeat; + + repeat = ((clip_star.mode == SELECT_MODE_CHAR + || clip_star.mode == SELECT_MODE_LINE) + && (mod_mask & MOD_MASK_2CLICK)) + || (clip_star.mode == SELECT_MODE_WORD + && (mod_mask & MOD_MASK_3CLICK)); + if (is_click && button == MOUSE_RIGHT) + { + // Right mouse button: If there was no selection, start one. + // Otherwise extend the existing selection. + if (clip_star.state == SELECT_CLEARED) + clip_start_selection(mouse_col, mouse_row, FALSE); + clip_process_selection(button, mouse_col, mouse_row, repeat); + } + else if (is_click) + clip_start_selection(mouse_col, mouse_row, repeat); + else if (is_drag) + { + // Don't try extending a selection if there isn't one. Happens when + // button-down is in the cmdline and them moving mouse upwards. + if (clip_star.state != SELECT_CLEARED) + clip_process_selection(button, mouse_col, mouse_row, repeat); + } + else // release + clip_process_selection(MOUSE_RELEASE, mouse_col, mouse_row, FALSE); +} + +/* + * Update the currently selected region by adding and/or subtracting from the + * beginning or end and inverting the changed area(s). + */ + static void +clip_update_modeless_selection( + Clipboard_T *cb, + int row1, + int col1, + int row2, + int col2) +{ + // See if we changed at the beginning of the selection + if (row1 != cb->start.lnum || col1 != (int)cb->start.col) + { + clip_invert_area(cb, row1, col1, (int)cb->start.lnum, cb->start.col, + CLIP_TOGGLE); + cb->start.lnum = row1; + cb->start.col = col1; + } + + // See if we changed at the end of the selection + if (row2 != cb->end.lnum || col2 != (int)cb->end.col) + { + clip_invert_area(cb, (int)cb->end.lnum, cb->end.col, row2, col2, + CLIP_TOGGLE); + cb->end.lnum = row2; + cb->end.col = col2; + } +} + +/* + * Find the starting and ending positions of the word at the given row and + * column. Only white-separated words are recognized here. + */ +#define CHAR_CLASS(c) (c <= ' ' ? ' ' : vim_iswordc(c)) + + static void +clip_get_word_boundaries(Clipboard_T *cb, int row, int col) +{ + int start_class; + int temp_col; + char_u *p; + int mboff; + + if (row >= screen_Rows || col >= screen_Columns || ScreenLines == NULL) + return; + + p = ScreenLines + LineOffset[row]; + // Correct for starting in the right halve of a double-wide char + if (enc_dbcs != 0) + col -= dbcs_screen_head_off(p, p + col); + else if (enc_utf8 && p[col] == 0) + --col; + start_class = CHAR_CLASS(p[col]); + + temp_col = col; + for ( ; temp_col > 0; temp_col--) + if (enc_dbcs != 0 + && (mboff = dbcs_screen_head_off(p, p + temp_col - 1)) > 0) + temp_col -= mboff; + else if (CHAR_CLASS(p[temp_col - 1]) != start_class + && !(enc_utf8 && p[temp_col - 1] == 0)) + break; + cb->word_start_col = temp_col; + + temp_col = col; + for ( ; temp_col < screen_Columns; temp_col++) + if (enc_dbcs != 0 && dbcs_ptr2cells(p + temp_col) == 2) + ++temp_col; + else if (CHAR_CLASS(p[temp_col]) != start_class + && !(enc_utf8 && p[temp_col] == 0)) + break; + cb->word_end_col = temp_col; +} + +/* + * Find the column position for the last non-whitespace character on the given + * line at or before start_col. + */ + static int +clip_get_line_end(Clipboard_T *cbd UNUSED, int row) +{ + int i; + + if (row >= screen_Rows || ScreenLines == NULL) + return 0; + for (i = +#ifdef FEAT_PROP_POPUP + cbd->max_col; +#else + screen_Columns; +#endif + i > 0; i--) + if (ScreenLines[LineOffset[row] + i - 1] != ' ') + break; + return i; +} + +/* + * Start the selection + */ + void +clip_start_selection(int col, int row, int repeated_click) +{ + Clipboard_T *cb = &clip_star; +#ifdef FEAT_PROP_POPUP + win_T *wp; + int row_cp = row; + int col_cp = col; + + wp = mouse_find_win(&row_cp, &col_cp, FIND_POPUP); + if (wp != NULL && WIN_IS_POPUP(wp) + && popup_is_in_scrollbar(wp, row_cp, col_cp)) + // click or double click in scrollbar does not start a selection + return; +#endif + + if (cb->state == SELECT_DONE) + clip_clear_selection(cb); + + row = check_row(row); + col = check_col(col); + col = mb_fix_col(col, row); + + cb->start.lnum = row; + cb->start.col = col; + cb->end = cb->start; + cb->origin_row = (short_u)cb->start.lnum; + cb->state = SELECT_IN_PROGRESS; +#ifdef FEAT_PROP_POPUP + if (wp != NULL && WIN_IS_POPUP(wp)) + { + // Click in a popup window restricts selection to that window, + // excluding the border. + cb->min_col = wp->w_wincol + wp->w_popup_border[3]; + cb->max_col = wp->w_wincol + popup_width(wp) + - wp->w_popup_border[1] - wp->w_has_scrollbar; + if (cb->max_col > screen_Columns) + cb->max_col = screen_Columns; + cb->min_row = wp->w_winrow + wp->w_popup_border[0]; + cb->max_row = wp->w_winrow + popup_height(wp) - 1 + - wp->w_popup_border[2]; + } + else + { + cb->min_col = 0; + cb->max_col = screen_Columns; + cb->min_row = 0; + cb->max_row = screen_Rows; + } +#endif + + if (repeated_click) + { + if (++cb->mode > SELECT_MODE_LINE) + cb->mode = SELECT_MODE_CHAR; + } + else + cb->mode = SELECT_MODE_CHAR; + +#ifdef FEAT_GUI + // clear the cursor until the selection is made + if (gui.in_use) + gui_undraw_cursor(); +#endif + + switch (cb->mode) + { + case SELECT_MODE_CHAR: + cb->origin_start_col = cb->start.col; + cb->word_end_col = clip_get_line_end(cb, (int)cb->start.lnum); + break; + + case SELECT_MODE_WORD: + clip_get_word_boundaries(cb, (int)cb->start.lnum, cb->start.col); + cb->origin_start_col = cb->word_start_col; + cb->origin_end_col = cb->word_end_col; + + clip_invert_area(cb, (int)cb->start.lnum, cb->word_start_col, + (int)cb->end.lnum, cb->word_end_col, CLIP_SET); + cb->start.col = cb->word_start_col; + cb->end.col = cb->word_end_col; + break; + + case SELECT_MODE_LINE: + clip_invert_area(cb, (int)cb->start.lnum, 0, (int)cb->start.lnum, + (int)Columns, CLIP_SET); + cb->start.col = 0; + cb->end.col = Columns; + break; + } + + cb->prev = cb->start; + +#ifdef DEBUG_SELECTION + printf("Selection started at (%ld,%d)\n", cb->start.lnum, cb->start.col); +#endif +} + +/* + * Continue processing the selection + */ + void +clip_process_selection( + int button, + int col, + int row, + int_u repeated_click) +{ + Clipboard_T *cb = &clip_star; + int diff; + int slen = 1; // cursor shape width + + if (button == MOUSE_RELEASE) + { + if (cb->state != SELECT_IN_PROGRESS) + return; + + // Check to make sure we have something selected + if (cb->start.lnum == cb->end.lnum && cb->start.col == cb->end.col) + { +#ifdef FEAT_GUI + if (gui.in_use) + gui_update_cursor(FALSE, FALSE); +#endif + cb->state = SELECT_CLEARED; + return; + } + +#ifdef DEBUG_SELECTION + printf("Selection ended: (%ld,%d) to (%ld,%d)\n", cb->start.lnum, + cb->start.col, cb->end.lnum, cb->end.col); +#endif + if (clip_isautosel_star() + || ( +#ifdef FEAT_GUI + gui.in_use ? (vim_strchr(p_go, GO_ASELML) != NULL) : +#endif + clip_autoselectml)) + clip_copy_modeless_selection(FALSE); +#ifdef FEAT_GUI + if (gui.in_use) + gui_update_cursor(FALSE, FALSE); +#endif + + cb->state = SELECT_DONE; + return; + } + + row = check_row(row); + col = check_col(col); + col = mb_fix_col(col, row); + + if (col == (int)cb->prev.col && row == cb->prev.lnum && !repeated_click) + return; + + /* + * When extending the selection with the right mouse button, swap the + * start and end if the position is before half the selection + */ + if (cb->state == SELECT_DONE && button == MOUSE_RIGHT) + { + /* + * If the click is before the start, or the click is inside the + * selection and the start is the closest side, set the origin to the + * end of the selection. + */ + if (clip_compare_pos(row, col, (int)cb->start.lnum, cb->start.col) < 0 + || (clip_compare_pos(row, col, + (int)cb->end.lnum, cb->end.col) < 0 + && (((cb->start.lnum == cb->end.lnum + && cb->end.col - col > col - cb->start.col)) + || ((diff = (cb->end.lnum - row) - + (row - cb->start.lnum)) > 0 + || (diff == 0 && col < (int)(cb->start.col + + cb->end.col) / 2))))) + { + cb->origin_row = (short_u)cb->end.lnum; + cb->origin_start_col = cb->end.col - 1; + cb->origin_end_col = cb->end.col; + } + else + { + cb->origin_row = (short_u)cb->start.lnum; + cb->origin_start_col = cb->start.col; + cb->origin_end_col = cb->start.col; + } + if (cb->mode == SELECT_MODE_WORD && !repeated_click) + cb->mode = SELECT_MODE_CHAR; + } + + // set state, for when using the right mouse button + cb->state = SELECT_IN_PROGRESS; + +#ifdef DEBUG_SELECTION + printf("Selection extending to (%d,%d)\n", row, col); +#endif + + if (repeated_click && ++cb->mode > SELECT_MODE_LINE) + cb->mode = SELECT_MODE_CHAR; + + switch (cb->mode) + { + case SELECT_MODE_CHAR: + // If we're on a different line, find where the line ends + if (row != cb->prev.lnum) + cb->word_end_col = clip_get_line_end(cb, row); + + // See if we are before or after the origin of the selection + if (clip_compare_pos(row, col, cb->origin_row, + cb->origin_start_col) >= 0) + { + if (col >= (int)cb->word_end_col) + clip_update_modeless_selection(cb, cb->origin_row, + cb->origin_start_col, row, (int)Columns); + else + { + if (has_mbyte && mb_lefthalve(row, col)) + slen = 2; + clip_update_modeless_selection(cb, cb->origin_row, + cb->origin_start_col, row, col + slen); + } + } + else + { + if (has_mbyte + && mb_lefthalve(cb->origin_row, cb->origin_start_col)) + slen = 2; + if (col >= (int)cb->word_end_col) + clip_update_modeless_selection(cb, row, cb->word_end_col, + cb->origin_row, cb->origin_start_col + slen); + else + clip_update_modeless_selection(cb, row, col, + cb->origin_row, cb->origin_start_col + slen); + } + break; + + case SELECT_MODE_WORD: + // If we are still within the same word, do nothing + if (row == cb->prev.lnum && col >= (int)cb->word_start_col + && col < (int)cb->word_end_col && !repeated_click) + return; + + // Get new word boundaries + clip_get_word_boundaries(cb, row, col); + + // Handle being after the origin point of selection + if (clip_compare_pos(row, col, cb->origin_row, + cb->origin_start_col) >= 0) + clip_update_modeless_selection(cb, cb->origin_row, + cb->origin_start_col, row, cb->word_end_col); + else + clip_update_modeless_selection(cb, row, cb->word_start_col, + cb->origin_row, cb->origin_end_col); + break; + + case SELECT_MODE_LINE: + if (row == cb->prev.lnum && !repeated_click) + return; + + if (clip_compare_pos(row, col, cb->origin_row, + cb->origin_start_col) >= 0) + clip_update_modeless_selection(cb, cb->origin_row, 0, row, + (int)Columns); + else + clip_update_modeless_selection(cb, row, 0, cb->origin_row, + (int)Columns); + break; + } + + cb->prev.lnum = row; + cb->prev.col = col; + +#ifdef DEBUG_SELECTION + printf("Selection is: (%ld,%d) to (%ld,%d)\n", cb->start.lnum, + cb->start.col, cb->end.lnum, cb->end.col); +#endif +} + +# if defined(FEAT_GUI) || defined(PROTO) +/* + * Redraw part of the selection if character at "row,col" is inside of it. + * Only used for the GUI. + */ + void +clip_may_redraw_selection(int row, int col, int len) +{ + int start = col; + int end = col + len; + + if (clip_star.state != SELECT_CLEARED + && row >= clip_star.start.lnum + && row <= clip_star.end.lnum) + { + if (row == clip_star.start.lnum && start < (int)clip_star.start.col) + start = clip_star.start.col; + if (row == clip_star.end.lnum && end > (int)clip_star.end.col) + end = clip_star.end.col; + if (end > start) + clip_invert_area(&clip_star, row, start, row, end, 0); + } +} +# endif + +/* + * Called from outside to clear selected region from the display + */ + void +clip_clear_selection(Clipboard_T *cbd) +{ + + if (cbd->state == SELECT_CLEARED) + return; + + clip_invert_area(cbd, (int)cbd->start.lnum, cbd->start.col, + (int)cbd->end.lnum, cbd->end.col, CLIP_CLEAR); + cbd->state = SELECT_CLEARED; +} + +/* + * Clear the selection if any lines from "row1" to "row2" are inside of it. + */ + void +clip_may_clear_selection(int row1, int row2) +{ + if (clip_star.state == SELECT_DONE + && row2 >= clip_star.start.lnum + && row1 <= clip_star.end.lnum) + clip_clear_selection(&clip_star); +} + +/* + * Called before the screen is scrolled up or down. Adjusts the line numbers + * of the selection. Call with big number when clearing the screen. + */ + void +clip_scroll_selection( + int rows) // negative for scroll down +{ + int lnum; + + if (clip_star.state == SELECT_CLEARED) + return; + + lnum = clip_star.start.lnum - rows; + if (lnum <= 0) + clip_star.start.lnum = 0; + else if (lnum >= screen_Rows) // scrolled off of the screen + clip_star.state = SELECT_CLEARED; + else + clip_star.start.lnum = lnum; + + lnum = clip_star.end.lnum - rows; + if (lnum < 0) // scrolled off of the screen + clip_star.state = SELECT_CLEARED; + else if (lnum >= screen_Rows) + clip_star.end.lnum = screen_Rows - 1; + else + clip_star.end.lnum = lnum; +} + +/* + * Copy the currently selected area into the '*' register so it will be + * available for pasting. + * When "both" is TRUE also copy to the '+' register. + */ + void +clip_copy_modeless_selection(int both UNUSED) +{ + char_u *buffer; + char_u *bufp; + int row; + int start_col; + int end_col; + int line_end_col; + int add_newline_flag = FALSE; + int len; + char_u *p; + int row1 = clip_star.start.lnum; + int col1 = clip_star.start.col; + int row2 = clip_star.end.lnum; + int col2 = clip_star.end.col; + + // Can't use ScreenLines unless initialized + if (ScreenLines == NULL) + return; + + /* + * Make sure row1 <= row2, and if row1 == row2 that col1 <= col2. + */ + if (row1 > row2) + { + row = row1; row1 = row2; row2 = row; + row = col1; col1 = col2; col2 = row; + } + else if (row1 == row2 && col1 > col2) + { + row = col1; col1 = col2; col2 = row; + } +#ifdef FEAT_PROP_POPUP + if (col1 < clip_star.min_col) + col1 = clip_star.min_col; + if (col2 > clip_star.max_col) + col2 = clip_star.max_col; + if (row1 > clip_star.max_row || row2 < clip_star.min_row) + return; + if (row1 < clip_star.min_row) + row1 = clip_star.min_row; + if (row2 > clip_star.max_row) + row2 = clip_star.max_row; +#endif + // correct starting point for being on right halve of double-wide char + p = ScreenLines + LineOffset[row1]; + if (enc_dbcs != 0) + col1 -= (*mb_head_off)(p, p + col1); + else if (enc_utf8 && p[col1] == 0) + --col1; + + // Create a temporary buffer for storing the text + len = (row2 - row1 + 1) * Columns + 1; + if (enc_dbcs != 0) + len *= 2; // max. 2 bytes per display cell + else if (enc_utf8) + len *= MB_MAXBYTES; + buffer = alloc(len); + if (buffer == NULL) // out of memory + return; + + // Process each row in the selection + for (bufp = buffer, row = row1; row <= row2; row++) + { + if (row == row1) + start_col = col1; + else +#ifdef FEAT_PROP_POPUP + start_col = clip_star.min_col; +#else + start_col = 0; +#endif + + if (row == row2) + end_col = col2; + else +#ifdef FEAT_PROP_POPUP + end_col = clip_star.max_col; +#else + end_col = Columns; +#endif + + line_end_col = clip_get_line_end(&clip_star, row); + + // See if we need to nuke some trailing whitespace + if (end_col >= +#ifdef FEAT_PROP_POPUP + clip_star.max_col +#else + Columns +#endif + && (row < row2 || end_col > line_end_col)) + { + // Get rid of trailing whitespace + end_col = line_end_col; + if (end_col < start_col) + end_col = start_col; + + // If the last line extended to the end, add an extra newline + if (row == row2) + add_newline_flag = TRUE; + } + + // If after the first row, we need to always add a newline + if (row > row1 && !LineWraps[row - 1]) + *bufp++ = NL; + + // Safetey check for in case resizing went wrong + if (row < screen_Rows && end_col <= screen_Columns) + { + if (enc_dbcs != 0) + { + int i; + + p = ScreenLines + LineOffset[row]; + for (i = start_col; i < end_col; ++i) + if (enc_dbcs == DBCS_JPNU && p[i] == 0x8e) + { + // single-width double-byte char + *bufp++ = 0x8e; + *bufp++ = ScreenLines2[LineOffset[row] + i]; + } + else + { + *bufp++ = p[i]; + if (MB_BYTE2LEN(p[i]) == 2) + *bufp++ = p[++i]; + } + } + else if (enc_utf8) + { + int off; + int i; + int ci; + + off = LineOffset[row]; + for (i = start_col; i < end_col; ++i) + { + // The base character is either in ScreenLinesUC[] or + // ScreenLines[]. + if (ScreenLinesUC[off + i] == 0) + *bufp++ = ScreenLines[off + i]; + else + { + bufp += utf_char2bytes(ScreenLinesUC[off + i], bufp); + for (ci = 0; ci < Screen_mco; ++ci) + { + // Add a composing character. + if (ScreenLinesC[ci][off + i] == 0) + break; + bufp += utf_char2bytes(ScreenLinesC[ci][off + i], + bufp); + } + } + // Skip right halve of double-wide character. + if (ScreenLines[off + i + 1] == 0) + ++i; + } + } + else + { + STRNCPY(bufp, ScreenLines + LineOffset[row] + start_col, + end_col - start_col); + bufp += end_col - start_col; + } + } + } + + // Add a newline at the end if the selection ended there + if (add_newline_flag) + *bufp++ = NL; + + // First cleanup any old selection and become the owner. + clip_free_selection(&clip_star); + clip_own_selection(&clip_star); + + // Yank the text into the '*' register. + clip_yank_selection(MCHAR, buffer, (long)(bufp - buffer), &clip_star); + + // Make the register contents available to the outside world. + clip_gen_set_selection(&clip_star); + +#ifdef FEAT_X11 + if (both) + { + // Do the same for the '+' register. + clip_free_selection(&clip_plus); + clip_own_selection(&clip_plus); + clip_yank_selection(MCHAR, buffer, (long)(bufp - buffer), &clip_plus); + clip_gen_set_selection(&clip_plus); + } +#endif + vim_free(buffer); +} + + void +clip_gen_set_selection(Clipboard_T *cbd) +{ + if (!clip_did_set_selection) + { + // Updating postponed, so that accessing the system clipboard won't + // hang Vim when accessing it many times (e.g. on a :g command). + if ((cbd == &clip_plus && (clip_unnamed_saved & CLIP_UNNAMED_PLUS)) + || (cbd == &clip_star && (clip_unnamed_saved & CLIP_UNNAMED))) + { + clipboard_need |