Преглед на файлове

Merge pull request #725 from jcfr/4236-python-console-completion-2017.07.18

4236 python console completion 2017.07.18
Jean-Christophe Fillion-Robin преди 7 години
родител
ревизия
3d30e4d160

+ 30 - 3
Applications/ctkSimplePythonShell/ctkSimplePythonShellMain.cpp

@@ -20,6 +20,9 @@
 
 // Qt includes
 #include <QApplication>
+#include <QLabel>
+#include <QMainWindow>
+#include <QStatusBar>
 #include <QTextStream>
 #include <QTimer>
 
@@ -59,6 +62,19 @@ void executeScripts(void * data)
     }
 }
 
+//-----------------------------------------------------------------------------
+void onCursorPositionChanged(void *data)
+{
+  ctkPythonConsole* pythonConsole = reinterpret_cast<ctkPythonConsole*>(data);
+  QMainWindow* mainWindow = qobject_cast<QMainWindow*>(
+        pythonConsole->parentWidget());
+  QLabel * label = mainWindow->statusBar()->findChild<QLabel*>();
+  label->setText(QString("Position %1, Column %2, Line %3")
+                 .arg(pythonConsole->cursorPosition())
+                 .arg(pythonConsole->cursorColumn())
+                 .arg(pythonConsole->cursorLine()));
+}
+
 } // end of anonymous namespace
 
 //-----------------------------------------------------------------------------
@@ -106,9 +122,20 @@ int main(int argc, char** argv)
 
     ctkPythonConsole console;
     console.initialize(&pythonManager);
-    console.setAttribute(Qt::WA_QuitOnClose, true);
-    console.resize(600, 280);
-    console.show();
+
+    QMainWindow mainWindow;
+    mainWindow.setCentralWidget(&console);
+    mainWindow.resize(600, 280);
+    mainWindow.show();
+
+    QLabel cursorPositionLabel;
+    mainWindow.statusBar()->addWidget(&cursorPositionLabel);
+
+    ctkCallback cursorPositionChangedCallback;
+    cursorPositionChangedCallback.setCallbackData(&console);
+    cursorPositionChangedCallback.setCallback(onCursorPositionChanged);
+    QObject::connect(&console, SIGNAL(cursorPositionChanged()),
+                     &cursorPositionChangedCallback, SLOT(invoke()));
 
     console.setProperty("isInteractive", parsedArgs.contains("interactive"));
 

+ 57 - 0
Libs/Scripting/Python/Core/Testing/Cpp/PythonAttributes-test.py

@@ -0,0 +1,57 @@
+
+class Maths(object):
+
+  MATHS_CLASS_MEMBER=0.1
+
+  def __init__(self, num):
+    self.maths_instance_member = num
+
+  def maths_instance_method(self):
+    print("Hello from instance method")
+
+class MultipleArg(object):
+  def __init__(self, num, str, other = 0):
+    self.multipleArg_instance_member_num = num + other
+    self.multipleArg_instance_member_str = str
+    self.multipleArg_instance_member_other = other
+
+class Bar(object):
+
+  BAR_CLASS_MEMBER = 1
+
+  def __init__(self):
+    self.bar_instance_member = 1
+
+  def bar_instance_method(self):
+    print("Hello from instance method")
+
+  def bar_maths(self, num = 0):
+    return Maths(num)
+
+  @staticmethod
+  def bar_class_method():
+    print("Hello from class method")
+
+class Foo(object):
+
+  FOO_CLASS_MEMBER = 1
+
+  def __init__(self):
+    self.foo_instance_member = 1
+
+  def foo_instance_method(self):
+    print("Hello from instance method")
+
+  def instantiate_bar(self):
+    return Bar()
+
+  @staticmethod
+  def foo_class_method():
+    print("Hello from class method")
+
+f = Foo()
+
+class Object(object): pass
+
+d = Object()
+setattr(d, 'foo_class', Foo)

+ 132 - 1
Libs/Scripting/Python/Core/Testing/Cpp/ctkAbstractPythonManagerTest.cpp

@@ -54,7 +54,11 @@ private Q_SLOTS:
   void testExecuteFile();
   void testExecuteFile_data();
 
-  //void testPythonAttributes(); // TODO
+  void testPythonAttributes();
+  void testPythonAttributes_data();
+
+  void testPythonAttributeValues();
+  void testPythonAttributeValues_data();
 
   void testPythonModule();
   void testPythonModule_data();
@@ -271,6 +275,133 @@ void ctkAbstractPythonManagerTester::testExecuteFile_data()
 }
 
 // ----------------------------------------------------------------------------
