예제 #1
0
/*
 * Return a `~/foo' like path if the user is under his home directory,
 * and restart from / if // found,
 * else the unmodified path.
 */
astr compact_path(const astr path)
{
  astr buf = astr_new();
  struct passwd *pw;
  size_t i;

  if ((pw = getpwuid(getuid())) == NULL) {
    /* User not found in password file. */
    astr_cpy(buf, path);
    return buf;
  }

  /* Replace `/userhome/' (if existent) with `~/'. */
  i = strlen(pw->pw_dir);
  if (!strncmp(pw->pw_dir, astr_cstr(path), i)) {
    astr_cpy_cstr(buf, "~/");
    if (!strcmp(pw->pw_dir, "/"))
      astr_cat_cstr(buf, astr_char(path, 1));
    else
      astr_cat_cstr(buf, astr_char(path, i + 1));
  } else
    astr_cpy(buf, path);

  return buf;
}
예제 #2
0
static astr astr_replace_x(astr as, int pos, size_t size, const char *s, size_t csize)
{
	astr dest = astr_new();
	if (pos < 0) {
		pos = as->size + pos;
		if (pos < 0)
			pos = 0;
	}
	if ((unsigned int)pos > as->size)
		pos = as->size;

	if (as->size - pos < size)
		size = as->size - pos;
	if (size > 0) {
		dest->size = as->size - size + csize;
		resize_if_smaller(dest, dest->size);
		memcpy(dest->text, as->text, pos);
		memcpy(dest->text + pos, s, csize);
		strcpy(dest->text + pos + csize, as->text + pos + size);
	}
	free(as->text);
	*as = *dest;
	free(dest);
	return as;
}
예제 #3
0
astr astr_copy(castr as)
{
	astr dest;
	assert(as != NULL);
	dest = astr_new();
	astr_assign(dest, as);
	return dest;
}
예제 #4
0
astr astr_copy_cstr(const char *s)
{
	astr dest;
	assert(s != NULL);
	dest = astr_new();
	astr_assign_cstr(dest, s);
	return dest;
}
예제 #5
0
astr astr_right(castr as, size_t size)
{
	assert(as != NULL);
	if (size == 0)
		return astr_new();
	if (size > as->size)
		size = as->size;
	return astr_substr(as, as->size - size, size);
}
예제 #6
0
astr astr_left(castr as, size_t size)
{
	assert(as != NULL);
	if (size == 0)
		return astr_new();
	if (size > as->size)
		size = as->size;
	return astr_substr(as, 0, size);
}
예제 #7
0
static void draw_status_line(size_t line, Window *wp)
{
  size_t i;
  char *buf, *eol_type;
  Point pt = window_pt(wp);
  astr as, bs;

  term_attrset(1, FONT_REVERSE);

  term_move(line, 0);
  for (i = 0; i < wp->ewidth; ++i)
    term_addch('-');

  if (cur_bp->eol == coding_eol_cr)
    eol_type = "(Mac)";
  else if (cur_bp->eol == coding_eol_crlf)
    eol_type = "(DOS)";
  else
    eol_type = ":";
    
  term_move(line, 0);
  bs = astr_afmt(astr_new(), "(%d,%d)", pt.n+1, get_goalc_wp(wp));
  as = astr_afmt(astr_new(), "--%s%2s  %-15s   %s %-9s (Text",
                 eol_type, make_mode_line_flags(wp), wp->bp->name,
                 make_screen_pos(wp, &buf), astr_cstr(bs));
  free(buf);
  astr_delete(bs);

  if (wp->bp->flags & BFLAG_AUTOFILL)
    astr_cat_cstr(as, " Fill");
  if (wp->bp->flags & BFLAG_OVERWRITE)
    astr_cat_cstr(as, " Ovwrt");
  if (thisflag & FLAG_DEFINING_MACRO)
    astr_cat_cstr(as, " Def");
  if (wp->bp->flags & BFLAG_ISEARCH)
    astr_cat_cstr(as, " Isearch");

  astr_cat_char(as, ')');
  term_addnstr(astr_cstr(as), min(term_width(), astr_len(as)));
  astr_delete(as);

  term_attrset(1, FONT_NORMAL);
}
예제 #8
0
astr variableDump(le *varlist)
{
  astr as = astr_new();

  for (; varlist; varlist = varlist->list_next) {
    if (varlist->branch) {
      astr_afmt(as, "%s \t", varlist->data);
      astr_cat_delete(as, leDumpReformat(varlist->branch));
      astr_cat_char(as, '\n');
    }
  }

  return as;
}
예제 #9
0
/*
 * Convert a key code sequence into a key code sequence string.
 */
