HAL
python_console.cpp
Go to the documentation of this file.
2 
4 #include "gui/gui_globals.h"
5 
6 #include <QKeyEvent>
7 #include <QHBoxLayout>
8 #include <QLabel>
9 #include <QPushButton>
10 #include <QTimer>
11 
16 
17 namespace hal
18 {
20  : QTextEdit(parent), mStandardPrompt(">>> "), mCompoundPrompt("... "), mInputPrompt("==> "),
21  mPromptBlockNumber(0), mPromptLength(0), mPromptEndPosition(0), mCompoundPromptEndPosition(0),
22  mPromptType(Standard), mInCompletion(false), mCurrentCompoundInput(""), mCurrentInput(""), mCurrentHistoryIndex(-1), mCurrentCompleterIndex(0),
23  mHistory(std::make_shared<PythonConsoleHistory>())
24  {
25  this->document()->setMaximumBlockCount(1000);
27  setUndoRedoEnabled(false);
29 
30  gPythonContext->setConsole(this);
31  gPythonContext->interpretForeground("print(\"Python \" + sys.version)");
32  gPythonContext->interpretForeground("print(sys.executable + \" on \" + sys.platform)");
33  displayPrompt();
34  mAbortThreadWidget = new PythonConsoleAbortThread(this);
35  connect(MainWindow::sSettingStyle,&SettingsItemDropdown::intChanged,this,&PythonConsole::handleStyleChanged);
36  }
37 
39  {
40  gPythonContext->setConsole(nullptr);
41  }
42 
43  void PythonConsole::handleStyleChanged(int istyle)
44  {
45  Q_UNUSED(istyle);
47  }
48 
49  void PythonConsole::keyPressEventInputMode(QKeyEvent *e)
50  {
51  mCurrentHistoryIndex = -1;
52  switch (e->key())
53  {
54  case Qt::Key_Return:
55  case Qt::Key_Enter:
56  {
58  QString input = getCurrentCommand();
60  cursor.movePosition(QTextCursor::End);
61  cursor.insertText("\n");
62  Q_EMIT inputReceived(input);
63  break;
64  }
65  default:
66  if (textCursor().selectionStart() < mPromptEndPosition)
67  {
69  }
70  mInCompletion = false;
71  }
72  }
73 
75  {
76  auto cursor = textCursor();
77  if (textCursor().hasSelection())
78  {
80  {
81  e->accept();
82  copy();
83  return;
84  }
85  else if (e->matches(QKeySequence::Cut))
86  {
87  // cut cutable stuff and copy rest
88  return;
89  }
90  else if (e->key() == Qt::Key_Control)
91  {
92  return;
93  }
94  }
95 
96  if (isInputMode())
97  {
98  keyPressEventInputMode(e);
99  }
100  else
101  {
102  switch (e->key())
103  {
104  case Qt::Key_Return:
105  case Qt::Key_Enter:
108  mCurrentHistoryIndex = -1;
109  return;
110 
111  case Qt::Key_Backspace:
112  if (textCursor().hasSelection())
113  {
114  if (textCursor().selectionStart() >= mPromptEndPosition)
115  {
116  break;
117  }
118  else
119  {
120  return;
121  }
122  }
123  else
124  {
125  if (textCursor().selectionStart() > mPromptEndPosition)
126  {
127  break;
128  }
129  else
130  {
131  return;
132  }
133  }
134 
135  case Qt::Key_Up:
137  return;
138 
139  case Qt::Key_Down:
141  return;
142 
143  case Qt::Key_Left:
144  if (e->modifiers() & Qt::ControlModifier) // Needed for macOS
145  {
146  cursor.setPosition(mPromptEndPosition);
148  return;
149  }
150  if (textCursor().hasSelection())
151  {
152  if (textCursor().selectionStart() >= mPromptEndPosition)
153  {
154  break;
155  }
156  else
157  {
158  return;
159  }
160  }
161  else
162  {
163  if (textCursor().selectionStart() > mPromptEndPosition)
164  {
165  break;
166  }
167  else
168  {
169  return;
170  }
171  }
172  case Qt::Key_Home:
173  cursor.setPosition(mPromptEndPosition);
175  return;
176  case Qt::Key_End:
177  cursor.movePosition(QTextCursor::End);
179  return;
180  case Qt::Key_Right:
181  if (e->modifiers() & Qt::ControlModifier) // Needed for macOS
182  {
183  cursor.movePosition(QTextCursor::End);
185  return;
186  }
187  break;
188 
189  case Qt::Key_Tab:
191  return;
192 
193  default:
194  if (textCursor().selectionStart() < mPromptEndPosition)
195  {
197  }
198  mInCompletion = false;
199  mCurrentHistoryIndex = -1;
200  }
201  }
203  }
204 
206  {
207  // m_position = textCursor().position();
208  // if (event->button() == Qt::MidButton)
209  // {
210  // copy();
211  // QTextCursor cursor = cursorForPosition(event->pos());
212  // setTextCursor(cursor);
213  // paste();
214  // return;
215  // }
216  // cursor.movePosition(QTextCursor::End);
218  }
219 
220  void PythonConsole::insertAtEnd(const QString& text, QColor textColor)
221  {
224  insertPlainText(QString(text));
225  }
226 
228  {
230  }
231 
233  {
234  QString append_out = output;
235  if (!append_out.endsWith("\n"))
236  append_out += "\n";
237  insertAtEnd(append_out, PythonConsoleQssAdapter::instance()->errorColor());
238  }
239 
241  {
243  }
244 
246  {
247  if (state)
248  mPromptType = Input;
249  else
250  mPromptType = Standard;
251  }
252 
254  {
255  //QTextCursor cursor = textCursor();
257  cursor.movePosition(QTextCursor::End);
258  //DEBUG SAVE FORMATS AS MEMBERS
259  QTextCharFormat format;
260  format.setForeground(PythonConsoleQssAdapter::instance()->promtColor());
261  cursor.setCharFormat(format);
262  switch (mPromptType)
263  {
264  case Compound:
265  cursor.insertText(mCompoundPrompt);
266  if (mCompoundPromptEndPosition < 0)
267  {
268  mCompoundPromptEndPosition = mPromptEndPosition;
269  }
270  break;
271  case Standard:
272  cursor.insertText(mStandardPrompt);
273  mCompoundPromptEndPosition = -1;
274  break;
275  case Input:
276  cursor.insertText(mInputPrompt);
277  mCompoundPromptEndPosition = -1;
278  break;
279  }
280  cursor.movePosition(QTextCursor::EndOfLine);
282 
283  // mPromptBlockNumber = textCursor().blockNumber();
284  mPromptLength = mStandardPrompt.length();
285  mPromptEndPosition = textCursor().position();
286  }
287 
289  {
290  mAbortThreadWidget->stop();
291  }
292 
294  {
297  cursor.movePosition(QTextCursor::End);
298  cursor.insertText("\n");
299  if (!input.isEmpty())
300  {
301  // gPythonContext->addHistory(input);
302  mHistory->addHistory(input.toStdString());
303  }
304  if ((!isCompound() && gPythonContext->checkCompleteStatement(input) != 0) || (isCompound() && input.isEmpty() && gPythonContext->checkCompleteStatement(input) != 0))
305  {
306  mCurrentCompoundInput += input;
307  if (isCompound())
308  {
309  gPythonContext->interpretBackground(this, mCurrentCompoundInput, true);
310  }
311  else
312  {
313  gPythonContext->interpretBackground(this, input, false);
314  }
315  mPromptType = Standard;
316  mCurrentCompoundInput = "";
317  mAbortThreadWidget->start();
318  }
319  else
320  {
321  mCurrentCompoundInput += input + "\n";
322  mPromptType = Compound;
323  displayPrompt();
324  }
325  mHistory->updateFromFile();
326  }
327 
329  {
331 
332  cursor.setPosition(mPromptEndPosition);
334 
335  QString command = cursor.selectedText();
336  cursor.clearSelection();
337  return command;
338  }
339 
341  {
343  cursor.setPosition(mPromptEndPosition);
345  cursor.insertText(new_command);
346  }
347 
349  {
351  cursor.movePosition(QTextCursor::End);
352  cursor.insertText(appendix);
353  }
354 
356  {
358  cursor.setPosition(textCursor().selectionStart());
359  return false;
360  }
361 
363  {
364  auto lastIndex = mHistory->size() - 1;
365  if (lastIndex < 0)
366  {
367  // history empty
368  return;
369  }
370 
371  if (mCurrentHistoryIndex == -1)
372  {
373  mCurrentInput = getCurrentCommand();
374  mCurrentHistoryIndex = lastIndex;
375  replaceCurrentCommand(QString::fromStdString(mHistory->getHistoryItem(mCurrentHistoryIndex)));
376  }
377  else
378  {
379  if (mCurrentHistoryIndex == 0)
380  {
381  return;
382  }
383 
384  mCurrentHistoryIndex--;
385  replaceCurrentCommand(QString::fromStdString(mHistory->getHistoryItem(mCurrentHistoryIndex)));
386  }
387  }
388 
390  {
391  auto lastIndex = mHistory->size() - 1;
392 
393  if (mCurrentHistoryIndex == -1)
394  {
395  return;
396  }
397 
398  if (mCurrentHistoryIndex == lastIndex)
399  {
400  mCurrentHistoryIndex = -1;
401  replaceCurrentCommand(mCurrentInput);
402  }
403  else
404  {
405  mCurrentHistoryIndex++;
406  replaceCurrentCommand(QString::fromStdString(mHistory->getHistoryItem(mCurrentHistoryIndex)));
407  }
408  }
409 
411  {
412  switch (mPromptType)
413  {
414  case Compound:
415  mCurrentInput = mCurrentCompoundInput + getCurrentCommand();
416  break;
417  case Standard:
418  mCurrentInput = getCurrentCommand();
419  break;
420  case Input:
421  mCurrentInput += "\t";
422  insertPlainText("\t");
423  return;
424  }
425 
426  QString current_line = getCurrentCommand();
427  if (current_line.isEmpty())
428  {
429  insertPlainText("\t");
430  }
431  else
432  {
433  log_info("python", "completing: '{}'", mCurrentInput.toStdString());
434  auto r = gPythonContext->complete(mCurrentInput, true);
435  if (r.size() == 1)
436  {
437  appendToCurrentCommand(QString::fromStdString(std::get<1>(r.at(0))));
438  }
439  else if (r.size() > 1)
440  {
441  mInCompletion = true;
442  QString candidates = "\n";
443  QString matching_prefix = QString::fromStdString(std::get<1>(r[0]));
444  for (auto& tup : r)
445  {
446  auto candidate = QString::fromStdString(std::get<0>(tup));
447  auto completion = QString::fromStdString(std::get<1>(tup));
448  candidates += candidate + " ";
449  for (int i = 0; i < matching_prefix.size() && i < completion.size(); ++i)
450  {
451  if (matching_prefix[i] != completion[i])
452  {
453  matching_prefix = matching_prefix.mid(0, i);
454  }
455  }
456  }
457  candidates += "\n";
458  handleStdout(candidates);
459  displayPrompt();
460  replaceCurrentCommand(current_line + matching_prefix);
461  }
462  }
463  }
464 
466  {
467  return mAbortThreadWidget;
468  }
469 
470  //------------------------------
472  : QFrame(parent), mCount(0)
473  {
475  setLineWidth(2);
476  QHBoxLayout* layout = new QHBoxLayout(this);
477  layout->setMargin(2);
478  mLabel = new QLabel(this);
479  layout->addWidget(mLabel);
480  mAbortButton = new QPushButton("Abort", this);
481  mAbortButton->setDisabled(true);
482  mAbortButton->setMaximumWidth(270);
483  connect(mAbortButton,&QPushButton::clicked,this,&PythonConsoleAbortThread::handleAbortButton);
484  layout->addWidget(mAbortButton);
485  mTimer = new QTimer(this);
486  connect(mTimer,&QTimer::timeout,this,&PythonConsoleAbortThread::handleTimeout);
487  }
488 
489  void PythonConsoleAbortThread::handleAbortButton()
490  {
491  gPythonContext->abortThread();
492  log_info("gui", "Python console command execution aborted by user");
493  }
494 
495 
496  void PythonConsoleAbortThread::handleTimeout()
497  {
498  if (isVisible() && !gPythonContext->isThreadRunning())
499  {
500  stop();
501  return;
502  }
503  ++mCount;
504  mLabel->setText(QString("Python interpreter running for %1 seconds").arg(mCount));
505  if (mCount > 5)
506  {
507  mAbortButton->setEnabled(true);
508  show();
509  }
510  }
511 
513  {
514  mCount = 0;
515  mTimer->start(1000);
516  mAbortButton->setDisabled(true);
517  hide();
518  }
519 
521  {
522  mTimer->stop();
523  mAbortButton->setDisabled(true);
524  hide();
525  }
526 
527 }
static SettingsItemDropdown * sSettingStyle
Definition: main_window.h:302
PythonConsoleAbortThread(QWidget *parent=nullptr)
Stores the history of python commands.
void mousePressEvent(QMouseEvent *event) override
PythonConsole(QWidget *parent=nullptr)
void keyPressEvent(QKeyEvent *e) override
void setInputMode(bool state)
void inputReceived(QString input)
virtual void clear() override
virtual void handleStdout(const QString &output) override
PythonConsoleAbortThread * abortThreadWidget()
void replaceCurrentCommand(const QString &new_command)
void insertAtEnd(const QString &text, QColor textColor)
virtual void handleError(const QString &output) override
void appendToCurrentCommand(const QString &new_command)
static PythonConsoleQssAdapter * instance()
void intChanged(int value)
#define log_info(channel,...)
Definition: log.h:70
string command
Definition: control.py:76
PythonContext * gPythonContext
Definition: plugin_gui.cpp:88
void clicked(bool checked)
virtual bool event(QEvent *event) override
void accept()
void setLineWidth(int)
void setFrameStyle(int style)
int key() const const
bool matches(QKeySequence::StandardKey key) const const
Qt::KeyboardModifiers modifiers() const const
void setText(const QString &)
void setMargin(int margin)
void addWidget(QWidget *w)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString fromStdString(const std::string &str)
bool isEmpty() const const
int length() const const
QString mid(int position, int n) const const
int size() const const
std::string toStdString() const const
Key_Return
ControlModifier
int position() const const
void clear()
void copy()
void ensureCursorVisible()
void setHtml(const QString &text)
void insertPlainText(const QString &text)
virtual void keyPressEvent(QKeyEvent *e) override
virtual void mousePressEvent(QMouseEvent *e) override
void moveCursor(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode)
void setTextColor(const QColor &c)
void setTextCursor(const QTextCursor &cursor)
QColor textColor() const const
QTextCursor textCursor() const const
void setUndoRedoEnabled(bool enable)
void setForeground(const QBrush &brush)
void start(int msec)
void stop()
void timeout()
void setEnabled(bool)
void hide()
QLayout * layout() const const
void setMaximumWidth(int maxw)
void setDisabled(bool disable)
void show()
bool isVisible() const const