6D/
6D/area/
6D/boards/
6D/city/
6D/color/
6D/corpses/
6D/councils/
6D/htowns/
6D/news/
6D/specials/
6D/src/specials/
6D/src/trades/
/* LoP code team: Remcon, Paradigm
 * Special thinks go out to all these people for verious snippets used
 *   in this codebase.
 * Samson, Cronel, Kratas, John 'Noplex' Bellone, Desden el Chaman Tibetano,
 *   Orion, Scion, Xerves, Whir, Belgarath, Zarius, Gangien, Desden, Cid,
 *   John Strange, Cyrus, Robcon, Xorith, and Jay Roman
 * SMAUG code team: Thoric, Altrag, Blodkai, Narn, Haus,
 *   Scryn, Rennard, Swordbearer, Gorog, Grishnakh, Nivek,
 *   Tricops and Fireblade
 * Merc 2.1 Diku Mud improvments copyright(C)1992, 1993 by Michael
 *   Chastain, Michael Quan, and Mitchell Tse.
 * Original Diku Mud copyright(C)1990, 1991 by Sebastian Hammer,
 *   Michael Seifert, Hans Henrik Strfeldt, Tom Madsen, and Katja Nyboe. */

/* New editor code
 * Author: Cronel (cronel_kal@hotmail.com)
 * of FrozenMUD (empire.digiunix.net 4000)
 * Permission to use and distribute this code is granted provided
 * this header is retained and unaltered, and the distribution
 * package contains all the original files unmodified.
 * If you modify this code and use/distribute modified versions
 * you must give credit to the original author(s). */

#include <ctype.h>
#include <stdarg.h>
#include <string.h>
#include "h/mud.h"

/* Data types and other definitions */
typedef struct editor_line EDITOR_LINE;

#define CHAR_BLOCK (80)
#define BLOCK_ROUNDUP(size) (((size)+CHAR_BLOCK-1) / CHAR_BLOCK * CHAR_BLOCK)
#define NOLIMIT (-1)
#define RESIZE_IF_NEEDED(buf, buf_size, buf_used, added_use) \
  if((buf_used) + (added_use) >= (buf_size)) \
  { \
    short added_size; \
    added_size = BLOCK_ROUNDUP(added_use); \
    if(added_size == 0) \
      added_size = CHAR_BLOCK; \
    RECREATE((buf), char, buf_size + added_size); \
    (buf_size) += added_size; \
  }
struct editor_line
{
  char                   *line; /* line text */
  short                   line_size;  /* size allocated in "line" */
  short                   line_used;  /* bytes used of "line" */
  EDITOR_LINE            *next;
};

struct editor_data
{
  EDITOR_LINE            *first_line; /* list of lines */
  short                   line_count; /* number of lines allocated */
  EDITOR_LINE            *on_line;  /* pointer to the line being edited */
  int                     text_size;  /* total size of text (not counting newlines). */
  int                     max_size; /* max size in chars of string being edited (counting
                                     * newlines) */
  char                   *desc; /* buffer description */
};

/* "max_size" is the maximum size of the final text converted to string 
   "text_size" is equal to the strlen of all lines added up; the actual
   total length when converted to string is equal to this number plus
   line_count * 2, because of the trailing "\r\n" that has to be added
   to each line (of course, plus 1 because of the final \0).
   Thus, if(total_size + line_count * 2 +1) > max_size, the buffer cant
   hold more data. 
   Hence, this define: */
#define TOTAL_BUFFER_SIZE(edd) (edd->text_size + edd->line_count * 2 + 1)

/* Function declarations */
/* funcs to manipulate editor datas */
EDITOR_LINE            *make_new_line(char *str);
void                    discard_editdata(EDITOR_DATA * edd);
EDITOR_DATA            *clone_editdata(EDITOR_DATA * edd);
EDITOR_DATA            *str_to_editdata(char *str, short max_size);
char                   *editdata_to_str(EDITOR_DATA * edd);
void                    discard_line_list(EDITOR_LINE * eline_list);
EDITOR_LINE            *detach_line_range(EDITOR_DATA * edd, short from, short to);
void                    attach_line_range(EDITOR_DATA * edd, short position, EDITOR_LINE * line_list);

/* simple functions to set a description for what's currently being edited */

/* the main editor functions visible to the rest of the code */
/* main editing function */

/* misc functions */
char                   *finer_one_argument(char *argument, char *arg_first);
char                   *text_replace(char *src, char *word_src, char *word_dst, short *pnew_size, short *prepl_count);
EDITOR_LINE            *format_text(EDITOR_LINE * line_list, short max_width);

/* editor sub functions */
void                    editor_print_info(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument);
void                    editor_help(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument);
void                    editor_clear_buf(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument);
void                    editor_search_and_replace(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument);
void                    editor_insert_line(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument);
void                    editor_delete_line(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument);
void                    editor_goto_line(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument);
void                    editor_list(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument);
void                    editor_abort(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument);
void                    editor_escaped_cmd(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument);
void                    editor_save(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument);
void                    editor_format(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument);

/* Edit_data manipulation functions */
EDITOR_LINE            *make_new_line(char *str)
{
  EDITOR_LINE            *new_line;
  short                   size;

  size = strlen(str);
  size = BLOCK_ROUNDUP(size);
  if(size == 0)
    size = CHAR_BLOCK;
  CREATE(new_line, EDITOR_LINE, 1);
  CREATE(new_line->line, char, size);

  new_line->line_size = size;
  new_line->line_used = strlen(str);
  strcpy(new_line->line, str);
  return new_line;
}

void discard_editdata(EDITOR_DATA * edd)
{
  discard_line_list(edd->first_line);
  if(edd->desc)
    STRFREE(edd->desc);
  DISPOSE(edd);
  return;
}

