/*
 * CSCOPE support for Vim added by Andy Kahn <kahn@zk3.dec.com>
 * Ported to Win32 by Sergey Khorev <sergey.khorev@gmail.com>
 *
 * The basic idea/structure of cscope for Vim was borrowed from Nvi.  There
 * might be a few lines of code that look similar to what Nvi has.
 *
 * See README.txt for an overview of the Vim source code.
 */
#include <stdbool.h>

#include <assert.h>
#include <errno.h>
#include <inttypes.h>

#include "nvim/vim.h"
#include "nvim/ascii.h"
#include "nvim/if_cscope.h"
#include "nvim/charset.h"
#include "nvim/eval.h"
#include "nvim/fileio.h"
#include "nvim/message.h"
#include "nvim/misc2.h"
#include "nvim/memory.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
#include "nvim/quickfix.h"
#include "nvim/strings.h"
#include "nvim/tag.h"
#include "nvim/tempfile.h"
#include "nvim/window.h"
#include "nvim/os/os.h"
#include "nvim/os/input.h"

#include <sys/types.h>
#include <sys/stat.h>
#if defined(UNIX)
# include <sys/wait.h>
#endif
#include "nvim/if_cscope_defs.h"

#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "if_cscope.c.generated.h"
#endif

static csinfo_T *   csinfo = NULL;
static int csinfo_size = 0;             /* number of items allocated in
                                           csinfo[] */

static int eap_arg_len;             /* length of eap->arg, set in
                                       cs_lookup_cmd() */
static cscmd_T cs_cmds[] =
{
  { "add",    cs_add,
    N_("Add a new database"),     "add file|dir [pre-path] [flags]", 0 },
  { "find",   cs_find,
    N_("Query for a pattern"),    "find c|d|e|f|g|i|s|t name", 1 },
  { "help",   cs_help,
    N_("Show this message"),      "help", 0 },
  { "kill",   cs_kill,
    N_("Kill a connection"),      "kill #", 0 },
  { "reset",  cs_reset,
    N_("Reinit all connections"), "reset", 0 },
  { "show",   cs_show,
    N_("Show connections"),       "show", 0 },
  { NULL, NULL, NULL, NULL, 0 }
};

static void cs_usage_msg(csid_e x)
{
  (void)EMSG2(_("E560: Usage: cs[cope] %s"), cs_cmds[(int)x].usage);
}


static enum {
  EXP_CSCOPE_SUBCMD,    /* expand ":cscope" sub-commands */
  EXP_SCSCOPE_SUBCMD,   /* expand ":scscope" sub-commands */
  EXP_CSCOPE_FIND,      /* expand ":cscope find" arguments */
  EXP_CSCOPE_KILL       /* expand ":cscope kill" arguments */
} expand_what;

/*
 * Function given to ExpandGeneric() to obtain the cscope command
 * expansion.
 */
char_u *get_cscope_name(expand_T *xp, int idx)
{
  int current_idx;
  int i;

  switch (expand_what) {
  case EXP_CSCOPE_SUBCMD:
    /* Complete with sub-commands of ":cscope":
     * add, find, help, kill, reset, show */
    return (char_u *)cs_cmds[idx].name;
  case EXP_SCSCOPE_SUBCMD:
    /* Complete with sub-commands of ":scscope": same sub-commands as
     * ":cscope" but skip commands which don't support split windows */
    for (i = 0, current_idx = 0; cs_cmds[i].name != NULL; i++)
      if (cs_cmds[i].cansplit)
        if (current_idx++ == idx)
          break;
    return (char_u *)cs_cmds[i].name;
  case EXP_CSCOPE_FIND:
  {
    const char *query_type[] =
    {
      "c", "d", "e", "f", "g", "i", "s", "t", NULL
    };

    /* Complete with query type of ":cscope find {query_type}".
     * {query_type} can be letters (c, d, ... t) or numbers (0, 1,
     * ..., 8) but only complete with letters, since numbers are
     * redundant. */
    return (char_u *)query_type[idx];
  }
  case EXP_CSCOPE_KILL:
  {
    static char connection[5];

    /* ":cscope kill" accepts connection numbers or partial names of
     * the pathname of the cscope database as argument.  Only complete
     * with connection numbers. -1 can also be used to kill all
     * connections. */
    for (i = 0, current_idx = 0; i < csinfo_size; i++) {
      if (csinfo[i].fname == NULL)
        continue;
      if (current_idx++ == idx) {
        vim_snprintf(connection, sizeof(connection), "%d", i);
        return (char_u *)connection;
      }
    }
    return (current_idx == idx && idx > 0) ? (char_u *)"-1" : NULL;
  }
  default:
    return NULL;
  }
}

/*
 * Handle command line completion for :cscope command.
 */
void set_context_in_cscope_cmd(expand_T *xp, char_u *arg, cmdidx_T cmdidx)
{
  char_u      *p;

  /* Default: expand subcommands */
  xp->xp_context = EXPAND_CSCOPE;
  xp->xp_pattern = arg;
  expand_what = (cmdidx == CMD_scscope)
                ? EXP_SCSCOPE_SUBCMD : EXP_CSCOPE_SUBCMD;

  /* (part of) subcommand already typed */
  if (*arg != NUL) {
    p = skiptowhite(arg);
    if (*p != NUL) {                /* past first word */
      xp->xp_pattern = skipwhite(p);
      if (*skiptowhite(xp->xp_pattern) != NUL)
        xp->xp_context = EXPAND_NOTHING;
      else if (STRNICMP(arg, "add", p - arg) == 0)
        xp->xp_context = EXPAND_FILES;
      else if (STRNICMP(arg, "kill", p - arg) == 0)
        expand_what = EXP_CSCOPE_KILL;
      else if (STRNICMP(arg, "find", p - arg) == 0)
        expand_what = EXP_CSCOPE_FIND;
      else
        xp->xp_context = EXPAND_NOTHING;
    }
  }
}


/*
 * PRIVATE: do_cscope_general
 *
 * Find the command, print help if invalid, and then call the corresponding
 * command function.
 */
static void 
do_cscope_general (
    exarg_T *eap,
    int make_split             /* whether to split window */
)
{
  cscmd_T *cmdp;

  if ((cmdp = cs_lookup_cmd(eap)) == NULL) {
    cs_help(eap);
    return;
  }

  if (make_split) {
    if (!cmdp->cansplit) {
      (void)MSG_PUTS(_(
              "This cscope command does not support splitting the window.\n"));
      return;
    }
    postponed_split = -1;
    postponed_split_flags = cmdmod.split;
    postponed_split_tab = cmdmod.tab;
  }

  cmdp->func(eap);

  postponed_split_flags = 0;
  postponed_split_tab = 0;
}

/*
 * PUBLIC: do_cscope
 */
void do_cscope(exarg_T *eap)
{
  do_cscope_general(eap, FALSE);
}

/*
 * PUBLIC: do_scscope
 *
 * same as do_cscope, but splits window, too.
 */
void do_scscope(exarg_T *eap)
{
  do_cscope_general(eap, TRUE);
}

/*
 * PUBLIC: do_cstag
 *
 */
void do_cstag(exarg_T *eap)
{
  int ret = FALSE;

  if (*eap->arg == NUL) {
    (void)EMSG(_("E562: Usage: cstag <ident>"));
    return;
  }

  switch (p_csto) {
  case 0:
    if (cs_check_for_connections()) {
      ret = cs_find_common("g", (char *)(eap->arg), eap->forceit, FALSE,
          FALSE, *eap->cmdlinep);
      if (ret == FALSE) {
        cs_free_tags();
        if (msg_col)
          msg_putchar('\n');

        if (cs_check_for_tags())
          ret = do_tag(eap->arg, DT_JUMP, 0, eap->forceit, FALSE);
      }
    } else if (cs_check_for_tags()) {
      ret = do_tag(eap->arg, DT_JUMP, 0, eap->forceit, FALSE);
    }
    break;
  case 1:
    if (cs_check_for_tags()) {
      ret = do_tag(eap->arg, DT_JUMP, 0, eap->forceit, FALSE);
      if (ret == FALSE) {
        if (msg_col)
          msg_putchar('\n');

        if (cs_check_for_connections()) {
          ret = cs_find_common("g", (char *)(eap->arg), eap->forceit,
              FALSE, FALSE, *eap->cmdlinep);
          if (ret == FALSE)
            cs_free_tags();
        }
      }
    } else if (cs_check_for_connections()) {
      ret = cs_find_common("g", (char *)(eap->arg), eap->forceit, FALSE,
          FALSE, *eap->cmdlinep);
      if (ret == FALSE)
        cs_free_tags();
    }
    break;
  default:
    break;
  }

  if (!ret) {
    (void)EMSG(_("E257: cstag: tag not found"));
    g_do_tagpreview = 0;
  }

} /* do_cscope */


