]> SALOME platform Git repositories - modules/gui.git/commitdiff
Salome HOME
PyConsole: new class PyConsole_EnhConsole offering autocompletion.
authorbruneton <bruneton>
Thu, 4 Apr 2013 10:46:11 +0000 (10:46 +0000)
committerbruneton <bruneton>
Thu, 4 Apr 2013 10:46:11 +0000 (10:46 +0000)
A new interpreter PyConsole_EnhInterp has also been created for this purpose and the LightApp_PyInterp and SalomeApp_PyInterp have been tweaked accordingly.

20 files changed:
src/LightApp/LightApp_Application.cxx
src/LightApp/LightApp_PyInterp.cxx
src/LightApp/LightApp_PyInterp.h
src/PyConsole/CMakeLists.txt
src/PyConsole/PyConsole_Console.cxx
src/PyConsole/PyConsole_Console.h
src/PyConsole/PyConsole_EnhEditor.cxx [new file with mode: 0644]
src/PyConsole/PyConsole_EnhEditor.h [new file with mode: 0644]
src/PyConsole/PyConsole_EnhInterp.cxx [new file with mode: 0644]
src/PyConsole/PyConsole_EnhInterp.h [new file with mode: 0644]
src/PyConsole/PyConsole_Event.h
src/PyConsole/PyConsole_Request.cxx
src/PyConsole/PyConsole_Request.h
src/PyInterp/PyInterp_Dispatcher.cxx
src/PyInterp/PyInterp_Event.h
src/PyInterp/PyInterp_Request.cxx
src/PyInterp/PyInterp_Request.h
src/SalomeApp/SalomeApp_Application.cxx
src/SalomeApp/SalomeApp_PyInterp.cxx
src/SalomeApp/SalomeApp_PyInterp.h

