0000489: python console: "RFC"s for even more "rapid" prototyping :-)

git-svn-id: https://free-cad.svn.sourceforge.net/svnroot/free-cad/trunk@5253 e8eeb9e2-ec13-0410-a4a9-efa5cf37419d
This commit is contained in:
wmayer 2011-12-10 14:40:44 +00:00
parent f8866986b6
commit 6ed7ad3a15
4 changed files with 253 additions and 124 deletions

View File

@ -46,10 +46,37 @@
#include <Gui/DocumentPy.h> #include <Gui/DocumentPy.h>
#include "CallTips.h" #include "CallTips.h"
Q_DECLARE_METATYPE( Gui::CallTip ); //< allows use of QVariant
namespace Gui
{
/**
* template class Temporary.
* Allows variable changes limited to a scope.
*/
template <typename TYPE>
class Temporary
{
public:
Temporary( TYPE &var, const TYPE tmpVal )
: _var(var), _saveVal(var)
{ var = tmpVal; }
~Temporary( void )
{ _var = _saveVal; }
private:
TYPE &_var;
TYPE _saveVal;
};
} /* namespace Gui */
using namespace Gui; using namespace Gui;
CallTipsList::CallTipsList(QPlainTextEdit* parent) CallTipsList::CallTipsList(QPlainTextEdit* parent)
: QListWidget(parent), textEdit(parent), cursorPos(0), validObject(true) : QListWidget(parent), textEdit(parent), cursorPos(0), validObject(true), doCallCompletion(false)
{ {
// make the user assume that the widget is active // make the user assume that the widget is active
QPalette pal = parent->palette(); QPalette pal = parent->palette();
@ -406,7 +433,7 @@ void CallTipsList::showTips(const QString& line)
addItem(it.key()); addItem(it.key());
QListWidgetItem *item = this->item(this->count()-1); QListWidgetItem *item = this->item(this->count()-1);
item->setData(Qt::ToolTipRole, QVariant(it.value().description)); item->setData(Qt::ToolTipRole, QVariant(it.value().description));
item->setData(Qt::UserRole, QVariant(it.value().parameter)); item->setData(Qt::UserRole, qVariantFromValue( it.value() )); //< store full CallTip data
switch (it.value().type) switch (it.value().type)
{ {
case CallTip::Module: case CallTip::Module:
@ -526,7 +553,13 @@ bool CallTipsList::eventFilter(QObject * watched, QEvent * event)
itemActivated(currentItem()); itemActivated(currentItem());
return false; return false;
} }
else if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Tab) { else if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) {
itemActivated(currentItem());
return true;
}
else if (ke->key() == Qt::Key_Tab) {
// enable call completion for activating items
Temporary<bool> tmp( this->doCallCompletion, true ); //< previous state restored on scope exit
itemActivated( currentItem() ); itemActivated( currentItem() );
return true; return true;
} }
@ -585,6 +618,29 @@ void CallTipsList::callTipItemActivated(QListWidgetItem *item)
cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor); cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
} }
cursor.insertText( text ); cursor.insertText( text );
// get CallTip from item's UserRole-data
const CallTip &callTip = qVariantValue<CallTip>( item->data(Qt::UserRole) );
// if call completion enabled and we've something callable (method or class constructor) ...
if (this->doCallCompletion
&& (callTip.type == CallTip::Method || callTip.type == CallTip::Class))
{
cursor.insertText( QLatin1String("()") ); //< just append parenthesis to identifier even inserted.
/**
* Try to find out if call needs arguments.
* For this we search the description for appropriate hints ...
*/
QRegExp argumentMatcher( QRegExp::escape( callTip.name ) + QLatin1String("\\s*\\(\\s*\\w+.*\\)") );
argumentMatcher.setMinimal( true ); //< set regex non-greedy!
if (argumentMatcher.indexIn( callTip.description ) != -1)
{
// if arguments are needed, we just move the cursor one left, to between the parentheses.
cursor.movePosition( QTextCursor::Left, QTextCursor::MoveAnchor, 1 );
textEdit->setTextCursor( cursor );
}
}
textEdit->ensureCursorVisible(); textEdit->ensureCursorVisible();
QRect rect = textEdit->cursorRect(cursor); QRect rect = textEdit->cursorRect(cursor);
@ -593,7 +649,7 @@ void CallTipsList::callTipItemActivated(QListWidgetItem *item)
QPoint p(posX, posY); QPoint p(posX, posY);
p = textEdit->mapToGlobal(p); p = textEdit->mapToGlobal(p);
QToolTip::showText(p, item->data(Qt::UserRole).toString()); QToolTip::showText( p, callTip.parameter );
} }
QString CallTipsList::stripWhiteSpace(const QString& str) const QString CallTipsList::stripWhiteSpace(const QString& str) const

