Logo Search packages:      
Sourcecode: ddd version File versions  Download package

history.C

// $Id$ -*- C++ -*-
// DDD command history

// Copyright (C) 1996-1998 Technische Universitaet Braunschweig, Germany.
// Copyright (C) 2000 Universitaet Passau, Germany.
// Written by Andreas Zeller <zeller@gnu.org>.
// 
// This file is part of DDD.
// 
// DDD 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 of the License, or (at your option) any later version.
// 
// DDD 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 DDD -- see the file COPYING.
// If not, write to the Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// 
// DDD is the data display debugger.
// For details, see the DDD World-Wide-Web page, 
// `http://www.gnu.org/software/ddd/',
// or send a mail to the DDD developers <ddd@gnu.org>.

// ------------------------------------------------------
// Yesterday
// (sung to Yesterday by the Beatles)
// 
// Yesterday, all my code still worked this way
// Now it looks as though bugs are here to stay
// Oh, I believe in yesterday.
// 
// Suddenly, it's not half as good as it used to be,
// There's a release hanging over me.
// Oh, failure came suddenly.
// 
// Why it had to break I don't know it wouldn't say.
// I typed something wrong, now I long for yesterday.
// 
// Yesterday, coding was a kind of game to play.
// Now I need a place to hide away.
// Oh, I believe in yesterday.
//
// -- Ralf Hildebrandt <Ralf.Hildebrandt@gmx.de>
// ------------------------------------------------------

char history_rcsid[] = 
    "$Id$";

#include "history.h"

#include "AppData.h"
#include "Assoc.h"
#include "ComboBox.h"
#include "Command.h"
#include "Delay.h"
#include "DestroyCB.h"
#include "GDBAgent.h"
#include "HelpCB.h"
#include "MString.h"
#include "MakeMenu.h"
#include "SmartC.h"
#include "SourceView.h"
#include "StringA.h"
#include "VoidArray.h"
#include "args.h"
#include "cook.h"
#include "cwd.h"
#include "ddd.h"
#include "disp-read.h"
#include "editing.h"
#include "filetype.h"
#include "home.h"
#include "logo.h"
#include "misc.h"
#include "mydialogs.h"
#include "post.h"
#include "regexps.h"
#include "shell.h"
#include "status.h"
#include "string-fun.h"
#include "uniquify.h"
#include "verify.h"
#include "wm.h"

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <fstream>
#include <Xm/Xm.h>
#include <Xm/Text.h>
#include <Xm/List.h>
#include <Xm/SelectioB.h>

#if WITH_READLINE
// `history.h' has no complete declaration for `add_history',
// so we install our own.
#define add_history old_add_history
extern "C" {
#include "readline/history.h"
}
#undef add_history
extern "C" void add_history(const char *line);
#endif

#ifndef ARG_MAX
#define ARG_MAX 4096
#endif


//-----------------------------------------------------------------------------
// Command history
//-----------------------------------------------------------------------------

// History viewer
static Widget gdb_history_w  = 0;
static Widget gdb_commands_w = 0;

// History storage
static StringArray gdb_history;

// Index to current history entry
static int gdb_current_history;

// File name to save the history
static string _gdb_history_file;

// Size of saved history
static int gdb_history_size = 100;

// True if the history was just loaded
static bool gdb_new_history = true;

// True if history command was issued
static bool private_gdb_history = false;

// Update all combo boxes
static void update_combo_boxes();

// Update combo boxes listening to NEW_ENTRY
static void update_combo_boxes(const string& new_entry);


#if WITH_READLINE
// Initialize history
struct HistoryInitializer {
    HistoryInitializer()
    {
      using_history();
    }
};

static HistoryInitializer history_initializer;
#endif


void set_gdb_history_file(const string& file)
{
    _gdb_history_file = file;
}

string gdb_history_file()
{
    return _gdb_history_file;
}

static void set_line_from_history()
{
    private_gdb_history = true;

    const string& input = gdb_history[gdb_current_history];
    XmTextReplace(gdb_w, promptPosition,
              XmTextGetLastPosition(gdb_w), XMST(input.chars()));
    XmTextSetInsertionPosition(gdb_w, XmTextGetLastPosition(gdb_w));

    if (gdb_history_w)
      ListSetAndSelectPos(gdb_commands_w, gdb_current_history + 1);

    private_gdb_history = false;
}

