]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - bashline.c
fix quoting for positional parameters if not word splitting; retry open for startup...
[thirdparty/bash.git] / bashline.c
index 75a5ec69140e919b1f70a97151ff3c1fa172b08b..353e8b1e68aab2578cbcb2db8e003e14a8aa6dd3 100644 (file)
@@ -1,6 +1,6 @@
 /* bashline.c -- Bash's interface to the readline library. */
 
-/* Copyright (C) 1987-2023 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2024 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -593,13 +593,7 @@ initialize_readline (void)
   /* Tell the completer that we want a crack first. */
   rl_attempted_completion_function = attempt_shell_completion;
 
-  /* Tell the completer that we might want to follow symbolic links or
-     do other expansion on directory names. */
-  set_directory_hook ();
-
-  rl_filename_rewrite_hook = bash_filename_rewrite_hook;
-
-  rl_filename_stat_hook = bash_filename_stat_hook;
+  bashline_set_filename_hooks ();
 
   /* Tell the filename completer we want a chance to ignore some names. */
   rl_ignore_some_completions_function = filename_completion_ignore;
@@ -612,6 +606,7 @@ initialize_readline (void)
   rl_bind_key_if_unbound_in_map ('@', posix_edit_macros, vi_movement_keymap);
 #  endif
 
+  rl_add_defun ("bash-vi-complete", bash_vi_complete, -1);
   rl_bind_key_in_map ('\\', bash_vi_complete, vi_movement_keymap);
   rl_bind_key_in_map ('*', bash_vi_complete, vi_movement_keymap);
   rl_bind_key_in_map ('=', bash_vi_complete, vi_movement_keymap);
@@ -686,8 +681,9 @@ bashline_reset (void)
   rl_filename_quote_characters = default_filename_quote_characters;
   set_filename_bstab (rl_filename_quote_characters);
 
-  set_directory_hook ();
-  rl_filename_stat_hook = bash_filename_stat_hook;
+  rl_filename_quoting_function = bash_quote_filename;
+
+  bashline_set_filename_hooks ();
 
   bashline_reset_event_hook ();
 
@@ -802,7 +798,11 @@ snarf_hosts_from_file (const char *filename)
   char *temp, buffer[256], name[256];
   register int i, start;
 
+#ifdef __MSYS__
+  file = fopen (filename, "rt");
+#else
   file = fopen (filename, "r");
+#endif
   if (file == 0)
     return;
 
@@ -917,7 +917,7 @@ hostnames_matching (const char *text)
        continue;
 
       /* OK, it matches.  Add it to the list. */
