/*
 * Copyright (C) 1996-2000,2007,2011,2013 Michael R. Elkins <me@mutt.org>
 * Copyright (C) 2000-2001 Edmund Grimley Evans <edmundo@rano.org>
 *
 *     This program is free software; you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation; either version 2 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program; if not, write to the Free Software
 *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#if HAVE_CONFIG_H
# include "config.h"
#endif

#include "mutt.h"
#include "mutt_menu.h"
#include "mutt_curses.h"
#include "keymap.h"
#include "history.h"
#include "buffy.h"

#include <string.h>

/* redraw flags for mutt_enter_string() */
enum
{
  MUTT_REDRAW_INIT = 1, /* go to end of line and redraw */
  MUTT_REDRAW_LINE              /* redraw entire line */
};

static int my_wcwidth(wchar_t wc)
{
  int n = wcwidth(wc);
  if (IsWPrint(wc) && n > 0)
    return n;
  if (!(wc & ~0x7f))
    return 2;
  if (!(wc & ~0xffff))
    return 6;
  return 10;
}

/* combining mark / non-spacing character */
#define COMB_CHAR(wc) (IsWPrint(wc) && !wcwidth(wc))

static int my_wcswidth(const wchar_t *s, size_t n)
{
  int w = 0;
  while (n--)
    w += my_wcwidth(*s++);
  return w;
}

static int my_addwch(wchar_t wc)
{
  int n = wcwidth(wc);
  if (IsWPrint(wc) && n > 0)
    return mutt_addwch(wc);
  if (!(wc & ~0x7f))
    return printw("^%c", ((int)wc + 0x40) & 0x7f);
  if (!(wc & ~0xffff))
    return printw("\\u%04x", (int)wc);
  return printw("\\u%08x", (int)wc);
}

static size_t width_ceiling(const wchar_t *s, size_t n, int w1)
{
  const wchar_t *s0 = s;
  int w = 0;
  for (; n; s++, n--)
    if ((w += my_wcwidth(*s)) > w1)
      break;
  return s - s0;
}

static void my_wcstombs(char *dest, size_t dlen, const wchar_t *src, size_t slen)
{
  mbstate_t st;
  size_t k;

  /* First convert directly into the destination buffer */
  memset(&st, 0, sizeof(st));
  for (; slen && dlen >= MB_LEN_MAX; dest += k, dlen -= k, src++, slen--)
    if ((k = wcrtomb(dest, *src, &st)) == (size_t)(-1))
      break;

  /* If this works, we can stop now */
  if (dlen >= MB_LEN_MAX)
  {
    /* We don't need to update dest - this just appeases -Wunused-result */
    dest += wcrtomb(dest, 0, &st);
    return;
  }

  /* Otherwise convert any remaining data into a local buffer */
  {
    char buf[3 * MB_LEN_MAX];
    char *p = buf;

    for (; slen && p - buf < dlen; p += k, src++, slen--)
      if ((k = wcrtomb(p, *src, &st)) == (size_t)(-1))
        break;
    p += wcrtomb(p, 0, &st);

    /* If it fits into the destination buffer, we can stop now */
    if (p - buf <= dlen)
    {
      memcpy(dest, buf, p - buf);
      return;
    }

    /* Otherwise we truncate the string in an ugly fashion */
    memcpy(dest, buf, dlen);
    dest[dlen - 1] = '\0'; /* assume original dlen > 0 */
  }
}