/*
 * PUBLIC: cs_find
 *
 * this simulates a vim_fgets(), but for cscope, returns the next line
 * from the cscope output.  should only be called from find_tags()
 *
 * returns TRUE if eof, FALSE otherwise
 */
int cs_fgets(char_u *buf, int size)
{
  char *p;

  if ((p = cs_manage_matches(NULL, NULL, -1, Get)) == NULL)
    return TRUE;
  STRLCPY(buf, p, size);

  return FALSE;
} /* cs_fgets */


/*
 * PUBLIC: cs_free_tags
 *
 * called only from do_tag(), when popping the tag stack
 */
void cs_free_tags(void)
{
  cs_manage_matches(NULL, NULL, -1, Free);
}

/*
 * PUBLIC: cs_print_tags
 *
 * called from do_tag()
 */
void cs_print_tags(void)
{
  cs_manage_matches(NULL, NULL, -1, Print);
}

/*
 * "cscope_connection([{num} , {dbpath} [, {prepend}]])" function
 *
 *		Checks for the existence of a |cscope| connection.  If no
 *		parameters are specified, then the function returns:
 *
 *		0, if cscope was not available (not compiled in), or if there
 *		are no cscope connections; or
 *		1, if there is at least one cscope connection.
 *
 *		If parameters are specified, then the value of {num}
 *		determines how existence of a cscope connection is checked:
 *
 *		{num}	Description of existence check
 *		-----	------------------------------
 *		0	Same as no parameters (e.g., "cscope_connection()").
 *		1	Ignore {prepend}, and use partial string matches for
 *			{dbpath}.
 *		2	Ignore {prepend}, and use exact string matches for
 *			{dbpath}.
 *		3	Use {prepend}, use partial string matches for both
 *			{dbpath} and {prepend}.
 *		4	Use {prepend}, use exact string matches for both
 *			{dbpath} and {prepend}.
 *
 *		Note: All string comparisons are case sensitive!
 */
int cs_connection(int num, char_u *dbpath, char_u *ppath)
{
  int i;

  if (num < 0 || num > 4 || (num > 0 && !dbpath))
    return FALSE;

  for (i = 0; i < csinfo_size; i++) {
    if (!csinfo[i].fname)
      continue;

    if (num == 0)
      return TRUE;

    switch (num) {
    case 1:
      if (strstr(csinfo[i].fname, (char *)dbpath))
        return TRUE;
      break;
    case 2:
      if (strcmp(csinfo[i].fname, (char *)dbpath) == 0)
        return TRUE;
      break;
    case 3:
      if (strstr(csinfo[i].fname, (char *)dbpath)
          && ((!ppath && !csinfo[i].ppath)
              || (ppath
                  && csinfo[i].ppath
                  && strstr(csinfo[i].ppath, (char *)ppath))))
        return TRUE;
      break;
    case 4:
      if ((strcmp(csinfo[i].fname, (char *)dbpath) == 0)
          && ((!ppath && !csinfo[i].ppath)
              || (ppath
                  && csinfo[i].ppath
                  && (strcmp(csinfo[i].ppath, (char *)ppath) == 0))))
        return TRUE;
      break;
    }
  }

  return FALSE;
} /* cs_connection */


/*
 * PRIVATE functions
 ****************************************************************************/

/*
 * PRIVATE: cs_add
 *
 * add cscope database or a directory name (to look for cscope.out)
 * to the cscope connection list
 *
 * MAXPATHL 256
 */
static int cs_add(exarg_T *eap)
{
  char *fname, *ppath, *flags = NULL;

  if ((fname = strtok((char *)NULL, (const char *)" ")) == NULL) {
    cs_usage_msg(Add);
    return CSCOPE_FAILURE;
  }
  if ((ppath = strtok((char *)NULL, (const char *)" ")) != NULL)
    flags = strtok((char *)NULL, (const char *)" ");

  return cs_add_common(fname, ppath, flags);
}

static void cs_stat_emsg(char *fname)
{
  char *stat_emsg = _("E563: stat(%s) error: %d");
  char *buf = xmalloc(strlen(stat_emsg) + MAXPATHL + 10);

  (void)sprintf(buf, stat_emsg, fname, errno);
  (void)EMSG(buf);
  xfree(buf);
}


/*
 * PRIVATE: cs_add_common
 *
 * the common routine to add a new cscope connection.  called by
 * cs_add() and cs_reset().  i really don't like to do this, but this
 * routine uses a number of goto statements.
 */
static int 
cs_add_common (
    char *arg1,         /* filename - may contain environment variables */
    char *arg2,         /* prepend path - may contain environment variables */
    char *flags
)
{
  char        *fname = NULL;
  char        *fname2 = NULL;
  char        *ppath = NULL;
  int i;
  size_t usedlen = 0;
  char_u      *fbuf = NULL;

  /* get the filename (arg1), expand it, and try to stat it */
  fname = xmalloc(MAXPATHL + 1);

  expand_env((char_u *)arg1, (char_u *)fname, MAXPATHL);
  size_t len = STRLEN(fname);
  fbuf = (char_u *)fname;
  (void)modify_fname((char_u *)":p", &usedlen, (char_u **)&fname, &fbuf, &len);
  if (fname == NULL)
    goto add_err;
  fname = (char *)vim_strnsave((char_u *)fname, len);
  xfree(fbuf);
  FileInfo file_info;
  bool file_info_ok  = os_fileinfo(fname, &file_info);
  if (!file_info_ok) {
staterr:
    if (p_csverbose)
      cs_stat_emsg(fname);
    goto add_err;
  }

  // get the prepend path (arg2), expand it, and see if it exists
  if (arg2 != NULL) {
    ppath = xmalloc(MAXPATHL + 1);
    expand_env((char_u *)arg2, (char_u *)ppath, MAXPATHL);
    if (!os_file_exists((char_u *)ppath))
      goto staterr;
  }

  /* if filename is a directory, append the cscope database name to it */
  if ((file_info.stat.st_mode & S_IFMT) == S_IFDIR) {
    fname2 = (char *)xmalloc(strlen(CSCOPE_DBFILE) + strlen(fname) + 2);

    while (fname[strlen(fname)-1] == '/'
           ) {
      fname[strlen(fname)-1] = '\0';
      if (fname[0] == '\0')
        break;
    }
    if (fname[0] == '\0')
      (void)sprintf(fname2, "/%s", CSCOPE_DBFILE);
    else
      (void)sprintf(fname2, "%s/%s", fname, CSCOPE_DBFILE);

    file_info_ok = os_fileinfo(fname2, &file_info);
    if (!file_info_ok) {
      if (p_csverbose)
        cs_stat_emsg(fname2);
      goto add_err;
    }

    i = cs_insert_filelist(fname2, ppath, flags, &file_info);
  }
  else if (S_ISREG(file_info.stat.st_mode) || S_ISLNK(file_info.stat.st_mode))
  {
    i = cs_insert_filelist(fname, ppath, flags, &file_info);
  } else {
    if (p_csverbose)
      (void)EMSG2(
          _("E564: %s is not a directory or a valid cscope database"),
          fname);
    goto add_err;
  }

  if (i != -1) {
    if (cs_create_connection(i) == CSCOPE_FAILURE
        || cs_read_prompt(i) == CSCOPE_FAILURE) {
      cs_release_csp(i, TRUE);
      goto add_err;
    }

    if (p_csverbose) {
      msg_clr_eos();
      (void)smsg_attr(hl_attr(HLF_R),
            _("Added cscope database %s"),
            csinfo[i].fname);
    }
  }

  xfree(fname);
  xfree(fname2);
  xfree(ppath);
  return CSCOPE_SUCCESS;

add_err:
  xfree(fname2);
  xfree(fname);
  xfree(ppath);
  return CSCOPE_FAILURE;
} /* cs_add_common */