index 9142c8da6e1802f267d8b2f98137e2f57ad0d656..8dd648f27aa78e305caa0e07ca06acdecb3d2d1f 100644 (file)
@@ -1839,7 +1839,7 @@ QWidget* LightApp_Application::createWindow( const int flag )
 #ifndef DISABLE_PYCONSOLE
   else  if ( flag == WT_PyConsole )
   {
-    PyConsole_Console* pyCons = new PyConsole_Console( desktop(),new LightApp_PyInterp());
+    PyConsole_Console* pyCons = new PyConsole_EnhConsole( desktop(),new LightApp_PyInterp());
     pyCons->setWindowTitle( tr( "PYTHON_CONSOLE" ) );
     pyCons->setFont(resourceMgr()->fontValue( "PyConsole", "font" ));
     pyCons->setIsShowBanner(resourceMgr()->booleanValue( "PyConsole", "show_banner", true ));
index d6179eca30248175b63defeb5a8accad1585bc5a..5bc90b951807659edf253527aeb71eab865a73c1 100644 (file)
@@ -32,7 +32,7 @@
  * calls initialize method defined in base class, which calls virtual methods
  * initstate & initcontext redefined here.
  */
-LightApp_PyInterp::LightApp_PyInterp(): PyConsole_Interp()
+LightApp_PyInterp::LightApp_PyInterp(): PyConsole_EnhInterp()
 {
 }
 
index 22fdb87f0d0b8ca6a6f5e8855f64d03c17ff8a9a..d13d6bd85ccc04c0410510f74d66347e9b243692 100644 (file)
@@ -23,9 +23,9 @@
 #ifndef _LIGHTAPP_PYINTERP_H_
 #define _LIGHTAPP_PYINTERP_H_
 
-#include <PyConsole_Interp.h> // this include must be first (see PyInterp_base.h)!
+#include <PyConsole_EnhInterp.h> // this include must be first (see PyInterp_base.h)!
 
-class LightApp_PyInterp : public PyConsole_Interp
+class LightApp_PyInterp : public PyConsole_EnhInterp
 {
 public:
   LightApp_PyInterp();
index 48a250f6354239d32c5816184b066f0d36695c16..00d0f49ca7047993fc9059bcae5a72ab285a19f7 100755 (executable)
@@ -34,6 +34,7 @@ SET(COMMON_LIBS ${PYTHON_LIBRARIES} ${QT_LIBRARIES} ${SALOMELocalTrace} qtx suit
 
 SET(GUI_HEADERS   
   PyConsole_Editor.h
+  PyConsole_EnhEditor.h
   PyConsole_Console.h
 )
 QT4_WRAP_CPP(GUI_HEADERS_MOC ${GUI_HEADERS})
@@ -41,7 +42,9 @@ QT4_WRAP_CPP(GUI_HEADERS_MOC ${GUI_HEADERS})
 SET(PyConsole_SOURCES
   PyConsole_Console.cxx
   PyConsole_Editor.cxx
+  PyConsole_EnhEditor.cxx
   PyConsole_Interp.cxx
+  PyConsole_EnhInterp.cxx
   PyConsole_Event.cxx
   PyConsole_Request.cxx
 )
@@ -61,7 +64,9 @@ SET(COMMON_HEADERS_H
   PyConsole.h
   PyConsole_Console.h
   PyConsole_Editor.h
+  PyConsole_EnhEditor.h
   PyConsole_Interp.h
+  PyConsole_EnhInterp.h
   PyConsole_Event.h
   PyConsole_Request.h
 )
index 8fbd17296af79e3396940da98bb5728a37f5c8b9..58944c956c3369fec22d5bbb6ab8e4d836cd865a 100644 (file)
@@ -30,7 +30,7 @@
 
 #include "PyConsole_Interp.h"   /// !!! WARNING !!! THIS INCLUDE MUST BE VERY FIRST !!!
 #include "PyConsole_Console.h"
-#include "PyConsole_Editor.h"
+#include "PyConsole_EnhEditor.h"
 
 #include <Qtx.h>
 
@@ -49,8 +49,7 @@
   \param interp python interpreter
 */
 PyConsole_Console::PyConsole_Console( QWidget* parent, PyConsole_Interp* interp )
-: QWidget( parent ),
-  myEditor( 0 )
+: QWidget( parent )
 {
   // create python interpreter
   myInterp = interp;
@@ -75,6 +74,13 @@ PyConsole_Console::PyConsole_Console( QWidget* parent, PyConsole_Interp* interp
   createActions();
 }
 
+/**
+ * Protected constructor.
+ */
+PyConsole_Console::PyConsole_Console( QWidget* parent, PyConsole_Interp* i,  PyConsole_Editor* e)
+  : QWidget (parent), myEditor(e), myInterp(i)
+{}
+
 /*!
   \brief Destructor.
 
@@ -324,3 +330,35 @@ void PyConsole_Console::updateActions()
   myActions[PasteId]->setEnabled( !myEditor->isReadOnly() && !QApplication::clipboard()->text().isEmpty() );
   myActions[SelectAllId]->setEnabled( !myEditor->document()->isEmpty() );
 }
+
+/**
+ * Similar to constructor of the base class but using enhanced objects.
+ * TODO: this should really be done in a factory to avoid code duplication.
+ * @param parent
+ * @param interp
+ */
+PyConsole_EnhConsole::PyConsole_EnhConsole( QWidget* parent, PyConsole_EnhInterp* interp)
+  : PyConsole_Console(parent, interp, 0)
+{
+  // create python interpreter
+  myInterp = interp;
+  if ( !myInterp )
+    myInterp = new PyConsole_EnhInterp();
+
+  // initialize Python interpretator
+  myInterp->initialize();
+
+  // create editor console
+  QVBoxLayout* lay = new QVBoxLayout( this );
+  lay->setMargin( 0 );
+  myEditor = new PyConsole_EnhEditor( static_cast<PyConsole_EnhInterp*>(myInterp), this );
+  char* synchronous = getenv("PYTHON_CONSOLE_SYNC");
+  if (synchronous && atoi(synchronous))
+  {
+      myEditor->setIsSync(true);
+  }
+  myEditor->viewport()->installEventFilter( this );
+  lay->addWidget( myEditor );
+
+  createActions();
+}
index 6258ee97f1eff3d1019d208a47e98057a4e4836c..9da256727ad6b6662977da4d7172d5c3d4195e49 100644 (file)
@@ -28,6 +28,7 @@
 
 #include "PyConsole.h"
 
+#include "PyConsole_EnhInterp.h"
 #include <SUIT_PopupClient.h>
 #include <QWidget>
 #include <QMap>
@@ -81,14 +82,30 @@ public:
   void                setMenuActions( const int );
   int                 menuActions() const;
 
-private:
+protected:
   void                createActions();
   void                updateActions();
 
-private:
+  PyConsole_Console( QWidget* parent, PyConsole_Interp*,  PyConsole_Editor*);
+
+
   PyConsole_Interp*   myInterp;    //!< python interpreter
   PyConsole_Editor*   myEditor;    //!< python console editor widget
   QMap<int, QAction*> myActions;   //!< menu actions list
 };
 
+/**
+ * Enhance console object providing auto-completion.
+ * Similar to PyConsole_Console except that an enhanced interpreter and enhanced editor
+ * are encapsulated.
+ */
+class PYCONSOLE_EXPORT PyConsole_EnhConsole: public PyConsole_Console
+{
+  Q_OBJECT
+
+public:
+  PyConsole_EnhConsole( QWidget* parent, PyConsole_EnhInterp* interp = 0);
+  virtual ~PyConsole_EnhConsole() {}
+};
+
 #endif // PYCONSOLE_CONSOLE_H
diff --git a/src/PyConsole/PyConsole_EnhEditor.cxx b/src/PyConsole/PyConsole_EnhEditor.cxx
new file mode 100644 (file)
index 0000000..aa73d15
--- /dev/null
@@ -0,0 +1,349 @@
+// Copyright (C) 2007-2013  CEA/DEN, EDF R&D
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// Author : Adrien Bruneton (CEA/DEN)
+// Created on: 4 avr. 2013
+
+#include "PyConsole.h"
+#include <Python.h>
+
+#include <QKeyEvent>
+#include <QTextBlock>
+#include <QTextCursor>
+#include <QTextCharFormat>
+#include <QRegExp>
+
+#include "PyConsole_EnhEditor.h"
+#include "PyConsole_EnhInterp.h"
+#include "PyConsole_Request.h"
+#include "PyInterp_Dispatcher.h"
+
+// Initialize list of valid separators
+static const char * tmp_a[] = {" ", "(", "[","+", "-", "*", "/", ";", "^", "="};
+const std::vector<QString> PyConsole_EnhEditor::SEPARATORS = \
+    std::vector<QString>(tmp_a, tmp_a + sizeof(tmp_a)/sizeof(tmp_a[0]));
+
+PyConsole_EnhEditor::PyConsole_EnhEditor(PyConsole_EnhInterp * interp, QWidget * parent) :
+     PyConsole_Editor(interp, parent),
+     _tab_mode(false),
+     _cursor_pos(-1)
+{
+  document()->setUndoRedoEnabled(true);
+}
+
+void PyConsole_EnhEditor::keyPressEvent ( QKeyEvent* event)
+{
+  // check if <Ctrl> is pressed
+  bool ctrlPressed = event->modifiers() & Qt::ControlModifier;
+  // check if <Shift> is pressed
+  bool shftPressed = event->modifiers() & Qt::ShiftModifier;
+
+  if (event->key() == Qt::Key_Tab && !shftPressed)
+    {
+      if (!ctrlPressed)
+        handleTab();
+      else
+        {
+          clearCompletion();
+          handleBackTab();
+        }
+      PyConsole_Editor::keyPressEvent(event);
+    }
+  else
+    {
+      if (!ctrlPressed)
+        {
+          clearCompletion();
+          _cursor_pos = -1;
+        }
+      PyConsole_Editor::keyPressEvent(event);
+    }
+}
+
+void PyConsole_EnhEditor::mousePressEvent(QMouseEvent* e)
+{
+  clearCompletion();
+  _cursor_pos = -1;
+  PyConsole_Editor::mousePressEvent(e);
+}
+
+void PyConsole_EnhEditor::clearCompletion()
+{
+  // Delete completion text if present
+  if (_tab_mode)
+    {
+      // Remove completion display
+      document()->undo();
+      // Remove trailing line return:
+      QTextCursor tc(textCursor());
+      tc.setPosition(document()->characterCount()-1);
+      setTextCursor(tc);
+      textCursor().deletePreviousChar();
+      // TODO: before wait for any TAB event to be completed
+      static_cast<PyConsole_EnhInterp *>(myInterp)->clearCompletion();
+    }
+  _tab_mode = false;
+}
+
+void PyConsole_EnhEditor::handleTab()
+{
+  if (_tab_mode)
+    {
+      // Already tab mode - nothing to do !
+      return;
+    }
+
+  QTextCursor cursor(textCursor());
+
+  // Cursor at end of input
+  cursor.movePosition(QTextCursor::End);
+  setTextCursor(cursor);
+
+  // Save cursor position if needed
+  if (_cursor_pos == -1)
+    _cursor_pos = textCursor().position();
+
+  // get last line
+  QTextBlock par = document()->end().previous();
+  if ( !par.isValid() ) return;
+
+  // Switch to completion mode
+  _tab_mode = true;
+
+  QString cmd = par.text().mid(promptSize());
+
+  // Post completion request
+  // Editor will be informed via a custom event that completion has been run
+  PyInterp_Request* req = createTabRequest(cmd);
+  PyInterp_Dispatcher::Get()->Exec(req);
+}
+
+void PyConsole_EnhEditor::handleBackTab()
+{
+  QTextCursor cursor(textCursor());
+
+  if (_cursor_pos == -1)
+    {
+      // Invalid cursor position - we can't do anything:
+      return;
+    }
+  // Ensure cursor is at the end of command line
+  cursor.setPosition(_cursor_pos);
+  cursor.movePosition(QTextCursor::EndOfLine);
+  //setCursor(cursor);
+
+  // Delete last completed text
+  int i = cursor.position() - _cursor_pos;
+  cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, i);
+  cursor.removeSelectedText();
+  _cursor_pos = -1;
+}
+
+PyInterp_Request* PyConsole_EnhEditor::createTabRequest( const QString& input )
+{
+  // Parse input to extract on what part the dir() has to be executed
+  QString input2(input);
+
+  // Split up to the last syntaxical separator
+  int lastSp = -1;
+  for (std::vector<QString>::const_iterator i = SEPARATORS.begin(); i != SEPARATORS.end(); i++)
+    {
+      int j = input2.lastIndexOf(*i);
+      if (j > lastSp)
+        lastSp = j;
+    }
+  if (lastSp >= 0)
+    input2 = input.mid(lastSp+1);
+
+  // Detect a qualified name (with a point)
+  int lastPt = input2.lastIndexOf(QString("."));
+
+  // Split the 2 surrounding parts of the qualified name
+  if (lastPt != -1)
+    {
+      _compl_before_point = input2.left(lastPt);
+      _compl_after_point = input2.mid(lastPt+1);
+    }
+  else
+    {
+      // No point found - do a global matching -
+      // (the following will call dir() with an empty string)
+      _compl_after_point = input2;
+      _compl_before_point = QString("");
+    }
+
+  return new CompletionCommand( static_cast<PyConsole_EnhInterp *>(myInterp), _compl_before_point,
+                               _compl_after_point, this, isSync() );
+}
+
+/**
+ * Format completion results - this is where we should create 3 columns etc ...
+ * @param matches list of possible completions
+ * @param result return value
+ */
+void PyConsole_EnhEditor::formatCompletion(const std::vector<QString> & matches, QString & result) const
+{
+  int sz = matches.size();
+
+  if (sz > MAX_COMPLETIONS)
+    {
+      sz = MAX_COMPLETIONS;
+      result.append("[Too many matches! Displaying first ones only ...]\n");
+    }
+
+  for (int i = 0; i < sz; ++i)
+    {
+      result.append(matches[i]);
+      result.append("\n");
+    }
+}
+
+void PyConsole_EnhEditor::customEvent( QEvent* event )
+{
+  std::vector<QString> matches;
+  QString first_match, comple_text, doc, base;
+  QTextCursor cursor(textCursor());
+  QTextBlockFormat bf;
+  QTextCharFormat cf;
+  PyConsole_EnhInterp * interp = static_cast<PyConsole_EnhInterp *>(myInterp);
+
+  switch( event->type() )
+  {
+    case PyInterp_Event::ES_TAB_COMPLETE_OK:
+      // Extract corresponding matches from the interpreter
+      matches = interp->getLastMatches();
+
+      if (matches.size() == 0)
+        {
+          // Completion successful but nothing returned.
+          _tab_mode = false;
+          _cursor_pos = -1;
+          return;
+        }
+
+      // Only one match - complete directly and update doc string window
+      doc = interp->getDocStr();
+      if (matches.size() == 1)
+        {
+          first_match = matches[0].mid(_compl_after_point.size());
+          cursor.insertText(first_match);
+          _tab_mode = false;
+          if (doc == QString(""))
+            emit updateDoc(formatDocHTML("(no documentation available)\n"));
+          else
+            emit updateDoc(formatDocHTML(doc));
+        }
+      else
+        {
+          // Detect if there is a common base to all available completion
+          // In this case append this base to the text already
+          extractCommon(matches, base);
+          first_match = base.mid(_compl_after_point.size());
+          cursor.insertText(first_match);
+
+          // If this happens to match exaclty the first completion
+          // also provide doc
+          if (base == matches[0])
+            {
+              doc = formatDocHTML(doc);
+              emit updateDoc(doc);
+            }
+
+          // Print all matching completion in a "undo-able" block
+          cursor.insertBlock();
+          cursor.beginEditBlock();
+
+          // Insert all matches
+          QTextCharFormat cf;
+          cf.setForeground(QBrush(Qt::darkGreen));
+          cursor.setCharFormat(cf);
+          formatCompletion(matches, comple_text);
+          cursor.insertText(comple_text);
+          cursor.endEditBlock();
+        }
+      break;
+    case PyInterp_Event::ES_TAB_COMPLETE_ERR:
+      // Tab completion was unsuccessful, switch off mode:
+      _tab_mode = false;
+      _cursor_pos = -1;
+      break;
+//    case PyEnhInterp_Event::ES_MULTI_PASTE:
+//      multilinePaste();
+//      break;
+    default:
+      PyConsole_Editor::customEvent( event );
+      break;
+  }
+}
+
+/**
+ * Extract the common leading part of all strings in matches.
+ * @param matches
+ * @param result
+ */
+void PyConsole_EnhEditor::extractCommon(const std::vector<QString> & matches, QString & result) const
+{
+  result = "";
+  int charIdx = 0;
+
+  if (matches.size() < 2)
+    return;
+
+  while (true)
+    {
+      if (charIdx >= matches[0].size())
+        return;
+      QChar ch = matches[0][charIdx];
+      for (int j = 1; j < matches.size(); j++)
+        if (charIdx >= matches[j].size() || matches[j][charIdx] != ch)
+          return;
+      result += ch;
+      charIdx++;
+    }
+}
+
+QString PyConsole_EnhEditor::formatDocHTML(const QString & doc) const
+{
+  QString templ = QString("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" ") +
+      QString(" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n ") +
+      QString("<html><head><meta name=\"qrichtext\" content=\"1\" /> ") +
+      QString("<style type=\"text/css\">\np, li { white-space: pre-wrap; }\n</style> ") +
+      QString("</head><body style=\" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;\">\n") +
+      QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">") +
+      QString("<span style=\" font-weight:600; color:#0000ff;\">%1</span></p>") +
+      QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%2</p>") +
+      QString("</body></html>");
+
+  QString fst, rest("");
+
+  // Extract first line of doc
+  int idx = doc.indexOf("\n");
+  if (idx > 0)
+    {
+      fst = doc.left(idx);
+      rest = doc.mid(idx+1);
+    }
+  else
+    {
+      fst = doc;
+    }
+
+  fst = fst.replace("\n", " ");
+  rest = rest.replace("\n", " ");
+  return templ.arg(fst).arg(rest);
+}
diff --git a/src/PyConsole/PyConsole_EnhEditor.h b/src/PyConsole/PyConsole_EnhEditor.h
new file mode 100644 (file)
index 0000000..8a9bc28
--- /dev/null
@@ -0,0 +1,90 @@
+// Copyright (C) 2007-2013  CEA/DEN, EDF R&D
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// Author : Adrien Bruneton (CEA/DEN)
+// Created on: 4 avr. 2013
+
+#ifndef PYCONSOLE_ENHEDITOR_H_
+#define PYCONSOLE_ENHEDITOR_H_
+
+#include "PyConsole.h"
+
+#include "PyConsole_Editor.h"
+#include <QObject>
+
+class PyConsole_EnhInterp;
+
+/**
+ * Enhanced Python editor handling tab completion.
+ */
+class PyConsole_EnhEditor: public PyConsole_Editor
+{
+  Q_OBJECT;
+
+public:
+  PyConsole_EnhEditor(PyConsole_EnhInterp * interp, QWidget * parent=0);
+  virtual ~PyConsole_EnhEditor() {}
+
+signals:
+  /**
+   * Signal emitted by the editor widget when the doc string should be updated.
+   * @param doc a HTML block with the formatted doc string.
+   * TODO: for now this signal is left uncaught.
+   */
+  void updateDoc(QString doc);
+
+protected:
+  /** List of separators identifying the last parsable token for completion */
+  static const std::vector<QString> SEPARATORS;
+
+  /** Maximum number of completions shown at once */
+  static const int MAX_COMPLETIONS = 70;
+
+  /** Are we in completion mode */
+  bool _tab_mode;
+
+  /** String on which the dir() comamnd is executed */
+  QString _compl_before_point;
+  /** String on which the results of the dir() are matched */
+  QString _compl_after_point;
+
+  /** Cursor position when <TAB> is hit */
+  int _cursor_pos;
+
+//  std::stack<QString> _multi_line_content;
+
+  // Overrides:
+  virtual void   keyPressEvent ( QKeyEvent* event);
+  virtual void   customEvent( QEvent* event);
+  virtual void   mousePressEvent( QMouseEvent* event );
+//  virtual void   insertFromMimeData(const QMimeData * source);
+
+  virtual PyInterp_Request* createTabRequest( const QString& input );
+  virtual void handleTab();
+  virtual void handleBackTab();
+  virtual void clearCompletion();
+  virtual void formatCompletion(const std::vector<QString> & matches, QString & result) const;
+  virtual QString formatDocHTML(const QString & doc) const;
+//  virtual void multilinePaste();
+
+private:
+  void extractCommon(const std::vector<QString> & matches, QString & result) const;
+
+};
+
+#endif /* PYCONSOLE_ENHEDITOR_H_ */
diff --git a/src/PyConsole/PyConsole_EnhInterp.cxx b/src/PyConsole/PyConsole_EnhInterp.cxx
new file mode 100644 (file)
index 0000000..c648086
--- /dev/null
@@ -0,0 +1,154 @@
+// Copyright (C) 2007-2013  CEA/DEN, EDF R&D
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// Author : Adrien Bruneton (CEA/DEN)
+// Created on: 4 avr. 2013
+
+
+#include "PyConsole.h"
+
+#include "PyConsole_EnhInterp.h"
+
+#include <pythonrun.h>
+#include <string>
+#include <QRegExp>
+
+static const char * tmp_k[] = {"and",  "as", "assert", "break",  "class",
+    "continue", "def",  "del",
+    "elif", "else", "except", "exec", "finally",  "for",  "from", "global", "if",
+    "import", "in", "is", "lambda", "not",  "or", "pass", "print",  "raise",
+    "return", "try",  "while",  "with", "yield"};
+
+const std::vector<QString> PyConsole_EnhInterp::PYTHON_KEYWORDS = \
+      std::vector<QString>(tmp_k, tmp_k+sizeof(tmp_k)/sizeof(tmp_k[0]));
+
+/*!
+  \brief Run Python dir() command and saves the result internally in _lastPy
+  \param dirArgument Python expression to pass to the dir command. The parsing of what the
+  user actually started typing is dedicated to the caller
+  \param startMatch string representing the begining of the patter to be completed. For example when
+  the user types "a_string_variable.rsp <TAB>", this is "rsp".
+  \return command exit status - 0 = success
+*/
+int PyConsole_EnhInterp::runDirCommand(const QString & dirArgument, const QString & startMatch)
+{
+  int ret;
+  std::vector<QString> v;
+
+  clearCompletion();
+  if ( (ret = runDirAndExtract(dirArgument, startMatch, _last_matches)) )
+      return ret;
+
+  // If dirArgument is empty, we append the __builtins__
+  if (dirArgument.isEmpty())
+    {
+      if ( (ret = runDirAndExtract(QString("__builtins__"), startMatch, _last_matches, false)) )
+            return ret;
+
+      // ... and we match on Python's keywords as well:
+      for (std::vector<QString>::const_iterator it = PYTHON_KEYWORDS.begin(); it != PYTHON_KEYWORDS.end(); it++)
+          if ((*it).startsWith(startMatch))
+            _last_matches.push_back(*it);
+    }
+
+  // Try to get doc string of the first match
+  if (_last_matches.size() > 0)
+    {
+      QString cmd("");
+      if (dirArgument.trimmed() != "")
+        cmd = dirArgument + ".";
+      cmd += _last_matches[0] + ".__doc__";
+      PyObject * str = PyRun_String(cmd.toStdString().c_str(), Py_eval_input, _g, _g);
+      if (!str || str == Py_None || !PyString_Check(str))
+        {
+          if (!str)
+            PyErr_Clear();
+          _doc_str = "";
+        }
+      else
+        _doc_str = QString(PyString_AsString(str));
+      Py_XDECREF(str);
+    }
+
+  // The command has been successfully executed
+  return 0;
+}
+
+/**
+ * See runDirCommand().
+ * @param dirArgument see runDirCommand()
+ * @param startMatch  see runDirCommand()
+ * @param[out] result the list of matches
+ * @param discardSwig if true a regular expression is used to discard all static method generated
+ * by SWIG. typically: MEDCouplingUMesh_Blabla
+ * @return -1 if the call to dir() or the parsing of the result failed, 0 otherwise.
+ */
+int PyConsole_EnhInterp::runDirAndExtract(const QString& dirArgument,
+       const QString & startMatch, std::vector<QString> & result,
+       bool discardSwig) const
+{
+  QRegExp re("^[A-Z].+_[A-Z]+[a-z]+.+$");  // discard SWIG static method, e.g. MEDCouplingUMesh_Blabla
+  QString command("dir(" + dirArgument + ")");
+  PyObject * plst = PyRun_String(command.toStdString().c_str(), Py_eval_input, _g, _g);
+  if(!plst || plst == Py_None) {
+    if(!plst)
+      PyErr_Clear();
+
+    Py_XDECREF(plst);
+    return -1;
+  }
+
+  // Extract the returned list and convert it to a vector<>
+  if (!PySequence_Check(plst))
+    {
+      // Should never happen ...
+      //std::cerr << "not a list!" << std::endl;
+      Py_XDECREF(plst);
+      return -1;
+    }
+
+  // Convert plst to a vector of QString
+  int n = PySequence_Length(plst);
+  for (int i = 0; i < n; i++)
+    {
+      PyObject * it;
+      it = PySequence_GetItem(plst, i);
+      QString s(PyString_AsString(it));
+      // if the method is not from swig, not static (guessed from the reg exp) and matches
+      // what is already there
+      if (s.startsWith(startMatch))
+        if(!discardSwig || (!re.exactMatch(s) && !s.contains("swig")))
+          result.push_back(s);
+      Py_DECREF(it);
+    }
+  Py_DECREF(plst);
+
+  return 0;
+}
+
+/**
+ * Clear internal members containing the last completion results.
+ */
+void PyConsole_EnhInterp::clearCompletion()
+{
+  _last_matches.resize(0);
+  _doc_str = QString("");
+}
+
+
+
diff --git a/src/PyConsole/PyConsole_EnhInterp.h b/src/PyConsole/PyConsole_EnhInterp.h
new file mode 100644 (file)
index 0000000..5e19ca5
--- /dev/null
@@ -0,0 +1,67 @@
+// Copyright (C) 2007-2013  CEA/DEN, EDF R&D
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// Author : Adrien Bruneton (CEA/DEN)
+// Created on: 4 avr. 2013
+
+
+#ifndef PYCONSOLE_ENHINTERP_H_
+#define PYCONSOLE_ENHINTERP_H_
+
+#include "PyConsole.h"
+
+#include <Python.h>
+#include "PyConsole_Interp.h"
+
+#include <vector>
+#include <QString>
+
+/**
+ * Enhanced Python interpreter used for auto-completion.
+ * This extends PyConsole_Interp with an API wrapping the Python dir() command nicely.
+ */
+class PyConsole_EnhInterp: public PyConsole_Interp
+{
+public:
+  PyConsole_EnhInterp()
+    : PyConsole_Interp(), _last_matches(0), _doc_str("")
+    {}
+
+  virtual ~PyConsole_EnhInterp() {}
+
+  const std::vector<QString>& getLastMatches() const { return _last_matches; }
+  const QString & getDocStr() const                  { return _doc_str; }
+
+  virtual int runDirCommand(const QString& dirArgument, const QString& startMatch);
+  virtual void clearCompletion();
+
+protected:
+  /** Hard coded list of Python keywords */
+  static const std::vector<QString> PYTHON_KEYWORDS;
+
+  /** Last computed matches */
+  std::vector<QString> _last_matches;
+  /** Doc string of the first match - when only one match it will be displayed by the Editor*/
+  QString _doc_str;
+
+  virtual int runDirAndExtract(const QString& dirArgument, const QString & startMatch,
+      std::vector<QString> & result, bool discardSwig=true) const;
+
+};
+
+#endif /* PYCONSOLE_ENHINTERP_H_ */
index b99a58d58d7fa531702414aea743ca4265bb3b42..ac6a75fa2955543466840ad5a7671d54f299a970 100644 (file)
@@ -24,6 +24,8 @@
 #ifndef PYCONSOLE_EVENT_H
 #define PYCONSOLE_EVENT_H
 
+#include "PyConsole.h"
+
 #include <QEvent>
 #include <QString>
 
@@ -40,6 +42,7 @@ public:
   /*!
     \brief Constructor
     \param c message text (python trace)
+    \param isError default to false - if true indicates that an error is being printed.
   */
   PrintEvent( const char* c, bool isError = false) :
     QEvent( (QEvent::Type)EVENT_ID ), myText( c ), errorFlag(isError)
@@ -50,6 +53,10 @@ public:
     \return message text (python trace)
   */
   QString text() const { return myText; }
+
+  /**
+   * @return true if this is an error message
+   */
   bool isError() const { return errorFlag; }
 
 protected:
index 7b00958628834ecd3f4165a7f55de01dcb3106eb..4e3c134a2f6acf07f5bddd05a5ba2607be9e4343 100644 (file)
@@ -23,8 +23,8 @@
 
 #include "PyInterp_Event.h"
 #include "PyConsole_Event.h"
-#include "PyInterp_Interp.h"
-#include "PyConsole_Editor.h"
+#include "PyConsole_EnhInterp.h"
+#include "PyConsole_EnhEditor.h"
 
 #include <QCoreApplication>
 
@@ -48,9 +48,40 @@ void ExecCommand::execute()
     }
 }
 
