Logo Search packages:      
Sourcecode: jove version File versions

ask.c

/**************************************************************************
 * This program is Copyright (C) 1986-2002 by Jonathan Payne.  JOVE is    *
 * provided by Jonathan and Jovehacks without charge and without          *
 * warranty.  You may copy, modify, and/or distribute JOVE, provided that *
 * this notice is included in all the source files and documentation.     *
 **************************************************************************/

#include "jove.h"
#include "jctype.h"
#include "chars.h"
#include "disp.h"
#include "fp.h"
#include "scandir.h"
#include "screen.h"     /* for Placur */
#include "ask.h"
#include "delete.h"
#include "insert.h"
#include "extend.h"
#include "fmt.h"
#include "marks.h"
#include "move.h"

#ifdef MAC
# include "mac.h"
#else /* !MAC */
# ifdef F_COMPLETION
#  include <sys/stat.h>
# endif
#endif /* !MAC */

ZXchar      AbortChar = CTL('G');   /* VAR: cancels command input */

bool  Asking = NO;
int   AskingWidth;

char  Minibuf[LBSIZE];
private LinePtr   CurAskPtr = NULL; /* points at some line in mini-buffer */
private Buffer    *AskBuffer = NULL;      /* Askbuffer points to actual structure */

/* The way the mini-buffer works is this:  The first line of the mini-buffer
 * is where the user does his stuff.  The rest of the buffer contains
 * strings that the user often wants to use, for instance, file names, or
 * common search strings, etc.  If he types ^N or ^P while in ask(), we
 * bump the point up or down a line and extract the contents (we make sure
 * is somewhere in the mini-buffer).
 */
private Buffer *
get_minibuf()
{
      if (AskBuffer) {        /* make sure it still exists */
            register Buffer   *b;

            for (b = world; b != NULL; b = b->b_next)
                  if (b == AskBuffer)
                        return b;
      }
      AskBuffer = do_select((Window *)NULL, "*minibuf*");
      AskBuffer->b_type = B_SCRATCH;
      return AskBuffer;
}

/* Add a string to the mini-buffer. */

void
minib_add(str, movedown)
char  *str;
bool  movedown;
{
      register Buffer   *saveb = curbuf;

      SetBuf(get_minibuf());
      LineInsert(1);
      ins_str(str);
      if (movedown)
            CurAskPtr = curline;
      SetBuf(saveb);
}

bool  InRealAsk = NO;

private const char *
real_ask(delim, d_proc, def, prompt)
const char  *delim,
      *def,
      *prompt;
