changeset 22171:0a4c5a90f286

Allow tab completion of arrays of structures. * input.h, input.cc (find_indexed_expression): New function. Parse a partial command line back, skipping () & {}, until a variable name is found. * variables.cc (generate_struct_completions): Call the above if prefix starts with ".". * input.cc (generate_completion): Handle manipulation of prefix by generate_struct_completions.
author Lachlan Andrew <lachlanbis@gmail.com>
date Thu, 30 Jun 2016 18:19:37 +1000
parents 20257791e358
children ed8a0c39e14c
files libinterp/corefcn/input.cc libinterp/corefcn/input.h libinterp/corefcn/variables.cc libinterp/corefcn/variables.h liboctave/util/cmd-edit.cc liboctave/util/cmd-edit.h liboctave/util/oct-rl-edit.c liboctave/util/oct-rl-edit.h
diffstat 8 files changed, 130 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/corefcn/input.cc	Sat Jul 23 14:49:54 2016 -0400
+++ b/libinterp/corefcn/input.cc	Thu Jun 30 18:19:37 2016 +1000
@@ -348,13 +348,16 @@
 
 static string_vector
 generate_possible_completions (const std::string& text, std::string& prefix,
-                               std::string& hint)
+                               std::string& hint, bool& deemed_struct)
 {
   string_vector names;
 
   prefix = "";
 
-  if (looks_like_struct (text))
+  char prev_char = octave::command_editor::get_prev_char (text.length ());
+  deemed_struct = looks_like_struct (text, prev_char);
+
+  if (deemed_struct)
     names = generate_struct_completions (text, prefix, hint);
   else
     names = make_name_list ();
@@ -419,16 +422,26 @@
       // No reason to display symbols while completing a
       // file/directory operation.
 
+      bool deemed_struct = false;
+
       if (is_completing_dirfns ())
         name_list = string_vector ();
       else
-        name_list = generate_possible_completions (text, prefix, hint);
+        name_list = generate_possible_completions (text, prefix, hint,
+                                                   deemed_struct);
 
       name_list_len = name_list.numel ();
 
-      file_name_list = octave::command_editor::generate_filename_completions (text);
+      // If the line was something like "a{1}." then text = "." but
+      // we don't want to expand all the . files.
+      if (! deemed_struct)
+        {
 
-      name_list.append (file_name_list);
+          file_name_list = octave::command_editor::generate_filename_completions (text);
+
+          name_list.append (file_name_list);
+
+        }
 
       name_list_total_len = name_list.numel ();
 
@@ -451,15 +464,16 @@
 
           if (hint == name.substr (0, hint_len))
             {
+                    // Special case: array reference forces prefix="."
+                    //               in generate_struct_completions ()
               if (list_index <= name_list_len && ! prefix.empty ())
-                retval = prefix + "." + name;
+                retval = (prefix == "." ? "" : prefix) + "." + name;
               else
                 retval = name;
 
-              // FIXME: looks_like_struct is broken for now,
-              //        so it always returns false.
-
-              if (matches == 1 && looks_like_struct (retval))
+              char prev_char = octave::command_editor::get_prev_char
+                                                       (text.length ());
+              if (matches == 1 && looks_like_struct (retval, prev_char))
                 {
                   // Don't append anything, since we don't know
                   // whether it should be '(' or '.'.
@@ -487,6 +501,53 @@
     return (std::string ("'") + text);
 }
 
+// Try to parse a partial command line in reverse, excluding trailing TEXT.
+// If it appears a variable has been indexed by () or {},
+// return that expression,
+// to allow autocomplete of field names of arrays of structures.
+std::string
+find_indexed_expression (const std::string& text)
+{
+  std::string line = octave::command_editor::get_line_buffer ();
+
+  int pos = line.length () - text.length ();
+  int curly_count = 0;
+  int paren_count = 0;
+
+  int last = --pos;
+
+  while (pos >= 0 && (line[pos] == ')' || line[pos] == '}'))
+    {
+      if (line[pos] == ')')
+        paren_count++;
+      else if (line[pos] == '}')
+        curly_count++;
+
+      while (curly_count + paren_count > 0 && --pos >= 0)
+        {
+          if (line[pos] == ')')
+            paren_count++;
+          else if (line[pos] == '(')
+            paren_count--;
+          else if (line[pos] == '}')
+            curly_count++;
+          else if (line[pos] == '{')
+            curly_count--;
+        }
+
+      while (--pos >= 0 && line[pos] == ' ')
+        ;
+    }
+
+  while (pos >= 0 && (isalnum (line[pos]) || line[pos] == '_'))
+    pos--;
+
+  if (++pos >= 0)
+    return (line.substr (pos, last + 1 - pos));
+  else
+    return std::string ();
+}
+
 void
 initialize_command_input (void)
 {
--- a/libinterp/corefcn/input.h	Sat Jul 23 14:49:54 2016 -0400
+++ b/libinterp/corefcn/input.h	Thu Jun 30 18:19:37 2016 +1000
@@ -53,6 +53,8 @@
 // TRUE if we are not executing a command direct from debug> prompt.
 extern OCTINTERP_API bool Vtrack_line_num;
 
+extern std::string find_indexed_expression (const std::string& text);
+
 extern void initialize_command_input (void);
 
 extern bool octave_yes_or_no (const std::string& prompt);
--- a/libinterp/corefcn/variables.cc	Sat Jul 23 14:49:54 2016 -0400
+++ b/libinterp/corefcn/variables.cc	Thu Jun 30 18:19:37 2016 +1000
@@ -238,6 +238,7 @@
   string_vector names;
 
   size_t pos = text.rfind ('.');
+  bool array = false;
 
   if (pos != std::string::npos)
     {
@@ -248,9 +249,15 @@
 
       prefix = text.substr (0, pos);
 
+      if (prefix == "")
+        {
+          array = true;
+          prefix = find_indexed_expression (text);
+        }
+
       std::string base_name = prefix;
 
-      pos = base_name.find_first_of ("{(.");
+      pos = base_name.find_first_of ("{(. ");
 
       if (pos != std::string::npos)
         base_name = base_name.substr (0, pos);
@@ -284,16 +291,20 @@
         }
     }
 
+  // Undo look-back that found the array expression,
+  // but insert an extra "." to distinguish from the non-struct case.
+  if (array)
+    prefix = ".";
+
   return names;
 }
 
 // FIXME: this will have to be much smarter to work "correctly".
-
 bool
-looks_like_struct (const std::string& text)
+looks_like_struct (const std::string& text, char prev_char)
 {
   bool retval = (! text.empty ()
-                 && text != "."
+                 && (text != "." || prev_char == ')' || prev_char == '}')
                  && text.find_first_of (octave::sys::file_ops::dir_sep_chars ()) == std::string::npos
                  && text.find ("..") == std::string::npos
                  && text.rfind ('.') != std::string::npos);
--- a/libinterp/corefcn/variables.h	Sat Jul 23 14:49:54 2016 -0400
+++ b/libinterp/corefcn/variables.h	Thu Jun 30 18:19:37 2016 +1000
@@ -68,7 +68,7 @@
                              std::string& hint);
 
 extern OCTINTERP_API bool
-looks_like_struct (const std::string& text);
+looks_like_struct (const std::string& text, char prev_char);
 
 extern OCTINTERP_API int
 symbol_exist (const std::string& name, const std::string& type = "any");
--- a/liboctave/util/cmd-edit.cc	Sat Jul 23 14:49:54 2016 -0400
+++ b/liboctave/util/cmd-edit.cc	Thu Jun 30 18:19:37 2016 +1000
@@ -153,6 +153,8 @@
 
     std::string do_get_current_line (void) const;
 
+    char do_get_prev_char (int) const;
+
     void do_replace_line (const std::string& text, bool clear_undo);
 
     void do_kill_full_line (void);
@@ -647,6 +649,17 @@
     return retval;
   }
 
