view liboctave/util/cmd-hist.cc @ 30564:796f54d4ddbf stable

update Octave Project Developers copyright for the new year In files that have the "Octave Project Developers" copyright notice, update for 2021. In all .txi and .texi files except gpl.txi and gpl.texi in the doc/liboctave and doc/interpreter directories, change the copyright to "Octave Project Developers", the same as used for other source files. Update copyright notices for 2022 (not done since 2019). For gpl.txi and gpl.texi, change the copyright notice to be "Free Software Foundation, Inc." and leave the date at 2007 only because this file only contains the text of the GPL, not anything created by the Octave Project Developers. Add Paul Thomas to contributors.in.
author John W. Eaton <jwe@octave.org>
date Tue, 28 Dec 2021 18:22:40 -0500
parents 32f4357ac8d9
children 6a5e4ef80a95
line wrap: on
line source

////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 1996-2022 The Octave Project Developers
//
// See the file COPYRIGHT.md in the top-level directory of this
// distribution or <https://octave.org/copyright/>.
//
// This file is part of Octave.
//
// Octave 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 3 of the License, or
// (at your option) any later version.
//
// Octave 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.
//
// You should have received a copy of the GNU General Public License
// along with Octave; see the file COPYING.  If not, see
// <https://www.gnu.org/licenses/>.
//
////////////////////////////////////////////////////////////////////////

#if defined (HAVE_CONFIG_H)
#  include "config.h"
#endif

#include <cstring>

#include <fstream>
#include <sstream>
#include <string>

#include "cmd-edit.h"
#include "cmd-hist.h"
#include "file-ops.h"
#include "lo-error.h"
#include "lo-sysdep.h"
#include "singleton-cleanup.h"
#include "str-vec.h"

#if defined (USE_READLINE)
#include <cstdlib>

#include "oct-rl-hist.h"

#include "file-ops.h"
#include "file-stat.h"
#endif

namespace octave
{
  command_history *command_history::s_instance = nullptr;

#if defined (USE_READLINE)

  class
  gnu_history : public command_history
  {
  public:

    gnu_history (void)
      : command_history (), mark (0) { }

    ~gnu_history (void) = default;

    void do_process_histcontrol (const std::string&);

    std::string do_histcontrol (void) const;

    bool do_add (const std::string&);

    void do_remove (int);

    void do_clear (void);

    int do_where (void) const;

    int do_length (void) const;

    int do_max_input_history (void) const;

    int do_base (void) const;

    int do_current_number (void) const;

    void do_stifle (int);

    int do_unstifle (void);

    int do_is_stifled (void) const;

    void do_set_mark (int);

    int do_goto_mark (void);

    void do_read (const std::string&, bool);

    void do_read_range (const std::string&, int, int, bool);

    void do_write (const std::string&) const;

    void do_append (const std::string&);

    void do_truncate_file (const std::string&, int) const;

    string_vector do_list (int, bool) const;

    std::string do_get_entry (int) const;

    void do_replace_entry (int, const std::string&);

    void do_clean_up_and_save (const std::string&, int);

  private:

    int mark;
  };

  void
  gnu_history::do_process_histcontrol (const std::string& control_arg)
  {
    m_history_control = 0;

    std::size_t len = control_arg.length ();
    std::size_t beg = 0;

    while (beg < len)
      {
        if (control_arg[beg] == ':')
          beg++;
        else
          {
            std::size_t end = control_arg.find (':', beg);

            if (end == std::string::npos)
              end = len;

            std::string tmp = control_arg.substr (beg, end-beg);

            if (tmp == "erasedups")
              m_history_control |= HC_ERASEDUPS;
            else if (tmp == "ignoreboth")
              m_history_control |= (HC_IGNDUPS | HC_IGNSPACE);
            else if (tmp == "ignoredups")
              m_history_control |= HC_IGNDUPS;
            else if (tmp == "ignorespace")
              m_history_control |= HC_IGNSPACE;
            else
              (*current_liboctave_warning_with_id_handler)
                ("Octave:history-control",
                 "unknown histcontrol directive %s", tmp.c_str ());

            if (end != std::string::npos)
              beg = end + 1;
          }
      }
  }