bool  (*d_proc) ptrproto((ZXchar));
{
      jmp_buf     savejmp;
      ZXchar      c;
      int   prompt_len;
      Buffer      *saveb = curbuf;
      volatile bool     aborted = NO;
      bool  no_typed = NO;
      const data_obj    *push_cmd = LastCmd;
      int   saved_as, saved_ac;

#ifdef MAC
      menus_off();
#endif
      if (InRealAsk)
            complain((char *) NULL);
      save_arg(saved_as, saved_ac);
      push_env(savejmp);
      InRealAsk = YES;
      SetBuf(get_minibuf());
      if (!inlist(AskBuffer->b_first, CurAskPtr))
            CurAskPtr = curline;
      prompt_len = strlen(prompt);
      ToFirst();  /* Beginning of buffer. */
      linebuf[0] = '\0';
      modify();
      makedirty(curline);

      if (setjmp(mainjmp)) {
            if (InJoverc) {         /* this is a kludge */
                  aborted = YES;
                  goto cleanup;
            }
      }

      this_cmd = OTHER_CMD;   /* probably redundant */
      for (;;) {
            cmd_sync();
            s_mess("%s%s", prompt, linebuf);
            Asking = YES;
            AskingWidth = curchar + prompt_len;
            c = getch();
            if (c != '\0' && strchr(delim, c) != NULL) {
                  if (d_proc == NULL_ASK_EXT || !(*d_proc)(c))
                        break;
            } else if (c == AbortChar) {
                  message("[Aborted]");
                  aborted = YES;
                  break;
            } else switch (c) {
            case CTL('N'):
            case CTL('P'):
                  if (CurAskPtr != NULL) {
                        int   n = (c == CTL('P') ? -arg_value() : arg_value());

                        CurAskPtr = next_line(CurAskPtr, n);
                        if (CurAskPtr == curbuf->b_first && CurAskPtr->l_next != NULL)
                              CurAskPtr = CurAskPtr->l_next;
                        (void) ltobuf(CurAskPtr, linebuf);
                        modify();
                        makedirty(curline);
                        Eol();
                        this_cmd = OTHER_CMD;
                  }
                  break;

            case CTL('R'):
                  if (def != NULL)
                        ins_str(def);
                  else
                        rbell();
                  break;

            default:
                  dispatch(c);
                  break;
            }
            if (curbuf != AskBuffer)
                  SetBuf(AskBuffer);
            if (curline != curbuf->b_first) {
                  CurAskPtr = curline;
                  curline = curbuf->b_first;    /* with whatever is in linebuf */
            }
      }
cleanup:
      pop_env(savejmp);

      LastCmd = push_cmd;
      restore_arg(saved_as, saved_ac);
      no_typed = (linebuf[0] == '\0');
      strcpy(Minibuf, linebuf);
      SetBuf(saveb);
      InRealAsk = Asking = Interactive = NO;
      if (!aborted) {
            if (!PreEmptOutput()) {
                  Placur(ILI, 0);
                  flushscreen();
            }
            if (no_typed)
                  return NULL;
      } else
            complain((char *)NULL);
      return Minibuf;
}

#ifdef STDARGS
const char *
ask(const char *def, const char *fmt, ...)
#else
/*VARARGS2*/ const char *
ask(def, fmt, va_alist)
      const char  *def;
      const char  *fmt;
      va_dcl
#endif
{
      char  prompt[128];
      const char  *ans;
      va_list     ap;

      va_init(ap, fmt);
      format(prompt, sizeof prompt, fmt, ap);
      va_end(ap);
      ans = real_ask("\r\n", NULL_ASK_EXT, def, prompt);
      if (ans == NULL) {            /* Typed nothing. */
            if (def == NULL)
                  complain("[No default]");
            ans = def;
      }
      return ans;
}

#ifdef STDARGS
const char *
do_ask(const char *delim, bool (*d_proc) ptrproto((ZXchar)), const char *def, const char *fmt, ...)
#else
/*VARARGS4*/ const char *
do_ask(delim, d_proc, def, fmt, va_alist)
      const char  *delim;
      bool  (*d_proc) ptrproto((ZXchar));
      const char  *def;
      const char  *fmt;
      va_dcl
#endif
{
      char  prompt[128];
      va_list     ap;

      va_init(ap, fmt);
      format(prompt, sizeof prompt, fmt, ap);
      va_end(ap);
      return real_ask(delim, d_proc, def, prompt);
}

bool  OneKeyConfirmation = NO;      /* VAR: single y or n keystroke sufficient? */

#ifdef STDARGS
bool
yes_or_no_p(const char *fmt, ...)
#else
/*VARARGS1*/ bool
yes_or_no_p(fmt, va_alist)
      const char  *fmt;
      va_dcl
#endif
{
      char  prompt[128];
      va_list     ap;

      va_init(ap, fmt);
      format(prompt, sizeof prompt, fmt, ap);
      va_end(ap);

      for (;;) {
            if (OneKeyConfirmation) {
                  ZXchar      c;

                  message(prompt);
                  Asking = YES;     /* so redisplay works */
                  AskingWidth = strlen(prompt);
                  c = getch();
                  Asking = NO;
                  if (c == AbortChar)
                        complain("[Aborted]");
                  switch (CharUpcase(c)) {
                  case 'Y':
                        return YES;

                  case 'N':
                        return NO;

                  default:
                        add_mess("[Type Y or N]");
                        Asking = YES;     /* so cursor sits on question */
                        SitFor(10);
                        break;
                  }
            } else {
                  const char *ans = ask((char *) NULL, prompt);

                  if (caseeqn(ans, "yes", strlen(ans)))
                        return YES;

                  if (caseeqn(ans, "no", strlen(ans)))
                        return NO;

                  rbell();
            }
      }
      /* NOTREACHED */
}