astr keyvectostr(size_t *keys, size_t numkeys)
{
    size_t i;
    astr as = astr_new();

    for (i = 0; i < numkeys; i++) {
        astr key = chordtostr(keys[i]);
        astr_cat(as, key);
        astr_delete(key);
        if (i < numkeys - 1)
            astr_cat_char(as, ' ');
    }

    return as;
}
예제 #10
0
파일: glue.c 프로젝트: gpaquet/zile
/*
 * Return a string of maximum length `maxlen' beginning with a `...'
 * sequence if a cut is need.
 */
astr
shorten_string (char *s, int maxlen)
{
  astr as = astr_new ();
  int len = strlen (s);

  if (len <= maxlen)
    astr_cpy_cstr (as, s);
  else
    {
      astr_cpy_cstr (as, "...");
      astr_cat_cstr (as, s + len - maxlen + 3);
    }

  return as;
}
예제 #11
0
/*
 * Allocate a new completion structure.
 */
Completion *
completion_new (int fileflag)
{
  Completion *cp = (Completion *) XZALLOC (Completion);

  cp->completions = gl_list_create_empty (GL_LINKED_LIST,
                                          completion_streq, NULL,
                                          (gl_listelement_dispose_fn) free, false);
  cp->matches = gl_list_create_empty (GL_LINKED_LIST,
                                      completion_streq, NULL,
                                      NULL, false);

  if (fileflag)
    {
      cp->path = astr_new ();
      cp->flags |= CFLAG_FILENAME;
    }

  return cp;
}
예제 #12
0
파일: file.c 프로젝트: M1lan/zile
bool
find_file (const char *filename)
{
  Buffer bp;
  for (bp = head_bp; bp != NULL; bp = get_buffer_next (bp))
    if (get_buffer_filename (bp) != NULL &&
        STREQ (get_buffer_filename (bp), filename))
      break;

  if (bp == NULL)
    {
      if (exist_file (filename) && !is_regular_file (filename))
        {
          minibuf_error ("File exists but could not be read");
          return false;
        }
      else
        {
          bp = buffer_new ();
          set_buffer_names (bp, filename);
          set_buffer_dir (bp, astr_new_cstr (dir_name (filename)));

          estr es = estr_readf (filename);
          if (es)
            set_buffer_readonly (bp, !check_writable (filename));
          else
            es = estr_new_astr (astr_new ());
          set_buffer_text (bp, es);

          /* Reset undo history. */
          set_buffer_next_undop (bp, NULL);
          set_buffer_last_undop (bp, NULL);
          set_buffer_modified (bp, false);
        }
    }

  switch_to_buffer (bp);
  thisflag |= FLAG_NEED_RESYNC;
  return true;
}
예제 #13
0
/*
 * Convert a key like "\\C-xrs" to "C-x r s"
 */
