9 #include <QTextCharFormat>
13 #include "PyConsole_EnhEditor.h"
14 #include "PyConsole_EnhInterp.h"
15 #include "PyConsole_Request.h"
16 #include "PyInterp_Dispatcher.h"
18 // Initialize list of valid separators
19 static const char * tmp_a[] = {" ", "(", "[","+", "-", "*", "/", ";", "^", "="};
20 const std::vector<QString> PyConsole_EnhEditor::SEPARATORS = \
21 std::vector<QString>(tmp_a, tmp_a + sizeof(tmp_a)/sizeof(tmp_a[0]));
25 * @param interp the interpreter linked to the editor
26 * @param parent parent widget
28 PyConsole_EnhEditor::PyConsole_EnhEditor(PyConsole_EnhInterp * interp, QWidget * parent) :
29 PyConsole_Editor(interp, parent),
32 _multi_line_paste(false),
35 document()->setUndoRedoEnabled(true);
39 * Overrides. Catches the TAB and Ctrl+TAB combinations.
42 void PyConsole_EnhEditor::keyPressEvent ( QKeyEvent* event)
44 // check if <Ctrl> is pressed
45 bool ctrlPressed = event->modifiers() & Qt::ControlModifier;
46 // check if <Shift> is pressed
47 bool shftPressed = event->modifiers() & Qt::ShiftModifier;
49 if (event->key() == Qt::Key_Tab && !shftPressed)
58 PyConsole_Editor::keyPressEvent(event);
62 // If ctrl is not pressed (and sth else is pressed with it),
63 // or if ctrl is not pressed alone
64 if (!ctrlPressed || (ctrlPressed && event->key() != Qt::Key_Control))
69 // Discard ctrl pressed alone:
70 if (event->key() != Qt::Key_Control)
71 PyConsole_Editor::keyPressEvent(event);
76 * Whenever the mouse is clicked, clear the completion.
79 void PyConsole_EnhEditor::mousePressEvent(QMouseEvent* e)
83 PyConsole_Editor::mousePressEvent(e);
87 * Clear in the editor the block of text displayed after having hit <TAB>.
89 void PyConsole_EnhEditor::clearCompletion()
91 // Delete completion text if present
94 // Remove completion display
96 // Remove trailing line return:
97 QTextCursor tc(textCursor());
98 tc.setPosition(document()->characterCount()-1);
100 textCursor().deletePreviousChar();
101 // TODO: before wait for any TAB event to be completed
102 static_cast<PyConsole_EnhInterp *>(myInterp)->clearCompletion();
108 * Handle the sequence of events after having hit <TAB>
110 void PyConsole_EnhEditor::handleTab()
114 // Already tab mode - nothing to do !
118 QTextCursor cursor(textCursor());
120 // Cursor at end of input
121 cursor.movePosition(QTextCursor::End);
122 setTextCursor(cursor);
124 // Save cursor position if needed
125 if (_cursor_pos == -1)
126 _cursor_pos = textCursor().position();
129 QTextBlock par = document()->end().previous();
130 if ( !par.isValid() ) return;
132 // Switch to completion mode
135 QString cmd = par.text().mid(promptSize());
137 // Post completion request
138 // Editor will be informed via a custom event that completion has been run
139 PyInterp_Request* req = createTabRequest(cmd);
140 PyInterp_Dispatcher::Get()->Exec(req);
144 * Handles what happens after hitting Ctrl-TAB
146 void PyConsole_EnhEditor::handleBackTab()
148 QTextCursor cursor(textCursor());
150 if (_cursor_pos == -1)
152 // Invalid cursor position - we can't do anything:
155 // Ensure cursor is at the end of command line
156 cursor.setPosition(_cursor_pos);
157 cursor.movePosition(QTextCursor::EndOfLine);
160 // Delete last completed text
161 int i = cursor.position() - _cursor_pos;
162 cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, i);
163 cursor.removeSelectedText();
168 * Create the Python requested that will be posted to the interpreter to
169 * get the completions.
170 * @param input line typed by the user at the time TAB was hit
171 * @return a CompletionCommand
172 * @sa CompletionCommand
174 PyInterp_Request* PyConsole_EnhEditor::createTabRequest( const QString& input )
176 // Parse input to extract on what part the dir() has to be executed
177 QString input2(input);
179 // Split up to the last syntaxical separator
181 for (std::vector<QString>::const_iterator i = SEPARATORS.begin(); i != SEPARATORS.end(); i++)
183 int j = input2.lastIndexOf(*i);
188 input2 = input.mid(lastSp+1);
190 // Detect a qualified name (with a point)
191 int lastPt = input2.lastIndexOf(QString("."));
193 // Split the 2 surrounding parts of the qualified name
196 _compl_before_point = input2.left(lastPt);
197 _compl_after_point = input2.mid(lastPt+1);
201 // No point found - do a global matching -
202 // (the following will call dir() with an empty string)
203 _compl_after_point = input2;
204 _compl_before_point = QString("");
207 return new CompletionCommand( static_cast<PyConsole_EnhInterp *>(myInterp), _compl_before_point,
208 _compl_after_point, this, isSync() );
212 * Format completion results - this is where we should create 3 columns etc ...
213 * @param matches list of possible completions
214 * @param result return value
216 void PyConsole_EnhEditor::formatCompletion(const std::vector<QString> & matches, QString & result) const
218 int sz = matches.size();
220 if (sz > MAX_COMPLETIONS)
222 sz = MAX_COMPLETIONS;
223 result.append("[Too many matches! Displaying first ones only ...]\n");
226 for (int i = 0; i < sz; ++i)
228 result.append(matches[i]);
234 * Override. Catches the events generated by the enhanced interpreter after the execution
235 * of a completion request.
238 void PyConsole_EnhEditor::customEvent( QEvent* event )
240 std::vector<QString> matches;
241 QString first_match, comple_text, doc, base;
242 QTextCursor cursor(textCursor());
245 PyConsole_EnhInterp * interp = static_cast<PyConsole_EnhInterp *>(myInterp);
248 switch( event->type() )
250 case PyInterp_Event::ES_TAB_COMPLETE_OK:
251 // Extract corresponding matches from the interpreter
252 matches = interp->getLastMatches();
254 if (matches.size() == 0)
256 // Completion successful but nothing returned.
262 // Only one match - complete directly and update doc string window
263 doc = interp->getDocStr();
264 if (matches.size() == 1)
266 first_match = matches[0].mid(_compl_after_point.size());
267 cursor.insertText(first_match);
269 if (doc == QString(""))
270 emit updateDoc(formatDocHTML("(no documentation available)\n"));
272 emit updateDoc(formatDocHTML(doc));
276 // Detect if there is a common base to all available completion
277 // In this case append this base to the text already
278 extractCommon(matches, base);
279 first_match = base.mid(_compl_after_point.size());
280 cursor.insertText(first_match);
282 // If this happens to match exaclty the first completion
284 if (base == matches[0])
286 doc = formatDocHTML(doc);
290 // Print all matching completion in a "undo-able" block
291 cursorPos = cursor.position();
292 cursor.insertBlock();
293 cursor.beginEditBlock();
295 // Insert all matches
297 cf.setForeground(QBrush(Qt::darkGreen));
298 cursor.setCharFormat(cf);
299 formatCompletion(matches, comple_text);
300 cursor.insertText(comple_text);
301 cursor.endEditBlock();
303 // Position cursor where it was before inserting the completion list:
304 cursor.setPosition(cursorPos);
305 setTextCursor(cursor);
308 case PyInterp_Event::ES_TAB_COMPLETE_ERR:
309 // Tab completion was unsuccessful, switch off mode:
313 case PyInterp_Event::ES_OK:
314 case PyInterp_Event::ES_ERROR:
315 case PyInterp_Event::ES_INCOMPLETE:
316 // Before everything else, call super()
317 PyConsole_Editor::customEvent(event);
318 // If we are in multi_paste_mode, process the next item:
319 multiLineProcessNextLine();
322 PyConsole_Editor::customEvent( event );
328 * Extract the common leading part of all strings in matches.
332 void PyConsole_EnhEditor::extractCommon(const std::vector<QString> & matches, QString & result) const
337 if (matches.size() < 2)
342 if (charIdx >= matches[0].size())
344 QChar ch = matches[0][charIdx];
345 for (size_t j = 1; j < matches.size(); j++)
346 if (charIdx >= matches[j].size() || matches[j][charIdx] != ch)
354 * Format the doc string in HTML format with the first line in bold blue
355 * @param doc initial doc string
356 * @return HTML string
358 QString PyConsole_EnhEditor::formatDocHTML(const QString & doc) const
360 QString templ = QString("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" ") +
361 QString(" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n ") +
362 QString("<html><head><meta name=\"qrichtext\" content=\"1\" /> ") +
363 QString("<style type=\"text/css\">\np, li { white-space: pre-wrap; }\n</style> ") +
364 QString("</head><body style=\" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;\">\n") +
365 QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">") +
366 QString("<span style=\" font-weight:600; color:#0000ff;\">%1</span></p>") +
367 QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">%2</p>") +
368 QString("</body></html>");
370 QString fst, rest("");
372 // Extract first line of doc
373 int idx = doc.indexOf("\n");
377 rest = doc.mid(idx+1);
384 fst = fst.replace("\n", " ");
385 rest = rest.replace("\n", " ");
386 return templ.arg(fst).arg(rest);
390 * Handle properly multi-line pasting. Qt4 doc recommends overriding this function.
391 * If the pasted text doesn't contain a line return, no special treatment is done.
394 void PyConsole_EnhEditor::insertFromMimeData(const QMimeData * source)
396 if (_multi_line_paste)
399 if (source->hasText())
401 QString s = source->text();
402 if (s.contains("\n"))
405 PyConsole_Editor::insertFromMimeData(source);
409 PyConsole_Editor::insertFromMimeData(source);
414 void PyConsole_EnhEditor::multilinePaste(const QString & s)
416 // Turn on multi line pasting mode
417 _multi_line_paste = true;
421 s2.replace("\r", ""); // Windows string format converted to Unix style
423 QStringList lst = s2.split(QChar('\n'), QString::KeepEmptyParts);
425 // Perform the proper paste operation for the first line to handle the case where
426 // sth was already there:
428 source.setText(lst[0]);
429 PyConsole_Editor::insertFromMimeData(&source);
431 // Prepare what will have to be executed after the first line:
432 _multi_line_content = std::queue<QString>();
433 for (int i = 1; i < lst.size(); ++i)
434 _multi_line_content.push(lst[i]);
436 // Trigger the execution of the first (mixed) line
439 // See customEvent() and multiLineProcessNext() for the rest of the handling.
443 * Process the next line in the queue of a multiple copy/paste:
445 void PyConsole_EnhEditor::multiLineProcessNextLine()
447 if (!_multi_line_paste)
450 QString line(_multi_line_content.front());
451 _multi_line_content.pop();
452 if (!_multi_line_content.size())
454 // last line in the queue, just paste it
455 addText(line, false, false);
456 _multi_line_paste = false;
460 // paste the line and simulate a <RETURN> key stroke
461 addText(line, false, false);