#ifdef F_COMPLETION

/* look for any substrings of the form $foo in linebuf, and expand
 * them according to their value in the environment (if possible) -
 * this munges all over curchar and linebuf without giving it a second
 * thought (I must be getting lazy in my old age)
 */
# ifndef MAC      /* no environment in MacOS */

bool  DoEVexpand = YES; /* VAR: should we expand evironment variables? */

private void
EVexpand()
{
      register char     c;
      register char     *lp = linebuf,
                  *ep;
      char  varname[128],
            *vp,
            *lp_start;
      Mark  *m = MakeMark(curline, curchar);

      while ((c = *lp++) != '\0') {
            if (c == '$') {
                  lp_start = lp - 1;      /* the $ */
                  vp = varname;
                  /* Pick up env. variable name ([a-zA-Z0-9_]*) */
                  while (('a' <= (c = *lp++) && c <= 'z')
                  || ('A' <= c && c <= 'Z')
                  || ('0' <= c && c <= '9')
                  || c == '_')
                        *vp++ = c;
                  *vp = '\0';
                  /* if we find an env. variable with the right
                   * name, we insert it in linebuf, and then delete
                   * the variable name that we're replacing - and
                   * then we continue in case there are others ...
                   */
                  if ((ep = getenv(varname)) != NULL) {
                        curchar = lp_start - linebuf;
                        ins_str(ep);
                        del_char(FORWARD, (int)strlen(varname) + 1, NO);
                        lp = linebuf + curchar;
                  }
            }
      }
      ToMark(m);
      DelMark(m);
}

# endif /* !MAC */

private char      *fc_filebase;
bool  DispBadFs = YES;  /* VAR: display filenames with bad extensions? */

char  BadExtensions[sizeof(BadExtensions)] =    /* VAR: extensions to ignore */
# ifndef MSFILESYSTEM
/* .o: object code format
 * .Z .gz: compressed formats
 * .tar .zip .zoo: archive formats
 * .gif .jpg .jpeg .mpg .mpeg .tif .tiff .rgb: graphics formats
 * .dvi: TeX or ditroff output
 * .elc: compiled GNU EMACS code
 */
".o .Z .gz .tar .zip .zoo .gif .jpg .jpeg .mpg .mpeg .tif .tiff .rgb .dvi .elc";
# else /* MSFILESYSTEM */
/* .obj .lib .exe .com .dll: object code files
 * .arc .zip .zoo: archive formats
 * .bmp .gif .jpg .mpg .tif .pcx: graphics formats
 * .wks .wk1 .xls: spreadsheet formats
 */
".obj .lib .exe .com .dll .arc .zip .zoo .bmp .gif .jpg .mpg .tif .pcx .wks .wk1 .xls";
# endif /* MSFILESYSTEM */

private bool
bad_extension(name)
char  *name;
{
      char  *ip,
            *bads;
      size_t      namelen = strlen(name),
            ext_len;

# if defined(UNIX) || defined(MSFILESYSTEM)
#  ifdef DIRECTORY_ADD_SLASH
      if (strcmp(name, "./")==0 || strcmp(name, "../")==0)
            return YES;
#  else
      if (strcmp(name, ".")==0 || strcmp(name, "..")==0)
            return YES;
#  endif /* DIRECTORY_ADD_SLASH */
# endif /* UNIX || MSFILESYSTEM */
      for (ip=bads=BadExtensions; *ip!='\0'; bads = ip+1) {
            if ((ip = strchr(bads, ' ')) == NULL)
                  ip = bads + strlen(bads);
            ext_len = ip - bads;
            if (ext_len != 0 && ext_len < namelen
# ifdef FILENAME_CASEINSENSITIVE
            && caseeqn(&name[namelen - ext_len], bads, ext_len))
# else
            && strncmp(&name[namelen - ext_len], bads, ext_len) == 0)
# endif
                  return YES;
      }
      return NO;
}

