summaryrefslogtreecommitdiffstats
path: root/src/clipboard.c
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2020-03-24 21:42:01 +0100
committerBram Moolenaar <Bram@vim.org>2020-03-24 21:42:01 +0100
commit45fffdf10b7cb6e59794e76e9b8a2930fcb4b192 (patch)
tree9615bfdb0787577637e8d25b4049a31de10e52cd /src/clipboard.c
parent71658f74ae64c366b2d35b82c0a2eadb1317f028 (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.c2212
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