1 // Copyright (C) 2007-2022 CEA/DEN, EDF R&D, OPEN CASCADE
3 // Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Lesser General Public
8 // License as published by the Free Software Foundation; either
9 // version 2.1 of the License, or (at your option) any later version.
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 // Lesser General Public License for more details.
16 // You should have received a copy of the GNU Lesser General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
22 // File : PyConsole_Editor.cxx
23 // Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com)
26 \class PyConsole_Editor
27 \brief Python command line interpreter front-end GUI widget.
29 This class provides simple GUI interface to the Python interpreter, including basic
30 navigation operations, executing commands (both interactively and programmatically),
31 copy-paste operations, history of the commands and so on.
33 Here below is the shortcut keyboard boundings used for navigation and other operations:
34 - <Enter> : execute current command
35 - <Ctrl><Break> : clear current command
36 - <Escape> : clear current command
37 - <Up> : previous command in the history
38 - <Shift><Up> : move cursor one row up with selection
39 - <Ctrl><Up> : move cursor one row up without selection
40 - <Ctrl><Shift><Up> : move cursor one row up with selection
41 - <Down> : next command in the history
42 - <Shift><Down> : move cursor one row down with selection
43 - <Ctrl><Down> : move cursor one row down without selection
44 - <Ctrl><Shift><Down> : move cursor one row down with selection
45 - <Left> : move one symbol left without selection
46 - <Shift><Left> : move one symbol left with selection
47 - <Ctrl><Left> : move one word left without selection
48 - <Ctrl><Shift><Left> : move one word left with selection
49 - <Right> : move one symbol right without selection
50 - <Shift><Right> : move one symbol right with selection
51 - <Ctrl><Right> : move one word right without selection
52 - <Ctrl><Shift><Right> : move one word right with selection
53 - <PgUp> : first command in the history
54 - <Shift><PgUp> : move one page up with selection
55 - <Ctrl><PgUp> : move one page up without selection
56 - <Ctrl><Shift><PgUp> : scroll one page up
57 - <PgDn> : last command in the history
58 - <Shift><PgDn> : move one page down with selection
59 - <Ctrl><PgDn> : move one page down without selection
60 - <Ctrl><Shift><PgDn> : scroll one page down
61 - <Home> : move to the beginning of the line without selection
62 - <Shift><Home> : move to the beginning of the line with selection
63 - <Ctrl><Home> : move to the very first symbol without selection
64 - <Ctrl><Shift><Home> : move to the very first symbol with selection
65 - <End> : move to the end of the line without selection
66 - <Shift><End> : move to the end of the line with selection
67 - <Ctrl><End> : move to the very last symbol without selection
68 - <Ctrl><Shift><End> : move to the very last symbol with selection
69 - <Backspace> : delete symbol before the cursor
70 / remove selected text and put it to the clipboard (cut)
71 - <Shift><Backspace> : delete previous word
72 / remove selected text and put it to the clipboard (cut)
73 - <Ctrl><Backspace> : delete text from the cursor to the beginning of the line
74 / remove selected text and put it to the clipboard (cut)
75 - <Delete> : delete symbol after the cursor
76 / remove selected text and put it to the clipboard (cut)
77 - <Shift><Delete> : delete next word
78 / remove selected text and put it to the clipboard (cut)
79 - <Ctrl><Delete> : delete text from the cursor to the end of the line
80 / remove selected text and put it to the clipboard (cut)
81 - <Ctrl><Insert> : copy
82 - <Shift><Insert> : paste
87 - <Tab> : performs auto-completion
88 - <Ctrl><Tab> : undoes auto-completion
91 #include "PyConsole_Interp.h"
92 #include "PyConsole_Editor.h"
93 #include "PyConsole_Event.h"
94 #include "PyInterp_Dispatcher.h"
95 #include "PyConsole_Request.h"
97 #include <QApplication>
103 #include <QMouseEvent>
104 #include <QScrollBar>
105 #include <QTextBlock>
106 #include <QTextCursor>
107 #include <QTextStream>
109 #include <QFileDialog>
110 #include <QMessageBox>
113 //VSR: uncomment below macro to support unicode text properly in SALOME
114 // current commented out due to regressions
115 //#define PAL22528_UNICODE
119 QString fromUtf8( const char* txt )
121 #ifdef PAL22528_UNICODE
122 return QString::fromUtf8( txt );
124 return QString( txt );
129 static QString READY_PROMPT = ">>> ";
130 static QString DOTS_PROMPT = "... ";
132 void PyConsole_CallbackStdout( void* data, char* c )
134 if(!((PyConsole_Editor*)data)->isSuppressOutput()) {
135 PyConsole_Editor* e = (PyConsole_Editor*)data;
136 e->putLog( fromUtf8(c) );
137 QApplication::postEvent( e, new PyConsole_PrintEvent( fromUtf8(c), false ) );
141 void PyConsole_CallbackStderr( void* data, char* c )
143 if(!((PyConsole_Editor*)data)->isSuppressOutput()) {
144 PyConsole_Editor* e = (PyConsole_Editor*)data;
145 e->putLog( fromUtf8(c) );
146 QApplication::postEvent( e, new PyConsole_PrintEvent( fromUtf8(c), true ) );
151 \brief Default constructor.
153 Creates python editor window.
154 \param theParent parent widget
156 PyConsole_Editor::PyConsole_Editor( QWidget* parent )
157 : QTextEdit( parent )
159 PyConsole_Interp *interp(new PyConsole_Interp);
160 interp->initialize();
168 Creates python editor window.
169 \param parent parent widget
170 \param interp python interpreter
172 PyConsole_Editor::PyConsole_Editor( QWidget* parent,
173 PyConsole_Interp* interp )
174 : QTextEdit( parent )
176 myInterp.takeRef(interp);
181 void PyConsole_Editor::init()
187 myIsSuppressOutput = false;
188 myMultiLinePaste = false;
189 myAutoCompletion = false;
191 myComplCursorPos = -1;
193 setFont( QFont( "Courier", 11 ) ); // default font
194 setUndoRedoEnabled( false );
196 myPrompt = READY_PROMPT;
197 setLineWrapMode( QTextEdit::WidgetWidth );
198 setWordWrapMode( QTextOption::WrapAnywhere );
199 setAcceptRichText( false );
201 // set callbacks to interpreter
202 myInterp->setvoutcb( PyConsole_CallbackStdout, this );
203 myInterp->setverrcb( PyConsole_CallbackStderr, this );
205 if ( isShowBanner() )
207 // clear command buffer
208 myCommandBuffer.truncate(0);
209 // unset read-only state
210 setReadOnly( false );
211 // unset history browsing mode
216 viewport()->unsetCursor();
222 PyConsole_Editor::~PyConsole_Editor()
227 \brief Get Python interpreter
229 PyConsole_Interp *PyConsole_Editor::getInterp() const
231 return myInterp.iAmATrollConstCast();
235 \brief Get synchronous mode flag value.
238 \return \c true if python console works in synchronous mode
240 bool PyConsole_Editor::isSync() const
246 \brief Set synchronous mode flag value.
248 In synhronous mode the Python commands are executed in the GUI thread
249 and the GUI is blocked until the command is finished. In the asynchronous
250 mode each Python command is executed in the separate thread that does not
251 block the main GUI loop.
253 \param on synhronous mode flag
255 void PyConsole_Editor::setIsSync( const bool on )
261 \brief Get suppress output flag value.
263 \sa setIsSuppressOutput()
264 \return \c true if python console output is suppressed.
266 bool PyConsole_Editor::isSuppressOutput() const
268 return myIsSuppressOutput;
272 \brief Set suppress output flag value.
274 In case if suppress output flag is \c true, the python
275 console output suppressed.
277 \param on suppress output flag
279 void PyConsole_Editor::setIsSuppressOutput( const bool on )
281 myIsSuppressOutput = on;
285 \brief Get 'show banner' flag value.
287 \sa setIsShowBanner()
288 \return \c true if python console shows banner
290 bool PyConsole_Editor::isShowBanner() const
296 \brief Set 'show banner' flag value.
298 The banner is shown in the top of the python console window.
301 \param on 'show banner' flag
303 void PyConsole_Editor::setIsShowBanner( const bool on )
305 if ( myShowBanner != on ) {
312 \brief Switch on/off commands auto-completion feature
315 void PyConsole_Editor::setAutoCompletion( bool on )
317 myAutoCompletion = on;
318 document()->setUndoRedoEnabled( myAutoCompletion );
322 \brief Returns \c true if auto-completion feature is switched on
323 or \c false otherwise
324 \sa setAutoCompletion()
326 bool PyConsole_Editor::autoCompletion() const
328 return myAutoCompletion;
332 \brief Check if trace logging is switched on.
334 \sa startLog(), stopLog()
335 \return \c true if trace logging is switched on
337 bool PyConsole_Editor::isLogging() const
339 return !myLogFile.isEmpty();
343 \brief Get size hint for the Python console window
344 \return size hint value
346 QSize PyConsole_Editor::sizeHint() const
348 QFontMetrics fm( font() );
349 int nbLines = ( isShowBanner() ? banner().split("\n").count() : 0 ) + 1;
350 QSize s(100, fm.lineSpacing()*nbLines);
355 \brief Put the string \a str to the python editor.
356 \param str string to be put in the command line of the editor
357 \param newBlock if \c true, then the string is printed on a new line
358 \param isError if \c true, the text is printed in dark red
360 void PyConsole_Editor::addText( const QString& str,
364 QTextCursor aCursor = textCursor();
367 moveCursor( QTextCursor::End );
369 aCursor.insertBlock();
371 cf.setForeground( QBrush( Qt::red ) );
373 cf.setForeground( QBrush( Qt::black ) );
374 aCursor.insertText( str, cf );
375 moveCursor( QTextCursor::End );
376 ensureCursorVisible();
380 \brief Convenient method for executing a Python command,
381 as if the user typed it manually.
382 \param command python command to be executed
384 !!! WARNING: doesn't work properly with multi-line commands. !!!
386 void PyConsole_Editor::exec( const QString& command )
388 if ( isReadOnly() ) {
389 // some interactive command is being executed in this editor...
390 // schedule the command to the queue
391 myQueue.push_back( command );
396 moveCursor( QTextCursor::End );
397 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
398 textCursor().removeSelectedText();
400 // set "ready" prompt
401 myPrompt = READY_PROMPT;
403 // clear command buffer
404 myCommandBuffer.truncate( 0 );
406 // unset history browsing mode
409 // print command line by line
410 QString cmd = command;
411 if ( !cmd.endsWith( "\n" ) ) cmd += "\n";
412 QStringList lines = command.split( "\n" );
413 for ( int i = 0; i < lines.size(); i++ ) {
414 if ( !lines[i].trimmed().isEmpty() )
415 myHistory.push_back( lines[i] );
416 addText( ( i == 0 ? READY_PROMPT : DOTS_PROMPT ) + lines[i], i != 0 );
417 putLog( QString( "%1%2\n" ).arg( i == 0 ? READY_PROMPT : DOTS_PROMPT ).arg( lines[i] ) );
423 // set read-only mode
427 setCursor( Qt::BusyCursor );
429 // post a request to execute Python command;
430 // editor will be informed via a custom event that execution has been completed
431 PyInterp_Dispatcher::Get()->Exec( createCmdRequest( cmd ) );
435 \brief Create request to the python dispatcher for the command execution.
436 \param command python command to be executed
438 PyInterp_Request* PyConsole_Editor::createCmdRequest( const QString& command )
440 return new PyConsole_ExecCommand( myInterp, command, this, isSync() );
444 \brief Create the Python request that will be posted to the interpreter to
446 \param input line entered by the user at the time <TAB> was pressed
447 \return completion command
448 \sa CompletionCommand
450 PyInterp_Request* PyConsole_Editor::createTabRequest( const QString& input )
453 static QStringList separators;
454 if ( separators.isEmpty() ) {
455 separators << " " << "(" << "[" << "+" << "-" << "*" << "/" << ";" << "^" << "=";
458 // parse input to extract on what part the dir() has to be executed
460 QString input2( input );
462 // split up to the last syntaxical separator
464 foreach ( QString separator, separators ) {
465 int j = input2.lastIndexOf( separator );
470 input2 = input.mid( lastSp + 1 );
472 // detect a qualified name (with a point)
473 int lastPt = input2.lastIndexOf( "." );
475 if ( lastPt != -1 ) {
476 // split the 2 surrounding parts of the qualified name
477 myComplBeforePoint = input2.left( lastPt );
478 myComplAfterPoint = input2.mid( lastPt+1 );
481 // no point found - do a global matching
482 // (the following will call dir() with an empty string)
483 myComplAfterPoint = input2;
484 myComplBeforePoint = "";
487 return new PyConsole_CompletionCommand( myInterp, myComplBeforePoint,
488 myComplAfterPoint, this, isSync() );
492 \brief Execute command in the python interpreter
493 and wait until it is finished.
495 \param command python command to be executed
497 void PyConsole_Editor::execAndWait( const QString& command )
503 // create new event loop
504 bool sync = isSync();
506 myEventLoop = new QEventLoop( this );
515 // delete event loop after command is processed
522 \brief Process <Enter> key press event.
524 Execute the command entered by the user.
526 void PyConsole_Editor::handleReturn()
528 // Position cursor at the end
529 QTextCursor aCursor = textCursor();
530 aCursor.movePosition( QTextCursor::End );
531 setTextCursor( aCursor );
534 QTextBlock par = document()->end().previous();
535 if ( !par.isValid() ) return;
538 QString cmd = par.text().remove( 0, promptSize() );
540 // extend the command buffer with the current command
541 myCommandBuffer.append( cmd );
543 // add command to the history
544 if ( !cmd.trimmed().isEmpty() )
545 myHistory.push_back( cmd );
546 putLog( QString( "%1%2\n" ).arg( myPrompt ).arg( cmd ) );
551 // set read-only mode
555 setCursor( Qt::BusyCursor );
557 // post a request to execute Python command;
558 // editor will be informed via a custom event that execution has been completed
559 PyInterp_Dispatcher::Get()->Exec( createCmdRequest( myCommandBuffer ) );
563 \brief Process <Tab> key press event.
565 Perform auto-completion of the currently entered command, if this feature is enabled
567 void PyConsole_Editor::handleTab()
569 if ( !autoCompletion() )
570 return; // auto-completion feature is disabled
573 return; // already in tab mode
575 QTextCursor aCursor = textCursor();
577 // move cursor to the end of input
578 aCursor.movePosition( QTextCursor::End );
579 setTextCursor( aCursor );
581 // save cursor position if needed
582 if ( myComplCursorPos == -1 )
583 myComplCursorPos = textCursor().position();
586 QTextBlock par = document()->end().previous();
587 if ( !par.isValid() ) return; // empty line
589 // switch to completion mode
592 // get currently entered command
593 QString cmd = par.text().mid( promptSize() );
595 // post completion request
596 // editor will be informed that completion has been done via a custom event
597 PyInterp_Dispatcher::Get()->Exec( createTabRequest( cmd ) );
601 \brief Process <Ctrl><Tab> key press event.
603 Undoe last auto-completion
605 void PyConsole_Editor::handleBackTab()
607 if ( !autoCompletion() )
608 return; // auto-completion feature is disabled
610 QTextCursor aCursor = textCursor();
612 if ( myComplCursorPos == -1 )
613 return; // invalid cursor position
615 // ensure cursor is at the end of command line
616 aCursor.setPosition( myComplCursorPos );
617 aCursor.movePosition( QTextCursor::EndOfLine );
618 //setCursor( aCursor );
620 // delete last completed text
621 int i = aCursor.position() - myComplCursorPos;
622 aCursor.movePosition( QTextCursor::Left, QTextCursor::KeepAnchor, i );
623 aCursor.removeSelectedText();
624 myComplCursorPos = -1;
628 \brief Process drop event.
631 \param event drop event
633 void PyConsole_Editor::dropEvent( QDropEvent* event )
635 // get the initial drop position
636 #if QT_VERSION >= 0x060000
637 QPoint pos = event->position().toPoint();
639 QPoint pos = event->pos();
641 QTextCursor aCursor = cursorForPosition( pos );
643 // if the position is not in the last line move it to the end of the command line
644 if ( aCursor.position() < document()->end().previous().position() + promptSize() ) {
645 moveCursor( QTextCursor::End );
646 pos = cursorRect().center();
649 // create new drop event and use it instead of the original
651 event->possibleActions(),
653 #if QT_VERSION >= 0x060000
657 event->mouseButtons(),
658 event->keyboardModifiers(),
661 QTextEdit::dropEvent( &de );
663 // accept the original event
664 event->acceptProposedAction();
668 \brief Process mouse press event
670 Clear the completion when any mouse button is pressed.
672 \param e mouse press event
674 void PyConsole_Editor::mousePressEvent( QMouseEvent* event )
676 if ( autoCompletion() ) {
678 myComplCursorPos = -1;
680 QTextEdit::mousePressEvent( event );
684 \brief Process mouse button release event.
686 Left mouse button: copy selection to the clipboard.
687 Middle mouse button: paste clipboard's contents.
689 \param event mouse event
691 void PyConsole_Editor::mouseReleaseEvent( QMouseEvent* event )
693 if ( event->button() == Qt::LeftButton ) {
694 QTextEdit::mouseReleaseEvent( event );
696 else if ( event->button() == Qt::MiddleButton ) {
697 QTextCursor aCursor = cursorForPosition( event->pos() );
698 // if the position is not in the last line move it to the end of the command line
699 if ( aCursor.position() < document()->end().previous().position() + promptSize() ) {
700 moveCursor( QTextCursor::End );
703 setTextCursor( aCursor );
705 const QMimeData* md = QApplication::clipboard()->mimeData( QApplication::clipboard()->supportsSelection() ?
706 QClipboard::Selection : QClipboard::Clipboard );
708 insertFromMimeData( md );
711 QTextEdit::mouseReleaseEvent( event );
716 \brief Check if the string is command.
718 Return \c true if the string \a str is likely to be the command
719 (i.e. it is started from the '>>>' or '...').
720 \param str string to be checked
722 bool PyConsole_Editor::isCommand( const QString& str ) const
724 return str.startsWith( READY_PROMPT ) || str.startsWith( DOTS_PROMPT );
728 \brief Handle keyboard event.
730 Implement navigation, history browsing, copy/paste and other common
733 \param event keyboard event
735 void PyConsole_Editor::keyPressEvent( QKeyEvent* event )
737 // get cursor position
738 QTextCursor aCursor = textCursor();
739 int curLine = aCursor.blockNumber();
740 int curCol = aCursor.columnNumber();
742 // get last edited line
743 int endLine = document()->blockCount()-1;
745 // get pressed key code
746 int aKey = event->key();
748 // check if <Ctrl> is pressed
749 bool ctrlPressed = event->modifiers() & Qt::ControlModifier;
750 // check if <Shift> is pressed
751 bool shftPressed = event->modifiers() & Qt::ShiftModifier;
753 if ( autoCompletion() ) {
754 // auto-completion support
755 if ( aKey == Qt::Key_Tab && !shftPressed ) {
757 if ( !ctrlPressed ) {
767 // If <Ctrl> is not pressed (or if something else is pressed with <Ctrl>),
768 // or if <Ctrl> is not pressed alone, we have to clear completion
769 if ( !ctrlPressed || ( ctrlPressed && aKey != Qt::Key_Control ) ) {
771 myComplCursorPos = -1;
774 // Discard <Ctrl> pressed alone:
775 if ( aKey == Qt::Key_Control )
779 if ( aKey == Qt::Key_Escape || ( ctrlPressed && aKey == -1 ) ) {
780 // process <Ctrl>+<Break> key-binding and <Escape> key: clear current command
781 myCommandBuffer.truncate( 0 );
782 myPrompt = READY_PROMPT;
783 addText( myPrompt, true );
784 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
787 else if ( ctrlPressed && aKey == Qt::Key_C ) {
788 // process <Ctrl>+<C> key-binding : copy
792 else if ( ctrlPressed && aKey == Qt::Key_X ) {
793 // process <Ctrl>+<X> key-binding : cut
797 else if ( ctrlPressed && aKey == Qt::Key_V ) {
798 // process <Ctrl>+<V> key-binding : paste
803 // check for printed key
804 // #### aKey = ( aKey < Qt::Key_Space || aKey > Qt::Key_ydiaeresis ) ? aKey : 0;
806 aKey = !(QChar(aKey).isPrint()) ? aKey : 0;
810 // any printed key: just print it
812 if ( curLine < endLine || curCol < promptSize() ) {
813 moveCursor( QTextCursor::End );
815 QTextEdit::keyPressEvent( event );
820 // <Enter> key: process the current command
826 // <Up> arrow key: process as follows:
827 // - without <Ctrl>, <Shift> modifiers: previous command in history
828 // - with <Ctrl> modifier key pressed: move cursor one row up without selection
829 // - with <Shift> modifier key pressed: move cursor one row up with selection
830 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row up
832 if ( ctrlPressed && shftPressed ) {
833 int value = verticalScrollBar()->value();
834 int spacing = fontMetrics().lineSpacing();
835 verticalScrollBar()->setValue( value > spacing ? value-spacing : 0 );
837 else if ( shftPressed || ctrlPressed ) {
839 moveCursor( QTextCursor::Up,
840 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
843 if ( myCmdInHistory < 0 && myHistory.count() > 0 ) {
844 // set history browsing mode
845 myCmdInHistory = myHistory.count();
846 // remember current command
847 QTextBlock par = document()->end().previous();
848 myCurrentCommand = par.text().remove( 0, promptSize() );
850 if ( myCmdInHistory > 0 ) {
852 // get previous command in the history
853 QString previousCommand = myHistory.at( myCmdInHistory );
854 // print previous command
855 moveCursor( QTextCursor::End );
856 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
857 textCursor().removeSelectedText();
858 addText( myPrompt + previousCommand );
859 // move cursor to the end
860 moveCursor( QTextCursor::End );
866 // <Down> arrow key: process as follows:
867 // - without <Ctrl>, <Shift> modifiers: next command in history
868 // - with <Ctrl> modifier key pressed: move cursor one row down without selection
869 // - with <Shift> modifier key pressed: move cursor one row down with selection
870 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one row down
872 if ( ctrlPressed && shftPressed ) {
873 int value = verticalScrollBar()->value();
874 int maxval = verticalScrollBar()->maximum();
875 int spacing = fontMetrics().lineSpacing();
876 verticalScrollBar()->setValue( value+spacing < maxval ? value+spacing : maxval );
878 else if ( shftPressed || ctrlPressed) {
879 if ( curLine < endLine )
880 moveCursor( QTextCursor::Down,
881 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
884 if ( myCmdInHistory >= 0 ) {
885 // get next command in the history
888 if ( myCmdInHistory < myHistory.count() ) {
889 // next command in history
890 nextCommand = myHistory.at( myCmdInHistory );
893 // end of history is reached
894 // last printed command
895 nextCommand = myCurrentCommand;
896 // unset history browsing mode
899 // print next or current command
900 moveCursor( QTextCursor::End );
901 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
902 textCursor().removeSelectedText();
903 addText( myPrompt + nextCommand );
904 // move cursor to the end
905 moveCursor( QTextCursor::End );
911 // <Left> arrow key: process as follows:
912 // - without <Ctrl>, <Shift> modifiers: move one symbol left (taking into account prompt)
913 // - with <Ctrl> modifier key pressed: move one word left (taking into account prompt)
914 // - with <Shift> modifier key pressed: move one symbol left with selection
915 // - with <Ctrl>+<Shift> modifier keys pressed: move one word left with selection
917 QString txt = textCursor().block().text();
918 if ( !shftPressed && isCommand( txt ) && curCol <= promptSize() ) {
919 moveCursor( QTextCursor::Up );
920 moveCursor( QTextCursor::EndOfBlock );
923 QTextEdit::keyPressEvent( event );
928 // <Right> arrow key: process as follows:
929 // - without <Ctrl>, <Shift> modifiers: move one symbol right (taking into account prompt)
930 // - with <Ctrl> modifier key pressed: move one word right (taking into account prompt)
931 // - with <Shift> modifier key pressed: move one symbol right with selection
932 // - with <Ctrl>+<Shift> modifier keys pressed: move one word right with selection
934 QString txt = textCursor().block().text();
935 if ( !shftPressed ) {
936 if ( curCol < txt.length() ) {
937 if ( isCommand( txt ) && curCol < promptSize() ) {
938 aCursor.setPosition( aCursor.block().position() + promptSize() );
939 setTextCursor( aCursor );
944 if ( curLine < endLine && isCommand( textCursor().block().next().text() ) ) {
945 aCursor.setPosition( aCursor.position() + promptSize()+1 );
946 setTextCursor( aCursor );
947 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
952 QTextEdit::keyPressEvent( event );
956 // <PageUp> key: process as follows:
957 // - without <Ctrl>, <Shift> modifiers: first command in history
958 // - with <Ctrl> modifier key pressed: move cursor one page up without selection
959 // - with <Shift> modifier key pressed: move cursor one page up with selection
960 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page up
962 if ( ctrlPressed && shftPressed ) {
963 verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub);
965 else if ( shftPressed || ctrlPressed ) {
967 qreal lastY = cursorRect( aCursor ).top();
969 // move using movePosition to keep the cursor's x
971 qreal y = cursorRect( aCursor ).top();
972 distance += qAbs( y - lastY );
974 moved = aCursor.movePosition( QTextCursor::Up,
975 shftPressed ? QTextCursor::KeepAnchor :
976 QTextCursor::MoveAnchor );
977 } while ( moved && distance < viewport()->height() );
979 aCursor.movePosition( QTextCursor::Down,
980 shftPressed ? QTextCursor::KeepAnchor :
981 QTextCursor::MoveAnchor );
982 verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
984 setTextCursor( aCursor );
987 if ( myCmdInHistory < 0 && myHistory.count() > 0 ) {
988 // set history browsing mode
989 myCmdInHistory = myHistory.count();
990 // remember current command
991 QTextBlock par = document()->end().previous();
992 myCurrentCommand = par.text().remove( 0, promptSize() );
994 if ( myCmdInHistory > 0 ) {
996 // get very first command in the history
997 QString firstCommand = myHistory.at( myCmdInHistory );
998 // print first command
999 moveCursor( QTextCursor::End );
1000 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
1001 textCursor().removeSelectedText();
1002 addText( myPrompt + firstCommand );
1003 // move cursor to the end
1004 moveCursor( QTextCursor::End );
1009 case Qt::Key_PageDown:
1010 // <PageDown> key: process as follows:
1011 // - without <Ctrl>, <Shift> modifiers: last command in history
1012 // - with <Ctrl> modifier key pressed: move cursor one page down without selection
1013 // - with <Shift> modifier key pressed: move cursor one page down with selection
1014 // - with <Ctrl>+<Shift> modifier keys pressed: scroll one page down
1016 if ( ctrlPressed && shftPressed ) {
1017 verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd);
1019 else if ( shftPressed || ctrlPressed ) {
1021 qreal lastY = cursorRect( aCursor ).top();
1023 // move using movePosition to keep the cursor's x
1025 qreal y = cursorRect( aCursor ).top();
1026 distance += qAbs( y - lastY );
1028 moved = aCursor.movePosition( QTextCursor::Down,
1029 shftPressed ? QTextCursor::KeepAnchor :
1030 QTextCursor::MoveAnchor );
1031 } while ( moved && distance < viewport()->height() );
1033 aCursor.movePosition( QTextCursor::Up,
1034 shftPressed ? QTextCursor::KeepAnchor :
1035 QTextCursor::MoveAnchor );
1036 verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
1038 setTextCursor( aCursor );
1041 if ( myCmdInHistory >= 0 ) {
1042 // unset history browsing mode
1043 myCmdInHistory = -1;
1044 // print current command
1045 moveCursor( QTextCursor::End );
1046 moveCursor( QTextCursor::StartOfBlock, QTextCursor::KeepAnchor );
1047 textCursor().removeSelectedText();
1048 addText( myPrompt + myCurrentCommand );
1049 // move cursor to the end
1050 moveCursor( QTextCursor::End );
1056 // <Home> key: process as follows:
1057 // - without <Ctrl>, <Shift> modifiers: move cursor to the beginning of the current line without selection
1058 // - with <Ctrl> modifier key pressed: move cursor to the very first symbol without selection
1059 // - with <Shift> modifier key pressed: move cursor to the beginning of the current line with selection
1060 // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very first symbol with selection
1062 if ( ctrlPressed ) {
1063 moveCursor( QTextCursor::Start,
1064 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
1067 QString txt = textCursor().block().text();
1068 if ( isCommand( txt ) ) {
1069 if ( shftPressed ) {
1070 if ( curCol > promptSize() ) {
1071 aCursor.movePosition( QTextCursor::StartOfLine, QTextCursor::KeepAnchor );
1072 aCursor.movePosition( QTextCursor::Right, QTextCursor::KeepAnchor, promptSize() );
1076 aCursor.movePosition( QTextCursor::StartOfLine );
1077 aCursor.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, promptSize() );
1079 setTextCursor( aCursor );
1082 moveCursor( QTextCursor::StartOfBlock,
1083 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
1085 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1090 // <End> key: process as follows:
1091 // - without <Ctrl>, <Shift> modifiers: move cursor to the end of the current line without selection
1092 // - with <Ctrl> modifier key pressed: move cursor to the very last symbol without selection
1093 // - with <Shift> modifier key pressed: move cursor to the end of the current line with selection
1094 // - with <Ctrl>+<Shift> modifier keys pressed: move cursor to the very last symbol with selection
1096 moveCursor( ctrlPressed ? QTextCursor::End : QTextCursor::EndOfBlock,
1097 shftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor );
1100 case Qt::Key_Backspace :
1101 // <Backspace> key: process as follows
1102 // - without any modifiers : delete symbol before the cursor / selection (taking into account prompt)
1103 // - with <Shift> modifier key pressed: delete previous word
1104 // - with <Ctrl> modifier key pressed: delete text from the cursor to the line beginning
1105 // works only for last (command) line
1107 if ( aCursor.hasSelection() ) {
1110 else if ( aCursor.position() > document()->end().previous().position() + promptSize() ) {
1111 if ( shftPressed ) {
1112 moveCursor( QTextCursor::PreviousWord, QTextCursor::KeepAnchor );
1113 textCursor().removeSelectedText();
1115 else if ( ctrlPressed ) {
1116 aCursor.setPosition( document()->end().previous().position() + promptSize(),
1117 QTextCursor::KeepAnchor );
1118 setTextCursor( aCursor );
1119 textCursor().removeSelectedText();
1122 QTextEdit::keyPressEvent( event );
1126 aCursor.setPosition( document()->end().previous().position() + promptSize() );
1127 setTextCursor( aCursor );
1128 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1132 case Qt::Key_Delete :
1133 // <Delete> key: process as follows
1134 // - without any modifiers : delete symbol after the cursor / selection (taking into account prompt)
1135 // - with <Shift> modifier key pressed: delete next word
1136 // - with <Ctrl> modifier key pressed: delete text from the cursor to the end of line
1137 // works only for last (command) line
1139 if ( aCursor.hasSelection() ) {
1142 else if ( aCursor.position() > document()->end().previous().position() + promptSize()-1 ) {
1143 if ( shftPressed ) {
1144 moveCursor( QTextCursor::NextWord, QTextCursor::KeepAnchor );
1145 textCursor().removeSelectedText();
1147 else if ( ctrlPressed ) {
1148 moveCursor( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
1149 textCursor().removeSelectedText();
1152 QTextEdit::keyPressEvent( event );
1156 aCursor.setPosition( document()->end().previous().position() + promptSize() );
1157 setTextCursor( aCursor );
1158 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1162 case Qt::Key_Insert :
1163 // <Insert> key: process as follows
1164 // - with <Ctrl> modifier key pressed: copy()
1165 // - with <Shift> modifier key pressed: paste() to the command line
1167 if ( ctrlPressed ) {
1170 else if ( shftPressed ) {
1174 QTextEdit::keyPressEvent( event );
1183 \brief Handle notification event coming from Python dispatcher.
1184 \param event notification event
1186 void PyConsole_Editor::customEvent( QEvent* event )
1188 switch( (int) event->type() )
1190 case PyConsole_PrintEvent::EVENT_ID:
1192 PyConsole_PrintEvent* pe = (PyConsole_PrintEvent*)event;
1193 addText( pe->text(), false, pe->isError() );
1196 case PyConsole_CompletionEvent::EVENT_ID:
1198 PyConsole_CompletionEvent* ce = (PyConsole_CompletionEvent*)event;
1199 bool status = ce->status();
1200 QStringList matches = ce->matches();
1201 QString doc = ce->doc();
1204 // completion was successful
1205 QTextCursor aCursor = textCursor();
1207 if ( matches.isEmpty() ) {
1208 // completion successful but there are no matches.
1210 myComplCursorPos = -1;
1214 if ( matches.size() == 1 ) {
1215 // there's only one match - complete directly and update doc string window
1216 aCursor.insertText( matches[0].mid( myComplAfterPoint.size() ) );
1218 if ( doc.isEmpty() )
1219 emit updateDoc( formatDocHTML( QString( "(%1)\n" ).arg( tr( "NO_DOC_AVAILABLE" ) ) ) );
1221 emit updateDoc( formatDocHTML( doc ) );
1224 // there are several matches
1226 // detect if there is a common base to all available completion
1227 // in this case append this base to the text
1228 QString base = extractCommon( matches );
1229 aCursor.insertText( base.mid( myComplAfterPoint.size() ) );
1231 // if this happens to match exactly the first completion
1233 if ( base == matches[0] )
1234 emit updateDoc( formatDocHTML( doc ) );
1236 // print all matching completion in a "undo-able" block
1237 int cursorPos = aCursor.position();
1238 aCursor.insertBlock();
1239 aCursor.beginEditBlock();
1241 // insert all matches
1243 cf.setForeground( QBrush( Qt::darkGreen ) );
1244 aCursor.setCharFormat( cf );
1245 aCursor.insertText( formatCompletion( matches ) );
1246 aCursor.endEditBlock();
1248 // position cursor where it was before inserting the completion list
1249 aCursor.setPosition( cursorPos );
1250 setTextCursor( aCursor );
1254 // completion failed
1256 myComplCursorPos = -1;
1260 case PyInterp_Event::ES_OK:
1261 case PyInterp_Event::ES_ERROR:
1263 // clear command buffer
1264 myCommandBuffer.truncate( 0 );
1265 // add caret return line if necessary
1266 QTextBlock par = document()->end().previous();
1267 QString txt = par.text();
1268 txt.truncate( txt.length() - 1 );
1269 // IPAL19397 : addText moved to handleReturn() method
1270 //if ( !txt.isEmpty() )
1271 // addText( "", true );
1272 // set "ready" prompt
1273 myPrompt = READY_PROMPT;
1274 addText( myPrompt );
1275 // unset busy cursor
1277 // stop event loop (if running)
1279 myEventLoop->exit();
1280 // if we are in multi_paste_mode, process the next item
1281 multiLineProcessNextLine();
1284 case PyInterp_Event::ES_INCOMPLETE:
1286 // extend command buffer (multi-line command)
1287 myCommandBuffer.append( "\n" );
1288 // add caret return line if necessary
1289 QTextBlock par = document()->end().previous();
1290 QString txt = par.text();
1291 txt.truncate( txt.length() - 1 );
1292 // IPAL19397 : addText moved to handleReturn() method
1293 //if ( !txt.isEmpty() )
1294 // addText( "", true );
1296 myPrompt = DOTS_PROMPT;
1297 addText( myPrompt/*, true*/ ); // IPAL19397
1298 // unset busy cursor
1300 // stop event loop (if running)
1302 myEventLoop->exit();
1303 // if we are in multi_paste_mode, process the next item
1304 multiLineProcessNextLine();
1308 QTextEdit::customEvent( event );
1311 // unset read-only state
1312 setReadOnly( false );
1313 // unset history browsing mode
1314 myCmdInHistory = -1;
1316 if ( (int)event->type() == (int)PyInterp_Event::ES_OK && myQueue.count() > 0 )
1318 // process the next scheduled command from the queue (if there is any)
1319 QString nextcmd = myQueue[0];
1320 myQueue.pop_front();
1326 \brief "Copy" operation.
1328 Reimplemented from Qt.
1329 Warning! In Qt this method is not virtual.
1331 void PyConsole_Editor::cut()
1333 QTextCursor aCursor = textCursor();
1334 if ( aCursor.hasSelection() ) {
1335 QApplication::clipboard()->setText( aCursor.selectedText() );
1336 int startSelection = aCursor.selectionStart();
1337 if ( startSelection < document()->end().previous().position() + promptSize() )
1338 startSelection = document()->end().previous().position() + promptSize();
1339 int endSelection = aCursor.selectionEnd();
1340 if ( endSelection < document()->end().previous().position() + promptSize() )
1341 endSelection = document()->end().previous().position() + promptSize();
1342 aCursor.setPosition( startSelection );
1343 aCursor.setPosition( endSelection, QTextCursor::KeepAnchor );
1344 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1345 setTextCursor( aCursor );
1346 textCursor().removeSelectedText();
1351 \brief "Paste" operation.
1353 Reimplemented from Qt.
1354 Warning! In Qt this method is not virtual.
1356 void PyConsole_Editor::paste()
1358 QTextCursor aCursor = textCursor();
1359 if ( aCursor.hasSelection() ) {
1360 int startSelection = aCursor.selectionStart();
1361 if ( startSelection < document()->end().previous().position() + promptSize() )
1362 startSelection = document()->end().previous().position() + promptSize();
1363 int endSelection = aCursor.selectionEnd();
1364 if ( endSelection < document()->end().previous().position() + promptSize() )
1365 endSelection = document()->end().previous().position() + promptSize();
1366 aCursor.setPosition( startSelection );
1367 aCursor.setPosition( endSelection, QTextCursor::KeepAnchor );
1368 horizontalScrollBar()->setValue( horizontalScrollBar()->minimum() );
1369 setTextCursor( aCursor );
1370 textCursor().removeSelectedText();
1372 if ( textCursor().position() < document()->end().previous().position() + promptSize() )
1373 moveCursor( QTextCursor::End );
1378 \brief "Clear" operation.
1380 Reimplemented from Qt.
1381 Warning! In Qt this method is not virtual.
1383 void PyConsole_Editor::clear()
1386 if ( isShowBanner() )
1387 addText( banner() );
1388 myPrompt = READY_PROMPT;
1389 addText( myPrompt );
1393 \brief Dumps recorded Python commands to the file
1394 \param fileName path to the dump file
1395 \return \c true if dump operation succeeded or \c false otherwise
1397 bool PyConsole_Editor::dump( const QString& fileName )
1400 if ( !fileName.isEmpty() ) {
1401 QFile file( fileName );
1402 if ( file.open( QFile::WriteOnly ) ) {
1403 QTextStream out( &file );
1404 for ( int i = 0; i < myHistory.count(); i++ ) {
1405 #if QT_VERSION >= QT_VERSION_CHECK(5,14,0)
1406 out << myHistory[i] << Qt::endl;
1408 out << myHistory[i] << endl;
1419 \brief Dump menu action slot
1421 void PyConsole_Editor::dump()
1425 QString fileName = getDumpFileName();
1427 if ( fileName.isEmpty() )
1430 if ( dump( fileName ) )
1433 QMessageBox::warning( this,
1435 tr( "ERR_FILE_NOT_WRITEABLE" ) );
1440 \brief Get file name for Dump commands operation.
1442 This function can be redefined in successor classes to show application
1443 specific dialog box.
1445 \return path to the dump file
1447 QString PyConsole_Editor::getDumpFileName()
1449 return QFileDialog::getSaveFileName( this,
1450 tr( "GET_DUMP_COMMANDS_FILENAME" ),
1452 QString( "%1 (*.py)" ).arg( tr( "PYTHON_SCRIPTS" ) ) );
1456 \brief Get file name for Log Python trace operation.
1458 This function can be redefined in successor classes to show application
1459 specific dialog box.
1461 \return path to the log file
1463 QString PyConsole_Editor::getLogFileName()
1465 return QFileDialog::getSaveFileName( this,
1466 tr( "GET_PYTHON_TRACE_FILENAME" ),
1468 QString( "%1 (*.log *.txt)" ).arg( tr( "LOG_FILES" ) ) );
1472 \brief Start python trace logging
1473 \param fileName the path to the log file
1474 \return \c true if operation succeeded or \c false otherwise
1475 (for example, if file is not writeable)
1478 bool PyConsole_Editor::startLog( const QString& fileName )
1480 // stop possibly already running logging
1485 if ( !fileName.isEmpty() ) {
1486 QFile file( fileName );
1487 if ( file.open( QFile::WriteOnly ) ) {
1489 myLogFile = fileName;
1497 \brief Start log action slot
1499 void PyConsole_Editor::startLog()
1503 QString fileName = getLogFileName();
1505 if ( fileName.isEmpty() )
1508 if ( startLog( fileName ) )
1511 QMessageBox::warning( this,
1513 tr( "File is not writable" ) );
1518 \brief Stop log action slot
1520 Stops Python trace logging.
1522 void PyConsole_Editor::stopLog()
1524 myLogFile = QString();
1528 \brief Put data to the log file
1530 void PyConsole_Editor::putLog( const QString& s )
1532 if ( !myLogFile.isEmpty() ) {
1533 QFile file( myLogFile );
1534 if ( !file.open( QFile::Append ) )
1537 QTextStream out( &file );
1545 \brief Handle properly multi-line pasting. Qt documentation recommends overriding this function.
1546 If the pasted text doesn't contain a line return, no special treatment is done.
1549 void PyConsole_Editor::insertFromMimeData(const QMimeData* source)
1551 if ( myMultiLinePaste )
1554 if ( source->hasText() ) {
1555 QString s = source->text();
1556 if ( s.contains( "\n" ) )
1557 multilinePaste( s );
1559 QTextEdit::insertFromMimeData( source );
1562 QTextEdit::insertFromMimeData( source );
1567 Start multi-line paste operation
1570 void PyConsole_Editor::multilinePaste( const QString& s )
1572 // Turn on multi line pasting mode
1573 myMultiLinePaste = true;
1575 // Split string data to lines
1577 s2.replace( "\r", "" ); // Windows string format converted to Unix style
1578 #if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
1579 QStringList lst = s2.split( QChar('\n'), Qt::KeepEmptyParts );
1581 QStringList lst = s2.split( QChar('\n'), QString::KeepEmptyParts );
1584 // Perform the proper paste operation for the first line to handle the case where
1585 // something was already there
1587 source.setText( lst[0] );
1588 QTextEdit::insertFromMimeData( &source );
1590 // Prepare what will have to be executed after the first line
1591 myMultiLineContent.clear();
1592 for ( int i = 1; i < lst.size(); ++i )
1593 myMultiLineContent.enqueue( lst[i] );
1595 // Trigger the execution of the first (mixed) line
1598 // See customEvent() and multiLineProcessNext() for the rest of the handling.
1602 \brief Process the next line in the queue of multi-line paste operation; called
1603 from customEvent() function
1606 void PyConsole_Editor::multiLineProcessNextLine()
1608 // not in multi-line paste mode
1609 if ( !myMultiLinePaste || myMultiLineContent.isEmpty() )
1612 QString line = myMultiLineContent.dequeue();
1613 if ( myMultiLineContent.empty() )
1615 // this isa last line in the queue, just paste it
1616 addText( line, false, false );
1617 myMultiLinePaste = false;
1621 // paste the line and simulate a <RETURN> key stroke
1622 addText( line, false, false );
1628 \brief Clear results of completion
1630 void PyConsole_Editor::clearCompletion()
1632 // delete completion text if present
1634 // remove completion display
1636 // remove trailing line return:
1637 QTextCursor tc( textCursor() );
1638 tc.setPosition( document()->characterCount()-1 );
1639 setTextCursor( tc );
1640 textCursor().deletePreviousChar();
1641 // TODO: before wait for any <Tab> event to be completed
1647 \brief Format completion results - this is where we should create 3 columns etc ...
1648 \param matches list of possible completions
1649 \return result string
1651 QString PyConsole_Editor::formatCompletion( const QStringList& matches ) const
1653 static const int MAX_COMPLETIONS = 70;
1656 int sz = matches.size();
1658 if ( sz > MAX_COMPLETIONS )
1659 result.append( QString( "[%1]" ).arg( tr( "TOO_MANY_MATCHES" ) ) );
1661 for ( int i = 0; i < qMin( sz, MAX_COMPLETIONS); ++i )
1662 result.append( matches[i] );
1664 return result.join( "\n" );
1668 \brief Format the doc string in HTML format with the first line in bold blue
1669 \param doc initial doc string
1672 QString PyConsole_Editor::formatDocHTML( const QString& doc ) const
1674 static const char* templ = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" " \
1675 "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n" \
1676 "<html><head><meta name=\"qrichtext\" content=\"1\" /> " \
1677 "<style type=\"text/css\">\np, li { white-space: pre-wrap; }\n</style> " \
1678 "</head><body style=\" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;\">\n" \
1679 "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"> " \
1680 "<span style=\" font-weight:600; color:#0000ff;\">%1</span></p> " \
1681 "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%2</p> " \
1684 QString fst, rest("");
1686 // Extract first line of doc
1687 int idx = doc.indexOf( "\n" );
1689 fst = doc.left( idx );
1690 rest = doc.mid( idx+1 );
1696 fst = fst.replace( "\n", " " );
1697 rest = rest.replace(" \n", " " );
1698 return QString( templ ).arg( fst ).arg( rest );
1702 \fn void PyConsole_Editor::updateDoc( const QString& doc);
1703 \brief Signal emitted by the editor widget when the doc string should be updated.
1704 \param doc a HTML block with the formatted doc string.
1705 \todo currently this signal is left uncaught.
1709 \brief Extract the common leading part of all strings in matches.
1713 QString PyConsole_Editor::extractCommon( const QStringList& matches ) const
1715 QString result = "";
1717 if ( matches.size() > 1 ) {
1720 while ( ok && l+1 < matches[0].size() ) {
1721 QString match = matches[0].left( l+1 );
1722 for ( int j = 1; j < matches.size() && ok; j++ )
1723 ok = matches[j].startsWith( match );
1727 result = matches[0].left( l );
1734 \brief Useful method to get banner from Python interpreter
1737 QString PyConsole_Editor::banner() const
1739 return myInterp->getBanner().c_str();