void discard_line_list(EDITOR_LINE * eline_list)
{
  EDITOR_LINE            *eline, *elnext;

  eline = eline_list;
  while(eline)
  {
    elnext = eline->next;
    DISPOSE(eline->line);
    DISPOSE(eline);
    eline = elnext;
  }
  return;
}

/* Returns a copy of the editor */
EDITOR_DATA            *clone_editdata(EDITOR_DATA * edd)
{
  EDITOR_DATA            *new_edd;
  EDITOR_LINE            *new_line, *eline, root_line;

  CREATE(new_edd, EDITOR_DATA, 1);
  new_line = &root_line;
  for(eline = edd->first_line; eline; eline = eline->next)
  {
    new_line->next = make_new_line(eline->line);
    if(edd->on_line == eline)
      new_edd->on_line = new_line->next;
    new_line = new_line->next;
  }
  new_edd->max_size = edd->max_size;
  new_edd->text_size = edd->text_size;
  new_edd->line_count = edd->line_count;
  new_edd->first_line = root_line.next;
  if(VLD_STR(edd->desc))
    new_edd->desc = STRALLOC(edd->desc);
  else
    new_edd->desc = NULL;
  return new_edd;
}

/* Converts a string to editdata. It will stop converting if it reaches "max_size" and will return the partial conversion at that point. */
EDITOR_DATA            *str_to_editdata(char *str, short max_size)
{
  char                   *p;
  EDITOR_DATA            *edd;
  EDITOR_LINE            *eline;
  short                   i;
  short                   tsize, line_count;

  CREATE(edd, EDITOR_DATA, 1);
  eline = make_new_line((char *)"");
  edd->first_line = eline;
  i = 0;
  tsize = 0;
  line_count = 1;
  p = str;
  if(!p)
    p = (char *)"";
  while(*p)
  {
    if(max_size != NOLIMIT && tsize + line_count * 2 + 1 >= max_size)
      break;
    if(*p == '\r');
    else if(*p == '\n')
    {
      eline->line[i] = '\0';
      eline->next = make_new_line((char *)"");
      eline = eline->next;
      line_count++;
      i = 0;
    }
    else
    {
      eline->line[i] = *p;
      eline->line_used++;
      tsize++;
      i++;
      RESIZE_IF_NEEDED(eline->line, eline->line_size, eline->line_used, 1);
    }
    p++;
  }
  if(eline->line[0] != '\0')
  {
    eline->line[i] = '\0';
    eline->next = make_new_line((char *)"");
    line_count++;
    eline = eline->next;
  }
  edd->line_count = line_count;
  edd->on_line = eline;
  edd->max_size = max_size;
  edd->text_size = tsize;
  return edd;
}

/* Removes the tildes from a line, except if it's the last character. */
void smush_tilde(char *str)
{
  int                     len;
  char                    last;
  char                   *strptr;

  strptr = str;
  len = strlen(str);
  if(len)
    last = strptr[len - 1];
  else
    last = '\0';
  for(; *str != '\0'; str++)
  {
    if(*str == '~')
      *str = '-';
  }
  if(len)
    strptr[len - 1] = last;
  return;
}

char                   *editdata_to_str(EDITOR_DATA * edd)
{
  EDITOR_LINE            *eline;
  char                   *buf, *src, *tmp;
  short                   size, used, i;

  CREATE(buf, char, MSL);

  size = MSL;
  used = 0;
  buf[0] = '\0';
  eline = edd->first_line;
  i = 0;
  while(eline)
  {
    /*
     * ignore the last empty line 
     */
    if(eline->next == NULL && eline->line[0] == '\0')
      break;
    src = eline->line;
    while(*src)
    {
      buf[i++] = *src++;
      used++;
      if(used >= size - 3)
      {
        RECREATE(buf, char, size + MSL);

        size += MSL;
      }
    }
    buf[i++] = '\n';
    buf[i++] = '\r';
    used += 2;
    eline = eline->next;
  }
  buf[i++] = '\0';
  used++;
  if(VLD_STR(buf))
  {
    tmp = STRALLOC(buf);
    smush_tilde(tmp);
  }
  else
    tmp = NULL;
  DISPOSE(buf);
  return tmp;
}

/* Returns the line number currently being edited 
 * or -1 if there are no lines in the editor or the
 * insertion point is not positioned anywhere */
short get_online_index(EDITOR_DATA * edd)
{
  short                   i;
  EDITOR_LINE            *eline;

  eline = edd->first_line;
  i = 0;
  while(eline)
  {
    i++;
    if(eline == edd->on_line)
      break;
    eline = eline->next;
  }
  if(eline == NULL)
    return -1;
  else
    return i;
}

/* Sets the insertion point of the editor to the given
 * index. If the index is out of range it will set it
 * to the first or the last line. */
void set_online_index(EDITOR_DATA * edd, short index)
{
  short                   i;
  EDITOR_LINE            *eline, *prev;

  if(edd->first_line == NULL)
    return;
  eline = edd->first_line;
  i = 1;
  prev = NULL;
  while(i < index && eline)
  {
    i++;
    prev = eline;
    eline = eline->next;
  }
  edd->on_line = eline ? eline : prev;
  return;
}

/* Detaches a range of lines from the editor. If the line range is invalid, it 
 * does nothing and returns NULL. If it's valid, it detaches it and returns it 
 * as a list of lines. If the editor insertion point is somewhere within the 
 * range it will leave it pointing immediately after or before the line range.
 * If the range comprises all the lines in the editor, it will leave the 
 * editor empty. */