-QEvent* ExecCommand::createEvent() const
+QEvent* ExecCommand::createEvent()
 {
   if ( IsSync() )
     QCoreApplication::sendPostedEvents( listener(), PrintEvent::EVENT_ID );
-  return new PyInterp_Event( myState, (PyInterp_Request*)this );
+  return new PyInterp_Event( myState, this );
 }
+
+
+
+CompletionCommand::CompletionCommand( PyConsole_EnhInterp*  theInterp,
+               const QString&          input,
+               const QString&         startMatch,
+               PyConsole_EnhEditor*           theListener,
+               bool                    sync)
+     : PyInterp_LockRequest( theInterp, theListener, sync ),
+       _tabSuccess(false), _dirArg(input), _startMatch(startMatch)
+{}
+
+void CompletionCommand::execute()
+{
+  PyConsole_EnhInterp * interp = static_cast<PyConsole_EnhInterp *>(getInterp());
+    int ret = interp->runDirCommand( _dirArg,  _startMatch);
+    if (ret == 0)
+      _tabSuccess = true;
+    else
+      _tabSuccess = false;
+}
+
+QEvent* CompletionCommand::createEvent()
+{
+  int typ = _tabSuccess ? PyInterp_Event::ES_TAB_COMPLETE_OK : PyInterp_Event::ES_TAB_COMPLETE_ERR;
+
+  return new PyInterp_Event( typ, this);
+}
+
+
+
index e4f20801f2b4c3ab980503d92b10d8fed7e1724d..d20bcd84aeb6849794886cf42df686e71ceab190 100644 (file)
 #ifndef PYCONSOLE_REQUEST_H_
 #define PYCONSOLE_REQUEST_H_
 