static int cs_check_for_connections(void)
{
  return cs_cnt_connections() > 0;
} /* cs_check_for_connections */

static int cs_check_for_tags(void)
{
  return p_tags[0] != NUL && curbuf->b_p_tags != NULL;
} /* cs_check_for_tags */

/*
 * PRIVATE: cs_cnt_connections
 *
 * count the number of cscope connections
 */
static int cs_cnt_connections(void)
{
  short i;
  short cnt = 0;

  for (i = 0; i < csinfo_size; i++) {
    if (csinfo[i].fname != NULL)
      cnt++;
  }
  return cnt;
} /* cs_cnt_connections */

static void 
cs_reading_emsg (
    int idx        /* connection index */
)
{
  EMSGN(_("E262: error reading cscope connection %" PRId64), idx);
}

#define CSREAD_BUFSIZE  2048
/*
 * PRIVATE: cs_cnt_matches
 *
 * count the number of matches for a given cscope connection.
 */
static int cs_cnt_matches(int idx)
{
  char *stok;
  int nlines;

  char *buf = xmalloc(CSREAD_BUFSIZE);
  for (;; ) {
    if (!fgets(buf, CSREAD_BUFSIZE, csinfo[idx].fr_fp)) {
      if (feof(csinfo[idx].fr_fp))
        errno = EIO;

      cs_reading_emsg(idx);

      xfree(buf);
      return -1;
    }

    /*
     * If the database is out of date, or there's some other problem,
     * cscope will output error messages before the number-of-lines output.
     * Display/discard any output that doesn't match what we want.
     * Accept "\S*cscope: X lines", also matches "mlcscope".
     */
    if ((stok = strtok(buf, (const char *)" ")) == NULL)
      continue;
    if (strstr((const char *)stok, "cscope:") == NULL)
      continue;

    if ((stok = strtok(NULL, (const char *)" ")) == NULL)
      continue;
    nlines = atoi(stok);
    if (nlines < 0) {
      nlines = 0;
      break;
    }

    if ((stok = strtok(NULL, (const char *)" ")) == NULL)
      continue;
    if (strncmp((const char *)stok, "lines", 5))
      continue;

    break;
  }

  xfree(buf);
  return nlines;
} /* cs_cnt_matches */


/*
 * PRIVATE: cs_create_cmd
 *
 * Creates the actual cscope command query from what the user entered.
 */
static char *cs_create_cmd(char *csoption, char *pattern)
{
  char *cmd;
  short search;
  char *pat;

  switch (csoption[0]) {
  case '0': case 's':
    search = 0;
    break;
  case '1': case 'g':
    search = 1;
    break;
  case '2': case 'd':
    search = 2;
    break;
  case '3': case 'c':
    search = 3;
    break;
  case '4': case 't':
    search = 4;
    break;
  case '6': case 'e':
    search = 6;
    break;
  case '7': case 'f':
    search = 7;
    break;
  case '8': case 'i':
    search = 8;
    break;
  default:
    (void)EMSG(_("E561: unknown cscope search type"));
    cs_usage_msg(Find);
    return NULL;
  }

  /* Skip white space before the patter, except for text and pattern search,
   * they may want to use the leading white space. */
  pat = pattern;
  if (search != 4 && search != 6)
    while (ascii_iswhite(*pat))
      ++pat;

  cmd = xmalloc(strlen(pat) + 2);

  (void)sprintf(cmd, "%d%s", search, pat);

  return cmd;
} /* cs_create_cmd */


/*
 * PRIVATE: cs_create_connection
 *
 * This piece of code was taken/adapted from nvi.  do we need to add
 * the BSD license notice?
 */
static int cs_create_connection(int i)
{
#ifdef UNIX
  int to_cs[2], from_cs[2];
#endif
  int len;
  char        *prog, *cmd, *ppath = NULL;

#if defined(UNIX)
  /*
   * Cscope reads from to_cs[0] and writes to from_cs[1]; vi reads from
   * from_cs[0] and writes to to_cs[1].
   */
  to_cs[0] = to_cs[1] = from_cs[0] = from_cs[1] = -1;
  if (pipe(to_cs) < 0 || pipe(from_cs) < 0) {
    (void)EMSG(_("E566: Could not create cscope pipes"));
err_closing:
    if (to_cs[0] != -1)
      (void)close(to_cs[0]);
    if (to_cs[1] != -1)
      (void)close(to_cs[1]);
    if (from_cs[0] != -1)
      (void)close(from_cs[0]);
    if (from_cs[1] != -1)
      (void)close(from_cs[1]);
    return CSCOPE_FAILURE;
  }

  switch (csinfo[i].pid = fork()) {
  case -1:
    (void)EMSG(_("E622: Could not fork for cscope"));
    goto err_closing;
  case 0:                               /* child: run cscope. */
    if (dup2(to_cs[0], STDIN_FILENO) == -1)
      PERROR("cs_create_connection 1");
    if (dup2(from_cs[1], STDOUT_FILENO) == -1)
      PERROR("cs_create_connection 2");
    if (dup2(from_cs[1], STDERR_FILENO) == -1)
      PERROR("cs_create_connection 3");

    /* close unused */
    (void)close(to_cs[1]);
    (void)close(from_cs[0]);
#else
  /* WIN32 */
  /* Create pipes to communicate with cscope */
  sa.nLength = sizeof(SECURITY_ATTRIBUTES);
  sa.bInheritHandle = TRUE;
  sa.lpSecurityDescriptor = NULL;

  if (!(pipe_stdin = CreatePipe(&stdin_rd, &stdin_wr, &sa, 0))
      || !(pipe_stdout = CreatePipe(&stdout_rd, &stdout_wr, &sa, 0))) {
    (void)EMSG(_("E566: Could not create cscope pipes"));
err_closing:
    if (pipe_stdin) {
      CloseHandle(stdin_rd);
      CloseHandle(stdin_wr);
    }
    if (pipe_stdout) {
      CloseHandle(stdout_rd);
      CloseHandle(stdout_wr);
    }
    return CSCOPE_FAILURE;
  }
#endif
    /* expand the cscope exec for env var's */
    prog = xmalloc(MAXPATHL + 1);
    expand_env(p_csprg, (char_u *)prog, MAXPATHL);

    /* alloc space to hold the cscope command */
    len = (int)(strlen(prog) + strlen(csinfo[i].fname) + 32);
    if (csinfo[i].ppath) {
      /* expand the prepend path for env var's */
      ppath = xmalloc(MAXPATHL + 1);
      expand_env((char_u *)csinfo[i].ppath, (char_u *)ppath, MAXPATHL);

      len += (int)strlen(ppath);
    }

    if (csinfo[i].flags)
      len += (int)strlen(csinfo[i].flags);

    cmd = xmalloc(len);

    /* run the cscope command; is there execl for non-unix systems? */
#if defined(UNIX)
    (void)sprintf(cmd, "exec %s -dl -f %s", prog, csinfo[i].fname);
#else
    /* WIN32 */
    (void)sprintf(cmd, "%s -dl -f %s", prog, csinfo[i].fname);
#endif
    if (csinfo[i].ppath != NULL) {
      (void)strcat(cmd, " -P");
      (void)strcat(cmd, csinfo[i].ppath);
    }
    if (csinfo[i].flags != NULL) {
      (void)strcat(cmd, " ");
      (void)strcat(cmd, csinfo[i].flags);
    }
# ifdef UNIX
    /* on Win32 we still need prog */
    xfree(prog);
# endif
    xfree(ppath);

#if defined(UNIX)
# if defined(HAVE_SETSID) || defined(HAVE_SETPGID)
    /* Change our process group to avoid cscope receiving SIGWINCH. */
#  if defined(HAVE_SETSID)
    (void)setsid();
#  else
    if (setpgid(0, 0) == -1)
      PERROR(_("cs_create_connection setpgid failed"));
#  endif
# endif
    if (execl("/bin/sh", "sh", "-c", cmd, (char *)NULL) == -1)
      PERROR(_("cs_create_connection exec failed"));

    stream_set_blocking(input_global_fd(), true);  // normalize stream (#2598)
    exit(127);
  /* NOTREACHED */
  default:      /* parent. */
    /*
     * Save the file descriptors for later duplication, and
     * reopen as streams.
     */
    if ((csinfo[i].to_fp = fdopen(to_cs[1], "w")) == NULL)
      PERROR(_("cs_create_connection: fdopen for to_fp failed"));
    if ((csinfo[i].fr_fp = fdopen(from_cs[0], "r")) == NULL)
      PERROR(_("cs_create_connection: fdopen for fr_fp failed"));

    /* close unused */
    (void)close(to_cs[0]);
    (void)close(from_cs[1]);

    break;
  }

#else
    /* WIN32 */
    /* Create a new process to run cscope and use pipes to talk with it */
    GetStartupInfo(&si);
    si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_HIDE;  /* Hide child application window */
    si.hStdOutput = stdout_wr;
    si.hStdError  = stdout_wr;
    si.hStdInput  = stdin_rd;
    created = CreateProcess(NULL, cmd, NULL, NULL, TRUE, CREATE_NEW_CONSOLE,
        NULL, NULL, &si, &pi);
    xfree(prog);
    xfree(cmd);

    if (!created) {
      PERROR(_("cs_create_connection exec failed"));
      (void)EMSG(_("E623: Could not spawn cscope process"));
      goto err_closing;
    }
    /* else */
    csinfo[i].pid = pi.dwProcessId;
    csinfo[i].hProc = pi.hProcess;
    CloseHandle(pi.hThread);

    /* TODO - tidy up after failure to create files on pipe handles. */
    if (((fd = _open_osfhandle((OPEN_OH_ARGTYPE)stdin_wr,
              _O_TEXT|_O_APPEND)) < 0)
        || ((csinfo[i].to_fp = _fdopen(fd, "w")) == NULL))
      PERROR(_("cs_create_connection: fdopen for to_fp failed"));
    if (((fd = _open_osfhandle((OPEN_OH_ARGTYPE)stdout_rd,
              _O_TEXT|_O_RDONLY)) < 0)
        || ((csinfo[i].fr_fp = _fdopen(fd, "r")) == NULL))
      PERROR(_("cs_create_connection: fdopen for fr_fp failed"));

    /* Close handles for file descriptors inherited by the cscope process */
    CloseHandle(stdin_rd);
    CloseHandle(stdout_wr);

#endif /* !UNIX */

  return CSCOPE_SUCCESS;
} /* cs_create_connection */


