text: /* vi:set ts=8 sts=4 sw=4:
   *
   * VIM - Vi IMproved	by Bram Moolenaar
   *			Visual Workshop integration by Gordon Prieur
   *
   * Do ":help uganda"  in Vim to read copying and usage conditions.
   * Do ":help credits" in Vim to see a list of people who contributed.
   * See README.txt for an overview of the Vim source code.
   */
  
  #ifdef HAVE_CONFIG_H
  # include "auto/config.h"
  #endif
  #include <stdio.h>
  #include <stdlib.h>
  #include <sys/types.h>
  #include <netdb.h>
  #include <netinet/in.h>
  #include <errno.h>
  #include <sys/socket.h>
  #ifdef HAVE_LIBGEN_H
  # include <libgen.h>
  #endif
  #include <unistd.h>
  #include <string.h>
  #include <stdlib.h>
  #include <ctype.h>
  
  #include <X11/Intrinsic.h>
  #include <Xm/Xm.h>
  #include <Xm/PushB.h>
  
  #include "integration.h"	/* <EditPlugin/integration.h> */
  
  #include "vim.h"
  #include "version.h"
  #include "gui_beval.h"
  #include "workshop.h"
  
  void		 workshop_hotkeys(Boolean);
  
  static Boolean	 isShowing(int);
  static win_T	*get_window(buf_T *);
  #if 0
  static int	 get_buffer_number(buf_T *);
  #endif
  static void	 updatePriority(Boolean);
  static char	*addUniqueMnemonic(char *, char *);
  static char	*fixup(char *);
  static char	*get_selection(buf_T *);
  static char	*append_selection(int, char *, int *, int *);
  static void	 load_buffer_by_name(char *, int);
  #if 0
  static void	 load_buffer_by_number(int, int);
  #endif
  static void	 load_window(char *, int lnum);
  static void	 warp_to_pc(int);
  #ifdef FEAT_BEVAL
  void		workshop_beval_cb(BalloonEval *, int);
  #endif
  static char	*fixAccelText(char *);
  static void	 addMenu(char *, char *, char *);
  static char	*lookupVerb(char *, int);
  static int	 computeIndex(int, char_u *, int);
  static void	 coloncmd(char *, Boolean);
  
  extern Widget	 vimShell;
  extern Widget	 textArea;
  extern XtAppContext app_context;
  
  static int	 tbpri;			/* ToolBar priority */
  int		 usingSunWorkShop = 0;	/* set if -ws flag is used */
  char		 curMenuName[BUFSIZ];
  char		 curMenuPriority[BUFSIZ];
  
  static Boolean	 workshopInitDone = False;
  static Boolean	 workshopHotKeysEnabled = False;
  
  /*
   * The following enum is from <gp_dbx/gp_dbx_common.h>. We can't include it
   * here because its C++.
   */
  enum
  {
      GPLineEval_EVALUATE,		/* evaluate expression */
      GPLineEval_INDIRECT,		/* evaluate *<expression> */
      GPLineEval_TYPE			/* type of expression */
  };
  
  /*
   * Store each verb in the MenuMap. This lets us map from a verb to a menu.
   * There may be multiple matches for a single verb in this table.
   */
  #define MENU_INC	50		/* menuMap incremental size increases */
  typedef struct
  {
      char	*name;			/* name of the menu */
      char	*accel;			/* optional accelerator key */
      char	*verb;			/* menu verb */
  } MenuMap;
  static MenuMap	*menuMap;		/* list of verb/menu mappings */
  static int	 menuMapSize;		/* current size of menuMap */
  static int	 menuMapMax;		/* allocated size of menuMap */
  static char	*initialFileCmd;	/* save command but defer doing it */
  
  
      void
  workshop_init()
  {
      char_u	 buf[64];
      int		 is_dirty = FALSE;
      int		 width, height;
      XtInputMask	 mask;
  
      /*
       * Turn on MenuBar, ToolBar, and Footer.
       */
      STRCPY(buf, p_go);
      if (vim_strchr(p_go, GO_MENUS) == NULL)
      {
  	STRCAT(buf, "m");
  	is_dirty = TRUE;
      }
      if (vim_strchr(p_go, GO_TOOLBAR) == NULL)
      {
  	STRCAT(buf, "T");
  	is_dirty = TRUE;
      }
      if (vim_strchr(p_go, GO_FOOTER) == NULL)
      {
  	STRCAT(buf, "F");
  	is_dirty = TRUE;
      }
      if (is_dirty)
  	set_option_value((char_u *)"go", 0L, buf, 0);
  
      /*
       * Set size from workshop_get_width_height().
       */
      width = height = 0;
      if (workshop_get_width_height(&width, &height))
      {
  	XtVaSetValues(vimShell,
  		XmNwidth, width,
  		XmNheight, height,
  		NULL);
      }
  
      /*
       * Now read in the initial messages from eserve.
       */
      while ((mask = XtAppPending(app_context))
  	    && (mask & XtIMAlternateInput) && !workshopInitDone)
  	XtAppProcessEvent(app_context, (XtInputMask)XtIMAlternateInput);
  }
  
      void
  workshop_postinit()
  {
      do_cmdline_cmd((char_u *)initialFileCmd);
      ALT_INPUT_LOCK_OFF;
      free(initialFileCmd);
      initialFileCmd = NULL;
  }
  
      void
  ex_wsverb(exarg_T *eap)
  {
      msg_clr_cmdline();
      workshop_perform_verb((char *) eap->arg, NULL);
  }
  
  /*
   * Editor name
   * This string is recognized by eserve and should be all lower case.
   * This is how the editor detects that it is talking to gvim instead
   * of NEdit, for example, when the connection is initiated from the editor.
   */
      char *
  workshop_get_editor_name()
  {
      return "gvim";
  }
  
  /*
   * Version number of the editor.
   * This number is communicated along with the protocol
   * version to the application.
   */
      char *
  workshop_get_editor_version()
  {
      return Version;
  }
  
  /*
   * Answer functions: called by eserve
   */
  
  /*
   * Name:
   *	workshop_load_file
   *
   * Function:
   *	Load a given file into the WorkShop buffer.
   */
  /*ARGSUSED*/
      void
  workshop_load_file(
  	char	*filename,		/* the file to load */
  	int	 line,			/* an optional line number (or 0) */
  	char	*frameid)		/* used for multi-frame support */
  {
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_load_file(%s, %d)\n", filename, line);
  #endif
  
  #ifdef FEAT_BEVAL
      bevalServers |= BEVAL_WORKSHOP;
  #endif
  
      load_window(filename, line);
  }
  
  /*
   * Reload the WorkShop buffer
   */
      void
  workshop_reload_file(
  	char	*filename,
  	int	 line)
  {
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_reload_file(%s, %d)\n", filename, line);
  #endif
      load_window(filename, line);
  }
  
      void
  workshop_show_file(
      char	*filename)
  {
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_show_file(%s)\n", filename);
  #endif
  
      load_window(filename, 0);
  }
  
      void
  workshop_goto_line(
      char	*filename,
      int		 lineno)
  {
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_goto_line(%s, %d)\n", filename, lineno);
  #endif
  
      load_window(filename, lineno);
  }
  
  /*ARGSUSED*/
      void
  workshop_front_file(
  	char	*filename)
  {
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_front_file()\n");
  #endif
      /*
       * Assumption: This function will always be called after a call to
       * workshop_show_file(), so the file is always showing.
       */
      if (vimShell != NULL)
  	XRaiseWindow(gui.dpy, XtWindow(vimShell));
  }
  
      void
  workshop_save_file(
  	    char	*filename)
  {
      char	 cbuf[BUFSIZ];		/* build vim command here */
  
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_save_file(%s)\n", filename);
  #endif
  
      /* Save the given file */
      vim_snprintf(cbuf, sizeof(cbuf), "w %s", filename);
      coloncmd(cbuf, TRUE);
  }
  
      void
  workshop_save_files()
  {
      /* Save the given file */
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_save_files()\n");
  #endif
  
      add_to_input_buf((char_u *) ":wall\n", 6);
  }
  
      void
  workshop_quit()
  {
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_quit()\n");
  #endif
  
      add_to_input_buf((char_u *) ":qall\n", 6);
  }
  
      void
  workshop_minimize()
  {
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_minimize()\n");
  #endif
      workshop_minimize_shell(vimShell);
  }
      void
  workshop_maximize()
  {
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_maximize()\n");
  #endif
  
      workshop_maximize_shell(vimShell);
  }
  
      void
  workshop_add_mark_type(
  	int		 idx,
  	char		*colorspec,
  	char		*sign)
  {
      char	 gbuf[BUFSIZ];	/* buffer for sign name */
      char	 cibuf[BUFSIZ];	/* color information */
      char	 cbuf[BUFSIZ];	/* command buffer */
      char	*bp;
  
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
      {
  	char *cp;
  
  	cp = strrchr(sign, '/');
  	if (cp == NULL)
  	    cp = sign;
  	else
  	    cp++;		/* skip '/' character */
  	wstrace("workshop_add_mark_type(%d, \"%s\", \"%s\")\n", idx,
  		colorspec && *colorspec ? colorspec : "<None>", cp);
      }
  #endif
  
      /*
       * Isolate the basename of sign in gbuf. We will use this for the
       * GroupName in the highlight command sent to vim.
       */
      STRCPY(gbuf, gettail((char_u *)sign));
      bp = strrchr(gbuf, '.');
      if (bp != NULL)
  	*bp = NUL;
  
      if (gbuf[0] != '-' && gbuf[1] != NUL)
      {
  	if (colorspec != NULL && *colorspec)
  	{
  	    vim_snprintf(cbuf, sizeof(cbuf),
  				  "highlight WS%s guibg=%s", gbuf, colorspec);
  	    coloncmd(cbuf, FALSE);
  	    vim_snprintf(cibuf, sizeof(cibuf), "linehl=WS%s", gbuf);
  	}
  	else
  	    cibuf[0] = NUL;
  
  	vim_snprintf(cbuf, sizeof(cbuf),
  			       "sign define %d %s icon=%s", idx, cibuf, sign);
  	coloncmd(cbuf, TRUE);
      }
  }
  
      void
  workshop_set_mark(
  	char		*filename,	/* filename which gets the mark */
  	int		 lineno,	/* line number which gets the mark */
  	int		 markId,	/* unique mark identifier */
  	int		 idx)		/* which mark to use */
  {
      char	cbuf[BUFSIZ];	/* command buffer */
  
      /* Set mark in a given file */
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_set_mark(%s, %d (ln), %d (id), %d (idx))\n",
  		filename, lineno, markId, idx);
  #endif
  
      vim_snprintf(cbuf, sizeof(cbuf), "sign place %d line=%d name=%d file=%s",
  					       markId, lineno, idx, filename);
      coloncmd(cbuf, TRUE);
  }
  
      void
  workshop_change_mark_type(
  	char		*filename,	/* filename which gets the mark */
  	int		 markId,	/* unique mark identifier */
  	int		 idx)		/* which mark to use */
  {
      char	cbuf[BUFSIZ];	/* command buffer */
  
      /* Change mark type */
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_change_mark_type(%s, %d, %d)\n",
  		filename, markId, idx);
  #endif
  
      vim_snprintf(cbuf, sizeof(cbuf),
  		      "sign place %d name=%d file=%s", markId, idx, filename);
      coloncmd(cbuf, TRUE);
  }
  
  /*
   * Goto the given mark in a file (e.g. show it).
   * If message is not null, display it in the footer.
   */
      void
  workshop_goto_mark(
  	char		*filename,
  	int		 markId,
  	char		*message)
  {
      char	cbuf[BUFSIZ];	/* command buffer */
  
      /* Goto mark */
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_goto_mark(%s, %d (id), %s)\n",
  		filename, markId, message && *message &&
  		!(*message == ' ' && message[1] == NULL) ?
  		message : "<None>");
  #endif
  
      vim_snprintf(cbuf, sizeof(cbuf), "sign jump %d file=%s", markId, filename);
      coloncmd(cbuf, TRUE);
      if (message != NULL && *message != NUL)
  	gui_mch_set_footer((char_u *)message);
  }
  
      void
  workshop_delete_mark(
  	char		*filename,
  	int		 markId)
  {
      char	cbuf[BUFSIZ];	/* command buffer */
  
      /* Delete mark */
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_delete_mark(%s, %d (id))\n",
  		filename, markId);
  #endif
  
      vim_snprintf(cbuf, sizeof(cbuf),
  				 "sign unplace %d file=%s", markId, filename);
      coloncmd(cbuf, TRUE);
  }
  
  #if 0	/* not used */
      void
  workshop_delete_all_marks(
      void	*window,
      Boolean	 doRefresh)
  {
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_delete_all_marks(%#x, %s)\n",
  		window, doRefresh ? "True" : "False");
  #endif
  
      coloncmd("sign unplace *", TRUE);
  }
  #endif
  
      int
  workshop_get_mark_lineno(
  	char	*filename,
  	int	 markId)
  {
      buf_T	*buf;		/* buffer containing filename */
      int		lineno;		/* line number of filename in buf */
  
      /* Get mark line number */
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_get_mark_lineno(%s, %d)\n",
  		filename, markId);
  #endif
  
      lineno = 0;
      buf = buflist_findname((char_u *)filename);
      if (buf != NULL)
  	lineno = buf_findsign(buf, markId);
  
      return lineno;
  }
  
  
  #if 0	/* not used */
      void
  workshop_adjust_marks(Widget *window, int pos,
  			int inserted, int deleted)
  {
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("XXXworkshop_adjust_marks(%s, %d, %d, %d)\n",
  		window ? XtName(window) : "<None>", pos, inserted, deleted);
  #endif
  }
  #endif
  
  /*
   * Are there any moved marks? If so, call workshop_move_mark on
   * each of them now. This is how eserve can find out if for example
   * breakpoints have moved when a program has been recompiled and
   * reloaded into dbx.
   */
  /*ARGSUSED*/
      void
  workshop_moved_marks(char *filename)
  {
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("XXXworkshop_moved_marks(%s)\n", filename);
  #endif
  }
  
      int
  workshop_get_font_height()
  {
      XmFontList	 fontList;	/* fontList made from gui.norm_font */
      XmString	 str;
      Dimension	 w;
      Dimension	 h;
  
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_get_font_height()\n");
  #endif
  
      /* Pick the proper signs for this font size */
      fontList = gui_motif_create_fontlist((XFontStruct *)gui.norm_font);
      h = 0;
      if (fontList != NULL)
      {
  	str = XmStringCreateLocalized("A");
  	XmStringExtent(fontList, str, &w, &h);
  	XmStringFree(str);
  	XmFontListFree(fontList);
      }
  
      return (int)h;
  }
  
  /*ARGSUSED*/
      void
  workshop_footer_message(
  	char		*message,
  	int		 severity)	/* severity is currently unused */
  {
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_footer_message(%s, %d)\n", message, severity);
  #endif
  
      gui_mch_set_footer((char_u *) message);
  }
  
  /*
   * workshop_menu_begin() is passed the menu name. We determine its mnemonic
   * here and store its name and priority.
   */
      void
  workshop_menu_begin(
  	char		*label)
  {
      vimmenu_T	*menu;			/* pointer to last menu */
      int		menuPriority = 0;	/* priority of new menu */
      char	mnembuf[64];		/* store menubar mnemonics here */
      char	*name;			/* label with a mnemonic */
      char	*p;			/* used to find mnemonics */
      int		idx;			/* index into mnembuf */
  
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_menu_begin()\n");
  #endif
  
      /*
       * Look through all existing (non-PopUp and non-Toolbar) menus
       * and gather their mnemonics. Use this list to decide what
       * mnemonic should be used for label.
       */
  
      idx = 0;
      mnembuf[idx++] = 'H';		/* H is mnemonic for Help */
      for (menu = root_menu; menu != NULL; menu = menu->next)
      {
  	if (menu_is_menubar(menu->name))
  	{
  	    p = strchr((char *)menu->name, '&');
  	    if (p != NULL)
  		mnembuf[idx++] = *++p;
  	}
  	if (menu->next != NULL
  		&& strcmp((char *) menu->next->dname, "Help") == 0)
  	{
  	    menuPriority = menu->priority + 10;
  	    break;
  	}
      }
      mnembuf[idx++] = NUL;
      name = addUniqueMnemonic(mnembuf, label);
  
      vim_snprintf(curMenuName, sizeof(curMenuName), "%s", name);
      sprintf(curMenuPriority, "%d.0", menuPriority);
  }
  
  /*
   * Append the name and priority to strings to be used in vim menu commands.
   */
      void
  workshop_submenu_begin(
  	char		*label)
  {
  #ifdef WSDEBUG_TRACE
      if (ws_debug  && ws_dlevel & WS_TRACE
  	    && strncmp(curMenuName, "ToolBar", 7) != 0)
  	wstrace("workshop_submenu_begin(%s)\n", label);
  #endif
  
      strcat(curMenuName, ".");
      strcat(curMenuName, fixup(label));
  
      updatePriority(True);
  }
  
  /*
   * Remove the submenu name and priority from curMenu*.
   */
  
      void
  workshop_submenu_end()
  {
      char		*p;
  
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE)
  	    && strncmp(curMenuName, "ToolBar", 7) != 0)
  	wstrace("workshop_submenu_end()\n");
  #endif
  
      p = strrchr(curMenuPriority, '.');
      ASSERT(p != NULL);
      *p = NUL;
  
      p = strrchr(curMenuName, '.');
      ASSERT(p != NULL);
      *p = NUL;
  }
  
  /*
   * This is where menus are really made. Each item will generate an amenu vim
   * command. The globals curMenuName and curMenuPriority contain the name and
   * priority of the parent menu tree.
   */
  /*ARGSUSED*/
      void
  workshop_menu_item(
  	char		*label,
  	char		*verb,
  	char		*accelerator,
  	char		*acceleratorText,
  	char		*name,
  	char		*filepos,
  	char		*sensitive)
  {
      char		 cbuf[BUFSIZ];
      char		 namebuf[BUFSIZ];
      char		 accText[BUFSIZ];
  
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE)
  	    && strncmp(curMenuName, "ToolBar", 7) != 0)
      {
  	if (ws_dlevel & WS_TRACE_VERBOSE)
  	    wsdebug("workshop_menu_item(\n"
  		    "\tlabel = \"%s\",\n"
  		    "\tverb = %s,\n"
  		    "\taccelerator = %s,\n"
  		    "\tacceleratorText = \"%s\",\n"
  		    "\tname = %s,\n"
  		    "\tfilepos = %s,\n"
  		    "\tsensitive = %s)\n",
  		    label && *label ? label : "<None>",
  		    verb && *verb ? verb : "<None>",
  		    accelerator && *accelerator ?
  		    accelerator : "<None>",
  		    acceleratorText && *acceleratorText ?
  		    acceleratorText : "<None>",
  		    name && *name ? name : "<None>",
  		    filepos && *filepos ? filepos : "<None>",
  		    sensitive);
  	else if (ws_dlevel & WS_TRACE)
  	    wstrace("workshop_menu_item(\"%s\", %s)\n",
  		    label && *label ? label : "<None>",
  		    verb && *verb ? verb : "<None>", sensitive);
      }
  #endif
  #ifdef WSDEBUG_SENSE
      if (ws_debug)
  	wstrace("menu:   %-21.20s%-21.20s(%s)\n", label, verb,
  		*sensitive == '1' ? "Sensitive" : "Insensitive");
  #endif
  
      if (acceleratorText != NULL)
  	vim_snprintf(accText, sizeof(accText), "<Tab>%s", acceleratorText);
      else
  	accText[0] = NUL;
      updatePriority(False);
      vim_snprintf(namebuf, sizeof(namebuf), "%s.%s", curMenuName, fixup(label));
      vim_snprintf(cbuf, sizeof(cbuf), "amenu %s %s%s\t:wsverb %s<CR>",
  	    curMenuPriority, namebuf, accText, verb);
  
      coloncmd(cbuf, TRUE);
      addMenu(namebuf, fixAccelText(acceleratorText), verb);
  
      if (*sensitive == '0')
      {
  	vim_snprintf(cbuf, sizeof(cbuf), "amenu disable %s", namebuf);
  	coloncmd(cbuf, TRUE);
      }
  }
  
  /*
   * This function is called when a complete WorkShop menu description has been
   * sent over from eserve. We do some menu cleanup.
   */
  
      void
  workshop_menu_end()
  {
      Boolean		 using_tearoff;	/* set per current option setting */
  
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_menu_end()\n");
  #endif
  
      using_tearoff = vim_strchr(p_go, GO_TEAROFF) != NULL;
      gui_mch_toggle_tearoffs(using_tearoff);
  }
  
      void
  workshop_toolbar_begin()
  {
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_toolbar_begin()\n");
  #endif
  
      coloncmd("aunmenu ToolBar", True);
      tbpri = 10;
  }
  
      void
  workshop_toolbar_end()
  {
      char_u	buf[64];
  
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
      {
  	wstrace("workshop_toolbar_end()\n");
      }
  #endif
  
      /*
       * Turn on ToolBar.
       */
      STRCPY(buf, p_go);
      if (vim_strchr(p_go, 'T') == NULL)
      {
  	STRCAT(buf, "T");
  	set_option_value((char_u *)"go", 0L, buf, 0);
      }
      workshopInitDone = True;
  }
  
  /*ARGSUSED*/
      void
  workshop_toolbar_button(
  	char	*label,
  	char	*verb,
  	char	*senseVerb,
  	char	*filepos,
  	char	*help,
  	char	*sense,
  	char	*file,
  	char	*left)
  {
      char	cbuf[BUFSIZ + MAXPATHLEN];
      char	namebuf[BUFSIZ];
      static int	tbid = 1;
      char_u	*p;
      int		len;
  
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE))
  	wsdebug("workshop_toolbar_button(\"%s\", %s, %s,\n"
  		"\t%s, \"%s\", %s,\n\t\"%s\",\n\t<%s>)\n",
  		label   && *label   ? label   : "<None>",
  		verb    && *verb    ? verb    : "<None>",
  		senseVerb && *senseVerb    ? senseVerb    : "<None>",
  		filepos && *filepos ? filepos : "<None>",
  		help    && *help    ? help    : "<None>",
  		sense   && *sense   ? sense   : "<None>",
  		file    && *file    ? file    : "<None>",
  		left    && *left    ? left    : "<None>");
      else if (WSDLEVEL(WS_TRACE))
  	wstrace("workshop_toolbar_button(\"%s\", %s)\n",
  		label   && *label   ? label   : "<None>",
  		verb    && *verb    ? verb    : "<None>");
  #endif
  #ifdef WSDEBUG_SENSE
      if (ws_debug)
  	wsdebug("button: %-21.20s%-21.20s(%s)\n", label, verb,
  		*sense == '1' ? "Sensitive" : "Insensitive");
  #endif
  
      if (left && *left && atoi(left) > 0)
      {
  	/* Add a separator (but pass the width passed after the ':') */
  	sprintf(cbuf, "amenu 1.%d ToolBar.-sep%d:%s- <nul>",
  		tbpri - 5, tbid++, left);
  
  	coloncmd(cbuf, True);
      }
  
      p = vim_strsave_escaped((char_u *)label, (char_u *)"\\. ");
      vim_snprintf(namebuf, sizeof(namebuf), "ToolBar.%s", p);
      vim_free(p);
      STRCPY(cbuf, "amenu <silent> ");
      if (file != NULL && *file != NUL)
      {
  	p = vim_strsave_escaped((char_u *)file, (char_u *)" ");
  	len = STRLEN(cbuf);
  	vim_snprintf(cbuf + len, sizeof(cbuf) - len, "icon=%s ", p);
  	vim_free(p);
      }
      len = STRLEN(cbuf);
      vim_snprintf(cbuf + len, sizeof(cbuf) - len,"1.%d %s :wsverb %s<CR>",
  							tbpri, namebuf, verb);
  
      /* Define the menu item */
      coloncmd(cbuf, True);
  
      if (*sense == '0')
      {
  	/* If menu isn't sensitive at startup... */
  	vim_snprintf(cbuf, sizeof(cbuf), "amenu disable %s", namebuf);
  	coloncmd(cbuf, True);
      }
  
      if (help && *help)
      {
  	/* Do the tooltip */
  	vim_snprintf(cbuf, sizeof(cbuf), "tmenu %s %s", namebuf, help);
  	coloncmd(cbuf, True);
      }
  
      addMenu(namebuf, NULL, verb);
      tbpri += 10;
  }
  
      void
  workshop_frame_sensitivities(
  	VerbSense	*vs)		/* list of verbs to (de)sensitize */
  {
      VerbSense	*vp;		/* iterate through vs */
      char	*menu_name;	/* used in menu lookup */
      int		 cnt;		/* count of verbs to skip */
      int		 len;		/* length of nonvariant part of command */
      char	 cbuf[4096];
  
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE) || WSDLEVEL(4))
      {
  	wsdebug("workshop_frame_sensitivities(\n");
  	for (vp = vs; vp->verb != NULL; vp++)
  	    wsdebug("\t%-25s%d\n", vp->verb, vp->sense);
  	wsdebug(")\n");
      }
      else if (WSDLEVEL(WS_TRACE))
  	wstrace("workshop_frame_sensitivities()\n");
  #endif
  #ifdef WSDEBUG_SENSE
      if (ws_debug)
  	for (vp = vs; vp->verb != NULL; vp++)
  	    wsdebug("change: %-21.20s%-21.20s(%s)\n",
  		    "", vp->verb, vp->sense == 1 ?
  		    "Sensitive" : "Insensitive");
  #endif
  
      /*
       * Look for all matching menu entries for the verb. There may be more
       * than one if the verb has both a menu and toolbar entry.
       */
      for (vp = vs; vp->verb != NULL; vp++)
      {
  	cnt = 0;
  	strcpy(cbuf, "amenu");
  	strcat(cbuf, " ");
  	strcat(cbuf, vp->sense ? "enable" : "disable");
  	strcat(cbuf, " ");
  	len = strlen(cbuf);
  	while ((menu_name = lookupVerb(vp->verb, cnt++)) != NULL)
  	{
  	    strcpy(&cbuf[len], menu_name);
  	    coloncmd(cbuf, FALSE);
  	}
      }
      gui_update_menus(0);
      gui_mch_flush();
  }
  
      void
  workshop_set_option(
  	char	*option,		/* name of a supported option */
  	char	*value)			/* value to set option to */
  {
      char	 cbuf[BUFSIZ];		/* command buffer */
  
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
      {
  	wstrace("workshop_set_option(%s, %s)\n", option, value);
      }
  #endif
  
      cbuf[0] = NUL;
      switch (*option)		/* switch on 1st letter */
      {
  	case 's':
  	    if (strcmp(option, "syntax") == 0)
  		vim_snprintf(cbuf, sizeof(cbuf), "syntax %s", value);
  	    else if (strcmp(option, "savefiles") == 0)
  		; /* XXX - Not yet implemented */
  	    break;
  
  	case 'l':
  	    if (strcmp(option, "lineno") == 0)
  		sprintf(cbuf, "set %snu",
  			(strcmp(value, "on") == 0) ? "" : "no");
  	    break;
  
  	case 'p':
  	    if (strcmp(option, "parentheses") == 0)
  		sprintf(cbuf, "set %ssm",
  			(strcmp(value, "on") == 0) ? "" : "no");
  	    break;
  
  	case 'w':
  	    /* this option is set by a direct call */
  #ifdef WSDEBUG
  	    wsdebug("workshop_set_option: "
  		    "Got unexpected workshopkeys option");
  #endif
  	    break;
  
  	case 'b':	/* these options are set from direct calls */
  	    if (option[7] == NUL && strcmp(option, "balloon") == 0)
  	    {
  #ifdef WSDEBUG
  		/* set by direct call to workshop_balloon_mode */
  		wsdebug("workshop_set_option: "
  			"Got unexpected ballooneval option");
  #endif
  	    }
  	    else if (strcmp(option, "balloondelay") == 0)
  	    {
  #ifdef WSDEBUG
  		/* set by direct call to workshop_balloon_delay */
  		wsdebug("workshop_set_option: "
  			"Got unexpected balloondelay option");
  #endif
  	    }
  	    break;
      }
      if (cbuf[0] != NUL)
  	coloncmd(cbuf, TRUE);
  }
  
  
      void
  workshop_balloon_mode(
  	Boolean	 on)
  {
      char	 cbuf[BUFSIZ];		/* command buffer */
  
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_balloon_mode(%s)\n", on ? "True" : "False");
  #endif
  
      sprintf(cbuf, "set %sbeval", on ? "" : "no");
      coloncmd(cbuf, TRUE);
  }
  
  
      void
  workshop_balloon_delay(
  	int	 delay)
  {
      char	 cbuf[BUFSIZ];		/* command buffer */
  
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_balloon_delay(%d)\n", delay);
  #endif
  
      sprintf(cbuf, "set bdlay=%d", delay);
      coloncmd(cbuf, TRUE);
  }
  
  
      void
  workshop_show_balloon_tip(
  	char	*tip)
  {
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_show_balloon_tip(%s)\n", tip);
  #endif
  
      if (balloonEval != NULL)
  	gui_mch_post_balloon(balloonEval, (char_u *)tip);
  }
  
  
      void
  workshop_hotkeys(
  	Boolean	on)
  {
      char	 cbuf[BUFSIZ];		/* command buffer */
      MenuMap	*mp;			/* iterate over menuMap entries */
  
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_hotkeys(%s)\n", on ? "True" : "False");
  #endif
  
      workshopHotKeysEnabled = on;
      if (workshopHotKeysEnabled)
  	for (mp = menuMap; mp < &menuMap[menuMapSize]; mp++)
  	{
  	    if (mp->accel != NULL)
  	    {
  		vim_snprintf(cbuf, sizeof(cbuf),
  			"map %s :wsverb %s<CR>", mp->accel, mp->verb);
  		coloncmd(cbuf, TRUE);
  	    }
  	}
      else
  	for (mp = menuMap; mp < &menuMap[menuMapSize]; mp++)
  	{
  	    if (mp->accel != NULL)
  	    {
  		vim_snprintf(cbuf, sizeof(cbuf), "unmap %s", mp->accel);
  		coloncmd(cbuf, TRUE);
  	    }
  	}
  }
  
  /*
   * A button in the toolbar has been pushed.
   */
  /*ARGSUSED*/
      int
  workshop_get_positions(
  	void		*clientData,	/* unused */
  	char	       **filename,	/* output data */
  	int		*curLine,	/* output data */
  	int		*curCol,	/* output data */
  	int		*selStartLine,	/* output data */
  	int		*selStartCol,	/* output data */
  	int		*selEndLine,	/* output data */
  	int		*selEndCol,	/* output data */
  	int		*selLength,	/* output data */
  	char	       **selection)	/* output data */
  {
      static char	 ffname[MAXPATHLEN];
  
  #ifdef WSDEBUG_TRACE
      if (WSDLEVEL(WS_TRACE_VERBOSE | WS_TRACE))
  	wstrace("workshop_get_positions(%#x, \"%s\", ...)\n",
  		clientData, (curbuf && curbuf->b_sfname != NULL)
  				      ? (char *)curbuf->b_sfname : "<None>");
  #endif
  
      strcpy(ffname, (char *) curbuf->b_ffname);
      *filename = ffname;		/* copy so nobody can change b_ffname */
      *curLine = curwin->w_cursor.lnum;
      *curCol = curwin->w_cursor.col;
  
      if (curbuf->b_visual.vi_mode == 'v' &&
  	    equalpos(curwin->w_cursor, curbuf->b_visual.vi_end))
      {
  	*selStartLine = curbuf->b_visual.vi_start.lnum;
  	*selStartCol = curbuf->b_visual.vi_start.col;
  	*selEndLine = curbuf->b_visual.vi_end.lnum;
  	*selEndCol = curbuf->b_visual.vi_end.col;
  	*selection = get_selection(curbuf);
  	if (*selection)
  	    *selLength = strlen(*selection);
  	else
  	    *selLength = 0;
      }
      else
      {
  	*selStartLine = *selEndLine = -1;
  	*selStartCol = *selEndCol = -1;
  	*selLength = 0;
  	*selection = "";
      }
  
      return True;
  }
  
  
  
  /************************************************************************
   * Utility functions
   ************************************************************************/
  
      static char *
  get_selection(
  	buf_T		*buf)		/* buffer whose selection we want */
  {
      pos_T	*start;		/* start of the selection */
      pos_T	*end;		/* end of the selection */
      char	*lp;		/* pointer to actual line data */
      int		 llen;		/* length of actual line data */
      char	*sp;		/* pointer to selection  buffer */
      int		 slen;		/* string length in selection buffer */
      int		 size;		/* size of selection buffer */
      char	*new_sp;	/* temp pointer to new sp */
      int		 lnum;		/* line number we are appending */
  
      if (buf->b_visual.vi_mode == 'v')
      {
  	start = &buf->b_visual.vi_start;
  	end = &buf->b_visual.vi_end;
  	if (start->lnum == end->lnum)
  	{
  	    /* selection is all on one line */
  	    lp = (char *) ml_get_pos(start);
  	    llen = end->col - start->col + 1;
  	    sp = (char *) malloc(llen + 1);
  	    if (sp != NULL)
  	    {
  		strncpy(sp, lp, llen);
  		sp[llen] = NUL;
  	    }
  	}
  	else
  	{
  	    /* multi-line selection */
  	    lp = (char *) ml_get_pos(start);
  	    llen = strlen(lp);
  	    sp = (char *) malloc(BUFSIZ + llen);
  	    if (sp != NULL)
  	    {
  		size = BUFSIZ + llen;
  		strcpy(sp, lp);
  		sp[llen] = '\n';
  		slen = llen + 1;
  
  		lnum = start->lnum + 1;
  		while (lnum < end->lnum)
  		    sp = append_selection(lnum++, sp, &size, &slen);
  
  		lp = (char *) ml_get(end->lnum);
  		llen = end->col + 1;
  		if ((slen + llen) >= size)
  		{
  		    new_sp = (char *)
  			realloc(sp, slen + llen + 1);
  		    if (new_sp != NULL)
  		    {
  			size += llen + 1;
  			sp = new_sp;
  		    }
  		}
  		if ((slen + llen) < size)
  		{
  		    strncpy(&sp[slen], lp, llen);
  		    sp[slen + llen] = NUL;
  		}
  
  	    }
  	}
      }
      else
  	sp = NULL;
  
      return sp;
  }
  
      static char *
  append_selection(
  	int		 lnum,		/* line number to append */
  	char		*sp,		/* pointer to selection buffer */
  	int		*size,		/* ptr to size of sp */
  	int		*slen)		/* ptr to length of selection string */
  {
      char	*lp;		/* line of data from buffer */
      int		 llen;		/* strlen of lp */
      char	*new_sp;	/* temp pointer to new sp */
  
      lp = (char *)ml_get((linenr_T)lnum);
      llen = strlen(lp);
  
      if ((*slen + llen) <= *size)
      {
  	new_sp = (char *) realloc((void *) sp, BUFSIZ + *slen + llen);
  	if (*new_sp != NUL)
  	{
  	    *size = BUFSIZ + *slen + llen;
  	    sp = new_sp;
  	}
      }
      if ((*slen + llen) > *size)
      {
  	strcat(&sp[*slen], lp);
  	*slen += llen;
  	sp[*slen++] = '\n';
      }
  
      return sp;
  }
  
  
  
      static void
  load_buffer_by_name(
  	char	*filename,		/* the file to load */
  	int	 lnum)			/* an optional line number (or 0) */
  {
      char	 lnumbuf[16];		/* make line number option for :e */
      char	 cbuf[BUFSIZ];		/* command buffer */
  
      if (lnum > 0)
  	sprintf(lnumbuf, "+%d", lnum);
      else
  	lnumbuf[0] = NUL;
  
      vim_snprintf(cbuf, sizeof(cbuf), "e %s %s", lnumbuf, filename);
      coloncmd(cbuf, False);
  }
  
  
      static void
  load_window(
  	char	*filename,		/* filename to load */
  	int	 lnum)			/* linenumber to go to */
  {
      buf_T	*buf;		/* buffer filename is stored in */
      win_T	*win;		/* window filenme is displayed in */
  
      /*
       * Make sure filename is displayed and is the current window.
       */
  
      buf = buflist_findname((char_u *)filename);
      if (buf == NULL || (win = get_window(buf)) == NULL)
      {
  	/* No buffer or buffer is not in current window */
  	/* wsdebug("load_window: load_buffer_by_name(\"%s\", %d)\n",
  		filename, lnum); */
  	load_buffer_by_name(filename, lnum);
      }
      else
      {
  	/* buf is in a window */
  	if (win != curwin)
  	{
  	    win_enter(win, False);
  	    /* wsdebug("load_window: window endter %s\n",
  		    win->w_buffer->b_sfname); */
  	}
  	if (lnum > 0 && win->w_cursor.lnum != lnum)
  	{
  	    warp_to_pc(lnum);
  	    /* wsdebug("load_window: warp to %s[%d]\n",
  		    win->w_buffer->b_sfname, lnum); */
  	}
      }
      out_flush();
  }
  
  
  
      static void
  warp_to_pc(
  	int	 lnum)			/* line number to warp to */
  {
      char	 lbuf[256];		/* build line command here */
  
      if (lnum > 0)
      {
  	if (State & INSERT)
  	    add_to_input_buf((char_u *) "\033", 1);
  	if (isShowing(lnum))
  	    sprintf(lbuf, "%dG", lnum);
  	else
  	    sprintf(lbuf, "%dz.", lnum);
  	add_to_input_buf((char_u *) lbuf, strlen(lbuf));
      }
  }
  
      static Boolean
  isShowing(
  	int	 lnum)			/* tell if line number is showing */
  {
      return lnum >= curwin->w_topline && lnum < curwin->w_botline;
  }
  
  
  
      static win_T *
  get_window(
  	buf_T	*buf)		/* buffer to find window for */
  {
      win_T	*wp = NULL;	/* window filename is in */
  
      for (wp = firstwin; wp != NULL; wp = W_NEXT(wp))
  	if (buf == wp->w_buffer)
  	    break;
      return wp;
  }
  
  
  #if 0 /* not used */
      static int
  get_buffer_number(
  	buf_T		*buf)		/* buffer to get position of */
  {
      buf_T	*bp;		/* iterate over buffer list */
      int		 pos;		/* the position in the buffer list */
  
      pos = 1;
      for (bp = firstbuf; bp != NULL; bp = bp->b_next)
      {
  	if (bp == buf)
  	    return pos;
  	pos++;
      }
  
      return 1;
  }
  #endif
  
      static void
  updatePriority(
  	Boolean		 subMenu)	/* if True then start new submenu pri */
  {
      int		 pri;		/* priority of this menu/item */
      char	*p;
  
      p = strrchr(curMenuPriority, '.');
      ASSERT(p != NULL);
      *p++ = NUL;
  
      pri = atoi(p) + 10;		/* our new priority */
  
      if (subMenu)
  	vim_snprintf(curMenuPriority, sizeof(curMenuPriority),
  					     "%s.%d.0", curMenuPriority, pri);
      else
  	vim_snprintf(curMenuPriority, sizeof(curMenuPriority),
  					       "%s.%d", curMenuPriority, pri);
  }
  
      static char *
  addUniqueMnemonic(
  	char		*mnemonics,	/* currently used mnemonics */
  	char		*label)		/* label of menu needing mnemonic */
  {
      static char	 name[BUFSIZ];	/* buffer for the updated name */
      char	*p;		/* pointer into label */
      char	*found;		/* pointer to possible mnemonic */
  
      found = NULL;
      for (p = label; *p != NUL; p++)
  	if (strchr(mnemonics, *p) == 0)
  	    if (found == NULL || (isupper((int)*p) && islower((int)*found)))
  		found = p;
  
      if (found != NULL)
      {
  	strncpy(name, label, (found - label));
  	strcat(name, "&");
  	strcat(name, found);
      }
      else
  	strcpy(name, label);
  
      return name;
  }
  
  /*
   * Some characters in a menu name must be escaped in vim. Since this is vim
   * specific, it must be done on this side.
   */
      static char *
  fixup(
  	char		*label)
  {
      static char	 buf[BUFSIZ];
      char		*bp;		/* pointer into buf */
      char		*lp;		/* pointer into label */
  
      lp = label;
      bp = buf;
      while (*lp != NUL)
      {
  	if (*lp == ' ' || *lp == '.')
  	    *bp++ = '\\';
  	*bp++ = *lp++;
      }
      *bp = NUL;
  
      return buf;
  }
  
  
  #ifdef NOHANDS_SUPPORT_FUNCTIONS
  
  /* For the NoHands test suite */
  
      char *
  workshop_test_getcurrentfile()
  {
      char	*filename, *selection;
      int		curLine, curCol, selStartLine, selStartCol, selEndLine;
      int		selEndCol, selLength;
  
      if (workshop_get_positions(
  		NULL, &filename, &curLine, &curCol, &selStartLine,
  		&selStartCol, &selEndLine, &selEndCol, &selLength,
  		&selection))
  	return filename;
      else
  	return NULL;
  }
  
      int
  workshop_test_getcursorrow()
  {
      return 0;
  }
  
      int
  workshop_test_getcursorcol()
  {
      char	*filename, *selection;
      int		curLine, curCol, selStartLine, selStartCol, selEndLine;
      int		selEndCol, selLength;
  
      if (workshop_get_positions(
  		NULL, &filename, &curLine, &curCol, &selStartLine,
  		&selStartCol, &selEndLine, &selEndCol, &selLength,
  		&selection))
  	return curCol;
      else
  	return -1;
  }
  
      char *
  workshop_test_getcursorrowtext()
  {
      return NULL;
  }
  
      char *
  workshop_test_getselectedtext()
  {
      char	*filename, *selection;
      int		curLine, curCol, selStartLine, selStartCol, selEndLine;
      int		selEndCol, selLength;
  
      if (workshop_get_positions(
  		NULL, &filename, &curLine, &curCol, &selStartLine,
  		&selStartCol, &selEndLine, &selEndCol, &selLength,
  		&selection))
  	return selection;
      else
  	return NULL;
  }
  
  /*ARGSUSED*/
      void
  workshop_save_sensitivity(char *filename)
  {
  }
  
  #endif
  
      static char *
  fixAccelText(
  	char		*ap)		/* original acceleratorText */
  {
      char	buf[256];	/* build in temp buffer */
      char	*shift;		/* shift string of "" */
  
      if (ap == NULL)
  	return NULL;
  
      /* If the accelerator is shifted use the vim form */
      if (strncmp("Shift+", ap, 6) == 0)
      {
  	shift = "S-";
  	ap += 6;
      }
      else
  	shift = "";
  
      if (*ap == 'F' && atoi(&ap[1]) > 0)
      {
  	vim_snprintf(buf, sizeof(buf), "<%s%s>", shift, ap);
  	return strdup(buf);
      }
      else
  	return NULL;
  }
  
  #ifdef FEAT_BEVAL
      void
  workshop_beval_cb(
  	BalloonEval	*beval,
  	int		 state)
  {
      win_T	*wp;
      char_u	*text;
      int		 type;
      linenr_T	 lnum;
      int		 col;
      int		 idx;
      char	 buf[MAXPATHLEN * 2];
      static int	 serialNo = -1;
  
      if (!p_beval)
  	return;
  
      if (get_beval_info(beval, FALSE, &wp, &lnum, &text, &col) == OK)
      {
  	if (text && text[0])
  	{
  	    /* Send debugger request */
  	    if (strlen((char *) text) > (MAXPATHLEN/2))
  	    {
  		/*
  		 * The user has probably selected the entire
  		 * buffer or something like that - don't attempt
  		 * to evaluate it
  		 */
  		return;
  	    }
  
  	    /*
  	     * WorkShop expects the col to be a character index, not
  	     * a column number. Compute the index from col. Also set
  	     * line to 0 because thats what dbx expects.
  	     */
  	    idx = computeIndex(col, text, beval->ts);
  	    if (idx > 0)
  	    {
  		lnum = 0;
  
  		/*
  		 * If successful, it will respond with a balloon cmd.
  		 */
  		if (state & ControlMask)
  		    /* Evaluate *(expression) */
  		    type = (int)GPLineEval_INDIRECT;
  		else if (state & ShiftMask)
  		    /* Evaluate type(expression) */
  		    type = (int)GPLineEval_TYPE;
  		else
  		    /* Evaluate value(expression) */
  		    type = (int)GPLineEval_EVALUATE;
  
  		/* Send request to dbx */
  		vim_snprintf(buf, sizeof(buf), "toolVerb debug.balloonEval "
  			"%s %ld,0 %d,0 %d,%d %ld %s\n",
  			(char *)wp->w_buffer->b_ffname,
  			(long)lnum, idx, type, serialNo++,
  			(long)strlen((char *)text), (char *)text);
  		balloonEval = beval;
  		workshop_send_message(buf);
  	    }
  	}
      }
  }
  #endif
  
  
      static int
  computeIndex(
  	int		 wantedCol,
  	char_u		*line,
  	int		 ts)
  {
      int		 col = 0;
      int		 idx = 0;
  
      while (line[idx])
      {
  	if (line[idx] == '\t')
  	    col += ts - (col % ts);
  	else
  	    col++;
  	idx++;
  	if (col >= wantedCol)
  	    return idx;
      }
  
      return -1;
  }
  
      static void
  addMenu(
  	char		*menu,		/* menu name */
  	char		*accel,		/* accelerator text (optional) */
  	char		*verb)		/* WorkShop action-verb */
  {
      MenuMap		*newMap;
      char		 cbuf[BUFSIZ];
  
      if (menuMapSize >= menuMapMax)
      {
  	newMap = realloc(menuMap,
  		sizeof(MenuMap) * (menuMapMax + MENU_INC));
  	if (newMap != NULL)
  	{
  	    menuMap = newMap;
  	    menuMapMax += MENU_INC;
  	}
      }
      if (menuMapSize < menuMapMax)
      {
  	menuMap[menuMapSize].name = strdup(menu);
  	menuMap[menuMapSize].accel = accel && *accel ? strdup(accel) : NULL;
  	menuMap[menuMapSize++].verb = strdup(verb);
  	if (accel && workshopHotKeysEnabled)
  	{
  	    vim_snprintf(cbuf, sizeof(cbuf),
  					"map %s :wsverb %s<CR>", accel, verb);
  	    coloncmd(cbuf, TRUE);
  	}
      }
  }
  
      static char *
  nameStrip(
  	char		*raw)		/* menu name, possibly with & chars */
  {
      static char		buf[BUFSIZ];	/* build stripped name here */
      char		*bp = buf;
  
      while (*raw)
      {
  	if (*raw != '&')
  	    *bp++ = *raw;
  	raw++;
      }
      *bp = NUL;
      return buf;
  }
  
  
      static char *
  lookupVerb(
  	char	*verb,
  	int	skip)		/* number of matches to skip */
  {
      int		i;		/* loop iterator */
  
      for (i = 0; i < menuMapSize; i++)
  	if (strcmp(menuMap[i].verb, verb) == 0 && skip-- == 0)
  	    return nameStrip(menuMap[i].name);
  
      return NULL;
  }
  
  
      static void
  coloncmd(
  	char	*cmd,		/* the command to print */
  	Boolean	force)		/* force cursor update */
  {
      char_u	*cpo_save = p_cpo;
  
  #ifdef WSDEBUG
      if (WSDLEVEL(WS_TRACE_COLONCMD))
  	wsdebug("Cmd: %s\n", cmd);
  #endif
  
      p_cpo = empty_option;
  
      ALT_INPUT_LOCK_ON;
      do_cmdline_cmd((char_u *)cmd);
      ALT_INPUT_LOCK_OFF;
  
      p_cpo = cpo_save;
  
      if (force)
  	gui_update_screen();
  }
  
  /*
   * setDollarVim -	Given the run directory, search for the vim install
   *			directory and set $VIM.
   *
   *			We can be running out of SUNWspro/bin or out of
   *			SUNWspro/contrib/contrib6/vim5.6/bin so we check
   *			relative to both of these directories.
   */
      static void
  setDollarVim(
  	char	*rundir)
  {
      char	 buf[MAXPATHLEN];
      char	*cp;
  
      /*
       * First case: Running from <install-dir>/SUNWspro/bin
       */
      strcpy(buf, rundir);
      strcat(buf, "/../contrib/contrib6/vim" VIM_VERSION_SHORT "/share/vim/"
  	    VIM_VERSION_NODOT "/syntax/syntax.vim");
      if (access(buf, R_OK) == 0)
      {
  	strcpy(buf, "SPRO_WSDIR=");
  	strcat(buf, rundir);
  	cp = strrchr(buf, '/');
  	if (cp != NULL)
  	    strcpy(cp, "/WS6U2");
  	putenv(strdup(buf));
  
  	strcpy(buf, "VIM=");
  	strcat(buf, rundir);
  	strcat(buf, "/../contrib/contrib6/vim" VIM_VERSION_SHORT "/share/vim/"
  		VIM_VERSION_NODOT);
  	putenv(strdup(buf));
  	return;
      }
  
      /*
       * Second case: Probably running from
       *		<install-dir>/SUNWspro/contrib/contrib6/vim5.6/bin
       */
      strcpy(buf, rundir);
      strcat(buf, "/../../../contrib/contrib6/vim" VIM_VERSION_SHORT
  	    "/share/vim/" VIM_VERSION_NODOT "/syntax/syntax.vim");
      if (access(buf, R_OK) == 0)
      {
  	strcpy(buf, "SPRO_WSDIR=");
  	strcat(buf, rundir);
  	cp = strrchr(buf, '/');
  	if (cp != NULL)
  	    strcpy(cp, "../../../../WS6U2");
  	putenv(strdup(buf));
  
  	strcpy(buf, "VIM=");
  	strcat(buf, rundir);
  	strcat(buf, "/../../../contrib/contrib6/vim" VIM_VERSION_SHORT
  		"/share/vim/" VIM_VERSION_NODOT);
  	putenv(strdup(buf));
  	return;
      }
  }
  
  /*
   * findYourself -	Find the directory we are running from. This is used to
   *			set $VIM. We need to set this because users can install
   *			the package in a different directory than the compiled
   *			directory. This is a Sun Visual WorkShop requirement!
   *
   * Note:		We override a user's $VIM because it won't have the
   *			WorkShop specific files. S/he may not like this but its
   *			better than getting the wrong files (especially as the
   *			user is likely to have $VIM set to 5.4 or later).
   */
      void
  findYourself(
      char	*argv0)
  {
      char	*runpath = NULL;
      char	*path;
      char	*pathbuf;
  
      if (*argv0 == '/')
  	runpath = strdup(argv0);
      else if (*argv0 == '.' || strchr(argv0, '/'))
      {
  	runpath = (char *) malloc(MAXPATHLEN);
  	getcwd(runpath, MAXPATHLEN);
  	strcat(runpath, "/");
  	strcat(runpath, argv0);
      }
      else
      {
  	path = getenv("PATH");
  	if (path != NULL)
  	{
  	    runpath = (char *) malloc(MAXPATHLEN);
  	    pathbuf = strdup(path);
  	    path = strtok(pathbuf, ":");
  	    do
  	    {
  		strcpy(runpath, path);
  		strcat(runpath, "/");
  		strcat(runpath, argv0);
  		if (access(runpath, X_OK) == 0)
  		    break;
  	    } while ((path = strtok(NULL, ":")) != NULL);
  	    free(pathbuf);
  	}
      }
  
      if (runpath != NULL)
      {
  	char runbuf[MAXPATHLEN];
  
  	/*
  	 * We found the run directory. Now find the install dir.
  	 */
  	(void)vim_FullName((char_u *)runpath, (char_u *)runbuf, MAXPATHLEN, 1);
  	path = strrchr(runbuf, '/');
  	if (path != NULL)
  	    *path = NUL;		/* remove the vim/gvim name */
  	path = strrchr(runbuf, '/');
  	if (path != NULL)
  	{
  	    if (strncmp(path, "/bin", 4) == 0)
  		setDollarVim(runbuf);
  	    else if (strncmp(path, "/src", 4) == 0)
  	    {
  		*path = NUL;	/* development tree */
  		setDollarVim(runbuf);
  	    }
  	}
  	free(runpath);
      }
  }
  