+#include "PyConsole.h"
 #include "PyInterp_Request.h"
 
+#include <vector>
 #include <QString>
 #include <QEvent>
 
@@ -64,12 +66,46 @@ protected:
     \brief Create and return a notification event.
     \return new notification event
   */
-  virtual QEvent* createEvent() const;
+  virtual QEvent* createEvent();
 
 private:
   QString myCommand;   //!< Python command
   int     myState;     //!< Python command execution status
 };
 
+class PyConsole_EnhInterp;
+class PyConsole_EnhEditor;
+
+class CompletionCommand : public PyInterp_LockRequest
+{
+public:
+  /*!
+    Constructor.
+    Creates a new python completion request.
+    \param theInterp   python interpreter
+    \param input  string containing the dir() command to be executed
+    \param startMatch  part to be matched with the results of the dir() command
+    \param theListener widget to get the notification messages
+    \param sync        if True the request is processed synchronously
+  */
+  CompletionCommand( PyConsole_EnhInterp*      theInterp,
+               const QString&          input,
+               const QString&          startMatch,
+               PyConsole_EnhEditor*    theListener,
+               bool                    sync = false );
+
+
+protected:
+  /** List of separators identifying the last parsable token for completion */
+  static const std::vector<QString> SEPARATORS;
+
+  QString _dirArg;
+  QString _startMatch;
+  bool _tabSuccess;
+
+  virtual void execute();
+  virtual QEvent* createEvent();
+
+};
 
 #endif /* PYCONSOLE_REQUEST_H_ */