static size_t my_mbstowcs(wchar_t **pwbuf, size_t *pwbuflen, size_t i, char *buf)
{
  wchar_t wc;
  mbstate_t st;
  size_t k;
  wchar_t *wbuf;
  size_t wbuflen;

  wbuf = *pwbuf, wbuflen = *pwbuflen;

  while (*buf)
  {
    memset(&st, 0, sizeof(st));
    for (; (k = mbrtowc(&wc, buf, MB_LEN_MAX, &st)) &&
           k != (size_t)(-1) && k != (size_t)(-2); buf += k)
    {
      if (i >= wbuflen)
      {
        wbuflen = i + 20;
        safe_realloc(&wbuf, wbuflen * sizeof(*wbuf));
      }
      wbuf[i++] = wc;
    }
    if (*buf && (k == (size_t) -1 || k == (size_t) -2))
    {
      if (i >= wbuflen)
      {
        wbuflen = i + 20;
        safe_realloc(&wbuf, wbuflen * sizeof(*wbuf));
      }
      wbuf[i++] = replacement_char();
      buf++;
    }
  }
  *pwbuf = wbuf, *pwbuflen = wbuflen;
  return i;
}

/*
 * Replace part of the wchar_t buffer, from FROM to CURPOS, by BUF.
 */

static void replace_part(ENTER_STATE *state, size_t from, char *buf)
{
  /* Save the suffix */
  size_t savelen = state->lastchar - state->curpos;
  wchar_t *savebuf = NULL;

  if (savelen)
  {
    savebuf = safe_calloc(savelen, sizeof(wchar_t));
    memcpy(savebuf, state->wbuf + state->curpos, savelen * sizeof(wchar_t));
  }

  /* Convert to wide characters */
  state->curpos = my_mbstowcs(&state->wbuf, &state->wbuflen, from, buf);

  if (savelen)
  {
    /* Make space for suffix */
    if (state->curpos + savelen > state->wbuflen)
    {
      state->wbuflen = state->curpos + savelen;
      safe_realloc(&state->wbuf, state->wbuflen * sizeof(wchar_t));
    }

    /* Restore suffix */
    memcpy(state->wbuf + state->curpos, savebuf, savelen * sizeof(wchar_t));
    FREE(&savebuf);
  }

  state->lastchar = state->curpos + savelen;
}

/*
 * Return 1 if the character is not typically part of a pathname
 */
static inline int is_shell_char(wchar_t ch)
{
  static const wchar_t shell_chars[] = L"<>&()$?*;{}| "; /* ! not included because it can be part of a pathname in Mutt */
  return wcschr(shell_chars, ch) != NULL;
}

/* This function is for very basic input, currently used only by the
 * built-in editor.  It does not handle screen redrawing on resizes
 * well, because there is no active menu for the built-in editor.
 *
 * Most callers should prefer one of the public functions
 * listed in the comment header for _mutt_enter_string().
 *
 * Returns:
 *      0 if input was given
 *      -1 if abort.
 */
int mutt_enter_string(char *buf, size_t buflen, int col, int flags)
{
  int rv;
  ENTER_STATE *es = mutt_new_enter_state();
  do
  {
#if defined(USE_SLANG_CURSES) || defined(HAVE_RESIZETERM)
    if (SigWinch)
    {
      SigWinch = 0;
      mutt_resize_screen();
      clearok(stdscr, TRUE);
    }
#endif
    rv = _mutt_enter_string(buf, buflen, col, flags, 0, NULL, NULL, es);
  }
  while (rv == 1);
  mutt_free_enter_state(&es);
  return rv;
}

/* Generic Editor Menu.
 *
 * This function should not be called directly.  It must be wrapped by
 * _get_field().  Instead call one of the public interfaces:
 *
 *   mutt_enter_filename()
 *   mutt_enter_filenames()
 *   mutt_enter_mailbox()
 *
 *   mutt_get_field() / mutt_buffer_get_field()
 *   mutt_get_field_unbuffered() / mutt_get_password()
 *
 * If multiple is set, then the string (or selected files from the
 * browser) will be returned via files and numfiles.
 *
 * Otherwise the string will be returned inside buf.
 *
 * Returns:
 *      1 need to redraw the screen and call me again
 *      0 if input was given
 *      -1 if abort.
 */
