Beispiel #1
0
char *
term_minibuf_read (const char *prompt, const char *value, size_t pos,
                   Completion * cp, History * hp)
{
  Window *wp, *old_wp = cur_wp;
  char *s = NULL;
  astr as;

  if (hp)
    prepare_history (hp);

  as = do_minibuf_read (prompt, value, pos, cp, hp);
  if (as)
    {
      s = xstrdup (astr_cstr (as));
      astr_delete (as);
    }

  if (cp != NULL && (get_completion_flags (cp) & CFLAG_POPPEDUP)
      && (wp = find_window ("*Completions*")) != NULL)
    {
      set_current_window (wp);
      if (get_completion_flags (cp) & CFLAG_CLOSE)
        FUNCALL (delete_window);
      else if (get_completion_old_bp (cp))
        switch_to_buffer (get_completion_old_bp (cp));
      set_current_window (old_wp);
    }

  return s;
}
Beispiel #2
0
/* compile, link, and load the file */
static void process_file(struct astr *sfilename) {
	char *dsofile;
	struct dso_entry *entry;
	int r;
	fprintf(stderr, PROCTEXT("Compiling %s...\n"), astr_cstr(sfilename));
	dsofile = compile(sfilename);
	if(dsofile == NULL) {
		fprintf(stderr, ERRORTEXT("Compilation failed.\n"));
		return;
	}
	fprintf(stderr, SUCCESSTEXT("Compilation succeeded.\n"));
	fprintf(stderr, PROCTEXT("Loading %s...\n"), dsofile);
	entry = load(dsofile);
	if(entry == NULL) {
		fprintf(stderr, ERRORTEXT("Load failed.\n"));
		r = unlink(dsofile);
		if(r != 0) {
			fprintf(stderr, ERRORTEXT("Failed to unlink %s")
			        ": %s\n", dsofile, strerror(errno));
		}
		return;
	}
	fprintf(stderr, SUCCESSTEXT("Load succeeded.\n"));
	run(entry);
}
Beispiel #3
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;
}
Beispiel #4
0
static int
find_substr (astr as, const char *s2, size_t s2size, size_t from, size_t to,
             bool forward, bool notbol, bool noteol, bool regex, bool icase)
{
  int ret = -1;
  struct re_pattern_buffer pattern;
  struct re_registers search_regs;
  reg_syntax_t syntax = RE_SYNTAX_EMACS;

  memset (&pattern, 0, sizeof (pattern));

  if (!regex)
    syntax |= RE_PLAIN;
  if (icase)
    syntax |= RE_ICASE;
  re_set_syntax (syntax);
  search_regs.num_regs = 1;

  re_find_err = re_compile_pattern (s2, (int) s2size, &pattern);
  pattern.not_bol = notbol;
  pattern.not_eol = noteol;
  if (!re_find_err)
    ret = re_search (&pattern, astr_cstr (as), (int) astr_len (as), forward ? from : to - 1,
                     forward ? (to - from) : -(to - 1 - from), &search_regs);

  if (ret >= 0)
    {
      ret = forward ? search_regs.end[0] : ret;
      free (search_regs.start);
      free (search_regs.end);
    }

  regfree (&pattern);
  return ret;
}
Beispiel #5
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);
}
Beispiel #6
0
Datei: file.c Projekt: M1lan/zile
/*
 * Return a `~/foo' like path if the user is under his home directory,
 * else the unmodified path.
 */