astr simplify_key(char *key)
{
    int i, j;
    size_t *keys;
    astr dest = astr_new();

    if (key == NULL)
        return dest;
    i = keystrtovec(key, &keys);
    for (j = 0; j < i; j++) {
        astr as;
        if (j > 0)
            astr_cat_char(dest, ' ');
        as = chordtostr(keys[j]);
        astr_cat(dest, as);
        astr_delete(as);
    }
    if (i > 0)
        free(keys);

    return dest;
}
예제 #14
0
astr astr_substr(castr as, int pos, size_t size)
{
	astr dest;
	assert(as != NULL);
	dest = astr_new();
	if (pos < 0) {
		pos = as->size + pos;
		if (pos < 0)
			pos = 0;
	}
	if ((unsigned int)pos > as->size)
		pos = as->size;

	if (as->size - pos < size)
		size = as->size - pos;
	if (size > 0) {
		resize_if_smaller(dest, size);
		memcpy(dest->text, as->text + pos, size);
		dest->size = size;
	}
	return dest;
}
예제 #15
0
/*
 * Read the file contents into a buffer.
 * Return quietly if the file doesn't exist, or other error.
 */
void read_from_disk(const char *filename)
{
  Line *lp;
  FILE *fp;
  int i, size, first_eol = TRUE;
  char *this_eol_type;
  size_t eol_len = 0, total_eols = 0;
  char buf[BUFSIZ];

  if ((fp = fopen(filename, "r")) == NULL) {
    if (errno != ENOENT) {
      minibuf_write("%s: %s", filename, strerror(errno));
      cur_bp->flags |= BFLAG_READONLY;
    }
    return;
  }

#if HAVE_UNISTD_H
  if (!check_writable(filename))
    cur_bp->flags |= BFLAG_READONLY;
#endif
  
  lp = cur_bp->pt.p;

  /* Read first chunk and determine EOL type. */
  if ((size = fread(buf, 1, BUFSIZ, fp)) > 0) {
    for (i = 0; i < size && total_eols < MAX_EOL_CHECK_COUNT; i++) {
      if (buf[i] == '\n' || buf[i] == '\r') {
        total_eols++;
        if (buf[i] == '\n')
          this_eol_type = coding_eol_lf;
        else if (i >= size || buf[i + 1] != '\n')
          this_eol_type = coding_eol_cr;
        else {
          this_eol_type = coding_eol_crlf;
          i++;
        }
        
        if (first_eol) {
          /* This is the first end-of-line. */
          cur_bp->eol = this_eol_type;
          first_eol = FALSE;
        } else if (cur_bp->eol != this_eol_type) {
          /* This EOL is different from the last; arbitrarily choose
             LF. */
          cur_bp->eol = coding_eol_lf;
          break;
        }
      }
    }
    
    /* Process this and subsequent chunks into lines. */
    eol_len = strlen(cur_bp->eol);
    do {
      for (i = 0; i < size; i++) {
        if (strncmp(cur_bp->eol, buf + i, eol_len) != 0)
          astr_cat_char(lp->item, buf[i]);
        else {
          lp = list_prepend(lp, astr_new());
          ++cur_bp->num_lines;
          i += eol_len - 1;
        }
      }
    } while ((size = fread(buf, 1, BUFSIZ, fp)) > 0);
  }

  list_next(lp) = cur_bp->lines;
  list_prev(cur_bp->lines) = lp;
  cur_bp->pt.p = list_next(cur_bp->lines);

  fclose(fp);
}
예제 #16
0
/*
 * This functions does some corrections and expansions to
 * the passed path:
 * - expands `~/' and `~name/' expressions;
 * - replaces `//' with `/' (restarting from the root directory);
 * - removes `..' and `.' entries.
 *
 * If something goes wrong, the string is deleted and NULL returned
 */
