#include "fm.h"
#include "local.h"
#include "myctype.h"

#ifdef __EMX__
#include <sys/kbdscan.h>
#endif

#define CLEN (COLS - 2)

static void insertself(char c, InputLineEnv *env),
    insC(InputLineEnv *env), _dcompl(InputLineEnv *env), _rdcompl(InputLineEnv *env);
static void _rcompl(InputLineEnv *env);
#ifdef _EMX_
static int getcntrl();
#endif
#ifdef MANY_CHARSET
static void insMBC(size_t cn, InputLineEnv *env);
#endif

#define iself ((void(*)())insertself)

static void next_compl(int next, InputLineEnv *env);
static Str do_filename_complete(Str ifn, int *status, int next, InputLineEnv *env);
static Str do_hist_complete(Str ifn, int *status, int next, InputLineEnv *env);
static Str do_ci_tab_complete(Str ifn, int *status, int next, InputLineEnv *env);

static KeyTabItem w3mDefaultEditKeyTab[] = {
    K_SET_FUNC(K_GEN(0, K_CTL_ATMARK), FUNCNAME_linein_compl),
    K_SET_FUNC(K_GEN(0, K_CTL_A), FUNCNAME_linein_mvB),
    K_SET_FUNC(K_GEN(0, K_CTL_B), FUNCNAME_linein_mvL),
    K_SET_FUNC(K_GEN(0, K_CTL_C), FUNCNAME_linein_inbrk),
    K_SET_FUNC(K_GEN(0, K_CTL_D), FUNCNAME_linein_delC),
    K_SET_FUNC(K_GEN(0, K_CTL_E), FUNCNAME_linein_mvE),
    K_SET_FUNC(K_GEN(0, K_CTL_F), FUNCNAME_linein_mvR),
    K_SET_FUNC(K_GEN(0, K_CTL_G), FUNCNAME_linein_inbrk),
    K_SET_FUNC(K_GEN(0, K_CTL_H), FUNCNAME_linein_bs),
    K_SET_FUNC(K_GEN(0, K_CTL_J), FUNCNAME_linein_enter),
    K_SET_FUNC(K_GEN(0, K_CTL_K), FUNCNAME_linein_killn),
    K_SET_FUNC(K_GEN(0, K_CTL_M), FUNCNAME_linein_enter),
    K_SET_FUNC(K_GEN(0, K_CTL_N), FUNCNAME_linein_next),
    K_SET_FUNC(K_GEN(0, K_CTL_P), FUNCNAME_linein_prev),
    K_SET_FUNC(K_GEN(0, K_CTL_Q), FUNCNAME_linein_quo),
    K_SET_FUNC(K_GEN(0, K_CTL_R), FUNCNAME_linein_bsw),
    K_SET_FUNC(K_GEN(0, K_CTL_T), FUNCNAME_linein_mvLw),
    K_SET_FUNC(K_GEN(0, K_CTL_U), FUNCNAME_linein_killb),
    K_SET_FUNC(K_GEN(0, K_CTL_V), FUNCNAME_linein_quo),
    K_SET_FUNC(K_GEN(0, K_CTL_X), FUNCNAME_linein_tcompl),
    K_SET_FUNC(K_GEN(0, K_CTL_Y), FUNCNAME_linein_mvRw),
    K_SET_FUNC(K_GEN(0, K_CTL_LBRACKET), FUNCNAME_linein_esc),
    K_SET_FUNC(K_GEN(0, K_ESCB | K_ALPHA_A), FUNCNAME_linein_prev),
    K_SET_FUNC(K_GEN(0, K_ESCB | K_ALPHA_B), FUNCNAME_linein_next),
    K_SET_FUNC(K_GEN(0, K_ESCB | K_ALPHA_C), FUNCNAME_linein_mvR),
    K_SET_FUNC(K_GEN(0, K_ESCB | K_ALPHA_D), FUNCNAME_linein_mvL),
};

KeyTabList w3mDefaultEditKeyTabList = {
    NULL,
    w3mDefaultEditKeyTab,
    sizeof(w3mDefaultEditKeyTab) / sizeof(w3mDefaultEditKeyTab[0]),
    sizeof(w3mDefaultEditKeyTab) / sizeof(w3mDefaultEditKeyTab[0]),
};

static int setStrType(InputLineEnv *env);
static void addPasswd(char *p, Lineprop *pr, int len, int pos, int limit);
static void addStr(char *p, Lineprop *pr, int len, int pos, int limit);

#ifdef JP_CHARSET
static void ins_kanji(Str tmp, InputLineEnv *env);
#endif

static int
same_type_word_char_p(
#ifdef MANY_CHARSET
		      mb_wchar_t x, mb_wchar_t y
#else
		      int x, int y
#endif
		      )
{
    if (!(x & ~0xFF) && IS_ALNUM(x)) {
	if (!(y & ~0xFF) && IS_ALNUM(y))
	    return 1;
	else
	    return 0;
    }
    else {
	if (!(y & ~0xFF) && IS_ALNUM(y))
	    return 0;
	else
	    return 1;
    }
}

static int
same_type_path_char_p(
#ifdef MANY_CHARSET
		      mb_wchar_t x, mb_wchar_t y
#else
		      int x, int y
#endif
		      )
{
    if (!(x & ~0xFF) && strchr("/&?", x)) {
	if (!(y & ~0xFF) && strchr("/&?", y))
	    return 1;
	else
	    return 0;
    }
    else {
	if (!(y & ~0xFF) && strchr("/&?", y))
	    return 0;
	else
	    return 1;
    }
}

void
call_edit_func(int c, void (*fallback)(char, InputLineEnv *), InputLineEnv *env)
{
    if (env->nstroke < K_LEN_MAX) {
	FuncList *fl;

	++(env->nstroke);
	env->prev_stroke = K_GEN(env->prev_stroke, c);

	if ((fl = lookupKey(env->prev_stroke, w3mEditKeyTabList))) {
#ifdef MANY_CHARSET
	    switch (fl - w3mFuncList) {
	    default:
		process_getch_mode(GETCH_MODE_CLR);
		env->i_wchar = FALSE;
	    case FUNCNAME_linein_esc:
	    case FUNCNAME_linein_nextchar:
		break;
	    }
#endif
	    fl->func.edit_func(env->prev_stroke, env);
	    return;
	}
    }

#ifdef MANY_CHARSET
    if (!env->i_wchar) {
	process_getch_mode(GETCH_MODE_RST);
	env->i_wchar = TRUE;
	return;
    }
    else {
	process_getch_mode(GETCH_MODE_CLR);
	env->i_wchar = FALSE;
    }
#endif

    if (fallback)
	fallback(c, env);
}

