From b6d10d1e205a001ec88cf967677b950fbcf68e80 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Fri, 1 Sep 2023 02:43:32 -0400 Subject: [PATCH] implement command completion popup --- tools/lammps-gui/codeeditor.cpp | 71 ++++++++++++++++++- tools/lammps-gui/codeeditor.h | 5 ++ tools/lammps-gui/lammps_internal_commands.txt | 70 ++++++++++++++++++ tools/lammps-gui/lammpsgui.cpp | 20 ++++++ tools/lammps-gui/lammpsgui.qrc | 4 +- tools/lammps-gui/lammpswrapper.cpp | 27 +++++++ tools/lammps-gui/lammpswrapper.h | 2 + 7 files changed, 195 insertions(+), 4 deletions(-) create mode 100644 tools/lammps-gui/lammps_internal_commands.txt diff --git a/tools/lammps-gui/codeeditor.cpp b/tools/lammps-gui/codeeditor.cpp index 9eb96e6a7a..3557326a8b 100644 --- a/tools/lammps-gui/codeeditor.cpp +++ b/tools/lammps-gui/codeeditor.cpp @@ -15,6 +15,7 @@ #include "lammpsgui.h" #include "linenumberarea.h" +#include #include #include #include @@ -25,7 +26,9 @@ #include #include #include +#include #include +#include #include #include @@ -42,8 +45,8 @@ static std::vector split_line(const std::string &text) std::size_t beg = 0; std::size_t len = 0; std::size_t add = 0; - char c = *buf; + char c = *buf; while (c) { // leading whitespace if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\f') { c = *++buf; @@ -115,7 +118,8 @@ static std::vector split_line(const std::string &text) return list; } -CodeEditor::CodeEditor(QWidget *parent) : QPlainTextEdit(parent), highlight(NO_HIGHLIGHT) +CodeEditor::CodeEditor(QWidget *parent) : + QPlainTextEdit(parent), command_completer(nullptr), highlight(NO_HIGHLIGHT) { help_action = new QShortcut(QKeySequence::fromString("Ctrl+?"), parent); connect(help_action, &QShortcut::activated, this, &CodeEditor::get_help); @@ -269,14 +273,64 @@ QString CodeEditor::reformatLine(const QString &line) return newtext; } +void CodeEditor::setCommandList(const QStringList &words) +{ + delete command_completer; + command_completer = new QCompleter(this); + command_completer->setModel(new QStringListModel(words, command_completer)); + command_completer->setCompletionMode(QCompleter::PopupCompletion); + command_completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel); + command_completer->setWidget(this); + command_completer->setWrapAround(false); + + QObject::connect(command_completer, QOverload::of(&QCompleter::activated), + this, &CodeEditor::insertCompletedCommand); + reformatCurrentLine(); +} + void CodeEditor::keyPressEvent(QKeyEvent *event) { + if (command_completer && command_completer->popup()->isVisible()) { + // The following keys are forwarded by the completer to the widget + switch (event->key()) { + case Qt::Key_Enter: + case Qt::Key_Return: + case Qt::Key_Escape: + case Qt::Key_Tab: + case Qt::Key_Backtab: + event->ignore(); + return; // let the completer do default behavior + default: + break; + } + } if (event->key() == Qt::Key_Tab) { reformatCurrentLine(); return; } if (event->key() == Qt::Key_Backtab) { - fprintf(stderr, "Shift + Tab key hit\n"); + if (command_completer) { + auto cursor = textCursor(); + auto line = cursor.block().text().trimmed(); + if (line.isEmpty()) return; + + auto words = split_line(line.toStdString()); + cursor.select(QTextCursor::WordUnderCursor); + + // if on first word, try to complete command + if ((words.size() > 0) && (words[0] == cursor.selectedText().toStdString())) { + // no completion on comment lines + if (words[0][0] == '#') return; + command_completer->setCompletionPrefix(words[0].c_str()); + auto popup = command_completer->popup(); + QRect cr = cursorRect(); + cr.setWidth(popup->sizeHintForColumn(0) + + popup->verticalScrollBar()->sizeHint().width()); + popup->setAlternatingRowColors(true); + command_completer->setCurrentRow(0); + command_completer->complete(cr); + } + } return; } QPlainTextEdit::keyPressEvent(event); @@ -425,6 +479,17 @@ void CodeEditor::reformatCurrentLine() } } +void CodeEditor::insertCompletedCommand(const QString &completion) +{ + if (command_completer->widget() != this) return; + auto cursor = textCursor(); + int extra = completion.length() - command_completer->completionPrefix().length(); + cursor.movePosition(QTextCursor::Left); + cursor.movePosition(QTextCursor::EndOfWord); + cursor.insertText(completion.right(extra)); + setTextCursor(cursor); +} + void CodeEditor::get_help() { QString page, help; diff --git a/tools/lammps-gui/codeeditor.h b/tools/lammps-gui/codeeditor.h index d3e5d50777..f3efd99635 100644 --- a/tools/lammps-gui/codeeditor.h +++ b/tools/lammps-gui/codeeditor.h @@ -14,11 +14,13 @@ #ifndef CODEEDITOR_H #define CODEEDITOR_H +#include #include #include #include #include #include +#include class CodeEditor : public QPlainTextEdit { Q_OBJECT @@ -32,6 +34,7 @@ public: void setCursor(int block); void setHighlight(int block, bool error); QString reformatLine(const QString &line); + void setCommandList(const QStringList &words); static constexpr int NO_HIGHLIGHT = 1 << 30; @@ -50,10 +53,12 @@ private slots: void find_help(QString &page, QString &help); void open_help(); void reformatCurrentLine(); + void insertCompletedCommand(const QString &completion); private: QWidget *lineNumberArea; QShortcut *help_action; + QCompleter *command_completer; int highlight; QMap cmd_map; diff --git a/tools/lammps-gui/lammps_internal_commands.txt b/tools/lammps-gui/lammps_internal_commands.txt new file mode 100644 index 0000000000..f2e24b8008 --- /dev/null +++ b/tools/lammps-gui/lammps_internal_commands.txt @@ -0,0 +1,70 @@ +clear +echo +if +include +jump +label +log +next +partition +print +python +quit +shell +variable +angle_coeff +angle_style +atom_modify +atom_style +bond_coeff +bond_style +bond_write +boundary +comm_modify +comm_style +compute +compute_modify +dielectric +dihedral_coeff +dihedral_style +dimension +dump +dump_modify +fix +fix_modify +group +improper_coeff +improper_style +kspace_modify +kspace_style +labelmap +lattice +mass +min_modify +min_style +molecule +neigh_modify +neighbor +newton +package +pair_coeff +pair_modify +pair_style +pair_write +processors +region +reset_timestep +restart +run_style +special_bonds +suffix +thermo +thermo_modify +thermo_style +timestep +timer +uncompute +undump +unfix +units +reset_atoms diff --git a/tools/lammps-gui/lammpsgui.cpp b/tools/lammps-gui/lammpsgui.cpp index 7966db61af..b313dfe727 100644 --- a/tools/lammps-gui/lammpsgui.cpp +++ b/tools/lammps-gui/lammpsgui.cpp @@ -57,6 +57,7 @@ static const QString blank(" "); static constexpr int MAXRECENT = 5; +static constexpr int BUFLEN = 128; // duplicate string static char *mystrdup(const std::string &text) @@ -296,6 +297,25 @@ LammpsGui::LammpsGui(QWidget *parent, const char *filename) : setWindowTitle(QString("LAMMPS-GUI - *unknown*")); } resize(settings.value("mainx", "500").toInt(), settings.value("mainy", "320").toInt()); + + // start LAMMPS and initialize command completion + start_lammps(); + QStringList command_list; + QFile internal_commands(":/lammps_internal_commands.txt"); + if (internal_commands.open(QIODevice::ReadOnly | QIODevice::Text)) { + while (!internal_commands.atEnd()) { + command_list << QString(internal_commands.readLine()).trimmed(); + } + } + internal_commands.close(); + int ncmds = lammps.style_count("command"); + char buf[BUFLEN]; + for (int i = 0; i < ncmds; ++i) { + if (lammps.style_name("command", i, buf, BUFLEN)) + command_list << buf; + } + command_list.sort(); + ui->textEdit->setCommandList(command_list); } LammpsGui::~LammpsGui() diff --git a/tools/lammps-gui/lammpsgui.qrc b/tools/lammps-gui/lammpsgui.qrc index f68caade2f..2cb4da64cf 100644 --- a/tools/lammps-gui/lammpsgui.qrc +++ b/tools/lammps-gui/lammpsgui.qrc @@ -2,6 +2,9 @@ lammps-icon-128x128.png + help_index.table + + lammps_internal_commands.txt antialias.png application-calc.png application-exit.png @@ -34,7 +37,6 @@ help-about.png help-browser.png help-faq.png - help_index.table image-x-generic.png media-playback-start-2.png media-playlist-repeat.png diff --git a/tools/lammps-gui/lammpswrapper.cpp b/tools/lammps-gui/lammpswrapper.cpp index d1a130c3c5..024aae5301 100644 --- a/tools/lammps-gui/lammpswrapper.cpp +++ b/tools/lammps-gui/lammpswrapper.cpp @@ -97,6 +97,33 @@ int LammpsWrapper::id_name(const char *keyword, int idx, char *buf, int len) return val; } +int LammpsWrapper::style_count(const char *keyword) +{ + int val = 0; + if (lammps_handle) { +#if defined(LAMMPS_GUI_USE_PLUGIN) + val = ((liblammpsplugin_t *)plugin_handle)->style_count(lammps_handle, keyword); +#else + val = lammps_style_count(lammps_handle, keyword); +#endif + } + return val; +} + +int LammpsWrapper::style_name(const char *keyword, int idx, char *buf, int len) +{ + int val = 0; + if (lammps_handle) { +#if defined(LAMMPS_GUI_USE_PLUGIN) + val = + ((liblammpsplugin_t *)plugin_handle)->style_name(lammps_handle, keyword, idx, buf, len); +#else + val = lammps_style_name(lammps_handle, keyword, idx, buf, len); +#endif + } + return val; +} + int LammpsWrapper::variable_info(int idx, char *buf, int len) { int val = 0; diff --git a/tools/lammps-gui/lammpswrapper.h b/tools/lammps-gui/lammpswrapper.h index f5a391aed8..f5d73a0d87 100644 --- a/tools/lammps-gui/lammpswrapper.h +++ b/tools/lammps-gui/lammpswrapper.h @@ -35,6 +35,8 @@ public: int id_count(const char *idtype); int id_name(const char *idtype, int idx, char *buf, int buflen); + int style_count(const char *keyword); + int style_name(const char *keyword, int idx, char *buf, int buflen); int variable_info(int idx, char *buf, int buflen); double get_thermo(const char *keyword);