1 // Copyright (C) 2007-2013 CEA/DEN, EDF R&D
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.
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>
31 #include "PyConsole_EnhEditor.h"
32 #include "PyConsole_EnhInterp.h"
33 #include "PyConsole_Request.h"
34 #include "PyInterp_Dispatcher.h"
36 // Initialize list of valid separators
37 static const char * tmp_a[] = {" ", "(", "[","+", "-", "*", "/", ";", "^", "="};
38 const std::vector<QString> PyConsole_EnhEditor::SEPARATORS = \
39 std::vector<QString>(tmp_a, tmp_a + sizeof(tmp_a)/sizeof(tmp_a[0]));
43 * @param interp the interpreter linked to the editor
44 * @param parent parent widget
46 PyConsole_EnhEditor::PyConsole_EnhEditor(PyConsole_EnhInterp * interp, QWidget * parent) :
47 PyConsole_Editor(interp, parent),
50 _multi_line_paste(false),
53 document()->setUndoRedoEnabled(true);
57 * Overrides. Catches the TAB and Ctrl+TAB combinations.
60 void PyConsole_EnhEditor::keyPressEvent ( QKeyEvent* event)
62 // check if <Ctrl> is pressed
63 bool ctrlPressed = event->modifiers() & Qt::ControlModifier;
64 // check if <Shift> is pressed
65 bool shftPressed = event->modifiers() & Qt::ShiftModifier;
67 if (event->key() == Qt::Key_Tab && !shftPressed)
76 PyConsole_Editor::keyPressEvent(event);
80 // If ctrl is not pressed (and sth else is pressed with it),
81 // or if ctrl is not pressed alone
82 if (!ctrlPressed || (ctrlPressed && event->key() != Qt::Key_Control))
87 // Discard ctrl pressed alone:
88 if (event->key() != Qt::Key_Control)
89 PyConsole_Editor::keyPressEvent(event);
94 * Whenever the mouse is clicked, clear the completion.
97 void PyConsole_EnhEditor::mousePressEvent(QMouseEvent* e)
101 PyConsole_Editor::mousePressEvent(e);
105 * Clear in the editor the block of text displayed after having hit <TAB>.
107 void PyConsole_EnhEditor::clearCompletion()
109 // Delete completion text if present
112 // Remove completion display
114 // Remove trailing line return:
115 QTextCursor tc(textCursor());
116 tc.setPosition(document()->characterCount()-1);
118 textCursor().deletePreviousChar();
119 // TODO: before wait for any TAB event to be completed
120 static_cast<PyConsole_EnhInterp *>(myInterp)->clearCompletion();
126 * Handle the sequence of events after having hit <TAB>
128 void PyConsole_EnhEditor::handleTab()
132 // Already tab mode - nothing to do !
136 QTextCursor cursor(textCursor());
138 // Cursor at end of input
139 cursor.movePosition(QTextCursor::End);
140 setTextCursor(cursor);
142 // Save cursor position if needed
143 if (_cursor_pos == -1)
144 _cursor_pos = textCursor().position();
147 QTextBlock par = document()->end().previous();
148 if ( !par.isValid() ) return;
150 // Switch to completion mode
153 QString cmd = par.text().mid(promptSize());
155 // Post completion request
156 // Editor will be informed via a custom event that completion has been run
157 PyInterp_Request* req = createTabRequest(cmd);
158 PyInterp_Dispatcher::Get()->Exec(req);
162 * Handles what happens after hitting Ctrl-TAB
164 void PyConsole_EnhEditor::handleBackTab()
166 QTextCursor cursor(textCursor());
168 if (_cursor_pos == -1)
170 // Invalid cursor position - we can't do anything:
173 // Ensure cursor is at the end of command line
174 cursor.setPosition(_cursor_pos);
175 cursor.movePosition(QTextCursor::EndOfLine);
178 // Delete last completed text
179 int i = cursor.position() - _cursor_pos;
180 cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, i);
181 cursor.removeSelectedText();
186 * Create the Python requested that will be posted to the interpreter to
187 * get the completions.
188 * @param input line typed by the user at the time TAB was hit
189 * @return a CompletionCommand
190 * @sa CompletionCommand
192 PyInterp_Request* PyConsole_EnhEditor::createTabRequest( const QString& input )
194 // Parse input to extract on what part the dir() has to be executed
195 QString input2(input);
197 // Split up to the last syntaxical separator
199 for (std::vector<QString>::const_iterator i = SEPARATORS.begin(); i != SEPARATORS.end(); i++)
201 int j = input2.lastIndexOf(*i);
206 input2 = input.mid(lastSp+1);
208 // Detect a qualified name (with a point)
209 int lastPt = input2.lastIndexOf(QString("."));
211 // Split the 2 surrounding parts of the qualified name
214 _compl_before_point = input2.left(lastPt);
215 _compl_after_point = input2.mid(lastPt+1);
219 // No point found - do a global matching -
220 // (the following will call dir() with an empty string)
221 _compl_after_point = input2;
222 _compl_before_point = QString("");
225 return new CompletionCommand( static_cast<PyConsole_EnhInterp *>(myInterp), _compl_before_point,
226 _compl_after_point, this, isSync() );
230 * Format completion results - this is where we should create 3 columns etc ...
231 * @param matches list of possible completions
232 * @param result return value
234 void PyConsole_EnhEditor::formatCompletion(const std::vector<QString> & matches, QString & result) const
236 int sz = matches.size();
238 if (sz > MAX_COMPLETIONS)
240 sz = MAX_COMPLETIONS;
241 result.append("[Too many matches! Displaying first ones only ...]\n");
244 for (int i = 0; i < sz; ++i)
246 result.append(matches[i]);
252 * Override. Catches the events generated by the enhanced interpreter after the execution
253 * of a completion request.
256 void PyConsole_EnhEditor::customEvent( QEvent* event )
258 std::vector<QString> matches;
259 QString first_match, comple_text, doc, base;
260 QTextCursor cursor(textCursor());
263 PyConsole_EnhInterp * interp = static_cast<PyConsole_EnhInterp *>(myInterp);
266 switch( event->type() )
268 case PyInterp_Event::ES_TAB_COMPLETE_OK:
269 // Extract corresponding matches from the interpreter
270 matches = interp->getLastMatches();
272 if (matches.size() == 0)
274 // Completion successful but nothing returned.
280 // Only one match - complete directly and update doc string window
281 doc = interp->getDocStr();
282 if (matches.size() == 1)
284 first_match = matches[0].mid(_compl_after_point.size());
285 cursor.insertText(first_match);
287 if (doc == QString(""))
288 emit updateDoc(formatDocHTML("(no documentation available)\n"));
290 emit updateDoc(formatDocHTML(doc));
294 // Detect if there is a common base to all available completion
295 // In this case append this base to the text already
296 extractCommon(matches, base);
297 first_match = base.mid(_compl_after_point.size());
298 cursor.insertText(first_match);
300 // If this happens to match exaclty the first completion
302 if (base == matches[0])
304 doc = formatDocHTML(doc);
308 // Print all matching completion in a "undo-able" block
309 cursorPos = cursor.position();
310 cursor.insertBlock();
311 cursor.beginEditBlock();
313 // Insert all matches
315 cf.setForeground(QBrush(Qt::darkGreen));
316 cursor.setCharFormat(cf);
317 formatCompletion(matches, comple_text);
318 cursor.insertText(comple_text);
319 cursor.endEditBlock();
321 // Position cursor where it was before inserting the completion list:
322 cursor.setPosition(cursorPos);
323 setTextCursor(cursor);
326 case PyInterp_Event::ES_TAB_COMPLETE_ERR:
327 // Tab completion was unsuccessful, switch off mode:
331 case PyInterp_Event::ES_OK:
332 case PyInterp_Event::ES_ERROR:
333 case PyInterp_Event::ES_INCOMPLETE:
334 // Before everything else, call super()
335 PyConsole_Editor::customEvent(event);
336 // If we are in multi_paste_mode, process the next item:
337 multiLineProcessNextLine();
340 PyConsole_Editor::customEvent( event );
346 * Extract the common leading part of all strings in matches.
350 void PyConsole_EnhEditor::extractCommon(const std::vector<QString> & matches, QString & result) const
355 if (matches.size() < 2)
360 if (charIdx >= matches[0].size())
362 QChar ch = matches[0][charIdx];
363 for (int j = 1; j < matches.size(); j++)
364 if (charIdx >= matches[j].size() || matches[j][charIdx] != ch)
372 * Format the doc string in HTML format with the first line in bold blue
373 * @param doc initial doc string
374 * @return HTML string
376 QString PyConsole_EnhEditor::formatDocHTML(const QString & doc) const
378 QString templ = QString("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" ") +
379 QString(" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n ") +
380 QString("<html><head><meta name=\"qrichtext\" content=\"1\" /> ") +
381 QString("<style type=\"text/css\">\np, li { white-space: pre-wrap; }\n</style> ") +
382 QString("</head><body style=\" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;\">\n") +
383 QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">") +
384 QString("<span style=\" font-weight:600; color:#0000ff;\">%1</span></p>") +
385 QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%2</p>") +
386 QString("</body></html>");
388 QString fst, rest("");
390 // Extract first line of doc
391 int idx = doc.indexOf("\n");
395 rest = doc.mid(idx+1);
402 fst = fst.replace("\n", " ");
403 rest = rest.replace("\n", " ");
404 return templ.arg(fst).arg(rest);
408 * Handle properly multi-line pasting. Qt4 doc recommends overriding this function.
409 * If the pasted text doesn't contain a line return, no special treatment is done.
412 void PyConsole_EnhEditor::insertFromMimeData(const QMimeData * source)
414 if (_multi_line_paste)
417 if (source->hasText())
419 QString s = source->text();
420 if (s.contains("\n"))
423 PyConsole_Editor::insertFromMimeData(source);
427 PyConsole_Editor::insertFromMimeData(source);
432 void PyConsole_EnhEditor::multilinePaste(const QString & s)
434 // Turn on multi line pasting mode
435 _multi_line_paste = true;
439 s2.replace("\r", ""); // Windows string format converted to Unix style
441 QStringList lst = s2.split(QChar('\n'), QString::KeepEmptyParts);
443 // Perform the proper paste operation for the first line to handle the case where
444 // sth was already there:
446 source.setText(lst[0]);
447 PyConsole_Editor::insertFromMimeData(&source);
449 // Prepare what will have to be executed after the first line:
450 _multi_line_content = std::queue<QString>();
451 for (int i = 1; i < lst.size(); ++i)
452 _multi_line_content.push(lst[i]);
454 // Trigger the execution of the first (mixed) line
457 // See customEvent() and multiLineProcessNext() for the rest of the handling.
461 * Process the next line in the queue of a multiple copy/paste:
463 void PyConsole_EnhEditor::multiLineProcessNextLine()
465 if (!_multi_line_paste)
468 QString line(_multi_line_content.front());
469 _multi_line_content.pop();
470 if (!_multi_line_content.size())
472 // last line in the queue, just paste it
473 addText(line, false, false);
474 _multi_line_paste = false;
478 // paste the line and simulate a <RETURN> key stroke
479 addText(line, false, false);