void
call_edit_func_nostroke(int c, void (*fallback)(char, InputLineEnv *), InputLineEnv *env)
{
    FuncList *fl;

    env->prev_stroke = K_OVER(env->prev_stroke, c);

    if ((fl = lookupKey(env->prev_stroke, w3mEditKeyTabList))) {
#ifdef MANY_CHARSET
	switch (fl - w3mFuncList) {
	default:
	    process_getch_mode(GETCH_MODE_CLR);
	    env->i_wchar = FALSE;
	case FUNCNAME_linein_esc:
	case FUNCNAME_linein_nextchar:
	    break;
	}
#endif
	fl->func.edit_func(env->prev_stroke, env);
    }
#ifdef MANY_CHARSET
    else if (!env->i_wchar) {
	process_getch_mode(GETCH_MODE_RST);
	env->i_wchar = TRUE;
    }
#endif
    else {
#ifdef MANY_CHARSET
	process_getch_mode(GETCH_MODE_CLR);
	env->i_wchar = FALSE;
#endif

	if (fallback)
	    fallback(c, env);
    }
}

int inputLineBusy = 0;
UrgentInputQueue inputLineQueue = {0};

static void
processInputLineQueue(void)
{
    int i = 0;
    char *p;

    do {
	if (*inputLineQueue.v[i].pfp) {
	    p = inputLineHist(inputLineQueue.v[i].prompt, inputLineQueue.v[i].def_str,
			      inputLineQueue.v[i].flag, inputLineQueue.v[i].hist);
	    fprintf(*inputLineQueue.v[i].pfp, "%s\n", halfdump_buffer_quote(p));
	    fflush(*inputLineQueue.v[i].pfp);
	}
    } while (++i < inputLineQueue.n);

    inputLineQueue.n = 0;
}