EDITOR_LINE            *detach_line_range(EDITOR_DATA * edd, short linefrom, short lineto)
{
  EDITOR_LINE            *eline_from_prev, *eline_to, *eline, root;
  short                   i;
  bool                    move_on_line;

  if(linefrom < 1 || lineto > edd->line_count)
  {
    bug("detach_line_range: %d-%d Out of range (%d-%d).", linefrom, lineto, 1, edd->line_count);
    return NULL;
  }
  /*
   * Find the start and end lines 
   */
  root.next = edd->first_line;
  eline = &root;
  i = 1;
  while(i < linefrom)
  {
    i++;
    eline = eline->next;
  }
  eline_from_prev = eline;
  /*
   * We actualy need the line previous to the first one, se we can delink it. Single linked lists... 
   */
  while(i <= lineto)
  {
    i++;
    eline = eline->next;
  }
  eline_to = eline;
  /*
   * Substract the sizes of all lines from the editor 
   */
  /*
   * and check to see if the insertion point is within the range 
   */
  move_on_line = FALSE;
  eline = eline_from_prev->next;
  while(eline != eline_to->next)
  {
    if(eline == edd->on_line)
      move_on_line = TRUE;
    edd->text_size -= eline->line_used;
    edd->line_count--;
    eline = eline->next;
  }
  /*
   * Leave the insertion point somewhere valid 
   */
  if(move_on_line)
  {
    if(eline_to->next)
      /*
       * there's something below the range? leave it there 
       */
      edd->on_line = eline_to->next;
    else if(eline_from_prev != &root)
      /*
       * nothing bellow the range, but something above it? there.. 
       */
      edd->on_line = eline_from_prev;
    else
      /*
       * the entire buffer is being detached. leave it on NULL 
       */
      edd->on_line = NULL;
  }
  /*
   * Delink the lines from the editor 
   */
  eline = eline_from_prev->next;
  eline_from_prev->next = eline_to->next;
  eline_to->next = NULL;
  edd->first_line = root.next;
  return eline;
}

/* Inserts a given list of lines at the specified position within the
 * editor data. If the position is invalid, it will try to insert at the
 * beginning or end. The insertion point is not touched.  */
void attach_line_range(EDITOR_DATA * edd, short position, EDITOR_LINE * line_list)
{
  EDITOR_LINE             root, *eline, *eline_last;
  short                   i;

  if(position > edd->line_count && edd->line_count > 0)
    position = edd->line_count;
  else if(position < 1 || position > edd->line_count)
    position = 1;
  /*
   * Add to text size of edd 
   */
  eline = line_list;
  eline_last = NULL;
  while(eline)
  {
    edd->text_size += eline->line_used;
    edd->line_count++;
    /*
     * remember the last one on the list 
     */
    if(eline->next == NULL)
      eline_last = eline;
    eline = eline->next;
  }
  /*
   * Find the position to insert 
   */
  root.next = edd->first_line;
  eline = &root;
  i = 1;
  while(i < position)
  {
    i++;
    eline = eline->next;
  }
  /*
   * link it at the end 
   */
  eline_last->next = eline->next;
  eline->next = line_list;
  edd->first_line = root.next;
}

/* Main editor functions */
void set_editor_desc(CHAR_DATA *ch, const char *new_desc)
{
  if(!ch || !ch->editor)
    return;
  if(VLD_STR(ch->editor->desc))
    STRFREE(ch->editor->desc);
  if(VLD_STR(new_desc))
    ch->editor->desc = STRALLOC(new_desc);
  else
    ch->editor->desc = NULL;
  return;
}

/* Just to save code... */
void editor_desc_printf(CHAR_DATA *ch, const char *desc_fmt, ...)
{
  char                    buf[MSL * 2]; /* umpf.. */
  va_list                 args;

  va_start(args, desc_fmt);
  vsnprintf(buf, MSL * 2, desc_fmt, args);
  va_end(args);
  set_editor_desc(ch, buf);
  return;
}

void start_editing_nolimit(CHAR_DATA *ch, char *old_text, short max_total)
{
  if(!ch->desc)
  {
    bug("%s", "Fatal: start_editing: no desc");
    return;
  }

  if(ch->substate == SUB_RESTRICTED)
    bug("%s", "NOT GOOD: start_editing: ch->substate == SUB_RESTRICTED");

  if(ch->editor)
    stop_editing(ch);

  set_char_color(AT_CYAN, ch);
  send_to_char("&cBegin entering your text now\r\nWhile writing you can use the below commands.\r\n/? = help /s = save and exit /c = clear /l = list /i = insert line /g = goto line number\r\n", ch);
  send_to_char("-----------------------------------------------------------------------\r\n", ch);
  if(!old_text)
    old_text = (char *)"";

  if(!old_text)
    bug("%s: old_text is NULL!", __FUNCTION__);
  else
  {
    ch->editor = str_to_editdata(old_text, max_total);
    ch->editor->desc = STRALLOC("Unknown buffer");
    ch->desc->connected = CON_EDITING;
    send_to_char("> ", ch);
    return;
  }
  set_char_color(AT_GREEN, ch);
  send_to_char("Begin entering your text now (/? = help /s = save /c = clear /l = list)\r\n", ch);
  send_to_char("-----------------------------------------------------------------------\r\n> ", ch);

  ch->editor->desc = STRALLOC("unkown buffer");
  ch->desc->connected = CON_EDITING;

}

char                   *copy_buffer(CHAR_DATA *ch)
{
  char                   *buf;

  if(!ch)
  {
    bug("%s", "copy_buffer: null ch");
    return NULL;
  }
  if(!ch->editor)
  {
    bug("%s", "copy_buffer: null editor");
    return NULL;
  }
  buf = editdata_to_str(ch->editor);
  return buf;
}