+void ctkAbstractPythonManagerTester::testPythonAttributes()
+{
+  QFETCH(QString, pythonVariableName);
+  QFETCH(QStringList, expectedAttributeList);
+
+  QString test_path = __FILE__; // return the local path of this source file
+  test_path.lastIndexOf("/");
+  test_path.replace(test_path.lastIndexOf("/"),
+                         test_path.size() - test_path.lastIndexOf("/"),
+                         "/PythonAttributes-test.py");
+  this->PythonManager.executeFile(test_path);
+
+  QStringList AttributeList = this->PythonManager.pythonAttributes(pythonVariableName, QString("__main__").toLatin1(), false);
+
+  foreach (const QString& expectedAttribute, expectedAttributeList)
+    {
+    QVERIFY(AttributeList.contains(expectedAttribute));
+    }
+}
+
+// ----------------------------------------------------------------------------
+void ctkAbstractPythonManagerTester::testPythonAttributes_data()
+{
+  QTest::addColumn<QString>("pythonVariableName");
+  QTest::addColumn<QStringList>("expectedAttributeList");
+
+  QTest::newRow("d.foo_class()")
+                     << "d.foo_class()"
+                     << (QStringList()
+                         << "FOO_CLASS_MEMBER"
+                         << "foo_class_method"
+                         << "foo_instance_member"
+                         << "foo_instance_method"
+                         << "instantiate_bar");
+
+  QTest::newRow("d.foo_class().instantiate_bar()")
+                     << "d.foo_class().instantiate_bar()"
+                     << (QStringList()
+                         << "BAR_CLASS_MEMBER"
+                         << "bar_class_method"
+                         << "bar_instance_member"
+                         << "bar_instance_method");
+
+  QTest::newRow("d.foo_class().instantiate_bar().bar_maths(5)")
+                     << "d.foo_class().instantiate_bar().bar_maths(5)"
+                     << (QStringList()
+                         << "MATHS_CLASS_MEMBER"
+                         << "maths_instance_member" // TODO: verify result is 5
+                         << "maths_instance_method");
+
+  QTest::newRow("MultipleArg( 5 + 5 , '(')")
+                     << "MultipleArg( 5 + 5 , '(')"
+                     << (QStringList()
+                         << "multipleArg_instance_member_num" // TODO: verify result is 10
+                         << "multipleArg_instance_member_str" // TODO: verify result is '('
+                         << "multipleArg_instance_member_other"); // TODO: verify result is 0
+
+  QTest::newRow("MultipleArg( 5 % 5 + 1, '\"', 0.1)")
+                     << "MultipleArg( 5 + 5 , '\"', 0.1)"
+                     << (QStringList()
+                         << "multipleArg_instance_member_num" // TODO: verify result is 1.1
+                         << "multipleArg_instance_member_str" // TODO: verify result is '"'
+                         << "multipleArg_instance_member_other"); // TODO: verify result is 0.1
+
+}
+
+// ----------------------------------------------------------------------------
+void ctkAbstractPythonManagerTester::testPythonAttributeValues()
+{
+  QFETCH(QString, variableName);
+  QFETCH(QVariant, expectedVariableValue);
+
+  QString test_path = __FILE__; // return the local path of this source file
+  test_path.lastIndexOf("/");
+  test_path.replace(test_path.lastIndexOf("/"),
+                         test_path.size() - test_path.lastIndexOf("/"),
+                         "/PythonAttributes-test.py");
+  this->PythonManager.executeFile(test_path);
+
+  QCOMPARE(
+        expectedVariableValue,
+        this->PythonManager.getVariable(variableName));
+}
+
+// ----------------------------------------------------------------------------
+void ctkAbstractPythonManagerTester::testPythonAttributeValues_data()
+{
+  QTest::addColumn<QString>("variableName");
+  QTest::addColumn<QVariant>("expectedVariableValue");
+
+  // d.foo_class().instantiate_bar().bar_maths(5)
+  QTest::newRow("")
+      << "d.foo_class().instantiate_bar().bar_maths(5).MATHS_CLASS_MEMBER"
+      << QVariant(0.1);
+
+  QTest::newRow("")
+      << "d.foo_class().instantiate_bar().bar_maths(5).maths_instance_member"
+      << QVariant(5);
+
+  // MultipleArg( 5 + 5 , '(')
+  QTest::newRow("")
+      << "MultipleArg( 5 + 5 , '(').multipleArg_instance_member_num"
+      << QVariant(10);
+
+  QTest::newRow("")
+      << "MultipleArg( 5 + 5 , '(').multipleArg_instance_member_str"
+      << QVariant("(");
+
+  QTest::newRow("")
+      << "MultipleArg( 5 + 5 , '(').multipleArg_instance_member_other"
+      << QVariant(0);
+
+  // MultipleArg( 5 + 5 , '\"', 0.1)
+  QTest::newRow("")
+      << "MultipleArg( 5 + 5 , '\"', 0.1).multipleArg_instance_member_num"
+      << QVariant(0);
+
+  QTest::newRow("")
+      << "MultipleArg( 5 + 5 , '\"', 0.1).multipleArg_instance_member_str"
+      << QVariant('"');
+
+  QTest::newRow("")
+      << "MultipleArg( 5 + 5 , '\"', 0.1).multipleArg_instance_member_other"
+      << QVariant(0.1);
+}
+
+// ----------------------------------------------------------------------------
 void ctkAbstractPythonManagerTester::testPythonModule()
 {
   QFETCH(QString, pythonCode);

+ 159 - 33
Libs/Scripting/Python/Core/ctkAbstractPythonManager.cpp

@@ -338,6 +338,93 @@ void ctkAbstractPythonManager::setInitializationFunction(void (*initFunction)())
   d->InitFunction = initFunction;
 }
 