astr expand_path(astr path)
{
  int ret = TRUE;
  struct passwd *pw;
  const char *sp = astr_cstr(path);
  astr epath = astr_new();

  if (*sp != '/') {
    astr_cat_delete(epath, agetcwd());
    if (astr_len(epath) == 0 || *astr_char(epath, -1) != '/')
      astr_cat_char(epath, '/');
  }

  while (*sp != '\0') {
    if (*sp == '/') {
      if (*++sp == '/') {
        /* Got `//'.  Restart from this point. */
        while (*sp == '/')
          sp++;
        astr_truncate(epath, 0);
      }
      astr_cat_char(epath, '/');
    } else if (*sp == '~') {
      if (*(sp + 1) == '/') {
        /* Got `~/'. Restart from this point and insert the user's
           home directory. */
        astr_truncate(epath, 0);
        if ((pw = getpwuid(getuid())) == NULL) {
          ret = FALSE;
          break;
        }
        if (strcmp(pw->pw_dir, "/") != 0)
          astr_cat_cstr(epath, pw->pw_dir);
        ++sp;
      } else {
        /* Got `~something'.  Restart from this point and insert that
           user's home directory. */
        astr as = astr_new();
        astr_truncate(epath, 0);
        ++sp;
        while (*sp != '\0' && *sp != '/')
          astr_cat_char(as, *sp++);
        pw = getpwnam(astr_cstr(as));
        astr_delete(as);
        if (pw == NULL) {
          ret = FALSE;
          break;
        }
        astr_cat_cstr(epath, pw->pw_dir);
      }
    } else if (*sp == '.') {
      if (*(sp + 1) == '/' || *(sp + 1) == '\0') {
        ++sp;
        if (*sp == '/' && *(sp + 1) != '/')
          ++sp;
      } else if (*(sp + 1) == '.' &&
                 (*(sp + 2) == '/' || *(sp + 2) == '\0')) {
        if (astr_len(epath) >= 1 && *astr_char(epath, -1) == '/')
          astr_truncate(epath, -1);
        while (*astr_char(epath, -1) != '/' &&
               astr_len(epath) >= 1)
          astr_truncate(epath, -1);
        sp += 2;
        if (*sp == '/' && *(sp + 1) != '/')
          ++sp;
      } else
        goto got_component;
    } else {
      const char *p;
    got_component:
      p = sp;
      while (*p != '\0' && *p != '/')
        p++;
      if (*p == '\0') { /* Final filename */
        astr_cat_cstr(epath, sp);
        break;
      } else { /* Non-final directory */
        while (*sp != '/')
          astr_cat_char(epath, *sp++);
      }
    }
  }

  astr_cpy(path, epath);
  astr_delete(epath);

  if (!ret) {
    astr_delete(path);
    return NULL;
  }
  return path;
}
예제 #17
0
static astr snagAToken(getcCallback getachar, ungetcCallback ungetachar, enum tokenname *tokenid)
{
  int c;
  int doublequotes = 0;
  astr tok = astr_new();

  *tokenid = T_EOF;

  /* Chew space to next token */
  do {
    c = getachar();

    /* Munch comments */
    if (c == ';')
      do {
        c = getachar();
      } while (c != EOF && c != '\n');
  } while (c != EOF && (c == ' ' || c == '\t'));

  /* Snag token */
  if (c == '(') {
    *tokenid = T_OPENPAREN;
    return tok;
  } else if (c == ')') {
    *tokenid = T_CLOSEPAREN;
    return tok;
  } else if (c == '\'') {
    *tokenid = T_QUOTE;
    return tok;
  } else if (c == '\n') {
    *tokenid = T_NEWLINE;
    return tok;
  } else if (c == EOF) {
    *tokenid = T_EOF;
    return tok;
  }

  /* It looks like a string. Snag to the next whitespace. */
  if (c == '\"') {
    doublequotes = 1;
    c = getachar();
  }

  while (1) {
    astr_cat_char(tok, (char)c);

    if (!doublequotes) {
      if (c == ')' || c == '(' || c == ';' || c == ' ' || c == '\n' || c == '\r' || c == EOF) {
        ungetachar(c);
        astr_truncate(tok, (ptrdiff_t)-1);

        if (!astr_cmp_cstr(tok, "quote")) {
          *tokenid = T_QUOTE;
          return tok;
        }
        *tokenid = T_WORD;
        return tok;
      }
    } else {
      switch (c) {
      case '\n':
      case '\r':
      case EOF:
        ungetachar(c);
        /* Fall through */

      case '\"':
        astr_truncate(tok, (ptrdiff_t)-1);
        *tokenid = T_WORD;
        return tok;

      }
    }

    c = getachar();
  }

  return tok;
}
예제 #18
0
/*
 * Convert a key chord into its ASCII representation
 */
