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

complete.C

// $Id$ -*- C++ -*-
// Command and arg completion

// 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>.

char complete_rcsid[] = 
    "$Id$";

#include "complete.h"

#include "AppData.h"
#include "Command.h"
#include "Delay.h"
#include "SmartC.h"
#include "ddd.h"
#include "disp-read.h"
#include "editing.h"
#include "isid.h"
#include "post.h"
#include "question.h"
#include "regexps.h"
#include "string-fun.h"
#include "casts.h"

#include <ctype.h>

#include <Xm/Xm.h>
#include <Xm/Text.h>
#include <Xm/TextF.h>

#if WITH_READLINE
extern "C" {
#include "readline/readline.h"
}
#endif


// Completion delay flag
static Delay *completion_delay = 0;

//-----------------------------------------------------------------------------
// Line Completion
//-----------------------------------------------------------------------------

// Remove adjacent duplicates in A
static void uniq(string *a, int& size)
{
    int j = 1;
    for (int i = 1; i < size; i++)
      if (a[i] != a[j - 1])
          a[j++] = a[i];
    
    size = j;
}

// Info passed to reply functions
00081 struct CompletionInfo {
    Widget widget;            // Widget
    XEvent *event;            // Event 
    string input;       // Current input
    string cmd;               // Current command
    string prefix;            // Current prefix

    CompletionInfo()
      : widget(0), event(0), input(), cmd(), prefix()
    {}
private:
    CompletionInfo(const CompletionInfo&);
    CompletionInfo& operator = (const CompletionInfo&);
};

static void complete_reply(const string& complete_answer, void *qu_data);

// Set completion
static void set_completion(const CompletionInfo& info, const string& completion)
{
    // Set input to common prefix
    if (info.widget == gdb_w)
    {
      private_gdb_output = true;

      XmTextReplace(gdb_w, promptPosition,
                  XmTextGetLastPosition(gdb_w), 
                  XMST(completion.chars()));

      private_gdb_output = false;
    }
    else
    {
      if (XmIsTextField(info.widget))
      {
          XmTextFieldSetString(info.widget, XMST(completion.chars()));
      }
      else if (XmIsText(info.widget))
      {
          XmTextSetString(info.widget, XMST(completion.chars()));
      }
    }
}

// Same, but for single possible completion
static string complete_single_completion(string completion)
{
    // Only one possible expansion: Add final single quote if
    // necessary and add final space as well.

    if (gdb->has_quotes())
    {
      if (completion.freq('\'') % 2 != 0)
          completion += '\'';
    }
    completion += ' ';

    return completion;
}


// All completions are done
static void completion_done(const CompletionInfo& info)
{
    if (XmIsTextField(info.widget))
    {
      XmTextPosition last_pos = 
          XmTextFieldGetLastPosition(info.widget);
      XmTextFieldSetInsertionPosition(info.widget, last_pos);
      XmTextFieldShowPosition(info.widget, last_pos);
      XmTextFieldSetEditable(info.widget, True);
    }
    else if (XmIsText(info.widget))
    {
      XmTextPosition last_pos = 
          XmTextGetLastPosition(info.widget);
      XmTextSetInsertionPosition(info.widget, last_pos);
      XmTextShowPosition(info.widget, last_pos);
      XmTextSetEditable(info.widget, True);
    }

    XmTextSetEditable(gdb_w, True);
}

void clear_completion_delay()
{
    if (completion_delay)
      delete completion_delay;
    completion_delay = 0;
}

static string completion_command(const string& cmd)
{
    switch (gdb->type())
    {
    case GDB:
      return "complete " + cmd;

    case PERL:
    {
      string arg = cmd.after(rxwhite);
      return "S ^" + arg + ".*";
    }

    default:
      assert(0);
      ::abort();
      return "";
    }
}

static string *completions  = 0;
static int completions_size = 0;