-      if (nmatch >= (rsize - 1))
+      if (nmatch + 1 >= rsize)
        {
          rsize = (rsize + 16) - (rsize % 16);
          result = strvec_resize (result, rsize);
@@ -953,8 +953,11 @@ edit_and_execute_command (int count, int c, int editing_mode, const char *edit_c
 
   if (rl_explicit_arg)
     {
-      command = (char *)xmalloc (strlen (edit_command) + 8);
-      sprintf (command, "%s %d", edit_command, count);
+      size_t clen;
+      /* 32 exceeds strlen (itos (INTMAX_MAX)) (19) */
+      clen = strlen (edit_command) + 32;
+      command = (char *)xmalloc (clen);
+      snprintf (command, clen, "%s %d", edit_command, count);
     }
   else
     {
@@ -1344,6 +1347,8 @@ bash_spell_correct_shellword (int count, int key)
        break;
 
       text = rl_copy_text (wbeg, wend);
+      if (text == 0 || *text == 0)
+       break;
 
       newdir = dirspell (text);
       if (newdir)
@@ -1388,17 +1393,18 @@ bash_spell_correct_shellword (int count, int key)
 static inline int
 check_redir (int ti)
 {
-  register int this_char, prev_char;
+  int this_char, prev_char, next_char;
 
   /* Handle the two character tokens `>&', `<&', and `>|'.
      We are not in a command position after one of these. */
   this_char = rl_line_buffer[ti];
   prev_char = (ti > 0) ? rl_line_buffer[ti - 1] : 0;
+  next_char = (ti < rl_end) ? rl_line_buffer[ti + 1] : 0;
 
   if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) ||
       (this_char == '|' && prev_char == '>'))
     return (1);
-  else if (this_char == '{' && prev_char == '$') /*}*/
+  else if (this_char == '{' && prev_char == '$' && FUNSUB_CHAR (next_char) == 0) /*}*/
     return (1);
 #if 0  /* Not yet */
   else if (this_char == '(' && prev_char == '$') /*)*/
@@ -1560,8 +1566,7 @@ attempt_shell_completion (const char *text, int start, int end)
   complete_fullquote = 1;              /* full filename quoting by default */
   rl_filename_quote_characters = default_filename_quote_characters;
   set_filename_bstab (rl_filename_quote_characters);
-  set_directory_hook ();
-  rl_filename_stat_hook = bash_filename_stat_hook;
+  bashline_set_filename_hooks ();
 
   rl_sort_completion_matches = 1;      /* sort by default */
 
@@ -1668,6 +1673,10 @@ attempt_shell_completion (const char *text, int start, int end)
              s1 = s = e1;
              break;
            }
+         else if (s > e)
+           {
+             s = s1 = start; e = e1 = end;     /* reset */
+           }
          /* Skip over assignment statements preceding a command name.  If we
             don't find a command name at all, we can perform command name
             completion.  If we find a partial command name, we should perform
@@ -1687,10 +1696,12 @@ attempt_shell_completion (const char *text, int start, int end)
         end == index of where word to be completed ends
         if (s == start) we are doing command word completion for sure
         if (e1 == end) we are at the end of the command name and completing it */
-      if (start == 0 && end == 0 && e != 0 && text[0] == '\0') /* beginning of non-empty line */
+      if (start == 0 && end == 0 && e != 0 && e1 < rl_end && text[0] == '\0')  /* beginning of non-empty line */
         foundcs = 0;
       else if (start == end && start == s1 && e != 0 && e1 > end)      /* beginning of command name, leading whitespace */
        foundcs = 0;
+      else if (start == 0 && start == end && start < s1 && e != 0 && e1 > end && text[0] == '\0' && have_progcomps)    /* no command name, leading whitespace only */
+        prog_complete_matches = programmable_completions (EMPTYCMD, text, s, e, &foundcs);
       else if (e == 0 && e == s && text[0] == '\0' && have_progcomps)  /* beginning of empty line */
         prog_complete_matches = programmable_completions (EMPTYCMD, text, s, e, &foundcs);
       else if (start == end && text[0] == '\0' && s1 > start && whitespace (rl_line_buffer[start]))
@@ -1734,7 +1745,14 @@ attempt_shell_completion (const char *text, int start, int end)
       /* If we have defined a compspec for the initial (command) word, call
         it and process the results like any other programmable completion. */
       if (in_command_position && have_progcomps && foundcs == 0 && iw_compspec)
-       prog_complete_matches = programmable_completions (INITIALWORD, text, s, e, &foundcs);
+       {
+         /* Do some sanity checking on S and E. Room for more here. */
+         if (s > rl_point)
+           s = rl_point;
+         if (e > rl_end)
+           e = rl_end;
+         prog_complete_matches = programmable_completions (INITIALWORD, text, s, e, &foundcs);
+       }
 
       FREE (n);
       /* XXX - if we found a COMPSPEC for the command, just return whatever
@@ -1780,6 +1798,8 @@ bash_default_completion (const char *text, int start, int end, int qc, int compf
     {
       if (qc != '\'' && text[1] == '(') /* ) */
        matches = rl_completion_matches (text, command_subst_completion_function);
+      else if (qc != '\'' && text[1] == '{' && FUNSUB_CHAR (text[2])) /* } */
+       matches = rl_completion_matches (text, command_subst_completion_function);
       else
        {
          matches = rl_completion_matches (text, variable_completion_function);
@@ -1846,8 +1866,7 @@ bash_default_completion (const char *text, int start, int end, int qc, int compf
              rl_completion_suppress_append = 1;
              rl_filename_completion_desired = 0;
            }
-#if 0
-         /* TAG:bash-5.3 jidanni@jidanni.org 11/11/2022 */
+#if 1
          else if (matches[0] && matches[1] && STREQ (matches[0], matches[1]) &&
                   matches[2] && STREQ (matches[1], matches[2]) && CMD_IS_DIR (matches[0]))
 #else
@@ -1906,6 +1925,7 @@ bash_command_name_stat_hook (char **name)
   result = search_for_command (cname, 0);
   if (result)
     {
+      FREE (*name);
       *name = result;
       return 1;
     }
@@ -1920,12 +1940,8 @@ executable_completion (const char *filename, int searching_path)
 
   /* This gets an unquoted filename, so we need to quote special characters
      in the filename before the completion hook gets it. */
-#if 0
-  f = savestring (filename);
-#else
   c = 0;
   f = bash_quote_filename ((char *)filename, SINGLE_MATCH, &c);
-#endif
   bash_directory_completion_hook (&f);
   
   r = searching_path ? executable_file (f) : executable_or_directory (f);
@@ -1968,6 +1984,7 @@ command_word_completion_function (const char *hint_text, int state)
        free (dequoted_hint);
       if (hint)
        free (hint);
+      dequoted_hint = hint = (char *)NULL;
 
       mapping_over = searching_path = 0;
       hint_is_dir = CMD_IS_DIR (hint_text);
@@ -2062,7 +2079,7 @@ command_word_completion_function (const char *hint_text, int state)
       if (rl_completion_found_quote && rl_completion_quote_character == 0)
        dequoted_hint = bash_dequote_filename (hint, 0);
       
-      path = get_string_value ("PATH");
+      path = path_value ("PATH", 0);
       path_index = dot_in_path = 0;
 
       /* Initialize the variables for each type of command word. */
@@ -2178,7 +2195,11 @@ globword:
          local_index = 0;
                
          if (glob_matches[1] && rl_completion_type == TAB)     /* multiple matches are bad */
-           return ((char *)NULL);
+           {
+             strvec_dispose (glob_matches);
+             glob_matches = (char **)NULL;
+             return ((char *)NULL);
+           }
        }
 
       while (val = glob_matches[local_index++])
@@ -2250,6 +2271,7 @@ globword:
        free (fnhint);
       if (filename_hint)
        free (filename_hint);
+      fnhint = filename_hint = (char *)NULL;
 
       filename_hint = sh_makepath (current_path, hint, 0);
       /* Need a quoted version (though it doesn't matter much in most
@@ -2345,6 +2367,7 @@ globword:
          t1 = make_absolute (val, t);
          free (t);
          cval = sh_canonpath (t1, PATH_CHECKDOTDOT|PATH_CHECKEXISTS);
+         free (t1);
        }
       else
 #endif
@@ -2389,13 +2412,18 @@ command_subst_completion_function (const char *text, int state)
        text++;
       else if (*text == '$' && text[1] == '(') /* ) */
        text += 2;
+      else if (*text == '$' && text[1] == '{' && FUNSUB_CHAR (text[2])) /*}*/
+       text += 3;      /* nofork command substitution */
       /* If the text was quoted, suppress any quote character that the
         readline completion code would insert. */
       rl_completion_suppress_quote = 1;
       start_len = text - orig_start;
       filename_text = savestring (text);
       if (matches)
-       free (matches);
+       {
+         free (matches);
+         matches = (char **)NULL;
+       }
 
       /*
        * At this point we can entertain the idea of re-parsing
@@ -2707,14 +2735,27 @@ maybe_make_readline_line (const char *new_line)
     }
 }
 
-/* Make NEW_LINE be the current readline line.  This frees NEW_LINE. */
+/* Make NEW_LINE be the current readline line.  This frees NEW_LINE. We use
+   several heuristics to decide where to put rl_point. */
+
 static void
 set_up_new_line (char *new_line)
 {
-  int old_point, at_end;
+
+  int old_point, old_end, dist, nb;
+  char *s, *t;
+
+  /* If we didn't expand anything, don't change anything. */
+  if (STREQ (new_line, rl_line_buffer))
+    {
+      free (new_line);
+      return;
+    }
 
   old_point = rl_point;
-  at_end = rl_point == rl_end;
+  old_end = rl_end;
+  dist = rl_end - rl_point;
+  nb = rl_end - old_end;
 
   /* If the line was history and alias expanded, then make that
      be one thing to undo. */
@@ -2722,14 +2763,31 @@ set_up_new_line (char *new_line)
   free (new_line);
 
   /* Place rl_point where we think it should go. */
-  if (at_end)
+  if (dist == 0)
     rl_point = rl_end;
-  else if (old_point < rl_end)
+  else if (old_point == 0)
+    {
+      /* this is what the old code did, but separate it out so we can treat
+        it differently */
+      rl_point = 0;
+      if (!whitespace (rl_line_buffer[rl_point]))
+       rl_forward_word (1, 0);
+    }
+  else if (rl_point < old_point+nb)
     {
+      /* let's assume that this means the new point is within the changed region */
       rl_point = old_point;
       if (!whitespace (rl_line_buffer[rl_point]))
        rl_forward_word (1, 0);
     }
+  else
+    rl_point = rl_end - dist;  /* try to put point the same distance from end */
+
+  if (rl_point < 0)
+    rl_point = 0;
+  else if (rl_point > rl_end)
+    rl_point = rl_end;
+  rl_mark = 0;         /* XXX */
 }
 
 #if defined (ALIAS)
@@ -3034,7 +3092,7 @@ name_is_acceptable (const char *name)
 
   for (nlen = strlen (name), p = fignore.ignores; p->val; p++)
     {
-      if (nlen > p->len && p->len > 0 && STREQ (p->val, &name[nlen - p->len]))
+      if (nlen >= p->len && p->len > 0 && STREQ (p->val, &name[nlen - p->len]))
        return (0);
     }
 
@@ -3075,6 +3133,19 @@ test_for_directory (const char *name)
   int r;
 
   fn = bash_tilde_expand (name, 0);
+
+#if __CYGWIN
+  /* stat("//server") can only be successful as a directory, but can take
+     seconds to time out on failure.  It is much faster to assume that
+     "//server" is a valid name than it is to wait for a stat, even if it
+     gives false positives on bad names.  */
+  if (fn[0] == '/' && fn[1] == '/' && ! strchr (&fn[2], '/'))
+    {
+      free (fn);
+      return 1;
+    }
+#endif
+
   r = file_isdir (fn);
   free (fn);
 
@@ -3283,6 +3354,20 @@ restore_directory_hook (rl_icppfunc_t *hookf)
     rl_directory_rewrite_hook = hookf;
 }
 