char *
inputLineHistTabSearch(char *prompt, char *def_str, int flag, Hist *hist, btri_string_tab_t *tab,
		    int (*incrfunc)(int ch, Str str, Lineprop *prop))
{
    int opos, x, y, lpos, rpos, epos;
#ifdef MANY_CHARSET
    mb_wchar_t c;
#else
    unsigned char c;
#endif
    char *p;
    Lineprop mode;
#ifdef JP_CHARSET
    Str tmp;
#endif				/* JP_CHARSET */
    InputLineEnv env;
    int oldbusy, old_y, old_x;

    oldbusy = inputLineBusy;
    inputLineBusy = 1;

    if (w3m_backend) {
	p = backend_inputLine(prompt, def_str, flag, hist);
	goto end;
    }
    else if (!fmInitialized) {
	if (feof(stdin) || ferror(stdin))
	    p = def_str ? def_str : "";
	else {
	    Str ans;

	    if (prompt)
		fputs(prompt, stdout);

	    if (def_str && *def_str && !(flag & IN_PASSWORD))
		printf("(default: %s)", def_str);

	    if (prompt || (def_str && !(flag & IN_PASSWORD)))
		fflush(stdout);

	    ans = Strfgets(stdin);
	    Strchop(ans);
	    p = ans->length ? ans->ptr : def_str ? def_str : "";
	}

	goto end;
    }

    getyx(old_y, old_x);
    memset(&env, 0, sizeof(env));
    mode = PC_ASCII;
#ifdef JP_CHARSET
    tmp = Strnew();
    env.in_kanji = FALSE;
#endif
    env.is_passwd = FALSE;
    env.same_type_char_p = same_type_word_char_p;

    env.CurrentHist = hist;
    if (hist != NULL) {
	env.use_hist = TRUE;
	env.strCurrentBuf = NULL;
    }
    else
	env.use_hist = FALSE;
    if (flag & (IN_URL | IN_FILENAME)) {
	env.cm_mode = CPL_IMMUTABLE | CPL_ON | (flag & IN_URL ? CPL_URL : 0);
	env.same_type_char_p = same_type_path_char_p;
	env.do_complete = do_filename_complete;
	env.ComplPrivate = New(ComplFileNameEnv);
	((ComplFileNameEnv *)env.ComplPrivate)->CFileName = ((ComplFileNameEnv *)env.ComplPrivate)->CDirBuf = NULL;
    }
    else if (flag & IN_PASSWORD) {
	env.cm_mode = CPL_IMMUTABLE | CPL_OFF;
	env.is_passwd = TRUE;
    }
    else if (flag & IN_COMMAND) {
	env.cm_mode = CPL_ON | CPL_COMMAND | CPL_WORDS;
	env.do_complete = do_filename_complete;
	env.ComplPrivate = New(ComplFileNameEnv);
	((ComplFileNameEnv *)env.ComplPrivate)->CFileName = ((ComplFileNameEnv *)env.ComplPrivate)->CDirBuf = NULL;
    }
    else if (tab) {
	env.cm_mode = CPL_IMMUTABLE | CPL_ON | (flag & IN_WORDS ? CPL_WORDS : 0);
	env.do_complete = do_ci_tab_complete;
	env.ComplPrivate = tab;
    }
    else if (hist) {
	env.cm_mode = CPL_ON;
	env.do_complete = do_hist_complete;
    }
    else 
	env.cm_mode = CPL_OFF;
#ifdef MANY_CHARSET
    opos = ttyfix_width(prompt);
#else
    opos = strlen(prompt);
#endif
    epos = CLEN - opos;
    if (epos < 0)
	epos = 0;
    lpos = epos / 3;
    if (lpos > Tabstop) lpos = Tabstop;
    rpos = epos * 2 / 3;
    if (epos - rpos > Tabstop) rpos = epos - Tabstop;
    env.offset = 0;

    if (def_str) {
	env.strBuf = Strnew_charp(def_str);
	env.CLen = env.CPos = setStrType(&env);
    } else {
	env.strBuf = Strnew();
	env.CLen = env.CPos = 0;
    }

    env.i_cont = TRUE;
    env.i_broken = FALSE;
    env.i_quote = FALSE;
    env.cm_next = FALSE;
    env.cm_disp_next = -1;
    env.need_redraw = FALSE;
    do {
	x = calcPosition(env.strBuf->ptr, env.strProp, env.CLen, env.CPos, 0, CP_FORCE);
	if (x - rpos > env.offset) {
	    y = calcPosition(env.strBuf->ptr, env.strProp, env.CLen, env.CLen, 0, CP_AUTO);
	    if (y - epos > x - rpos)
		env.offset = x - rpos;
	    else if (y - epos > 0)
		env.offset = y - epos;
	} else if (x - lpos < env.offset) {
	    if (x - lpos > 0)
		env.offset = x - lpos;
	    else
		env.offset = 0;
	}
	move(INPUTLINE, 0);
	addstr(prompt);
	if (env.is_passwd)
	    addPasswd(env.strBuf->ptr, env.strProp, env.CLen, env.offset, COLS - opos);
	else
	    addStr(env.strBuf->ptr, env.strProp, env.CLen, env.offset, COLS - opos);
	clrtoeolx();
	move(INPUTLINE, opos + x - env.offset);
	refresh();
#ifdef USE_IMAGE
	if (activeImage)
	    drawImage();
#endif

    next_char:
	env.prev_stroke = env.nstroke = 0;
#ifdef MANY_CHARSET
	if (env.i_wchar)
	    switch (c = tty_getwc()) {
	    case mb_notchar_enc_invalid:
		env.i_wchar = FALSE;
		process_getch_mode(GETCH_MODE_SKIP);
		break;
	    default:
		goto wc_found;
	    }
	c = (unsigned char)getch_internal(0);
    wc_found:
#else
	c = getch();
#endif
#ifdef __EMX__
	if (c == 0) {
	    if (!(c = getcntrl()))
		goto next_char;
	}
#endif
	env.cm_clear = TRUE;
 	env.cm_disp_clear = TRUE;
#ifdef JP_CHARSET
	if (mode == PC_KANJI1) {
	    mode = PC_KANJI2;
	    if (env.CLen >= STR_LEN)
		goto next_char;
	    Strcat_char(tmp, (c | (DisplayCode == CODE_SJIS ? 0 : 0x80)));
	    tmp = conv_str(tmp,
			   (DisplayCode == CODE_SJIS ? CODE_SJIS : CODE_EUC), InnerCode);
	    ins_kanji(tmp, &env);
	    if (incrfunc)
		incrfunc(-1, env.strBuf, env.strProp);
	}
	else
#endif
	    if (!env.i_quote &&
		((CPL_ALWAYS(env.cm_mode) && (c == CTRL_I || c == ' ')) ||
		 ((env.cm_mode & CPL_ON) && (c == CTRL_I)))) {
#ifdef EMACS_LIKE_LINEEDIT
		if (emacs_like_lineedit && env.cm_next) {
		    _dcompl(&env);
		    env.need_redraw = TRUE;
		}
		else
#endif
		    {
			linein_compl(c, &env);
			env.cm_disp_next = -1;
		    }
#ifdef MANY_CHARSET
		process_getch_mode(GETCH_MODE_CLR);
		env.i_wchar = FALSE;
#endif
	    }
	    else if (!env.i_quote && env.CLen == env.CPos &&
		     env.cm_mode & CPL_ON && c == CTRL_D) {
#ifdef EMACS_LIKE_LINEEDIT
		if (!emacs_like_lineedit) {
#endif
		    _dcompl(&env);
		    env.need_redraw = TRUE;
#ifdef EMACS_LIKE_LINEEDIT
		}
#endif
#ifdef MANY_CHARSET
		process_getch_mode(GETCH_MODE_CLR);
		env.i_wchar = FALSE;
#endif
	    }
	    else if (!env.i_quote && c == DEL_CODE) {
		linein_bs(c, &env);
		env.cm_next = FALSE;
		env.cm_disp_next = -1;
#ifdef MANY_CHARSET
		process_getch_mode(GETCH_MODE_CLR);
		env.i_wchar = FALSE;
#endif
	    }
	    else if (!env.i_quote && c < 0x20) {	/* Control code */
		if (incrfunc == NULL || (c = incrfunc((int)c, env.strBuf, env.strProp)) < 0x20) {
		    call_edit_func(c, insertself, &env);
#ifdef MANY_CHARSET
		    if (env.i_wchar)
			goto next_char;
#endif
		}
		if (incrfunc)
		    incrfunc(-1, env.strBuf, env.strProp);
		if (env.cm_clear)
		    env.cm_next = FALSE;
		if (env.cm_disp_clear)
		    env.cm_disp_next = -1;
	    }
#ifdef JP_CHARSET
	    else if (DisplayCode == CODE_SJIS && 0xa0 <= c && c <= 0xdf) {
		env.i_quote = FALSE;
		env.cm_next = FALSE;
		if (env.CLen >= STR_LEN)
		    goto next_char;
		Strclear(tmp);
		Strcat_char(tmp, c);
		tmp = conv_str(tmp, DisplayCode, InnerCode);
		ins_kanji(tmp, &env);
		if (incrfunc)
		    incrfunc(-1, env.strBuf, env.strProp);
	    }
	    else if ((c & 0x80) || env.in_kanji) {	/* Kanji 1 */
		env.i_quote = FALSE;
		env.cm_next = FALSE;
		env.cm_disp_next = -1;
		if (env.CLen >= STR_LEN - 1)
		    goto next_char;
		Strclear(tmp);
		Strcat_char(tmp, (c | 0x80));
		mode = PC_KANJI1;
		goto next_char;
	    }
#endif				/* JP_CHARSET */
#ifdef MANY_CHARSET
	    else if (!env.i_wchar) {
		process_getch_mode(GETCH_MODE_RST);
		env.i_wchar = TRUE;
		goto next_char;
	    }
#endif
	    else {
#ifdef MANY_CHARSET
		char tmp[MB_MBC_LEN_MAX];
		size_t cn;
#endif
		env.i_quote = FALSE;
		env.cm_next = FALSE;
		env.cm_disp_next = -1;
#ifdef MANY_CHARSET
		env.i_wchar = FALSE;
		cn = mb_wchar_to_mbc(c, tmp);
		if (STR_LEN - env.CLen < cn)
		    goto next_char;
		insMBC(cn, &env);
		bcopy(tmp, &env.strBuf->ptr[env.CPos], cn);
		memset(&env.strProp[env.CPos], (!env.is_passwd && !(c & ~0xFF) && IS_CNTRL(c)) ? PC_CTRL : PC_ASCII, cn);
		env.CPos += cn;
#else
		if (env.CLen >= STR_LEN)
		    goto next_char;
		insC(&env);
		env.strBuf->ptr[env.CPos] = c;
		if (!env.is_passwd && IS_CNTRL(c))
		    env.strProp[env.CPos] = PC_CTRL;
		else
		    env.strProp[env.CPos] = PC_ASCII;
		env.CPos++;
#endif
		mode = PC_ASCII;
		if (incrfunc)
		    incrfunc(-1, env.strBuf, env.strProp);
	    }
	if (env.CLen && (flag & IN_CHAR))
	    break;
    } while (env.i_cont);

    if (env.need_redraw && Currentbuf) {
	next_dcompl_top = -1;
	next_dcompl_env = NULL;
	displayCurrentView(NULL);
    }

    move(INPUTLINE, 0);
    clrtoeol();
    refresh();

    if (env.i_broken) {
	p = NULL;
	goto end;
    }

    p = env.strBuf->ptr;
    if (flag & (IN_FILENAME | IN_COMMAND)) {
	SKIP_BLANKS(p);
    }
    if (env.use_hist && !(flag & IN_URL)) {
	char *q;
	if (!(q = lastHist(hist)) || strcmp(q, p))
	    pushHist(hist, p);
    }
end:
    move(old_y, old_x);

    if (!oldbusy && inputLineQueue.n)
	processInputLineQueue();

    inputLineBusy = oldbusy;

    if (!p)
	return p;
    else if (flag & IN_FILENAME)
	return expandName(p);
    else
	return allocStr(p, -1);
}