private bool f_match proto((char* file)); /* needed to comfort dumb MS Visual C */

private bool
f_match(file)
char  *file;
{
      size_t      len = strlen(fc_filebase);

      if (!DispBadFs && bad_extension(file))
            return NO;

      return len == 0
# ifdef FILENAME_CASEINSENSITIVE
            || caseeqn(file, fc_filebase, len);
# else
            || strncmp(file, fc_filebase, len) == 0;
# endif
}

# ifndef DIRECTORY_ADD_SLASH
private bool
isdir(name)
char  *name;
{
      (void) do_stat(name, (Buffer *) NULL, DS_DIR);
      return was_dir;
}
# endif /* !DIRECTORY_ADD_SLASH */

private void
fill_in(dir_vec, n)
register char     **dir_vec;
int   n;
{
      bool  filter;

      for (filter = DispBadFs; ; filter = NO) {
            int
                  minmatch = 0,
                  numfound = 0,
                  lastmatch = -1,
                  i;

            for (i = 0; i < n; i++) {
                  /* If filter is NO, we accept all entries
                   * (either DispBadFs is NO, in which case filtering
                   * was done when dir_vec was built, or DispBadFs
                   * is YES, but we are trying again, after finding
                   * no files passed the filter.
                   */
                  if (!filter || !bad_extension(dir_vec[i])) {
                        minmatch = numfound == 0
                              ? (int)strlen(dir_vec[i])
#ifdef FILENAME_CASEINSENSITIVE
                              : min(minmatch, numcompcase(dir_vec[lastmatch], dir_vec[i]));
#else
                              : min(minmatch, numcomp(dir_vec[lastmatch], dir_vec[i]));
#endif
                        lastmatch = i;
                        numfound += 1;
                  }
            }
            if (numfound == 0) {
                  if (!filter) {
                        /* hopeless: complain and give up */
                        rbell();
                        break;
                  }
            } else {
                  bool
                        the_same = YES; /* After filling in, are we the same
                                          as when we were called? */

                  if (minmatch > (int)strlen(fc_filebase)) {
                        if (minmatch >= LBSIZE - (fc_filebase - linebuf))
                              len_error(JMP_COMPLAIN);
                        the_same = NO;
                        null_ncpy(fc_filebase, dir_vec[lastmatch], (size_t) minmatch);
                        modify();
                        makedirty(curline);
                  }
                  Eol();
# ifndef DIRECTORY_ADD_SLASH
                  if (numfound == 1 && fc_filebase[0] != '\0' && isdir(linebuf)) {
                        the_same = NO;
                        insert_c('/', 1);
                  }
# endif
                  if (the_same) {
                        add_mess(" [%s%s]",
                              !filter && DispBadFs? "!" : NullStr,
                              numfound == 1? "Unique" : "Ambiguous");
                        SitFor(7);
                  }
                  break;      /* we're done */
            }
      }
}

/* called from do_ask() when one of "\r\n ?" is typed.  Does the right
 * thing, depending on which.
 */