  std::string
  gnu_history::do_histcontrol (void) const
  {
    // FIXME: instead of reconstructing this value, should we just save
    // the string we were given when constructing the command_history object?

    std::string retval;

    if (m_history_control & HC_IGNSPACE)
      retval.append ("ignorespace");

    if (m_history_control & HC_IGNDUPS)
      {
        if (retval.length () > 0)
          retval += ':';

        retval.append ("ignoredups");
      }

    if (m_history_control & HC_ERASEDUPS)
      {
        if (retval.length () > 0)
          retval += ':';

        retval.append ("erasedups");
      }

    return retval;
  }

  bool
  gnu_history::do_add (const std::string& s)
  {
    if (! do_ignoring_entries ())
      {
        if (s.empty ()
            || (s.length () == 1 && (s[0] == '\r' || s[0] == '\n')))
          return false;

        // Strip newline before adding to list
        std::string stmp = s;
        if (stmp.back () == '\n')
          stmp.pop_back ();

        int added = ::octave_add_history (stmp.c_str (), m_history_control);
        m_lines_this_session += added;
        return added > 0 ? true : false;
      }
    return false;
  }

  void
  gnu_history::do_remove (int n)
  {
    ::octave_remove_history (n);
  }

  void
  gnu_history::do_clear (void)
  {
    ::octave_clear_history ();
  }

  int
  gnu_history::do_where (void) const
  {
    return ::octave_where_history ();
  }

  int
  gnu_history::do_length (void) const
  {
    return ::octave_history_length ();
  }

  int
  gnu_history::do_max_input_history (void) const
  {
    return ::octave_max_input_history ();
  }

  int
  gnu_history::do_base (void) const
  {
    return ::octave_history_base ();
  }

  int
  gnu_history::do_current_number (void) const
  {
    return m_size > 0 ? do_base () + do_where () : -1;
  }

  void
  gnu_history::do_stifle (int n)
  {
    ::octave_stifle_history (n);
  }

  int
  gnu_history::do_unstifle (void)
  {
    return ::octave_unstifle_history ();
  }

  int
  gnu_history::do_is_stifled (void) const
  {
    return ::octave_history_is_stifled ();
  }

  void
  gnu_history::do_set_mark (int n)
  {
    mark = n;
  }

  int
  gnu_history::do_goto_mark (void)
  {
    if (mark)
      {
        char *line = ::octave_history_goto_mark (mark);

        if (line)
          {
            command_editor::insert_text (line);

            command_editor::clear_undo_list ();
          }
      }

    mark = 0;

    // FIXME: for operate_and_get_next.
    command_editor::remove_startup_hook (command_history::goto_mark);

    return 0;
  }

  void
  gnu_history::do_read (const std::string& f, bool must_exist)
  {
    if (! f.empty ())
      {
        int status = ::octave_read_history (f.c_str ());

        if (status != 0 && must_exist)
          {
            std::string msg = "reading file '" + f + "'";

            error (status, msg);
          }
        else
          {
            m_lines_in_file = do_where ();

            ::octave_using_history ();
          }
      }
    else
      error ("gnu_history::read: missing filename");
  }

  void
  gnu_history::do_read_range (const std::string& f, int from, int to,
                              bool must_exist)
  {
    if (from < 0)
      from = m_lines_in_file;

    if (! f.empty ())
      {
        int status = ::octave_read_history_range (f.c_str (), from, to);

        if (status != 0 && must_exist)
          {
            std::ostringstream buf;
            buf << "reading lines " << from << " to " << to
                << " from file '" << f << "'";

            error (status, buf.str ());
          }
        else
          {
            m_lines_in_file = do_where ();

            ::octave_using_history ();
          }
      }
    else
      error ("gnu_history::read_range: missing filename");
  }

