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>
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_Interp* 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
121 myInterp->clearCompletion();
127 * Handle the sequence of events after having hit <TAB>
129 void PyConsole_EnhEditor::handleTab()
133 // Already tab mode - nothing to do !
137 QTextCursor cursor(textCursor());
139 // Cursor at end of input
140 cursor.movePosition(QTextCursor::End);
141 setTextCursor(cursor);
143 // Save cursor position if needed
144 if (_cursor_pos == -1)
145 _cursor_pos = textCursor().position();
148 QTextBlock par = document()->end().previous();
149 if ( !par.isValid() ) return;
151 // Switch to completion mode
154 QString cmd = par.text().mid(promptSize());
156 // Post completion request
157 // Editor will be informed via a custom event that completion has been run
158 PyInterp_Request* req = createTabRequest(cmd);
159 PyInterp_Dispatcher::Get()->Exec(req);
163 * Handles what happens after hitting Ctrl-TAB
165 void PyConsole_EnhEditor::handleBackTab()
167 QTextCursor cursor(textCursor());
169 if (_cursor_pos == -1)
171 // Invalid cursor position - we can't do anything:
174 // Ensure cursor is at the end of command line
175 cursor.setPosition(_cursor_pos);
176 cursor.movePosition(QTextCursor::EndOfLine);
179 // Delete last completed text
180 int i = cursor.position() - _cursor_pos;
181 cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, i);
182 cursor.removeSelectedText();
187 * Create the Python requested that will be posted to the interpreter to
188 * get the completions.
189 * @param input line typed by the user at the time TAB was hit
190 * @return a CompletionCommand
191 * @sa CompletionCommand
193 PyInterp_Request* PyConsole_EnhEditor::createTabRequest( const QString& input )
195 // Parse input to extract on what part the dir() has to be executed
196 QString input2(input);
198 // Split up to the last syntaxical separator
200 for (std::vector<QString>::const_iterator i = SEPARATORS.begin(); i != SEPARATORS.end(); i++)
202 int j = input2.lastIndexOf(*i);
207 input2 = input.mid(lastSp+1);
209 // Detect a qualified name (with a point)
210 int lastPt = input2.lastIndexOf(QString("."));
212 // Split the 2 surrounding parts of the qualified name
215 _compl_before_point = input2.left(lastPt);
216 _compl_after_point = input2.mid(lastPt+1);
220 // No point found - do a global matching -
221 // (the following will call dir() with an empty string)
222 _compl_after_point = input2;
223 _compl_before_point = QString("");
226 return new CompletionCommand( myInterp, _compl_before_point,
227 _compl_after_point, this, isSync() );
231 * Format completion results - this is where we should create 3 columns etc ...
232 * @param matches list of possible completions
233 * @param result return value
235 void PyConsole_EnhEditor::formatCompletion(const QStringList& matches, QString& result) const
237 int sz = matches.size();
239 if (sz > MAX_COMPLETIONS)
241 sz = MAX_COMPLETIONS;
242 result.append("[Too many matches! Displaying first ones only ...]\n");
245 for (int i = 0; i < sz; ++i)
247 result.append(matches[i]);
253 * Override. Catches the events generated by the enhanced interpreter after the execution
254 * of a completion request.
257 void PyConsole_EnhEditor::customEvent( QEvent* event )
260 QString first_match, comple_text, doc, base;
261 QTextCursor cursor(textCursor());
266 switch( event->type() )
268 case PyInterp_Event::ES_TAB_COMPLETE_OK:
270 // Extract corresponding matches from the interpreter
271 matches = getInterp()->getLastMatches();
272 doc = getInterp()->getDocStr();
274 if (matches.size() == 0)
276 // Completion successful but nothing returned.
282 // Only one match - complete directly and update doc string window
283 if (matches.size() == 1)
285 first_match = matches[0].mid(_compl_after_point.size());
286 cursor.insertText(first_match);
289 emit updateDoc(formatDocHTML("(no documentation available)\n"));
291 emit updateDoc(formatDocHTML(doc));
295 // Detect if there is a common base to all available completion
296 // In this case append this base to the text already
297 extractCommon(matches, base);
298 first_match = base.mid(_compl_after_point.size());
299 cursor.insertText(first_match);
301 // If this happens to match exaclty the first completion
303 if (base == matches[0])
305 doc = formatDocHTML(doc);
309 // Print all matching completion in a "undo-able" block
310 cursorPos = cursor.position();
311 cursor.insertBlock();
312 cursor.beginEditBlock();
314 // Insert all matches
316 cf.setForeground(QBrush(Qt::darkGreen));
317 cursor.setCharFormat(cf);
318 formatCompletion(matches, comple_text);
319 cursor.insertText(comple_text);
320 cursor.endEditBlock();
322 // Position cursor where it was before inserting the completion list:
323 cursor.setPosition(cursorPos);
324 setTextCursor(cursor);
328 case PyInterp_Event::ES_TAB_COMPLETE_ERR:
330 // Tab completion was unsuccessful, switch off mode:
335 case PyInterp_Event::ES_OK:
336 case PyInterp_Event::ES_ERROR:
337 case PyInterp_Event::ES_INCOMPLETE:
339 // Before everything else, call super()
340 PyConsole_Editor::customEvent(event);
341 // If we are in multi_paste_mode, process the next item:
342 multiLineProcessNextLine();
347 PyConsole_Editor::customEvent( event );
354 * Extract the common leading part of all strings in matches.
358 void PyConsole_EnhEditor::extractCommon(const QStringList& matches, QString& result) const
363 if (matches.size() < 2)
368 if (charIdx >= matches[0].size())
370 QChar ch = matches[0][charIdx];
371 for (int j = 1; j < matches.size(); j++)
372 if (charIdx >= matches[j].size() || matches[j][charIdx] != ch)
380 * Format the doc string in HTML format with the first line in bold blue
381 * @param doc initial doc string
382 * @return HTML string
384 QString PyConsole_EnhEditor::formatDocHTML(const QString & doc) const
386 QString templ = QString("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" ") +
387 QString(" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n ") +
388 QString("<html><head><meta name=\"qrichtext\" content=\"1\" /> ") +
389 QString("<style type=\"text/css\">\np, li { white-space: pre-wrap; }\n</style> ") +
390 QString("</head><body style=\" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;\">\n") +
391 QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">") +
392 QString("<span style=\" font-weight:600; color:#0000ff;\">%1</span></p>") +
393 QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%2</p>") +
394 QString("</body></html>");
396 QString fst, rest("");
398 // Extract first line of doc
399 int idx = doc.indexOf("\n");
403 rest = doc.mid(idx+1);
410 fst = fst.replace("\n", " ");
411 rest = rest.replace("\n", " ");
412 return templ.arg(fst).arg(rest);
416 * Handle properly multi-line pasting. Qt4 doc recommends overriding this function.
417 * If the pasted text doesn't contain a line return, no special treatment is done.
420 void PyConsole_EnhEditor::insertFromMimeData(const QMimeData * source)
422 if (_multi_line_paste)
425 if (source->hasText())
427 QString s = source->text();
428 if (s.contains("\n"))
431 PyConsole_Editor::insertFromMimeData(source);
435 PyConsole_Editor::insertFromMimeData(source);
440 void PyConsole_EnhEditor::multilinePaste(const QString & s)
442 // Turn on multi line pasting mode
443 _multi_line_paste = true;
447 s2.replace("\r", ""); // Windows string format converted to Unix style
449 QStringList lst = s2.split(QChar('\n'), QString::KeepEmptyParts);
451 // Perform the proper paste operation for the first line to handle the case where
452 // sth was already there:
454 source.setText(lst[0]);
455 PyConsole_Editor::insertFromMimeData(&source);
457 // Prepare what will have to be executed after the first line:
458 _multi_line_content = std::queue<QString>();
459 for (int i = 1; i < lst.size(); ++i)
460 _multi_line_content.push(lst[i]);
462 // Trigger the execution of the first (mixed) line
465 // See customEvent() and multiLineProcessNext() for the rest of the handling.
469 * Process the next line in the queue of a multiple copy/paste:
471 void PyConsole_EnhEditor::multiLineProcessNextLine()
473 if (!_multi_line_paste)
476 QString line(_multi_line_content.front());
477 _multi_line_content.pop();
478 if (!_multi_line_content.size())
480 // last line in the queue, just paste it
481 addText(line, false, false);
482 _multi_line_paste = false;
486 // paste the line and simulate a <RETURN> key stroke
487 addText(line, false, false);