private bool
f_complete(c)
ZXchar      c;
{
      char
            dir[FILESIZE],
            **dir_vec;
      int
            nentries;

# ifndef MAC      /* no environment in MacOS */
      if (DoEVexpand)
            EVexpand();
# endif
      if (c == CR || c == LF)
            return NO;  /* tells ask to return now */

      fc_filebase = strrchr(linebuf, '/');
# ifdef MSFILESYSTEM
      {
            char  *p = strrchr(fc_filebase == NULL? linebuf : fc_filebase, '\\');

            if (p != NULL)
                  fc_filebase = p;
            if (fc_filebase == NULL)
                  fc_filebase = strrchr(linebuf, ':');
      }
# endif /* MSFILESYSTEM */
      if (fc_filebase != NULL) {
            char  tmp[FILESIZE];

            fc_filebase += 1;
            if ((size_t) (fc_filebase - linebuf) >= sizeof(tmp))
                  len_error(JMP_COMPLAIN);
            null_ncpy(tmp, linebuf, (size_t) (fc_filebase - linebuf));
            if (tmp[0] == '\0')
                  strcpy(tmp, "/");
            PathParse(tmp, dir);
      } else {
            fc_filebase = linebuf;
            strcpy(dir, ".");
      }
      if ((nentries = jscandir(dir, &dir_vec, f_match, fnamecomp)) == -1) {
            add_mess(" [Unknown directory: %s]", dir);
            SitFor(7);
            return YES;
      }
      if (nentries == 0) {
            add_mess(" [No match]");
            SitFor(7);
      } else if (jiswhite(c)) {
            fill_in(dir_vec, nentries);
      } else {
            /* we're a '?' */
            int
                  i,
                  maxlen = 0,
                  ncols,
                  line,
                  entriespercol;

            TOstart("Completion");
            Typeout("(! means file will not be chosen unless typed explicitly)");
            Typeout("Possible completions (in %s):", dir);

            for (i = 0; i < nentries; i++)
                  maxlen = max((int)strlen(dir_vec[i]), maxlen);
            maxlen += 4;      /* pad each column with at least 4 spaces */
            ncols = min((CO - 2), MAX_TYPEOUT) / maxlen;
            entriespercol = (nentries + ncols - 1) / ncols;

            for (line = 0; line < entriespercol; line++) {
                  int
                        col,
                        which = line,
                        bufcol = 0;
                  char  buf[MAX_TYPEOUT + 1];

                  for (col = 0; col < ncols; col++) {
                        if (which < nentries) {
                              bool  isbad = DispBadFs && bad_extension(dir_vec[which]);

                              swritef(buf+bufcol, sizeof(buf) - bufcol, "%s%-*s",
                                    isbad ? "!" : NullStr,
                                    maxlen - isbad, dir_vec[which]);
                        }
                        which += entriespercol;
                        bufcol += maxlen;
                  }
                  Typeout("%s", buf);
            }
            TOstop();
      }
      freedir(&dir_vec, nentries);
      return YES;
}

#endif /* F_COMPLETION */

/* ask for file or directory
 *
 * These are only different under MSFILESYSTEM.
 * Note: def and buf may be equal.
 */

#ifdef MSFILESYSTEM
private
#endif
char *
ask_ford(prmt, def, buf)
const char  *prmt;
char
      *def,
      *buf;
{
      /* Note: pr_name yields a pointer into its static buffer so
       * pretty_name will not be a pointer into def.  This allows
       * us to accept *def and *buf being aliases.  Otherwise we
       * could end up calling PathParse with aliases.  (Yes, you
       * are looking at a battle scar.)
       */
      char
            *pretty_name = pr_name(def, YES),
            prompt[128];
      const char
            *ans;

      if (prmt == NULL)
            swritef(prompt, sizeof(prompt), ProcFmt);
      else
            jamstr(prompt, prmt);
      if (def != NULL && *def != '\0') {
            size_t      pl = strlen(prompt);

            swritef(prompt + pl, sizeof(prompt)-pl, "(default %s) ", pretty_name);
            if ((int)strlen(prompt) * 2 >= CO)
                  prompt[pl] = '\0';
      }
#ifdef F_COMPLETION
      ans = real_ask("\r\n \t?", f_complete, pretty_name, prompt);
      /* note: *pretty_name may have been overwritten -- we can't use it */
      if (ans == NULL && (ans = pr_name(def, YES)) == NULL)
            complain("[No default file name]");
#else
      ans = ask(pretty_name, prompt);
#endif
      PathParse(ans, buf);

      return buf;
}

#ifdef MSFILESYSTEM
char *
ask_file(prmt, def, buf)
const char  *prmt;
char
      *def,
      *buf;
{
      MatchDir = NO;
      return ask_ford(prmt, def, buf);
}

char *
ask_dir(prmt, def, buf)
const char  *prmt;
char
      *def,
      *buf;
{
      MatchDir = YES;
      return ask_ford(prmt, def, buf);
}
#endif

Generated by  Doxygen 1.6.0   Back to index