char                   *editdata_to_str_nohash(EDITOR_DATA * edd)
{
  EDITOR_LINE            *eline;
  char                   *buf, *src, *tmp;
  short                   size, used, i;

  CREATE(buf, char, MSL);

  size = MSL;
  used = 0;
  buf[0] = '\0';
  eline = edd->first_line;
  i = 0;
  while(eline)
  {
    /*
     * ignore the last empty line 
     */
    if(eline->next == NULL && eline->line[0] == '\0')
      break;
    src = eline->line;
    while(*src)
    {
      buf[i++] = *src++;
      used++;
      if(used >= size - 3)
      {
        RECREATE(buf, char, size + MSL);

        size += MSL;
      }
    }
    buf[i++] = '\n';
    buf[i++] = '\r';
    used += 2;
    eline = eline->next;
  }
  buf[i++] = '\0';
  used++;
  if(VLD_STR(buf))
  {
    tmp = STRALLOC(buf);
    smush_tilde(tmp);
  }
  else
    tmp = NULL;
  STRFREE(buf);
  return tmp;
}

char                   *copy_buffer_nohash(CHAR_DATA *ch)
{
  char                   *buf;

  if(!ch)
  {
    bug("%s", "copy_buffer: null ch");
    return NULL;
  }
  if(!ch->editor)
  {
    bug("%s", "copy_buffer: null editor");
    return NULL;
  }
  buf = editdata_to_str_nohash(ch->editor);
  return buf;
}

void stop_editing(CHAR_DATA *ch)
{
  set_char_color(AT_PLAIN, ch);
  discard_editdata(ch->editor);
  ch->editor = NULL;
  send_to_char("Done.\r\n", ch);
  ch->dest_buf = NULL;
  ch->spare_ptr = NULL;
  ch->substate = SUB_NONE;
  if(!ch)
  {
    bug("%s", "Fatal: stop_editing: no character");
    return;
  }
  if(!ch->desc)
  {
    bug("%s", "Fatal: stop_editing: no desc");
    return;
  }
  ch->desc->connected = CON_PLAYING;
  return;
}

void edit_buffer(CHAR_DATA *ch, char *argument)
{
  DESCRIPTOR_DATA        *d;
  EDITOR_DATA            *edd;
  EDITOR_LINE            *newline;
  char                    cmd[MIL];
  short                   linelen;
  bool                    cont_line;
  char                   *p;

  d = ch->desc;
  if(d == NULL)
  {
    send_to_char("You have no descriptor.\r\n", ch);
    return;
  }
  if(d->connected != CON_EDITING)
  {
    send_to_char("You can't do that!\r\n", ch);
    bug("%s", "Edit_buffer: d->connected != CON_EDITING");
    return;
  }
  if(ch->substate <= SUB_PAUSE)
  {
    send_to_char("You can't do that!\r\n", ch);
    bug("Edit_buffer: illegal ch->substate (%d)", ch->substate);
    d->connected = CON_PLAYING;
    return;
  }
  if(!ch->editor)
  {
    send_to_char("You can't do that!\r\n", ch);
    bug("%s", "Edit_buffer: null editor");
    d->connected = CON_PLAYING;
    return;
  }
  edd = ch->editor;
  if((argument[0] == '/' && argument[1] != '/') || argument[0] == '\\')
  {
    argument = one_argument(argument, cmd);
    if(!str_cmp(cmd + 1, "?"))
      editor_help(ch, edd, argument);
    else if(!str_cmp(cmd + 1, "c"))
      editor_clear_buf(ch, edd, argument);
    else if(!str_cmp(cmd + 1, "r"))
      editor_search_and_replace(ch, edd, argument);
    else if(!str_cmp(cmd + 1, "i"))
      editor_insert_line(ch, edd, argument);
    else if(!str_cmp(cmd + 1, "d"))
      editor_delete_line(ch, edd, argument);
    else if(!str_cmp(cmd + 1, "g"))
      editor_goto_line(ch, edd, argument);
    else if(!str_cmp(cmd + 1, "l"))
      editor_list(ch, edd, argument);
    else if(!str_cmp(cmd + 1, "a"))
      editor_abort(ch, edd, argument);
    else if(!str_cmp(cmd + 1, "s"))
      editor_save(ch, edd, argument);
    else if(!str_cmp(cmd + 1, "!"))
      editor_escaped_cmd(ch, edd, argument);
    else if(!str_cmp(cmd + 1, "p"))
      editor_print_info(ch, edd, argument);
    else if(!str_cmp(cmd + 1, "f"))
      editor_format(ch, edd, argument);
    else
      send_to_char("Uh? Type '/?' to see the list of valid editor commands.\r\n", ch);
    if(cmd[1] != 'a' && cmd[1] != 's')
      send_to_char("> ", ch);
    return;
  }
  /*
   * Kludgy fix. Read_from_buffer in comm.c adds a space on
   * empty lines. Don't let this fill up usable buffer space.. 
   */
  if(argument[0] == ' ' && argument[1] == '\0')
    argument[0] = '\0';
  linelen = strlen(argument);
  /*
   * "Line continuation" feature to go around the "Line too long" 
   * truncation forced by the input functions in comm.c (could be 
   * done with /g too). 
   */
  p = argument + linelen - 1;
  while(p > argument && isspace(*p))
    p--;
  if(p > argument && *p == '\\')
  {
    cont_line = TRUE;
    *p = '\0';
  }
  else
    cont_line = FALSE;
  if(edd->max_size != NOLIMIT && TOTAL_BUFFER_SIZE(edd) + linelen + 2 >= edd->max_size)
  {
    send_to_char("Buffer full.\r\n", ch);
    editor_save(ch, edd, (char *)"");
  }
  else
  {
    /*
     * add it to the current line 
     */
    RESIZE_IF_NEEDED(edd->on_line->line, edd->on_line->line_size, edd->on_line->line_used, linelen + 1);
    strcat(edd->on_line->line, argument);
    edd->on_line->line_used += linelen;
    edd->text_size += linelen;
    /*
     * create a line and advance to it 
     */
    if(!cont_line)
    {
      newline = make_new_line((char *)"");
      newline->next = edd->on_line->next;
      edd->on_line->next = newline;
      edd->on_line = newline;
      edd->line_count++;
    }
    else
      send_to_char("(Continued)\r\n", ch);
    send_to_char("> ", ch);
  }
}