#ifdef __EMX__
static int
getcntrl(void)
{
    switch (
#ifdef MANY_CHARSET
	    getch_internal(0)
#else
	    getch()
#endif
	    ) {
    case K_DEL:
	return CTRL_D;
    case K_LEFT:
	retrun CTRL_B;
    case K_RIGHT:
	return CTRL_F;
    case K_UP:
	return CTRL_P;
    case K_DOWN:
	return CTRL_N;
    case K_HOME:
    case K_CTRL_LEFT:
	return CTRL_A;
    case K_END:
    case K_CTRL_RIGHT:
	return CTRL_E;
    case K_CTRL_HOME:
	return CTRL_U;
    case K_CTRL_END:
	return CTRL_K;
    }
#ifdef MANY_CHARSET
    process_getch_mode(GETCH_MODE_CLR);
#endif
    return 0;
}
#endif

static void
addPasswd(char *p, Lineprop *pr, int len, int offset, int limit)
{
    int rcol = 0, ncol;

    ncol = calcPosition(p, pr, len, len, 0, CP_AUTO);
    if (ncol> offset + limit)
	ncol = offset + limit;
    if (offset) {
	addChar('{', 0);
	rcol = offset + 1;
    }
    for (; rcol < ncol; rcol++)
	addChar('*', 0);
}

static void
addStr(char *p, Lineprop *pr, int len, int offset, int limit)
{
    int i = 0, rcol = 0, ncol, delta = 1;
#ifdef MANY_CHARSET
    size_t b, e;
    mb_wchar_t wc;
#endif

    if (offset) {
	for (i = 0; i < len; i++) {
	    if (calcPosition(p, pr, len, i, 0, CP_AUTO) > offset)
		break;
	}
	if (i >= len)
	    return;
#ifdef MANY_CHARSET
	b = i;
	e = len;
	mb_mem_to_wchar(p, &b, &e);
	if (b < i)
	    i = e;
#endif
#ifdef JP_CHARSET
	while (pr[i] == PC_KANJI2)
	    i++;
#endif
	addChar('{', 0);
	rcol = offset + 1;
	ncol = calcPosition(p, pr, len, i, 0, CP_AUTO);
	for (; rcol < ncol; rcol++)
	    addChar(' ', 0);
    }
    for (; i < len; i += delta) {
#ifdef JP_CHARSET
	if (CharType(pr[i]) == PC_KANJI1)
	    delta = 2;
	else
	    delta = 1;
#endif
#ifdef MANY_CHARSET
	if ((delta = mb_mem_to_wchar_internal(&p[i], len - i, wc)) < 0)
	    delta = 1;
#endif
	ncol = calcPosition(p, pr, len, i + delta, 0, CP_AUTO);
	if (ncol - offset > limit)
	    break;
	if (p[i] == '\t') {
	    for (; rcol < ncol; rcol++)
		addChar(' ', 0);
	    continue;
#ifdef JP_CHARSET
	} else if (delta == 2) {
	    addChar(p[i], pr[i]);
	    addChar(p[i+1], pr[i+1]);
#endif
	} else
#ifdef MANY_CHARSET
	    addWChar(wc, ttyfix_wchar_width(wc), pr[i]);
#else
	addChar(p[i], pr[i]);
#endif
	rcol = ncol;
    }
}

#ifdef JP_CHARSET
static void
ins_kanji(Str tmp, InputLineEnv *env)
{
    if (tmp->length != 2)
	return;
    insC(env);
    env->strBuf->ptr[env->CPos] = tmp->ptr[0];
    env->strProp[env->CPos] = PC_KANJI1;
    (env->CPos)++;
    insC(env);
    env->strBuf->ptr[env->CPos] = tmp->ptr[1];
    env->strProp[env->CPos] = PC_KANJI2;
    (env->CPos)++;
}
#endif

void
linein_esc(int key, InputLineEnv *env)
{
    char c;
#ifdef JP_CHARSET
    char c2;
#endif

    switch (c =
#ifdef MANY_CHARSET
	    getch_internal(0)
#else
	    getch()
#endif
	    ) {
    case '[':
    case 'O':
	c =
#ifdef MANY_CHARSET
	    getch_internal(0)
#else
	    getch()
#endif
	    ;
#ifdef __CYGWIN__
	if ((is_xterm & (NEED_CYGWIN_ON | NEED_CYGWIN_OFF)) == NEED_CYGWIN_ON &&
	    c == 'M') {
	    getch();
	    getch();
	    getch();
	    break;
	}
#endif
	call_edit_func_nostroke(K_ESCB | c, NULL, env);
	break;
    case CTRL_I:
    case ' ':
#ifdef EMACS_LIKE_LINEEDIT
	if (emacs_like_lineedit) {
	    _rdcompl(env);
	    env->cm_clear = FALSE;
	    env->need_redraw = TRUE;
	}
	else
#endif
	    _rcompl(env);
	break;
    case CTRL_D:
#ifdef EMACS_LIKE_LINEEDIT
	if (!emacs_like_lineedit)
#endif
	    _rdcompl(env);
	env->need_redraw = TRUE;
	break;
#ifdef EMACS_LIKE_LINEEDIT
    case 'f':
	if (!emacs_like_lineedit)
	    goto not_emacs_like;
	linein_mvRw(c, env);
	break;
    case 'b':
	if (!emacs_like_lineedit)
	    goto not_emacs_like;
	linein_mvLw(c, env);
	break;
    case CTRL_H:
	if (!emacs_like_lineedit)
	    goto not_emacs_like;
	linein_bsw(c, env);
	break;
#endif
#ifdef JP_CHARSET
    case '$':
	/* ISO-2022-jp characters */
	c2 = getch();
	env->in_kanji = TRUE;
	break;
    case '(':
	/* ISO-2022-jp characters */
	c2 = getch();
	env->in_kanji = FALSE;
	break;
#endif
    default:
#ifdef EMACS_LIKE_LINEEDIT
	    not_emacs_like:
#endif
	call_edit_func_nostroke(K_ESC | c, NULL, env);
	break;
    }
}

#ifdef MANY_CHARSET
static void
insMBC(size_t cn, InputLineEnv *env)
{
    char tmp[MB_MBC_LEN_MAX + 1];

    memset(tmp, ' ', cn);
    tmp[cn] = '\0';
    Strinsert_charp(env->strBuf, env->CPos, tmp);
    env->CLen = env->strBuf->length;
    memmove(&env->strProp[env->CPos + cn], &env->strProp[env->CPos], sizeof(Lineprop) * cn);
}
#endif

