HAL  v4.5.0-133-g64838ea8d
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 <QHBoxLayout>
9 #include <QLabel>
10 #include <QPushButton>
11 #include <QTimer>
12 
17 
18 namespace hal
19 {
21  : QTextEdit(parent), mStandardPrompt(">>> "), mCompoundPrompt("... "), mInputPrompt("==> "),
22  mPromptBlockNumber(0), mPromptLength(0), mPromptEndPosition(0), mCompoundPromptEndPosition(0),
23  mPromptType(Standard), mInCompletion(false), mCurrentCompoundInput(""), mCurrentInput(""), mCurrentHistoryIndex(-1), mCurrentCompleterIndex(0),
24  mHistory(std::make_shared<PythonConsoleHistory>())
25  {
26  this->document()->setMaximumBlockCount(1000);
28  setUndoRedoEnabled(false);
30 
32  gPythonContext->interpretForeground("print(\"Python \" + sys.version)");
33  gPythonContext->interpretForeground("print(sys.executable + \" on \" + sys.platform)");
34  displayPrompt();
35  mAbortThreadWidget = new PythonConsoleAbortThread(this);
36  connect(MainWindow::sSettingStyle,&SettingsItemDropdown::intChanged,this,&PythonConsole::handleStyleChanged);
37  }
38 
40  {
41  gPythonContext->setConsole(nullptr);
42  }
43 
44  void PythonConsole::handleStyleChanged(int istyle)
45  {
46  Q_UNUSED(istyle);
48  }
49 
50  void PythonConsole::keyPressEventInputMode(QKeyEvent *e)
51  {
52  mCurrentHistoryIndex = -1;
53  switch (e->key())
54  {
55  case Qt::Key_Return:
56  case Qt::Key_Enter:
57  {
59  QString input = getCurrentCommand();
61  cursor.movePosition(QTextCursor::End);
62  cursor.insertText("\n");
63  Q_EMIT inputReceived(input);
64  break;
65  }
66  default:
67  if (textCursor().selectionStart() < mPromptEndPosition)
68  {
70  }
71  mInCompletion = false;
72  }
73  }
74 
76  {
77  auto cursor = textCursor();
78  if (textCursor().hasSelection())
79  {
81  {
82  e->accept();
83  copy();
84  return;
85  }
86  else if (e->matches(QKeySequence::Cut))
87  {
88  // cut cutable stuff and copy rest
89  return;
90  }
91  else if (e->key() == Qt::Key_Control)
92  {
93  return;
94  }
95  }
96 
97  if (isInputMode())
98  {
99  keyPressEventInputMode(e);
100  }
101  else
102  {
103  switch (e->key())
104  {
105  case Qt::Key_Return:
106  case Qt::Key_Enter:
109  mCurrentHistoryIndex = -1;
110  return;
111 
112  case Qt::Key_Backspace:
113  if (textCursor().hasSelection())
114  {
115  if (textCursor().selectionStart() >= mPromptEndPosition)
116  {
117  break;
118  }
119  else
120  {
121  return;
122  }
123  }
124  else
125  {
126  if (textCursor().selectionStart() > mPromptEndPosition)
127  {
128  break;
129  }
130  else
131  {
132  return;
133  }
134  }
135 
136  case Qt::Key_Up:
138  return;
139 
140  case Qt::Key_Down:
142  return;
143 
144  case Qt::Key_Left:
145  if (e->modifiers() & Qt::ControlModifier) // Needed for macOS
146  {
147  cursor.setPosition(mPromptEndPosition);
149  return;
150  }
151  if (textCursor().hasSelection())
152  {
153  if (textCursor().selectionStart() >= mPromptEndPosition)
154  {
155  break;
156  }
157  else
158  {
159  return;
160  }
161  }
162  else
163  {
164  if (textCursor().selectionStart() > mPromptEndPosition)
165  {
166  break;
167  }
168  else
169  {
170  return;
171  }
172  }
173  case Qt::Key_Home:
174  cursor.setPosition(mPromptEndPosition);
176  return;
177  case Qt::Key_End:
178  cursor.movePosition(QTextCursor::End);
180  return;
181  case Qt::Key_Right:
182  if (e->modifiers() & Qt::ControlModifier) // Needed for macOS
183  {
184  cursor.movePosition(QTextCursor::End);
186  return;
187  }
188  break;
189 
190  case Qt::Key_Tab:
192  return;
193 
194  default:
195  if (textCursor().selectionStart() < mPromptEndPosition)
196  {
198  }
199  mInCompletion = false;
200  mCurrentHistoryIndex = -1;
201  }
202  }
204  }
205 
207  {
208  // m_position = textCursor().position();
209  // if (event->button() == Qt::MiddleButton)
210  // {
211  // copy();
212  // QTextCursor cursor = cursorForPosition(event->pos());
213  // setTextCursor(cursor);
214  // paste();
215  // return;
216  // }
217  // cursor.movePosition(QTextCursor::End);
219  }
220 
221  void PythonConsole::insertAtEnd(const QString& text, QColor textColor)
222  {
225  insertPlainText(QString(text));
226  }
227 
229  {
231  }
232 
234  {
235  QString append_out = output;
236  if (!append_out.endsWith("\n"))
237  append_out += "\n";
238  insertAtEnd(append_out, PythonConsoleQssAdapter::instance()->errorColor());
239  }
240 
242  {
244  }
245 
247  {
248  if (state)
249  mPromptType = Input;
250  else
251  mPromptType = Standard;
252  }
253 
255  {
256  //QTextCursor cursor = textCursor();
258  cursor.movePosition(QTextCursor::End);
259  //DEBUG SAVE FORMATS AS MEMBERS
260  QTextCharFormat format;
261  format.setForeground(PythonConsoleQssAdapter::instance()->promtColor());
262  cursor.setCharFormat(format);
263  switch (mPromptType)
264  {
265  case Compound:
266  cursor.insertText(mCompoundPrompt);
267  if (mCompoundPromptEndPosition < 0)
268  {
269  mCompoundPromptEndPosition = mPromptEndPosition;
270  }
271  break;
272  case Standard:
273  cursor.insertText(mStandardPrompt);
274  mCompoundPromptEndPosition = -1;
275  break;
276  case Input:
277  cursor.insertText(mInputPrompt);
278  mCompoundPromptEndPosition = -1;
279  break;
280  }
281  cursor.movePosition(QTextCursor::EndOfLine);
283 
284  // mPromptBlockNumber = textCursor().blockNumber();
285  mPromptLength = mStandardPrompt.length();
286  mPromptEndPosition = textCursor().position();
287  }
288 
290  {
291  mAbortThreadWidget->stop();
292  }
293 
295  {
298  cursor.movePosition(QTextCursor::End);
299  cursor.insertText("\n");
300  if (!input.isEmpty())
301  {
302  // gPythonContext->addHistory(input);
303  mHistory->addHistory(input.toStdString());
304  }
305  if ((!isCompound() && gPythonContext->checkCompleteStatement(input) != 0) || (isCompound() && input.isEmpty() && gPythonContext->checkCompleteStatement(input) != 0))
306  {
307  mCurrentCompoundInput += input;
308  if (isCompound())
309  {
310  gPythonContext->interpretBackground(this, mCurrentCompoundInput, true);
311  }
312  else
313  {
315  }
316  mPromptType = Standard;
317  mCurrentCompoundInput = "";
318  mAbortThreadWidget->start();
319  }
320  else
321  {
322  mCurrentCompoundInput += input + "\n";
323  mPromptType = Compound;
324  displayPrompt();
325  }
326  mHistory->updateFromFile();
327  }
328 
330  {
332 
333  cursor.setPosition(mPromptEndPosition);
335 
336  QString command = cursor.selectedText();
337  cursor.clearSelection();
338  return command;
339  }
340 
342  {
344  cursor.setPosition(mPromptEndPosition);
346  cursor.insertText(new_command);
347  }
348 
350  {
352  cursor.movePosition(QTextCursor::End);
353  cursor.insertText(appendix);
354  }
355 
357  {
359  cursor.setPosition(textCursor().selectionStart());
360  return false;
361  }
362 
364  {
365  auto lastIndex = mHistory->size() - 1;
366  if (lastIndex < 0)
367  {
368  // history empty
369  return;
370  }
371 
372  if (mCurrentHistoryIndex == -1)
373  {
374  mCurrentInput = getCurrentCommand();
375  mCurrentHistoryIndex = lastIndex;
376  replaceCurrentCommand(QString::fromStdString(mHistory->getHistoryItem(mCurrentHistoryIndex)));
377  }
378  else
379  {
380  if (mCurrentHistoryIndex == 0)
381  {
382  return;
383  }
384 
385  mCurrentHistoryIndex--;
386  replaceCurrentCommand(QString::fromStdString(mHistory->getHistoryItem(mCurrentHistoryIndex)));
387  }
388  }
389 
391  {
392  auto lastIndex = mHistory->size() - 1;
393 
394  if (mCurrentHistoryIndex == -1)
395  {
396  return;
397  }
398 
399  if (mCurrentHistoryIndex == lastIndex)
400  {
401  mCurrentHistoryIndex = -1;
402  replaceCurrentCommand(mCurrentInput);
403  }
404  else
405  {
406  mCurrentHistoryIndex++;
407  replaceCurrentCommand(QString::fromStdString(mHistory->getHistoryItem(mCurrentHistoryIndex)));
408  }
409  }
410 
412  {
413  switch (mPromptType)
414  {
415  case Compound:
416  mCurrentInput = mCurrentCompoundInput + getCurrentCommand();
417  break;
418  case Standard:
419  mCurrentInput = getCurrentCommand();
420  break;
421  case Input:
422  mCurrentInput += "\t";
423  insertPlainText("\t");
424  return;
425  }
426 
427  QString current_line = getCurrentCommand();
428  if (current_line.isEmpty())
429  {
430  insertPlainText("\t");
431  }
432  else
433  {
434  log_info("python", "completing: '{}'", mCurrentInput.toStdString());
435  auto r = gPythonContext->complete(mCurrentInput, true);
436  if (r.size() == 1)
437  {
438  appendToCurrentCommand(QString::fromStdString(std::get<1>(r.at(0))));
439  }
440  else if (r.size() > 1)
441  {
442  mInCompletion = true;
443  QString candidates = "\n";
444  QString matching_prefix = QString::fromStdString(std::get<1>(r[0]));
445  for (auto& tup : r)
446  {
447  auto candidate = QString::fromStdString(std::get<0>(tup));
448  auto completion = QString::fromStdString(std::get<1>(tup));
449  candidates += candidate + " ";
450  for (int i = 0; i < matching_prefix.size() && i < completion.size(); ++i)
451  {
452  if (matching_prefix[i] != completion[i])
453  {
454  matching_prefix = matching_prefix.mid(0, i);
455  }
456  }
457  }
458  candidates += "\n";
459  handleStdout(candidates);
460  displayPrompt();
461  replaceCurrentCommand(current_line + matching_prefix);
462  }
463  }
464  }
465 
467  {
468  return mAbortThreadWidget;
469  }
470 
471  //------------------------------
473  : QFrame(parent), mCount(0)
474  {
476  setLineWidth(2);
477  QHBoxLayout* layout = new QHBoxLayout(this);
479  mLabel = new QLabel(this);
480  layout->addWidget(mLabel);
481  mAbortButton = new QPushButton("Abort", this);
482  mAbortButton->setDisabled(true);
483  mAbortButton->setMaximumWidth(270);
484  connect(mAbortButton,&QPushButton::clicked,this,&PythonConsoleAbortThread::handleAbortButton);
485  layout->addWidget(mAbortButton);
486  mTimer = new QTimer(this);
487  connect(mTimer,&QTimer::timeout,this,&PythonConsoleAbortThread::handleTimeout);
488  }
489 
490  void PythonConsoleAbortThread::handleAbortButton()
491  {
493  log_info("gui", "Python console command execution aborted by user");
494  }
495 
496 
497  void PythonConsoleAbortThread::handleTimeout()
498  {
500  {
501  stop();
502  return;
503  }
504  ++mCount;
505  mLabel->setText(QString("Python interpreter running for %1 seconds").arg(mCount));
506  if (mCount > 5)
507  {
508  mAbortButton->setEnabled(true);
509  show();
510  }
511  }
512 
514  {
515  mCount = 0;
516  mTimer->start(1000);
517  mAbortButton->setDisabled(true);
518  hide();
519  }
520 
522  {
523  mTimer->stop();
524  mAbortButton->setDisabled(true);
525  hide();
526  }
527 
528 }
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()
std::vector< std::tuple< std::string, std::string > > complete(const QString &text, bool use_console_context)
int checkCompleteStatement(const QString &text)
bool isThreadRunning() const
void 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
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)
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