HAL  v4.5.0-253-g7435877c7
The Hardware Analyzer - a comprehensive reverse engineering and manipulation framework for gate-level netlists.
python_console.cpp
Go to the documentation of this file.
2 
4 #include "gui/gui_globals.h"
6 
7 #include <QKeyEvent>
8 #include <QMimeData>
9 #include <QHBoxLayout>
10 #include <QLabel>
11 #include <QPushButton>
12 #include <QTimer>
13 
18 
19 namespace hal
20 {
22  : QTextEdit(parent), mStandardPrompt(">>> "), mCompoundPrompt("... "), mInputPrompt("==> "),
23  mPromptBlockNumber(0), mPromptLength(0), mPromptEndPosition(0), mCompoundPromptEndPosition(0),
24  mPromptType(Standard), mInCompletion(false), mCurrentCompoundInput(""), mCurrentInput(""), mCurrentHistoryIndex(-1), mCurrentCompleterIndex(0),
25  mHistory(std::make_shared<PythonConsoleHistory>())
26  {
27  this->document()->setMaximumBlockCount(1000);
29  setUndoRedoEnabled(false);
31 
33  gPythonContext->interpretForeground("print(\"Python \" + sys.version)");
34  gPythonContext->interpretForeground("print(sys.executable + \" on \" + sys.platform)");
35  displayPrompt();
36  mAbortThreadWidget = new PythonConsoleAbortThread(this);
37  connect(MainWindow::sSettingStyle,&SettingsItemDropdown::intChanged,this,&PythonConsole::handleStyleChanged);
38  }
39 
41  {
42  gPythonContext->setConsole(nullptr);
43  }
44 
45  void PythonConsole::handleStyleChanged(int istyle)
46  {
47  Q_UNUSED(istyle);
49  }
50 
51  void PythonConsole::keyPressEventInputMode(QKeyEvent *e)
52  {
53  mCurrentHistoryIndex = -1;
54  switch (e->key())
55  {
56  case Qt::Key_Return:
57  case Qt::Key_Enter:
58  {
60  QString input = getCurrentCommand();
62  cursor.movePosition(QTextCursor::End);
63  cursor.insertText("\n");
64  Q_EMIT inputReceived(input);
65  break;
66  }
67  default:
68  if (textCursor().selectionStart() < mPromptEndPosition)
69  {
71  }
72  mInCompletion = false;
73  }
74  }
75 
77  {
78  auto cursor = textCursor();
79 
81  {
82  if (textCursor().hasSelection())
83  {
84  e->accept();
85  copy();
86  return;
87  }
88  e->accept();
91  else
92  // discards the current line only, [Esc] discards a multi-line statement
93  discardInput(false);
94  return;
95  }
96 
97  if (textCursor().hasSelection())
98  {
99  if (e->matches(QKeySequence::Cut))
100  {
101  // cut cutable stuff and copy rest
102  return;
103  }
104  else if (e->key() == Qt::Key_Control)
105  {
106  return;
107  }
108  }
109 
110  if (isInputMode())
111  {
112  keyPressEventInputMode(e);
113  }
114  else
115  {
116  switch (e->key())
117  {
118  case Qt::Key_Return:
119  case Qt::Key_Enter:
120  // console is read only while a thread is running, the next prompt follows when it has finished
122  return;
125  mCurrentHistoryIndex = -1;
126  return;
127 
128  case Qt::Key_Backspace:
129  if (textCursor().hasSelection())
130  {
131  if (textCursor().selectionStart() >= mPromptEndPosition)
132  {
133  break;
134  }
135  else
136  {
137  return;
138  }
139  }
140  else
141  {
142  if (textCursor().position() <= mPromptEndPosition)
143  {
144  return;
145  }
146 
147  int posInLine = textCursor().position() - mPromptEndPosition;
148  if (posInLine >= 4 && posInLine % 4 == 0)
149  {
150  QString cmd = getCurrentCommand();
151  QString before = cmd.left(posInLine);
152  if (before.right(4) == " ")
153  {
154  QTextCursor tc = textCursor();
156  tc.removeSelectedText();
157  return;
158  }
159  }
160 
161  break;
162  }
163 
164  case Qt::Key_Up:
166  return;
167 
168  case Qt::Key_Down:
170  return;
171 
172  case Qt::Key_Left:
173  if (e->modifiers() & Qt::ControlModifier) // Needed for macOS
174  {
175  cursor.setPosition(mPromptEndPosition);
177  return;
178  }
179  if (textCursor().hasSelection())
180  {
181  if (textCursor().selectionStart() >= mPromptEndPosition)
182  {
183  break;
184  }
185  else
186  {
187  return;
188  }
189  }
190  else
191  {
192  if (textCursor().selectionStart() > mPromptEndPosition)
193  {
194  break;
195  }
196  else
197  {
198  return;
199  }
200  }
201  case Qt::Key_Home:
202  cursor.setPosition(mPromptEndPosition);
204  return;
205  case Qt::Key_End:
206  cursor.movePosition(QTextCursor::End);
208  return;
209  case Qt::Key_Right:
210  if (e->modifiers() & Qt::ControlModifier) // Needed for macOS
211  {
212  cursor.movePosition(QTextCursor::End);
214  return;
215  }
216  break;
217 
218  case Qt::Key_Tab:
220  return;
221 
222  case Qt::Key_Escape:
223  // discard the compound statement entered so far
224  if (isCompound())
225  {
226  discardInput(true);
227  return;
228  }
229  break;
230 
231  default:
232  if (textCursor().selectionStart() < mPromptEndPosition)
233  {
235  }
236  mInCompletion = false;
237  mCurrentHistoryIndex = -1;
238  }
239  }
241  }
242 
244  {
245  // m_position = textCursor().position();
246  // if (event->button() == Qt::MiddleButton)
247  // {
248  // copy();
249  // QTextCursor cursor = cursorForPosition(event->pos());
250  // setTextCursor(cursor);
251  // paste();
252  // return;
253  // }
254  // cursor.movePosition(QTextCursor::End);
256  }
257 
258  void PythonConsole::insertAtEnd(const QString& text, QColor textColor)
259  {
262  insertPlainText(QString(text));
263  }
264 
266  {
268  }
269 
271  {
272  QString append_out = output;
273  if (!append_out.endsWith("\n"))
274  append_out += "\n";
275  insertAtEnd(append_out, PythonConsoleQssAdapter::instance()->errorColor());
276  }
277 
279  {
281  }
282 
284  {
285  if (state)
286  mPromptType = Input;
287  else
288  mPromptType = Standard;
289  }
290 
292  {
293  //QTextCursor cursor = textCursor();
295  cursor.movePosition(QTextCursor::End);
296  //DEBUG SAVE FORMATS AS MEMBERS
297  QTextCharFormat format;
298  format.setForeground(PythonConsoleQssAdapter::instance()->promtColor());
299  cursor.setCharFormat(format);
300  switch (mPromptType)
301  {
302  case Compound:
303  cursor.insertText(mCompoundPrompt);
304  if (mCompoundPromptEndPosition < 0)
305  {
306  mCompoundPromptEndPosition = mPromptEndPosition;
307  }
308  break;
309  case Standard:
310  cursor.insertText(mStandardPrompt);
311  mCompoundPromptEndPosition = -1;
312  break;
313  case Input:
314  cursor.insertText(mInputPrompt);
315  mCompoundPromptEndPosition = -1;
316  break;
317  }
318  cursor.movePosition(QTextCursor::EndOfLine);
320 
321  // mPromptBlockNumber = textCursor().blockNumber();
322  mPromptLength = mStandardPrompt.length();
323  mPromptEndPosition = textCursor().position();
324  }
325 
327  {
328  mAbortThreadWidget->stop();
329  }
330 
332  {
335  cursor.movePosition(QTextCursor::End);
336  cursor.insertText("\n");
337 
338  if (!input.trimmed().isEmpty())
339  {
340  // gPythonContext->addHistory(input);
341  mHistory->addHistory(input.toStdString());
342  }
343  if (!isCompound() && input.trimmed().isEmpty())
344  {
345  displayPrompt();
346  }
347  else if (!isCompound() && gPythonContext->checkCompleteStatement(input) != 0)
348  {
349  bool threadStarted = gPythonContext->interpretBackground(this, input, input.contains('\n'));
350  mPromptType = Standard;
351  mCurrentCompoundInput = "";
352  if (threadStarted)
353  mAbortThreadWidget->start();
354  else
355  displayPrompt();
356  }
357  else if (isCompound() && input.trimmed().isEmpty())
358  {
359  if (gPythonContext->checkCompleteStatement(mCurrentCompoundInput) != 0)
360  {
361  bool threadStarted = gPythonContext->interpretBackground(this, mCurrentCompoundInput, true);
362  mPromptType = Standard;
363  mCurrentCompoundInput = "";
364  if (threadStarted)
365  mAbortThreadWidget->start();
366  else
367  displayPrompt();
368  }
369  else
370  {
371  mPromptType = Compound;
372  displayPrompt();
373  }
374  }
375  else
376  {
377  if (input.trimmed() != "") mCurrentCompoundInput += input + "\n";
378 
379  mPromptType = Compound;
380  displayPrompt();
381 
382  // continue at the indentation of the previous line, one level deeper after a ':'
383  QString expanded = expandTabs(input);
384  int indent = 0;
385  while (indent < expanded.length() && expanded[indent] == ' ') indent++;
386 
387  if (expanded.trimmed().endsWith(":")) insertPlainText(QString(indent + 4, ' '));
388  else if (indent > 0) insertPlainText(QString(indent, ' '));
389 
390  }
391  mHistory->updateFromFile();
392  }
393 
395  {
397 
398  cursor.setPosition(mPromptEndPosition);
400 
401  // selectedText() returns line breaks as U+2029 (paragraph separator) which python cannot parse
402  QString command = cursor.selectedText().replace(QChar::ParagraphSeparator, '\n');
403  cursor.clearSelection();
404  return command;
405  }
406 
408  {
410  cursor.setPosition(mPromptEndPosition);
412  // history entries of older versions are indented with tabs
413  cursor.insertText(expandTabs(new_command));
414  }
415 
417  {
418  if (source->hasText())
419  insertPlainText(expandTabs(source->text()));
420  }
421 
422  void PythonConsole::discardInput(bool wholeStatement)
423  {
425  textCursor().insertText("\n");
426  if (wholeStatement)
427  {
428  mCurrentCompoundInput = "";
429  mPromptType = Standard;
430  }
431  mCurrentHistoryIndex = -1;
432  mInCompletion = false;
433  displayPrompt();
434  }
435 
436  QString PythonConsole::expandTabs(const QString& text)
437  {
438  QString retval = text;
439  return retval.replace('\t', QString(4, ' '));
440  }
441 
443  {
445  cursor.movePosition(QTextCursor::End);
446  cursor.insertText(appendix);
447  }
448 
450  {
452  cursor.setPosition(textCursor().selectionStart());
453  return false;
454  }
455 
457  {
458  auto lastIndex = mHistory->size() - 1;
459  if (lastIndex < 0)
460  {
461  // history empty
462  return;
463  }
464 
465  if (mCurrentHistoryIndex == -1)
466  {
467  mCurrentInput = getCurrentCommand();
468  mCurrentHistoryIndex = lastIndex;
469  replaceCurrentCommand(QString::fromStdString(mHistory->getHistoryItem(mCurrentHistoryIndex)));
470  }
471  else
472  {
473  if (mCurrentHistoryIndex == 0)
474  {
475  return;
476  }
477 
478  mCurrentHistoryIndex--;
479  replaceCurrentCommand(QString::fromStdString(mHistory->getHistoryItem(mCurrentHistoryIndex)));
480  }
481  }
482 
484  {
485  auto lastIndex = mHistory->size() - 1;
486 
487  if (mCurrentHistoryIndex == -1)
488  {
489  return;
490  }
491 
492  if (mCurrentHistoryIndex == lastIndex)
493  {
494  mCurrentHistoryIndex = -1;
495  replaceCurrentCommand(mCurrentInput);
496  }
497  else
498  {
499  mCurrentHistoryIndex++;
500  replaceCurrentCommand(QString::fromStdString(mHistory->getHistoryItem(mCurrentHistoryIndex)));
501  }
502  }
503 
505  {
506  switch (mPromptType)
507  {
508  case Compound:
509  mCurrentInput = mCurrentCompoundInput + getCurrentCommand();
510  break;
511  case Standard:
512  mCurrentInput = getCurrentCommand();
513  break;
514  case Input:
515  mCurrentInput += " ";
516  insertPlainText(" ");
517  return;
518  }
519 
520  QString current_line = getCurrentCommand();
521  if (current_line.trimmed().isEmpty())
522  {
523  insertPlainText(" ");
524  }
525  else
526  {
527  log_info("python", "completing: '{}'", mCurrentInput.toStdString());
528  auto r = gPythonContext->complete(mCurrentInput, true);
529  if (r.size() == 1)
530  {
531  appendToCurrentCommand(QString::fromStdString(std::get<1>(r.at(0))));
532  }
533  else if (r.size() > 1)
534  {
535  mInCompletion = true;
536  QString candidates = "\n";
537  QString matching_prefix = QString::fromStdString(std::get<1>(r[0]));
538  for (auto& tup : r)
539  {
540  auto candidate = QString::fromStdString(std::get<0>(tup));
541  auto completion = QString::fromStdString(std::get<1>(tup));
542  candidates += candidate + " ";
543  for (int i = 0; i < matching_prefix.size() && i < completion.size(); ++i)
544  {
545  if (matching_prefix[i] != completion[i])
546  {
547  matching_prefix = matching_prefix.mid(0, i);
548  }
549  }
550  }
551  candidates += "\n";
552  handleStdout(candidates);
553  displayPrompt();
554  replaceCurrentCommand(current_line + matching_prefix);
555  }
556  }
557  }
558 
560  {
561  return mAbortThreadWidget;
562  }
563 
564  //------------------------------
566  : QFrame(parent), mCount(0)
567  {
569  setLineWidth(2);
570  QHBoxLayout* layout = new QHBoxLayout(this);
572  mLabel = new QLabel(this);
573  layout->addWidget(mLabel);
574  mAbortButton = new QPushButton("Abort", this);
575  mAbortButton->setDisabled(true);
576  mAbortButton->setMaximumWidth(270);
577  connect(mAbortButton,&QPushButton::clicked,this,&PythonConsoleAbortThread::handleAbortButton);
578  layout->addWidget(mAbortButton);
579  mTimer = new QTimer(this);
580  connect(mTimer,&QTimer::timeout,this,&PythonConsoleAbortThread::handleTimeout);
581  }
582 
583  void PythonConsoleAbortThread::handleAbortButton()
584  {
586  log_info("gui", "Python console command execution aborted by user");
587  }
588 
589 
590  void PythonConsoleAbortThread::handleTimeout()
591  {
593  {
594  stop();
595  return;
596  }
597  ++mCount;
598  mLabel->setText(QString("Python interpreter running for %1 seconds").arg(mCount));
599  if (mCount > 5)
600  {
601  mAbortButton->setEnabled(true);
602  show();
603  }
604  }
605 
607  {
608  mCount = 0;
609  mTimer->start(1000);
610  mAbortButton->setDisabled(true);
611  hide();
612  }
613 
615  {
616  mTimer->stop();
617  mAbortButton->setDisabled(true);
618  hide();
619  }
620 
621 }
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 insertFromMimeData(const QMimeData *source) override
void insertAtEnd(const QString &text, QColor textColor)
virtual void handleError(const QString &output) override
void appendToCurrentCommand(const QString &new_command)
static PythonConsoleQssAdapter * instance()
std::vector< std::tuple< std::string, std::string > > complete(const QString &text, bool use_console_context)
int checkCompleteStatement(const QString &text)
bool isThreadRunning() const
bool interpretBackground(QObject *caller, const QString &input, bool multiple_expressions=false)
void setConsole(PythonConsole *console)
void interpretForeground(const QString &input)
void intChanged(int value)
#define log_info(channel,...)
Definition: log.h:70
void setMarginWidth(QLayout *layout, int marginWidth)
Definition: defines.h:45
PythonContext * gPythonContext
Definition: plugin_gui.cpp:89
void clicked(bool checked)
virtual bool event(QEvent *event) override
ParagraphSeparator
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 addWidget(QWidget *w)
bool hasText() const const
QString text() const const
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
QString left(int n) const const
int length() const const
QString mid(int position, int n) const const
QString & replace(int position, int n, QChar after)
QString right(int n) const const
int size() const const
std::string toStdString() const const
QString trimmed() const const
Key_Return
ControlModifier
void insertText(const QString &text)
bool movePosition(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode, int n)
int position() const const
void removeSelectedText()
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