  void
  gnu_history::do_write (const std::string& f_arg) const
  {
    if (m_initialized)
      {
        std::string f = f_arg;

        if (f.empty ())
          f = m_file;

        if (! f.empty ())
          {
            // Try to create the folder if it does not exist
            std::string hist_dir = sys::file_ops::dirname (f);
            if (! hist_dir.empty ())
              {
                sys::file_stat fs (hist_dir);
                if (! fs.is_dir () && (sys::mkdir (hist_dir, 0777) < 0))
                  (*current_liboctave_error_handler)
                    ("%s: Could not create directory \"%s\" for history",
                     "gnu_history::do_write", hist_dir.c_str ());
              }

            int status = ::octave_write_history (f.c_str ());

            if (status != 0)
              {
                std::string msg = "writing file '" + f + "'";

                error (status, msg);
              }
          }
        else
          error ("gnu_history::write: missing filename");
      }
  }

  void
  gnu_history::do_append (const std::string& f_arg)
  {
    if (m_initialized)
      {
        if (m_lines_this_session)
          {
            if (m_lines_this_session < do_where ())
              {
                // Create file if it doesn't already exist.

                std::string f = f_arg;

                if (f.empty ())
                  f = m_file;

                if (! f.empty ())
                  {
                    sys::file_stat fs (f);

                    if (! fs)
                      {
                        std::ofstream tmp = sys::ofstream (f, std::ios::out);
                        tmp.close ();
                      }

                    int status
                      = ::octave_append_history (m_lines_this_session, f.c_str ());

                    if (status != 0)
                      {
                        std::string msg = "appending to file '" + f_arg + "'";

                        error (status, msg);
                      }
                    else
                      m_lines_in_file += m_lines_this_session;

                    m_lines_this_session = 0;
                  }
                else
                  error ("gnu_history::append: missing filename");
              }
          }
      }
  }

  void
  gnu_history::do_truncate_file (const std::string& f_arg, int n) const
  {
    if (m_initialized)
      {
        std::string f = f_arg;

        if (f.empty ())
          f = m_file;

        if (! f.empty ())
          ::octave_history_truncate_file (f.c_str (), n);
        else
          error ("gnu_history::truncate_file: missing filename");
      }
  }

  string_vector
  gnu_history::do_list (int limit, bool number_lines) const
  {
    string_vector retval;

    if (limit)
      retval = ::octave_history_list (limit, number_lines);

    return retval;
  }

  std::string
  gnu_history::do_get_entry (int n) const
  {
    std::string retval;

    char *line = ::octave_history_get (do_base () + n);

    if (line)
      retval = line;

    return retval;
  }

  void
  gnu_history::do_replace_entry (int which, const std::string& line)
  {
    ::octave_replace_history_entry (which, line.c_str ());
  }

  void
  gnu_history::do_clean_up_and_save (const std::string& f_arg, int n)
  {
    if (m_initialized)
      {
        std::string f = f_arg;

        if (f.empty ())
          f = m_file;

        if (! f.empty ())
          {
            if (n < 0)
              n = m_size;

            stifle (n);

            do_write (f.c_str ());
          }
        else
          error ("gnu_history::clean_up_and_save: missing filename");
      }
  }

#endif

  bool
  command_history::instance_ok (void)
  {
    bool retval = true;

    if (! s_instance)
      {
        make_command_history ();

        if (s_instance)
          singleton_cleanup_list::add (cleanup_instance);
      }

    if (! s_instance)
      (*current_liboctave_error_handler)
        ("unable to create command history object!");

    return retval;
  }

  void
  command_history::make_command_history (void)
  {
#if defined (USE_READLINE)
    s_instance = new gnu_history ();
#else
    s_instance = new command_history ();
#endif
  }

  void
  command_history::initialize (bool read_history_file,
                               const std::string& f_arg, int sz,
                               const std::string & control_arg)
  {
    if (instance_ok ())
      s_instance->do_initialize (read_history_file, f_arg, sz, control_arg);
  }

  bool
  command_history::is_initialized (void)
  {
    // We just want to check the status of an existing instance, not
    // create one.
    return s_instance && s_instance->do_is_initialized ();
  }

  void
  command_history::set_file (const std::string& f_arg)
  {
    if (instance_ok ())
      {
        std::string f = sys::file_ops::tilde_expand (f_arg);

        s_instance->do_set_file (f);
      }
  }