/*
 * PRIVATE: cs_find
 *
 * query cscope using command line interface.  parse the output and use tselect
 * to allow choices.  like Nvi, creates a pipe to send to/from query/cscope.
 *
 * returns TRUE if we jump to a tag or abort, FALSE if not.
 */
static int cs_find(exarg_T *eap)
{
  char *opt, *pat;
  int i;

  if (cs_check_for_connections() == FALSE) {
    (void)EMSG(_("E567: no cscope connections"));
    return FALSE;
  }

  if ((opt = strtok((char *)NULL, (const char *)" ")) == NULL) {
    cs_usage_msg(Find);
    return FALSE;
  }

  pat = opt + strlen(opt) + 1;
  if (pat >= (char *)eap->arg + eap_arg_len) {
    cs_usage_msg(Find);
    return FALSE;
  }

  /*
   * Let's replace the NULs written by strtok() with spaces - we need the
   * spaces to correctly display the quickfix/location list window's title.
   */
  for (i = 0; i < eap_arg_len; ++i)
    if (NUL == eap->arg[i])
      eap->arg[i] = ' ';

  return cs_find_common(opt, pat, eap->forceit, TRUE,
      eap->cmdidx == CMD_lcscope, *eap->cmdlinep);
} /* cs_find */


/*
 * PRIVATE: cs_find_common
 *
 * common code for cscope find, shared by cs_find() and do_cstag()
 */
static int cs_find_common(char *opt, char *pat, int forceit, int verbose, int use_ll, char_u *cmdline)
{
  int i;
  char *cmd;
  int *nummatches;
  int totmatches;
  char cmdletter;
  char *qfpos;

  /* get cmd letter */
  switch (opt[0]) {
  case '0':
    cmdletter = 's';
    break;
  case '1':
    cmdletter = 'g';
    break;
  case '2':
    cmdletter = 'd';
    break;
  case '3':
    cmdletter = 'c';
    break;
  case '4':
    cmdletter = 't';
    break;
  case '6':
    cmdletter = 'e';
    break;
  case '7':
    cmdletter = 'f';
    break;
  case '8':
    cmdletter = 'i';
    break;
  default:
    cmdletter = opt[0];
  }

  qfpos = (char *)vim_strchr(p_csqf, cmdletter);
  if (qfpos != NULL) {
    qfpos++;
    /* next symbol must be + or - */
    if (strchr(CSQF_FLAGS, *qfpos) == NULL) {
      char *nf = _("E469: invalid cscopequickfix flag %c for %c");
      /* strlen will be enough because we use chars */
      char *buf = xmalloc(strlen(nf));

      sprintf(buf, nf, *qfpos, *(qfpos-1));
      (void)EMSG(buf);
      xfree(buf);
      return FALSE;
    }

    if (*qfpos != '0') {
      apply_autocmds(EVENT_QUICKFIXCMDPRE, (char_u *)"cscope",
          curbuf->b_fname, TRUE, curbuf);
      if (did_throw || force_abort)
        return FALSE;
    }
  }

  /* create the actual command to send to cscope */
  cmd = cs_create_cmd(opt, pat);
  if (cmd == NULL)
    return FALSE;

  nummatches = xmalloc(sizeof(int) * csinfo_size);

  /* Send query to all open connections, then count the total number
   * of matches so we can alloc all in one swell foop. */
  for (i = 0; i < csinfo_size; i++)
    nummatches[i] = 0;
  totmatches = 0;
  for (i = 0; i < csinfo_size; i++) {
    if (csinfo[i].fname == NULL || csinfo[i].to_fp == NULL)
      continue;

    /* send cmd to cscope */
    (void)fprintf(csinfo[i].to_fp, "%s\n", cmd);
    (void)fflush(csinfo[i].to_fp);

    nummatches[i] = cs_cnt_matches(i);

    if (nummatches[i] > -1)
      totmatches += nummatches[i];

    if (nummatches[i] == 0)
      (void)cs_read_prompt(i);
  }
  xfree(cmd);

  if (totmatches == 0) {
    char *nf = _("E259: no matches found for cscope query %s of %s");
    char *buf;

    if (!verbose) {
      xfree(nummatches);
      return FALSE;
    }

    buf = xmalloc(strlen(opt) + strlen(pat) + strlen(nf));
    sprintf(buf, nf, opt, pat);
    (void)EMSG(buf);
    xfree(buf);
    xfree(nummatches);
    return FALSE;
  }

  if (qfpos != NULL && *qfpos != '0' && totmatches > 0) {
    /* fill error list */
    FILE        *f;
    char_u      *tmp = vim_tempname();
    qf_info_T   *qi = NULL;
    win_T       *wp = NULL;

    f = mch_fopen((char *)tmp, "w");
    if (f == NULL)
      EMSG2(_(e_notopen), tmp);
    else {
      cs_file_results(f, nummatches);
      fclose(f);
      if (use_ll)           /* Use location list */
        wp = curwin;
      /* '-' starts a new error list */
      if (qf_init(wp, tmp, (char_u *)"%f%*\\t%l%*\\t%m",
              *qfpos == '-', cmdline) > 0) {
        if (postponed_split != 0) {
          win_split(postponed_split > 0 ? postponed_split : 0,
              postponed_split_flags);
          RESET_BINDING(curwin);
          postponed_split = 0;
        }

        apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)"cscope",
            curbuf->b_fname, TRUE, curbuf);
        if (use_ll)
          /*
           * In the location list window, use the displayed location
           * list. Otherwise, use the location list for the window.
           */
          qi = (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL)
               ?  wp->w_llist_ref : wp->w_llist;
        qf_jump(qi, 0, 0, forceit);
      }
    }
    os_remove((char *)tmp);
    xfree(tmp);
    xfree(nummatches);
    return TRUE;
  } else {
    char **matches = NULL, **contexts = NULL;
    int matched = 0;

    /* read output */
    cs_fill_results((char *)pat, totmatches, nummatches, &matches,
        &contexts, &matched);
    xfree(nummatches);
    if (matches == NULL)
      return FALSE;

    (void)cs_manage_matches(matches, contexts, matched, Store);

    return do_tag((char_u *)pat, DT_CSCOPE, 0, forceit, verbose);
  }

} /* cs_find_common */