void editor_print_info(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument)
{
  ch_printf(ch, "Currently editing: %s\r\n", edd->desc ? edd->desc : "(Null description)");
  ch_printf(ch, "Total lines: %4d   On line:  %4d\r\n", edd->line_count, get_online_index(edd));
  ch_printf(ch, "Buffer size: %4d   Max size: ", TOTAL_BUFFER_SIZE(edd));
  if(edd->max_size == NOLIMIT)
    send_to_char("Infinite\r\n", ch);
  else
    ch_printf(ch, "%d\r\n", edd->max_size);
  return;
}

void editor_help(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument)
{
  short                   i;
  const char             *arg[] = { "", "l", "c", "d", "g", "i", "r", "a", "p", "!", "s", "f", NULL };
  const char             *editor_help[] = {
    /*
     * general help 
     */
    "Editing commands\r\n"
      "---------------------------------\r\n"
      "/l [range]      list buffer\r\n"
      "/c              clear buffer\r\n"
      "/d <line>       delete line\r\n"
      "/g <line>       goto line\r\n"
      "/i <line>       insert line\r\n"
      "/r <old> <new>  global replace\r\n"
      "/a              abort editing\r\n"
      "/p              print information\r\n"
      "/! <command>    execute command (do not use another editing command)\r\n"
      "/s              save buffer\r\n" "/f [range]      format paragraph\r\n" "Type /? <command>  to get more information on each command.\r\n\r\n",
    "/l [range]: Lists the buffer. Shows what you've written. Optionaly\r\n" "   takes a range of lines as argument.\r\n",
    "/c: Clears the buffer, leaving only one empty line.\r\n",
    "/d <line>: Deletes a line. If you delete the line currently being\r\n" "   edited, the insertion point is moved down if possible, if not, up.\r\n",
    "/g <line>: Moves the insertion point to a given line.\r\n",
    "/i <line>: Inserts an empty line before the given line.\r\n",
    "/r <old text> <new text>: Global search and replace text. The arguments\r\n"
      "  are case-sensitive. To replace a multi-word text, surround it with\r\n"
      "  single quotes. When inside quotes, you must escape the single quote\r\n"
      "  character, double quote character, and the bar: (') becomes (\\'),\r\n" "  (\") becomes (\\\") and (\\) becomes (\\\\)\r\n",
    "/a: Aborts edition, terminating the edition session and throwing\r\n" "   away what you've edited.\r\n",
    "/p: Prints information about the current editing session.\r\n",
    "/!: Escaped command. Executes the given command as if you were\r\n" "   outside the editor. This is only allowed to imms, since it can\r\n" "   potentialy crash the mud.\r\n",
    "/s: Saves the current buffer, terminating the edition session.\r\n",
    "/f [range] [width]; Format paragraph. Takes the text in the \r\n"
      "   given lines and formats it to form a uniform looking \"paragraph\",\r\n"
      "   by concatenating the lines and breaking them, doing word wrapping.\r\n"
      "   It gets rid of excess spaces, respecting only the spaces at the\r\n"
      "   beginning of the first line. First optional argument is the line\r\n"
      "   range to wich the formatting is to be applied, wich can be two\r\n"
      "   line numbers or the word \"all\". If no range is given, the\r\n"
      "   formatting is applied to the whole buffer. Second optional argument\r\n" "   is the maximum width of lines; this value defaults to 75 characters.\r\n",
  };
  for(i = 0; arg[i] != NULL; i++)
  {
    if(!str_cmp(argument, arg[i]))
      break;
  }
  if(arg[i] == NULL)
    send_to_char("No editor help about that.\r\n", ch);
  else
    send_to_char(editor_help[i], ch);
  return;
}

void editor_clear_buf(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument)
{
  char                   *desc;
  short                   max_size;

  max_size = edd->max_size;
  if(VLD_STR(edd->desc))
    desc = STRALLOC(edd->desc);
  else
    desc = NULL;
  discard_editdata(edd);
  ch->editor = str_to_editdata((char *)"", max_size);
  ch->editor->desc = desc;
  send_to_char("Buffer cleared.\r\n", ch);
  return;
}