View File

@ -81,6 +81,7 @@ private:
QPlainTextEdit* textEdit; QPlainTextEdit* textEdit;
int cursorPos; int cursorPos;
bool validObject; bool validObject;
bool doCallCompletion;
QList<int> hideKeys; QList<int> hideKeys;
QList<int> compKeys; QList<int> compKeys;
}; };

View File

@ -55,7 +55,11 @@
using namespace Gui; using namespace Gui;
namespace Gui { namespace Gui
{
static size_t promptLength = 4; //< length of prompt string: ">>> " or "... ", in either case 4 characters
struct PythonConsoleP struct PythonConsoleP
{ {
enum Output {Error = 20, Message = 21}; enum Output {Error = 20, Message = 21};
@ -310,6 +314,11 @@ bool InteractiveInterpreter::push(const char* line)
return false; return false;
} }
bool InteractiveInterpreter::hasPendingInput( void ) const
{
return (!d->buffer.isEmpty());
}
QStringList InteractiveInterpreter::getBuffer() const QStringList InteractiveInterpreter::getBuffer() const
{ {
return d->buffer; return d->buffer;
@ -430,97 +439,125 @@ void PythonConsole::OnChange( Base::Subject<const char*> &rCaller,const char* sR
*/ */
void PythonConsole::keyPressEvent(QKeyEvent * e) void PythonConsole::keyPressEvent(QKeyEvent * e)
{ {
if (e->modifiers() & Qt::ControlModifier) { bool restartHistory = true;
QTextCursor cursor = this->textCursor();
// construct reference cursor at begin of input line ...
QTextCursor inputLineBegin = cursor;
inputLineBegin.movePosition( QTextCursor::End );
inputLineBegin.movePosition( QTextCursor::StartOfLine );
inputLineBegin.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, promptLength );
if (cursor < inputLineBegin)
{
/**
* The cursor is placed not on the input line (or within the prompt string)
* So we handle key input as follows:
* - don't allow changing previous lines.
* - allow full movement (no prompt restriction)
* - allow copying content (Ctrl+C)
* - "escape" to end of input line
*/
switch (e->key()) switch (e->key())
{ {
case Qt::Key_Up: case Qt::Key_Return:
{ case Qt::Key_Enter:
// no modification, just history facility case Qt::Key_Escape:
if (!d->history.isEmpty()) { this->moveCursor( QTextCursor::End );
if (d->history.prev()) { break;
QString cmd = d->history.value();
overrideCursor(cmd);
} return;
}
} break;
case Qt::Key_Down:
{
// no modification, just history facility
if (!d->history.isEmpty()) {
if (d->history.next()) {
QString cmd = d->history.value();
overrideCursor(cmd);
} return;
}
} break;
default: default:
if (e->text().isEmpty() || e->matches( QKeySequence::Copy ))
{ TextEdit::keyPressEvent(e); }
break; break;
} }
} }
else
{
/**
* The cursor sits somewhere on the input line (after the prompt)
* Here we handle key input a bit different:
* - restrict cursor movement to input line range (excluding the prompt characters)
* - roam the history by Up/Down keys
* - show call tips on period
*/
QTextBlock inputBlock = inputLineBegin.block(); //< get the last paragraph's text
QString inputLine = inputBlock.text().mid(promptLength); //< and skip prompt characters
switch (e->key()) switch (e->key())
{ {
// running Python interpreter? case Qt::Key_Escape:
{
// disable current input line - i.e. put it to history but don't execute it.
if (!inputLine.isEmpty())
{
d->history.append( QLatin1String("# ") + inputLine ); //< put line to history ...
inputLineBegin.insertText( QString::fromAscii("# ") ); //< but comment it on console
setTextCursor( inputLineBegin );
printPrompt( d->interpreter->hasPendingInput() ); //< print adequate prompt
}
} break;
case Qt::Key_Return: case Qt::Key_Return:
case Qt::Key_Enter: case Qt::Key_Enter:
{ {
// make sure to be at the end runSource( inputLine ); //< commit input line
QTextCursor cursor = textCursor(); d->history.append( inputLine ); //< put statement to history
cursor.movePosition(QTextCursor::End);
// get the last paragraph's text
QTextBlock block = cursor.block();
QString line = block.text();
// and skip the first 4 characters consisting of either ">>> " or "... "
line = line.mid(4);
// put statement to the history
d->history.append(line);
// evaluate and run the command
runSource(line);
} break; } break;
case Qt::Key_Period: case Qt::Key_Period:
{ {
QTextCursor cursor = textCursor(); // analyse context and show available call tips
QTextBlock block = cursor.block(); int contextLength = cursor.position() - inputLineBegin.position();
QString text = block.text();
int length = cursor.position() - block.position();
TextEdit::keyPressEvent(e); TextEdit::keyPressEvent(e);
d->callTipsList->showTips(text.left(length)); d->callTipsList->showTips( inputLine.left( contextLength ) );
} break; } break;
case Qt::Key_Home: case Qt::Key_Home:
{ {
if (e->modifiers() & Qt::ControlModifier) { QTextCursor::MoveMode mode = (e->modifiers() & Qt::ShiftModifier)? QTextCursor::KeepAnchor
TextEdit::keyPressEvent(e); /* else */ : QTextCursor::MoveAnchor;
} else { cursor.setPosition( inputBlock.position() + promptLength, mode );
QTextCursor::MoveMode mode = e->modifiers() & Qt::ShiftModifier
? QTextCursor::KeepAnchor
: QTextCursor::MoveAnchor;
QTextCursor cursor = textCursor();
QTextBlock block = cursor.block();
QString text = block.text();
int cursorPos = block.position();
if (text.startsWith(QLatin1String(">>> ")) ||
text.startsWith(QLatin1String("... ")))
cursorPos += 4;
cursor.setPosition(cursorPos, mode);
setTextCursor( cursor ); setTextCursor( cursor );
ensureCursorVisible(); ensureCursorVisible();
}
} break; } break;
case Qt::Key_Up:
{
// if possible, move back in history
if (d->history.prev( inputLine ))
{ overrideCursor( d->history.value() ); }
restartHistory = false;
} break;
case Qt::Key_Down:
{
// if possible, move forward in history
if (d->history.next())
{ overrideCursor( d->history.value() ); }
restartHistory = false;
} break;
case Qt::Key_Backspace:
case Qt::Key_Left:
{
if (cursor > inputLineBegin)
{ TextEdit::keyPressEvent(e); }
} break;
default: default:
{ {
TextEdit::keyPressEvent(e); TextEdit::keyPressEvent(e);
// This can't be done in CallTipsList::eventFilter() because we must first perform
// the event and afterwards update the list widget
if (d->callTipsList->isVisible()) {
d->callTipsList->validateCursor();
}
} break; } break;
} }
// This can't be done in CallTipsList::eventFilter() because we must first perform
// the event and afterwards update the list widget
if (d->callTipsList->isVisible())
{ d->callTipsList->validateCursor(); }
}
// any cursor move resets the history to its latest item.
if (restartHistory)
{ d->history.restart(); }
} }
/** /**
@ -844,8 +881,8 @@ QMimeData * PythonConsole::createMimeDataFromSelection () const
if ( pos >= s && pos <= e ) { if ( pos >= s && pos <= e ) {
if (b.userState() > -1 && b.userState() < pythonSyntax->maximumUserState()) { if (b.userState() > -1 && b.userState() < pythonSyntax->maximumUserState()) {
QString line = b.text(); QString line = b.text();
// and skip the first 4 characters consisting of either ">>> " or "... " // and skip the prompt characters consisting of either ">>> " or "... "
line = line.mid(4); line = line.mid(promptLength);
lines << line; lines << line;
} }
} }
@ -916,7 +953,7 @@ void PythonConsole::runSourceFromMimeData(const QString& source)
cursor.removeSelectedText(); cursor.removeSelectedText();
last = last + select; last = last + select;
line = cursor.block().text(); line = cursor.block().text();
line = line.mid(4); line = line.mid(promptLength);
} }
// put statement to the history // put statement to the history
@ -972,7 +1009,7 @@ void PythonConsole::overrideCursor(const QString& txt)
QTextBlock block = cursor.block(); QTextBlock block = cursor.block();
cursor.movePosition(QTextCursor::End); cursor.movePosition(QTextCursor::End);
cursor.movePosition(QTextCursor::StartOfLine); cursor.movePosition(QTextCursor::StartOfLine);
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 4); cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, promptLength);
cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, block.text().length()); cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, block.text().length());
cursor.removeSelectedText(); cursor.removeSelectedText();
cursor.insertText(txt); cursor.insertText(txt);
@ -1124,7 +1161,7 @@ void PythonConsoleHighlighter::colorChanged(const QString& type, const QColor& c
ConsoleHistory::ConsoleHistory() ConsoleHistory::ConsoleHistory()
{ {
it = _history.end(); _it = _history.end();
} }
ConsoleHistory::~ConsoleHistory() ConsoleHistory::~ConsoleHistory()
@ -1133,38 +1170,62 @@ ConsoleHistory::~ConsoleHistory()
void ConsoleHistory::first() void ConsoleHistory::first()
{ {
it = _history.begin(); _it = _history.begin();
} }
bool ConsoleHistory::more() bool ConsoleHistory::more()
{ {
return (it != _history.end()); return (_it != _history.end());
} }
/**
* next switches the history pointer to the next item.
* While searching the next item, the routine respects the search prefix set by prev().
* @return true if the pointer was switched to a later item, false otherwise.
*/
bool ConsoleHistory::next() bool ConsoleHistory::next()
{ {
if (it != _history.end()) { bool wentNext = false;
for (++it; it != _history.end(); ++it) {
if (!it->isEmpty())
break;
}
return true;
}
return false; // if we didn't reach history's end ...
} if (_it != _history.end())
bool ConsoleHistory::prev()
{ {
if (it != _history.begin()) { // we go forward until we find an item matching the prefix.
for (--it; it != _history.begin(); --it) { for (++_it; _it != _history.end(); ++_it)
if (!it->isEmpty()) {
break; if (!_it->isEmpty() && _it->startsWith( _prefix ))
{ break; }
} }
return true; // we did a step - no matter of a matching prefix.
wentNext = true;
}
return wentNext;
} }
return false; /**
* prev switches the history pointer to the previous item.
* The optional parameter prefix allows to search the history selectively for commands that start
* with a certain character sequence.
* @param prefix - prefix string for searching backwards in history, empty string by default
* @return true if the pointer was switched to an earlier item, false otherwise.
*/
bool ConsoleHistory::prev( const QString &prefix )
{
bool wentPrev = false;
// store prefix if it's the first history access
if (_it == _history.end())
{ _prefix = prefix; }
// while we didn't go back or reach history's begin ...
while (!wentPrev && _it != _history.begin())
{
// go back in history and check if item matches prefix
// Skip empty items
--_it;
wentPrev = (!_it->isEmpty() && _it->startsWith( _prefix ));
}
return wentPrev;
} }
bool ConsoleHistory::isEmpty() const bool ConsoleHistory::isEmpty() const
@ -1172,18 +1233,18 @@ bool ConsoleHistory::isEmpty() const
return _history.isEmpty(); return _history.isEmpty();
} }
QString ConsoleHistory::value() const const QString& ConsoleHistory::value() const
{ {
if ( it != _history.end() ) return ((_it != _history.end())? *_it
return *it; /* else */ : _prefix);
else
return QString::null;
} }
void ConsoleHistory::append( const QString& item ) void ConsoleHistory::append( const QString& item )
{ {
_history.append( item ); _history.append( item );
it = _history.end(); // reset iterator to make the next history
// access begin with the latest item.
_it = _history.end();
} }
const QStringList& ConsoleHistory::values() const const QStringList& ConsoleHistory::values() const
@ -1191,6 +1252,14 @@ const QStringList& ConsoleHistory::values() const
return this->_history; return this->_history;
} }
/**
* restart resets the history access to the latest item.
*/
void ConsoleHistory::restart( void )
{
_it = _history.end();
}
// ----------------------------------------------------- // -----------------------------------------------------
/* TRANSLATOR Gui::PythonInputField */ /* TRANSLATOR Gui::PythonInputField */

View File

@ -45,6 +45,7 @@ public:
bool push(const char*); bool push(const char*);
int compileCommand(const char*) const; int compileCommand(const char*) const;
bool hasPendingInput( void ) const;
void setBuffer(const QStringList&); void setBuffer(const QStringList&);
QStringList getBuffer() const; QStringList getBuffer() const;
void clearBuffer(); void clearBuffer();
@ -72,15 +73,17 @@ public:
void first(); void first();
bool more(); bool more();
bool next(); bool next();
bool prev(); bool prev(const QString &prefix = QString());
bool isEmpty() const; bool isEmpty() const;
QString value() const; const QString& value() const;
void append( const QString& ); void append(const QString &inputLine);
const QStringList& values() const; const QStringList& values() const;
void restart();
private: private:
QStringList _history; QStringList _history;
QStringList::ConstIterator it; QStringList::ConstIterator _it;
QString _prefix;
}; };
/** /**