index acaeafec9c74c7376a86386caa4bf550a1494e97..44052093a3817c796828949e20e23c39fa0a38ae 100755 (executable)
@@ -64,9 +64,9 @@ void PyInterp_Request::Destroy( PyInterp_Request* request )
   delete request;
 }
 
-QEvent* PyInterp_Request::createEvent() const
+QEvent* PyInterp_Request::createEvent()
 {
-  return new PyInterp_Event( PyInterp_Event::ES_NOTIFY, (PyInterp_Request*)this );
+  return new PyInterp_Event( PyInterp_Event::ES_NOTIFY, this );
 }
 
 void PyInterp_Request::processEvent( QObject* o )
index cab3dcd8df109a325ae6dae551087038b79a7509..44e40ba9ca90f1ca867a3658d608ea2c3f89eada 100644 (file)
@@ -55,7 +55,8 @@ class PYINTERP_EXPORT PyInterp_Event : public QEvent
 
 public:
   //Execution state
-  enum { ES_NOTIFY = QEvent::User + 5000, ES_OK, ES_ERROR, ES_INCOMPLETE, ES_LAST };
+  enum { ES_NOTIFY = QEvent::User + 5000, ES_OK, ES_ERROR, ES_INCOMPLETE,
+         ES_TAB_COMPLETE_OK, ES_TAB_COMPLETE_ERR, ES_LAST };
 
   PyInterp_Event( int type, PyInterp_Request* request )
     : QEvent( (QEvent::Type)type ), myRequest( request ) {}