static void
insC(InputLineEnv *env)
{
    int i;

    Strinsert_char(env->strBuf, env->CPos, ' ');
    env->CLen = env->strBuf->length;
    for (i = env->CLen; i > env->CPos; i--) {
	env->strProp[i] = env->strProp[i - 1];
    }
}

void
linein_delC(int key, InputLineEnv *env)
{
    int i = env->CPos;
    int delta = 1;
#ifdef MANY_CHARSET
    mb_wchar_t wc;
#endif

    if (env->CLen == env->CPos)
	return;
#ifdef MANY_CHARSET
    if ((delta = mb_mem_to_wchar_internal(&env->strBuf->ptr[env->CPos], env->CLen - env->CPos, wc)) < 0)
	delta = 1;
#endif
#ifdef JP_CHARSET
    if (env->strProp[i] == PC_KANJI1)
	delta = 2;
#endif				/* JP_CHARSET */
    for (i = env->CPos; i < env->CLen; i++) {
	env->strProp[i] = env->strProp[i + delta];
    }
    Strdelete(env->strBuf, env->CPos, delta);
    env->CLen -= delta;
}

void
linein_mvL(int key, InputLineEnv *env)
{
#ifdef MANY_CHARSET
    if (env->CPos > 0) {
	size_t b = env->CPos - 1;
	size_t e = env->CLen;

	mb_mem_to_wchar(env->strBuf->ptr, &b, &e);
	env->CPos = b;
    }
#else
    if (env->CPos > 0)
	(env->CPos)--;
#ifdef JP_CHARSET
    if (env->strProp[env->CPos] == PC_KANJI2)
	(env->CPos)--;
#endif				/* JP_CHARSET */
#endif
}

int
find_Lw(InputLineEnv *env)
{
    if (env->CPos > 0) {
#ifdef MANY_CHARSET
	size_t b, e;
	mb_wchar_t first, cur;
#else
	int b, first, cur;
#endif
	int word;

	word = env->CPos - 1;
#ifdef MANY_CHARSET
	e = env->CPos;
	first = mb_mem_to_wchar(env->strBuf->ptr, &word, &e);
#else
#ifdef JP_CHARSET
	if (env->strProp[word] == PC_KANJI2 && word > 0)
	    --word;
#endif
	first = mctowc(&env->strBuf->ptr[word], env->strProp[word]);
#endif

	while (word > 0) {
	    b = word - 1;
#ifdef MANY_CHARSET
	    e = word;
	    cur = mb_mem_to_wchar(env->strBuf->ptr, &b, &e);
#else
#ifdef JP_CHARSET
	    if (env->strProp[b] == PC_KANJI2 && b > 0)
		--b;
#endif
	    cur = mctowc(&env->strBuf->ptr[b], env->strProp[b]);
#endif

	    if (!env->same_type_char_p(cur, first))
		break;

	    word = b;
	}

	return word;
    }
    else
	return env->CPos;
}

void
linein_mvLw(int key, InputLineEnv *env)
{
    env->CPos = find_Lw(env);
}

static int
find_Rw(InputLineEnv *env)
{
    if (env->CPos < env->CLen) {
#ifdef MANY_CHARSET
	size_t b, e;
	mb_wchar_t first, cur;
#else
	int e, first, cur;
#endif
	int word;

#ifdef MANY_CHARSET
	b = env->CPos;
	e = env->CLen;
	first = mb_mem_to_wchar(env->strBuf->ptr, &b, &e);
	word = e;
#else
	word = env->CPos + get_mclen(env->strProp[env->CPos]);
	first = mctowc(&env->strBuf->ptr[env->CPos], env->strProp[env->CPos]);
#endif

	while (word < env->CLen) {
#ifdef MANY_CHARSET
	    b = word;
	    e = env->CLen;
	    cur = mb_mem_to_wchar(env->strBuf->ptr, &b, &e);
#else
	    e = word + get_mclen(env->strProp[word]);
	    cur = mctowc(&env->strBuf->ptr[word], env->strProp[word]);
#endif

	    if (!env->same_type_char_p(cur, first))
		break;

	    word = e;
	}

	return word;
    }
    else
	return env->CPos;
}

void
linein_mvRw(int key, InputLineEnv *env)
{
    env->CPos = find_Rw(env);
}

void
linein_mvR(int key, InputLineEnv *env)
{
#ifdef MANY_CHARSET
    if (env->CPos < env->CLen) {
	size_t b = env->CPos;
	size_t e = env->CLen;

	mb_mem_to_wchar(env->strBuf->ptr, &b, &e);
	env->CPos = e;
    }
#else
    if (env->CPos < env->CLen)
	env->CPos++;
#ifdef JP_CHARSET
    if (env->strProp[env->CPos] == PC_KANJI2)
	env->CPos++;
#endif				/* JP_CHARSET */
#endif
}

void
linein_bs(int key, InputLineEnv *env)
{
    if (env->CPos > 0) {
	linein_mvL(key, env);
	linein_delC(key, env);
    }
}

void
linein_bsw(int key, InputLineEnv *env)
{
    int word;

    if ((word = find_Lw(env)) < env->CPos) {
	memmove(&env->strProp[word], &env->strProp[env->CPos], sizeof(Lineprop) * (env->CPos - word));
	Strdelete(env->strBuf, word, env->CPos - word);
	env->CLen -= env->CPos - word;
	env->CPos = word;
    }
}

void
linein_delRw(int key, InputLineEnv *env)
{
    int word;

    if ((word = find_Rw(env)) > env->CPos) {
	memmove(&env->strProp[env->CPos], &env->strProp[word], sizeof(Lineprop) * (word - env->CPos));
	Strdelete(env->strBuf, env->CPos, word - env->CPos);
	env->CLen -= word - env->CPos;
    }
}

void
linein_enter(int key, InputLineEnv *env)
{
    env->i_cont = FALSE;
}

void
insertself(char c, InputLineEnv *env)
{
    if (env->CLen >= STR_LEN)
	return;
    insC(env);
    env->strBuf->ptr[env->CPos] = c;
    env->strProp[env->CPos] = env->is_passwd ? PC_ASCII : PC_CTRL;
    env->CPos++;
}

void
linein_quo(int key, InputLineEnv *env)
{
    env->i_quote = TRUE;
}

void
linein_mvB(int key, InputLineEnv *env)
{
    env->CPos = 0;
}

void
linein_mvE(int key, InputLineEnv *env)
{
    env->CPos = env->CLen;
}

void
linein_killn(int key, InputLineEnv *env)
{
    env->CLen = env->CPos;
    Strtruncate(env->strBuf, env->CLen);
}

void
linein_killb(int key, InputLineEnv *env)
{
    while (env->CPos > 0)
	linein_bs(key, env);
}

void
linein_inbrk(int key, InputLineEnv *env)
{
    env->i_cont = FALSE;
    env->i_broken = TRUE;
}