/*
 * PRIVATE: cs_help
 *
 * print help
 */
static int cs_help(exarg_T *eap)
{
  cscmd_T *cmdp = cs_cmds;

  (void)MSG_PUTS(_("cscope commands:\n"));
  while (cmdp->name != NULL) {
    char *help = _(cmdp->help);
    int space_cnt = 30 - vim_strsize((char_u *)help);

    /* Use %*s rather than %30s to ensure proper alignment in utf-8 */
    if (space_cnt < 0)
      space_cnt = 0;
    (void)smsg(_("%-5s: %s%*s (Usage: %s)"),
        cmdp->name,
        help, space_cnt, " ",
        cmdp->usage);
    if (strcmp(cmdp->name, "find") == 0)
      MSG_PUTS(_("\n"
              "       c: Find functions calling this function\n"
              "       d: Find functions called by this function\n"
              "       e: Find this egrep pattern\n"
              "       f: Find this file\n"
              "       g: Find this definition\n"
              "       i: Find files #including this file\n"
              "       s: Find this C symbol\n"
              "       t: Find this text string\n"));

    cmdp++;
  }

  wait_return(TRUE);
  return 0;
} /* cs_help */


static void clear_csinfo(int i)
{
  csinfo[i].fname  = NULL;
  csinfo[i].ppath  = NULL;
  csinfo[i].flags  = NULL;
  csinfo[i].file_id = FILE_ID_EMPTY;
  csinfo[i].pid    = 0;
  csinfo[i].fr_fp  = NULL;
  csinfo[i].to_fp  = NULL;
}

#ifndef UNIX
static char *GetWin32Error(void)
{
  char *msg = NULL;
  FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
      NULL, GetLastError(), 0, (LPSTR)&msg, 0, NULL);
  if (msg != NULL) {
    /* remove trailing \r\n */
    char *pcrlf = strstr(msg, "\r\n");
    if (pcrlf != NULL)
      *pcrlf = '\0';
  }
  return msg;
}
#endif

/*
 * PRIVATE: cs_insert_filelist
 *
 * insert a new cscope database filename into the filelist
 */
static int cs_insert_filelist(char *fname, char *ppath, char *flags,
                              FileInfo *file_info)
{
  short i, j;

  i = -1;   /* can be set to the index of an empty item in csinfo */
  for (j = 0; j < csinfo_size; j++) {
    if (csinfo[j].fname != NULL
        && os_fileid_equal_fileinfo(&(csinfo[j].file_id), file_info)) {
      if (p_csverbose)
        (void)EMSG(_("E568: duplicate cscope database not added"));
      return -1;
    }

    if (csinfo[j].fname == NULL && i == -1)
      i = j;       /* remember first empty entry */
  }

  if (i == -1) {
    i = csinfo_size;
    if (csinfo_size == 0) {
      /* First time allocation: allocate only 1 connection. It should
       * be enough for most users.  If more is needed, csinfo will be
       * reallocated. */
      csinfo_size = 1;
      csinfo = xcalloc(1, sizeof(csinfo_T));
    } else {
      /* Reallocate space for more connections. */
      csinfo_size *= 2;
      csinfo = xrealloc(csinfo, sizeof(csinfo_T)*csinfo_size);
    }
    for (j = csinfo_size/2; j < csinfo_size; j++)
      clear_csinfo(j);
  }

  csinfo[i].fname = xmalloc(strlen(fname) + 1);

  (void)strcpy(csinfo[i].fname, (const char *)fname);

  if (ppath != NULL) {
    csinfo[i].ppath = xmalloc(strlen(ppath) + 1);
    (void)strcpy(csinfo[i].ppath, (const char *)ppath);
  } else
    csinfo[i].ppath = NULL;

  if (flags != NULL) {
    csinfo[i].flags = xmalloc(strlen(flags) + 1);
    (void)strcpy(csinfo[i].flags, (const char *)flags);
  } else
    csinfo[i].flags = NULL;

  os_fileinfo_id(file_info, &(csinfo[i].file_id));
  return i;
} /* cs_insert_filelist */


/*
 * PRIVATE: cs_lookup_cmd
 *
 * find cscope command in command table
 */
static cscmd_T * cs_lookup_cmd(exarg_T *eap)
{
  cscmd_T *cmdp;
  char *stok;
  size_t len;

  if (eap->arg == NULL)
    return NULL;

  /* Store length of eap->arg before it gets modified by strtok(). */
  eap_arg_len = (int)STRLEN(eap->arg);

  if ((stok = strtok((char *)(eap->arg), (const char *)" ")) == NULL)
    return NULL;

  len = strlen(stok);
  for (cmdp = cs_cmds; cmdp->name != NULL; ++cmdp) {
    if (strncmp((const char *)(stok), cmdp->name, len) == 0)
      return cmdp;
  }
  return NULL;
} /* cs_lookup_cmd */


/*
 * PRIVATE: cs_kill
 *
 * nuke em
 */
static int cs_kill(exarg_T *eap)
{
  char *stok;
  short i;

  if ((stok = strtok((char *)NULL, (const char *)" ")) == NULL) {
    cs_usage_msg(Kill);
    return CSCOPE_FAILURE;
  }

  /* only single digit positive and negative integers are allowed */
  if ((strlen(stok) < 2 && ascii_isdigit((int)(stok[0])))
      || (strlen(stok) < 3 && stok[0] == '-'
          && ascii_isdigit((int)(stok[1]))))
    i = atoi(stok);
  else {
    /* It must be part of a name.  We will try to find a match
     * within all the names in the csinfo data structure
     */
    for (i = 0; i < csinfo_size; i++) {
      if (csinfo[i].fname != NULL && strstr(csinfo[i].fname, stok))
        break;
    }
  }

  if ((i != -1) && (i >= csinfo_size || i < -1 || csinfo[i].fname == NULL)) {
    if (p_csverbose)
      (void)EMSG2(_("E261: cscope connection %s not found"), stok);
  } else {
    if (i == -1) {
      for (i = 0; i < csinfo_size; i++) {
        if (csinfo[i].fname)
          cs_kill_execute(i, csinfo[i].fname);
      }
    } else
      cs_kill_execute(i, stok);
  }

  return 0;
} /* cs_kill */


/*
 * PRIVATE: cs_kill_execute
 *
 * Actually kills a specific cscope connection.
 */
static void 
cs_kill_execute (
    int i,              /* cscope table index */
    char *cname        /* cscope database name */
)
{
  if (p_csverbose) {
    msg_clr_eos();
    (void)smsg_attr(hl_attr(HLF_R) | MSG_HIST,
          _("cscope connection %s closed"), cname);
  }
  cs_release_csp(i, TRUE);
}