astr
compact_path (astr path)
{
  struct passwd *pw = getpwuid (getuid ());

  if (pw != NULL)
    {
      /* Replace `/userhome/' (if found) with `~/'. */
      size_t homelen = strlen (pw->pw_dir);
      if (homelen > 0 && pw->pw_dir[homelen - 1] == '/')
        homelen--;

      if (astr_len (path) > homelen &&
          !strncmp (pw->pw_dir, astr_cstr (path), homelen) &&
          astr_get (path, homelen) == '/')
        astr_cpy (path, astr_cat_cstr (astr_new_cstr ("~/"),
                                       astr_cstr (path) + homelen + 1));
    }

  return path;
}
Beispiel #7
0
static void
draw_status_line (size_t line, Window wp)
{
  term_attrset (FONT_REVERSE);

  term_move (line, 0);
  for (size_t i = 0; i < get_window_ewidth (wp); ++i)
    term_addstr ("-");

  const char *eol_type;
  if (get_buffer_eol (cur_bp) == coding_eol_cr)
    eol_type = "(Mac)";
  else if (get_buffer_eol (cur_bp) == coding_eol_crlf)
    eol_type = "(DOS)";
  else
    eol_type = ":";

  term_move (line, 0);
  size_t n = offset_to_line (get_window_bp (wp), window_o (wp));
  astr as = astr_fmt ("--%s%2s  %-15s   %s %-9s (Fundamental",
                      eol_type, make_mode_line_flags (wp), get_buffer_name (get_window_bp (wp)),
                      make_screen_pos (wp), astr_cstr (astr_fmt ("(%zu,%zu)", n + 1,
                                                                 get_goalc_bp (get_window_bp (wp), window_o (wp)))));

  if (get_buffer_autofill (get_window_bp (wp)))
    astr_cat_cstr (as, " Fill");
  if (thisflag & FLAG_DEFINING_MACRO)
    astr_cat_cstr (as, " Def");
  if (get_buffer_isearch (get_window_bp (wp)))
    astr_cat_cstr (as, " Isearch");

  astr_cat_char (as, ')');
  term_addstr (astr_cstr (as));

  term_attrset (FONT_NORMAL);
}
Beispiel #8
0
/*
 * This function calculates the best start column to draw if the line
 * needs to get truncated.
 * Called only for the line where is the point.
 */
static void calculate_start_column(Window *wp)
{
  size_t col = 0, lastcol = 0, t = tab_width(wp->bp);
  int rpfact, lpfact;
  char *buf, *rp, *lp, *p;
  Point pt = window_pt(wp);

  rp = astr_char(pt.p->item, (ptrdiff_t)pt.o);
  rpfact = pt.o / (wp->ewidth / 3);

  for (lp = rp; lp >= astr_cstr(pt.p->item); --lp) {
    for (col = 0, p = lp; p < rp; ++p)
      if (*p == '\t') {
        col |= t - 1;
        ++col;
      } else if (isprint(*p))
        ++col;
      else {
        col += make_char_printable(&buf, (size_t)*p);
        free(buf);
      }

    lpfact = (lp - astr_cstr(pt.p->item)) / (wp->ewidth / 3);

    if (col >= wp->ewidth - 1 || lpfact < (rpfact - 2)) {
      wp->start_column = lp + 1 - astr_cstr(pt.p->item);
      point_screen_column = lastcol;
      return;
    }

    lastcol = col;
  }

  wp->start_column = 0;
  point_screen_column = col;
}
Beispiel #9
0
/*
 * Revert an action.  Return the next undo entry.
 */
static Undo *
revert_action (Undo * up)
{
  size_t i;
  Point pt;

  pt.n = up->n;
  pt.o = up->o;

  doing_undo = true;

  if (up->type == UNDO_END_SEQUENCE)
    {
      undo_save (UNDO_START_SEQUENCE, pt, 0, 0);
      up = up->next;
      while (up->type != UNDO_START_SEQUENCE)
        up = revert_action (up);
      pt.n = up->n;
      pt.o = up->o;
      undo_save (UNDO_END_SEQUENCE, pt, 0, 0);
      goto_point (pt);
      return up->next;
    }

  goto_point (pt);

  if (up->type == UNDO_REPLACE_BLOCK)
    {
      undo_save (UNDO_REPLACE_BLOCK, pt,
                 up->block.size, up->block.osize);
      undo_nosave = true;
      for (i = 0; i < up->block.size; ++i)
        delete_char ();
      insert_nstring (astr_cstr (up->block.text), up->block.osize);
      undo_nosave = false;
    }

  doing_undo = false;

  if (up->unchanged)
    set_buffer_modified (cur_bp, false);

  return up->next;
}
Beispiel #10
0
struct le *parseInFile(getcCallback getachar, ungetcCallback ungetachar, struct le *list, int *line)
{
  astr tok;
  enum tokenname tokenid;
  int isquoted = 0;

  assert(getachar && ungetachar);