void
linein_compl(int key, InputLineEnv *env)
{
    next_compl(1, env);
}

static void
_rcompl(InputLineEnv *env)
{
    next_compl(-1, env);
}

void
linein_tcompl(int key, InputLineEnv *env)
{
    if (!(env->cm_mode & CPL_IMMUTABLE) && env->do_complete)
	env->cm_mode ^= CPL_ON;
}

static void
next_compl(int next, InputLineEnv *env)
{
    int status;
    int b, a;
    Str buf;
    Str s;

    if (!(env->cm_mode & CPL_ON) || !env->do_complete)
	return;
    env->cm_clear = FALSE;
    if (!env->cm_next) {
	if (env->cm_mode & CPL_WORDS) {
	    for (b = env->CPos - 1; b >= 0; b--) {
		if ((env->strBuf->ptr[b] == ' ' || env->strBuf->ptr[b] == CTRL_I) &&
		    !((b > 0) && env->strBuf->ptr[b - 1] == '\\'))
		    break;
	    }
	    b++;
	}
	else 
	    b = 0;
	a = env->CPos;
	env->CBeforeBuf = Strsubstr(env->strBuf, 0, b);
	buf = Strsubstr(env->strBuf, b, a - b);
	env->CAfterBuf = Strsubstr(env->strBuf, a, env->strBuf->length - a);
	s = env->do_complete(buf, &status, next, env);
    }
    else {
	s = env->do_complete(env->strBuf, &status, next, env);
    }
    if (next == 0)
	return;

    if (status != CPL_OK && status != CPL_MENU)
	bell();
    if (status == CPL_FAIL)
	return;

    env->strBuf = Strnew_m_charp(env->CBeforeBuf->ptr, s->ptr, env->CAfterBuf->ptr, NULL);
    env->CLen = setStrType(env);
    env->CPos = env->CBeforeBuf->length + s->length;
    if (env->CPos > env->CLen)
	env->CPos = env->CLen;
}

static void
_dcompl(InputLineEnv *env)
{
    next_dcompl(1, env);
}

static void
_rdcompl(InputLineEnv *env)
{
    next_dcompl(-1, env);
}

void
next_dcompl(int next, InputLineEnv *env)
{
    int i, j, n, y;
    int comment, nline;

    if (!(env->cm_mode & CPL_ON) || !env->do_complete)
	return;
    env->cm_disp_clear = FALSE;
    if (Currentbuf) {
	next_dcompl_top = -1;
	next_dcompl_env = NULL;
	displayCurrentView(NULL);
    }

    if (LASTLINE >= 3) {
	comment = TRUE;
	nline = LASTLINE - 2;
    } else if (LASTLINE) {
	comment = FALSE;
	nline = LASTLINE;
    } else {
	return;
    }
    if (env->cm_disp_next >= 0) {
	if (next == 1) {
	    env->cm_disp_next += env->dcompl_env.col * nline;
	    if (env->cm_disp_next >= env->NCandBuf)
		env->cm_disp_next = 0;
	}
	else if (next == -1) {
	    env->cm_disp_next -= env->dcompl_env.col * nline;
	    if (env->cm_disp_next < 0)
		env->cm_disp_next = 0;
	}
	env->dcompl_env.row = (env->NCandBuf - env->cm_disp_next + env->dcompl_env.col - 1) / env->dcompl_env.col;
	goto disp_next;
    }

    env->cm_next = FALSE;
    next_compl(0, env);
    if (env->NCandBuf == 0)
	return;
    env->cm_disp_next = 0;

    env->dcompl_env.len = 0;
    for (i = 0; i < env->NCandBuf; i++) {
#ifdef MANY_CHARSET
	n = ttyfix_width(env->CandBuf[i]) + 3;
#else
	n = strlen(env->CandBuf[i]) + 3;
#endif
	if (env->dcompl_env.len < n)
	    env->dcompl_env.len = n;
    }
    env->dcompl_env.col = COLS / env->dcompl_env.len;
    if (env->dcompl_env.col == 0)
	env->dcompl_env.col = 1;
    env->dcompl_env.row = (env->NCandBuf + env->dcompl_env.col - 1) / env->dcompl_env.col;

disp_next:
    if (comment) {
	if (env->dcompl_env.row > nline) {
	    env->dcompl_env.row = nline;
	    y = 0;
	} else
	    y = nline - env->dcompl_env.row + 1;
    } else {
	if (env->dcompl_env.row >= nline) {
	    env->dcompl_env.row = nline;
	    y = 0;
	} else
	    y = nline - env->dcompl_env.row - 1;
    }
    if (y) {
	next_dcompl_top = y - 1;
	move(y - 1, 0);
	clrtoeolx();
    }
    else
	next_dcompl_top = y;
    next_dcompl_next = next;
    next_dcompl_env = env;
    if (comment) {
	move(y, 0);
	clrtoeolx();
	bold();
	addstr("----- Completion list -----");
	boldend();
	y++;
    }
    for (i = 0; i < env->dcompl_env.row; i++) {
	for (j = 0; j < env->dcompl_env.col; j++) {
	    n = env->cm_disp_next + j * env->dcompl_env.row + i;
	    if (n >= env->NCandBuf)
		break;
	    move(y, j * env->dcompl_env.len);
	    clrtoeolx();
	    addstr(env->CandBuf[n]);
	}
	y++;
    }
    if (comment && y == LASTLINE - 1) {
	move(y, 0);
	clrtoeolx();
	bold();
#ifdef EMACS_LIKE_LINEEDIT
	if (emacs_like_lineedit)
	    addstr("----- Press TAB to continue -----");
	else
#endif
	    addstr("----- Press CTRL-D to continue -----");
	boldend();
    }
}

static Str
escape_spaces(Str s)
{
    Str tmp = NULL;
    char *p;

    if (s == NULL)
	return s;
    for (p = s->ptr; *p; p++) {
	if (*p == ' ' || *p == CTRL_I) {
	    if (tmp == NULL)
		tmp = Strnew_charp_n(s->ptr, (int)(p - s->ptr));
	    Strcat_char(tmp, '\\');
	}
	if (tmp)
	    Strcat_char(tmp, *p);
    }
    if (tmp)
	return tmp;
    return s;
}

Str
unescape_spaces(Str s)
{
    Str tmp = NULL;
    char *p;

    if (s == NULL)
	return s;
    for (p = s->ptr; *p; p++) {
	if (*p == '\\' && (*(p + 1) == ' ' || *(p + 1) == CTRL_I)) {
	    if (tmp == NULL)
		tmp = Strnew_charp_n(s->ptr, (int)(p - s->ptr));
	}
	else {
	    if (tmp)
		Strcat_char(tmp, *p);
	}
    }
    if (tmp)
	return tmp;
    return s;
}

