/* misc.c -- miscellaneous bindable readline functions. */

/* Copyright (C) 1987-2002 Free Software Foundation, Inc.

   This file is part of the GNU Readline Library, a library for
   reading lines of text with interactive input and history editing.

   The GNU Readline Library is free software; you can redistribute it
   and/or modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2, or
   (at your option) any later version.

   The GNU Readline Library is distributed in the hope that it will be
   useful, but WITHOUT ANY WARRANTY; without even the implied warranty
   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   The GNU General Public License is often shipped with GNU software, and
   is generally kept in a file called COPYING or LICENSE.  If you do not
   have a copy of the license, write to the Free Software Foundation,
   59 Temple Place, Suite 330, Boston, MA 02111 USA. */
#define READLINE_LIBRARY

#if defined (HAVE_CONFIG_H)
#  include <config.h>
#endif

#if defined (HAVE_UNISTD_H)
#  include <unistd.h>
#endif /* HAVE_UNISTD_H */

#if defined (HAVE_STDLIB_H)
#  include <stdlib.h>
#else
#  include "ansi_stdlib.h"
#endif /* HAVE_STDLIB_H */

#if defined (HAVE_LOCALE_H)
#  include <locale.h>
#endif

#include <stdio.h>

/* System-specific feature definitions and include files. */
#include "rldefs.h"
#include "rlmbutil.h"

/* Some standard library routines. */
#include "readline.h"
#include "history.h"

#include "rlprivate.h"
#include "rlshell.h"
#include "xmalloc.h"

static int rl_digit_loop PARAMS((void));
static void _rl_history_set_point PARAMS((void));

/* Forward declarations used in this file */
void _rl_free_history_entry PARAMS((HIST_ENTRY *));

/* If non-zero, rl_get_previous_history and rl_get_next_history attempt
   to preserve the value of rl_point from line to line. */
int _rl_history_preserve_point = 0;

/* Saved target point for when _rl_history_preserve_point is set.  Special
   value of -1 means that point is at the end of the line. */
int _rl_history_saved_point = -1;

/* **************************************************************** */
/*								    */
/*			Numeric Arguments			    */
/*								    */
/* **************************************************************** */

/* Handle C-u style numeric args, as well as M--, and M-digits. */
static int
rl_digit_loop ()
{
  int key, c, sawminus, sawdigits;

  rl_save_prompt ();

  RL_SETSTATE(RL_STATE_NUMERICARG);
  sawminus = sawdigits = 0;
  while (1)
    {
      if (rl_numeric_arg > 1000000)
	{
	  sawdigits = rl_explicit_arg = rl_numeric_arg = 0;
	  rl_ding ();
	  rl_restore_prompt ();
	  rl_clear_message ();
	  RL_UNSETSTATE(RL_STATE_NUMERICARG);
	  return 1;
	}
      rl_message ("(arg: %d) ", rl_arg_sign * rl_numeric_arg);
      RL_SETSTATE(RL_STATE_MOREINPUT);
      key = c = rl_read_key ();
      RL_UNSETSTATE(RL_STATE_MOREINPUT);

      if (c < 0)
	{
	  _rl_abort_internal ();
	  return -1;
	}

      /* If we see a key bound to `universal-argument' after seeing digits,
	 it ends the argument but is otherwise ignored. */
      if (_rl_keymap[c].type == ISFUNC &&
	  _rl_keymap[c].function == rl_universal_argument)
	{
	  if (sawdigits == 0)
	    {
	      rl_numeric_arg *= 4;
	      continue;
	    }
	  else
	    {
	      RL_SETSTATE(RL_STATE_MOREINPUT);
	      key = rl_read_key ();
	      RL_UNSETSTATE(RL_STATE_MOREINPUT);
	      rl_restore_prompt ();
	      rl_clear_message ();
	      RL_UNSETSTATE(RL_STATE_NUMERICARG);
	      return (_rl_dispatch (key, _rl_keymap));
	    }
	}

      c = UNMETA (c);

      if (_rl_digit_p (c))
	{
	  rl_numeric_arg = rl_explicit_arg ? (rl_numeric_arg * 10) + c - '0' : c - '0';
	  sawdigits = rl_explicit_arg = 1;
	}
      else if (c == '-' && rl_explicit_arg == 0)
	{
	  rl_numeric_arg = sawminus = 1;
	  rl_arg_sign = -1;
	}
      else
	{
	  /* Make M-- command equivalent to M--1 command. */
	  if (sawminus && rl_numeric_arg == 1 && rl_explicit_arg == 0)
	    rl_explicit_arg = 1;
	  rl_restore_prompt ();
	  rl_clear_message ();
	  RL_UNSETSTATE(RL_STATE_NUMERICARG);
	  return (_rl_dispatch (key, _rl_keymap));
	}
    }

  /*NOTREACHED*/
}