index 4ace0354564afde884174d774af884794fff4361..e8032c1adda290009a3bcb932bd83c615d9268de 100644 (file)
 
 #include "PyInterp_Request.h"
 
-/*!
-  \class ExecCommand
-  \brief Python command execution request.
-  \internal
-*/
-class ExecCommand : public PyInterp_LockRequest
-{
-public:
-  /*!
-    \brief Constructor.
-
-    Creates new python command execution request.
-    \param theInterp   python interpreter
-    \param theCommand  python command
-    \param theListener widget to get the notification messages
-    \param sync        if True the request is processed synchronously
-  */
-  ExecCommand( PyInterp_Interp*        theInterp,
-               const QString&          theCommand,
-               PyConsole_Editor*       theListener,
-               bool                    sync = false );
-
-protected:
-  /*!
-    \brief Execute the python command in the interpreter and
-           get its execution status.
-  */
-  virtual void execute();
-
-  /*!
-    \brief Create and return a notification event.
-    \return new notification event
-  */
-  virtual QEvent* createEvent() const;
-
-  QString myCommand;   //!< Python command
-  int     myState;     //!< Python command execution status
-};
-
index 15310648bf84e7f62ed54b593c40716123a303bf..9a4291b36a5dbb84519cc47089b8b2d2236c0e59 100644 (file)
@@ -67,7 +67,7 @@ protected:
   virtual void    execute() = 0;
   // Should be redefined in successors, contains actual request code
 