static Str
do_filename_complete(Str ifn, int *status, int next, InputLineEnv *env)
{
    ComplFileNameEnv *cfn;
    int fl, i;
    char *fn, *p;
    DIR *d;
    Directory *dir;
    struct stat st;

    cfn = env->ComplPrivate;

    if (!env->cm_next) {
	int url_part = 0;

	env->NCandBuf = 0;
	ifn = Str_conv_to_system(ifn);
	if (env->cm_mode & CPL_COMMAND)
	    ifn = unescape_spaces(ifn);
	env->CompleteBuf = Strdup(ifn);
	while (Strlastchar(env->CompleteBuf) != '/' &&
	       env->CompleteBuf->length > 0)
	    Strshrink(env->CompleteBuf, 1);
	cfn->CDirBuf = Strdup(env->CompleteBuf);
	if (env->cm_mode & CPL_URL) {
	    if (strncmp(env->CompleteBuf->ptr, "file://localhost/", sizeof("file://localhost/") - 1) == 0)
		url_part = sizeof("file://localhost/") - sizeof("/");
	    else if (strncmp(env->CompleteBuf->ptr, "file:///", sizeof("file:///") - 1) == 0)
		url_part = sizeof("file:///") - sizeof("/");
	    else if (strncmp(env->CompleteBuf->ptr, "file:/", sizeof("file:/") - 1) == 0 &&
		     env->CompleteBuf->ptr[sizeof("file:/") - 1] != '/')
		url_part = sizeof("file:/") - sizeof("/");
	    else
		goto not_a_filename;
	    Strdelete(env->CompleteBuf, 0, url_part);
	}
	if (env->CompleteBuf->length == 0) {
	    Strcat_char(env->CompleteBuf, '.');
	}
	if (Strlastchar(env->CompleteBuf) == '/' && env->CompleteBuf->length > 1) {
	    Strshrink(env->CompleteBuf, 1);
	}
	if (!(d = opendir(expandName(env->CompleteBuf->ptr))))
	    goto not_a_filename;
	fn = lastFileName(ifn->ptr);
	fl = strlen(fn);
	cfn->CFileName = Strnew();
	for (;;) {
	    dir = readdir(d);
	    if (dir == NULL)
		break;
	    if (fl == 0 && (!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, "..")))
		continue;
	    if (!strncmp(dir->d_name, fn, fl)) {	/* match */
		Str fn, path;

		fn = Str_conv_from_system(Strnew_charp(dir->d_name));
		path = Strdup(cfn->CDirBuf);
		if (path->length == 0)
		    Strcat(path, fn);
		else {
		    if (Strlastchar(path) != '/')
			Strcat_char(path, '/');
		    Strcat(path, fn);
		}
		if (*status != CPL_AMBIG) {
		    p = path->ptr;
		    if (url_part)
			p += url_part;
		    if (stat(expandName(p), &st) != -1 && S_ISDIR(st.st_mode))
			Strcat_char(fn, '/');
		}
		(env->NCandBuf)++;
		env->CandBuf = New_Reuse(char *, env->CandBuf, env->NCandBuf);
		env->CandBuf[env->NCandBuf - 1] = NewAtom_N(char, fn->length + 1);
		strcpy(env->CandBuf[env->NCandBuf - 1], fn->ptr);
		if (env->NCandBuf == 1) {
		    cfn->CFileName = Strdup(fn);
		}
		else {
		    for (i = 0; i < fn->length && i < cfn->CFileName->length && cfn->CFileName->ptr[i] == fn->ptr[i]; i++);
		    Strtruncate(cfn->CFileName, i);
		}
	    }
	}
	closedir(d);
	if (env->NCandBuf == 0) {
	not_a_filename:
	    if (env->CurrentHist) {
		Str d;

		d = do_hist_complete(ifn, status, next, env);

		if (*status != CPL_FAIL)
		    return d;
	    }

	    env->CompleteBuf = Strdup(ifn);
	    *status = CPL_FAIL;
	    if (env->cm_mode & CPL_COMMAND)
		env->CompleteBuf = escape_spaces(env->CompleteBuf);
	    return Strdup(env->CompleteBuf);
	}
	qsort(env->CandBuf, env->NCandBuf, sizeof(env->CandBuf[0]), strCmp);
	env->NCandOffset = 0;
	if (env->NCandBuf >= 2) {
	    env->cm_next = TRUE;
	    *status = CPL_AMBIG;
	}
	else {
	    *status = CPL_OK;
	}
    }
    else {
	cfn->CFileName = Strnew_charp(env->CandBuf[env->NCandOffset]);
	env->NCandOffset = (env->NCandOffset + next + env->NCandBuf) % env->NCandBuf;
	*status = CPL_MENU;
    }
    env->CompleteBuf = Strdup(cfn->CDirBuf);
    if (env->CompleteBuf->length == 0)
	Strcat(env->CompleteBuf, cfn->CFileName);
    else if (Strlastchar(env->CompleteBuf) == '/')
	Strcat(env->CompleteBuf, cfn->CFileName);
    else {
	Strcat_char(env->CompleteBuf, '/');
	Strcat(env->CompleteBuf, cfn->CFileName);
    }
    if (*status != CPL_AMBIG && Strlastchar(env->CompleteBuf) != '/') {
	p = env->CompleteBuf->ptr;
	if (env->cm_mode & CPL_URL) {
	    if (strncmp(p, "file://localhost/", sizeof("file://localhost/") - 1) == 0)
		p += sizeof("file://localhost/") - sizeof("/");
	    else if (strncmp(p, "file:///", sizeof("file:///") - 1) == 0)
		p += sizeof("file:///") - sizeof("/");
	    else if (strncmp(p, "file:/", sizeof("file:/") - 1) == 0 && p[sizeof("file:/") - 1] != '/')
		p += sizeof("file:/") - sizeof("/");
	}
	if (stat(expandName(p), &st) != -1 && S_ISDIR(st.st_mode))
	    Strcat_char(env->CompleteBuf, '/');
    }
    if (env->cm_mode & CPL_COMMAND)
	env->CompleteBuf = escape_spaces(env->CompleteBuf);
    return Strdup(env->CompleteBuf);
}

