1 // Copyright (C) 2007-2015 CEA/DEN, EDF R&D, OPEN CASCADE
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Lesser General Public
5 // License as published by the Free Software Foundation; either
6 // version 2.1 of the License, or (at your option) any later version.
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 // Lesser General Public License for more details.
13 // You should have received a copy of the GNU Lesser General Public
14 // License along with this library; if not, write to the Free Software
15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
19 // Author : Adrien Bruneton (CEA/DEN)
20 // Created on: 4 avr. 2013
22 #include "PyConsole.h"
27 #include <QTextCursor>
28 #include <QTextCharFormat>
32 #include "PyConsole_EnhEditorBase.h"
33 #include "PyConsole_EnhInterp.h"
34 #include "PyConsole_Request.h"
35 #include "PyInterp_Dispatcher.h"
37 // Initialize list of valid separators
38 static const char * tmp_a[] = {" ", "(", "[","+", "-", "*", "/", ";", "^", "="};
39 const std::vector<QString> PyConsole_EnhEditorBase::SEPARATORS = \
40 std::vector<QString>(tmp_a, tmp_a + sizeof(tmp_a)/sizeof(tmp_a[0]));
44 * @param interp the interpreter linked to the editor
45 * @param parent parent widget
47 PyConsole_EnhEditorBase::PyConsole_EnhEditorBase(PyConsole_Interp* interp, QWidget* parent) :
48 PyConsole_EditorBase(interp, parent),
51 _multi_line_paste(false),
54 document()->setUndoRedoEnabled(true);
58 * Overrides. Catches the TAB and Ctrl+TAB combinations.
61 void PyConsole_EnhEditorBase::keyPressEvent ( QKeyEvent* event)
63 // check if <Ctrl> is pressed
64 bool ctrlPressed = event->modifiers() & Qt::ControlModifier;
65 // check if <Shift> is pressed
66 bool shftPressed = event->modifiers() & Qt::ShiftModifier;
68 if (event->key() == Qt::Key_Tab && !shftPressed)
77 PyConsole_EditorBase::keyPressEvent(event);
81 // If ctrl is not pressed (and sth else is pressed with it),
82 // or if ctrl is not pressed alone
83 if (!ctrlPressed || (ctrlPressed && event->key() != Qt::Key_Control))
88 // Discard ctrl pressed alone:
89 if (event->key() != Qt::Key_Control)
90 PyConsole_EditorBase::keyPressEvent(event);
95 * Whenever the mouse is clicked, clear the completion.
98 void PyConsole_EnhEditorBase::mousePressEvent(QMouseEvent* e)
102 PyConsole_EditorBase::mousePressEvent(e);
106 * Clear in the editor the block of text displayed after having hit <TAB>.
108 void PyConsole_EnhEditorBase::clearCompletion()
110 // Delete completion text if present
113 // Remove completion display
115 // Remove trailing line return:
116 QTextCursor tc(textCursor());
117 tc.setPosition(document()->characterCount()-1);
119 textCursor().deletePreviousChar();
120 // TODO: before wait for any TAB event to be completed
122 myInterp->clearCompletion();
128 * Handle the sequence of events after having hit <TAB>
130 void PyConsole_EnhEditorBase::handleTab()
134 // Already tab mode - nothing to do !
138 QTextCursor cursor(textCursor());
140 // Cursor at end of input
141 cursor.movePosition(QTextCursor::End);
142 setTextCursor(cursor);
144 // Save cursor position if needed
145 if (_cursor_pos == -1)
146 _cursor_pos = textCursor().position();
149 QTextBlock par = document()->end().previous();
150 if ( !par.isValid() ) return;
152 // Switch to completion mode
155 QString cmd = par.text().mid(promptSize());
157 // Post completion request
158 // Editor will be informed via a custom event that completion has been run
159 PyInterp_Request* req = createTabRequest(cmd);
160 PyInterp_Dispatcher::Get()->Exec(req);
164 * Handles what happens after hitting Ctrl-TAB
166 void PyConsole_EnhEditorBase::handleBackTab()
168 QTextCursor cursor(textCursor());
170 if (_cursor_pos == -1)
172 // Invalid cursor position - we can't do anything:
175 // Ensure cursor is at the end of command line
176 cursor.setPosition(_cursor_pos);
177 cursor.movePosition(QTextCursor::EndOfLine);
180 // Delete last completed text
181 int i = cursor.position() - _cursor_pos;
182 cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, i);
183 cursor.removeSelectedText();
188 * Create the Python requested that will be posted to the interpreter to
189 * get the completions.
190 * @param input line typed by the user at the time TAB was hit
191 * @return a CompletionCommand
192 * @sa CompletionCommand
194 PyInterp_Request* PyConsole_EnhEditorBase::createTabRequest( const QString& input )
196 // Parse input to extract on what part the dir() has to be executed
197 QString input2(input);
199 // Split up to the last syntaxical separator
201 for (std::vector<QString>::const_iterator i = SEPARATORS.begin(); i != SEPARATORS.end(); i++)
203 int j = input2.lastIndexOf(*i);
208 input2 = input.mid(lastSp+1);
210 // Detect a qualified name (with a point)
211 int lastPt = input2.lastIndexOf(QString("."));
213 // Split the 2 surrounding parts of the qualified name
216 _compl_before_point = input2.left(lastPt);
217 _compl_after_point = input2.mid(lastPt+1);
221 // No point found - do a global matching -
222 // (the following will call dir() with an empty string)
223 _compl_after_point = input2;
224 _compl_before_point = QString("");
227 return new CompletionCommand( myInterp, _compl_before_point,
228 _compl_after_point, this, isSync() );
232 * Format completion results - this is where we should create 3 columns etc ...
233 * @param matches list of possible completions
234 * @param result return value
236 void PyConsole_EnhEditorBase::formatCompletion(const QStringList& matches, QString& result) const
238 int sz = matches.size();
240 if (sz > MAX_COMPLETIONS)
242 sz = MAX_COMPLETIONS;
243 result.append("[Too many matches! Displaying first ones only ...]\n");
246 for (int i = 0; i < sz; ++i)
248 result.append(matches[i]);
254 * Override. Catches the events generated by the enhanced interpreter after the execution
255 * of a completion request.
258 void PyConsole_EnhEditorBase::customEvent( QEvent* event )
261 QString first_match, comple_text, doc, base;
262 QTextCursor cursor(textCursor());
267 switch( event->type() )
269 case PyInterp_Event::ES_TAB_COMPLETE_OK:
271 // Extract corresponding matches from the interpreter
272 matches = getInterp()->getLastMatches();
273 doc = getInterp()->getDocStr();
275 if (matches.size() == 0)
277 // Completion successful but nothing returned.
283 // Only one match - complete directly and update doc string window
284 if (matches.size() == 1)
286 first_match = matches[0].mid(_compl_after_point.size());
287 cursor.insertText(first_match);
290 emit updateDoc(formatDocHTML("(no documentation available)\n"));
292 emit updateDoc(formatDocHTML(doc));
296 // Detect if there is a common base to all available completion
297 // In this case append this base to the text already
298 extractCommon(matches, base);
299 first_match = base.mid(_compl_after_point.size());
300 cursor.insertText(first_match);
302 // If this happens to match exaclty the first completion
304 if (base == matches[0])
306 doc = formatDocHTML(doc);
310 // Print all matching completion in a "undo-able" block
311 cursorPos = cursor.position();
312 cursor.insertBlock();
313 cursor.beginEditBlock();
315 // Insert all matches
317 cf.setForeground(QBrush(Qt::darkGreen));
318 cursor.setCharFormat(cf);
319 formatCompletion(matches, comple_text);
320 cursor.insertText(comple_text);
321 cursor.endEditBlock();
323 // Position cursor where it was before inserting the completion list:
324 cursor.setPosition(cursorPos);
325 setTextCursor(cursor);
329 case PyInterp_Event::ES_TAB_COMPLETE_ERR:
331 // Tab completion was unsuccessful, switch off mode:
336 case PyInterp_Event::ES_OK:
337 case PyInterp_Event::ES_ERROR:
338 case PyInterp_Event::ES_INCOMPLETE:
340 // Before everything else, call super()
341 PyConsole_EditorBase::customEvent(event);
342 // If we are in multi_paste_mode, process the next item:
343 multiLineProcessNextLine();
348 PyConsole_EditorBase::customEvent( event );
355 * Extract the common leading part of all strings in matches.
359 void PyConsole_EnhEditorBase::extractCommon(const QStringList& matches, QString& result) const
364 if (matches.size() < 2)
369 if (charIdx >= matches[0].size())
371 QChar ch = matches[0][charIdx];
372 for (int j = 1; j < matches.size(); j++)
373 if (charIdx >= matches[j].size() || matches[j][charIdx] != ch)
381 * Format the doc string in HTML format with the first line in bold blue
382 * @param doc initial doc string
383 * @return HTML string
385 QString PyConsole_EnhEditorBase::formatDocHTML(const QString & doc) const
387 QString templ = QString("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" ") +
388 QString(" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n ") +
389 QString("<html><head><meta name=\"qrichtext\" content=\"1\" /> ") +
390 QString("<style type=\"text/css\">\np, li { white-space: pre-wrap; }\n</style> ") +
391 QString("</head><body style=\" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;\">\n") +
392 QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">") +
393 QString("<span style=\" font-weight:600; color:#0000ff;\">%1</span></p>") +
394 QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%2</p>") +
395 QString("</body></html>");
397 QString fst, rest("");
399 // Extract first line of doc
400 int idx = doc.indexOf("\n");
404 rest = doc.mid(idx+1);
411 fst = fst.replace("\n", " ");
412 rest = rest.replace("\n", " ");
413 return templ.arg(fst).arg(rest);
417 * Handle properly multi-line pasting. Qt4 doc recommends overriding this function.
418 * If the pasted text doesn't contain a line return, no special treatment is done.
421 void PyConsole_EnhEditorBase::insertFromMimeData(const QMimeData* source)
423 if (_multi_line_paste)
426 if (source->hasText())
428 QString s = source->text();
429 if (s.contains("\n"))
432 PyConsole_EditorBase::insertFromMimeData(source);
436 PyConsole_EditorBase::insertFromMimeData(source);
441 void PyConsole_EnhEditorBase::multilinePaste(const QString & s)
443 // Turn on multi line pasting mode
444 _multi_line_paste = true;
448 s2.replace("\r", ""); // Windows string format converted to Unix style
450 QStringList lst = s2.split(QChar('\n'), QString::KeepEmptyParts);
452 // Perform the proper paste operation for the first line to handle the case where
453 // sth was already there:
455 source.setText(lst[0]);
456 PyConsole_EditorBase::insertFromMimeData(&source);
458 // Prepare what will have to be executed after the first line:
459 _multi_line_content = std::queue<QString>();
460 for (int i = 1; i < lst.size(); ++i)
461 _multi_line_content.push(lst[i]);
463 // Trigger the execution of the first (mixed) line
466 // See customEvent() and multiLineProcessNext() for the rest of the handling.
470 * Process the next line in the queue of a multiple copy/paste:
472 void PyConsole_EnhEditorBase::multiLineProcessNextLine()
474 if (!_multi_line_paste)
477 QString line(_multi_line_content.front());
478 _multi_line_content.pop();
479 if (!_multi_line_content.size())
481 // last line in the queue, just paste it
482 addText(line, false, false);
483 _multi_line_paste = false;
487 // paste the line and simulate a <RETURN> key stroke
488 addText(line, false, false);