// Send completion question
static void complete(Widget w, XEvent *e, const string& input, string cmd)
{
    // Issue diagnostic if completion doesn't work right now
    if (!can_do_gdb_command())
    {
      post_gdb_busy(w);
      return;
    }

    static CompletionInfo info;
    info.widget = w;
    info.event  = e;
    info.input  = input;
    info.cmd    = cmd;
    info.prefix = "";

    // Compare with last completions
    static Widget last_completion_w     = 0;
    static int    last_completion_index = -1;
    static string last_completion       = string(char(-1));

    if (completions_size > 0 
      && completions
      && !(completions[0].empty())
      && w == last_completion_w)
    {
      // Check if this is to be the next completion
      string next_completion = "";

      if (input == last_completion)
      {
          // We have already shown possible completions:
          // Expand to first completion
          last_completion_index = 0;
          next_completion = complete_single_completion(completions[0]);
      }
      else if (completions_size > 1
             && last_completion_index >= 0 
             && input == complete_single_completion(
                 completions[last_completion_index]))
      {
          if (last_completion_index < completions_size - 1)
          {
            // Show next completion
            next_completion = complete_single_completion(
                completions[++last_completion_index]);
          }
          else
          {
            // All completions shown; re-start with initial input
            last_completion_index = -1;
            next_completion = last_completion;
          }
      }

      if (!next_completion.empty())
      {
          set_completion(info, next_completion);
          completion_done(info);
          return;
      }
    }

    // Start a new completion session

    // Clear old completions
    last_completion_w     = w;
    last_completion       = input;
    last_completion_index = -1;

    if (completions)
    {
      delete[] completions;
      completions = 0;
    }
    completions_size = 0;

    // Go and ask GDB for completions.

    if (is_graph_cmd(cmd))
    {
      // Allow completion of `graph' commands
      info.prefix = cmd.through(rxwhite);
      cmd = cmd.from(int(info.prefix.length()));
    }

    string complete_cmd = completion_command(cmd);
    if (gdb->type() == PERL)
    {
      if (cmd == input)
          info.prefix += cmd.through(rxwhite);
      else
          info.cmd = info.input;
    }

    if (XmIsTextField(w))
      XmTextFieldSetEditable(w, False);
    else if (XmIsText(w))
      XmTextSetEditable(w, False);
    XmTextSetEditable(gdb_w, False);
    
    gdb_command(complete_cmd, gdb_w, complete_reply, (void *)&info);

    completion_delay = new Delay;
}


// Handle possible completions
static void complete_reply(const string& complete_answer, void *qu_data)
{
    const CompletionInfo& info = *((CompletionInfo *)qu_data);

    bool from_gdb_w = (info.widget == gdb_w);

    string input = info.input;
    strip_space(input);

    assert(completions == 0);

    int lines = complete_answer.freq('\n') + 1;
    completions      = new string[lines];
    completions_size = split(complete_answer, completions, lines, '\n');
    smart_sort(completions, completions_size);
    uniq(completions, completions_size);

    if (completions_size == 0 || completions[0].empty())
    {
      // No completion (sigh)
      XtCallActionProc(gdb_w, "beep", info.event, 0, 0);
    }
    else if (completions[0].index("Undefined command:") == 0)
    {
      // GDB versions earlier than 4.13 do not support
      // the `complete' command.

      string message;
      for (int i = 0; i < completions_size; i++)
          message += completions[i] + '\n';
      post_error(message, "no_completion_error", info.widget);
    }
    else
    {
      if (info.cmd != info.input)
      {
          // Strip initial base command
          for (int i = 0; i < completions_size; i++)
            completions[i] = completions[i].after(' ');
      }

      if (!info.prefix.empty())
      {
          // Add prefix again
          for (int i = 0; i < completions_size; i++)
            completions[i].prepend(info.prefix);
      }

      // Find common prefix
      string common_pfx = completions[0];
      int i;
      for (i = 1; i < completions_size; i++)
          common_pfx = common_prefix(common_pfx, completions[i]);

      if (completions_size > 1 && input == common_pfx)
      {
          // We're already as far as we can get
          if (from_gdb_w)
          {
            // Show possible expansions in command window.
            int skip = common_pfx.index(rxwhite, -1) + 1;
          
            string insertion;
            if (from_gdb_w)
                insertion += input;
            insertion += "\n";
            for (i = 0; i < completions_size; i++)
            {
                insertion += completions[i].from(skip);
                insertion += "\n";
            }
            gdb_out(insertion);
            gdb_out(gdb->prompt());

            XmTextSetInsertionPosition(gdb_w, 
                                 XmTextGetLastPosition(gdb_w));
          }
      }
      else
      {
          string completion = common_pfx;
          if (completions_size == 1)
            completion = complete_single_completion(completion);
          set_completion(info, completion);
      }
    }

    completion_done(info);
}