/* Add the current digit to the argument in progress. */
int
rl_digit_argument (ignore, key)
     int ignore __attribute__((unused)), key;
{
  rl_execute_next (key);
  return (rl_digit_loop ());
}

/* What to do when you abort reading an argument. */
int
rl_discard_argument ()
{
  rl_ding ();
  rl_clear_message ();
  _rl_init_argument ();
  return 0;
}

/* Create a default argument. */
int
_rl_init_argument ()
{
  rl_numeric_arg = rl_arg_sign = 1;
  rl_explicit_arg = 0;
  return 0;
}

/* C-u, universal argument.  Multiply the current argument by 4.
   Read a key.  If the key has nothing to do with arguments, then
   dispatch on it.  If the key is the abort character then abort. */
int
rl_universal_argument (count, key)
     int count __attribute__((unused)), key __attribute__((unused));
{
  rl_numeric_arg *= 4;
  return (rl_digit_loop ());
}

/* **************************************************************** */
/*								    */
/*			History Utilities			    */
/*								    */
/* **************************************************************** */

/* We already have a history library, and that is what we use to control
   the history features of readline.  This is our local interface to
   the history mechanism. */

/* While we are editing the history, this is the saved
   version of the original line. */
HIST_ENTRY *_rl_saved_line_for_history = (HIST_ENTRY *)NULL;

/* Set the history pointer back to the last entry in the history. */
void
_rl_start_using_history ()
{
  using_history ();
  if (_rl_saved_line_for_history)
    _rl_free_history_entry (_rl_saved_line_for_history);

  _rl_saved_line_for_history = (HIST_ENTRY *)NULL;
}

/* Free the contents (and containing structure) of a HIST_ENTRY. */
void
_rl_free_history_entry (entry)
     HIST_ENTRY *entry;
{
  if (entry == 0)
    return;
  if (entry->line)
    free (entry->line);
  free (entry);
}

/* Perhaps put back the current line if it has changed. */
int
rl_maybe_replace_line ()
{
  HIST_ENTRY *temp;

  temp = current_history ();
  /* If the current line has changed, save the changes. */
  if (temp && ((UNDO_LIST *)(temp->data) != rl_undo_list))
    {
      temp = replace_history_entry (where_history (), rl_line_buffer, (histdata_t)rl_undo_list);
      free (temp->line);
      free (temp);
    }
  return 0;
}

/* Restore the _rl_saved_line_for_history if there is one. */
int
rl_maybe_unsave_line ()
{
  if (_rl_saved_line_for_history)
    {
      rl_replace_line (_rl_saved_line_for_history->line, 0);
      rl_undo_list = (UNDO_LIST *)_rl_saved_line_for_history->data;
      _rl_free_history_entry (_rl_saved_line_for_history);
      _rl_saved_line_for_history = (HIST_ENTRY *)NULL;
      rl_point = rl_end;	/* rl_replace_line sets rl_end */
    }
  else
    rl_ding ();
  return 0;
}

/* Save the current line in _rl_saved_line_for_history. */
int
rl_maybe_save_line ()
{
  if (_rl_saved_line_for_history == 0)
    {
      _rl_saved_line_for_history = (HIST_ENTRY *)xmalloc (sizeof (HIST_ENTRY));
      _rl_saved_line_for_history->line = savestring (rl_line_buffer);
      _rl_saved_line_for_history->data = (char *)rl_undo_list;
    }
  return 0;
}

int
_rl_free_saved_history_line ()
{
  if (_rl_saved_line_for_history)
    {
      _rl_free_history_entry (_rl_saved_line_for_history);
      _rl_saved_line_for_history = (HIST_ENTRY *)NULL;
    }
  return 0;
}

static void
_rl_history_set_point ()
{
  rl_point = (_rl_history_preserve_point && _rl_history_saved_point != -1)
		? _rl_history_saved_point
		: rl_end;
  if (rl_point > rl_end)
    rl_point = rl_end;

#if defined (VI_MODE)
  if (rl_editing_mode == vi_mode)
    rl_point = 0;
#endif /* VI_MODE */

  if (rl_editing_mode == emacs_mode)
    rl_mark = (rl_point == rl_end ? 0 : rl_end);
}

void
rl_replace_from_history (entry, flags)
     HIST_ENTRY *entry;
     int flags __attribute__((unused));			/* currently unused */
{
  rl_replace_line (entry->line, 0);
  rl_undo_list = (UNDO_LIST *)entry->data;
  rl_point = rl_end;
  rl_mark = 0;

#if defined (VI_MODE)
  if (rl_editing_mode == vi_mode)
    {
      rl_point = 0;
      rl_mark = rl_end;
    }
#endif
}  