void editor_search_and_replace(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument)
{
  char                    word_src[MIL];
  char                    word_dst[MIL];
  EDITOR_DATA            *cloned_edd;
  EDITOR_LINE            *eline;
  char                   *new_text;
  short                   new_size, repl_count, line_repl;

  argument = finer_one_argument(argument, word_src);
  argument = finer_one_argument(argument, word_dst);
  if(word_src[0] == '\0' || word_dst[0] == '\0')
  {
    send_to_char("Need word to replace, and replacement.\r\n", ch);
    return;
  }
  if(strcmp(word_src, word_dst) == 0)
  {
    send_to_char("Done.\r\n", ch);
    return;
  }
  /*
   * Warning: the replacement of the word can result in the buffer growing
   * * larger than its maximum allowed size. To control this, the buffer is
   * * cloned, the replacement is applied to the clone, and if the size results
   * * legal after the operation, the original buffer is discarded and the 
   * * clone is assigned as the current editing buffer. If the clone's size
   * * results too large after the replacement, the clone is simply discarded
   * * and a warning is given to the user 
   * * It's a lot of overhead but it'll have to do... 
   */
  cloned_edd = clone_editdata(edd);
  eline = cloned_edd->first_line;
  repl_count = 0;
  while(eline)
  {
    new_text = text_replace(eline->line, word_src, word_dst, &new_size, &line_repl);
    DISPOSE(eline->line);
    eline->line = new_text;
    cloned_edd->text_size -= eline->line_used;
    eline->line_used = strlen(eline->line);
    cloned_edd->text_size += eline->line_used;
    eline->line_size = new_size;
    repl_count += line_repl;
    eline = eline->next;
  }
  if(cloned_edd->max_size != NOLIMIT && TOTAL_BUFFER_SIZE(cloned_edd) >= cloned_edd->max_size)
  {
    send_to_char("As a result of this operation, the buffer would grow\r\n" "larger than its maximum allowed size. Operation has been\r\ncancelled.\r\n", ch);
    discard_editdata(cloned_edd);
  }
  else
  {
    ch_printf(ch, "Replacing all occurrences of '%s' with '%s'...\r\n", word_src, word_dst);
    discard_editdata(edd);
    ch->editor = cloned_edd;
    ch_printf(ch, "Found and replaced %d occurrence(s).\r\n", repl_count);
  }
  return;
}

void editor_insert_line(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument)
{
  short                   lineindex;
  EDITOR_LINE            *newline;

  if(argument[0] == '\0' || !is_number(argument))
  {
    send_to_char("Must supply the line number.\r\n", ch);
    return;
  }
  lineindex = atoi(argument);
  if(lineindex < 1 || lineindex > edd->line_count)
  {
    ch_printf(ch, "Line number is out of range (1-%d).\r\n", edd->line_count);
    return;
  }
  newline = make_new_line((char *)"");
  attach_line_range(edd, lineindex, newline);
  ch_printf(ch, "Inserted line at %d.\r\n", lineindex);
  return;
}

void editor_delete_line(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument)
{
  short                   lineindex;
  EDITOR_LINE            *del_line;

  if(argument[0] == '\0' || !is_number(argument))
  {
    send_to_char("Must supply the line number.\r\n", ch);
    return;
  }
  lineindex = atoi(argument);
  if(lineindex < 1 || lineindex > edd->line_count)
  {
    ch_printf(ch, "Line number is out of range (1-%d).\r\n", edd->line_count);
    return;
  }
  if(edd->line_count == 1)
  {
    if(edd->first_line->line[0] != '\0')
    {
      edd->first_line->line[0] = '\0';
      edd->first_line->line_used = 0;
      edd->text_size = 0;
      send_to_char("Deleted line 1.\r\n", ch);
    }
    else
      send_to_char("The buffer is empty.\r\n", ch);
    return;
  }
  del_line = detach_line_range(edd, lineindex, lineindex);  /* TEST ME */
  discard_line_list(del_line);
  ch_printf(ch, "Deleted line %d.\r\n", lineindex);
  return;
}

void editor_goto_line(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument)
{
  short                   lineindex;

  if(!VLD_STR(argument) || !is_number(argument))
  {
    send_to_char("Must supply the line number.\r\n", ch);
    return;
  }
  lineindex = atoi(argument);
  if(lineindex < 1 || lineindex > edd->line_count)
  {
    ch_printf(ch, "Line number is out of range (1-%d).\r\n", edd->line_count);
    return;
  }
  set_online_index(edd, lineindex);
  ch_printf(ch, "On line %d.\r\n", lineindex);
  return;
}

void editor_list(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument)
{
  EDITOR_LINE            *eline;
  short                   line_num;
  short                   from, to;
  char                    arg1[MIL];

  argument = one_argument(argument, arg1);
  if(arg1[0] != '\0' && is_number(arg1))
    from = atoi(arg1);
  else
    from = 1;
  argument = one_argument(argument, arg1);
  if(arg1[0] != '\0' && is_number(arg1))
    to = atoi(arg1);
  else
    to = edd->line_count;
  send_to_pager("------------------\r\n", ch);
  line_num = 1;
  eline = edd->first_line;
  while(eline)
  {
    if(line_num >= from && line_num <= to)
      pager_printf(ch, "%2d>%c%s\r\n", line_num, eline == edd->on_line ? '*' : ' ', eline->line);
    eline = eline->next;
    line_num++;
  }
  send_to_pager("------------------\r\n", ch);
  return;
}

void editor_abort(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument)
{
  /*
   * We want it to free the data and these are handled in the command to free them if
   * null so clear it and finish it 
   */
  if(ch->substate == SUB_BUG_DESC || ch->substate == SUB_IDEA_DESC || ch->substate == SUB_TYPO_DESC)
  {
    editor_clear_buf(ch, edd, argument);
    ch->desc->connected = CON_PLAYING;
    if(ch->last_cmd)
      (*ch->last_cmd) (ch, (char *)"");
    return;
  }

  send_to_char("\r\nAborting... ", ch);
  stop_editing(ch);

  if(ch->tempnum)
    ch->substate = ch->tempnum;
  if(ch->pcdata->in_progress)
    ch->pcdata->in_progress = NULL;
}

void editor_escaped_cmd(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument)
{
  if(get_trust(ch) > LEVEL_IMMORTAL)
  {
    DO_FUN                 *last_cmd;
    int                     substate = ch->substate;

    last_cmd = ch->last_cmd;
    ch->substate = SUB_RESTRICTED;
    interpret(ch, argument);
    ch->substate = substate;
    ch->last_cmd = last_cmd;
    set_char_color(AT_GREEN, ch);
    send_to_char("\r\n", ch);
  }
  else
    send_to_char("You can't use '/!'.\r\n", ch);
  return;
}