astr chordtostr(size_t key)
{
    astr as = astr_new();

    if (key & KBD_CTRL)
        astr_cat_cstr(as, "C-");
    if (key & KBD_META)
        astr_cat_cstr(as, "M-");
    key &= ~(KBD_CTRL | KBD_META);

    switch (key) {
    case KBD_PGUP:
        astr_cat_cstr(as, "<prior>");
        break;
    case KBD_PGDN:
        astr_cat_cstr(as, "<next>");
        break;
    case KBD_HOME:
        astr_cat_cstr(as, "<home>");
        break;
    case KBD_END:
        astr_cat_cstr(as, "<end>");
        break;
    case KBD_DEL:
        astr_cat_cstr(as, "<delete>");
        break;
    case KBD_BS:
        astr_cat_cstr(as, "<backspace>");
        break;
    case KBD_INS:
        astr_cat_cstr(as, "<insert>");
        break;
    case KBD_LEFT:
        astr_cat_cstr(as, "<left>");
        break;
    case KBD_RIGHT:
        astr_cat_cstr(as, "<right>");
        break;
    case KBD_UP:
        astr_cat_cstr(as, "<up>");
        break;
    case KBD_DOWN:
        astr_cat_cstr(as, "<down>");
        break;
    case KBD_RET:
        astr_cat_cstr(as, "<RET>");
        break;
    case KBD_TAB:
        astr_cat_cstr(as, "<TAB>");
        break;
    case KBD_F1:
        astr_cat_cstr(as, "<f1>");
        break;
    case KBD_F2:
        astr_cat_cstr(as, "<f2>");
        break;
    case KBD_F3:
        astr_cat_cstr(as, "<f3>");
        break;
    case KBD_F4:
        astr_cat_cstr(as, "<f4>");
        break;
    case KBD_F5:
        astr_cat_cstr(as, "<f5>");
        break;
    case KBD_F6:
        astr_cat_cstr(as, "<f6>");
        break;
    case KBD_F7:
        astr_cat_cstr(as, "<f7>");
        break;
    case KBD_F8:
        astr_cat_cstr(as, "<f8>");
        break;
    case KBD_F9:
        astr_cat_cstr(as, "<f9>");
        break;
    case KBD_F10:
        astr_cat_cstr(as, "<f10>");
        break;
    case KBD_F11:
        astr_cat_cstr(as, "<f11>");
        break;
    case KBD_F12:
        astr_cat_cstr(as, "<f12>");
        break;
    case ' ':
        astr_cat_cstr(as, "SPC");
        break;
    default:
        if (isgraph(key))
            astr_cat_char(as, (int)(key & 0xff));
        else
            astr_afmt(as, "<%x>", key);
    }

    return as;
}
예제 #19
0
int main(void)
{
	astr as1, as2, as3;
	int i;

	as1 = astr_new();
	astr_assign_cstr(as1, " world");
	astr_prepend_cstr(as1, "hello");
	astr_append_char(as1, '!');
	assert_eq(as1, "hello world!");

	as3 = astr_substr(as1, 6, 5);
	assert_eq(as3, "world");

	as2 = astr_new();
	astr_assign_cstr(as2, " ");
	astr_prepend_cstr(as2, "The");
	astr_append(as2, as3);
	astr_append_char(as2, '.');
	assert_eq(as2, "The world.");

	astr_delete(as3);
	as3 = astr_substr(as1, -6, 5);
	assert_eq(as3, "world");

	astr_assign_cstr(as1, "12345");
	astr_delete(as2);
	as2 = astr_left(as1, 10);
	assert_eq(as2, "12345");
	astr_delete(as2);
	as2 = astr_left(as1, 3);
	assert_eq(as2, "123");
	astr_delete(as2);
	as2 = astr_right(as1, 10);
	assert_eq(as2, "12345");
	astr_delete(as2);
	as2 = astr_right(as1, 3);
	assert_eq(as2, "345");

	astr_assign_cstr(as1, "12345");
	astr_insert_cstr(as1, 3, "mid");
	astr_insert_cstr(as1, 0, "begin");
	astr_insert_cstr(as1, 100, "end");
	assert_eq(as1, "begin123mid45end");

	astr_assign_cstr(as1, "12345");
	astr_insert_char(as1, -2, 'x');
	astr_insert_char(as1, -10, 'y');
	astr_insert_char(as1, 10, 'z');
	assert_eq(as1, "y123x45z");

	astr_assign_cstr(as1, "12345");
	astr_delete(as2);
	as2 = astr_substr(as1, -2, 5);
	assert_eq(as2, "45");

	astr_assign_cstr(as1, "12345");
	astr_delete(as2);
	as2 = astr_substr(as1, -10, 5);
	assert_eq(as2, "12345");

	astr_assign_cstr(as1, "1234567");
	astr_replace_cstr(as1, -4, 2, "foo");
	assert_eq(as1, "123foo67");

	astr_assign_cstr(as1, "1234567");
	astr_replace_cstr(as1, 1, 3, "foo");
	assert_eq(as1, "1foo567");

	astr_assign_cstr(as1, "1234567");
	astr_replace_cstr(as1, -1, 5, "foo");
	assert_eq(as1, "123456foo");

	astr_assign_cstr(as1, "1234567");
	astr_remove(as1, 4, 10);
	assert_eq(as1, "1234");

	astr_assign_cstr(as1, "abc def de ab cd ab de fg");
	while ((i = astr_find_cstr(as1, "de")) >= 0)
	       astr_replace_cstr(as1, i, 2, "xxx");
	assert_eq(as1, "abc xxxf xxx ab cd ab xxx fg");
	while ((i = astr_find_cstr(as1, "ab")) >= 0)
	       astr_remove(as1, i, 2);
	assert_eq(as1, "c xxxf xxx  cd  xxx fg");
	while ((i = astr_find_cstr(as1, "  ")) >= 0)
	       astr_replace_char(as1, i, 2, ' ');
	assert_eq(as1, "c xxxf xxx cd xxx fg");

	astr_fill(as1, 'x', 3);
	assert_eq(as1, "xxx");

	astr_fmt(as1, "%s * %d = ", "5", 3);
	astr_afmt(as1, "%d", 15);
	assert_eq(as1, "5 * 3 = 15");

	printf("Input one string: ");
	fflush(stdout);
	astr_fgets(as1, stdin);
	printf("You wrote: \"%s\"\n", astr_cstr(as1));

	astr_delete(as1);
	astr_delete(as2);
	astr_delete(as3);
	printf("astr test successful.\n");

	return 0;
}
예제 #20
0
파일: file.c 프로젝트: M1lan/zile
/*
 * This functions does some corrections and expansions to
 * the passed path:
 *
 * - expands `~/' and `~name/' expressions;
 * - replaces `//' with `/' (restarting from the root directory);
 * - removes `..' and `.' entries.
 *
 * The return value indicates success or failure.
 */