  std::string
  command_history::file (void)
  {
    return instance_ok () ? s_instance->do_file () : "";
  }

  void
  command_history::process_histcontrol (const std::string& control_arg)
  {
    if (instance_ok ())
      s_instance->do_process_histcontrol (control_arg);
  }

  std::string
  command_history::histcontrol (void)
  {
    return instance_ok () ? s_instance->do_histcontrol () : "";
  }

  void
  command_history::set_size (int n)
  {
    if (instance_ok ())
      s_instance->do_set_size (n);
  }

  int
  command_history::size (void)
  {
    return instance_ok () ? s_instance->do_size () : 0;
  }

  void
  command_history::ignore_entries (bool flag)
  {
    if (instance_ok ())
      s_instance->do_ignore_entries (flag);
  }

  bool
  command_history::ignoring_entries (void)
  {
    return instance_ok () ? s_instance->do_ignoring_entries () : false;
  }

  bool
  command_history::add (const std::string& s)
  {
    if (instance_ok ())
      return s_instance->do_add (s);
    return false;
  }

  void
  command_history::remove (int n)
  {
    if (instance_ok ())
      s_instance->do_remove (n);
  }

  void
  command_history::clear (void)
  {
    if (instance_ok ())
      s_instance->do_clear ();
  }

  int
  command_history::where (void)
  {
    return instance_ok () ? s_instance->do_where () : 0;
  }

  int
  command_history::length (void)
  {
    return instance_ok () ? s_instance->do_length () : 0;
  }

  int
  command_history::max_input_history (void)
  {
    return instance_ok () ? s_instance->do_max_input_history () : 0;
  }

  int
  command_history::base (void)
  {
    return instance_ok () ? s_instance->do_base () : 0;
  }

  int
  command_history::current_number (void)
  {
    return instance_ok () ? s_instance->do_current_number () : 0;
  }

  void
  command_history::stifle (int n)
  {
    if (instance_ok ())
      s_instance->do_stifle (n);
  }

  int
  command_history::unstifle (void)
  {
    return instance_ok () ? s_instance->do_unstifle () : 0;
  }

  int
  command_history::is_stifled (void)
  {
    return instance_ok () ? s_instance->do_is_stifled () : 0;
  }

  void
  command_history::set_mark (int n)
  {
    if (instance_ok ())
      s_instance->do_set_mark (n);
  }

  int
  command_history::goto_mark (void)
  {
    return instance_ok () ? s_instance->do_goto_mark () : 0;
  }

  void
  command_history::read (bool must_exist)
  {
    read (file (), must_exist);
  }

  void
  command_history::read (const std::string& f, bool must_exist)
  {
    if (instance_ok ())
      s_instance->do_read (f, must_exist);
  }

  void
  command_history::read_range (int from, int to, bool must_exist)
  {
    read_range (file (), from, to, must_exist);
  }

  void
  command_history::read_range (const std::string& f, int from, int to,
                               bool must_exist)
  {
    if (instance_ok ())
      s_instance->do_read_range (f, from, to, must_exist);
  }

  void
  command_history::write (const std::string& f)
  {
    if (instance_ok ())
      s_instance->do_write (f);
  }

  void
  command_history::append (const std::string& f)
  {
    if (instance_ok ())
      s_instance->do_append (f);
  }

  void
  command_history::truncate_file (const std::string& f, int n)
  {
    if (instance_ok ())
      s_instance->do_truncate_file (f, n);
  }

  string_vector
  command_history::list (int limit, bool number_lines)
  {
    return (instance_ok ()
            ? s_instance->do_list (limit, number_lines) : string_vector ());
  }

  std::string
  command_history::get_entry (int n)
  {
    return instance_ok () ? s_instance->do_get_entry (n) : "";
  }

  void
  command_history::replace_entry (int which, const std::string& line)
  {
    if (instance_ok ())
      s_instance->do_replace_entry (which, line);
  }

  void
  command_history::clean_up_and_save (const std::string& f, int n)
  {
    if (instance_ok ())
      s_instance->do_clean_up_and_save (f, n);
  }

  void
  command_history::do_process_histcontrol (const std::string&)
  { }