void set_history_from_line(const string& line,
                     bool ignore_history_commands)
{
    if (ignore_history_commands && private_gdb_history)
      return;

    while (gdb_history.size() < 1)
      gdb_history += "";
    gdb_history[gdb_history.size() - 1] = line;

    if (gdb_history_w)
    {
      int pos = gdb_history.size();

      // XmListReplaceItemsPos() disturbs the current selection, so
      // save it here
      int *selected;
      int selected_count;
      if (!XmListGetSelectedPos(gdb_commands_w, &selected, &selected_count))
          selected = 0;

      MString xm_line(line, LIST_CHARSET);
      XmString xms = xm_line.xmstring();
      XmListReplaceItemsPos(gdb_commands_w, &xms, 1, pos);

      if (selected)
      {
          // Restore old selection
          for (int i = 0; i < selected_count; i++)
            XmListSelectPos(gdb_commands_w, selected[i], False);
          XtFree((char *)selected);
      }
    }
}

// Enter LINE in history
void add_to_history(const string& line)
{
    if (!gdb->isReadyWithPrompt())
      return;

    set_history_from_line(line);

    if (gdb_history.size() < 2 || line != gdb_history[gdb_history.size() - 2])
    {
      gdb_history += "";

      if (gdb_history_w)
      {
          MString xm_line(line, LIST_CHARSET);
          int pos = gdb_history.size();
          XmListAddItem(gdb_commands_w, xm_line.xmstring(), pos - 1);
          XmListSelectPos(gdb_commands_w, 0, False);
          XmListSetBottomPos(gdb_commands_w, 0);
      }
    }

    gdb_current_history = gdb_history.size();
    set_history_from_line("");

    if (gdb_history_w)
    {
      XmListSelectPos(gdb_commands_w, 0, False);
      XmListSetBottomPos(gdb_commands_w, 0);
    }

    gdb_new_history = false;

    add_to_arguments(line);
    update_arguments();
    update_combo_boxes(line);

#if WITH_READLINE
    add_history(line.chars());
#endif
}

// Load history from history file
void load_history(const string& file)
{
    if (file.empty())
      return;

    // Delay d;

    std::ifstream is(file.chars());
    if (is.bad())
      return;

    static const StringArray empty;
    gdb_history = empty;

#if WITH_READLINE
    clear_history();
#endif

    assert(gdb_history.size() == 0);

    // If the first command in history is a `file' command,
    // insert them all and disregard later ones.
    bool first_command = true;
    bool first_is_file = false;
    bool get_files     = true;

    while (is)
    {
      // We accept lines up to a length of ARG_MAX (the maximum
      // length of the ARGV argument passed to a program)
      char _line[ARG_MAX + BUFSIZ];
      _line[0] = '\0';

      is.getline(_line, sizeof(_line));
      if (_line[0] != '\0')
      {
          string line(_line);

          bool added = false;
          if (is_file_cmd(line, gdb) && line != "# reset")
          {
            if (first_command)
                first_is_file = true;

            if (get_files)
            {
                string arg = line.after(rxwhite);
                if (gdb->type() == PERL)
                {
                  arg = arg.after(" -d ");
                  arg = arg.before('\"');
                  if (arg.contains(rxwhite))
                      arg = arg.before(rxwhite);
                }
                add_to_recent(arg);
                added = first_is_file;
            }
          }
          else
          {
            if (first_is_file)
                get_files = false;
            if (first_command)
                first_is_file = false;
          }

          if (!added && line[0] != '#')
          {
            gdb_history += line;
            add_to_arguments(line);

#if WITH_READLINE
            add_history(line.chars());
#endif
          }

          first_command = false;
      }
    }

    gdb_history += "";
    gdb_current_history = gdb_history.size() - 1;
    gdb_new_history = true;

    update_arguments();
    update_combo_boxes();
}

// Save history into history file
void save_history(const string& file, Widget origin)
{
    if (!file.empty())
    {
      StatusDelay delay("Saving history in " + quote(file));
      std::ofstream os(file.chars());
      if (os.bad())
      {
          post_error("Cannot save history in " + quote(file),
                   "history_save_error", origin);
          delay.outcome = "failed";
          return;
      }

      // Save the 10 most recently opened files
      int i;
      StringArray recent;
      get_recent(recent);
      for (i = recent.size() - 1; i >= 0 && i >= recent.size() - 10; i--)
          os << gdb->debug_command(recent[i]) << "\n";

      // Now save the command history itself
      int start = gdb_history.size() - gdb_history_size;
      if (start < 0)
          start = 0;

      for (i = start; i < gdb_history.size(); i++)
          os << gdb_history[i] << "\n";
    }
}

// Set history file name
void process_history_filename(string answer)
{
    answer = answer.after('"');
    answer = answer.before('"');
    set_gdb_history_file(answer);
}

// Set history size
void process_history_size(string answer)
{
    answer = answer.from(rxint);
    int ret = get_positive_nr(answer);
    if (ret >= 0)
      gdb_history_size = ret;
}

// History viewer
static void SelectHistoryCB(Widget, XtPointer, XtPointer call_data)
{
    XmListCallbackStruct *cbs = (XmListCallbackStruct *)call_data;
    gdb_current_history = cbs->item_position - 1;

    clear_isearch();
    set_line_from_history();
}