static Str
do_hist_complete(Str ifn, int *status, int next, InputLineEnv *env)
{
    int fl, i;

    if (!env->cm_next) {
	Hist *hist;

	env->NCandBuf = 0;

	if ((hist = env->CurrentHist) && hist->list) {
	    HistItem *hi;
	    Str s = NULL;

	    for (hi = hist->current ? hist->current : hist->list->last ; hi ; hi = hi->prev) {
		if (!strncmp(hi->ptr, ifn->ptr, ifn->length)) {
		    char *p;

		    p = hi->ptr;
		    fl = strlen(p);
		    (env->NCandBuf)++;
		    env->CandBuf = New_Reuse(char *, env->CandBuf, env->NCandBuf);
		    env->CandBuf[env->NCandBuf - 1] = NewAtom_N(char, fl + 1);
		    strcpy(env->CandBuf[env->NCandBuf - 1], p);

		    if (!s)
			s = Strnew_charp_n(p, fl);
		    else {
			for (i = ifn->length ; i < fl && i < s->length && p[i] == s->ptr[i] ; ++i)
			    ;

			Strtruncate(s, i);
		    }
		}
	    }

	    env->CompleteBuf = s;
	}

	if (env->NCandBuf == 0) {
	    env->CompleteBuf = Strdup(ifn);
	    *status = CPL_FAIL;
	    return Strdup(env->CompleteBuf);
	}

	qsort(env->CandBuf, env->NCandBuf, sizeof(env->CandBuf[0]), strCmp);
	env->NCandOffset = 0;

	if (env->NCandBuf >= 2) {
	    env->cm_next = TRUE;
	    *status = CPL_AMBIG;
	}
	else {
	    *status = CPL_OK;
	}
    }
    else {
	env->NCandOffset = (env->NCandOffset + next + env->NCandBuf) % env->NCandBuf;
	*status = CPL_MENU;
    }

    return Strdup(env->CompleteBuf);
}

struct do_tab_complete_arg {
    Str ifn;
    Str s;
    InputLineEnv *env;
    btri_key_cursor_t key;
};

static void
do_ci_tab_complete_loop(btri_string_tab_t *node, struct do_tab_complete_arg *p)
{
    if (node) {
	int i, j, n;
	char *q;

	for (i = 0 ; i < 2 ; ++i)
	    switch (node->type[i]) {
	    case bt_failure:
		break;
	    case bt_node:
		do_ci_tab_complete_loop(node->value[i], p);
		break;
	    default:
		if (node->key[i].n >= p->key.n &&
		    !strncasecmp(node->key[i].base, p->ifn->ptr, p->ifn->length)) {
		    (p->env->NCandBuf)++;
		    p->env->CandBuf = New_Reuse(char *, p->env->CandBuf, p->env->NCandBuf);
		    n = node->key[i].n / CHAR_BIT;
		    p->env->CandBuf[p->env->NCandBuf - 1] = q = allocStr(node->key[i].base, n);

		    if (!p->s)
			p->s = Strnew_charp_n(q, n);
		    else {
			for (j = p->ifn->length ; j < n && j < p->s->length && q[j] == p->s->ptr[j] ; ++j)
			    ;

			Strtruncate(p->s, j);
		    }
		}

		break;
	    }
    }
}

static Str
do_ci_tab_complete(Str ifn, int *status, int next, InputLineEnv *env)
{
    if (!env->cm_next) {
	env->NCandBuf = 0;

	if (env->ComplPrivate) {
	    struct do_tab_complete_arg e;

	    e.ifn = ifn;
	    e.s = NULL;
	    e.env = env;
	    e.key.base = ifn->ptr;
	    e.key.n = ifn->length * CHAR_BIT;
	    do_ci_tab_complete_loop(env->ComplPrivate, &e);
	    env->CompleteBuf = e.s;
	}

	if (env->NCandBuf == 0) {
	    env->CompleteBuf = Strdup(ifn);
	    *status = CPL_FAIL;
	    return Strdup(env->CompleteBuf);
	}

	qsort(env->CandBuf, env->NCandBuf, sizeof(env->CandBuf[0]), strCmp);
	env->NCandOffset = 0;

	if (env->NCandBuf >= 2) {
	    env->cm_next = TRUE;
	    *status = CPL_AMBIG;
	}
	else {
	    *status = CPL_OK;
	}
    }
    else {
	env->NCandOffset = (env->NCandOffset + next + env->NCandBuf) % env->NCandBuf;
	*status = CPL_MENU;
    }

    return Strdup(env->CompleteBuf);
}

void
linein_prev(int key, InputLineEnv *env)
{
    Hist *hist = env->CurrentHist;
    char *p;

    if (! env->use_hist)
	return;
    if (env->strCurrentBuf) {
	p = prevHist(hist);
	if (p == NULL)
	    return;
    } else {
	p = lastHist(hist);
	if (p == NULL)
	    return;
	env->strCurrentBuf = env->strBuf;
    }
    env->strBuf = Strnew_charp(p);
    env->CLen = env->CPos = setStrType(env);
    env->offset = 0;
}

void
linein_next(int key, InputLineEnv *env)
{
    Hist *hist = env->CurrentHist;
    char *p;

    if (! env->use_hist)
	return;
    if (env->strCurrentBuf == NULL)
	return;
    p = nextHist(hist);
    if (p) {
	env->strBuf = Strnew_charp(p);
    } else {
	env->strBuf = env->strCurrentBuf;
	env->strCurrentBuf = NULL;
    }
    env->CLen = env->CPos = setStrType(env);
    env->offset = 0;
}

void
linein_copyregion(int key, InputLineEnv *env)
{
#ifdef USE_MARK
    char *p;

    if (Currentbuf && (p = GetMarkedRegion(Currentbuf, NULL, NULL, NULL, NULL)) && *p) {
	while (*p) {
	    insC(env);
	    env->strBuf->ptr[(env->CPos)++] = *p++;
	}

	env->CLen = setStrType(env);

	if (env->CPos > env->CLen)
	    env->CPos = env->CLen;
    }
#endif
    ;
}

void
linein_nextchar(int key, InputLineEnv *env)
{
    call_edit_func(
#ifdef MANY_CHARSET
		   getch_internal(0)
#else
		   getch()
#endif
		   , NULL, env);
}

static int
setStrType(InputLineEnv *env)
{
    Lineprop ctype;
#ifdef MANY_CHARSET
    mb_wchar_t wc;
#endif
    int i = 0, delta;
    char *s = env->strBuf->ptr;

    for (; *s != '\0' && i < STR_LEN; s += delta, i += delta) {
	ctype = get_mctype(s);
	if (env->is_passwd && ctype & PC_CTRL)
	    ctype = PC_ASCII;
#ifdef MANY_CHARSET
	if ((delta = mb_mem_to_wchar_internal(s, STR_LEN - i, wc)) == MB_MBC_LEN_MAX) {
	    memset(&env->strProp[i], ctype, MB_MBC_LEN_MAX);
	    continue;
	}
	delta = 1;
#else
	delta = get_mclen(ctype);
#ifdef JP_CHARSET
	if (ctype == PC_KANJI) {
	    env->strProp[i] = PC_KANJI1;
	    env->strProp[i+1] = PC_KANJI2;
	}
	else
#endif
#endif
	    env->strProp[i] = ctype;
    }
    return i;
}

/* Local Variables:    */
/* c-basic-offset: 4   */
/* tab-width: 8        */
/* End:                */