+//-----------------------------------------------------------------------------
+QStringList ctkAbstractPythonManager::dir_object(PyObject* object,
+                                                 bool appendParenthesis)
+{
+  QStringList results;
+  if (!object)
+    {
+    return results;
+    }
+  PyObject* keys = PyObject_Dir(object);
+  if (keys)
+    {
+    PyObject* key;
+    PyObject* value;
+    int nKeys = PyList_Size(keys);
+    for (int i = 0; i < nKeys; ++i)
+      {
+      key = PyList_GetItem(keys, i);
+      value = PyObject_GetAttr(object, key);
+      if (!value)
+        {
+        continue;
+        }
+      QString key_str(PyString_AsString(key));
+      // Append "()" if the associated object is a function
+      if (appendParenthesis && PyCallable_Check(value))
+        {
+        key_str.append("()");
+        }
+      results << key_str;
+      Py_DECREF(value);
+      }
+    Py_DECREF(keys);
+    }
+  return results;
+}
+
+QStringList ctkAbstractPythonManager::splitByDotOutsideParenthesis(const QString& pythonVariableName)
+{
+  QStringList tmpNames;
+  int last_pos_dot = pythonVariableName.length();
+  int numberOfParenthesisClosed = 0;
+  bool betweenSingleQuotes = false;
+  bool betweenDoubleQuotes = false;
+  for (int i = pythonVariableName.length()-1; i >= 0; --i)
+    {
+    QChar c = pythonVariableName.at(i);
+    if (c == '\'' && !betweenDoubleQuotes)
+      {
+      betweenSingleQuotes = !betweenSingleQuotes;
+      }
+    if (c == '"' && !betweenSingleQuotes)
+      {
+      betweenDoubleQuotes = !betweenDoubleQuotes;
+      }
+    // note that we must not count parenthesis if they are between quote...
+    if (!betweenSingleQuotes && !betweenDoubleQuotes)
+      {
+      if (c == '(')
+        {
+        if (numberOfParenthesisClosed>0)
+          {
+          numberOfParenthesisClosed--;
+          }
+        }
+      if (c == ')')
+        {
+        numberOfParenthesisClosed++;
+        }
+      }
+    // if we are outside parenthesis and we find a dot, then split
+    if ((c == '.' && numberOfParenthesisClosed<=0)
+        || i == 0)
+      {
+      if (i == 0) {i--;} // last case where we have to split the begging this time
+      QString textToSplit = pythonVariableName.mid(i+1,last_pos_dot-(i+1));
+      if (!textToSplit.isEmpty())
+        {
+        tmpNames.push_front(textToSplit);
+        }
+      last_pos_dot =i;
+      }
+    }
+  return tmpNames;
+}
+
+
 //----------------------------------------------------------------------------
 QStringList ctkAbstractPythonManager::pythonAttributes(const QString& pythonVariableName,
                                                        const QString& module,
@@ -351,6 +438,7 @@ QStringList ctkAbstractPythonManager::pythonAttributes(const QString& pythonVari
   PyObject* object = ctkAbstractPythonManager::pythonModule(precedingModule);
   PyObject* prevObject = 0;
   QStringList moduleList = module.split(".", QString::SkipEmptyParts);
+
   foreach(const QString& module, moduleList)
     {
     object = PyDict_GetItemString(dict, module.toLatin1().data());
@@ -375,57 +463,95 @@ QStringList ctkAbstractPythonManager::pythonAttributes(const QString& pythonVari
 //    }
 //  Py_INCREF(object);
 
+  PyObject* main_object = object; // save the modue object (usually __main__ or __main__.__builtins__)
+  QString instantiated_class_name = "_ctkAbstractPythonManager_autocomplete_tmp";
+  QStringList results; // the list of attributes to return
+  QString line_code="";
+
   if (!pythonVariableName.isEmpty())
     {
-    QStringList tmpNames = pythonVariableName.split('.');
+    // Split the pythonVariableName at every dot
+    // /!\ // CAREFUL to don't take dot which are between parenthesis
+    // To avoid the problem: split by dots in a smarter way!
+    QStringList tmpNames = splitByDotOutsideParenthesis(pythonVariableName);
+
     for (int i = 0; i < tmpNames.size() && object; ++i)
       {
+      // fill the line step by step
+      // For example: pythonVariableName = d.foo_class().instantiate_bar().
+      // line_code will be filled first by 'd.' and then, line_code = 'd.foo_class().', etc
+      line_code.append(tmpNames[i]);
+      line_code.append(".");
+
       QByteArray tmpName = tmpNames.at(i).toLatin1();
-      PyObject* prevObj = object;
-      if (PyDict_Check(object))
+      if (tmpName.contains('(') && tmpName.contains(')'))
         {
-        object = PyDict_GetItemString(object, tmpName.data());
-        Py_XINCREF(object);
+        tmpNames[i] = tmpNames[i].left(tmpName.indexOf('('));
+        tmpName = tmpNames.at(i).toLatin1();
+
+        // Attempt to instantiate the associated python class
+        PyObject* classToInstantiate;
+        if (PyDict_Check(dict))
+          classToInstantiate = PyDict_GetItemString(dict, tmpName.data());
+        else
+          classToInstantiate = PyObject_GetAttrString(object, tmpName.data());
+
+        if (classToInstantiate)
+          {
+          QString code = " = ";
+          code.prepend(instantiated_class_name);
+          line_code.remove(line_code.size()-1,1); // remove the last char which is a dot
+          code.append(line_code);
+          // create a temporary attribute which will instantiate the class
+          // For example: code = '_ctkAbstractPythonManager_autocomplete_tmp = d.foo_class()'
+          PyRun_SimpleString(code.toLatin1().data());
+          line_code.append('.'); // add the point again in case we need to continue to fill line_code
+          object = PyObject_GetAttrString(main_object,instantiated_class_name.toLatin1().data());
+
+          dict = object;
+          results = ctkAbstractPythonManager::dir_object(object,appendParenthesis);
+          }
         }
       else
         {
-        object = PyObject_GetAttrString(object, tmpName.data());
+        PyObject* prevObj = object;
+        if (PyDict_Check(object))
+          {
+          object = PyDict_GetItemString(object, tmpName.data());
+          Py_XINCREF(object);
+          }
+        else
+          {
+          object = PyObject_GetAttrString(object, tmpName.data());
+          dict = object;
+          }
+        Py_DECREF(prevObj);
+
+        if (object)
+          {
+          results = ctkAbstractPythonManager::dir_object(object,appendParenthesis);
+          }
         }
-      Py_DECREF(prevObj);
       }
     PyErr_Clear();
     }
+  // By default if pythonVariable is empty, return the attributes of the module
+  else
+    {
+    results = ctkAbstractPythonManager::dir_object(object,appendParenthesis);
+    }
 
-  QStringList results;
   if (object)
     {
-    PyObject* keys = PyObject_Dir(object);
-    if (keys)
-      {
-      PyObject* key;
-      PyObject* value;
-      int nKeys = PyList_Size(keys);
-      for (int i = 0; i < nKeys; ++i)
-        {
-        key = PyList_GetItem(keys, i);
-        value = PyObject_GetAttr(object, key);
-        if (!value)
-          {
-          continue;
-          }
-        QString key_str(PyString_AsString(key));
-        // Append "()" if the associated object is a function
-        if (appendParenthesis && PyCallable_Check(value))
-          {
-          key_str.append("()");
-          }
-        results << key_str;
-        Py_DECREF(value);
-        }
-      Py_DECREF(keys);
-      }
     Py_DECREF(object);
     }
+
+  // remove the temporary attribute (created to instantiate a class) from the module object
+  if (PyObject_HasAttrString(main_object,instantiated_class_name.toLatin1().data()))
+    {
+    PyObject_DelAttrString(main_object,instantiated_class_name.toLatin1().data());
+    }
+
   return results;
 }
 

+ 13 - 1
Libs/Scripting/Python/Core/ctkAbstractPythonManager.h

@@ -104,7 +104,19 @@ public:
   /// \sa preInitialization executeInitializationScripts
   void setInitializationFunction(void (*initFunction)());
 
-  /// Given a python variable name, lookup its attributes and return them in a string list.
+  /// Given a python object, lookup its attributes and return them in a string list.
+  /// If the argument \c appendParenthesis is set to True, "()" will be appended to attributes
+  /// being Python callable.
+  static QStringList dir_object(PyObject* object,
+                                bool appendParenthesis = false);
+
+  /// Given a python variable name, it returns the string list splited
+  /// at every dots which will be outside parenthesis
+  /// (It also takes care about the possibility that quotes can include parenthesis)
+  static QStringList splitByDotOutsideParenthesis(const QString& pythonVariableName);
+
+  /// Given a python variable name, if it can be called, try to call the method or instantiate the class,
+  /// lookup its attributes and return them in a string list.
   /// By default the attributes are looked up from \c __main__.
   /// If the argument \c appendParenthesis is set to True, "()" will be appended to attributes
   /// being Python callable.

+ 60 - 16
Libs/Scripting/Python/Widgets/ctkPythonConsole.cpp

@@ -86,6 +86,8 @@ public:
 
   virtual int cursorOffset(const QString& completion);
   virtual void updateCompletionModel(const QString& completion);
+  static QString searchUsableCharForCompletion(const QString& completion);
+
 
 protected:
   bool isInUserDefinedClass(const QString &pythonFunctionPath);
@@ -264,31 +266,73 @@ int ctkPythonConsoleCompleter::parameterCountFromDocumentation(const QString& py
   return parameterCount;
 }
 
-void ctkPythonConsoleCompleter::updateCompletionModel(const QString& completion)
+//----------------------------------------------------------------------------
+QString ctkPythonConsoleCompleter::searchUsableCharForCompletion(const QString& completion)
 {
-  // Start by clearing the model
-  this->setModel(0);
-
-  // Don't try to complete the empty string
-  if (completion.isEmpty())
-    {
-    return;
-    }
-
+  bool betweenSingleQuotes = false;
+  bool betweenDoubleQuotes = false;
+  int numberOfParenthesisClosed = 0;
   // Search backward through the string for usable characters
   QString textToComplete;
   for (int i = completion.length()-1; i >= 0; --i)
     {
     QChar c = completion.at(i);
-    if (c.isLetterOrNumber() || c == '.' || c == '_')
+    if (c == '\'' && !betweenDoubleQuotes)
       {
+      betweenSingleQuotes = !betweenSingleQuotes;
+      }
+    if (c == '"' && !betweenSingleQuotes)
+      {
+      betweenDoubleQuotes = !betweenDoubleQuotes;
+      }
+    // Stop the completion if c is not a letter,number,.,_,(,) and outside parenthesis
+    if (c.isLetterOrNumber() || c == '.' || c == '_' || c == '(' || c == ')'
+        || numberOfParenthesisClosed)
+      {
+      // Keep adding caractere to the completion if
+      // the number of '(' is always <= to the number of ')'
+      // note that we must not count parenthesis if they are between quote...
+      if (!betweenSingleQuotes && !betweenDoubleQuotes)
+        {
+        if (c == '(')
+          {
+          if (numberOfParenthesisClosed>0)
+            {
+            numberOfParenthesisClosed--;
+            }
+          else
+            break; // stop to prepend
+          }
+        if (c == ')')
+          {
+          numberOfParenthesisClosed++;
+          }
+        }
       textToComplete.prepend(c);
       }
     else
       {
       break;
       }
-   }
+    }
+  return textToComplete;
+}
+
+//----------------------------------------------------------------------------
+void ctkPythonConsoleCompleter::updateCompletionModel(const QString& completion)
+{
+  // Start by clearing the model
+  this->setModel(0);
+
+  // Don't try to complete the empty string
+  if (completion.isEmpty())
+    {
+    return;
+    }
+
+  bool appendParenthesis = true;
+  // Search backward through the string for usable characters
+  QString textToComplete = searchUsableCharForCompletion(completion);
 
   // Split the string at the last dot, if one exists
   QString lookup;
@@ -304,9 +348,10 @@ void ctkPythonConsoleCompleter::updateCompletionModel(const QString& completion)
   QStringList attrs;
   if (!lookup.isEmpty() || !compareText.isEmpty())
     {
-    bool appendParenthesis = true;
-    attrs = this->PythonManager.pythonAttributes(lookup, QLatin1String("__main__"), appendParenthesis);
-    attrs << this->PythonManager.pythonAttributes(lookup, QLatin1String("__main__.__builtins__"),
+    QString module = "__main__";
+    attrs = this->PythonManager.pythonAttributes(lookup, module.toLatin1(), appendParenthesis);
+    module = "__main__.__builtins__";
+    attrs << this->PythonManager.pythonAttributes(lookup, module.toLatin1(),
                                                   appendParenthesis);
     attrs.removeDuplicates();
     }
@@ -319,7 +364,6 @@ void ctkPythonConsoleCompleter::updateCompletionModel(const QString& completion)
     this->setCaseSensitivity(Qt::CaseInsensitive);
     this->setCompletionPrefix(compareText.toLower());
 
-    //qDebug() << "completion" << completion;
     // If a dot as been entered and if an item of possible
     // choices matches one of the preference list, it will be selected.
     QModelIndex preferredIndex = this->completionModel()->index(0, 0);

+ 328 - 24
Libs/Widgets/ctkConsole.cpp

@@ -60,6 +60,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <QMimeData>
 #include <QPointer>
 #include <QPushButton>
+#include <QTextBlock>
 #include <QTextCursor>
 #include <QVBoxLayout>
 #include <QScrollBar>
@@ -93,7 +94,9 @@ ctkConsolePrivate::ctkConsolePrivate(ctkConsole& object) :
   Superclass(0),
   q_ptr(&object),
   InteractivePosition(documentEnd()),
+  MessageOutputSize(0),
   MultilineStatement(false), Ps1("$ "), Ps2("> "),
+  insertCompletionMethod(true),
   EditorHints(ctkConsole::AutomaticIndentation | ctkConsole::RemoveTrailingSpaces),
   ScrollbarAtBottom(false),
   CompleterShortcuts(QList<QKeySequence>() << Qt::Key_Tab),
@@ -115,6 +118,7 @@ void ctkConsolePrivate::init()
 
   this->PromptColor = QColor(0, 0, 0);    // Black
   this->OutputTextColor = QColor(0, 150, 0);  // Green
+  this->MessageOutputColor = QColor(Qt::gray);  // Gray
   this->ErrorTextColor = QColor(255, 0, 0);   // Red
   this->StdinTextColor = QColor(Qt::darkGray);
   this->CommandTextColor = QColor(0, 0, 150); // Blue
@@ -157,6 +161,8 @@ void ctkConsolePrivate::init()
           SLOT(onScrollBarValueChanged(int)));
   connect(this, SIGNAL(textChanged()), SLOT(onTextChanged()));
   connect(this->RunFileButton, SIGNAL(clicked()), q, SLOT(runFile()));
+  connect(this, SIGNAL(cursorPositionChanged()),
+          q, SIGNAL(cursorPositionChanged()));
 }
 
 //-----------------------------------------------------------------------------
@@ -229,6 +235,75 @@ bool ctkConsolePrivate::isMoveLeftWithinLine(QKeyEvent* e, QTextCursor::MoveOper
 }
 
 //-----------------------------------------------------------------------------
+bool ctkConsolePrivate::isMoveRighttWithinLine(QKeyEvent* e, QTextCursor::MoveOperation &moveOperation, QTextCursor::MoveMode &moveMode)
+{
+  if (e == QKeySequence::MoveToNextChar)
+    {
+    moveOperation = QTextCursor::Right;
+    moveMode = QTextCursor::MoveAnchor;
+    return true;
+    }
+  else if (e == QKeySequence::SelectNextChar)
+    {
+    moveOperation = QTextCursor::Right;
+    moveMode = QTextCursor::KeepAnchor;
+    return true;
+    }
+  else if (e == QKeySequence::MoveToNextWord)
+    {
+    moveOperation = QTextCursor::WordRight;
+    moveMode = QTextCursor::MoveAnchor;
+    return true;
+    }
+  else if (e == QKeySequence::SelectNextWord)
+    {
+    moveOperation = QTextCursor::WordRight;
+    moveMode = QTextCursor::KeepAnchor;
+    return true;
+    }
+  else if (e == QKeySequence::MoveToEndOfLine)
+    {
+    moveOperation = QTextCursor::EndOfLine;
+    moveMode = QTextCursor::MoveAnchor;
+    return true;
+    }
+  else if (e == QKeySequence::SelectEndOfLine)
+    {
+    moveOperation = QTextCursor::EndOfLine;
+    moveMode = QTextCursor::KeepAnchor;
+    return true;
+    }
+  else if (e == QKeySequence::MoveToEndOfBlock)
+    {
+    moveOperation = QTextCursor::EndOfLine;
+    moveMode = QTextCursor::MoveAnchor;
+    return true;
+    }
+  else if (e == QKeySequence::SelectEndOfBlock)
+    {
+    moveOperation = QTextCursor::EndOfLine;
+    moveMode = QTextCursor::KeepAnchor;
+    return true;
+    }
+  else if (e == QKeySequence::MoveToEndOfDocument)
+    {
+    moveOperation = QTextCursor::EndOfLine;
+    moveMode = QTextCursor::MoveAnchor;
+    return true;
+    }
+  else if (e == QKeySequence::SelectEndOfDocument)
+    {
+    moveOperation = QTextCursor::EndOfLine;
+    moveMode = QTextCursor::KeepAnchor;
+    return true;
+    }
+  else
+    {
+    return false;
+    }
+}
+
+//-----------------------------------------------------------------------------
 void ctkConsolePrivate::keyPressEvent(QKeyEvent* e)
 {
   if (this->Completer && this->Completer->popup()->isVisible())
@@ -254,6 +329,10 @@ void ctkConsolePrivate::keyPressEvent(QKeyEvent* e)
   const bool selection = text_cursor.anchor() != text_cursor.position();
   // Set to true if the cursor overlaps the history area
   const bool history_area = this->isCursorInHistoryArea();
+  // The message output area is defined just under the command line
+  // and it can display all messages catch during we autocomplete, etc.
+  // Set to true if the cursor overlaps the message output area
+  const bool message_output_area = this->isCursorInMessageOutputArea();
 
   // Allow copying anywhere in the console ...
   if(e == QKeySequence::Copy)
@@ -269,7 +348,7 @@ void ctkConsolePrivate::keyPressEvent(QKeyEvent* e)
   // Allow cut only if the selection is limited to the interactive area ...
   if(e == QKeySequence::Cut)
     {
-    if(selection && !history_area)
+    if(selection && !history_area && !message_output_area)
       {
       this->cut();
       }
@@ -277,12 +356,12 @@ void ctkConsolePrivate::keyPressEvent(QKeyEvent* e)
     return;
     }
 
-  // Paste to the end of document if in the history area
+  // Paste to the end of commandLine if in the history area or in message output area
   if(e == QKeySequence::Paste)
     {
-    if(history_area)
+    if(history_area || message_output_area)
       {
-      text_cursor.setPosition(this->documentEnd());
+      text_cursor.setPosition(this->commandEnd());
       this->setTextCursor(text_cursor);
       }
     this->paste();
@@ -310,13 +389,15 @@ void ctkConsolePrivate::keyPressEvent(QKeyEvent* e)
   // Force the cursor back to the interactive area if anything else than copy/paste or page up/down is done
   // but only when a "real" key is pressed, not just a modifier (otherwise we could not press Control-c in the
   // history area because the cursor would jump to the interactive area immediately when Control is pressed)
-  if(history_area
+  // Update: message_output_area is like the history_area: we can't modify it
+  if( (history_area
+       || message_output_area)
        && e->key() != Qt::Key_Control
        && e->key() != Qt::Key_Meta
        && e->key() != Qt::Key_Alt
        && e->key() != Qt::Key_Shift)
     {
-    text_cursor.setPosition(this->documentEnd());
+    text_cursor.setPosition(this->commandEnd());
     this->setTextCursor(text_cursor);
     }
 
@@ -326,7 +407,7 @@ void ctkConsolePrivate::keyPressEvent(QKeyEvent* e)
   if(isMoveLeftWithinLine(e, moveOperation, moveMode))
     {
     text_cursor.movePosition(moveOperation, moveMode);
-    if (text_cursor.position() > this->InteractivePosition)
+    if (text_cursor.position() >= this->InteractivePosition)
       {
       this->Superclass::keyPressEvent(e);
       }
@@ -336,14 +417,43 @@ void ctkConsolePrivate::keyPressEvent(QKeyEvent* e)
       this->setTextCursor(text_cursor);
       e->accept();
       }
+    this->updateCompleterIfVisible();
+    return;
+    }
+
+  // End of line should be the end of interactive area
+  moveOperation = QTextCursor::NoMove;
+  moveMode = QTextCursor::MoveAnchor;
+  if(isMoveRighttWithinLine(e, moveOperation, moveMode))
+    {
+    text_cursor.movePosition(moveOperation, moveMode);
+    if (text_cursor.position() <= this->commandEnd())
+      {
+      this->Superclass::keyPressEvent(e);
+      }
+    else
+      {
+      text_cursor.setPosition(this->commandEnd(), moveMode);
+      this->setTextCursor(text_cursor);
+      e->accept();
+      }
+    this->updateCompleterIfVisible();
     return;
     }
 
   if (e == QKeySequence::Delete)
     {
     e->accept();
-    this->Superclass::keyPressEvent(e);
-    this->updateCommandBuffer();
+    // Can delete only if we are not at the end of the command line.
+    // There is an exception if something (in the interactive area only) is selected,
+    // because it will erase the text selected instead.
+    if (text_cursor.position() < this->commandEnd()
+        || (text_cursor.position() <= this->commandEnd()
+             && selection && !message_output_area && !history_area))
+      {
+      this->Superclass::keyPressEvent(e);
+      this->updateCommandBuffer();
+      }
     return;
     }
 
@@ -352,7 +462,12 @@ void ctkConsolePrivate::keyPressEvent(QKeyEvent* e)
   if (e->key() == Qt::Key_Backspace && !(e->modifiers() & ~Qt::ShiftModifier))
     {
     e->accept();
-    if(text_cursor.position() > this->InteractivePosition)
+    // Can delete with backspace only if the cursor is after the InteractivePosition.
+    // There is an exception if something (in the interactive area only) is selected,
+    // because it will erase the text selected instead.
+    if (text_cursor.position() > this->InteractivePosition
+        || (text_cursor.position() >= this->InteractivePosition
+             && selection  && !message_output_area && !history_area))
       {
       this->Superclass::keyPressEvent(e);
       this->updateCommandBuffer();
@@ -364,7 +479,7 @@ void ctkConsolePrivate::keyPressEvent(QKeyEvent* e)
   if (e == QKeySequence::DeleteStartOfWord)
       {
       e->accept();
-      if(text_cursor.position() > this->InteractivePosition)
+      if (text_cursor.position() > this->InteractivePosition)
         {
         this->Superclass::keyPressEvent(e);
         this->updateCommandBuffer();
@@ -423,7 +538,11 @@ void ctkConsolePrivate::keyPressEvent(QKeyEvent* e)
     }
 
   e->accept();
-  this->switchToUserInputTextColor();
+  //Don't change the color of text outside the interactive area
+  if (!message_output_area && !history_area)
+    {
+    this->switchToUserInputTextColor();
+    }
   this->Superclass::keyPressEvent(e);
   this->updateCommandBuffer();
   this->updateCompleterIfVisible();
@@ -465,6 +584,14 @@ int ctkConsolePrivate::documentEnd() const
 }
 
 //-----------------------------------------------------------------------------
+int ctkConsolePrivate::commandEnd() const
+{
+  QTextCursor c(this->document());
+  c.setPosition(this->documentEnd()-this->MessageOutputSize);
+  return c.position();
+}
+
+//-----------------------------------------------------------------------------
 void ctkConsolePrivate::focusOutEvent(QFocusEvent *e)
 {
   this->Superclass::focusOutEvent(e);
@@ -538,12 +665,93 @@ void ctkConsolePrivate::updateCompleter()
     // Get the text between the current cursor position
     // and the start of the line
     QTextCursor text_cursor = this->textCursor();
-    text_cursor.setPosition(this->InteractivePosition, QTextCursor::KeepAnchor);
+    while (!text_cursor.selectedText().contains(q_ptr->ps1()))
+      {
+      text_cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
+      }
+
+    // search through the text the multiline statement symbol "... " + 1 char (go to the line char)
     QString commandText = text_cursor.selectedText();
+    int pos_Ps2 = commandText.indexOf(q_ptr->ps2())-1;
+    while (pos_Ps2 > -1)
+      {
+      // remove the multiline symbol + the previous character due to "enter"
+      int number_deleted_char=q_ptr->ps2().size()+1;
+      // remove the line continuation character '\' if it's here
+      if (commandText.at(pos_Ps2-1) == QChar('\\') )
+        {
+        pos_Ps2--;
+        number_deleted_char++;
+        }
+      commandText.remove(pos_Ps2,number_deleted_char);
+      pos_Ps2 = commandText.indexOf(q_ptr->ps2())-1;
+      }
+    commandText.remove(q_ptr->ps1());
+    commandText.remove('\r'); // not recongnize by python
+    //commandText.replace(QRegExp("\\s*$"), ""); // Remove trailing spaces ---- DOESN'T WORK ----
+
+    // Remove useless spaces
+    bool betweenSingleQuotes = false;
+    bool betweenDoubleQuotes = false;
+    if (commandText.contains(" "))
+      {
+      // For each char
+      for (int i=0; i<commandText.size();i++)
+        {
+        // Verify if you are between betweenDoubleQuotes:" " or betweenSingleQuotes:' '
+        QChar c = commandText.at(i);
+        if (c == '\'' && !betweenDoubleQuotes)
+          {
+          betweenSingleQuotes = !betweenSingleQuotes;
+          }
+        if (c == '"' && !betweenSingleQuotes)
+          {
+          betweenDoubleQuotes = !betweenDoubleQuotes;
+          }
+        // If we are not between quote: Erase spaces
+        if (!betweenSingleQuotes && !betweenDoubleQuotes)
+          {
+          if (c == ' ')
+            {
+            commandText.remove(i,1);
+            i--; // because we removed a char
+            }
+          }
+        }
+      }
+
+    // Save current positions: Since some implementation of
+    // updateCompletionModel (e.g python) can display messages
+    // while building the completion model, it is important to save
+    // and restore the positions.
+    int savedInteractivePosition = this->InteractivePosition;
+    int savedCursorPosition = this->textCursor().position();
+
+    //move the cursor at the end in case of a message displayed
+    QTextCursor tc = this->textCursor();
+    tc.setPosition(this->documentEnd());
+    this->setTextCursor(tc);
+    // Save color of displayed message
+    QColor savedOutputTextColor = this->OutputTextColor;
+    QColor savedErrorTextColor = this->ErrorTextColor;
+    // Change color of displayed message in message_output_area
+    this->OutputTextColor = this->MessageOutputColor;
+    this->ErrorTextColor = this->MessageOutputColor;
 
     // Call the completer to update the completion model
     this->Completer->updateCompletionModel(commandText);
 
+    // Restore Color
+    this->OutputTextColor = savedOutputTextColor;
+    this->ErrorTextColor = savedErrorTextColor;
+
+    // Restore positions
+    this->InteractivePosition = savedInteractivePosition;
+    QTextCursor textCursor = this->textCursor();
+    textCursor.setPosition(savedCursorPosition);
+    this->setTextCursor(textCursor);
+
+
     // Place and show the completer if there are available completions
     if (this->Completer->completionCount())
       {
@@ -565,9 +773,15 @@ void ctkConsolePrivate::updateCompleter()
 }
 
 //-----------------------------------------------------------------------------
-void ctkConsolePrivate::updateCommandBuffer()
+void ctkConsolePrivate::updateCommandBuffer(int commandLength)
 {
-  this->commandBuffer() = this->toPlainText().mid(this->InteractivePosition);
+  if (commandLength == -1)
+    {
+    commandLength =
+        this->commandEnd() - this->InteractivePosition;
+    }
+  this->commandBuffer() =
+      this->toPlainText().mid(this->InteractivePosition, commandLength);
 }
 
 //-----------------------------------------------------------------------------
@@ -579,6 +793,8 @@ void ctkConsolePrivate::replaceCommandBuffer(const QString& text)
   c.setPosition(this->InteractivePosition);
   c.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
   c.removeSelectedText();
+  // all text removed, we need then to re-init our tracker on the message output area
+  this->MessageOutputSize = 0;
   this->switchToUserInputTextColor(&c);
   c.insertText(text);
 }
@@ -631,6 +847,7 @@ void ctkConsolePrivate::internalExecuteCommand()
       }
     }
   this->promptForInput(indent);
+  this->MessageOutputSize = 0;
 }
 
 //-----------------------------------------------------------------------------
@@ -665,16 +882,26 @@ void ctkConsolePrivate::printString(const QString& text)
 void ctkConsolePrivate::printOutputMessage(const QString& text)
 {
   Q_Q(ctkConsole);
-
-  q->printMessage(text, q->outputTextColor());
+  QString textToPrint = text;
+  if (this->MessageOutputSize == 0)
+    {
+    textToPrint.prepend("\n");
+    }
+  this->MessageOutputSize += textToPrint.size();
+  q->printMessage(textToPrint, q->outputTextColor());
 }
 
 //----------------------------------------------------------------------------
 void ctkConsolePrivate::printErrorMessage(const QString& text)
 {
   Q_Q(ctkConsole);
-
-  q->printMessage(text, q->errorTextColor());
+  QString textToPrint = text;
+  if (this->MessageOutputSize == 0)
+    {
+    textToPrint.prepend("\n");
+    }
+  this->MessageOutputSize += textToPrint.size();
+  q->printMessage(textToPrint, q->errorTextColor());
 }
 
 //-----------------------------------------------------------------------------
@@ -696,6 +923,7 @@ void ctkConsolePrivate::promptForInput(const QString& indent)
   if(!this->MultilineStatement)
     {
     this->prompt(q->ps1());
+    this->MessageOutputSize=0;
     }
   else
     {
@@ -739,6 +967,10 @@ void ctkConsolePrivate::insertCompletion(const QString& completion)
 {
   Q_Q(ctkConsole);
   QTextCursor tc = this->textCursor();
+  // save the initial cursor position
+  QTextCursor endOfCompletion = this->textCursor();
+  endOfCompletion.setPosition(tc.position());
+  // Select the previous charactere
   tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
   if (tc.selectedText()==".")
     {
@@ -746,18 +978,35 @@ void ctkConsolePrivate::insertCompletion(const QString& completion)
     }
   else
     {
-    tc = this->textCursor();
+    //can't more autocomplete when cursor right after '(' or ')'
+    if (tc.selectedText()==")" || tc.selectedText()=="(")
+      {
+      return;
+      }
+    tc.clearSelection();
     tc.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
-    tc.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
+    if (insertCompletionMethod)
+      {
+      tc.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
+      }
+    else
+      {
+      tc.setPosition(endOfCompletion.position(), QTextCursor::KeepAnchor);
+      }
     tc.insertText(completion);
+    endOfCompletion.setPosition(tc.position());
     this->setTextCursor(tc);
     }
-  tc.movePosition(QTextCursor::StartOfBlock);
-  tc.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
+
+  // Get back the whole command line to apply a cursor offset
+  // (moving cursor between parenthsesis if the completion is
+  // a callable object with more than the self argument)
+  // StartOfBlock don't catch the whole command line if multi-line statement
+  tc.movePosition(QTextCursor::StartOfBlock,QTextCursor::KeepAnchor);
   QString shellLine = tc.selectedText();
   shellLine.replace(q->ps1(), "");
   shellLine.replace(q->ps2(), "");
-  tc.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor);
+  tc.setPosition(endOfCompletion.position());
   this->setTextCursor(tc);
   int cursorOffset = this->Completer->cursorOffset(shellLine);
   tc.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, cursorOffset);
@@ -786,6 +1035,13 @@ bool ctkConsolePrivate::isCursorInHistoryArea()const
 }
 
 //-----------------------------------------------------------------------------
+bool ctkConsolePrivate::isCursorInMessageOutputArea()const
+{
+  return this->textCursor().anchor() > this->commandEnd()
+    || this->textCursor().position() > this->commandEnd();
+}
+
+//-----------------------------------------------------------------------------
 void ctkConsolePrivate::insertFromMimeData(const QMimeData* source)
 {
   if (this->isCursorInHistoryArea())
@@ -813,6 +1069,12 @@ void ctkConsolePrivate::pasteText(const QString& text)
     return;
     }
   QTextCursor textCursor = this->textCursor();
+
+  // if there is anything else after the cursor position
+  // we have to remove it and paste it in the last line
+  textCursor.setPosition(this->commandEnd(), QTextCursor::KeepAnchor);
+  QString endOfCommand = textCursor.selectedText();
+  textCursor.removeSelectedText();
   if (this->EditorHints & ctkConsole::SplitCopiedTextByLine)
     {
     QStringList lines = text.split(QRegExp("(?:\r\n|\r|\n)"));
@@ -821,10 +1083,20 @@ void ctkConsolePrivate::pasteText(const QString& text)
       this->switchToUserInputTextColor(&textCursor);
       textCursor.insertText(lines.at(i));
       this->updateCommandBuffer();
+      // if it's not the last line to paste
       if (i < lines.count() - 1)
         {
+        // be sure to go to the end of document
+        // warn about wrong paste if there is something in the message_output_area
+        textCursor.setPosition(this->documentEnd());
+        this->setTextCursor(textCursor);
+
         this->internalExecuteCommand();
         }
+      else
+        {
+        textCursor.insertText(endOfCommand);
+        }
       }
     }
   else
@@ -962,6 +1234,38 @@ CTK_GET_CPP(ctkConsole, ctkConsole::EditorHints, editorHints, EditorHints);
 CTK_SET_CPP(ctkConsole, const ctkConsole::EditorHints&, setEditorHints, EditorHints);
 
 //-----------------------------------------------------------------------------
+int ctkConsole::cursorPosition() const
+{
+  Q_D(const ctkConsole);
+  return d->textCursor().position();
+}
+
+//-----------------------------------------------------------------------------
+int ctkConsole::cursorColumn() const
+{
+  Q_D(const ctkConsole);
+  QTextCursor cursor = d->textCursor();
+  cursor.movePosition(QTextCursor::StartOfLine);
+  return d->textCursor().position() - cursor.position();
+}
+
+//-----------------------------------------------------------------------------
+int ctkConsole::cursorLine() const
+{
+  Q_D(const ctkConsole);
+  QTextCursor cursor = d->textCursor();
+  cursor.movePosition(QTextCursor::StartOfLine);
+  int lines = 1;
+  QTextBlock block = cursor.block().previous();
+  while(block.isValid())
+    {
+    lines += block.lineCount();
+    block = block.previous();
+    }
+  return lines;
+}
+
+//-----------------------------------------------------------------------------
 Qt::ScrollBarPolicy ctkConsole::scrollBarPolicy()const
 {
   Q_D(const ctkConsole);

+ 19 - 0
Libs/Widgets/ctkConsole.h

@@ -76,6 +76,9 @@ class CTK_WIDGETS_EXPORT ctkConsole : public QWidget
   Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor)
   Q_PROPERTY(QString ps1 READ ps1 WRITE setPs1)
   Q_PROPERTY(QString ps2 READ ps2 WRITE setPs2)
+  Q_PROPERTY(int cursorPosition READ cursorPosition)
+  Q_PROPERTY(int cursorColumn READ cursorColumn)
+  Q_PROPERTY(int cursorLine READ cursorLine)
   Q_FLAGS(EditorHint EditorHints)
   Q_PROPERTY(EditorHints editorHints READ editorHints WRITE setEditorHints)
   Q_ENUMS(Qt::ScrollBarPolicy)
@@ -184,6 +187,18 @@ public:
   /// Set the string used as secondary prompt
   virtual void setPs2(const QString& newPs2);
 
+  /// Returns the absolute position of the cursor within the console.
+  /// \sa cursorPositionChanged(int)
+  int cursorPosition() const;
+
+  /// Returns the column of the cursor within the console.
+  /// \sa cursorPositionChanged(int)
+  int cursorColumn() const;
+
+  /// Returns the line of the cursor within the console.
+  /// \sa cursorPositionChanged(int)
+  int cursorLine() const;
+
   static QString stdInRedirectCallBack(void * callData);
 
   /// Get the list of shortcuts that trigger the completion options.
@@ -213,6 +228,10 @@ Q_SIGNALS:
   void aboutToExecute(const QString&);
   void executed(const QString&);
 
+  /// This signal is emitted whenever the position of the cursor changed.
+  /// \sa cursorPosition()
+  void cursorPositionChanged();
+
 public Q_SLOTS:
 
   /// Clears the contents of the console

+ 30 - 5
Libs/Widgets/ctkConsole_p.h

@@ -48,6 +48,8 @@ public:
 
   static bool isMoveLeftWithinLine(QKeyEvent* e, QTextCursor::MoveOperation &moveOperation, QTextCursor::MoveMode &moveMode);
 
+  static bool isMoveRighttWithinLine(QKeyEvent* e, QTextCursor::MoveOperation &moveOperation, QTextCursor::MoveMode &moveMode);
+
   virtual void keyPressEvent(QKeyEvent* e);
 
   void switchToUserInputTextColor(QTextCursor* textCursorToUpdate = 0);
@@ -55,6 +57,9 @@ public:
   /// Returns the end of the document
   int documentEnd() const;
 
+  /// Returns the end of the commandLine
+  int commandEnd() const;
+
   virtual void focusOutEvent(QFocusEvent *e);
 
   virtual void resizeEvent(QResizeEvent * e);
@@ -72,8 +77,10 @@ public:
 
   void updateCompleter();
   
-  /// Update the contents of the command buffer from the contents of the widget
-  void updateCommandBuffer();
+  /// Update the contents of the command buffer from the contents of the widget.
+  /// If \a commandLength is specified, buffer is updated limiting the content
+  /// of the widget.
+  void updateCommandBuffer(int commandLength = -1);
   
   /// Replace the contents of the command buffer, updating the display
   void replaceCommandBuffer(const QString& text);
@@ -109,9 +116,11 @@ public:
 
 public Q_SLOTS:
 
-  /// Inserts the given completion string at the cursor.  This will replace
-  /// the current word that the cursor is touching with the given text.
-  /// Determines the word using QTextCursor::StartOfWord, EndOfWord.
+  /// Inserts the given completion string at the cursor.
+  /// 2 Different ways of completion are established by \sa ctkConsolePrivate::insertCompletionMethod:
+  ///  TRUE  - Replace the current word that the cursor is touching with the given text.
+  ///          Determines the word using QTextCursor::StartOfWord, EndOfWord.
+  ///  FALSE - Just insert the word replacing only the text from the current position until StartOfWord
   void insertCompletion(const QString& text);
 
   /// Print a message
@@ -134,6 +143,10 @@ protected:
   /// false if it is after the InteractivePosition.
   bool isCursorInHistoryArea()const;
 
+  /// Return true if the cursor position is in the message output area
+  /// false if it is before the end of the commandLine.
+  bool isCursorInMessageOutputArea()const;
+
   /// Reimplemented to make sure there is no text added into the
   /// history logs.
   virtual void insertFromMimeData(const QMimeData* source);
@@ -149,6 +162,10 @@ public:
   /// changes can't be made to the text edit contents
   int InteractivePosition;
 
+  /// Stores the size of the message output area from the end of document
+  /// until the end of the command
+  int MessageOutputSize;
+
   /// Indicates if the last statement processes was incomplete.
   bool MultilineStatement;
 
@@ -164,6 +181,9 @@ public:
   /// Output text color
   QColor OutputTextColor;
 
+  /// Message Output text color (every message displayed during autocompletion)
+  QColor MessageOutputColor;
+
   /// Error text color
   QColor ErrorTextColor;
 
@@ -182,6 +202,11 @@ public:
   /// Secondary prompt
   QString Ps2;
 
+  /// Method to insert the completion word:
+  ///   TRUE  - Replace the whole word under the cursor
+  ///   FALSE - Insert the word and replace only from the cursor until the StartOfWord
+  bool insertCompletionMethod;
+
   ctkConsole::EditorHints EditorHints;
 
   bool ScrollbarAtBottom;