  while (1) {
    tok = snagAToken(getachar, ungetachar, &tokenid);

    switch (tokenid) {
    case T_QUOTE:
      isquoted = 1;
      break;

    case T_OPENPAREN:
      list = leAddBranchElement(list,
                                parseInFile(getachar, ungetachar, NULL, line),
                                isquoted);
      isquoted = 0;
      break;

    case T_NEWLINE:
      isquoted = 0;
      *line = *line +1;
      break;

    case T_WORD:
      list = leAddDataElement(list, astr_cstr(tok), isquoted);
      isquoted = 0;
      break;

    case T_CLOSEPAREN:
    case T_EOF:
      isquoted = 0;
      astr_delete(tok);
      return list;
    }

    astr_delete(tok);
  }
}
Beispiel #11
0
/* set up the appropriate watch on the directory indicated by sfilename */
static int setup_inotify_watch(int notify_fd, struct astr *sfilename) {
	char dirbuf[astr_len(sfilename) + 1];
	char *dir;
	int dwatch;

	strcpy(dirbuf, astr_cstr(sfilename));
	dir = dirname(dirbuf);
	
	dwatch = inotify_add_watch(notify_fd, dir,
	                           IN_CLOSE_WRITE|IN_MOVED_TO);
	if(dwatch <= 0) {
		fprintf(stderr,
		        ERRORTEXT("Fatal: failed to add inotify watch for %s")
		        ": %s\n", dir, strerror(errno));
		exit(EXIT_FAILURE);
	}

	return dwatch;
}
Beispiel #12
0
Datei: file.c Projekt: 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;
}
Beispiel #13
0
Datei: file.c Projekt: M1lan/zile
  return true;
}

DEFUN_ARGS ("find-file", find_file,
            STR_ARG (filename))
/*+
Edit file @i{filename}.
Switch to a buffer visiting file @i{filename},
creating one if none already exists.
+*/
{
  STR_INIT (filename)
  else
    {
      filename = minibuf_read_filename ("Find file: ",
                                        astr_cstr (get_buffer_dir (cur_bp)), NULL);

      if (filename == NULL)
        ok = FUNCALL (keyboard_quit);
    }

  if (filename == NULL || astr_len (filename) == 0)
    ok = leNIL;

  if (ok != leNIL)
    ok = bool_to_lisp (find_file (astr_cstr (filename)));
}
END_DEFUN

DEFUN ("find-file-read-only", find_file_read_only)
/*+
Beispiel #14
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;
}
Beispiel #15
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;
    }
}
Beispiel #16
0
void watch_file() {
	int r;
	int notify_fd;
	int dwatch;
	struct astr *sfilename;
	char *file;
	uint8_t inotify_buf[sizeof(struct inotify_event) + NAME_MAX + 1];
	struct inotify_event *event;
	size_t i;
	ssize_t len;

	/* initialize the inotify system */
	notify_fd = inotify_init();
	if(notify_fd < 0) {
		perror(ERRORTEXT("Fatal: failed to initialize "
		                 "inotify system"));
		exit(EXIT_FAILURE);
	}

setup_watch:
	/* set up the inotify watch and associated variables */
	sfilename = (struct astr *) arcp_load(&livec_opts.filename);
	if(sfilename == NULL) {
		fprintf(stderr, ERRORTEXT("Fatal: no filename defined\n"));
		exit(EXIT_FAILURE);
	}

	file = simple_basename(astr_cstr(sfilename));

	dwatch = setup_inotify_watch(notify_fd, sfilename);

	/* process the file */
	process_file(sfilename);

	/* main watch loop */
	for(;;) {
		if(sfilename != (struct astr *)
		   arcp_load_phantom(&livec_opts.filename)) {
			/* the filename option has changed; remove the watch
 			 * and restart */
			arcp_release(sfilename);
			r = inotify_rm_watch(notify_fd, dwatch);
			if(r != 0) {
				perror(ERRORTEXT("Failed to clean"
				                 " up old watch"));
			}
			goto setup_watch;
		}
		/* block until there's at least one event to be notified
 		 * about */
		len = read(notify_fd, inotify_buf,
		           sizeof(struct inotify_event) + NAME_MAX + 1);
		if(len <= 0) {
			perror(ERRORTEXT("read() of inotify event failed"));
			continue;
		}
		/* read through all events */
		for(i = 0; i <= len - sizeof(struct inotify_event);) {
			event = (struct inotify_event *) &inotify_buf[i];
			i += sizeof(struct inotify_event) + event->len;

			if(event->wd != dwatch) {
				/* FIXME: why wouldn't this be the same? */
				continue;
			}
			if((event->mask & (IN_CLOSE_WRITE|IN_MOVED_TO))
			   && (strcmp(event->name, file) == 0)) {
				/* FIXME: do we need the event mask test
 				 * here? */
				/* the (directory) event was about the file
 				 * we're interested in */
				process_file(sfilename);
				break;
			}
		}
	}
}
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;
}