int _mutt_enter_string(char *buf, size_t buflen, int col,
                       int flags, int multiple, char ***files, int *numfiles,
                       ENTER_STATE *state)
{
  int width = MuttMessageWindow->cols - col - 1;
  int redraw;
  int pass = (flags & MUTT_PASS);
  int first = 1;
  int ch, w, r;
  size_t i;
  wchar_t *tempbuf = 0;
  size_t templen = 0;
  history_class_t hclass;
  wchar_t wc;
  mbstate_t mbstate;

  int rv = 0;
  memset(&mbstate, 0, sizeof(mbstate));

  if (state->wbuf)
  {
    /* Coming back after return 1 */
    redraw = MUTT_REDRAW_LINE;
    first = 0;
  }
  else
  {
    /* Initialise wbuf from buf */
    state->wbuflen = 0;
    state->lastchar = my_mbstowcs(&state->wbuf, &state->wbuflen, 0, buf);
    redraw = MUTT_REDRAW_INIT;
  }

  if (flags & MUTT_FILE)
    hclass = HC_FILE;
  else if (flags & MUTT_MAILBOX)
    hclass = HC_MBOX;
  else if (flags & MUTT_CMD)
    hclass = HC_CMD;
  else if (flags & MUTT_ALIAS)
    hclass = HC_ALIAS;
  else if (flags & MUTT_COMMAND)
    hclass = HC_COMMAND;
  else if (flags & MUTT_PATTERN)
    hclass = HC_PATTERN;
  else
    hclass = HC_OTHER;

  for (;;)
  {
    if (redraw && !pass)
    {
      if (redraw == MUTT_REDRAW_INIT)
      {
        /* Go to end of line */
        state->curpos = state->lastchar;
        state->begin = width_ceiling(state->wbuf, state->lastchar, my_wcswidth(state->wbuf, state->lastchar) - width + 1);
      }
      if (state->curpos < state->begin ||
          my_wcswidth(state->wbuf + state->begin, state->curpos - state->begin) >= width)
        state->begin = width_ceiling(state->wbuf, state->lastchar, my_wcswidth(state->wbuf, state->curpos) - width / 2);
      mutt_window_move(MuttMessageWindow, 0, col);
      w = 0;
      for (i = state->begin; i < state->lastchar; i++)
      {
        w += my_wcwidth(state->wbuf[i]);
        if (w > width)
          break;
        my_addwch(state->wbuf[i]);
      }
      mutt_window_clrtoeol(MuttMessageWindow);
      mutt_window_move(MuttMessageWindow, 0,
                       col + my_wcswidth(state->wbuf + state->begin, state->curpos - state->begin));
    }
    mutt_refresh();

    if ((ch = km_dokey(MENU_EDITOR)) < 0)
    {
      rv = (SigWinch && ch == -2) ? 1 : -1;
      goto bye;
    }

    if (ch != OP_NULL)
    {
      first = 0;
      if (ch != OP_EDITOR_COMPLETE && ch != OP_EDITOR_COMPLETE_QUERY)
        state->tabs = 0;
      redraw = MUTT_REDRAW_LINE;
      switch (ch)
      {
        case OP_EDITOR_HISTORY_UP:
          state->curpos = state->lastchar;
          if (mutt_history_at_scratch(hclass))
          {
            my_wcstombs(buf, buflen, state->wbuf, state->curpos);
            mutt_history_save_scratch(hclass, buf);
          }
          replace_part(state, 0, mutt_history_prev(hclass));
          redraw = MUTT_REDRAW_INIT;
          break;

        case OP_EDITOR_HISTORY_DOWN:
          state->curpos = state->lastchar;
          if (mutt_history_at_scratch(hclass))
          {
            my_wcstombs(buf, buflen, state->wbuf, state->curpos);
            mutt_history_save_scratch(hclass, buf);
          }
          replace_part(state, 0, mutt_history_next(hclass));
          redraw = MUTT_REDRAW_INIT;
          break;

        case OP_EDITOR_HISTORY_SEARCH:
          state->curpos = state->lastchar;
          my_wcstombs(buf, buflen, state->wbuf, state->curpos);
          mutt_history_complete(buf, buflen, hclass);
          replace_part(state, 0, buf);
          rv = 1;
          goto bye;
          break;

        case OP_EDITOR_BACKSPACE:
          if (state->curpos == 0)
            BEEP();
          else
          {
            i = state->curpos;
            while (i && COMB_CHAR(state->wbuf[i - 1]))
              --i;
            if (i)
              --i;
            memmove(state->wbuf + i, state->wbuf + state->curpos, (state->lastchar - state->curpos) * sizeof(wchar_t));
            state->lastchar -= state->curpos - i;
            state->curpos = i;
          }
          break;

        case OP_EDITOR_BOL:
          state->curpos = 0;
          break;

        case OP_EDITOR_EOL:
          redraw= MUTT_REDRAW_INIT;
          break;

        case OP_EDITOR_KILL_LINE:
          state->curpos = state->lastchar = 0;
          break;

        case OP_EDITOR_KILL_EOL:
          state->lastchar = state->curpos;
          break;

        case OP_EDITOR_BACKWARD_CHAR:
          if (state->curpos == 0)
            BEEP();
          else
          {
            while (state->curpos && COMB_CHAR(state->wbuf[state->curpos - 1]))
              state->curpos--;
            if (state->curpos)
              state->curpos--;
          }
          break;

        case OP_EDITOR_FORWARD_CHAR:
          if (state->curpos == state->lastchar)
            BEEP();
          else
          {
            ++state->curpos;
            while (state->curpos < state->lastchar && COMB_CHAR(state->wbuf[state->curpos]))
              ++state->curpos;
          }
          break;

        case OP_EDITOR_BACKWARD_WORD:
          if (state->curpos == 0)
            BEEP();
          else
          {
            while (state->curpos && iswspace(state->wbuf[state->curpos - 1]))
              --state->curpos;
            while (state->curpos && !iswspace(state->wbuf[state->curpos - 1]))
              --state->curpos;
          }
          break;

        case OP_EDITOR_FORWARD_WORD:
          if (state->curpos == state->lastchar)
            BEEP();
          else
          {
            while (state->curpos < state->lastchar && iswspace(state->wbuf[state->curpos]))
              ++state->curpos;
            while (state->curpos < state->lastchar && !iswspace(state->wbuf[state->curpos]))
              ++state->curpos;
          }
          break;

        case OP_EDITOR_CAPITALIZE_WORD:
        case OP_EDITOR_UPCASE_WORD:
        case OP_EDITOR_DOWNCASE_WORD:
          if (state->curpos == state->lastchar)
          {
            BEEP();
            break;
          }
          while (state->curpos && !iswspace(state->wbuf[state->curpos]))
            --state->curpos;
          while (state->curpos < state->lastchar && iswspace(state->wbuf[state->curpos]))
            ++state->curpos;
          while (state->curpos < state->lastchar && !iswspace(state->wbuf[state->curpos]))
          {
            if (ch == OP_EDITOR_DOWNCASE_WORD)
              state->wbuf[state->curpos] = towlower(state->wbuf[state->curpos]);
            else
            {
              state->wbuf[state->curpos] = towupper(state->wbuf[state->curpos]);
              if (ch == OP_EDITOR_CAPITALIZE_WORD)
                ch = OP_EDITOR_DOWNCASE_WORD;
            }
            state->curpos++;
          }
          break;

        case OP_EDITOR_DELETE_CHAR:
          if (state->curpos == state->lastchar)
            BEEP();
          else
          {
            i = state->curpos;
            while (i < state->lastchar && COMB_CHAR(state->wbuf[i]))
              ++i;
            if (i < state->lastchar)
              ++i;
            while (i < state->lastchar && COMB_CHAR(state->wbuf[i]))
              ++i;
            memmove(state->wbuf + state->curpos, state->wbuf + i, (state->lastchar - i) * sizeof(wchar_t));
            state->lastchar -= i - state->curpos;
          }
          break;

        case OP_EDITOR_KILL_WORD:
          /* delete to beginning of word */
          if (state->curpos != 0)
          {
            i = state->curpos;
            while (i && iswspace(state->wbuf[i - 1]))
              --i;
            if (i)
            {
              if (iswalnum(state->wbuf[i - 1]))
              {
                for (--i; i && iswalnum(state->wbuf[i - 1]); i--)
                  ;
              }
              else
                --i;
            }
            memmove(state->wbuf + i, state->wbuf + state->curpos,
                    (state->lastchar - state->curpos) * sizeof(wchar_t));
            state->lastchar += i - state->curpos;
            state->curpos = i;
          }
          break;

        case OP_EDITOR_KILL_EOW:
          /* delete to end of word */

          /* first skip over whitespace */
          for (i = state->curpos;
               i < state->lastchar && iswspace(state->wbuf[i]); i++)
            ;

          /* if there are any characters left.. */
          if (i < state->lastchar)
          {
            /* if the current character is alphanumeric.. */
            if (iswalnum(state->wbuf[i]))
            {
              /* skip over the rest of the word consistent of only alphanumerics */
              for (; i < state->lastchar && iswalnum(state->wbuf[i]); i++)
                ;
            }
            else
            {
              /* skip over one non-alphanumeric character */
              ++i;
            }
          }

          memmove(state->wbuf + state->curpos, state->wbuf + i,
                  (state->lastchar - i) * sizeof(wchar_t));
          state->lastchar += state->curpos - i;
          break;

        case OP_EDITOR_BUFFY_CYCLE:
          if (flags & MUTT_INCOMING)
          {
            first = 1; /* clear input if user types a real key later */
            my_wcstombs(buf, buflen, state->wbuf, state->curpos);
            mutt_buffy(buf, buflen);
            state->curpos = state->lastchar = my_mbstowcs(&state->wbuf, &state->wbuflen, 0, buf);
            break;
          }
          else if (!(flags & (MUTT_FILE | MUTT_MAILBOX)))
            goto self_insert;
          /* else fall through */

        case OP_EDITOR_COMPLETE:
        case OP_EDITOR_COMPLETE_QUERY:
          state->tabs++;
          if (flags & MUTT_CMD)
          {
            for (i = state->curpos; i && !is_shell_char(state->wbuf[i-1]); i--)
              ;
            my_wcstombs(buf, buflen, state->wbuf + i, state->curpos - i);
            if (tempbuf && templen == state->lastchar - i &&
                !memcmp(tempbuf, state->wbuf + i, (state->lastchar - i) * sizeof(wchar_t)))
            {
              mutt_select_file(buf, buflen, 0);
              if (*buf)
                replace_part(state, i, buf);
              rv = 1;
              goto bye;
            }
            if (!mutt_complete(buf, buflen))
            {
              templen = state->lastchar - i;
              safe_realloc(&tempbuf, templen * sizeof(wchar_t));
              if (tempbuf)
                memcpy(tempbuf, state->wbuf + i, templen * sizeof(wchar_t));
            }
            else
              BEEP();

            replace_part(state, i, buf);
          }
          else if (flags & MUTT_ALIAS && ch == OP_EDITOR_COMPLETE)
          {
            /* invoke the alias-menu to get more addresses */
            for (i = state->curpos;
                 i && state->wbuf[i-1] != ',' && state->wbuf[i-1] != ':';
                 i--)
              ;
            for (; i < state->lastchar && state->wbuf[i] == ' '; i++)
              ;
            my_wcstombs(buf, buflen, state->wbuf + i, state->curpos - i);
            r = mutt_alias_complete(buf, buflen);
            replace_part(state, i, buf);
            if (!r)
            {
              rv = 1;
              goto bye;
            }
            break;
          }
          else if (flags & MUTT_LABEL && ch == OP_EDITOR_COMPLETE)
          {
            for (i = state->curpos;
                 i && state->wbuf[i-1] != ',' && state->wbuf[i-1] != ':';
                 i--)
              ;
            for (; i < state->lastchar && state->wbuf[i] == ' '; i++)
              ;
            my_wcstombs(buf, buflen, state->wbuf + i, state->curpos - i);
            r = mutt_label_complete(buf, buflen, state->tabs);
            replace_part(state, i, buf);
            if (!r)
            {
              rv = 1;
              goto bye;
            }
            break;
          }
          else if (flags & MUTT_PATTERN && ch == OP_EDITOR_COMPLETE)
          {
            i = state->curpos;
            if (i && state->wbuf[i - 1] == '~')
            {
              if (mutt_ask_pattern(buf, buflen))
                replace_part(state, i - 1, buf);
              rv = 1;
              goto bye;
            }
            for (; i && state->wbuf[i - 1] != '~'; i--)
              ;
            if (i && i < state->curpos && state->wbuf[i-1] == '~' && state->wbuf[i] == 'y')
            {
              i++;
              my_wcstombs(buf, buflen, state->wbuf + i, state->curpos - i);
              r = mutt_label_complete(buf, buflen, state->tabs);
              replace_part(state, i, buf);
              if (!r)
              {
                rv = 1;
                goto bye;
              }
            }
            else
              goto self_insert;
            break;
          }
          else if (flags & MUTT_ALIAS && ch == OP_EDITOR_COMPLETE_QUERY)
          {
            /* invoke the query-menu to get more addresses */
            if ((i = state->curpos))
            {
              for (; i && state->wbuf[i - 1] != ','; i--)
                ;
              for (; i < state->curpos && state->wbuf[i] == ' '; i++)
                ;
            }

            my_wcstombs(buf, buflen, state->wbuf + i, state->curpos - i);
            mutt_query_complete(buf, buflen);
            replace_part(state, i, buf);

            rv = 1;
            goto bye;
          }
          else if (flags & MUTT_COMMAND)
          {
            int complete_rv;

            my_wcstombs(buf, buflen, state->wbuf, state->curpos);
            i = strlen(buf);
            if (i && buf[i - 1] == '=' &&
                mutt_var_value_complete(buf, buflen, i))
            {
              replace_part(state, 0, buf);
              state->tabs = 0;
            }
            else
            {
              complete_rv = mutt_command_complete(buf, buflen, i, state->tabs);
              if (complete_rv > 0)
              {
                replace_part(state, 0, buf);
                if (complete_rv == 2)
                {
                  state->tabs = 0;
                  rv = 1;
                  goto bye;
                }
              }
              else
              {
                state->tabs = 0;
                BEEP();
              }
            }
          }
          else if (flags & (MUTT_FILE | MUTT_MAILBOX))
          {
            my_wcstombs(buf, buflen, state->wbuf, state->curpos);

            /* see if the path has changed from the last time */
            if ((!tempbuf && !state->lastchar) ||
                (tempbuf && templen == state->lastchar &&
                 !memcmp(tempbuf, state->wbuf, state->lastchar * sizeof(wchar_t))))
            {
              _mutt_select_file(buf, buflen,
                                ((flags & MUTT_MAILBOX) ? MUTT_SEL_MAILBOX : 0) | (multiple ? MUTT_SEL_MULTI : 0),
                                files, numfiles);
              if (!multiple && *buf)
              {
                mutt_pretty_mailbox(buf, buflen);
                if (!pass)
                  mutt_history_add(hclass, buf, 1);
                rv = 0;
                goto bye;
              }
              if (multiple && *numfiles > 0)
              {
                rv = 0;
                goto bye;
              }

              /* file selection cancelled */
              rv = 1;
              goto bye;
            }

            if (!mutt_complete(buf, buflen))
            {
              templen = state->lastchar;
              safe_realloc(&tempbuf, templen * sizeof(wchar_t));
              if (tempbuf)
                memcpy(tempbuf, state->wbuf, templen * sizeof(wchar_t));
            }
            else
              BEEP(); /* let the user know that nothing matched */
            replace_part(state, 0, buf);
          }
          else
            goto self_insert;
          break;

        case OP_EDITOR_QUOTE_CHAR:
        {
          event_t event;
          /*ADDCH (LastKey);*/
          do
          {
            event = mutt_getch();
          } while (event.ch == -2);
          if (event.ch >= 0)
          {
            LastKey = event.ch;
            goto self_insert;
          }
          break;
        }

        case OP_EDITOR_TRANSPOSE_CHARS:
          if (state->lastchar < 2)
            BEEP();
          else
          {
            wchar_t t;

            if (state->curpos == 0)
              state->curpos = 2;
            else if (state->curpos < state->lastchar)
              ++state->curpos;

            t = state->wbuf[state->curpos - 2];
            state->wbuf[state->curpos - 2] = state->wbuf[state->curpos - 1];
            state->wbuf[state->curpos - 1] = t;
          }
          break;

        default:
          BEEP();
      }
    }
    else
    {

self_insert:

      state->tabs = 0;
      /* use the raw keypress */
      ch = LastKey;

#ifdef KEY_ENTER
      /* treat ENTER the same as RETURN */
      if (ch == KEY_ENTER)
        ch = '\r';
#endif

      /* quietly ignore all other function keys */
      if (ch & ~0xff)
        continue;

      /* gather the octets into a wide character */
      {
        char c;
        size_t k;

        c = ch;
        k = mbrtowc(&wc, &c, 1, &mbstate);
        if (k == (size_t)(-2))
          continue;
        else if (k && k != 1)
        {
          memset(&mbstate, 0, sizeof(mbstate));
          continue;
        }
      }

      if (first && (flags & MUTT_CLEAR))
      {
        first = 0;
        if (IsWPrint(wc)) /* why? */
          state->curpos = state->lastchar = 0;
      }

      if (wc == '\r' || wc == '\n')
      {
        /* Convert from wide characters */
        my_wcstombs(buf, buflen, state->wbuf, state->lastchar);
        if (!pass)
          mutt_history_add(hclass, buf, 1);

        /* If multiple is set, the caller expects the result to be in
         * tfiles[0], not buf.
         */
        if (multiple)
        {
          char **tfiles;
          BUFFER *path = NULL;

          path = mutt_buffer_pool_get();
          *numfiles = 1;
          tfiles = safe_calloc(*numfiles, sizeof(char *));
          mutt_buffer_strcpy(path, buf);
          mutt_buffer_expand_path(path);
          tfiles[0] = safe_strdup(mutt_b2s(path));
          *files = tfiles;
          mutt_buffer_pool_release(&path);
        }
        rv = 0;
        goto bye;
      }
      else if (wc && (wc < ' ' || IsWPrint(wc))) /* why? */
      {
        if (state->lastchar >= state->wbuflen)
        {
          state->wbuflen = state->lastchar + 20;
          safe_realloc(&state->wbuf, state->wbuflen * sizeof(wchar_t));
        }
        memmove(state->wbuf + state->curpos + 1, state->wbuf + state->curpos, (state->lastchar - state->curpos) * sizeof(wchar_t));
        state->wbuf[state->curpos++] = wc;
        state->lastchar++;
      }
      else
      {
        mutt_flushinp();
        BEEP();
      }
    }
  }

bye:

  mutt_reset_history_state(hclass);
  FREE(&tempbuf);
  return rv;
}

void mutt_free_enter_state(ENTER_STATE **esp)
{
  if (!esp) return;

  FREE(&(*esp)->wbuf);
  FREE(esp);           /* __FREE_CHECKED__ */
}

/*
 * TODO:
 * very narrow screen might crash it
 * sort out the input side
 * unprintable chars
 */