static void tabAct(Widget w, XEvent *e, String* args, Cardinal* num_args)
{
    if (XmIsText(w))
      XtCallActionProc(w, "process-tab", e, args, *num_args);
    else if (XmIsPrimitive(w))
      XtCallActionProc(w, "PrimitiveNextTabGroup", e, args, *num_args);
}

// Complete current GDB command
void complete_commandAct(Widget w, XEvent *e, String* args, Cardinal* num_args)
{
    if ((gdb->type() != GDB && gdb->type() != PERL)
      || w != gdb_w
      || !can_do_gdb_command()
      || XmTextGetInsertionPosition(w) != XmTextGetLastPosition(w))
    {
      tabAct(w, e, args, num_args);
      return;
    }

    string input = current_line();
    if (gdb->has_quotes())
    {
      // Insert single quote if necessary
      if (is_break_cmd(input))
      {
          int last_space = input.index(rxwhite, -1);
          if (last_space >= 0)
          {
            string last_word = input.after(last_space);
            if (last_word.length() > 0 && last_word[0] != '\'')
            {
                for (int i = 0; i < int(last_word.length()); i++)
                {
                  char c = last_word[i];
                  if (!isid(c))
                  {
                      input.at(last_space + 1, 0) = '\'';
                      break;
                  }
                }
            }
          }
      }
    }

    complete(w, e, input, input);
}


// Complete GDB argument
static void _complete_argAct(Widget w, 
                       XEvent *e, 
                       String* args, 
                       Cardinal* num_args,
                       bool tab)
{
    if ((tab && !app_data.global_tab_completion) 
      || (gdb->type() != GDB && gdb->type() != PERL)
      || !can_do_gdb_command())
    {
      tabAct(w, e, args, num_args);
      return;
    }

    // The command to use as prefix for completions
    string base = gdb->print_command("");
    if (*num_args >= 1)
      base = args[0];
    strip_space(base);

    String _input = 0;
    if (XmIsTextField(w))
      _input = XmTextFieldGetString(w);
    else if (XmIsText(w))
      _input = XmTextGetString(w);

    if (_input == 0)
      return;

    string input(_input);
    XtFree(_input);

    if (gdb->has_quotes())
    {
      // Insert single quote if necessary
      if (is_break_cmd(base))
      {
          if (input.length() > 0 && input[0] != '\'')
          {
            for (int i = 0; i < int(input.length()); i++)
            {
                char c = input[i];
                if (!isid(c))
                {
                  input.at(0, 0) = '\'';
                  break;
                }
            }
          }
      }
    }

    if (!base.empty())
      complete(w, e, input, base + " " + input);
    else
      complete(w, e, input, input);
}

void complete_argAct(Widget w, XEvent *e, String* args, Cardinal* num_args)
{
    _complete_argAct(w, e, args, num_args, false);
}

void complete_tabAct(Widget w, XEvent *e, String* args, Cardinal* num_args)
{
    _complete_argAct(w, e, args, num_args, true);
}


//-----------------------------------------------------------------------------
// Readline interface
//-----------------------------------------------------------------------------

#if WITH_READLINE
extern "C" {
static char *complete_readline(const char *text, int state)
{
    static string reply;

    if (state == 0)
    {
      // Get completion
      string complete_cmd = completion_command(rl_line_buffer);
      reply = gdb_question(complete_cmd, -1, true);
      if (reply == NO_GDB_ANSWER)
          reply = "";

      int lines = reply.freq('\n') + 1;
      string *completions = new string[lines];
      int completions_size = split(reply, completions, lines, '\n');
      smart_sort(completions, completions_size);
      uniq(completions, completions_size);

      reply = "";
      for (int i = 0; i < completions_size; i++)
      {
          string completion = text + completions[i].from(rl_end);
          reply += completion + '\n';
      }

      delete[] completions;
    }

    // Return next completion
    string completion = reply.before('\n');
    reply = reply.after('\n');

    if (completion.empty())
      return 0;

    char *ret = (char *)malloc(completion.length() + 1);
    strcpy(ret, completion.chars());
    return ret;
}
}

// Initialize completion
struct CompletionInitializer {
    CompletionInitializer()
    {
      rl_completion_entry_function = complete_readline;
    }
};

static CompletionInitializer completion_initializer;
#endif // WITH_READLINE

Generated by  Doxygen 1.6.0   Back to index