Mercurial > vim
view src/clipboard.c @ 32936:c517845bd10e v9.0.1776
patch 9.0.1776: No support for stable Python 3 ABI
Commit: https://github.com/vim/vim/commit/c13b3d1350b60b94fe87f0761ea31c0e7fb6ebf3
Author: Yee Cheng Chin <ychin.git@gmail.com>
Date: Sun Aug 20 21:18:38 2023 +0200
patch 9.0.1776: No support for stable Python 3 ABI
Problem: No support for stable Python 3 ABI
Solution: Support Python 3 stable ABI
Commits:
1) Support Python 3 stable ABI to allow mixed version interoperatbility
Vim currently supports embedding Python for use with plugins, and the
"dynamic" linking option allows the user to specify a locally installed
version of Python by setting `pythonthreedll`. However, one caveat is
that the Python 3 libs are not binary compatible across minor versions,
and mixing versions can potentially be dangerous (e.g. let's say Vim was
linked against the Python 3.10 SDK, but the user sets `pythonthreedll`
to a 3.11 lib). Usually, nothing bad happens, but in theory this could
lead to crashes, memory corruption, and other unpredictable behaviors.
It's also difficult for the user to tell something is wrong because Vim
has no way of reporting what Python 3 version Vim was linked with.
For Vim installed via a package manager, this usually isn't an issue
because all the dependencies would already be figured out. For prebuilt
Vim binaries like MacVim (my motivation for working on this), AppImage,
and Win32 installer this could potentially be an issue as usually a
single binary is distributed. This is more tricky when a new Python
version is released, as there's a chicken-and-egg issue with deciding
what Python version to build against and hard to keep in sync when a new
Python version just drops and we have a mix of users of different Python
versions, and a user just blindly upgrading to a new Python could lead to
bad interactions with Vim.
Python 3 does have a solution for this problem: stable ABI / limited API
(see https://docs.python.org/3/c-api/stable.html). The C SDK limits the
API to a set of functions that are promised to be stable across
versions. This pull request adds an ifdef config that allows us to turn
it on when building Vim. Vim binaries built with this option should be
safe to freely link with any Python 3 libraies without having the
constraint of having to use the same minor version.
Note: Python 2 has no such concept and this doesn't change how Python 2
integration works (not that there is going to be a new version of Python
2 that would cause compatibility issues in the future anyway).
---
Technical details:
======
The stable ABI can be accessed when we compile with the Python 3 limited
API (by defining `Py_LIMITED_API`). The Python 3 code (in `if_python3.c`
and `if_py_both.h`) would now handle this and switch to limited API
mode. Without it set, Vim will still use the full API as before so this
is an opt-in change.
The main difference is that `PyType_Object` is now an opaque struct that
we can't directly create "static types" out of, and we have to create
type objects as "heap types" instead. This is because the struct is not
stable and changes from version to version (e.g. 3.8 added a
`tp_vectorcall` field to it). I had to change all the types to be
allocated on the heap instead with just a pointer to them.
Other functions are also simply missing in limited API, or they are
introduced too late (e.g. `PyUnicode_AsUTF8AndSize` in 3.10) to it that
we need some other ways to do the same thing, so I had to abstract a few
things into macros, and sometimes re-implement functions like
`PyObject_NEW`.
One caveat is that in limited API, `OutputType` (used for replacing
`sys.stdout`) no longer inherits from `PyStdPrinter_Type` which I don't
think has any real issue other than minor differences in how they
convert to a string and missing a couple functions like `mode()` and
`fileno()`.
Also fixed an existing bug where `tp_basicsize` was set incorrectly for
`BufferObject`, `TabListObject, `WinListObject`.
Technically, there could be a small performance drop, there is a little
more indirection with accessing type objects, and some APIs like
`PyUnicode_AsUTF8AndSize` are missing, but in practice I didn't see any
difference, and any well-written Python plugin should try to avoid
excessing callbacks to the `vim` module in Python anyway.
I only tested limited API mode down to Python 3.7, which seemes to
compile and work fine. I haven't tried earlier Python versions.
2) Fix PyIter_Check on older Python vers / type##Ptr unused warning
For PyIter_Check, older versions exposed them as either macros (used in
full API), or a function (for use in limited API). A previous change
exposed PyIter_Check to the dynamic build because Python just moved it
to function-only in 3.10 anyway. Because of that, just make sure we
always grab the function in dynamic builds in earlier versions since
that's what Python eventually did anyway.
3) Move Py_LIMITED_API define to configure script
Can now use --with-python-stable-abi flag to customize what stable ABI
version to target. Can also use an env var to do so as well.
4) Show +python/dyn-stable in :version, and allow has() feature query
Not sure if the "/dyn-stable" suffix would break things, or whether we
should do it another way. Or just don't show it in version and rely on
has() feature checking.
5) Documentation first draft. Still need to implement v:python3_version
6) Fix PyIter_Check build breaks when compiling against Python 3.8
7) Add CI coverage stable ABI on Linux/Windows / make configurable on Windows
This adds configurable options for Windows make files (both MinGW and
MSVC). CI will also now exercise both traditional full API and stable
ABI for Linux and Windows in the matrix for coverage.
Also added a "dynamic" option to Linux matrix as a drive-by change to
make other scripting languages like Ruby / Perl testable under both
static and dynamic builds.
8) Fix inaccuracy in Windows docs
Python's own docs are confusing but you don't actually want to use
`python3.dll` for the dynamic linkage.
9) Add generated autoconf file
10) Add v:python3_version support
This variable indicates the version of Python3 that Vim was built
against (PY_VERSION_HEX), and will be useful to check whether the Python
library you are loading in dynamically actually fits it. When built with
stable ABI, it will be the limited ABI version instead
(`Py_LIMITED_API`), which indicates the minimum version of Python 3 the
user should have, rather than the exact match. When stable ABI is used,
we won't be exposing PY_VERSION_HEX in this var because it just doesn't
seem necessary to do so (the whole point of stable ABI is the promise
that it will work across versions), and I don't want to confuse the user
with too many variables.
Also, cleaned up some documentation, and added help tags.
11) Fix Python 3.7 compat issues
Fix a couple issues when using limited API < 3.8
- Crash on exit: In Python 3.7, if a heap-allocated type is destroyed
before all instances are, it would cause a crash later. This happens
when we destroyed `OptionsType` before calling `Py_Finalize` when
using the limited API. To make it worse, later versions changed the
semantics and now each instance has a strong reference to its own type
and the recommendation has changed to have each instance de-ref its
own type and have its type in GC traversal. To avoid dealing with
these cross-version variations, we just don't free the heap type. They
are static types in non-limited-API anyway and are designed to last
through the entirety of the app, and we also don't restart the Python
runtime and therefore do not need it to have absolutely 0 leaks.
See:
- https://docs.python.org/3/whatsnew/3.8.html#changes-in-the-c-api
- https://docs.python.org/3/whatsnew/3.9.html#changes-in-the-c-api
- PyIter_Check: This function is not provided in limited APIs older than
3.8. Previously I was trying to mock it out using manual
PyType_GetSlot() but it was brittle and also does not actually work
properly for static types (it will generate a Python error). Just
return false. It does mean using limited API < 3.8 is not recommended
as you lose the functionality to handle iterators, but from playing
with plugins I couldn't find it to be an issue.
- Fix loading of PyIter_Check so it will be done when limited API < 3.8.
Otherwise loading a 3.7 Python lib will fail even if limited API was
specified to use it.
12) Make sure to only load `PyUnicode_AsUTF8AndSize` in needed in limited API
We don't use this function unless limited API >= 3.10, but we were
loading it regardless. Usually it's ok in Unix-like systems where Python
just has a single lib that we load from, but in Windows where there is a
separate python3.dll this would not work as the symbol would not have
been exposed in this more limited DLL file. This makes it much clearer
under what condition is this function needed.
closes: #12032
Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Yee Cheng Chin <ychin.git@gmail.com>
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Sun, 20 Aug 2023 21:30:04 +0200 |
parents | 4545f58c8490 |
children | 95db67c7b754 |
line wrap: on
line source
/* 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 & MODE_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() == MODE_VISUAL || get_real_state() == MODE_SELECT) && (cbd == &clip_star ? clip_isautosel_star() : clip_isautosel_plus()) && HL_ATTR(HLF_V) != HL_ATTR(HLF_VNC)) redraw_curbuf_later(UPD_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() == MODE_VISUAL || get_real_state() == MODE_SELECT) && (cbd == &clip_star ? clip_isautosel_star() : clip_isautosel_plus()) && HL_ATTR(HLF_V) != HL_ATTR(HLF_VNC) && !exiting) { update_curbuf(UPD_INVERTED_ALL); setcursor(); cursor_on(); out_flush_cursor(TRUE, FALSE); } } #endif } static void clip_copy_selection(Clipboard_T *clip) { if (VIsual_active && (State & MODE_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(void) { 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 half 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 half 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 half 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_needs_update = TRUE; return; } } #ifdef FEAT_XCLIPBOARD # ifdef FEAT_GUI if (gui.in_use) clip_mch_set_selection(cbd); else # endif clip_xterm_set_selection(cbd); #else clip_mch_set_selection(cbd); #endif } static void clip_gen_request_selection(Clipboard_T *cbd) { #ifdef FEAT_XCLIPBOARD # ifdef FEAT_GUI if (gui.in_use) clip_mch_request_selection(cbd); else # endif clip_xterm_request_selection(cbd); #else clip_mch_request_selection(cbd); #endif } #if (defined(FEAT_X11) && defined(FEAT_XCLIPBOARD) && defined(USE_SYSTEM)) \ || defined(PROTO) static int clip_x11_owner_exists(Clipboard_T *cbd) { return XGetSelectionOwner(X_DISPLAY, cbd->sel_atom) != None; } #endif #if (defined(FEAT_X11) && defined(USE_SYSTEM)) || defined(PROTO) int clip_gen_owner_exists(Clipboard_T *cbd UNUSED) { #ifdef FEAT_XCLIPBOARD # ifdef FEAT_GUI_GTK if (gui.in_use) return clip_gtk_owner_exists(cbd); else # endif return clip_x11_owner_exists(cbd); #else return TRUE; #endif } #endif /* * Extract the items in the 'clipboard' option and set global values. * Return an error message or NULL for success. */ char * did_set_clipboard(optset_T *args UNUSED) { int new_unnamed = 0; int new_autoselect_star = FALSE; int new_autoselect_plus = FALSE; int new_autoselectml = FALSE; int new_html = FALSE; regprog_T *new_exclude_prog = NULL; char *errmsg = NULL; char_u *p; for (p = p_cb; *p != NUL; ) { if (STRNCMP(p, "unnamed", 7) == 0 && (p[7] == ',' || p[7] == NUL)) { new_unnamed |= CLIP_UNNAMED; p += 7; } else if (STRNCMP(p, "unnamedplus", 11) == 0 && (p[11] == ',' || p[11] == NUL)) { new_unnamed |= CLIP_UNNAMED_PLUS; p += 11; } else if (STRNCMP(p, "autoselect", 10) == 0 && (p[10] == ',' || p[10] == NUL)) { new_autoselect_star = TRUE; p += 10; } else if (STRNCMP(p, "autoselectplus", 14) == 0 && (p[14] == ',' || p[14] == NUL)) { new_autoselect_plus = TRUE; p += 14; } else if (STRNCMP(p, "autoselectml", 12) == 0 && (p[12] == ',' || p[12] == NUL)) { new_autoselectml = TRUE; p += 12; } else if (STRNCMP(p, "html", 4) == 0 && (p[4] == ',' || p[4] == NUL)) { new_html = TRUE; p += 4; } else if (STRNCMP(p, "exclude:", 8) == 0 && new_exclude_prog == NULL) { p += 8; new_exclude_prog = vim_regcomp(p, RE_MAGIC); if (new_exclude_prog == NULL) errmsg = e_invalid_argument; break; } else { errmsg = e_invalid_argument; break; } if (*p == ',') ++p; } if (errmsg == NULL) { if (global_busy) // clip_unnamed will be reset to clip_unnamed_saved // at end_global_changes clip_unnamed_saved = new_unnamed; else clip_unnamed = new_unnamed; clip_autoselect_star = new_autoselect_star; clip_autoselect_plus = new_autoselect_plus; clip_autoselectml = new_autoselectml; clip_html = new_html; vim_regfree(clip_exclude_prog); clip_exclude_prog = new_exclude_prog; #ifdef FEAT_GUI_GTK if (gui.in_use) { gui_gtk_set_selection_targets(); gui_gtk_set_dnd_targets(); } #endif } else vim_regfree(new_exclude_prog); return errmsg; } /* * Stuff for the X clipboard. Shared between VMS and Unix. */ #if defined(FEAT_XCLIPBOARD) || defined(FEAT_GUI_X11) || defined(PROTO) # include <X11/Xatom.h> # include <X11/Intrinsic.h> /* * Open the application context (if it hasn't been opened yet). * Used for Motif GUI and the xterm clipboard. */ void open_app_context(void) { if (app_context == NULL) { XtToolkitInitialize(); app_context = XtCreateApplicationContext(); } } static Atom vim_atom; // Vim's own special selection format static Atom vimenc_atom; // Vim's extended selection format static Atom utf8_atom; static Atom compound_text_atom; static Atom text_atom; static Atom targets_atom; static Atom timestamp_atom; // Used to get a timestamp void x11_setup_atoms(Display *dpy) { vim_atom = XInternAtom(dpy, VIM_ATOM_NAME, False); vimenc_atom = XInternAtom(dpy, VIMENC_ATOM_NAME,False); utf8_atom = XInternAtom(dpy, "UTF8_STRING", False); compound_text_atom = XInternAtom(dpy, "COMPOUND_TEXT", False); text_atom = XInternAtom(dpy, "TEXT", False); targets_atom = XInternAtom(dpy, "TARGETS", False); clip_star.sel_atom = XA_PRIMARY; clip_plus.sel_atom = XInternAtom(dpy, "CLIPBOARD", False); timestamp_atom = XInternAtom(dpy, "TIMESTAMP", False); } /* * X Selection stuff, for cutting and pasting text to other windows. */ static Boolean clip_x11_convert_selection_cb( Widget w UNUSED, Atom *sel_atom, Atom *target, Atom *type, XtPointer *value, long_u *length, int *format) { static char_u *save_result = NULL; static long_u save_length = 0; char_u *string; int motion_type; Clipboard_T *cbd; int i; if (*sel_atom == clip_plus.sel_atom) cbd = &clip_plus; else cbd = &clip_star; if (!cbd->owned) return False; // Shouldn't ever happen // requestor wants to know what target types we support if (*target == targets_atom) { static Atom array[7]; *value = (XtPointer)array; i = 0; array[i++] = targets_atom; array[i++] = vimenc_atom; array[i++] = vim_atom; if (enc_utf8) array[i++] = utf8_atom; array[i++] = XA_STRING; array[i++] = text_atom; array[i++] = compound_text_atom; *type = XA_ATOM; // This used to be: *format = sizeof(Atom) * 8; but that caused // crashes on 64 bit machines. (Peter Derr) *format = 32; *length = i; return True; } if ( *target != XA_STRING && *target != vimenc_atom && (*target != utf8_atom || !enc_utf8) && *target != vim_atom && *target != text_atom && *target != compound_text_atom) return False; clip_get_selection(cbd); motion_type = clip_convert_selection(&string, length, cbd); if (motion_type < 0) return False; // For our own format, the first byte contains the motion type if (*target == vim_atom) (*length)++; // Our own format with encoding: motion 'encoding' NUL text if (*target == vimenc_atom) *length += STRLEN(p_enc) + 2; if (save_length < *length || save_length / 2 >= *length) *value = XtRealloc((char *)save_result, (Cardinal)*length + 1); else *value = save_result; if (*value == NULL) { vim_free(string); return False; } save_result = (char_u *)*value; save_length = *length; if (*target == XA_STRING || (*target == utf8_atom && enc_utf8)) { mch_memmove(save_result, string, (size_t)(*length)); *type = *target; } else if (*target == compound_text_atom || *target == text_atom) { XTextProperty text_prop; char *string_nt = (char *)save_result; int conv_result; // create NUL terminated string which XmbTextListToTextProperty wants mch_memmove(string_nt, string, (size_t)*length); string_nt[*length] = NUL; conv_result = XmbTextListToTextProperty(X_DISPLAY, &string_nt, 1, XCompoundTextStyle, &text_prop); if (conv_result != Success) { vim_free(string); return False; } *value = (XtPointer)(text_prop.value); // from plain text *length = text_prop.nitems; *type = compound_text_atom; XtFree((char *)save_result); save_result = (char_u *)*value; save_length = *length; } else if (*target == vimenc_atom) { int l = STRLEN(p_enc); save_result[0] = motion_type; STRCPY(save_result + 1, p_enc); mch_memmove(save_result + l + 2, string, (size_t)(*length - l - 2)); *type = vimenc_atom; } else { save_result[0] = motion_type; mch_memmove(save_result + 1, string, (size_t)(*length - 1)); *type = vim_atom; } *format = 8; // 8 bits per char vim_free(string); return True; } static void clip_x11_lose_ownership_cb(Widget w UNUSED, Atom *sel_atom) { if (*sel_atom == clip_plus.sel_atom) clip_lose_selection(&clip_plus); else clip_lose_selection(&clip_star); } static void clip_x11_notify_cb(Widget w UNUSED, Atom *sel_atom UNUSED, Atom *target UNUSED) { // To prevent automatically freeing the selection value. } /* * Property callback to get a timestamp for XtOwnSelection. */ # if (defined(FEAT_X11) && defined(FEAT_XCLIPBOARD)) || defined(PROTO) static void clip_x11_timestamp_cb( Widget w, XtPointer n UNUSED, XEvent *event, Boolean *cont UNUSED) { Atom actual_type; int format; unsigned long nitems, bytes_after; unsigned char *prop=NULL; XPropertyEvent *xproperty=&event->xproperty; // Must be a property notify, state can't be Delete (True), has to be // one of the supported selection types. if (event->type != PropertyNotify || xproperty->state || (xproperty->atom != clip_star.sel_atom && xproperty->atom != clip_plus.sel_atom)) return; if (XGetWindowProperty(xproperty->display, xproperty->window, xproperty->atom, 0, 0, False, timestamp_atom, &actual_type, &format, &nitems, &bytes_after, &prop)) return; if (prop) XFree(prop); // Make sure the property type is "TIMESTAMP" and it's 32 bits. if (actual_type != timestamp_atom || format != 32) return; // Get the selection, using the event timestamp. if (XtOwnSelection(w, xproperty->atom, xproperty->time, clip_x11_convert_selection_cb, clip_x11_lose_ownership_cb, clip_x11_notify_cb) == OK) { // Set the "owned" flag now, there may have been a call to // lose_ownership_cb in between. if (xproperty->atom == clip_plus.sel_atom) clip_plus.owned = TRUE; else clip_star.owned = TRUE; } } void x11_setup_selection(Widget w) { XtAddEventHandler(w, PropertyChangeMask, False, /*(XtEventHandler)*/clip_x11_timestamp_cb, (XtPointer)NULL); } # endif static void clip_x11_request_selection_cb( Widget w UNUSED, XtPointer success, Atom *sel_atom, Atom *type, XtPointer value, long_u *length, int *format) { int motion_type = MAUTO; long_u len; char_u *p; char **text_list = NULL; Clipboard_T *cbd; char_u *tmpbuf = NULL; if (*sel_atom == clip_plus.sel_atom) cbd = &clip_plus; else cbd = &clip_star; if (value == NULL || *length == 0) { clip_free_selection(cbd); // nothing received, clear register *(int *)success = FALSE; return; } p = (char_u *)value; len = *length; if (*type == vim_atom) { motion_type = *p++; len--; } else if (*type == vimenc_atom) { char_u *enc; vimconv_T conv; int convlen; motion_type = *p++; --len; enc = p; p += STRLEN(p) + 1; len -= p - enc; // If the encoding of the text is different from 'encoding', attempt // converting it. conv.vc_type = CONV_NONE; convert_setup(&conv, enc, p_enc); if (conv.vc_type != CONV_NONE) { convlen = len; // Need to use an int here. tmpbuf = string_convert(&conv, p, &convlen); len = convlen; if (tmpbuf != NULL) p = tmpbuf; convert_setup(&conv, NULL, NULL); } } else if (*type == compound_text_atom || *type == utf8_atom || (enc_dbcs != 0 && *type == text_atom)) { XTextProperty text_prop; int n_text = 0; int status; text_prop.value = (unsigned char *)value; text_prop.encoding = *type; text_prop.format = *format; text_prop.nitems = len; #if defined(X_HAVE_UTF8_STRING) if (*type == utf8_atom) status = Xutf8TextPropertyToTextList(X_DISPLAY, &text_prop, &text_list, &n_text); else #endif status = XmbTextPropertyToTextList(X_DISPLAY, &text_prop, &text_list, &n_text); if (status != Success || n_text < 1) { *(int *)success = FALSE; return; } p = (char_u *)text_list[0]; len = STRLEN(p); } clip_yank_selection(motion_type, p, (long)len, cbd); if (text_list != NULL) XFreeStringList(text_list); vim_free(tmpbuf); XtFree((char *)value); *(int *)success = TRUE; } void clip_x11_request_selection( Widget myShell, Display *dpy, Clipboard_T *cbd) { XEvent event; Atom type; static int success; int i; time_t start_time; int timed_out = FALSE; for (i = 0; i < 6; i++) { switch (i) { case 0: type = vimenc_atom; break; case 1: type = vim_atom; break; case 2: type = utf8_atom; break; case 3: type = compound_text_atom; break; case 4: type = text_atom; break; default: type = XA_STRING; } if (type == utf8_atom # if defined(X_HAVE_UTF8_STRING) && !enc_utf8 # endif ) // Only request utf-8 when 'encoding' is utf8 and // Xutf8TextPropertyToTextList is available. continue; success = MAYBE; XtGetSelectionValue(myShell, cbd->sel_atom, type, clip_x11_request_selection_cb, (XtPointer)&success, CurrentTime); // Make sure the request for the selection goes out before waiting for // a response. XFlush(dpy); /* * Wait for result of selection request, otherwise if we type more * characters, then they will appear before the one that requested the * paste! Don't worry, we will catch up with any other events later. */ start_time = time(NULL); while (success == MAYBE) { if (XCheckTypedEvent(dpy, PropertyNotify, &event) || XCheckTypedEvent(dpy, SelectionNotify, &event) || XCheckTypedEvent(dpy, SelectionRequest, &event)) { // This is where clip_x11_request_selection_cb() should be // called. It may actually happen a bit later, so we loop // until "success" changes. // We may get a SelectionRequest here and if we don't handle // it we hang. KDE klipper does this, for example. // We need to handle a PropertyNotify for large selections. XtDispatchEvent(&event); continue; } // Time out after 2 to 3 seconds to avoid that we hang when the // other process doesn't respond. Note that the SelectionNotify // event may still come later when the selection owner comes back // to life and the text gets inserted unexpectedly. Don't know // why that happens or how to avoid that :-(. if (time(NULL) > start_time + 2) { timed_out = TRUE; break; } // Do we need this? Probably not. XSync(dpy, False); // Wait for 1 msec to avoid that we eat up all CPU time. ui_delay(1L, TRUE); } if (success == TRUE) return; // don't do a retry with another type after timing out, otherwise we // hang for 15 seconds. if (timed_out) break; } // Final fallback position - use the X CUT_BUFFER0 store yank_cut_buffer0(dpy, cbd); } void clip_x11_lose_selection(Widget myShell, Clipboard_T *cbd) { XtDisownSelection(myShell, cbd->sel_atom, XtLastTimestampProcessed(XtDisplay(myShell))); } int clip_x11_own_selection(Widget myShell, Clipboard_T *cbd) { // When using the GUI we have proper timestamps, use the one of the last // event. When in the console we don't get events (the terminal gets // them), Get the time by a zero-length append, clip_x11_timestamp_cb will // be called with the current timestamp. #ifdef FEAT_GUI if (gui.in_use) { if (XtOwnSelection(myShell, cbd->sel_atom, XtLastTimestampProcessed(XtDisplay(myShell)), clip_x11_convert_selection_cb, clip_x11_lose_ownership_cb, clip_x11_notify_cb) == False) return FAIL; } else #endif { if (!XChangeProperty(XtDisplay(myShell), XtWindow(myShell), cbd->sel_atom, timestamp_atom, 32, PropModeAppend, NULL, 0)) return FAIL; } // Flush is required in a terminal as nothing else is doing it. XFlush(XtDisplay(myShell)); return OK; } /* * Send the current selection to the clipboard. Do nothing for X because we * will fill in the selection only when requested by another app. */ void clip_x11_set_selection(Clipboard_T *cbd UNUSED) { } #endif #if defined(FEAT_XCLIPBOARD) || defined(FEAT_GUI_X11) \ || defined(FEAT_GUI_GTK) || defined(PROTO) /* * Get the contents of the X CUT_BUFFER0 and put it in "cbd". */ void yank_cut_buffer0(Display *dpy, Clipboard_T *cbd) { int nbytes = 0; char_u *buffer = (char_u *)XFetchBuffer(dpy, &nbytes, 0); if (nbytes > 0) { int done = FALSE; // CUT_BUFFER0 is supposed to be always latin1. Convert to 'enc' when // using a multi-byte encoding. Conversion between two 8-bit // character sets usually fails and the text might actually be in // 'enc' anyway. if (has_mbyte) { char_u *conv_buf; vimconv_T vc; vc.vc_type = CONV_NONE; if (convert_setup(&vc, (char_u *)"latin1", p_enc) == OK) { conv_buf = string_convert(&vc, buffer, &nbytes); if (conv_buf != NULL) { clip_yank_selection(MCHAR, conv_buf, (long)nbytes, cbd); vim_free(conv_buf); done = TRUE; } convert_setup(&vc, NULL, NULL); } } if (!done) // use the text without conversion clip_yank_selection(MCHAR, buffer, (long)nbytes, cbd); XFree((void *)buffer); if (p_verbose > 0) { verbose_enter(); verb_msg(_("Used CUT_BUFFER0 instead of empty selection")); verbose_leave(); } } } #endif /* * SELECTION / PRIMARY ('*') * * Text selection stuff that uses the GUI selection register '*'. When using a * GUI this may be text from another window, otherwise it is the last text we * had highlighted with VIsual mode. With mouse support, clicking the middle * button performs the paste, otherwise you will need to do <"*p>. " * If not under X, it is synonymous with the clipboard register '+'. * * X CLIPBOARD ('+') * * Text selection stuff that uses the GUI clipboard register '+'. * Under X, this matches the standard cut/paste buffer CLIPBOARD selection. * It will be used for unnamed cut/pasting is 'clipboard' contains "unnamed", * otherwise you will need to do <"+p>. " * If not under X, it is synonymous with the selection register '*'. */ /* * Routine to export any final X selection we had to the environment * so that the text is still available after Vim has exited. X selections * only exist while the owning application exists, so we write to the * permanent (while X runs) store CUT_BUFFER0. * Dump the CLIPBOARD selection if we own it (it's logically the more * 'permanent' of the two), otherwise the PRIMARY one. * For now, use a hard-coded sanity limit of 1Mb of data. */ #if (defined(FEAT_X11) && defined(FEAT_CLIPBOARD)) || defined(PROTO) void x11_export_final_selection(void) { Display *dpy; char_u *str = NULL; long_u len = 0; int motion_type = -1; # ifdef FEAT_GUI if (gui.in_use) dpy = X_DISPLAY; else # endif # ifdef FEAT_XCLIPBOARD dpy = xterm_dpy; # else return; # endif // Get selection to export if (clip_plus.owned) motion_type = clip_convert_selection(&str, &len, &clip_plus); else if (clip_star.owned) motion_type = clip_convert_selection(&str, &len, &clip_star); // Check it's OK if (dpy != NULL && str != NULL && motion_type >= 0 && len < 1024*1024 && len > 0) { int ok = TRUE; // The CUT_BUFFER0 is supposed to always contain latin1. Convert from // 'enc' when it is a multi-byte encoding. When 'enc' is an 8-bit // encoding conversion usually doesn't work, so keep the text as-is. if (has_mbyte) { vimconv_T vc; vc.vc_type = CONV_NONE; if (convert_setup(&vc, p_enc, (char_u *)"latin1") == OK) { int intlen = len; char_u *conv_str; vc.vc_fail = TRUE; conv_str = string_convert(&vc, str, &intlen); len = intlen; if (conv_str != NULL) { vim_free(str); str = conv_str; } else { ok = FALSE; } convert_setup(&vc, NULL, NULL); } else { ok = FALSE; } } // Do not store the string if conversion failed. Better to use any // other selection than garbled text. if (ok) { XStoreBuffer(dpy, (char *)str, (int)len, 0); XFlush(dpy); } } vim_free(str); } #endif void clip_free_selection(Clipboard_T *cbd) { yankreg_T *y_ptr = get_y_current(); if (cbd == &clip_plus) set_y_current(get_y_register(PLUS_REGISTER)); else set_y_current(get_y_register(STAR_REGISTER)); free_yank_all(); get_y_current()->y_size = 0; set_y_current(y_ptr); } /* * Get the selected text and put it in register '*' or '+'. */ void clip_get_selection(Clipboard_T *cbd) { yankreg_T *old_y_previous, *old_y_current; pos_T old_cursor; pos_T old_visual; int old_visual_mode; colnr_T old_curswant; int old_set_curswant; pos_T old_op_start, old_op_end; oparg_T oa; cmdarg_T ca; if (cbd->owned) { if ((cbd == &clip_plus && get_y_register(PLUS_REGISTER)->y_array != NULL) || (cbd == &clip_star && get_y_register(STAR_REGISTER)->y_array != NULL)) return; // Avoid triggering autocmds such as TextYankPost. block_autocmds(); // Get the text between clip_star.start & clip_star.end old_y_previous = get_y_previous(); old_y_current = get_y_current(); old_cursor = curwin->w_cursor; old_curswant = curwin->w_curswant; old_set_curswant = curwin->w_set_curswant; old_op_start = curbuf->b_op_start; old_op_end = curbuf->b_op_end; old_visual = VIsual; old_visual_mode = VIsual_mode; clear_oparg(&oa); oa.regname = (cbd == &clip_plus ? '+' : '*'); oa.op_type = OP_YANK; CLEAR_FIELD(ca); ca.oap = &oa; ca.cmdchar = 'y'; ca.count1 = 1; ca.retval = CA_NO_ADJ_OP_END; do_pending_operator(&ca, 0, TRUE); // restore things set_y_previous(old_y_previous); set_y_current(old_y_current); curwin->w_cursor = old_cursor; changed_cline_bef_curs(); // need to update w_virtcol et al curwin->w_curswant = old_curswant; curwin->w_set_curswant = old_set_curswant; curbuf->b_op_start = old_op_start; curbuf->b_op_end = old_op_end; VIsual = old_visual; VIsual_mode = old_visual_mode; unblock_autocmds(); } else if (!is_clipboard_needs_update()) { clip_free_selection(cbd); // Try to get selected text from another window clip_gen_request_selection(cbd); } } /* * Convert from the GUI selection string into the '*'/'+' register. */ void clip_yank_selection( int type, char_u *str, long len, Clipboard_T *cbd) { yankreg_T *y_ptr; if (cbd == &clip_plus) y_ptr = get_y_register(PLUS_REGISTER); else y_ptr = get_y_register(STAR_REGISTER); clip_free_selection(cbd); str_to_reg(y_ptr, type, str, len, -1, FALSE); } /* * Convert the '*'/'+' register into a GUI selection string returned in *str * with length *len. * Returns the motion type, or -1 for failure. */ int clip_convert_selection(char_u **str, long_u *len, Clipboard_T *cbd) { char_u *p; int lnum; int i, j; int_u eolsize; yankreg_T *y_ptr; if (cbd == &clip_plus) y_ptr = get_y_register(PLUS_REGISTER); else y_ptr = get_y_register(STAR_REGISTER); # ifdef USE_CRNL eolsize = 2; # else eolsize = 1; # endif *str = NULL; *len = 0; if (y_ptr->y_array == NULL) return -1; for (i = 0; i < y_ptr->y_size; i++) *len += (long_u)STRLEN(y_ptr->y_array[i]) + eolsize; // Don't want newline character at end of last line if we're in MCHAR mode. if (y_ptr->y_type == MCHAR && *len >= eolsize) *len -= eolsize; p = *str = alloc(*len + 1); // add one to avoid zero if (p == NULL) return -1; lnum = 0; for (i = 0, j = 0; i < (int)*len; i++, j++) { if (y_ptr->y_array[lnum][j] == '\n') p[i] = NUL; else if (y_ptr->y_array[lnum][j] == NUL) { # ifdef USE_CRNL p[i++] = '\r'; # endif p[i] = '\n'; lnum++; j = -1; } else p[i] = y_ptr->y_array[lnum][j]; } return y_ptr->y_type; } /* * When "regname" is a clipboard register, obtain the selection. If it's not * available return zero, otherwise return "regname". */ int may_get_selection(int regname) { if (regname == '*') { if (!clip_star.available) regname = 0; else clip_get_selection(&clip_star); } else if (regname == '+') { if (!clip_plus.available) regname = 0; else clip_get_selection(&clip_plus); } return regname; } /* * If we have written to a clipboard register, send the text to the clipboard. */ void may_set_selection(void) { if ((get_y_current() == get_y_register(STAR_REGISTER)) && clip_star.available) { clip_own_selection(&clip_star); clip_gen_set_selection(&clip_star); } else if ((get_y_current() == get_y_register(PLUS_REGISTER)) && clip_plus.available) { clip_own_selection(&clip_plus); clip_gen_set_selection(&clip_plus); } } /* * Adjust the register name pointed to with "rp" for the clipboard being * used always and the clipboard being available. */ void adjust_clip_reg(int *rp) { // If no reg. specified, and "unnamed" or "unnamedplus" is in 'clipboard', // use '*' or '+' reg, respectively. "unnamedplus" prevails. if (*rp == 0 && (clip_unnamed != 0 || clip_unnamed_saved != 0)) { if (clip_unnamed != 0) *rp = ((clip_unnamed & CLIP_UNNAMED_PLUS) && clip_plus.available) ? '+' : '*'; else *rp = ((clip_unnamed_saved & CLIP_UNNAMED_PLUS) && clip_plus.available) ? '+' : '*'; } if (!clip_star.available && *rp == '*') *rp = 0; if (!clip_plus.available && *rp == '+') *rp = 0; } #endif // FEAT_CLIPBOARD