/*
 * PRIVATE: cs_make_vim_style_matches
 *
 * convert the cscope output into a ctags style entry (as might be found
 * in a ctags tags file).  there's one catch though: cscope doesn't tell you
 * the type of the tag you are looking for.  for example, in Darren Hiebert's
 * ctags (the one that comes with vim), #define's use a line number to find the
 * tag in a file while function definitions use a regexp search pattern.
 *
 * i'm going to always use the line number because cscope does something
 * quirky (and probably other things i don't know about):
 *
 *     if you have "#  define" in your source file, which is
 *     perfectly legal, cscope thinks you have "#define".  this
 *     will result in a failed regexp search. :(
 *
 * besides, even if this particular case didn't happen, the search pattern
 * would still have to be modified to escape all the special regular expression
 * characters to comply with ctags formatting.
 */
static char *cs_make_vim_style_matches(char *fname, char *slno, char *search, char *tagstr)
{
  /* vim style is ctags:
   *
   *	    <tagstr>\t<filename>\t<linenum_or_search>"\t<extra>
   *
   * but as mentioned above, we'll always use the line number and
   * put the search pattern (if one exists) as "extra"
   *
   * buf is used as part of vim's method of handling tags, and
   * (i think) vim frees it when you pop your tags and get replaced
   * by new ones on the tag stack.
   */
  char *buf;
  size_t amt;

  if (search != NULL) {
    amt = strlen(fname) + strlen(slno) + strlen(tagstr) + strlen(search) + 6;
    buf = xmalloc(amt);

    (void)sprintf(buf, "%s\t%s\t%s;\"\t%s", tagstr, fname, slno, search);
  } else {
    amt = strlen(fname) + strlen(slno) + strlen(tagstr) + 5;
    buf = xmalloc(amt);

    (void)sprintf(buf, "%s\t%s\t%s;\"", tagstr, fname, slno);
  }

  return buf;
} /* cs_make_vim_style_matches */


/*
 * PRIVATE: cs_manage_matches
 *
 * this is kind of hokey, but i don't see an easy way round this..
 *
 * Store: keep a ptr to the (malloc'd) memory of matches originally
 * generated from cs_find().  the matches are originally lines directly
 * from cscope output, but transformed to look like something out of a
 * ctags.  see cs_make_vim_style_matches for more details.
 *
 * Get: used only from cs_fgets(), this simulates a vim_fgets() to return
 * the next line from the cscope output.  it basically keeps track of which
 * lines have been "used" and returns the next one.
 *
 * Free: frees up everything and resets
 *
 * Print: prints the tags
 */
static char *cs_manage_matches(char **matches, char **contexts, int totmatches, mcmd_e cmd)
{
  static char **mp = NULL;
  static char **cp = NULL;
  static int cnt = -1;
  static int next = -1;
  char *p = NULL;

  switch (cmd) {
  case Store:
    assert(matches != NULL);
    assert(totmatches > 0);
    if (mp != NULL || cp != NULL)
      (void)cs_manage_matches(NULL, NULL, -1, Free);
    mp = matches;
    cp = contexts;
    cnt = totmatches;
    next = 0;
    break;
  case Get:
    if (next >= cnt)
      return NULL;

    p = mp[next];
    next++;
    break;
  case Free:
    if (mp != NULL) {
      if (cnt > 0)
        while (cnt--) {
          xfree(mp[cnt]);
          if (cp != NULL)
            xfree(cp[cnt]);
        }
      xfree(mp);
      xfree(cp);
    }
    mp = NULL;
    cp = NULL;
    cnt = 0;
    next = 0;
    break;
  case Print:
    cs_print_tags_priv(mp, cp, cnt);
    break;
  default:      /* should not reach here */
    (void)EMSG(_("E570: fatal error in cs_manage_matches"));
    return NULL;
  }

  return p;
} /* cs_manage_matches */


/*
 * PRIVATE: cs_parse_results
 *
 * parse cscope output
 */
static char *cs_parse_results(int cnumber, char *buf, int bufsize, char **context, char **linenumber, char **search)
{
  int ch;
  char *p;
  char *name;

  if (fgets(buf, bufsize, csinfo[cnumber].fr_fp) == NULL) {
    if (feof(csinfo[cnumber].fr_fp))
      errno = EIO;

    cs_reading_emsg(cnumber);

    return NULL;
  }

  /* If the line's too long for the buffer, discard it. */
  if ((p = strchr(buf, '\n')) == NULL) {
    while ((ch = getc(csinfo[cnumber].fr_fp)) != EOF && ch != '\n')
      ;
    return NULL;
  }
  *p = '\0';

  /*
   * cscope output is in the following format:
   *
   *	<filename> <context> <line number> <pattern>
   */
  if ((name = strtok((char *)buf, (const char *)" ")) == NULL)
    return NULL;
  if ((*context = strtok(NULL, (const char *)" ")) == NULL)
    return NULL;
  if ((*linenumber = strtok(NULL, (const char *)" ")) == NULL)
    return NULL;
  *search = *linenumber + strlen(*linenumber) + 1;      /* +1 to skip \0 */

  /* --- nvi ---
   * If the file is older than the cscope database, that is,
   * the database was built since the file was last modified,
   * or there wasn't a search string, use the line number.
   */
  if (strcmp(*search, "<unknown>") == 0)
    *search = NULL;

  name = cs_resolve_file(cnumber, name);
  return name;
}

/*
 * PRIVATE: cs_file_results
 *
 * write cscope find results to file
 */
static void cs_file_results(FILE *f, int *nummatches_a)
{
  int i, j;
  char *search, *slno;
  char *fullname;
  char *cntx;
  char *context;

  char *buf = xmalloc(CSREAD_BUFSIZE);

  for (i = 0; i < csinfo_size; i++) {
    if (nummatches_a[i] < 1)
      continue;

    for (j = 0; j < nummatches_a[i]; j++) {
      if ((fullname = cs_parse_results(i, buf, CSREAD_BUFSIZE, &cntx,
               &slno, &search)) == NULL)
        continue;

      context = xmalloc(strlen(cntx) + 5);

      if (strcmp(cntx, "<global>")==0)
        strcpy(context, "<<global>>");
      else
        sprintf(context, "<<%s>>", cntx);

      if (search == NULL)
        fprintf(f, "%s\t%s\t%s\n", fullname, slno, context);
      else
        fprintf(f, "%s\t%s\t%s %s\n", fullname, slno, context, search);

      xfree(context);
      xfree(fullname);
    }     /* for all matches */

    (void)cs_read_prompt(i);

  }   /* for all cscope connections */
  xfree(buf);
}

/*
 * PRIVATE: cs_fill_results
 *
 * get parsed cscope output and calls cs_make_vim_style_matches to convert
 * into ctags format
 * When there are no matches sets "*matches_p" to NULL.
 */
static void cs_fill_results(char *tagstr, int totmatches, int *nummatches_a, char ***matches_p, char ***cntxts_p, int *matched)
{
  int i, j;
  char *buf;
  char *search, *slno;
  int totsofar = 0;
  char **matches = NULL;
  char **cntxts = NULL;
  char *fullname;
  char *cntx;

  assert(totmatches > 0);

  buf = xmalloc(CSREAD_BUFSIZE);
  matches = xmalloc(sizeof(char *) * totmatches);
  cntxts = xmalloc(sizeof(char *) * totmatches);

  for (i = 0; i < csinfo_size; i++) {
    if (nummatches_a[i] < 1)
      continue;

    for (j = 0; j < nummatches_a[i]; j++) {
      if ((fullname = cs_parse_results(i, buf, CSREAD_BUFSIZE, &cntx,
               &slno, &search)) == NULL)
        continue;

      matches[totsofar] = cs_make_vim_style_matches(fullname, slno, search,
                                                    tagstr);

      xfree(fullname);

      if (strcmp(cntx, "<global>") == 0)
        cntxts[totsofar] = NULL;
      else {
        cntxts[totsofar] = xstrdup(cntx);
      }

      totsofar++;

    }     /* for all matches */

    (void)cs_read_prompt(i);

  }   /* for all cscope connections */

  if (totsofar == 0) {
    /* No matches, free the arrays and return NULL in "*matches_p". */
    xfree(matches);
    matches = NULL;
    xfree(cntxts);
    cntxts = NULL;
  }
  *matched = totsofar;
  *matches_p = matches;
  *cntxts_p = cntxts;

  xfree(buf);
} /* cs_fill_results */


