diff --git a/Filelist b/Filelist index d6a90e2c9c..7bf4fd6759 100644 --- a/Filelist +++ b/Filelist @@ -88,6 +88,7 @@ SRC_ALL = \ src/search.c \ src/sha256.c \ src/sign.c \ + src/sound.c \ src/spell.c \ src/spell.h \ src/spellfile.c \ @@ -150,6 +151,7 @@ SRC_ALL = \ src/testdir/samples/test000 \ src/testdir/if_ver*.vim \ src/testdir/color_ramp.vim \ + src/testdir/silent.wav \ src/proto.h \ src/protodef.h \ src/proto/arabic.pro \ @@ -209,6 +211,7 @@ SRC_ALL = \ src/proto/search.pro \ src/proto/sha256.pro \ src/proto/sign.pro \ + src/proto/sound.pro \ src/proto/spell.pro \ src/proto/spellfile.pro \ src/proto/syntax.pro \ diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index f4e4139ec2..f2de96d131 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2326,6 +2326,7 @@ extend({expr1}, {expr2} [, {expr3}]) exp({expr}) Float exponential of {expr} expand({expr} [, {nosuf} [, {list}]]) any expand special keywords in {expr} +expandcmd({expr}) String expand {expr} like with `:edit` feedkeys({string} [, {mode}]) Number add key sequence to typeahead buffer filereadable({file}) Number |TRUE| if {file} is a readable file filewritable({file}) Number |TRUE| if {file} is a writable file @@ -2622,6 +2623,12 @@ sin({expr}) Float sine of {expr} sinh({expr}) Float hyperbolic sine of {expr} sort({list} [, {func} [, {dict}]]) List sort {list}, using {func} to compare +sound_playevent({name} [, {callback}]) + Number play an event sound +sound_playfile({name} [, {callback}]) + Number play a sound file +sound_stop({id}) none stop playing sound {id} +sound_stopall() none stop playing all sounds soundfold({word}) String sound-fold {word} spellbadword() String badly spelled word at cursor spellsuggest({word} [, {max} [, {capital}]]) @@ -4212,6 +4219,14 @@ expand({expr} [, {nosuf} [, {list}]]) *expand()* See |glob()| for finding existing files. See |system()| for getting the raw output of an external command. +expandcmd({expr}) *expandcmd()* + Expand special items in {expr} like what is done for an Ex + command such as `:edit`. This expands special keywords, like + with |expand()|, and environment variables, anywhere in + {expr}. Returns the expanded string. + Example: > + :echo expandcmd('make %<.o') +< extend({expr1}, {expr2} [, {expr3}]) *extend()* {expr1} and {expr2} must be both |Lists| or both |Dictionaries|. @@ -8837,6 +8852,49 @@ sort({list} [, {func} [, {dict}]]) *sort()* *E702* return a:i1 - a:i2 endfunc < + *sound_playevent()* +sound_playevent({name} [, {callback}]) + Play a sound identified by {name}. Which event names are + supported depends on the system. Often the XDG sound names + are used. On Ubuntu they may be found in + /usr/share/sounds/freedesktop/stereo. Example: > + call sound_playevent('bell') + +< When {callback} is specified it is invoked when the sound is + finished. The first argument is the sound ID, the second + argument is the status: + 0 sound was played to the end + 1 sound was interruped + 2 error occured after sound started + Example: > + func Callback(id, status) + echomsg "sound " .. a:id .. " finished with " .. a:status + endfunc + call sound_playevent('bell', 'Callback') + +< Returns the sound ID, which can be passed to `sound_stop()`. + Returns zero if the sound could not be played. + {only available when compiled with the +sound feature} + + *sound_playfile()* +sound_playfile({name} [, {callback}]) + Like `sound_playevent()` but play sound file {name}. {name} + must be a full path. On Ubuntu you may find files to play + with this command: > + :!find /usr/share/sounds -type f | grep -v index.theme + +< {only available when compiled with the +sound feature} + + +sound_stop({id}) *sound_stop()* + Stop playing sound {id}. {id} must be previously returned by + `sound_playevent()` or `sound_playfile()`. + {only available when compiled with the +sound feature} + +sound_stopall() *sound_stopall()* + Stop playing all sounds. + {only available when compiled with the +sound feature} + *soundfold()* soundfold({word}) Return the sound-folded equivalent of {word}. Uses the first @@ -10759,6 +10817,7 @@ scrollbind Compiled with 'scrollbind' support. (always true) showcmd Compiled with 'showcmd' support. signs Compiled with |:sign| support. smartindent Compiled with 'smartindent' support. +sound Compiled with sound support, e.g. `sound_playevent()` spell Compiled with spell checking support |spell|. startuptime Compiled with |--startuptime| support. statusline Compiled with support for 'statusline', 'rulerformat' diff --git a/runtime/doc/tags b/runtime/doc/tags index a016433f95..10e53fba27 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -6220,6 +6220,7 @@ exp() eval.txt /*exp()* expand() eval.txt /*expand()* expand-env options.txt /*expand-env* expand-environment-var options.txt /*expand-environment-var* +expandcmd() eval.txt /*expandcmd()* expr eval.txt /*expr* expr-! eval.txt /*expr-!* expr-!= eval.txt /*expr-!=* @@ -8812,6 +8813,10 @@ slow-terminal term.txt /*slow-terminal* socket-interface channel.txt /*socket-interface* sort() eval.txt /*sort()* sorting change.txt /*sorting* +sound_playevent() eval.txt /*sound_playevent()* +sound_playfile() eval.txt /*sound_playfile()* +sound_stop() eval.txt /*sound_stop()* +sound_stopall() eval.txt /*sound_stopall()* soundfold() eval.txt /*soundfold()* space intro.txt /*space* spec-customizing pi_spec.txt /*spec-customizing* diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 719f015d79..ee3423cddf 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -1,4 +1,4 @@ -*usr_41.txt* For Vim version 8.1. Last change: 2019 May 29 +*usr_41.txt* For Vim version 8.1. Last change: 2019 Jun 09 VIM USER MANUAL - by Bram Moolenaar @@ -609,6 +609,7 @@ String manipulation: *string-functions* strcharpart() get part of a string using char index strgetchar() get character from a string using char index expand() expand special keywords + expandcmd() expand a command like done for `:edit` iconv() convert text from one encoding to another byteidx() byte index of a character in a string byteidxcomp() like byteidx() but count composing characters diff --git a/src/Makefile b/src/Makefile index f6f8b86db5..32810d0a71 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1645,6 +1645,7 @@ BASIC_SRC = \ search.c \ sha256.c \ sign.c \ + sound.c \ spell.c \ spellfile.c \ syntax.c \ @@ -1760,6 +1761,7 @@ OBJ_COMMON = \ objects/search.o \ objects/sha256.o \ objects/sign.o \ + objects/sound.o \ objects/spell.o \ objects/spellfile.o \ objects/syntax.o \ @@ -1900,6 +1902,7 @@ PRO_AUTO = \ search.pro \ sha256.pro \ sign.pro \ + sound.pro \ spell.pro \ spellfile.pro \ syntax.pro \ @@ -3261,6 +3264,9 @@ objects/sha256.o: sha256.c objects/sign.o: sign.c $(CCC) -o $@ sign.c +objects/sound.o: sound.c + $(CCC) -o $@ sound.c + objects/spell.o: spell.c $(CCC) -o $@ spell.c @@ -3707,6 +3713,10 @@ objects/sign.o: sign.c vim.h protodef.h auto/config.h feature.h os_unix.h \ auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ proto.h globals.h +objects/sound.o: spell.c vim.h protodef.h auto/config.h feature.h os_unix.h \ + auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ + proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ + proto.h globals.h objects/spell.o: spell.c vim.h protodef.h auto/config.h feature.h os_unix.h \ auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ diff --git a/src/auto/configure b/src/auto/configure index c5e1454add..9e6a82f4a7 100755 --- a/src/auto/configure +++ b/src/auto/configure @@ -9414,28 +9414,8 @@ fi - -if test -z "$SKIP_GTK2"; then - - { $as_echo "$as_me:${as_lineno-$LINENO}: checking --disable-gtktest argument" >&5 -$as_echo_n "checking --disable-gtktest argument... " >&6; } - # Check whether --enable-gtktest was given. -if test "${enable_gtktest+set}" = set; then : - enableval=$enable_gtktest; -else - enable_gtktest=yes -fi - - if test "x$enable_gtktest" = "xyes" ; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: gtk test enabled" >&5 -$as_echo "gtk test enabled" >&6; } - else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: gtk test disabled" >&5 -$as_echo "gtk test disabled" >&6; } - fi - - if test "X$PKG_CONFIG" = "X"; then - if test -n "$ac_tool_prefix"; then +if test "X$PKG_CONFIG" = "X"; then + if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args. set dummy ${ac_tool_prefix}pkg-config; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 @@ -9533,6 +9513,26 @@ else PKG_CONFIG="$ac_cv_path_PKG_CONFIG" fi +fi + + +if test -z "$SKIP_GTK2"; then + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking --disable-gtktest argument" >&5 +$as_echo_n "checking --disable-gtktest argument... " >&6; } + # Check whether --enable-gtktest was given. +if test "${enable_gtktest+set}" = set; then : + enableval=$enable_gtktest; +else + enable_gtktest=yes +fi + + if test "x$enable_gtktest" = "xyes" ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: gtk test enabled" >&5 +$as_echo "gtk test enabled" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: gtk test disabled" >&5 +$as_echo "gtk test disabled" >&6; } fi if test "x$PKG_CONFIG" != "xno"; then @@ -9788,107 +9788,6 @@ $as_echo "gtk test enabled" >&6; } $as_echo "gtk test disabled" >&6; } fi - if test "X$PKG_CONFIG" = "X"; then - if test -n "$ac_tool_prefix"; then - # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args. -set dummy ${ac_tool_prefix}pkg-config; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_path_PKG_CONFIG+:} false; then : - $as_echo_n "(cached) " >&6 -else - case $PKG_CONFIG in - [\\/]* | ?:[\\/]*) - ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. - ;; - *) - as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then - ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - - ;; -esac -fi -PKG_CONFIG=$ac_cv_path_PKG_CONFIG -if test -n "$PKG_CONFIG"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5 -$as_echo "$PKG_CONFIG" >&6; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -fi - - -fi -if test -z "$ac_cv_path_PKG_CONFIG"; then - ac_pt_PKG_CONFIG=$PKG_CONFIG - # Extract the first word of "pkg-config", so it can be a program name with args. -set dummy pkg-config; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_path_ac_pt_PKG_CONFIG+:} false; then : - $as_echo_n "(cached) " >&6 -else - case $ac_pt_PKG_CONFIG in - [\\/]* | ?:[\\/]*) - ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path. - ;; - *) - as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then - ac_cv_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - - ;; -esac -fi -ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG -if test -n "$ac_pt_PKG_CONFIG"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5 -$as_echo "$ac_pt_PKG_CONFIG" >&6; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -fi - - if test "x$ac_pt_PKG_CONFIG" = x; then - PKG_CONFIG="no" - else - case $cross_compiling:$ac_tool_warned in -yes:) -{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 -$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} -ac_tool_warned=yes ;; -esac - PKG_CONFIG=$ac_pt_PKG_CONFIG - fi -else - PKG_CONFIG="$ac_cv_path_PKG_CONFIG" -fi - - fi - if test "x$PKG_CONFIG" != "xno"; then if test "X$GTK_CONFIG" != "Xno" -o "X$PKG_CONFIG" != "Xno"; then @@ -13137,6 +13036,56 @@ rm -rf conftest* fi + +if test "x$PKG_CONFIG" != "xno"; then + canberra_lib=`$PKG_CONFIG --libs libcanberrax 2>/dev/null` + canberra_cflags=`$PKG_CONFIG --cflags libcanberrax 2>/dev/null` + { $as_echo "$as_me:${as_lineno-$LINENO}: result: canberra_lib: $canberra_lib" >&5 +$as_echo "canberra_lib: $canberra_lib" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: canberra_cflags: $canberra_cflags" >&5 +$as_echo "canberra_cflags: $canberra_cflags" >&6; } +fi +if test "x$canberra_lib" = "x"; then + canberra_lib=-lcanberra + canberra_cflags=-D_REENTRANT +fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: canberra_lib: $canberra_lib" >&5 +$as_echo "canberra_lib: $canberra_lib" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: canberra_cflags: $canberra_cflags" >&5 +$as_echo "canberra_cflags: $canberra_cflags" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for libcanberra" >&5 +$as_echo_n "checking for libcanberra... " >&6; } +ac_save_CFLAGS="$CFLAGS" +ac_save_LIBS="$LIBS" +CFLAGS="$CFLAGS $canberra_cflags" +LIBS="$LIBS $canberra_lib" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +# include + +int +main () +{ + + ca_context *hello; + ca_context_create(&hello); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; }; $as_echo "#define HAVE_CANBERRA 1" >>confdefs.h + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; }; CFLAGS="$ac_save_CFLAGS"; LIBS="$ac_save_LIBS" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for st_blksize" >&5 $as_echo_n "checking for st_blksize... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext diff --git a/src/channel.c b/src/channel.c index d53a01bfef..527916b776 100644 --- a/src/channel.c +++ b/src/channel.c @@ -2179,11 +2179,67 @@ remove_json_node(jsonq_T *head, jsonq_T *node) vim_free(node); } +/* + * Add "id" to the list of JSON message IDs we are waiting on. + */ + static void +channel_add_block_id(chanpart_T *chanpart, int id) +{ + garray_T *gap = &chanpart->ch_block_ids; + + if (gap->ga_growsize == 0) + ga_init2(gap, (int)sizeof(int), 10); + if (ga_grow(gap, 1) == OK) + { + ((int *)gap->ga_data)[gap->ga_len] = id; + ++gap->ga_len; + } +} + +/* + * Remove "id" from the list of JSON message IDs we are waiting on. + */ + static void +channel_remove_block_id(chanpart_T *chanpart, int id) +{ + garray_T *gap = &chanpart->ch_block_ids; + int i; + + for (i = 0; i < gap->ga_len; ++i) + if (((int *)gap->ga_data)[i] == id) + { + --gap->ga_len; + if (i < gap->ga_len) + { + int *p = ((int *)gap->ga_data) + i; + + mch_memmove(p, p + 1, (gap->ga_len - i) * sizeof(int)); + } + return; + } + siemsg("INTERNAL: channel_remove_block_id: cannot find id %d", id); +} + +/* + * Return TRUE if "id" is in the list of JSON message IDs we are waiting on. + */ + static int +channel_has_block_id(chanpart_T *chanpart, int id) +{ + garray_T *gap = &chanpart->ch_block_ids; + int i; + + for (i = 0; i < gap->ga_len; ++i) + if (((int *)gap->ga_data)[i] == id) + return TRUE; + return FALSE; +} + /* * Get a message from the JSON queue for channel "channel". * When "id" is positive it must match the first number in the list. - * When "id" is zero or negative jut get the first message. But not the one - * with id ch_block_id. + * When "id" is zero or negative jut get the first message. But not one + * in the ch_block_ids list. * When "without_callback" is TRUE also get messages that were pushed back. * Return OK when found and return the value in "rettv". * Return FAIL otherwise. @@ -2208,7 +2264,8 @@ channel_get_json( && ((id > 0 && tv->v_type == VAR_NUMBER && tv->vval.v_number == id) || (id <= 0 && (tv->v_type != VAR_NUMBER || tv->vval.v_number == 0 - || tv->vval.v_number != channel->ch_part[part].ch_block_id)))) + || !channel_has_block_id( + &channel->ch_part[part], tv->vval.v_number))))) { *rettv = item->jq_value; if (tv->v_type == VAR_NUMBER) @@ -3076,6 +3133,7 @@ channel_clear_one(channel_T *channel, ch_part_T part) } free_callback(&ch_part->ch_callback); + ga_clear(&ch_part->ch_block_ids); while (ch_part->ch_writeque.wq_next != NULL) remove_from_writeque(&ch_part->ch_writeque, @@ -3506,6 +3564,8 @@ channel_read_block( * result in "rettv". * When "id" is -1 accept any message; * Blocks until the message is received or the timeout is reached. + * In corner cases this can be called recursively, that is why ch_block_ids is + * a list. */ static int channel_read_json_block( @@ -3520,17 +3580,19 @@ channel_read_json_block( int timeout; chanpart_T *chanpart = &channel->ch_part[part]; - ch_log(channel, "Reading JSON"); - if (id != -1) - chanpart->ch_block_id = id; + ch_log(channel, "Blocking read JSON for id %d", id); + if (id >= 0) + channel_add_block_id(chanpart, id); for (;;) { more = channel_parse_json(channel, part); - /* search for message "id" */ + // search for message "id" if (channel_get_json(channel, part, id, TRUE, rettv) == OK) { - chanpart->ch_block_id = 0; + if (id >= 0) + channel_remove_block_id(chanpart, id); + ch_log(channel, "Received JSON for id %d", id); return OK; } @@ -3577,7 +3639,7 @@ channel_read_json_block( if (timeout == timeout_arg) { if (fd != INVALID_FD) - ch_log(channel, "Timed out"); + ch_log(channel, "Timed out on id %d", id); break; } } @@ -3585,7 +3647,8 @@ channel_read_json_block( channel_read(channel, part, "channel_read_json_block"); } } - chanpart->ch_block_id = 0; + if (id >= 0) + channel_remove_block_id(chanpart, id); return FAIL; } diff --git a/src/config.h.in b/src/config.h.in index 23e301c90e..c1ced6fe9c 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -207,6 +207,7 @@ #undef HAVE_STRNICMP #undef HAVE_STRPBRK #undef HAVE_STRTOL +#undef HAVE_CANBERRA #undef HAVE_ST_BLKSIZE #undef HAVE_SYSCONF #undef HAVE_SYSCTL diff --git a/src/configure.ac b/src/configure.ac index 8cc0ff8a6f..616687bfbe 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -2787,6 +2787,10 @@ AC_DEFUN([GNOME_INIT],[ GNOME_INIT_HOOK([],fail) ]) +if test "X$PKG_CONFIG" = "X"; then + AC_PATH_TOOL(PKG_CONFIG, pkg-config, no) +fi + dnl --------------------------------------------------------------------------- dnl Check for GTK2. If it fails, then continue on for Motif as before... @@ -2802,10 +2806,6 @@ if test -z "$SKIP_GTK2"; then AC_MSG_RESULT(gtk test disabled) fi - if test "X$PKG_CONFIG" = "X"; then - AC_PATH_TOOL(PKG_CONFIG, pkg-config, no) - fi - if test "x$PKG_CONFIG" != "xno"; then dnl First try finding version 2.2.0 or later. The 2.0.x series has dnl problems (bold fonts, --remote doesn't work). @@ -2854,10 +2854,6 @@ if test -z "$SKIP_GTK3"; then AC_MSG_RESULT(gtk test disabled) fi - if test "X$PKG_CONFIG" = "X"; then - AC_PATH_TOOL(PKG_CONFIG, pkg-config, no) - fi - if test "x$PKG_CONFIG" != "xno"; then AM_PATH_GTK(3.0.0, [GUI_LIB_LOC="$GTK_LIBDIR" @@ -3840,6 +3836,29 @@ dnl define _LARGE_FILES, _FILE_OFFSET_BITS and _LARGEFILE_SOURCE when dnl appropriate, so that off_t is 64 bits when needed. AC_SYS_LARGEFILE + +if test "x$PKG_CONFIG" != "xno"; then + canberra_lib=`$PKG_CONFIG --libs libcanberra 2>/dev/null` + canberra_cflags=`$PKG_CONFIG --cflags libcanberra 2>/dev/null` +fi +if test "x$canberra_lib" = "x"; then + canberra_lib=-lcanberra + canberra_cflags=-D_REENTRANT +fi +AC_MSG_CHECKING(for libcanberra) +ac_save_CFLAGS="$CFLAGS" +ac_save_LIBS="$LIBS" +CFLAGS="$CFLAGS $canberra_cflags" +LIBS="$LIBS $canberra_lib" +AC_TRY_LINK([ +# include + ], [ + ca_context *hello; + ca_context_create(&hello);], + AC_MSG_RESULT(yes); AC_DEFINE(HAVE_CANBERRA), + AC_MSG_RESULT(no); CFLAGS="$ac_save_CFLAGS"; LIBS="$ac_save_LIBS") + + dnl fstatfs() can take 2 to 4 arguments, try to use st_blksize if possible AC_MSG_CHECKING(for st_blksize) AC_TRY_COMPILE( diff --git a/src/evalfunc.c b/src/evalfunc.c index c327c55dcb..2f3410dcf5 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -149,6 +149,7 @@ static void f_exists(typval_T *argvars, typval_T *rettv); static void f_exp(typval_T *argvars, typval_T *rettv); #endif static void f_expand(typval_T *argvars, typval_T *rettv); +static void f_expandcmd(typval_T *argvars, typval_T *rettv); static void f_extend(typval_T *argvars, typval_T *rettv); static void f_feedkeys(typval_T *argvars, typval_T *rettv); static void f_filereadable(typval_T *argvars, typval_T *rettv); @@ -646,6 +647,7 @@ static struct fst {"exp", 1, 1, f_exp}, #endif {"expand", 1, 3, f_expand}, + {"expandcmd", 1, 1, f_expandcmd}, {"extend", 2, 3, f_extend}, {"feedkeys", 1, 2, f_feedkeys}, {"file_readable", 1, 1, f_filereadable}, /* obsolete */ @@ -925,6 +927,12 @@ static struct fst {"sinh", 1, 1, f_sinh}, #endif {"sort", 1, 3, f_sort}, +#ifdef FEAT_SOUND + {"sound_playevent", 1, 2, f_sound_playevent}, + {"sound_playfile", 1, 2, f_sound_playfile}, + {"sound_stop", 1, 1, f_sound_stop}, + {"sound_stopall", 0, 0, f_sound_stopall}, +#endif {"soundfold", 1, 1, f_soundfold}, {"spellbadword", 0, 1, f_spellbadword}, {"spellsuggest", 1, 3, f_spellsuggest}, @@ -3784,6 +3792,35 @@ f_expand(typval_T *argvars, typval_T *rettv) } } +/* + * "expandcmd()" function + * Expand all the special characters in a command string. + */ + static void +f_expandcmd(typval_T *argvars, typval_T *rettv) +{ + exarg_T eap; + char_u *cmdstr; + char *errormsg = NULL; + + rettv->v_type = VAR_STRING; + cmdstr = vim_strsave(tv_get_string(&argvars[0])); + + memset(&eap, 0, sizeof(eap)); + eap.cmd = cmdstr; + eap.arg = cmdstr; + eap.argt |= NOSPC; + eap.usefilter = FALSE; + eap.nextcmd = NULL; + eap.cmdidx = CMD_USER; + + expand_filename(&eap, &cmdstr, &errormsg); + if (errormsg != NULL && *errormsg != NUL) + emsg(errormsg); + + rettv->vval.v_string = cmdstr; +} + /* * "extend(list, list [, idx])" function * "extend(dict, dict [, action])" function @@ -6799,6 +6836,9 @@ f_has(typval_T *argvars, typval_T *rettv) #ifdef FEAT_ODB_EDITOR "odbeditor", #endif +#ifdef FEAT_SOUND + "sound", +#endif #ifdef FEAT_SPELL "spell", #endif diff --git a/src/feature.h b/src/feature.h index e7459bee7e..f86271db5a 100644 --- a/src/feature.h +++ b/src/feature.h @@ -663,6 +663,13 @@ # define FEAT_TERM_POPUP_MENU #endif +/* + * sound - currently only with libcanberra + */ +#if !defined(FEAT_SOUND) && defined(FEAT_BIG) && defined(HAVE_CANBERRA) +# define FEAT_SOUND +#endif + /* There are two ways to use XPM. */ #if (defined(HAVE_XM_XPMP_H) && defined(FEAT_GUI_MOTIF)) \ || defined(HAVE_X11_XPM_H) diff --git a/src/misc1.c b/src/misc1.c index 8efd66819d..a86891db39 100644 --- a/src/misc1.c +++ b/src/misc1.c @@ -1454,9 +1454,9 @@ prompt_for_number(int *mouse_used) i = get_number(TRUE, mouse_used); if (KeyTyped) { - /* don't call wait_return() now */ - /* msg_putchar('\n'); */ - cmdline_row = msg_row - 1; + // don't call wait_return() now + if (msg_row > 0) + cmdline_row = msg_row - 1; need_wait_return = FALSE; msg_didany = FALSE; msg_didout = FALSE; diff --git a/src/proto.h b/src/proto.h index 82b851c3cb..303b047c30 100644 --- a/src/proto.h +++ b/src/proto.h @@ -183,6 +183,7 @@ void qsort(void *base, size_t elm_count, size_t elm_size, int (*cmp)(const void # ifdef FEAT_SIGNS # include "sign.pro" # endif +# include "sound.pro" # include "spell.pro" # include "spellfile.pro" # include "syntax.pro" diff --git a/src/proto/sound.pro b/src/proto/sound.pro new file mode 100644 index 0000000000..43e4727750 --- /dev/null +++ b/src/proto/sound.pro @@ -0,0 +1,7 @@ +/* sound.c */ +void f_sound_playevent(typval_T *argvars, typval_T *rettv); +void f_sound_playfile(typval_T *argvars, typval_T *rettv); +void f_sound_stop(typval_T *argvars, typval_T *rettv); +void f_sound_stopall(typval_T *argvars, typval_T *rettv); +void sound_free(void); +/* vim: set ft=c : */ diff --git a/src/screen.c b/src/screen.c index 477557b7f7..686487666b 100644 --- a/src/screen.c +++ b/src/screen.c @@ -3961,12 +3961,11 @@ win_line( /* * Handle highlighting the last used search pattern and matches. * Do this for both search_hl and the match list. - * Not in a popup window. + * Do not use search_hl in a popup window. */ cur = wp->w_match_head; - shl_flag = FALSE; - while ((cur != NULL || shl_flag == FALSE) && !number_only - && !(screen_line_flags & SLF_POPUP)) + shl_flag = (screen_line_flags & SLF_POPUP); + while ((cur != NULL || shl_flag == FALSE) && !number_only) { if (shl_flag == FALSE) { @@ -4442,7 +4441,7 @@ win_line( */ v = (long)(ptr - line); cur = wp->w_match_head; - shl_flag = FALSE; + shl_flag = (screen_line_flags & SLF_POPUP); while (cur != NULL || shl_flag == FALSE) { if (shl_flag == FALSE diff --git a/src/sound.c b/src/sound.c new file mode 100644 index 0000000000..ac6c377be8 --- /dev/null +++ b/src/sound.c @@ -0,0 +1,193 @@ +/* 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. + */ + +/* + * sound.c: functions related making noise + */ + +#include "vim.h" + +#if (defined(FEAT_SOUND) && defined(HAVE_CANBERRA)) || defined(PROTO) + +#include + +static long sound_id = 0; +static ca_context *context = NULL; + +typedef struct soundcb_S soundcb_T; + +struct soundcb_S { + callback_T snd_callback; + soundcb_T *snd_next; +}; + +static soundcb_T *first_callback = NULL; + + static soundcb_T * +get_sound_callback(typval_T *arg) +{ + callback_T callback; + soundcb_T *soundcb; + + if (arg->v_type == VAR_UNKNOWN) + return NULL; + callback = get_callback(arg); + if (callback.cb_name == NULL) + return NULL; + + soundcb = ALLOC_ONE(soundcb_T); + if (soundcb == NULL) + free_callback(&callback); + else + { + soundcb->snd_next = first_callback; + first_callback = soundcb; + set_callback(&soundcb->snd_callback, &callback); + } + return soundcb; +} + +/* + * Delete "soundcb" from the list of pending callbacks. + */ + static void +delete_sound_callback(soundcb_T *soundcb) +{ + soundcb_T *p; + soundcb_T *prev = NULL; + + for (p = first_callback; p != NULL; prev = p, p = p->snd_next) + if (p == soundcb) + { + if (prev == NULL) + first_callback = p->snd_next; + else + prev->snd_next = p->snd_next; + free_callback(&p->snd_callback); + vim_free(p); + break; + } +} + + static void +sound_callback( + ca_context *c UNUSED, + uint32_t id, + int error_code, + void *userdata) +{ + soundcb_T *soundcb = (soundcb_T *)userdata; + typval_T argv[3]; + typval_T rettv; + int dummy; + + argv[0].v_type = VAR_NUMBER; + argv[0].vval.v_number = id; + argv[1].v_type = VAR_NUMBER; + argv[1].vval.v_number = error_code == CA_SUCCESS ? 0 + : error_code == CA_ERROR_CANCELED + || error_code == CA_ERROR_DESTROYED + ? 1 : 2; + argv[2].v_type = VAR_UNKNOWN; + + call_callback(&soundcb->snd_callback, -1, + &rettv, 2, argv, NULL, 0L, 0L, &dummy, TRUE, NULL); + clear_tv(&rettv); + + delete_sound_callback(soundcb); + redraw_after_callback(TRUE); +} + + static void +sound_play_common(typval_T *argvars, typval_T *rettv, int playfile) +{ + if (context == NULL) + ca_context_create(&context); + if (context != NULL) + { + soundcb_T *soundcb = get_sound_callback(&argvars[1]); + int res = CA_ERROR_INVALID; + + ++sound_id; + if (soundcb == NULL) + { + res = ca_context_play(context, sound_id, + playfile ? CA_PROP_MEDIA_FILENAME : CA_PROP_EVENT_ID, + tv_get_string(&argvars[0]), + CA_PROP_CANBERRA_CACHE_CONTROL, "volatile", + NULL); + } + else + { + static ca_proplist *proplist = NULL; + + ca_proplist_create(&proplist); + if (proplist != NULL) + { + if (playfile) + ca_proplist_sets(proplist, CA_PROP_MEDIA_FILENAME, + (char *)tv_get_string(&argvars[0])); + else + ca_proplist_sets(proplist, CA_PROP_EVENT_ID, + (char *)tv_get_string(&argvars[0])); + ca_proplist_sets(proplist, CA_PROP_CANBERRA_CACHE_CONTROL, + "volatile"); + res = ca_context_play_full(context, sound_id, proplist, + sound_callback, soundcb); + if (res != CA_SUCCESS) + delete_sound_callback(soundcb); + + ca_proplist_destroy(proplist); + } + } + rettv->vval.v_number = res == CA_SUCCESS ? sound_id : 0; + } +} + + void +f_sound_playevent(typval_T *argvars, typval_T *rettv) +{ + sound_play_common(argvars, rettv, FALSE); +} + + void +f_sound_playfile(typval_T *argvars, typval_T *rettv) +{ + sound_play_common(argvars, rettv, TRUE); +} + + void +f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED) +{ + if (context != NULL) + ca_context_cancel(context, tv_get_number(&argvars[0])); +} + + void +f_sound_stopall(typval_T *argvars UNUSED, typval_T *rettv UNUSED) +{ + if (context != NULL) + { + ca_context_destroy(context); + context = NULL; + } +} + +#if defined(EXITFREE) || defined(PROTO) + void +sound_free(void) +{ + if (context != NULL) + ca_context_destroy(context); + while (first_callback != NULL) + delete_sound_callback(first_callback); +} +#endif + +#endif // FEAT_SOUND && HAVE_CANBERRA diff --git a/src/structs.h b/src/structs.h index cb25b6a73c..759a6188cf 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1683,8 +1683,8 @@ typedef struct { readq_T ch_head; /* header for circular raw read queue */ jsonq_T ch_json_head; /* header for circular json read queue */ - int ch_block_id; /* ID that channel_read_json_block() is - waiting for */ + garray_T ch_block_ids; /* list of IDs that channel_read_json_block() + is waiting for */ /* When ch_wait_len is non-zero use ch_deadline to wait for incomplete * message to be complete. The value is the length of the incomplete * message when the deadline was set. If it gets longer (something was diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak index 34644d6b95..08f6c5bca6 100644 --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -228,6 +228,7 @@ NEW_TESTS = \ test_signs \ test_smartindent \ test_sort \ + test_sound \ test_source \ test_source_utf8 \ test_spell \ @@ -399,6 +400,7 @@ NEW_TESTS_RES = \ test_signals.res \ test_signs.res \ test_smartindent.res \ + test_sound.res \ test_source.res \ test_spell.res \ test_startup.res \ diff --git a/src/testdir/Makefile b/src/testdir/Makefile index bf64a03837..d3c1184e8c 100644 --- a/src/testdir/Makefile +++ b/src/testdir/Makefile @@ -85,9 +85,9 @@ RUN_VIM = VIMRUNTIME=$(SCRIPTSOURCE); export VIMRUNTIME; $(VALGRIND) $(VIMPROG) clean: -rm -rf *.out *.failed *.res *.rej *.orig - -rm opt_test.vim test.log test_result.log messages - -rm $(RM_ON_RUN) $(RM_ON_START) - -rm valgrind.* + -rm -f opt_test.vim test.log test_result.log messages + -rm -f $(RM_ON_RUN) $(RM_ON_START) + -rm -f valgrind.* test1.out: test1.in -rm -rf $*.failed $(RM_ON_RUN) $(RM_ON_START) wrongtermsize diff --git a/src/testdir/dumps/Test_popupwin_matches.dump b/src/testdir/dumps/Test_popupwin_matches.dump new file mode 100644 index 0000000000..ba3e720254 --- /dev/null +++ b/src/testdir/dumps/Test_popupwin_matches.dump @@ -0,0 +1,10 @@ +|1+0#ffffff16#e000002@2| +0#0000000#ffffff0|2@2| |3@2| @63 +>4+0#ffffff16#e000002@2| +0#0000000#ffffff0|5@2| |6+0&#ffff4012@2| +0&#ffffff0@63 +|~+0#4040ff13&| @7|╔+0#0000001#ffd7ff255|═@10|╗| +0#4040ff13#ffffff0@52 +|~| @7|║+0#0000001#ffd7ff255|1+0#ffffff16#e000002@2| +0#0000001#ffd7ff255|2@2| |3@2|║| +0#4040ff13#ffffff0@52 +|~| @7|║+0#0000001#ffd7ff255|4@2| |5+0#ffffff16#e000002@2| +0#0000001#ffd7ff255|6@2|║| +0#4040ff13#ffffff0@52 +|~| @7|╚+0#0000001#ffd7ff255|═@10|╝| +0#4040ff13#ffffff0@52 +|~| @73 +|~| @73 +|~| @73 +| +0#0000000&@56|2|,|1| @10|A|l@1| diff --git a/src/testdir/silent.wav b/src/testdir/silent.wav new file mode 100644 index 0000000000..4631a7e8ed Binary files /dev/null and b/src/testdir/silent.wav differ diff --git a/src/testdir/test_expand.vim b/src/testdir/test_expand.vim index b4f1363d12..bd8021f9fa 100644 --- a/src/testdir/test_expand.vim +++ b/src/testdir/test_expand.vim @@ -47,3 +47,37 @@ func Test_expand_tilde_filename() call assert_match('\~', expand('%:p')) bwipe! endfunc + +func Test_expandcmd() + let $FOO = 'Test' + call assert_equal('e x/Test/y', expandcmd('e x/$FOO/y')) + unlet $FOO + + new + edit Xfile1 + call assert_equal('e Xfile1', expandcmd('e %')) + edit Xfile2 + edit Xfile1 + call assert_equal('e Xfile2', expandcmd('e #')) + edit Xfile2 + edit Xfile3 + edit Xfile4 + let bnum = bufnr('Xfile2') + call assert_equal('e Xfile2', expandcmd('e #' . bnum)) + call setline('.', 'Vim!@#') + call assert_equal('e Vim', expandcmd('e ')) + call assert_equal('e Vim!@#', expandcmd('e ')) + enew! + edit Xfile.java + call assert_equal('e Xfile.py', expandcmd('e %:r.py')) + call assert_equal('make abc.java', expandcmd('make abc.%:e')) + call assert_equal('make Xabc.java', expandcmd('make %:s?file?abc?')) + edit a1a2a3.rb + call assert_equal('make b1b2b3.rb a1a2a3 Xfile.o', expandcmd('make %:gs?a?b? %< #<.o')) + + call assert_fails('call expandcmd("make ")', 'E495:') + call assert_fails('call expandcmd("make ")', 'E495:') + enew + call assert_fails('call expandcmd("make %")', 'E499:') + close +endfunc diff --git a/src/testdir/test_popupwin.vim b/src/testdir/test_popupwin.vim index 5983dfb47d..f275a4937a 100644 --- a/src/testdir/test_popupwin.vim +++ b/src/testdir/test_popupwin.vim @@ -188,6 +188,32 @@ func Test_popup_with_syntax_setbufvar() call delete('XtestPopup') endfunc +func Test_popup_with_matches() + if !CanRunVimInTerminal() + throw 'Skipped: cannot make screendumps' + endif + let lines =<< trim END + call setline(1, ['111 222 333', '444 555 666']) + let winid = popup_create([ + \ '111 222 333', + \ '444 555 666', + \], {'line': 3, 'col': 10, 'border': []}) + set hlsearch + /666 + call matchadd('ErrorMsg', '111') + call matchadd('ErrorMsg', '444') + call win_execute(winid, "call matchadd('ErrorMsg', '111')") + call win_execute(winid, "call matchadd('ErrorMsg', '555')") + END + call writefile(lines, 'XtestPopupMatches') + let buf = RunVimInTerminal('-S XtestPopupMatches', {'rows': 10}) + call VerifyScreenDump(buf, 'Test_popupwin_matches', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XtestPopupMatches') +endfunc + func Test_popup_all_corners() if !CanRunVimInTerminal() throw 'Skipped: cannot make screendumps' diff --git a/src/testdir/test_sound.vim b/src/testdir/test_sound.vim new file mode 100644 index 0000000000..e47c677e99 --- /dev/null +++ b/src/testdir/test_sound.vim @@ -0,0 +1,47 @@ +" Tests for the sound feature + +if !has('sound') + throw 'Skipped: sound feature not available' +endif + +func PlayCallback(id, result) + let g:id = a:id + let g:result = a:result +endfunc + +func Test_play_event() + let id = sound_playevent('bell', 'PlayCallback') + if id == 0 + throw 'Skipped: bell event not available' + endif + " Stop it quickly, avoid annoying the user. + sleep 20m + call sound_stop(id) + sleep 20m + call assert_equal(id, g:id) + call assert_equal(1, g:result) " sound was aborted +endfunc + +func Test_play_silent() + let fname = fnamemodify('silent.wav', '%p') + + " play without callback + let id1 = sound_playfile(fname) + if id1 == 0 + throw 'Skipped: playing a sound is not working' + endif + + " play until the end + let id2 = sound_playfile(fname, 'PlayCallback') + call assert_true(id2 > 0) + sleep 500m + call assert_equal(id2, g:id) + call assert_equal(0, g:result) + + let id2 = sound_playfile(fname, 'PlayCallback') + call assert_true(id2 > 0) + sleep 20m + call sound_stopall() + call assert_equal(id2, g:id) + call assert_equal(1, g:result) +endfunc diff --git a/src/version.c b/src/version.c index fbd0c546ca..9a23055972 100644 --- a/src/version.c +++ b/src/version.c @@ -590,6 +590,16 @@ static char *(features[]) = #else "-smartindent", #endif +#ifdef FEAT_SOUND + "+sound", +#else + "-sound", +#endif +#ifdef FEAT_SPELL + "+spell", +#else + "-spell", +#endif #ifdef STARTUPTIME "+startuptime", #else @@ -782,6 +792,28 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1512, +/**/ + 1511, +/**/ + 1510, +/**/ + 1509, +/**/ + 1508, +/**/ + 1507, +/**/ + 1506, +/**/ + 1505, +/**/ + 1504, +/**/ + 1503, +/**/ + 1502, /**/ 1501, /**/