/* **************************************************************** */
/*								    */
/*			History Commands			    */
/*								    */
/* **************************************************************** */

/* Meta-< goes to the start of the history. */
int
rl_beginning_of_history (count, key)
     int count __attribute__((unused)), key;
{
  return (rl_get_previous_history (1 + where_history (), key));
}

/* Meta-> goes to the end of the history.  (The current line). */
int
rl_end_of_history (count, key)
     int count __attribute__((unused)), key __attribute__((unused));
{
  rl_maybe_replace_line ();
  using_history ();
  rl_maybe_unsave_line ();
  return 0;
}

/* Move down to the next history line. */
int
rl_get_next_history (count, key)
     int count, key;
{
  HIST_ENTRY *temp;

  if (count < 0)
    return (rl_get_previous_history (-count, key));

  if (count == 0)
    return 0;

  rl_maybe_replace_line ();

  /* either not saved by rl_newline or at end of line, so set appropriately. */
  if (_rl_history_saved_point == -1 && (rl_point || rl_end))
    _rl_history_saved_point = (rl_point == rl_end) ? -1 : rl_point;

  temp = (HIST_ENTRY *)NULL;
  while (count)
    {
      temp = next_history ();
      if (!temp)
	break;
      --count;
    }

  if (temp == 0)
    rl_maybe_unsave_line ();
  else
    {
      rl_replace_from_history (temp, 0);
      _rl_history_set_point ();
    }
  return 0;
}

/* Get the previous item out of our interactive history, making it the current
   line.  If there is no previous history, just ding. */
int
rl_get_previous_history (count, key)
     int count, key;
{
  HIST_ENTRY *old_temp, *temp;

  if (count < 0)
    return (rl_get_next_history (-count, key));

  if (count == 0)
    return 0;

  /* either not saved by rl_newline or at end of line, so set appropriately. */
  if (_rl_history_saved_point == -1 && (rl_point || rl_end))
    _rl_history_saved_point = (rl_point == rl_end) ? -1 : rl_point;

  /* If we don't have a line saved, then save this one. */
  rl_maybe_save_line ();

  /* If the current line has changed, save the changes. */
  rl_maybe_replace_line ();

  temp = old_temp = (HIST_ENTRY *)NULL;
  while (count)
    {
      temp = previous_history ();
      if (temp == 0)
	break;

      old_temp = temp;
      --count;
    }

  /* If there was a large argument, and we moved back to the start of the
     history, that is not an error.  So use the last value found. */
  if (!temp && old_temp)
    temp = old_temp;

  if (temp == 0)
    rl_ding ();
  else
    {
      rl_replace_from_history (temp, 0);
      _rl_history_set_point ();
    }
  return 0;
}

/* **************************************************************** */
/*								    */
/*			    Editing Modes			    */
/*								    */
/* **************************************************************** */
/* How to toggle back and forth between editing modes. */
int
rl_vi_editing_mode (count, key)
     int count __attribute__((unused)), key;
{
#if defined (VI_MODE)
  _rl_set_insert_mode (RL_IM_INSERT, 1);	/* vi mode ignores insert mode */
  rl_editing_mode = vi_mode;
  rl_vi_insertion_mode (1, key);
#endif /* VI_MODE */

  return 0;
}

int
rl_emacs_editing_mode (count, key)
     int count __attribute__((unused)), key __attribute__((unused));
{
  rl_editing_mode = emacs_mode;
  _rl_set_insert_mode (RL_IM_INSERT, 1); /* emacs mode default is insert mode */
  _rl_keymap = emacs_standard_keymap;
  return 0;
}

/* Function for the rest of the library to use to set insert/overwrite mode. */
void
_rl_set_insert_mode (im, force)
     int im, force __attribute__((unused));
{
#ifdef CURSOR_MODE
  _rl_set_cursor (im, force);
#endif

  rl_insert_mode = im;
}

/* Toggle overwrite mode.  A positive explicit argument selects overwrite
   mode.  A negative or zero explicit argument selects insert mode. */
int
rl_overwrite_mode (count, key)
     int count, key __attribute__((unused));
{
  if (rl_explicit_arg == 0)
    _rl_set_insert_mode (rl_insert_mode ^ 1, 0);
  else if (count > 0)
    _rl_set_insert_mode (RL_IM_OVERWRITE, 0);
  else
    _rl_set_insert_mode (RL_IM_INSERT, 0);

  return 0;
}