-  virtual QEvent* createEvent() const;
+  virtual QEvent* createEvent();
   // This method can be overridden to customize notification event creation
 
   virtual void    processEvent( QObject* );
index 159997416d7fc7d30dc1f4c6496a18a2a889a422..33d73927d0de0388cd8409a853deddb1deecab31 100644 (file)
@@ -929,7 +929,7 @@ QWidget* SalomeApp_Application::createWindow( const int flag )
   }
   else if ( flag == WT_PyConsole )
   {
-    PyConsole_Console* pyCons = new PyConsole_Console( desktop(), new SalomeApp_PyInterp() );
+    PyConsole_Console* pyCons = new PyConsole_EnhConsole( desktop(), new SalomeApp_PyInterp() );
     pyCons->setWindowTitle( tr( "PYTHON_CONSOLE" ) );
     pyCons->setFont(resourceMgr()->fontValue( "PyConsole", "font" ));
     pyCons->setIsShowBanner(resourceMgr()->booleanValue( "PyConsole", "show_banner", true ));
index 185b01f30f02f1419e8cf19b71cfbf3f5ff4058f..a0301b6ca6b84c81bcfb283b9eaf43691c0f4254 100755 (executable)
@@ -37,7 +37,7 @@
  * initstate & initcontext redefined here.
  */
 SalomeApp_PyInterp::SalomeApp_PyInterp(): 
-  PyConsole_Interp(), myFirstRun( true )
+  PyConsole_EnhInterp(), myFirstRun( true )
 {
 }
 
index 84c7906fe6fc6df908e628a5930aaa487fdb6d82..d457cf763e88af2d739341e7913cf1b43b5472da 100755 (executable)
@@ -27,9 +27,9 @@
 #ifndef _SalomeApp_PYINTERP_H_
 #define _SalomeApp_PYINTERP_H_
 
-#include <PyConsole_Interp.h> // this include must be first (see PyInterp_base.h)!
+#include <PyConsole_EnhInterp.h> // this include must be first (see PyInterp_base.h)!
 
-class SalomeApp_PyInterp : public PyConsole_Interp
+class SalomeApp_PyInterp : public PyConsole_EnhInterp
 {
 public:
   SalomeApp_PyInterp();