Mercurial > vim
view src/ui.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 | 1009c33499e7 |
children | 664ee4c0daca |
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. */ /* * ui.c: functions that handle the user interface. * 1. Keyboard input stuff, and a bit of windowing stuff. These are called * before the machine specific stuff (mch_*) so that we can call the GUI * stuff instead if the GUI is running. * 2. Input buffer stuff. */ #include "vim.h" void ui_write(char_u *s, int len, int console UNUSED) { #ifdef FEAT_GUI if (gui.in_use && !gui.dying && !gui.starting # ifndef NO_CONSOLE && !console # endif ) { gui_write(s, len); if (p_wd) gui_wait_for_chars(p_wd, typebuf.tb_change_cnt); return; } #endif #ifndef NO_CONSOLE // Don't output anything in silent mode ("ex -s") unless 'verbose' set if (!(silent_mode && p_verbose == 0)) { # if !defined(MSWIN) char_u *tofree = NULL; if (output_conv.vc_type != CONV_NONE) { // Convert characters from 'encoding' to 'termencoding'. tofree = string_convert(&output_conv, s, &len); if (tofree != NULL) s = tofree; } # endif mch_write(s, len); # if defined(HAVE_FSYNC) if (console && s[len - 1] == '\n') vim_fsync(1); # endif # if !defined(MSWIN) if (output_conv.vc_type != CONV_NONE) vim_free(tofree); # endif } #endif } #if defined(UNIX) || defined(VMS) || defined(PROTO) || defined(MSWIN) /* * When executing an external program, there may be some typed characters that * are not consumed by it. Give them back to ui_inchar() and they are stored * here for the next call. */ static char_u *ta_str = NULL; static int ta_off; // offset for next char to use when ta_str != NULL static int ta_len; // length of ta_str when it's not NULL void ui_inchar_undo(char_u *s, int len) { char_u *new; int newlen; newlen = len; if (ta_str != NULL) newlen += ta_len - ta_off; new = alloc(newlen); if (new == NULL) return; if (ta_str != NULL) { mch_memmove(new, ta_str + ta_off, (size_t)(ta_len - ta_off)); mch_memmove(new + ta_len - ta_off, s, (size_t)len); vim_free(ta_str); } else mch_memmove(new, s, (size_t)len); ta_str = new; ta_len = newlen; ta_off = 0; } #endif /* * ui_inchar(): low level input function. * Get characters from the keyboard. * Return the number of characters that are available. * If "wtime" == 0 do not wait for characters. * If "wtime" == -1 wait forever for characters. * If "wtime" > 0 wait "wtime" milliseconds for a character. * * "tb_change_cnt" is the value of typebuf.tb_change_cnt if "buf" points into * it. When typebuf.tb_change_cnt changes (e.g., when a message is received * from a remote client) "buf" can no longer be used. "tb_change_cnt" is NULL * otherwise. */ int ui_inchar( char_u *buf, int maxlen, long wtime, // don't use "time", MIPS cannot handle it int tb_change_cnt) { int retval = 0; #if defined(FEAT_GUI) && (defined(UNIX) || defined(VMS)) /* * Use the typeahead if there is any. */ if (ta_str != NULL) { if (maxlen >= ta_len - ta_off) { mch_memmove(buf, ta_str + ta_off, (size_t)ta_len); VIM_CLEAR(ta_str); return ta_len; } mch_memmove(buf, ta_str + ta_off, (size_t)maxlen); ta_off += maxlen; return maxlen; } #endif #ifdef FEAT_PROFILE if (do_profiling == PROF_YES && wtime != 0) prof_inchar_enter(); #endif #ifdef NO_CONSOLE_INPUT // Don't wait for character input when the window hasn't been opened yet. // Do try reading, this works when redirecting stdin from a file. // Must return something, otherwise we'll loop forever. If we run into // this very often we probably got stuck, exit Vim. if (no_console_input()) { static int count = 0; # ifndef NO_CONSOLE retval = mch_inchar(buf, maxlen, wtime, tb_change_cnt); if (retval > 0 || typebuf_changed(tb_change_cnt) || wtime >= 0) goto theend; # endif if (wtime == -1 && ++count == 1000) read_error_exit(); buf[0] = CAR; retval = 1; goto theend; } #endif // If we are going to wait for some time or block... if (wtime == -1 || wtime > 100L) { // ... allow signals to kill us. (void)vim_handle_signal(SIGNAL_UNBLOCK); // ... there is no need for CTRL-C to interrupt something, don't let // it set got_int when it was mapped. if ((mapped_ctrl_c | curbuf->b_mapped_ctrl_c) & get_real_state()) ctrl_c_interrupts = FALSE; } /* * Here we call gui_inchar() or mch_inchar(), the GUI or machine-dependent * input function. The functionality they implement is like this: * * while (not timed out) * { * handle-resize; * parse-queued-messages; * if (waited for 'updatetime') * trigger-cursorhold; * ui_wait_for_chars_or_timer() * if (character available) * break; * } * * ui_wait_for_chars_or_timer() does: * * while (not timed out) * { * if (any-timer-triggered) * invoke-timer-callback; * wait-for-character(); * if (character available) * break; * } * * wait-for-character() does: * while (not timed out) * { * Wait for event; * if (something on channel) * read/write channel; * else if (resized) * handle_resize(); * else if (system event) * deal-with-system-event; * else if (character available) * break; * } * */ #ifdef FEAT_GUI if (gui.in_use) retval = gui_inchar(buf, maxlen, wtime, tb_change_cnt); #endif #ifndef NO_CONSOLE # ifdef FEAT_GUI else # endif retval = mch_inchar(buf, maxlen, wtime, tb_change_cnt); #endif if (wtime == -1 || wtime > 100L) // block SIGHUP et al. (void)vim_handle_signal(SIGNAL_BLOCK); ctrl_c_interrupts = TRUE; #ifdef NO_CONSOLE_INPUT theend: #endif #ifdef FEAT_PROFILE if (do_profiling == PROF_YES && wtime != 0) prof_inchar_exit(); #endif return retval; } #if defined(UNIX) || defined(VMS) || defined(FEAT_GUI) || defined(PROTO) /* * Common code for mch_inchar() and gui_inchar(): Wait for a while or * indefinitely until characters are available, dealing with timers and * messages on channels. * * "buf" may be NULL if the available characters are not to be returned, only * check if they are available. * * Return the number of characters that are available. * If "wtime" == 0 do not wait for characters. * If "wtime" == n wait a short time for characters. * If "wtime" == -1 wait forever for characters. */ int inchar_loop( char_u *buf, int maxlen, long wtime, // don't use "time", MIPS cannot handle it int tb_change_cnt, int (*wait_func)(long wtime, int *interrupted, int ignore_input), int (*resize_func)(int check_only)) { int len; int interrupted = FALSE; int did_call_wait_func = FALSE; int did_start_blocking = FALSE; long wait_time; long elapsed_time = 0; #ifdef ELAPSED_FUNC elapsed_T start_tv; ELAPSED_INIT(start_tv); #endif // repeat until we got a character or waited long enough for (;;) { // Check if window changed size while we were busy, perhaps the ":set // columns=99" command was used. if (resize_func != NULL) resize_func(FALSE); #ifdef MESSAGE_QUEUE // Only process messages when waiting. if (wtime != 0) { parse_queued_messages(); // If input was put directly in typeahead buffer bail out here. if (typebuf_changed(tb_change_cnt)) return 0; } #endif if (wtime < 0 && did_start_blocking) // blocking and already waited for p_ut wait_time = -1; else { if (wtime >= 0) wait_time = wtime; else // going to block after p_ut wait_time = p_ut; #ifdef ELAPSED_FUNC elapsed_time = ELAPSED_FUNC(start_tv); #endif wait_time -= elapsed_time; // If the waiting time is now zero or less, we timed out. However, // loop at least once to check for characters and events. Matters // when "wtime" is zero. if (wait_time <= 0 && did_call_wait_func) { if (wtime >= 0) // no character available within "wtime" return 0; // No character available within 'updatetime'. did_start_blocking = TRUE; if (trigger_cursorhold() && maxlen >= 3 && !typebuf_changed(tb_change_cnt)) { // Put K_CURSORHOLD in the input buffer or return it. if (buf == NULL) { char_u ibuf[3]; ibuf[0] = CSI; ibuf[1] = KS_EXTRA; ibuf[2] = (int)KE_CURSORHOLD; add_to_input_buf(ibuf, 3); } else { buf[0] = K_SPECIAL; buf[1] = KS_EXTRA; buf[2] = (int)KE_CURSORHOLD; } return 3; } // There is no character available within 'updatetime' seconds: // flush all the swap files to disk. Also done when // interrupted by SIGWINCH. before_blocking(); continue; } } #ifdef FEAT_JOB_CHANNEL if (wait_time < 0 || wait_time > 100L) { // Checking if a job ended requires polling. Do this at least // every 100 msec. if (has_pending_job()) wait_time = 100L; // If there is readahead then parse_queued_messages() timed out and // we should call it again soon. if (channel_any_readahead()) wait_time = 10L; } #endif #ifdef FEAT_BEVAL_GUI if (p_beval && wait_time > 100L) // The 'balloonexpr' may indirectly invoke a callback while waiting // for a character, need to check often. wait_time = 100L; #endif // Wait for a character to be typed or another event, such as the winch // signal or an event on the monitored file descriptors. did_call_wait_func = TRUE; if (wait_func(wait_time, &interrupted, FALSE)) { // If input was put directly in typeahead buffer bail out here. if (typebuf_changed(tb_change_cnt)) return 0; // We might have something to return now. if (buf == NULL) // "buf" is NULL, we were just waiting, not actually getting // input. return input_available(); len = read_from_input_buf(buf, (long)maxlen); if (len > 0) return len; continue; } // Timed out or interrupted with no character available. #ifndef ELAPSED_FUNC // estimate the elapsed time elapsed_time += wait_time; #endif if ((resize_func != NULL && resize_func(TRUE)) #if defined(FEAT_CLIENTSERVER) && defined(UNIX) || server_waiting() #endif #ifdef MESSAGE_QUEUE || interrupted #endif || wait_time > 0 || (wtime < 0 && !did_start_blocking)) // no character available, but something to be done, keep going continue; // no character available or interrupted, return zero break; } return 0; } #endif #if defined(FEAT_TIMERS) || defined(PROTO) /* * Wait for a timer to fire or "wait_func" to return non-zero. * Returns OK when something was read. * Returns FAIL when it timed out or was interrupted. */ int ui_wait_for_chars_or_timer( long wtime, int (*wait_func)(long wtime, int *interrupted, int ignore_input), int *interrupted, int ignore_input) { int due_time; long remaining = wtime; int tb_change_cnt = typebuf.tb_change_cnt; # ifdef FEAT_JOB_CHANNEL int brief_wait = FALSE; # endif // When waiting very briefly don't trigger timers. if (wtime >= 0 && wtime < 10L) return wait_func(wtime, NULL, ignore_input); while (wtime < 0 || remaining > 0) { // Trigger timers and then get the time in wtime until the next one is // due. Wait up to that time. due_time = check_due_timer(); if (typebuf.tb_change_cnt != tb_change_cnt) { // timer may have used feedkeys() return FAIL; } if (due_time <= 0 || (wtime > 0 && due_time > remaining)) due_time = remaining; # if defined(FEAT_JOB_CHANNEL) || defined(FEAT_SOUND_CANBERRA) || defined(FEAT_SOUND_MACOSX) if ((due_time < 0 || due_time > 10L) && ( # if defined(FEAT_JOB_CHANNEL) ( # if defined(FEAT_GUI) !gui.in_use && # endif (has_pending_job() || channel_any_readahead())) # if defined(FEAT_SOUND_CANBERRA) || defined(FEAT_SOUND_MACOSX) || # endif # endif # if defined(FEAT_SOUND_CANBERRA) || defined(FEAT_SOUND_MACOSX) has_any_sound_callback() # endif )) { // There is a pending job or channel, should return soon in order // to handle them ASAP. Do check for input briefly. due_time = 10L; # ifdef FEAT_JOB_CHANNEL brief_wait = TRUE; # endif } # endif if (wait_func(due_time, interrupted, ignore_input)) return OK; if ((interrupted != NULL && *interrupted) # ifdef FEAT_JOB_CHANNEL || brief_wait # endif ) // Nothing available, but need to return so that side effects get // handled, such as handling a message on a channel. return FAIL; if (wtime > 0) remaining -= due_time; } return FAIL; } #endif /* * Return non-zero if a character is available. */ int ui_char_avail(void) { #ifdef FEAT_GUI if (gui.in_use) { gui_mch_update(); return input_available(); } #endif #ifndef NO_CONSOLE # ifdef NO_CONSOLE_INPUT if (no_console_input()) return 0; # endif return mch_char_avail(); #else return 0; #endif } /* * Delay for the given number of milliseconds. If ignoreinput is FALSE then we * cancel the delay if a key is hit. */ void ui_delay(long msec_arg, int ignoreinput) { long msec = msec_arg; #ifdef FEAT_EVAL if (ui_delay_for_testing > 0) msec = ui_delay_for_testing; ch_log(NULL, "ui_delay(%ld)", msec); #endif #ifdef FEAT_GUI if (gui.in_use && !ignoreinput) gui_wait_for_chars(msec, typebuf.tb_change_cnt); else #endif mch_delay(msec, ignoreinput ? MCH_DELAY_IGNOREINPUT : 0); } /* * If the machine has job control, use it to suspend the program, * otherwise fake it by starting a new shell. * When running the GUI iconify the window. */ void ui_suspend(void) { #ifdef FEAT_GUI if (gui.in_use) { gui_mch_iconify(); return; } #endif mch_suspend(); } #if !defined(UNIX) || !defined(SIGTSTP) || defined(PROTO) /* * When the OS can't really suspend, call this function to start a shell. * This is never called in the GUI. */ void suspend_shell(void) { if (*p_sh == NUL) emsg(_(e_shell_option_is_empty)); else { msg_puts(_("new shell started\n")); do_shell(NULL, 0); } } #endif /* * Try to get the current Vim shell size. Put the result in Rows and Columns. * Use the new sizes as defaults for 'columns' and 'lines'. * Return OK when size could be determined, FAIL otherwise. */ int ui_get_shellsize(void) { int retval; #ifdef FEAT_GUI if (gui.in_use) retval = gui_get_shellsize(); else #endif retval = mch_get_shellsize(); check_shellsize(); // adjust the default for 'lines' and 'columns' if (retval == OK) { set_number_default("lines", Rows); set_number_default("columns", Columns); } return retval; } /* * Set the size of the Vim shell according to Rows and Columns, if possible. * The gui_set_shellsize() or mch_set_shellsize() function will try to set the * new size. If this is not possible, it will adjust Rows and Columns. */ void ui_set_shellsize( int mustset UNUSED) // set by the user { #ifdef FEAT_GUI if (gui.in_use) gui_set_shellsize(mustset, TRUE, RESIZE_BOTH); else #endif mch_set_shellsize(); } /* * Called when Rows and/or Columns changed. Adjust scroll region and mouse * region. */ void ui_new_shellsize(void) { if (full_screen && !exiting) { #ifdef FEAT_GUI if (gui.in_use) gui_new_shellsize(); else #endif mch_new_shellsize(); } } #if ((defined(FEAT_EVAL) || defined(FEAT_TERMINAL)) \ && (defined(FEAT_GUI) \ || defined(MSWIN) \ || (defined(HAVE_TGETENT) && defined(FEAT_TERMRESPONSE)))) \ || defined(PROTO) /* * Get the window position in pixels, if possible. * Return FAIL when not possible. */ int ui_get_winpos(int *x, int *y, varnumber_T timeout UNUSED) { # ifdef FEAT_GUI if (gui.in_use) return gui_mch_get_winpos(x, y); # endif # if defined(MSWIN) && (!defined(FEAT_GUI) || defined(VIMDLL)) return mch_get_winpos(x, y); # else # if defined(HAVE_TGETENT) && defined(FEAT_TERMRESPONSE) return term_get_winpos(x, y, timeout); # else return FAIL; # endif # endif } #endif void ui_breakcheck(void) { ui_breakcheck_force(FALSE); } /* * When "force" is true also check when the terminal is not in raw mode. * This is useful to read input on channels. */ void ui_breakcheck_force(int force) { static int recursive = FALSE; int save_updating_screen = updating_screen; // We could be called recursively if stderr is redirected, calling // fill_input_buf() calls settmode() when stdin isn't a tty. settmode() // calls vgetorpeek() which calls ui_breakcheck() again. if (recursive) return; recursive = TRUE; // We do not want gui_resize_shell() to redraw the screen here. ++updating_screen; #ifdef FEAT_GUI if (gui.in_use) gui_mch_update(); else #endif mch_breakcheck(force); if (save_updating_screen) updating_screen = TRUE; else after_updating_screen(FALSE); recursive = FALSE; } ////////////////////////////////////////////////////////////////////////////// // Functions that handle the input buffer. // This is used for any GUI version, and the unix terminal version. // // For Unix, the input characters are buffered to be able to check for a // CTRL-C. This should be done with signals, but I don't know how to do that // in a portable way for a tty in RAW mode. // // For the client-server code in the console the received keys are put in the // input buffer. #if defined(USE_INPUT_BUF) || defined(PROTO) /* * Internal typeahead buffer. Includes extra space for long key code * descriptions which would otherwise overflow. The buffer is considered full * when only this extra space (or part of it) remains. */ #if defined(FEAT_JOB_CHANNEL) || defined(FEAT_CLIENTSERVER) /* * NetBeans stuffs debugger commands into the input buffer. * This requires a larger buffer... * (Madsen) Go with this for remote input as well ... */ # define INBUFLEN 4096 #else # define INBUFLEN 250 #endif static char_u inbuf[INBUFLEN + MAX_KEY_CODE_LEN]; static int inbufcount = 0; // number of chars in inbuf[] /* * vim_is_input_buf_full(), vim_is_input_buf_empty(), add_to_input_buf(), and * trash_input_buf() are functions for manipulating the input buffer. These * are used by the gui_* calls when a GUI is used to handle keyboard input. */ int vim_is_input_buf_full(void) { return (inbufcount >= INBUFLEN); } int vim_is_input_buf_empty(void) { return (inbufcount == 0); } #if defined(FEAT_OLE) || defined(PROTO) int vim_free_in_input_buf(void) { return (INBUFLEN - inbufcount); } #endif #if defined(FEAT_GUI_GTK) || defined(PROTO) int vim_used_in_input_buf(void) { return inbufcount; } #endif /* * Return the current contents of the input buffer and make it empty. * The returned pointer must be passed to set_input_buf() later. */ char_u * get_input_buf(void) { garray_T *gap; // We use a growarray to store the data pointer and the length. gap = ALLOC_ONE(garray_T); if (gap != NULL) { // Add one to avoid a zero size. gap->ga_data = alloc(inbufcount + 1); if (gap->ga_data != NULL) mch_memmove(gap->ga_data, inbuf, (size_t)inbufcount); gap->ga_len = inbufcount; } trash_input_buf(); return (char_u *)gap; } /* * Restore the input buffer with a pointer returned from get_input_buf(). * The allocated memory is freed, this only works once! * When "overwrite" is FALSE input typed later is kept. */ void set_input_buf(char_u *p, int overwrite) { garray_T *gap = (garray_T *)p; if (gap == NULL) return; if (gap->ga_data != NULL) { if (overwrite || inbufcount + gap->ga_len >= INBUFLEN) { mch_memmove(inbuf, gap->ga_data, gap->ga_len); inbufcount = gap->ga_len; } else { mch_memmove(inbuf + gap->ga_len, inbuf, inbufcount); mch_memmove(inbuf, gap->ga_data, gap->ga_len); inbufcount += gap->ga_len; } vim_free(gap->ga_data); } vim_free(gap); } /* * Add the given bytes to the input buffer * Special keys start with CSI. A real CSI must have been translated to * CSI KS_EXTRA KE_CSI. K_SPECIAL doesn't require translation. */ void add_to_input_buf(char_u *s, int len) { if (inbufcount + len > INBUFLEN + MAX_KEY_CODE_LEN) return; // Shouldn't ever happen! while (len--) inbuf[inbufcount++] = *s++; } /* * Add "str[len]" to the input buffer while escaping CSI bytes. */ void add_to_input_buf_csi(char_u *str, int len) { int i; char_u buf[2]; for (i = 0; i < len; ++i) { add_to_input_buf(str + i, 1); if (str[i] == CSI) { // Turn CSI into K_CSI. buf[0] = KS_EXTRA; buf[1] = (int)KE_CSI; add_to_input_buf(buf, 2); } } } /* * Remove everything from the input buffer. Called when ^C is found. */ void trash_input_buf(void) { inbufcount = 0; } /* * Read as much data from the input buffer as possible up to maxlen, and store * it in buf. */ int read_from_input_buf(char_u *buf, long maxlen) { if (inbufcount == 0) // if the buffer is empty, fill it fill_input_buf(TRUE); if (maxlen > inbufcount) maxlen = inbufcount; mch_memmove(buf, inbuf, (size_t)maxlen); inbufcount -= maxlen; // check "maxlen" to avoid clang warning if (inbufcount > 0 && maxlen > 0) mch_memmove(inbuf, inbuf + maxlen, (size_t)inbufcount); return (int)maxlen; } void fill_input_buf(int exit_on_error UNUSED) { #if defined(UNIX) || defined(VMS) || defined(MACOS_X) int len; int try; static int did_read_something = FALSE; static char_u *rest = NULL; // unconverted rest of previous read static int restlen = 0; int unconverted; #endif #ifdef FEAT_GUI if (gui.in_use # ifdef NO_CONSOLE_INPUT // Don't use the GUI input when the window hasn't been opened yet. // We get here from ui_inchar() when we should try reading from stdin. && !no_console_input() # endif ) { gui_mch_update(); return; } #endif #if defined(UNIX) || defined(VMS) || defined(MACOS_X) if (vim_is_input_buf_full()) return; /* * Fill_input_buf() is only called when we really need a character. * If we can't get any, but there is some in the buffer, just return. * If we can't get any, and there isn't any in the buffer, we give up and * exit Vim. */ if (rest != NULL) { // Use remainder of previous call, starts with an invalid character // that may become valid when reading more. if (restlen > INBUFLEN - inbufcount) unconverted = INBUFLEN - inbufcount; else unconverted = restlen; mch_memmove(inbuf + inbufcount, rest, unconverted); if (unconverted == restlen) VIM_CLEAR(rest); else { restlen -= unconverted; mch_memmove(rest, rest + unconverted, restlen); } inbufcount += unconverted; } else unconverted = 0; len = 0; // to avoid gcc warning for (try = 0; try < 100; ++try) { size_t readlen = (size_t)((INBUFLEN - inbufcount) / input_conv.vc_factor); # ifdef VMS len = vms_read((char *)inbuf + inbufcount, readlen); # else len = read(read_cmd_fd, (char *)inbuf + inbufcount, readlen); # endif # ifdef FEAT_EVAL if (len > 0) { inbuf[inbufcount + len] = NUL; ch_log(NULL, "raw key input: \"%s\"", inbuf + inbufcount); } # endif if (len > 0 || got_int) break; /* * If reading stdin results in an error, continue reading stderr. * This helps when using "foo | xargs vim". */ if (!did_read_something && !isatty(read_cmd_fd) && read_cmd_fd == 0) { int m = cur_tmode; // We probably set the wrong file descriptor to raw mode. Switch // back to cooked mode, use another descriptor and set the mode to // what it was. settmode(TMODE_COOK); #ifdef HAVE_DUP // Use stderr for stdin, also works for shell commands. close(0); vim_ignored = dup(2); #else read_cmd_fd = 2; // read from stderr instead of stdin #endif settmode(m); } if (!exit_on_error) return; } if (len <= 0 && !got_int) read_error_exit(); if (len > 0) did_read_something = TRUE; if (got_int) { // Interrupted, pretend a CTRL-C was typed. inbuf[0] = 3; inbufcount = 1; } else { /* * May perform conversion on the input characters. * Include the unconverted rest of the previous call. * If there is an incomplete char at the end it is kept for the next * time, reading more bytes should make conversion possible. * Don't do this in the unlikely event that the input buffer is too * small ("rest" still contains more bytes). */ if (input_conv.vc_type != CONV_NONE) { inbufcount -= unconverted; len = convert_input_safe(inbuf + inbufcount, len + unconverted, INBUFLEN - inbufcount, rest == NULL ? &rest : NULL, &restlen); } while (len > 0) { // If a CTRL-C was typed, remove it from the buffer and set // got_int. Also recognize CTRL-C with modifyOtherKeys set, lower // and upper case, in two forms. if (ctrl_c_interrupts && (inbuf[inbufcount] == 3 || (len >= 10 && STRNCMP(inbuf + inbufcount, "\033[27;5;99~", 10) == 0) || (len >= 10 && STRNCMP(inbuf + inbufcount, "\033[27;5;67~", 10) == 0) || (len >= 7 && STRNCMP(inbuf + inbufcount, "\033[99;5u", 7) == 0) || (len >= 7 && STRNCMP(inbuf + inbufcount, "\033[67;5u", 7) == 0))) { // remove everything typed before the CTRL-C mch_memmove(inbuf, inbuf + inbufcount, (size_t)(len)); inbufcount = 0; got_int = TRUE; } --len; ++inbufcount; } } #endif // UNIX || VMS || MACOS_X } #endif // USE_INPUT_BUF /* * Exit because of an input read error. */ void read_error_exit(void) { if (silent_mode) // Normal way to exit for "ex -s" getout(0); STRCPY(IObuff, _("Vim: Error reading input, exiting...\n")); preserve_exit(); } #if defined(CURSOR_SHAPE) || defined(PROTO) /* * May update the shape of the cursor. */ void ui_cursor_shape_forced(int forced) { # ifdef FEAT_GUI if (gui.in_use) gui_update_cursor_later(); else # endif term_cursor_mode(forced); # ifdef MCH_CURSOR_SHAPE mch_update_cursor(); # endif # ifdef FEAT_CONCEAL conceal_check_cursor_line(FALSE); # endif } void ui_cursor_shape(void) { ui_cursor_shape_forced(FALSE); } #endif /* * Check bounds for column number */ int check_col(int col) { if (col < 0) return 0; if (col >= screen_Columns) return screen_Columns - 1; return col; } /* * Check bounds for row number */ int check_row(int row) { if (row < 0) return 0; if (row >= screen_Rows) return screen_Rows - 1; return row; } /* * Return length of line "lnum" in screen cells for horizontal scrolling. */ long scroll_line_len(linenr_T lnum) { char_u *p = ml_get(lnum); colnr_T col = 0; if (*p != NUL) for (;;) { int w = chartabsize(p, col); MB_PTR_ADV(p); if (*p == NUL) // don't count the last character break; col += w; } return col; } /* * Find the longest visible line number. This is used for horizontal * scrolling. If this is not possible (or not desired, by setting 'h' in * "guioptions") then the current line number is returned. */ linenr_T ui_find_longest_lnum(void) { linenr_T ret = 0; // Calculate maximum for horizontal scrollbar. Check for reasonable // line numbers, topline and botline can be invalid when displaying is // postponed. if ( # ifdef FEAT_GUI (!gui.in_use || vim_strchr(p_go, GO_HORSCROLL) == NULL) && # endif curwin->w_topline <= curwin->w_cursor.lnum && curwin->w_botline > curwin->w_cursor.lnum && curwin->w_botline <= curbuf->b_ml.ml_line_count + 1) { linenr_T lnum; long n; long max = 0; // Use maximum of all visible lines. Remember the lnum of the // longest line, closest to the cursor line. Used when scrolling // below. for (lnum = curwin->w_topline; lnum < curwin->w_botline; ++lnum) { n = scroll_line_len(lnum); if (n > max) { max = n; ret = lnum; } else if (n == max && abs((int)(lnum - curwin->w_cursor.lnum)) < abs((int)(ret - curwin->w_cursor.lnum))) ret = lnum; } } else // Use cursor line only. ret = curwin->w_cursor.lnum; return ret; } /* * Called when focus changed. Used for the GUI or for systems where this can * be done in the console (Win32). */ void ui_focus_change( int in_focus) // TRUE if focus gained. { static time_t last_time = (time_t)0; int need_redraw = FALSE; // When activated: Check if any file was modified outside of Vim. // Only do this when not done within the last two seconds (could get // several events in a row). if (in_focus && last_time + 2 < time(NULL)) { need_redraw = check_timestamps( # ifdef FEAT_GUI gui.in_use # else FALSE # endif ); last_time = time(NULL); } #ifdef FEAT_TERMINAL term_focus_change(in_focus); #endif /* * Fire the focus gained/lost autocommand. */ need_redraw |= apply_autocmds(in_focus ? EVENT_FOCUSGAINED : EVENT_FOCUSLOST, NULL, NULL, FALSE, curbuf); if (need_redraw) redraw_after_callback(TRUE, TRUE); // File may have been changed from 'readonly' to 'noreadonly' if (need_maketitle) maketitle(); } #if defined(HAVE_INPUT_METHOD) || defined(PROTO) /* * Save current Input Method status to specified place. */ void im_save_status(long *psave) { // Don't save when 'imdisable' is set or "xic" is NULL, IM is always // disabled then (but might start later). // Also don't save when inside a mapping, vgetc_im_active has not been set // then. // And don't save when the keys were stuffed (e.g., for a "." command). // And don't save when the GUI is running but our window doesn't have // input focus (e.g., when a find dialog is open). if (!p_imdisable && KeyTyped && !KeyStuffed # ifdef FEAT_XIM && xic != NULL # endif # ifdef FEAT_GUI && (!gui.in_use || gui.in_focus) # endif ) { // Do save when IM is on, or IM is off and saved status is on. if (vgetc_im_active) *psave = B_IMODE_IM; else if (*psave == B_IMODE_IM) *psave = B_IMODE_NONE; } } #endif