  void
  command_history::do_initialize (bool read_history_file,
                                  const std::string& f_arg, int sz,
                                  const std::string & control_arg)
  {
    command_history::set_file (f_arg);
    command_history::set_size (sz);
    command_history::process_histcontrol (control_arg);

    if (read_history_file)
      command_history::read (false);

    m_initialized = true;
  }

  bool
  command_history::do_is_initialized (void) const
  {
    return m_initialized;
  }

  void
  command_history::do_set_file (const std::string& f)
  {
    m_file = f;
  }

  std::string
  command_history::do_file (void)
  {
    return m_file;
  }

  void
  command_history::do_set_size (int n)
  {
    m_size = n;
  }

  int
  command_history::do_size (void) const
  {
    return m_size;
  }

  void
  command_history::do_ignore_entries (bool flag)
  {
    m_ignoring_additions = flag;
  }

  bool
  command_history::do_ignoring_entries (void) const
  {
    return m_ignoring_additions;
  }

  bool
  command_history::do_add (const std::string&)
  {
    return false;
  }

  void
  command_history::do_remove (int)
  { }

  void
  command_history::do_clear (void)
  { }

  int
  command_history::do_where (void) const
  {
    return 0;
  }

  int
  command_history::do_length (void) const
  {
    return 0;
  }

  int
  command_history::do_max_input_history (void) const
  {
    return 0;
  }

  int
  command_history::do_base (void) const
  {
    return 0;
  }

  int
  command_history::do_current_number (void) const
  {
    return m_size > 0 ? do_base () + do_where () : -1;
  }

  void
  command_history::do_stifle (int)
  { }

  int
  command_history::do_unstifle (void)
  {
    return -1;
  }

  int
  command_history::do_is_stifled (void) const
  {
    return 0;
  }

  void
  command_history::do_set_mark (int)
  { }

  int
  command_history::do_goto_mark (void)
  {
    return 0;
  }

  void
  command_history::do_read (const std::string& f, bool)
  {
    if (f.empty ())
      error ("command_history::read: missing filename");
  }

  void
  command_history::do_read_range (const std::string& f, int, int, bool)
  {
    if (f.empty ())
      error ("command_history::read_range: missing filename");
  }

  void
  command_history::do_write (const std::string& f_arg) const
  {
    if (m_initialized)
      {
        std::string f = f_arg;

        if (f.empty ())
          f = m_file;

        if (f.empty ())
          error ("command_history::write: missing filename");
      }
  }

  void
  command_history::do_append (const std::string& f_arg)
  {
    if (m_initialized)
      {
        if (m_lines_this_session)
          {
            if (m_lines_this_session < do_where ())
              {
                // Create file if it doesn't already exist.

                std::string f = f_arg;

                if (f.empty ())
                  f = m_file;

                if (f.empty ())
                  error ("command_history::append: missing filename");
              }
          }
      }
  }

  void
  command_history::do_truncate_file (const std::string& f_arg, int) const
  {
    if (m_initialized)
      {
        std::string f = f_arg;

        if (f.empty ())
          f = m_file;

        if (f.empty ())
          error ("command_history::truncate_file: missing filename");
      }
  }

  string_vector
  command_history::do_list (int, bool) const
  {
    return string_vector ();
  }

  std::string
  command_history::do_get_entry (int) const
  {
    return "";
  }

  void
  command_history::do_replace_entry (int, const std::string&)
  { }

  void
  command_history::do_clean_up_and_save (const std::string& f_arg, int)
  {
    if (m_initialized)
      {
        std::string f = f_arg;

        if (f.empty ())
          f = m_file;

        if (f.empty ())
          error ("command_history::clean_up_and_save: missing filename");
      }
  }

  void
  command_history::error (int err_num, const std::string& msg) const
  {
    if (msg.empty ())
      (*current_liboctave_error_handler) ("%s", std::strerror (err_num));
    else
      (*current_liboctave_error_handler) ("%s: %s", msg.c_str (),
                                          std::strerror (err_num));
  }

  void
  command_history::error (const std::string& s) const
  {
    (*current_liboctave_error_handler) ("%s", s.c_str ());
  }
}