bool
expand_path (astr path)
{
  int ok = true;
  const char *sp = astr_cstr (path);
  astr epath = astr_new ();

  if (*sp != '/' && *sp != '~')
    {
      astr_cat (epath, agetcwd ());
      if (astr_len (epath) == 0 ||
          astr_get (epath, astr_len (epath) - 1) != '/')
        astr_cat_char (epath, '/');
    }

  for (const char *p = sp; *p != '\0';)
    {
      if (*p == '/')
        {
          if (*++p == '/')
            { /* Got `//'.  Restart from this point. */
              while (*p == '/')
                p++;
              astr_truncate (epath, 0);
            }
          if (astr_len (epath) == 0 ||
              astr_get (epath, astr_len (epath) - 1) != '/')
            astr_cat_char (epath, '/');
        }
      else if (*p == '~' && (p == sp || p[-1] == '/'))
        { /* Got `/~' or leading `~'.  Restart from this point. */
          struct passwd *pw;

          astr_truncate (epath, 0);
          ++p;

          if (*p == '/')
            { /* Got `~/'.  Insert the user's home directory. */
              pw = getpwuid (getuid ());
              if (pw == NULL)
                {
                  ok = false;
                  break;
                }
              if (!STREQ (pw->pw_dir, "/"))
                astr_cat_cstr (epath, pw->pw_dir);
            }
          else
            { /* Got `~something'.  Insert that user's home directory. */
              astr as = astr_new ();
              while (*p != '\0' && *p != '/')
                astr_cat_char (as, *p++);
              pw = getpwnam (astr_cstr (as));
              if (pw == NULL)
                {
                  ok = false;
                  break;
                }
              astr_cat_cstr (epath, pw->pw_dir);
            }
        }
      else if (*p == '.' && (p[1] == '/' || p[1] == '\0'))
        { /* Got `.'. */
          ++p;
        }
      else if (*p == '.' && p[1] == '.' && (p[2] == '/' || p[2] == '\0'))
        { /* Got `..'. */
          if (astr_len (epath) >= 1 && astr_get (epath, astr_len (epath) - 1) == '/')
            astr_truncate (epath, astr_len (epath) - 1);
          while (astr_get (epath, astr_len (epath) - 1) != '/' && astr_len (epath) >= 1)
            astr_truncate (epath, astr_len (epath) - 1);
          p += 2;
        }

      if (*p != '~')
        while (*p != '\0' && *p != '/')
          astr_cat_char (epath, *p++);
    }

  astr_cpy (path, epath);

  return ok;
}
예제 #21
0
static astr
do_minibuf_read (const char *prompt, const char *value, size_t pos,
               Completion * cp, History * hp)
{
  static int overwrite_mode = 0;
  int c, thistab, lasttab = -1;
  size_t prompt_len;
  char *s;
  astr as = astr_new_cstr (value), saved = NULL;

  prompt_len = strlen (prompt);
  if (pos == SIZE_MAX)
    pos = astr_len (as);

  for (;;)
    {
      switch (lasttab)
        {
        case COMPLETION_MATCHEDNONUNIQUE:
          s = " [Complete, but not unique]";
          break;
        case COMPLETION_NOTMATCHED:
          s = " [No match]";
          break;
        case COMPLETION_MATCHED:
          s = " [Sole completion]";
          break;
        default:
          s = "";
        }
      draw_minibuf_read (prompt, astr_cstr (as), prompt_len, s, pos);

      thistab = -1;

      switch (c = getkey ())
        {
        case KBD_NOKEY:
          break;
        case KBD_CTRL | 'z':
          FUNCALL (suspend_emacs);
          break;
        case KBD_RET:
          term_move (term_height () - 1, 0);
          term_clrtoeol ();
          if (saved)
            astr_delete (saved);
          return as;
        case KBD_CANCEL:
          term_move (term_height () - 1, 0);
          term_clrtoeol ();
          if (saved)
            astr_delete (saved);
          astr_delete (as);
          return NULL;
        case KBD_CTRL | 'a':
        case KBD_HOME:
          pos = 0;
          break;
        case KBD_CTRL | 'e':
        case KBD_END:
          pos = astr_len (as);
          break;
        case KBD_CTRL | 'b':
        case KBD_LEFT:
          if (pos > 0)
            --pos;
          else
            ding ();
          break;
        case KBD_CTRL | 'f':
        case KBD_RIGHT:
          if (pos < astr_len (as))
            ++pos;
          else
            ding ();
          break;
        case KBD_CTRL | 'k':
          /* FIXME: do kill-register save. */
          if (pos < astr_len (as))
            astr_truncate (as, pos);
          else
            ding ();
          break;
        case KBD_BS:
          if (pos > 0)
            astr_remove (as, --pos, 1);
          else
            ding ();
          break;
        case KBD_CTRL | 'd':
        case KBD_DEL:
          if (pos < astr_len (as))
            astr_remove (as, pos, 1);
          else
            ding ();
          break;
        case KBD_INS:
          overwrite_mode = overwrite_mode ? 0 : 1;
          break;
        case KBD_META | 'v':
        case KBD_PGUP:
          if (cp == NULL)
            {
              ding ();
              break;
            }

          if (get_completion_flags (cp) & CFLAG_POPPEDUP)
            {
              completion_scroll_down ();
              thistab = lasttab;
            }
          break;
        case KBD_CTRL | 'v':
        case KBD_PGDN:
          if (cp == NULL)
            {
              ding ();
              break;
            }

          if (get_completion_flags (cp) & CFLAG_POPPEDUP)
            {
              completion_scroll_up ();
              thistab = lasttab;
            }
          break;
        case KBD_UP:
        case KBD_META | 'p':
          if (hp)
            {
              const char *elem = previous_history_element (hp);
              if (elem)
                {
                  if (!saved)
                    saved = astr_cpy (astr_new (), as);

                  astr_cpy_cstr (as, elem);
                }
            }
          break;
        case KBD_DOWN:
        case KBD_META | 'n':
          if (hp)
            {
              const char *elem = next_history_element (hp);
              if (elem)
                astr_cpy_cstr (as, elem);
              else if (saved)
                {
                  astr_cpy (as, saved);
                  astr_delete (saved);
                  saved = NULL;
                }
            }
          break;
        case KBD_TAB:
        got_tab:
          if (cp == NULL)
            {
              ding ();
              break;
            }

          if (lasttab != -1 && lasttab != COMPLETION_NOTMATCHED
              && get_completion_flags (cp) & CFLAG_POPPEDUP)
            {
              completion_scroll_up ();
              thistab = lasttab;
            }
          else
            {
              astr bs = astr_new ();
              astr_cpy (bs, as);
              thistab = completion_try (cp, bs, true);
              astr_delete (bs);
              switch (thistab)
                {
                case COMPLETION_MATCHED:
                case COMPLETION_MATCHEDNONUNIQUE:
                case COMPLETION_NONUNIQUE:
                  {
                    bs = astr_new ();
                    if (get_completion_flags (cp) & CFLAG_FILENAME)
                      astr_cat (bs, get_completion_path (cp));
                    astr_ncat_cstr (bs, get_completion_match (cp), get_completion_matchsize (cp));
                    if (strncmp (astr_cstr (as), astr_cstr (bs),
                                 astr_len (bs)) != 0)
                      thistab = -1;
                    astr_delete (as);
                    as = bs;
                    pos = astr_len (as);
                    break;
                  }
                case COMPLETION_NOTMATCHED:
                  ding ();
                }
            }
          break;
        case ' ':
          if (cp != NULL)
            goto got_tab;
          /* FALLTHROUGH */
        default:
          if (c > 255 || !isprint (c))
            {
              ding ();
              break;
            }
          astr_insert_char (as, pos++, c);
          if (overwrite_mode && pos != astr_len (as))
            astr_remove (as, pos, 1);
        }

      lasttab = thistab;
    }
}