/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999-2021 Free Software Foundation, Inc.
GNU Mailutils 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 3, or (at your option)
any later version.
GNU Mailutils 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 GNU Mailutils. If not, see . */
#include "mail.h"
#include
#include
#include
#ifdef WITH_READLINE
static char **ml_command_completion (char *cmd, int start, int end);
static char *ml_command_generator (const char *text, int state);
static void ml_sig_handler (int);
#else
# define ml_sig_handler(n)
#endif
static volatile int _interrupted;
static RETSIGTYPE
sig_handler (int signo)
{
switch (signo)
{
case SIGINT:
if (mailvar_is_true (mailvar_name_quit))
exit (0);
_interrupted++;
break;
#if defined (SIGWINCH)
case SIGWINCH:
util_do_command ("set screen=%d", util_getlines ());
util_do_command ("set columns=%d", util_getcols ());
page_invalidate (1);
break;
#endif
}
ml_sig_handler (signo);
#ifndef HAVE_SIGACTION
signal (signo, sig_handler);
#endif
}
void
ml_clear_interrupt (void)
{
_interrupted = 0;
}
int
ml_got_interrupt (void)
{
int rc = _interrupted;
_interrupted = 0;
return rc;
}
static int
ml_getc (FILE *stream)
{
unsigned char c;
while (1)
{
if (read (fileno (stream), &c, 1) == 1)
return c;
if (errno == EINTR)
{
if (_interrupted)
break;
/* keep going if we handled the signal */
}
else
break;
}
return EOF;
}
void
ml_readline_init (void)
{
if (!interactive)
return;
#ifdef WITH_READLINE
rl_readline_name = "mail";
rl_attempted_completion_function =
(rl_completion_func_t*) ml_command_completion;
rl_getc_function = ml_getc;
rl_catch_signals = 0;
#endif
#ifdef HAVE_SIGACTION
{
struct sigaction act;
act.sa_handler = sig_handler;
sigemptyset (&act.sa_mask);
act.sa_flags = 0;
sigaction (SIGINT, &act, NULL);
#if defined(SIGWINCH)
sigaction (SIGWINCH, &act, NULL);
#endif
}
#else
signal (SIGINT, sig_handler);
#if defined(SIGWINCH)
signal (SIGWINCH, sig_handler);
#endif
#endif
}
char *
ml_readline_internal (void)
{
char *buf = NULL;
size_t size = 0, n;
int rc;
rc = mu_stream_getline (mu_strin, &buf, &size, &n);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_stream_getline", NULL, rc);
return NULL;
}
if (_interrupted)
{
free (buf);
return NULL;
}
if (n == 0)
{
free (buf);
return NULL;
}
mu_rtrim_cset (buf, "\n");
return buf;
}
char *
ml_readline (const char *prompt)
{
if (interactive)
return readline (prompt);
return ml_readline_internal ();
}
char *
ml_readline_with_intr (const char *prompt)
{
char *str = ml_readline (prompt);
if (_interrupted)
mu_printf ("\n");
return str;
}
#ifdef WITH_READLINE
/* Readline-specific signal handling */
static void
ml_sig_handler (int sig)
{
switch (sig)
{
case SIGINT:
rl_free_line_state ();
break;
#if defined (SIGWINCH)
case SIGWINCH:
rl_resize_terminal ();
break;
#endif
}
rl_reset_after_signal ();
}
static char *insert_text;
static int
ml_insert_hook (void)
{
if (insert_text)
rl_insert_text (insert_text);
return 0;
}
int
ml_reread (const char *prompt, char **text)
{
char *s;
ml_clear_interrupt ();
insert_text = *text;
rl_startup_hook = ml_insert_hook;
s = readline ((char *)prompt);
if (!ml_got_interrupt ())
{
if (*text)
free (*text);
*text = s;
}
else
{
putc('\n', stdout);
}
rl_startup_hook = NULL;
return 0;
}
/*
* readline tab completion
*/
char **
ml_command_completion (char *cmd, int start, int end)
{
char **ret;
char *p;
struct mu_wordsplit ws;
for (p = rl_line_buffer; p < rl_line_buffer + start && mu_isblank (*p); p++)
;
if (mu_wordsplit_len (p, end, &ws, MU_WRDSF_DEFFLAGS))
{
mu_error (_("mu_wordsplit_len failed: %s"),
mu_wordsplit_strerror (&ws));
return NULL;
}
rl_completion_append_character = ' ';
if (ws.ws_wordc == 0 ||
(ws.ws_wordc == 1 && strlen (ws.ws_wordv[0]) <= end - start))
{
ret = rl_completion_matches (cmd, ml_command_generator);
rl_attempted_completion_over = 1;
}
else
{
const struct mail_command_entry *entry =
mail_find_command (ws.ws_wordv[0]);
if (entry && entry->command_completion)
{
int point = COMPL_DFL;
if (start == end)
point |= COMPL_WS;
if (mu_str_skip_class (p + end, MU_CTYPE_SPACE)[0] == 0)
point |= COMPL_LASTARG;
ret = entry->command_completion (ws.ws_wordc, ws.ws_wordv, point);
}
else
ret = NULL;
}
mu_wordsplit_free (&ws);
return ret;
}
/*
* more readline
*/
char *
ml_command_generator (const char *text, int state)
{
static int i, len;
const char *name;
const struct mail_command *cp;
if (!state)
{
i = 0;
len = strlen (text);
}
while ((cp = mail_command_name (i)))
{
name = cp->longname;
if (strlen (cp->shortname) > strlen (name))
name = cp->shortname;
i++;
if (strncmp (name, text, len) == 0)
return mu_strdup (name);
}
return NULL;
}
void
ml_set_completion_append_character (int c)
{
rl_completion_append_character = c;
}
void
ml_attempted_completion_over (void)
{
rl_attempted_completion_over = 1;
}
/* Concatenate results of two completions. Both A and B are expected to
be returned by rl_completion_matches, i.e. their first entry is the
longest common prefix of the remaining entries, which are sorted
lexicographically. Array B is treated as case-insensitive.
If either of the arrays is NULL, the other one is returned unchanged.
Otherwise, if A[0] begins with a lowercase letter, all items from B
will be converted to lowercase.
Both A and B (but not their elements) are freed prior to returning.
Note: This function assumes that no string from A is present in B and
vice versa.
*/
static char **
compl_concat (char **a, char **b)
{
size_t i, j, k, n = 0, an, bn;
char **ret;
int lwr = 0;
if (a)
{
lwr = mu_islower (a[0][0]);
for (an = 0; a[an]; an++)
;
}
else
return b;
if (b)
{
for (bn = 0; b[bn]; bn++)
{
if (lwr)
mu_strlower (b[bn]);
}
}
else
return a;
i = an == 1 ? 0 : 1;
j = bn == 1 ? 0 : 1;
n = (an - i) + (bn - j) + 1;
ret = mu_calloc (n + 1, sizeof (ret[0]));
if (an == bn && an == 1)
{
/* Compute LCP of both entries */
for (i = 0; a[i] && b[i] && a[i] == b[i]; i++)
;
ret[0] = mu_alloc (i + 1);
memcpy (ret[0], a[0], i);
ret[0][i] = 0;
}
else
/* The first entry is the LCP of the rest. Select the shortest one. */
ret[0] = mu_strdup ((strlen (a[0]) < strlen (b[0])) ? a[0] : b[0]);
if (i)
free (a[0]);
if (j)
free (b[0]);
k = 1;
while (k < n)
{
if (!a[i])
{
memcpy (ret + k, b + j, sizeof (b[0]) * (bn - j));
break;
}
else if (!b[j])
{
memcpy (ret + k, a + i, sizeof (a[0]) * (an - i));
break;
}
else
ret[k++] = (strcmp (a[i], b[j]) < 0) ? a[i++] : b[j++];
}
ret[n] = NULL;
free (a);
free (b);
return ret;
}
static char *header_generator (const char *text, int state);
/* Internal completion generator for commands that take a message list
(if MATCHES is NULL), or a messages list followed by another argument
(if MATCHES is not NULL).
In the latter case the MATCHES function generates expansions for the
last argument. It is used for such commands as write, where the last
object is a mailbox or pipe, where it is a command).
CLOSURE supplies argument for MATCHES. It is ignored if MATCHES is NULL.
*/
static char **
msglist_closure_compl (int argc, char **argv, int point,
char **(*matches) (void*),
void *closure)
{
char *text = (point & COMPL_WS) ? "" : argv[argc-1];
size_t len = strlen (text);
if (text[0] == ':')
{
if (text[1] && (text[1] == '/' || text[2]))
{
ml_set_completion_append_character (0);
ml_attempted_completion_over ();
return NULL;
}
ml_set_completion_append_character (' ');
return rl_completion_matches (text, msgtype_generator);
}
ml_set_completion_append_character (0);
if (len && text[len-1] == ':')
{
char **ret = mu_calloc(2, sizeof (ret[0]));
ret[0] = strcat (strcpy (mu_alloc (len + 2), text), "/");
return ret;
}
if (matches && (point & COMPL_LASTARG))
return compl_concat (matches (closure),
rl_completion_matches (text, header_generator));
else
return rl_completion_matches (text, header_generator);
}
/* Completion functions */
char **
no_compl (int argc MU_ARG_UNUSED, char **argv MU_ARG_UNUSED,
int flags MU_ARG_UNUSED)
{
ml_attempted_completion_over ();
return NULL;
}
char **
msglist_compl (int argc, char **argv, int point)
{
return msglist_closure_compl (argc, argv, point, NULL, NULL);
}
char **
command_compl (int argc, char **argv, int point)
{
ml_set_completion_append_character (0);
if (point & COMPL_WS)
return NULL;
return rl_completion_matches (argv[argc-1], ml_command_generator);
}
struct filegen
{
mu_list_t list; /* List of matches */
mu_iterator_t itr; /* Iterator over this list */
size_t prefix_len; /* Length of initial prefix */
char repl; /* Character to replace initial prefix with */
int flags;
};
static void
filegen_free (struct filegen *fg)
{
mu_iterator_destroy (&fg->itr);
mu_list_destroy (&fg->list);
}
#define PREFIX_AUTO 0
static int
folder_match_url (mu_folder_t folder, mu_url_t url)
{
mu_url_t furl;
int rc = mu_folder_get_url (folder, &furl);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_folder_get_url", NULL, rc);
return 0;
}
return mu_url_is_same_scheme (url, furl)
&& mu_url_is_same_user (url, furl)
&& mu_url_is_same_host (url, furl)
&& mu_url_is_same_portstr (url, furl);
}
static int
filegen_init (struct filegen *fg,
const char *text,
const char *folder_path,
int type,
size_t prefix_len,
int repl,
int flags)
{
char *pathref;
char *wcard;
mu_folder_t folder;
size_t count, i, len;
mu_url_t url;
int rc;
int free_folder;
rc = mu_url_create (&url, folder_path);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_url_create", folder_path, rc);
return -1;
}
rc = mu_mailbox_get_folder (mbox, &folder);
if (rc == 0)
{
if (folder_match_url (folder, url))
free_folder = 0;
else
folder = NULL;
}
else
folder = NULL;
if (!folder)
{
if (util_get_folder (&folder, url, type))
return -1;
free_folder = 1;
}
pathref = mu_strdup (text);
len = strlen (pathref);
for (i = len; i > 0; i--)
if (pathref[i-1] == '/')
break;
wcard = mu_alloc (len - i + 2);
strcpy (wcard, pathref + i);
strcat (wcard, "%");
pathref[i] = 0;
fg->prefix_len = prefix_len;
mu_folder_list (folder, pathref, wcard, 1, &fg->list);
free (wcard);
free (pathref);
if (free_folder)
mu_folder_destroy (&folder);
if (mu_list_count (fg->list, &count) || count == 0)
{
mu_list_destroy (&fg->list);
return -1;
}
else if (count == 1)
{
ml_set_completion_append_character (0);
if (flags & MU_FOLDER_ATTRIBUTE_DIRECTORY)
{
struct mu_list_response *resp;
mu_list_head (fg->list, (void**)&resp);
if ((resp->type & MU_FOLDER_ATTRIBUTE_DIRECTORY)
&& strcmp (resp->name, text) == 0)
ml_set_completion_append_character (resp->separator);
}
}
if (mu_list_get_iterator (fg->list, &fg->itr))
{
mu_list_destroy (&fg->list);
return -1;
}
mu_iterator_first (fg->itr);
fg->repl = repl;
fg->flags = flags;
return 0;
}
static char *
filegen_next (struct filegen *fg)
{
while (!mu_iterator_is_done (fg->itr))
{
struct mu_list_response *resp;
mu_iterator_current (fg->itr, (void**)&resp);
mu_iterator_next (fg->itr);
if (resp->type & fg->flags)
{
char *ret;
char *name;
char *ptr;
name = resp->name + fg->prefix_len;
ret = mu_alloc (strlen (name) + (fg->repl ? 1 : 0) + 1);
ptr = ret;
if (fg->repl)
*ptr++ = fg->repl;
strcpy (ptr, name);
return ret;
}
}
filegen_free (fg);
return NULL;
}
static char *
folder_generator (const char *text, int state)
{
static struct filegen fg;
if (!state)
{
int rc;
char *path = util_folder_path ("+");
if (!path)
return NULL;
rc = filegen_init (&fg, text, path,
any_folder,
PREFIX_AUTO, '+',
MU_FOLDER_ATTRIBUTE_ALL);
free (path);
if (rc)
return NULL;
}
return filegen_next (&fg);
}
static char *
header_generator (const char *text, int state)
{
static int i, len;
char *hdr;
char *hdrlist[] = {
MU_HEADER_RETURN_PATH,
MU_HEADER_RECEIVED,
MU_HEADER_DATE,
MU_HEADER_DCC,
MU_HEADER_FROM,
MU_HEADER_SENDER,
MU_HEADER_RESENT_FROM,
MU_HEADER_SUBJECT,
MU_HEADER_RESENT_SENDER,
MU_HEADER_TO,
MU_HEADER_RESENT_TO,
MU_HEADER_CC,
MU_HEADER_RESENT_CC,
MU_HEADER_BCC,
MU_HEADER_RESENT_BCC,
MU_HEADER_REPLY_TO,
MU_HEADER_RESENT_REPLY_TO,
MU_HEADER_MESSAGE_ID,
MU_HEADER_RESENT_MESSAGE_ID,
MU_HEADER_IN_REPLY_TO,
MU_HEADER_REFERENCE,
MU_HEADER_REFERENCES,
MU_HEADER_ENCRYPTED,
MU_HEADER_PRECEDENCE,
MU_HEADER_STATUS,
MU_HEADER_CONTENT_LENGTH,
MU_HEADER_CONTENT_LANGUAGE,
MU_HEADER_CONTENT_TRANSFER_ENCODING,
MU_HEADER_CONTENT_ID,
MU_HEADER_CONTENT_TYPE,
MU_HEADER_CONTENT_DESCRIPTION,
MU_HEADER_CONTENT_DISPOSITION,
MU_HEADER_CONTENT_MD5,
MU_HEADER_CONTENT_LOCATION,
MU_HEADER_MIME_VERSION,
MU_HEADER_X_MAILER,
MU_HEADER_X_UIDL,
MU_HEADER_X_UID,
MU_HEADER_X_IMAPBASE,
MU_HEADER_ENV_SENDER,
MU_HEADER_ENV_DATE,
MU_HEADER_FCC,
MU_HEADER_DELIVERY_DATE,
MU_HEADER_ENVELOPE_TO,
MU_HEADER_X_EXPIRE_TIMESTAMP,
MU_HEADER_USER_AGENT,
NULL
};
if (!state)
{
i = 0;
len = strlen (text);
}
while ((hdr = hdrlist[i]))
{
i++;
if (mu_c_strncasecmp (hdr, text, len) == 0)
return strcat (strcpy (mu_alloc (strlen (hdr) + 3), hdr), ":/");
}
return NULL;
}
char **
file_compl (int argc, char **argv, int point)
{
char *text;
if (point & COMPL_WS)
{
ml_set_completion_append_character (0);
ml_attempted_completion_over ();
return NULL;
}
text = argv[argc-1];
switch (text[0])
{
case '+':
return rl_completion_matches (text + 1, folder_generator);
case '%':
case '#':
case '&':
ml_attempted_completion_over ();
break;
default:
/* Suppose it is a file name */
return rl_completion_matches (text, rl_filename_completion_function);
}
return NULL;
}
struct compl_closure
{
int argc;
char **argv;
int point;
};
static char **
file_compl_matches (void *closure)
{
struct compl_closure *cp = closure;
return file_compl (cp->argc, cp->argv, cp->point);
}
char **
msglist_file_compl (int argc, char **argv, int point)
{
struct compl_closure clos = { argc, argv, point };
return msglist_closure_compl (argc, argv, point, file_compl_matches, &clos);
}
static char *
dir_generator (const char *text, int state)
{
static struct filegen fg;
if (!state)
{
char *path;
char *p;
int rc;
char repl;
size_t prefix_len;
switch (text[0])
{
case '+':
{
char *f;
repl = '+';
f = util_folder_path ("+");
prefix_len = strlen (f);
path = mu_make_file_name (f, text + 1);
free (f);
}
break;
case '~':
repl = '~';
if (text[1] == '/')
{
char *home = mu_get_homedir ();
prefix_len = strlen (home);
path = mu_make_file_name (home, text + 2);
free (home);
}
else
{
ml_attempted_completion_over ();
return NULL;
/* FIXME: implement user-name completion */
}
break;
case '/':
path = mu_strdup (text);
prefix_len = 0;
repl = 0;
break;
default:
{
char *cwd = mu_getcwd ();
prefix_len = strlen (cwd);
path = mu_make_file_name (cwd, text);
free (cwd);
}
repl = 0;
}
p = strrchr (path, '/');
if (*p)
{
if (p[1])
*p++ = 0;
else
p = "";
rc = filegen_init (&fg, p, path[0] ? path : "/",
local_folder,
prefix_len, repl,
MU_FOLDER_ATTRIBUTE_DIRECTORY);
}
else
{
ml_attempted_completion_over ();
rc = -1;
}
if (rc)
return NULL;
}
return filegen_next (&fg);
}
char **
dir_compl (int argc, char **argv, int point)
{
ml_attempted_completion_over ();
if (point & COMPL_WS)
{
ml_set_completion_append_character (0);
return NULL;
}
return rl_completion_matches (argv[argc-1], dir_generator);
}
static char *
alias_generator (const char *text, int state)
{
static alias_iterator_t itr;
const char *p;
if (!state)
p = alias_iterate_first (text, &itr);
else
p = alias_iterate_next (itr);
if (!p)
{
alias_iterate_end (&itr);
return NULL;
}
return mu_strdup (p);
}
char **
alias_compl (int argc, char **argv, int point)
{
ml_attempted_completion_over ();
return rl_completion_matches ((point & COMPL_WS) ? "" : argv[argc-1],
alias_generator);
}
static char *
exec_generator (const char *text, int state)
{
static int prefix_len;
static char *var;
static char *dir;
static size_t dsize;
static DIR *dp;
struct dirent *ent;
if (!state)
{
var = getenv ("PATH");
if (!var)
return NULL;
prefix_len = strlen (text);
dsize = 0;
dir = NULL;
dp = NULL;
}
while (1)
{
if (!dp)
{
char *p;
size_t len;
if (*var == 0)
break;
else
var++;
p = strchr (var, ':');
if (!p)
len = strlen (var) + 1;
else
len = p - var + 1;
if (dsize == 0)
{
dir = malloc (len);
dsize = len;
}
else if (len > dsize)
{
dir = realloc (dir, len);
dsize = len;
}
if (!dir)
return NULL;
memcpy (dir, var, len - 1);
dir[len - 1] = 0;
var += len - 1;
dp = opendir (dir);
if (!dp)
continue;
}
while ((ent = readdir (dp)))
{
char *name = mu_make_file_name (dir, ent->d_name);
if (name)
{
int rc = access (name, X_OK);
if (rc == 0)
{
struct stat st;
rc = !(stat (name, &st) == 0 && S_ISREG (st.st_mode));
}
free (name);
if (rc == 0
&& strlen (ent->d_name) >= prefix_len
&& strncmp (ent->d_name, text, prefix_len) == 0)
return mu_strdup (ent->d_name);
}
}
closedir (dp);
dp = NULL;
}
free (dir);
return NULL;
}
static char **
exec_matches (void *closure)
{
return rl_completion_matches ((char *)closure, exec_generator);
}
char **
exec_compl (int argc, char **argv, int point)
{
return msglist_closure_compl (argc, argv, point,
exec_matches,
(point & COMPL_WS) ? "" : argv[argc-1]);
}
char **
shell_compl (int argc, char **argv, int point)
{
if (argc == 2)
return rl_completion_matches (argv[1], exec_generator);
return no_compl (argc, argv, point);
}
#else
#include
#define STDOUT 1
#ifndef TIOCSTI
static int ch_erase;
static int ch_kill;
#endif
#if defined(TIOCSTI)
#elif defined(HAVE_TERMIOS_H)
# include
static struct termios term_settings;
int
set_tty (void)
{
struct termios new_settings;
if (tcgetattr(STDOUT, &term_settings) == -1)
return 1;
ch_erase = term_settings.c_cc[VERASE];
ch_kill = term_settings.c_cc[VKILL];
new_settings = term_settings;
new_settings.c_lflag &= ~(ICANON|ECHO);
#if defined(TAB3)
new_settings.c_oflag &= ~(TAB3);
#elif defined(OXTABS)
new_settings.c_oflag &= ~(OXTABS);
#endif
new_settings.c_cc[VMIN] = 1;
new_settings.c_cc[VTIME] = 0;
tcsetattr(STDOUT, TCSADRAIN, &new_settings);
return 0;
}
void
restore_tty (void)
{
tcsetattr (STDOUT, TCSADRAIN, &term_settings);
}
#elif defined(HAVE_TERMIO_H)
# include
static struct termio term_settings;
int
set_tty (void)
{
struct termio new_settings;
if (ioctl(STDOUT, TCGETA, &term_settings) < 0)
return -1;
ch_erase = term_settings.c_cc[VERASE];
ch_kill = term_settings.c_cc[VKILL];
new_settings = term_settings;
new_settings.c_lflag &= ~(ICANON | ECHO);
new_settings.c_oflag &= ~(TAB3);
new_settings.c_cc[VMIN] = 1;
new_settings.c_cc[VTIME] = 0;
ioctl(STDOUT, TCSETA, &new_settings);
return 0;
}
void
restore_tty (void)
{
ioctl(STDOUT, TCSETA, &term_settings);
}
#elif defined(HAVE_SGTTY_H)
# include
static struct sgttyb term_settings;
int
set_tty (void)
{
struct sgttyb new_settings;
if (ioctl(STDOUT, TIOCGETP, &term_settings) < 0)
return 1;
ch_erase = term_settings.sg_erase;
ch_kill = term_settings.sg_kill;
new_settings = term_settings;
new_settings.sg_flags |= CBREAK;
new_settings.sg_flags &= ~(ECHO | XTABS);
ioctl(STDOUT, TIOCSETP, &new_settings);
return 0;
}
void
restore_tty (void)
{
ioctl(STDOUT, TIOCSETP, &term_settings);
}
#else
# define DUMB_MODE
#endif
#define LINE_INC 80
int
ml_reread (const char *prompt, char **text)
{
int ch;
char *line;
int line_size;
int pos;
char *p;
if (*text)
{
line = strdup (*text);
if (line)
{
pos = strlen (line);
line_size = pos + 1;
}
}
else
{
line_size = LINE_INC;
line = malloc (line_size);
pos = 0;
}
if (!line)
{
mu_error (_("Not enough memory to edit the line"));
return -1;
}
line[pos] = 0;
if (prompt)
{
fputs (prompt, stdout);
fflush (stdout);
}
#ifdef TIOCSTI
for (p = line; *p; p++)
{
ioctl(0, TIOCSTI, p);
}
pos = 0;
while ((ch = ml_getc (stdin)) != EOF && ch != '\n')
{
if (pos >= line_size)
{
if ((p = realloc (line, line_size + LINE_INC)) == NULL)
{
fputs ("\n", stdout);
mu_error (_("Not enough memory to edit the line"));
break;
}
else
{
line_size += LINE_INC;
line = p;
}
}
line[pos++] = ch;
}
#else
fputs (line, stdout);
fflush (stdout);
# ifndef DUMB_MODE
set_tty ();
while ((ch = ml_getc (stdin)) != EOF)
{
if (ch == ch_erase)
{
/* kill last char */
if (pos > 0)
line[--pos] = 0;
putc('\b', stdout);
}
else if (ch == ch_kill)
{
/* kill the entire line */
pos = 0;
line[pos] = 0;
putc ('\r', stdout);
if (prompt)
fputs (prompt, stdout);
}
else if (ch == '\n' || ch == EOF)
break;
else
{
if (pos >= line_size)
{
if ((p = realloc (line, line_size + LINE_INC)) == NULL)
{
fputs ("\n", stdout);
mu_error (_("Not enough memory to edit the line"));
break;
}
else
{
line_size += LINE_INC;
line = p;
}
}
line[pos++] = ch;
putc(ch, stdout);
}
fflush (stdout);
}
putc ('\n', stdout);
restore_tty ();
# else
/* Dumb mode: the last resort */
putc ('\n', stdout);
if (prompt)
{
fputs (prompt, stdout);
fflush (stdout);
}
pos = 0;
while ((ch = ml_getc (stdin)) != EOF && ch != '\n')
{
if (pos >= line_size)
{
if ((p = realloc (line, line_size + LINE_INC)) == NULL)
{
fputs ("\n", stdout);
mu_error (_("Not enough memory to edit the line"));
break;
}
else
{
line_size += LINE_INC;
line = p;
}
}
line[pos++] = ch;
}
# endif
#endif
line[pos] = 0;
if (ml_got_interrupt ())
free (line);
else
{
if (*text)
free (*text);
*text = line;
}
return 0;
}
char *
readline (char *prompt)
{
if (prompt)
{
mu_printf ("%s", prompt);
mu_stream_flush (mu_strout);
}
return ml_readline_internal ();
}
void
ml_set_completion_append_character (int c MU_ARG_UNUSED)
{
}
void
ml_attempted_completion_over (void)
{
}
#endif