void editor_save(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument)
{
  DESCRIPTOR_DATA        *d;

  d = ch->desc;
  d->connected = CON_PLAYING;

  if(!ch->last_cmd)
    return;
  (*ch->last_cmd) (ch, (char *)"");
  return;
}

void editor_format(CHAR_DATA *ch, EDITOR_DATA * edd, char *argument)
{
  EDITOR_LINE            *eline_list, *formatted_lines, *eline;
  short                   lineto, linefrom, width, line_count;
  char                    arg1[MIL];
  bool                    add_final_line, complete_format;

  width = 75;
  linefrom = 1;
  lineto = edd->line_count;
  /*
   * parse out the arguments.. boring 
   */
  if(argument[0] != '\0')
  {
    argument = one_argument(argument, arg1);
    if(str_cmp(arg1, "all"))
    {
      if(arg1[0] == '\0')
      {
        send_to_char("You must give both starting and ending line.\r\n", ch);
        return;
      }
      if(!is_number(arg1))
      {
        send_to_char("Argument must be numeric.\r\n", ch);
        return;
      }
      if(atoi(arg1) < 1 || atoi(arg1) > edd->line_count)
      {
        ch_printf(ch, "Valid line number for first argument is from 1 to %d\r\n", edd->line_count);
        return;
      }
      linefrom = atoi(arg1);
      argument = one_argument(argument, arg1);
      if(arg1[0] == '\0')
      {
        send_to_char("You must give both starting and ending line.\r\n", ch);
        return;
      }
      if(!is_number(arg1))
      {
        send_to_char("Argument must be numeric.\r\n", ch);
        return;
      }
      if(atoi(arg1) < 1 || atoi(arg1) > edd->line_count || atoi(arg1) < linefrom)
      {
        ch_printf(ch, "Valid line number for second argument is from %d to %d\r\n", linefrom, edd->line_count);
        return;
      }
      lineto = atoi(arg1);
    }
    if(argument[0] == '\0')
      width = 75;
    else if(!is_number(argument))
    {
      send_to_char("Argument must be numeric.\r\n", ch);
      return;
    }
    else
    {
      width = atoi(argument);
      if(width < 15 || width > 240)
      {
        send_to_char("Valid widths are from 15 to 240 characters.\r\n", ch);
        return;
      }
    }
  }
  complete_format = FALSE;
  add_final_line = FALSE;
  if(lineto == edd->line_count)
  {
    add_final_line = TRUE;
    if(linefrom == 1)
      complete_format = TRUE;
  }
  eline_list = detach_line_range(edd, linefrom, lineto);
  if(!eline_list)
  {
    bug("%s", "editor_format: Null eline_list");
    return;
  }
  ch_printf(ch, "Formatting lines %d-%d (%d lines) at %d columns...\r\n", linefrom, lineto, lineto - linefrom + 1, width);
  formatted_lines = format_text(eline_list, width);
  /*
   * count the number of formatted lines 
   */
  for(line_count = 0, eline = formatted_lines; eline; line_count++, eline = eline->next);
  attach_line_range(edd, linefrom, formatted_lines);
  if(add_final_line)
  {
    eline = edd->first_line;
    while(eline->next)
      eline = eline->next;
    eline->next = make_new_line((char *)"");
    edd->line_count++;
  }
  /*
   * Check the size! Check the size! 
   */
  if(edd->max_size != NOLIMIT && TOTAL_BUFFER_SIZE(edd) >= edd->max_size)
  {
    send_to_char("As a result of this operation, the buffer would grow\r\n" "larger than its maximum allowed size. Operation has been\r\ncancelled.\r\n", ch);
    formatted_lines = detach_line_range(edd, linefrom, linefrom + line_count - 1);
    attach_line_range(edd, linefrom, eline_list);
  }
  else
  {
    discard_line_list(eline_list);
    ch_printf(ch, "Formatted to %d lines.\r\n", 40);
  }
  if(complete_format)
    set_online_index(edd, edd->line_count);
  return;
}

/* Misc functions */
/* Formats the given list of lines neatly and returns the formatted
 * result in a new list. "Formatting" consists in wrapping lines to
 * have a max of "max_width" at most, doing word wrapping, and getting
 * rid of excess whitespace, except for the whitespace at the start
 * of the first line. */