+/* Set the readline hooks that affect how directories and filenames are
+   converted. Extern so other parts of the shell can use. */
+void
+bashline_set_filename_hooks (void)
+{
+  /* Tell the completer that we might want to follow symbolic links or
+     do other expansion on directory names. */
+  set_directory_hook ();
+
+  rl_filename_rewrite_hook = bash_filename_rewrite_hook;
+  rl_completion_rewrite_hook = bash_filename_rewrite_hook;
+  rl_filename_stat_hook = bash_filename_stat_hook;
+}
+
 /* Check whether not DIRNAME, with any trailing slash removed, exists.  If
    SHOULD_DEQUOTE is non-zero, we dequote the directory name first. */
 static int
@@ -3841,9 +3926,11 @@ glob_complete_word (const char *text, int state)
     {
       rl_filename_completion_desired = 1;
       FREE (matches);
+      matches = (char **)NULL;
       if (globorig != globtext)
        FREE (globorig);
       FREE (globtext);
+      globorig = globtext = (char *)NULL;
 
       ttext = bash_tilde_expand (text, 0);
 
@@ -3946,10 +4033,38 @@ bash_specific_completion (int what_to_do, rl_compentry_func_t *generator)
 #endif /* SPECIFIC_COMPLETION_FUNCTIONS */
 
 #if defined (VI_MODE)
+/* This does pretty much what _rl_vi_advance_point does. */
+static inline int
+vi_advance_point (void)
+{
+  int point;
+  DECLARE_MBSTATE;
+
+  point = rl_point;
+  if (rl_point < rl_end)
+#if defined (HANDLE_MULTIBYTE)
+    {
+      if (locale_mb_cur_max == 1)
+       rl_point++;
+      else
+       {
+         point = rl_point;
+         ADVANCE_CHAR (rl_line_buffer, rl_end, rl_point);
+         if (point == rl_point || rl_point > rl_end)
+           rl_point = rl_end;
+       }
+    }
+#else
+    rl_point++:
+#endif
+  return point;
+}
+
 /* Completion, from vi mode's point of view.  This is a modified version of
    rl_vi_complete which uses the bash globbing code to implement what POSIX
-   specifies, which is to append a `*' and attempt filename generation (which
-   has the side effect of expanding any globbing characters in the word). */
+   specifies, which is to optinally append a `*' and attempt filename
+   generation (which has the side effect of expanding any globbing characters
+   in the word). */
 static int
 bash_vi_complete (int count, int key)
 {
@@ -3961,7 +4076,7 @@ bash_vi_complete (int count, int key)
     {
       if (!whitespace (rl_line_buffer[rl_point + 1]))
        rl_vi_end_word (1, 'E');
-      rl_point++;
+      vi_advance_point ();
     }
 
   /* Find boundaries of current word, according to vi definition of a
@@ -4016,15 +4131,13 @@ bash_dequote_filename (char *text, int quote_char)
   ret = (char *)xmalloc (l + 1);
   for (quoted = quote_char, p = text, r = ret; p && *p; p++)
     {
-      /* Allow backslash-escaped characters to pass through unscathed. */
-      if (*p == '\\')
+      /* Allow backslash-escaped characters to pass through unscathed. Backslashes
+        aren't special in single quotes. */
+      if (quoted != '\'' && *p == '\\')
        {
-         /* Backslashes are preserved within single quotes. */
-         if (quoted == '\'')
-           *r++ = *p;
          /* Backslashes are preserved within double quotes unless the
             character is one that is defined to be escaped */
-         else if (quoted == '"' && ((sh_syntaxtab[(unsigned char)p[1]] & CBSDQUOTE) == 0))
+         if (quoted == '"' && ((sh_syntaxtab[(unsigned char)p[1]] & CBSDQUOTE) == 0))
            *r++ = *p;
 
          *r++ = *++p;
@@ -4377,6 +4490,34 @@ readline_set_char_offset (int ind, int *varp)
     }
 }
 
+void
+uw_restore_parser_state (void *ps)
+{
+  restore_parser_state (ps);
+}
+
+void
+uw_rl_set_signals (void *ignore)
+{
+  rl_set_signals ();
+}
+
+static void
+unbind_readline_variables (void)
+{
+  check_unbind_variable ("READLINE_LINE");
+  check_unbind_variable ("READLINE_POINT");
+  check_unbind_variable ("READLINE_MARK");
+  check_unbind_variable ("READLINE_ARGUMENT");
+  array_needs_making = 1;
+}
+
+static void
+uw_unbind_readline_variables (void *ignore)
+{
+  unbind_readline_variables ();
+}
+   
 int
 bash_execute_unix_command (int count, int key)
 {
@@ -4395,7 +4536,7 @@ bash_execute_unix_command (int count, int key)
   kslen = rl_key_sequence_length;
 
   /* If we have a numeric argument, chop it off the front of the key sequence */
-  if (count > 1 || rl_explicit_arg)
+  if (count != 1 || rl_explicit_arg)
     {
       i = rl_trim_arg_from_keyseq (rl_executing_keyseq, rl_key_sequence_length, rl_get_keymap ());
       if (i > 0)
@@ -4447,7 +4588,7 @@ bash_execute_unix_command (int count, int key)
   if (v)
     VSETATTR (v, att_exported);
 
-  if (count > 1 || rl_explicit_arg)
+  if (count != 1 || rl_explicit_arg)
     {
       value = inttostr (count, ibuf, sizeof (ibuf));
       v = bind_int_variable ("READLINE_ARGUMENT", value, 0);
@@ -4456,8 +4597,12 @@ bash_execute_unix_command (int count, int key)
     }
   array_needs_making = 1;
 
+  begin_unwind_frame ("execute-unix-command");
   save_parser_state (&ps);
   rl_clear_signals ();
+  add_unwind_protect (uw_unbind_readline_variables, 0);
+  add_unwind_protect (uw_restore_parser_state, &ps);
+  add_unwind_protect (uw_rl_set_signals, 0);
   r = parse_and_execute (savestring (cmd), "bash_execute_unix_command", SEVAL_NOHIST);
   rl_set_signals ();
   restore_parser_state (&ps);
@@ -4466,18 +4611,15 @@ bash_execute_unix_command (int count, int key)
   maybe_make_readline_line (v ? value_cell (v) : 0);
 
   v = find_variable ("READLINE_POINT");
-  if (v && legal_number (value_cell (v), &mi))
+  if (v && valid_number (value_cell (v), &mi))
     readline_set_char_offset (mi, &rl_point);
 
   v = find_variable ("READLINE_MARK");
-  if (v && legal_number (value_cell (v), &mi))
+  if (v && valid_number (value_cell (v), &mi))
     readline_set_char_offset (mi, &rl_mark);
 
-  check_unbind_variable ("READLINE_LINE");
-  check_unbind_variable ("READLINE_POINT");
-  check_unbind_variable ("READLINE_MARK");
-  check_unbind_variable ("READLINE_ARGUMENT");
-  array_needs_making = 1;
+  unbind_readline_variables ();
+  discard_unwind_frame ("execute-unix-command");
 
   /* and restore the readline buffer and display after command execution. */
   /* If we clear the last line of the prompt above, redraw only that last
@@ -4491,15 +4633,29 @@ bash_execute_unix_command (int count, int key)
   return 0;
 }
 
+/* This only has to handle macros/shell commandsfrom print_unix_command_map */
+static void
+print_unix_command (const char *kseq, const char *value, int readable, const char *prefix)
+{
+  if (readable)
+    fprintf (rl_outstream, "\"%s%s\" \"%s\"\n", prefix ? prefix : "", kseq, value ? value : "");
+  else
+    fprintf (rl_outstream, "%s%s outputs %s\n", prefix ? prefix : "", kseq, value ? value : "");
+}
+
 int
 print_unix_command_map (void)
 {
   Keymap save, cmd_xmap;
+  rl_macro_print_func_t *old_printer;
 
   save = rl_get_keymap ();
   cmd_xmap = get_cmd_xmap_from_keymap (save);
   rl_set_keymap (cmd_xmap);
+  old_printer = rl_macro_display_hook;
+  rl_macro_display_hook = print_unix_command;
   rl_macro_dumper (1);
+  rl_macro_display_hook = old_printer;
   rl_set_keymap (save);
   return 0;
 }
@@ -4613,12 +4769,12 @@ bind_keyseq_to_unix_command (char *line)
 {
   Keymap kmap, cmd_xmap;
   char *kseq, *value;
-  int i, kstart;
+  int i, kstart, translate;
 
   kmap = rl_get_keymap ();
 
   /* We duplicate some of the work done by rl_parse_and_bind here, but
-     this code only has to handle `"keyseq"["]command["]' and can
+     this code only has to handle `"keyseq"[:][ \t]+["]command["]' and can
      generate an error for anything else. */
   i = isolate_sequence (line, 0, 1, &kstart);
   if (i < 0)
@@ -4627,16 +4783,26 @@ bind_keyseq_to_unix_command (char *line)
   /* Create the key sequence string to pass to rl_generic_bind */
   kseq = substring (line, kstart, i);
 
-  for ( ; line[i] && line[i] != ':'; i++)
+  /* Allow colon or whitespace to separate the key sequence and command string. */
+  for ( ; line[i] && line[i] != ':' && whitespace (line[i]) == 0; i++)
     ;
-  if (line[i] != ':')
+  if (line[i] != ':' && whitespace (line[i]) == 0)
     {
-      builtin_error (_("%s: missing colon separator"), line);
+      builtin_error (_("%s: missing separator"), line);
       FREE (kseq);
       return -1;
     }
 
-  i = isolate_sequence (line, i + 1, 0, &kstart);
+  /* If we have a whitespace separator we're going to call rl_macro_bind so
+     we get the readline-translated version of the value (backslash-escapes
+     handled, etc.) */
+  translate = line[i] != ':';
+
+  /* Kind of tricky. If we use whitespace as a delimiter, we can backslash-
+     quote double quotes and have them preserved in the value. However, we
+     still want to be able to auto-detect quoted strings and only require
+     them with whitespace delimiters. */
+  i = isolate_sequence (line, i + 1, translate, &kstart);
   if (i < 0)
     {
       FREE (kseq);
@@ -4648,7 +4814,10 @@ bind_keyseq_to_unix_command (char *line)
 
   /* Save the command to execute and the key sequence in the CMD_XMAP */
   cmd_xmap = get_cmd_xmap_from_keymap (kmap);
-  rl_generic_bind (ISMACR, kseq, value, cmd_xmap);
+  if (translate)
+    rl_macro_bind (kseq, value, cmd_xmap);
+  else
+    rl_generic_bind (ISMACR, kseq, value, cmd_xmap);
 
   /* and bind the key sequence in the current keymap to a function that
      understands how to execute from CMD_XMAP */