/* get the requested path components */
static char *cs_pathcomponents(char *path)
{
  int i;
  char        *s;

  if (p_cspc == 0)
    return path;

  s = path + strlen(path) - 1;
  for (i = 0; i < p_cspc; ++i)
    while (s > path && *--s != '/'
           )
      ;
  if ((s > path && *s == '/')
      )
    ++s;
  return s;
}

/*
 * PRIVATE: cs_print_tags_priv
 *
 * called from cs_manage_matches()
 */
static void cs_print_tags_priv(char **matches, char **cntxts, int num_matches)
{
  char        *ptag;
  char        *fname, *lno, *extra, *tbuf;
  int i, idx, num;
  char        *globalcntx = "GLOBAL";
  char        *context;
  char        *cstag_msg = _("Cscope tag: %s");

  assert (num_matches > 0);

  tbuf = xmalloc(strlen(matches[0]) + 1);

  strcpy(tbuf, matches[0]);
  ptag = strtok(tbuf, "\t");

  size_t newsize = strlen(cstag_msg) + strlen(ptag);
  char *buf = xmalloc(newsize);
  size_t bufsize = newsize;  // Track available bufsize
  (void)sprintf(buf, cstag_msg, ptag);
  MSG_PUTS_ATTR(buf, hl_attr(HLF_T));

  xfree(tbuf);

  MSG_PUTS_ATTR(_("\n   #   line"), hl_attr(HLF_T));      /* strlen is 7 */
  msg_advance(msg_col + 2);
  MSG_PUTS_ATTR(_("filename / context / line\n"), hl_attr(HLF_T));

  num = 1;
  for (i = 0; i < num_matches; i++) {
    idx = i;

    /* if we really wanted to, we could avoid this malloc and strcpy
     * by parsing matches[i] on the fly and placing stuff into buf
     * directly, but that's too much of a hassle
     */
    tbuf = xmalloc(strlen(matches[idx]) + 1);
    (void)strcpy(tbuf, matches[idx]);

    if (strtok(tbuf, (const char *)"\t") == NULL)
      continue;
    if ((fname = strtok(NULL, (const char *)"\t")) == NULL)
      continue;
    if ((lno = strtok(NULL, (const char *)"\t")) == NULL)
      continue;
    extra = strtok(NULL, (const char *)"\t");

    lno[strlen(lno)-2] = '\0';      /* ignore ;" at the end */

    const char *csfmt_str = "%4d %6s  ";
    /* hopefully 'num' (num of matches) will be less than 10^16 */
    newsize = strlen(csfmt_str) + 16 + strlen(lno);
    if (bufsize < newsize) {
      buf = xrealloc(buf, newsize);
      bufsize = newsize;
    }
    (void)sprintf(buf, csfmt_str, num, lno);
    MSG_PUTS_ATTR(buf, hl_attr(HLF_CM));
    MSG_PUTS_LONG_ATTR(cs_pathcomponents(fname), hl_attr(HLF_CM));

    /* compute the required space for the context */
    if (cntxts[idx] != NULL)
      context = cntxts[idx];
    else
      context = globalcntx;

    const char *cntxformat = " <<%s>>";
    // '%s' won't appear in result string, so:
    // newsize = len(cntxformat) - 2 + len(context) + 1 (for NUL).
    newsize = strlen(context) + strlen(cntxformat) - 1;

    if (bufsize < newsize) {
      buf = xrealloc(buf, newsize);
      bufsize = newsize;
    }
    (void)sprintf(buf, cntxformat, context);

    /* print the context only if it fits on the same line */
    if (msg_col + (int)strlen(buf) >= (int)Columns)
      msg_putchar('\n');
    msg_advance(12);
    MSG_PUTS_LONG(buf);
    msg_putchar('\n');
    if (extra != NULL) {
      msg_advance(13);
      MSG_PUTS_LONG(extra);
    }

    xfree(tbuf);     /* only after printing extra due to strtok use */

    if (msg_col)
      msg_putchar('\n');

    os_breakcheck();
    if (got_int) {
      got_int = FALSE;          /* don't print any more matches */
      break;
    }

    num++;
  }   /* for all matches */

  xfree(buf);
} /* cs_print_tags_priv */


/*
 * PRIVATE: cs_read_prompt
 *
 * read a cscope prompt (basically, skip over the ">> ")
 */
static int cs_read_prompt(int i)
{
  int ch;
  char        *buf = NULL;   /* buffer for possible error message from cscope */
  int bufpos = 0;
  char        *cs_emsg;
  int maxlen;
  static char *eprompt = "Press the RETURN key to continue:";
  int epromptlen = (int)strlen(eprompt);
  int n;

  cs_emsg = _("E609: Cscope error: %s");
  /* compute maximum allowed len for Cscope error message */
  maxlen = (int)(IOSIZE - strlen(cs_emsg));

  for (;; ) {
    while ((ch = getc(csinfo[i].fr_fp)) != EOF && ch != CSCOPE_PROMPT[0])
      /* if there is room and char is printable */
      if (bufpos < maxlen - 1 && vim_isprintc(ch)) {
        // lazy buffer allocation
        if (buf == NULL) {
          buf = xmalloc(maxlen);
        }
        {
          /* append character to the message */
          buf[bufpos++] = ch;
          buf[bufpos] = NUL;
          if (bufpos >= epromptlen
              && strcmp(&buf[bufpos - epromptlen], eprompt) == 0) {
            /* remove eprompt from buf */
            buf[bufpos - epromptlen] = NUL;

            /* print message to user */
            (void)EMSG2(cs_emsg, buf);

            /* send RETURN to cscope */
            (void)putc('\n', csinfo[i].to_fp);
            (void)fflush(csinfo[i].to_fp);

            /* clear buf */
            bufpos = 0;
            buf[bufpos] = NUL;
          }
        }
      }

    for (n = 0; n < (int)strlen(CSCOPE_PROMPT); ++n) {
      if (n > 0)
        ch = getc(csinfo[i].fr_fp);
      if (ch == EOF) {
        PERROR("cs_read_prompt EOF");
        if (buf != NULL && buf[0] != NUL)
          (void)EMSG2(cs_emsg, buf);
        else if (p_csverbose)
          cs_reading_emsg(i);           /* don't have additional information */
        cs_release_csp(i, TRUE);
        xfree(buf);
        return CSCOPE_FAILURE;
      }

      if (ch != CSCOPE_PROMPT[n]) {
        ch = EOF;
        break;
      }
    }

    if (ch == EOF)
      continue;             /* didn't find the prompt */
    break;                  /* did find the prompt */
  }

  xfree(buf);
  return CSCOPE_SUCCESS;
}

#if defined(UNIX) && defined(SIGALRM)
/*
 * Used to catch and ignore SIGALRM below.
 */
static RETSIGTYPE
sig_handler SIGDEFARG(sigarg) {
  /* do nothing */
  SIGRETURN;
}

#endif

/*
 * PRIVATE: cs_release_csp
 *
 * Does the actual free'ing for the cs ptr with an optional flag of whether
 * or not to free the filename.  Called by cs_kill and cs_reset.
 */