EDITOR_LINE            *format_text(EDITOR_LINE * line_list, short max_width)
{
  char                   *dest_buf;
  short                   dest_used, dest_size;
  short                   cur_width, i, j;
  short                   start_word, end_word;
  EDITOR_LINE            *eline, *last_line;
  EDITOR_DATA            *edd;

  if(!line_list)
  {
    bug("%s", "format_text: Null EDITOR_LINE");
  }
  /*
   * set up a destination buffer 
   */
  CREATE(dest_buf, char, CHAR_BLOCK);

  dest_used = 1;
  dest_size = CHAR_BLOCK;
  cur_width = 0;
  j = 0;
  /*
   * set up source coordinates 
   */
  eline = line_list;
  i = 0;
  /*
   * copy first line whitespace
   * * loop while space and while size is less than CHAR_BLOCK
   * * copy to destination 
   */
  while(isspace(eline->line[i]) && dest_used < CHAR_BLOCK)
    dest_buf[j++] = eline->line[i++];
  dest_buf[j] = '\0';
  cur_width = j;
  dest_used = j + 1;
  /*
   * loop through lines 
   */
  while(eline)
  {
    /*
     * loop within line 
     */
    while(eline->line[i] != '\0')
    {
      /*
       * loop while space (ignore) 
       */

/* Check for TWO spaces after a full stop. */
      while(isspace(eline->line[i]) && eline->line[i] != '\0')
      {
/*
        if(eline->line[i-1] == "." && !isspace(eline->line[i-2]) && eline->line[i+1] != '\0' && !isspace(eline->line[i+1]) )  // Volk - something. moo
        {

        }
*/
        i++;
      }

      if(eline->line[i] == '\0')
        break;
      start_word = i;
      /*
       * loop while non-space 
       */
      while(!isspace(eline->line[i]) && eline->line[i] != '\0')
        i++;
      end_word = i;
      /*
       * if current width plus the word is bigger than max width 
       */
      if(cur_width + (end_word - start_word) >= max_width)
      {
        /*
         * resize if needed 
         */
        RESIZE_IF_NEEDED(dest_buf, dest_size, dest_used, 2);
        /*
         * break a newline 
         */
        dest_buf[dest_used - 1] = '\0';
        /*
         * The -1 is to overwrite the last space 
         */
        strcat(dest_buf, "\r\n");
        dest_used += 2;
        cur_width = 0;
      }
      /*
       * resize if needed 
       */
      RESIZE_IF_NEEDED(dest_buf, dest_size, dest_used, (end_word - start_word) + 1);
      /*
       * add the word, plus a space 
       */
      strncat(dest_buf, eline->line + start_word, (end_word - start_word));
      strcat(dest_buf, " ");
      dest_used += (end_word - start_word) + 1;
      cur_width += (end_word - start_word) + 1;
    }
    /*
     * advance the line coordinates 
     */
    eline = eline->next;
    i = 0;
  }
  /*
   * convert the dest buffer to a list of lines 
   */
  edd = str_to_editdata(dest_buf, NOLIMIT);
  eline = edd->first_line;
  edd->first_line = NULL;
  discard_editdata(edd);
  DISPOSE(dest_buf);
  /*
   * get rid of the last empty line .. 
   */
  if(eline->next)
  {
    last_line = eline;
    while(last_line->next->next)
      last_line = last_line->next;
    discard_line_list(last_line->next);
    last_line->next = NULL;
  }
  return eline;
}

/* Replaces a word word_src in src for word_dst. Returns a pointer to a newly 
 * allocated buffer containing the line with the replacements. Stores in 
 * pnew_size the size of the allocated buffer, wich may be different from the
 * length of the string and is a multiple of CHAR_BLOCK. Stores in prepl_count
 * the number of replacements it made */
char                   *text_replace(char *src, char *word_src, char *word_dst, short *pnew_size, short *prepl_count)
{
  char                   *dst_buf;
  char                   *next_found, *last_found;
  short                   dst_used, dst_size, len;
  short                   repl_count;

  /*
   * prepare the destination buffer 
   */
  CREATE(dst_buf, char, CHAR_BLOCK);

  dst_size = CHAR_BLOCK;
  dst_used = 0;
  dst_buf[0] = '\0';
  last_found = src;
  repl_count = 0;
  for(;;)
  {
    /*
     * look for next instance of word 
     */
    next_found = strstr(last_found, word_src);
    if(next_found == NULL)
    {
      /*
       * if theres no more instances of word,
       * copy the rest of the src 
       */
      len = strlen(last_found);
      RESIZE_IF_NEEDED(dst_buf, dst_size, dst_used, len + 1);
      strcat(dst_buf, last_found);
      dst_used += len;
      break;
    }
    /*
     * copy the buffer up to this instance of the word
     * and then copy the replacement word 
     */
    len = next_found - last_found + strlen(word_dst);
    RESIZE_IF_NEEDED(dst_buf, dst_size, dst_used, len + 1);
    strncat(dst_buf, last_found, next_found - last_found);
    strcat(dst_buf, word_dst);
    dst_used += len;
    last_found = next_found + strlen(word_src);
    repl_count++;
  }
  *pnew_size = dst_size;
  *prepl_count = repl_count;
  return dst_buf;
}

/* Pick off one argument from a string and return the rest.
   Understands quotes.
   A pickier version than regular one_argument, it will not
   convert to lowercase, and it can handle the (') character
   when it's escaped inside '. */
char                   *finer_one_argument(char *argument, char *arg_first)
{
  char                    cEnd;
  short                   count;
  bool                    escaped;

  count = 0;

  while(isspace(*argument))
    argument++;
  cEnd = ' ';
  if(*argument == '\'' || *argument == '"')
    cEnd = *argument++;
  escaped = FALSE;
  while(*argument != '\0' || ++count >= MIL)
  {
    if(cEnd != ' ' && escaped)
    {
      if(*argument == '\\')
        *arg_first = '\\';
      else if(*argument == '\'')
        *arg_first = '\'';
      else if(*argument == '"')
        *arg_first = '"';
      else
        *arg_first = *argument;
      arg_first++;
      argument++;
      escaped = FALSE;
      continue;
    }
    if(cEnd != ' ' && *argument == '\\' && !escaped)
    {
      escaped = TRUE;
      argument++;
      continue;
    }
    if(*argument == cEnd)
    {
      argument++;
      break;
    }
    *arg_first = *argument;
    arg_first++;
    argument++;
  }
  *arg_first = '\0';
  while(isspace(*argument))
    argument++;
  return argument;
}

#undef CHAR_BLOCK
#undef BLOCK_ROUNDUP
#undef NOLIMIT
#undef RESIZE_IF_NEEDED
#undef TOTAL_BUFFER_SIZE

void nstralloc(char **pointer)
{
  if(!*pointer)
    *pointer = STRALLOC("");
  return;
}