+  // Return the character (offset+1) to the left of the cursor,
+  // or '\0' if the cursor is at the start of the line.
+  char
+  gnu_readline::do_get_prev_char (int offset) const
+  {
+    const char *buf = ::octave_rl_line_buffer ();
+    int p = ::octave_rl_point ();
+
+    return p > offset ? buf[p - offset - 1] : '\0';
+  }
+
   void
   gnu_readline::do_replace_line (const std::string& text, bool clear_undo)
   {
@@ -927,6 +940,8 @@
 
     std::string do_get_current_line (void) const;
 
+    char do_get_prev_char (int) const;
+
     void do_replace_line (const std::string& text, bool clear_undo);
 
     void do_kill_full_line (void);
@@ -1003,6 +1018,12 @@
     return "";
   }
 
+  char
+  default_command_editor::do_get_prev_char (int) const
+  {
+    return '\0';
+  }
+
   void
   default_command_editor::do_replace_line (const std::string&, bool)
   {
@@ -1411,6 +1432,14 @@
     return (instance_ok ()) ? instance->do_get_current_line () : "";
   }
 
+  // Return the character (offset+1) to the left of the cursor,
+  // or '\0' if the cursor is at the start of the line.
+  char
+  command_editor::get_prev_char (int offset)
+  {
+    return (instance_ok ()) ? instance->do_get_prev_char (offset) : '\0';
+  }
+
   void
   command_editor::replace_line (const std::string& text, bool clear_undo)
   {
--- a/liboctave/util/cmd-edit.h	Sat Jul 23 14:49:54 2016 -0400
+++ b/liboctave/util/cmd-edit.h	Thu Jun 30 18:19:37 2016 +1000
@@ -137,6 +137,8 @@
 
     static std::string get_current_line (void);
 
+    static char get_prev_char (int);
+
     static void replace_line (const std::string& text, bool clear_undo = true);
 
     static void kill_full_line (void);
@@ -308,6 +310,8 @@
 
     virtual std::string do_get_current_line (void) const = 0;
 
+    virtual char do_get_prev_char (int) const = 0;
+
     virtual void do_replace_line (const std::string& text, bool clear_undo) = 0;
 
     virtual void do_kill_full_line (void) = 0;
--- a/liboctave/util/oct-rl-edit.c	Sat Jul 23 14:49:54 2016 -0400
+++ b/liboctave/util/oct-rl-edit.c	Thu Jun 30 18:19:37 2016 +1000
@@ -173,6 +173,12 @@
 }
 
 int
+octave_rl_point (void)
+{
+  return rl_point;
+}
+
+int
 octave_rl_do_undo (void)
 {
   return rl_do_undo ();
--- a/liboctave/util/oct-rl-edit.h	Sat Jul 23 14:49:54 2016 -0400
+++ b/liboctave/util/oct-rl-edit.h	Thu Jun 30 18:19:37 2016 +1000
@@ -86,6 +86,8 @@
 
 extern const char *octave_rl_line_buffer (void);
 
+extern int octave_rl_point (void);
+
 extern int octave_rl_do_undo (void);
 
 extern void octave_rl_clear_undo_list (void);