static void cs_release_csp(int i, int freefnpp)
{
  /*
   * Trying to exit normally (not sure whether it is fit to UNIX cscope
   */
  if (csinfo[i].to_fp != NULL) {
    (void)fputs("q\n", csinfo[i].to_fp);
    (void)fflush(csinfo[i].to_fp);
  }
#if defined(UNIX)
  {
    int waitpid_errno;
    int pstat;
    pid_t pid;

# if defined(HAVE_SIGACTION)
    struct sigaction sa, old;

    /* Use sigaction() to limit the waiting time to two seconds. */
    sigemptyset(&sa.sa_mask);
    sa.sa_handler = sig_handler;
#  ifdef SA_NODEFER
    sa.sa_flags = SA_NODEFER;
#  else
    sa.sa_flags = 0;
#  endif
    sigaction(SIGALRM, &sa, &old);
    alarm(2);     /* 2 sec timeout */

    /* Block until cscope exits or until timer expires */
    pid = waitpid(csinfo[i].pid, &pstat, 0);
    waitpid_errno = errno;

    /* cancel pending alarm if still there and restore signal */
    alarm(0);
    sigaction(SIGALRM, &old, NULL);
# else
    int waited;

    /* Can't use sigaction(), loop for two seconds.  First yield the CPU
     * to give cscope a chance to exit quickly. */
    sleep(0);
    for (waited = 0; waited < 40; ++waited) {
      pid = waitpid(csinfo[i].pid, &pstat, WNOHANG);
      waitpid_errno = errno;
      if (pid != 0)
        break;          /* break unless the process is still running */
      os_delay(50L, false);       /* sleep 50 ms */
    }
# endif
    /*
     * If the cscope process is still running: kill it.
     * Safety check: If the PID would be zero here, the entire X session
     * would be killed.  -1 and 1 are dangerous as well.
     */
    if (pid < 0 && csinfo[i].pid > 1) {
# ifdef ECHILD
      int alive = TRUE;

      if (waitpid_errno == ECHILD) {
        /*
         * When using 'vim -g', vim is forked and cscope process is
         * no longer a child process but a sibling.  So waitpid()
         * fails with errno being ECHILD (No child processes).
         * Don't send SIGKILL to cscope immediately but wait
         * (polling) for it to exit normally as result of sending
         * the "q" command, hence giving it a chance to clean up
         * its temporary files.
         */
        int waited;

        sleep(0);
        for (waited = 0; waited < 40; ++waited) {
          /* Check whether cscope process is still alive */
          if (kill(csinfo[i].pid, 0) != 0) {
            alive = FALSE;             /* cscope process no longer exists */
            break;
          }
          os_delay(50L, false);           /* sleep 50ms */
        }
      }
      if (alive)
# endif
      {
        kill(csinfo[i].pid, SIGKILL);
        (void)waitpid(csinfo[i].pid, &pstat, 0);
      }
    }
  }
#else  /* !UNIX */
  if (csinfo[i].hProc != NULL) {
    /* Give cscope a chance to exit normally */
    if (WaitForSingleObject(csinfo[i].hProc, 1000) == WAIT_TIMEOUT)
      TerminateProcess(csinfo[i].hProc, 0);
    CloseHandle(csinfo[i].hProc);
  }
#endif

  if (csinfo[i].fr_fp != NULL)
    (void)fclose(csinfo[i].fr_fp);
  if (csinfo[i].to_fp != NULL)
    (void)fclose(csinfo[i].to_fp);

  if (freefnpp) {
    xfree(csinfo[i].fname);
    xfree(csinfo[i].ppath);
    xfree(csinfo[i].flags);
  }

  clear_csinfo(i);
} /* cs_release_csp */


/*
 * PRIVATE: cs_reset
 *
 * calls cs_kill on all cscope connections then reinits
 */
static int cs_reset(exarg_T *eap)
{
  char        **dblist = NULL, **pplist = NULL, **fllist = NULL;
  int i;
  char buf[20];   /* for sprintf " (#%d)" */

  if (csinfo_size == 0)
    return CSCOPE_SUCCESS;

  /* malloc our db and ppath list */
  dblist = xmalloc(csinfo_size * sizeof(char *));
  pplist = xmalloc(csinfo_size * sizeof(char *));
  fllist = xmalloc(csinfo_size * sizeof(char *));

  for (i = 0; i < csinfo_size; i++) {
    dblist[i] = csinfo[i].fname;
    pplist[i] = csinfo[i].ppath;
    fllist[i] = csinfo[i].flags;
    if (csinfo[i].fname != NULL)
      cs_release_csp(i, FALSE);
  }

  /* rebuild the cscope connection list */
  for (i = 0; i < csinfo_size; i++) {
    if (dblist[i] != NULL) {
      cs_add_common(dblist[i], pplist[i], fllist[i]);
      if (p_csverbose) {
        /* don't use smsg_attr() because we want to display the
         * connection number in the same line as
         * "Added cscope database..."
         */
        sprintf(buf, " (#%d)", i);
        MSG_PUTS_ATTR(buf, hl_attr(HLF_R));
      }
    }
    xfree(dblist[i]);
    xfree(pplist[i]);
    xfree(fllist[i]);
  }
  xfree(dblist);
  xfree(pplist);
  xfree(fllist);

  if (p_csverbose)
    MSG_ATTR(_("All cscope databases reset"), hl_attr(HLF_R) | MSG_HIST);
  return CSCOPE_SUCCESS;
} /* cs_reset */


/*
 * PRIVATE: cs_resolve_file
 *
 * Construct the full pathname to a file found in the cscope database.
 * (Prepends ppath, if there is one and if it's not already prepended,
 * otherwise just uses the name found.)
 *
 * We need to prepend the prefix because on some cscope's (e.g., the one that
 * ships with Solaris 2.6), the output never has the prefix prepended.
 * Contrast this with my development system (Digital Unix), which does.
 */
static char *cs_resolve_file(int i, char *name)
{
  char        *fullname;
  char_u      *csdir = NULL;

  /*
   * Ppath is freed when we destroy the cscope connection.
   * Fullname is freed after cs_make_vim_style_matches, after it's been
   * copied into the tag buffer used by Vim.
   */
  size_t len = strlen(name) + 2;
  if (csinfo[i].ppath != NULL)
    len += strlen(csinfo[i].ppath);
  else if (p_csre && csinfo[i].fname != NULL) {
    /* If 'cscoperelative' is set and ppath is not set, use cscope.out
     * path in path resolution. */
    csdir = xmalloc(MAXPATHL);
    STRLCPY(csdir, csinfo[i].fname,
        path_tail((char_u *)csinfo[i].fname)
        - (char_u *)csinfo[i].fname + 1);
    len += STRLEN(csdir);
  }

  /* Note/example: this won't work if the cscope output already starts
   * "../.." and the prefix path is also "../..".  if something like this
   * happens, you are screwed up and need to fix how you're using cscope. */
  if (csinfo[i].ppath != NULL
      && (strncmp(name, csinfo[i].ppath, strlen(csinfo[i].ppath)) != 0)
      && (name[0] != '/')
      ) {
    fullname = xmalloc(len);
    (void)sprintf(fullname, "%s/%s", csinfo[i].ppath, name);
  } else if (csdir != NULL && csinfo[i].fname != NULL && *csdir != NUL) {
    /* Check for csdir to be non empty to avoid empty path concatenated to
     * cscope output. */
    fullname = concat_fnames((char *)csdir, name, TRUE);
  } else {
    fullname = xstrdup(name);
  }

  xfree(csdir);
  return fullname;
}


/*
 * PRIVATE: cs_show
 *
 * show all cscope connections
 */
static int cs_show(exarg_T *eap)
{
  short i;
  if (cs_cnt_connections() == 0)
    MSG_PUTS(_("no cscope connections\n"));
  else {
    MSG_PUTS_ATTR(
        _(" # pid    database name                       prepend path\n"),
        hl_attr(HLF_T));
    for (i = 0; i < csinfo_size; i++) {
      if (csinfo[i].fname == NULL)
        continue;

      if (csinfo[i].ppath != NULL)
        (void)smsg("%2d %-5ld  %-34s  %-32s",
            i, (long)csinfo[i].pid, csinfo[i].fname, csinfo[i].ppath);
      else
        (void)smsg("%2d %-5ld  %-34s  <none>",
            i, (long)csinfo[i].pid, csinfo[i].fname);
    }
  }

  wait_return(TRUE);
  return CSCOPE_SUCCESS;
} /* cs_show */


/*
 * PUBLIC: cs_end
 *
 * Only called when VIM exits to quit any cscope sessions.
 */
void cs_end(void)
{
  int i;

  for (i = 0; i < csinfo_size; i++)
    cs_release_csp(i, TRUE);
  xfree(csinfo);
  csinfo_size = 0;
}

/* the end */