static void HistoryDestroyedCB(Widget, XtPointer client_data, XtPointer)
{
    Widget old_gdb_history_w = Widget(client_data);
    if (gdb_history_w == old_gdb_history_w)
    {
      gdb_history_w = 0;
      gdb_commands_w = 0;
    }
}

void gdbHistoryCB(Widget w, XtPointer, XtPointer)
{
    if (gdb_history_w)
    {
      manage_and_raise(gdb_history_w);
      return;
    }

    Arg args[10];
    int arg;
      
    // Create history viewer
    arg = 0;
    gdb_history_w =
      verify(createTopLevelSelectionDialog(find_shell(w), "history_dialog", 
                                   args, arg));
    Delay::register_shell(gdb_history_w);

    XtUnmanageChild(XmSelectionBoxGetChild(gdb_history_w, 
                                 XmDIALOG_OK_BUTTON));
    XtUnmanageChild(XmSelectionBoxGetChild(gdb_history_w, 
                                 XmDIALOG_TEXT));
    XtUnmanageChild(XmSelectionBoxGetChild(gdb_history_w, 
                                 XmDIALOG_SELECTION_LABEL));

    gdb_commands_w = XmSelectionBoxGetChild(gdb_history_w, XmDIALOG_LIST);
    XtVaSetValues(gdb_commands_w,
              XmNselectionPolicy, XmSINGLE_SELECT,
              XtPointer(0));

    XtAddCallback(gdb_commands_w,
              XmNsingleSelectionCallback, SelectHistoryCB, 0);
    XtAddCallback(gdb_commands_w,
              XmNmultipleSelectionCallback, SelectHistoryCB, 0);
    XtAddCallback(gdb_commands_w,
              XmNextendedSelectionCallback, SelectHistoryCB, 0);
    XtAddCallback(gdb_commands_w,
              XmNbrowseSelectionCallback, SelectHistoryCB, 0);

    XtAddCallback(gdb_history_w, XmNokCallback, gdbApplySelectionCB, 0);
    XtAddCallback(gdb_history_w, XmNapplyCallback, gdbApplySelectionCB, 0);
    XtAddCallback(gdb_history_w, XmNcancelCallback, DestroyThisCB, 
              gdb_history_w);
    XtAddCallback(gdb_history_w, XmNhelpCallback,  ImmediateHelpCB, 0);
    XtAddCallback(gdb_history_w, XmNdestroyCallback,
              HistoryDestroyedCB, XtPointer(gdb_history_w));

    bool *selected = new bool[gdb_history.size() + 1];
    for (int i = 0; i < gdb_history.size() + 1; i++)
      selected[i] = false;
    selected[gdb_current_history] = true;

    setLabelList(gdb_commands_w, gdb_history.values(), 
             selected, gdb_history.size(), false, false);

    delete[] selected;

    set_history_from_line(current_line());
    XmListSelectPos(gdb_commands_w, 0, False);
    XmListSetBottomPos(gdb_commands_w, 0);

    manage_and_raise(gdb_history_w);
}


// Return last command
string last_command_from_history()
{
    if (gdb_history.size() >= 2 && !gdb_new_history)
    {
      return gdb_history[gdb_history.size() - 2];
    }
    else
    {
      // No history yet -- perform a no-op command
      return gdb->echo_command("");
    }
}

// History actions
void prev_historyAct(Widget, XEvent*, String*, Cardinal*)
{
    if (gdb_current_history == 0)
      return;

    while (gdb_current_history >= gdb_history.size())
      gdb_current_history--;
    gdb_current_history--;

    clear_isearch();
    set_line_from_history();
}

void next_historyAct(Widget, XEvent*, String*, Cardinal*)
{
    if (gdb_current_history >= gdb_history.size() - 1)
      return;

    gdb_current_history++;

    clear_isearch();
    set_line_from_history();
}

// Search TEXT in history; return POS iff found, -1 if none
// DIRECTION = -1 means search backward, DIRECTION = +1 means search forward
// RESEARCH = true means skip current position
int search_history(const string& text, int direction, bool research)
{
    int i = min(gdb_current_history, gdb_history.size() - 1);
    if (research)
      i += direction;

    while (i < gdb_history.size() && i >= 0)
    {
      if (gdb_history[i].contains(text))
          return i;

      i += direction;
    }

    return -1;                // Not found
}

// Set history position to POS; -1 means last position.
void goto_history(int pos)
{
    if (pos == -1)
      pos = gdb_history.size() - 1;

    assert (pos >= 0 && pos < gdb_history.size());
    gdb_current_history = pos;
    set_line_from_history();
}


//-----------------------------------------------------------------------------
// Combo Box Histories
//-----------------------------------------------------------------------------

// Update combo box containing TEXT
static void update_combo_box(Widget text, HistoryFilter filter)
{
    StringArray entries;

    for (int i = gdb_history.size() - 1; i >= 0; i--)
    {
      const string& entry = gdb_history[i];
      string arg = filter(entry);
      if (arg.empty())
          continue;           // Empty arg

      if (entries.size() > 0 && entries[entries.size() - 1] == arg)
          continue;           // Adjacent duplicate

      if (app_data.popdown_history_size > 0 &&
          entries.size() >= int(app_data.popdown_history_size))
          break;        // Enough entries

      bool found_duplicate = false;
      if (!app_data.sort_popdown_history)
      {
          // We'll not be sorted.  If we already have this value,
          // ignore new entry.
          for (int j = 0; !found_duplicate && j < entries.size(); j++)
            found_duplicate = (entries[j] == arg);
      }
      if (!found_duplicate)
          entries += arg;
    }

    if (app_data.sort_popdown_history)
    {
      smart_sort(entries);
      uniq(entries);
    }

    ComboBoxSetList(text, entries);
}

static WidgetHistoryFilterAssoc combo_boxes;

// Update all combo boxes
static void update_combo_boxes()
{
    for (WidgetHistoryFilterAssocIter iter(combo_boxes); iter.ok(); ++iter)
      update_combo_box(iter.key(), iter.value());
}

// Update combo boxes listening to NEW_ENTRY
static void update_combo_boxes(const string& new_entry)
{
    for (WidgetHistoryFilterAssocIter iter(combo_boxes); iter.ok(); ++iter)
    {
      HistoryFilter filter = iter.value();
      string arg = filter(new_entry);
      if (!arg.empty())
          update_combo_box(iter.key(), iter.value());
    }
}

static void RemoveComboBoxCB(Widget text, XtPointer, XtPointer)
{
    combo_boxes.remove(text);
}

// Tie a ComboBox to global history
void tie_combo_box_to_history(Widget text, HistoryFilter filter)
{
    combo_boxes[text] = filter;
    update_combo_box(text, filter);
    XtAddCallback(text, XmNdestroyCallback, RemoveComboBoxCB, XtPointer(0));
}


//-----------------------------------------------------------------------------
// Recent file history
//-----------------------------------------------------------------------------

static void update_recent_menus();

// Recent files storage
static StringArray recent_files;

// Add FILE to recent file history
void add_to_recent(const string& file)
{
    string full_path;
    if (gdb->type() != JDB)
      full_path = SourceView::full_path(file);
    else
      full_path = file;

    if (recent_files.size() > 0 && 
      recent_files[recent_files.size() - 1] == full_path)
      return;                 // Already in list

    for (int i = 0; i < recent_files.size(); i++)
      if (recent_files[i] == full_path ||
          (!remote_gdb() && 
           gdb->type() != JDB &&
           same_file(recent_files[i], full_path)))
          recent_files[i] = ""; // Clear old entry

    recent_files += full_path;
    update_recent_menus();
}

// Get recent file history (most recent first)
void get_recent(StringArray& arr)
{
    for (int i = recent_files.size() - 1; i >= 0; i--)
    {
      string& recent = recent_files[i];
      if (recent.empty())
          continue;           // Removed

      arr += recent_files[i];
    }
}


// Menus to be updated
static VoidArray menus;

static void update_recent_menu(const MMDesc *items)
{
    StringArray recent_files;
    {
      StringArray r;
      get_recent(r);
      for (int i = 0; i < r.size() && items[i].widget != 0; i++)
          recent_files += r[i];
    }

    // Uniquify labels
    char sep = '/';
    if (gdb->type() == JDB)
      sep = '.';

    StringArray labels;
    uniquify(recent_files, labels, sep);

    // Set labels
    int i;
    for (i = 0; i < labels.size(); i++)
    {
      MString label(itostring(i + 1) + " ");
      label += tt(labels[i]);

      Widget w = items[i].widget;
      set_label(w, label);

      const string& file = recent_files[i];

      bool sens = true;
      if (!remote_gdb())
      {
          if (gdb->has_exec_files() && !is_debuggee_file(file))
            sens = false;     // File not accessible
          else if (!gdb->has_classes() && !is_regular_file(file))
            sens = false;     // File not accessible
      }

      set_sensitive(w, sens);
      XtManageChild(w);
    }

    // Unmanage remaining items
    for (; items[i].widget != 0; i++)
      XtUnmanageChild(items[i].widget);
}

static void update_recent_menus()
{
    for (int i = 0; i < menus.size(); i++)
    {
      const MMDesc *items = (const MMDesc *)menus[i];
      update_recent_menu(items);
    }
}

void tie_menu_to_recent_files(MMDesc *items)
{
    if (items == 0 || items[0].widget == 0)
      return;

    menus += (void *)items;
    update_recent_menu(items);
}


Generated by  Doxygen 1.6.0   Back to index