implement command completion popup

This commit is contained in:
Axel Kohlmeyer
2023-09-01 02:43:32 -04:00
parent 156ab0b338
commit b6d10d1e20
7 changed files with 195 additions and 4 deletions

View File

@ -15,6 +15,7 @@
#include "lammpsgui.h" #include "lammpsgui.h"
#include "linenumberarea.h" #include "linenumberarea.h"
#include <QAbstractItemView>
#include <QAction> #include <QAction>
#include <QDesktopServices> #include <QDesktopServices>
#include <QDragEnterEvent> #include <QDragEnterEvent>
@ -25,7 +26,9 @@
#include <QMimeData> #include <QMimeData>
#include <QPainter> #include <QPainter>
#include <QRegularExpression> #include <QRegularExpression>
#include <QScrollBar>
#include <QSettings> #include <QSettings>
#include <QStringListModel>
#include <QTextBlock> #include <QTextBlock>
#include <QUrl> #include <QUrl>
@ -42,8 +45,8 @@ static std::vector<std::string> split_line(const std::string &text)
std::size_t beg = 0; std::size_t beg = 0;
std::size_t len = 0; std::size_t len = 0;
std::size_t add = 0; std::size_t add = 0;
char c = *buf;
char c = *buf;
while (c) { // leading whitespace while (c) { // leading whitespace
if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\f') { if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\f') {
c = *++buf; c = *++buf;
@ -115,7 +118,8 @@ static std::vector<std::string> split_line(const std::string &text)
return list; 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); help_action = new QShortcut(QKeySequence::fromString("Ctrl+?"), parent);
connect(help_action, &QShortcut::activated, this, &CodeEditor::get_help); connect(help_action, &QShortcut::activated, this, &CodeEditor::get_help);
@ -269,14 +273,64 @@ QString CodeEditor::reformatLine(const QString &line)
return newtext; 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<const QString &>::of(&QCompleter::activated),
this, &CodeEditor::insertCompletedCommand);
reformatCurrentLine();
}
void CodeEditor::keyPressEvent(QKeyEvent *event) 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) { if (event->key() == Qt::Key_Tab) {
reformatCurrentLine(); reformatCurrentLine();
return; return;
} }
if (event->key() == Qt::Key_Backtab) { 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; return;
} }
QPlainTextEdit::keyPressEvent(event); 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() void CodeEditor::get_help()
{ {
QString page, help; QString page, help;

View File

@ -14,11 +14,13 @@
#ifndef CODEEDITOR_H #ifndef CODEEDITOR_H
#define CODEEDITOR_H #define CODEEDITOR_H
#include <QCompleter>
#include <QFont> #include <QFont>
#include <QMap> #include <QMap>
#include <QPlainTextEdit> #include <QPlainTextEdit>
#include <QShortcut> #include <QShortcut>
#include <QString> #include <QString>
#include <QStringList>
class CodeEditor : public QPlainTextEdit { class CodeEditor : public QPlainTextEdit {
Q_OBJECT Q_OBJECT
@ -32,6 +34,7 @@ public:
void setCursor(int block); void setCursor(int block);
void setHighlight(int block, bool error); void setHighlight(int block, bool error);
QString reformatLine(const QString &line); QString reformatLine(const QString &line);
void setCommandList(const QStringList &words);
static constexpr int NO_HIGHLIGHT = 1 << 30; static constexpr int NO_HIGHLIGHT = 1 << 30;
@ -50,10 +53,12 @@ private slots:
void find_help(QString &page, QString &help); void find_help(QString &page, QString &help);
void open_help(); void open_help();
void reformatCurrentLine(); void reformatCurrentLine();
void insertCompletedCommand(const QString &completion);
private: private:
QWidget *lineNumberArea; QWidget *lineNumberArea;
QShortcut *help_action; QShortcut *help_action;
QCompleter *command_completer;
int highlight; int highlight;
QMap<QString, QString> cmd_map; QMap<QString, QString> cmd_map;

View File

@ -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

View File

@ -57,6 +57,7 @@
static const QString blank(" "); static const QString blank(" ");
static constexpr int MAXRECENT = 5; static constexpr int MAXRECENT = 5;
static constexpr int BUFLEN = 128;
// duplicate string // duplicate string
static char *mystrdup(const std::string &text) static char *mystrdup(const std::string &text)
@ -296,6 +297,25 @@ LammpsGui::LammpsGui(QWidget *parent, const char *filename) :
setWindowTitle(QString("LAMMPS-GUI - *unknown*")); setWindowTitle(QString("LAMMPS-GUI - *unknown*"));
} }
resize(settings.value("mainx", "500").toInt(), settings.value("mainy", "320").toInt()); 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() LammpsGui::~LammpsGui()

View File

@ -2,6 +2,9 @@
<RCC> <RCC>
<qresource prefix="/"> <qresource prefix="/">
<file>lammps-icon-128x128.png</file> <file>lammps-icon-128x128.png</file>
<file>help_index.table</file>
<!-- This file is updated with: grep 'mycmd ==' ../../src/input.cpp | sed -e 's/^.*mycmd == "\(.*\)".*$/\1/' > lammps_internal_commands.txt -->
<file>lammps_internal_commands.txt</file>
<file>antialias.png</file> <file>antialias.png</file>
<file>application-calc.png</file> <file>application-calc.png</file>
<file>application-exit.png</file> <file>application-exit.png</file>
@ -34,7 +37,6 @@
<file>help-about.png</file> <file>help-about.png</file>
<file>help-browser.png</file> <file>help-browser.png</file>
<file>help-faq.png</file> <file>help-faq.png</file>
<file>help_index.table</file>
<file>image-x-generic.png</file> <file>image-x-generic.png</file>
<file>media-playback-start-2.png</file> <file>media-playback-start-2.png</file>
<file>media-playlist-repeat.png</file> <file>media-playlist-repeat.png</file>

View File

@ -97,6 +97,33 @@ int LammpsWrapper::id_name(const char *keyword, int idx, char *buf, int len)
return val; 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 LammpsWrapper::variable_info(int idx, char *buf, int len)
{ {
int val = 0; int val = 0;

View File

@ -35,6 +35,8 @@ public:
int id_count(const char *idtype); int id_count(const char *idtype);
int id_name(const char *idtype, int idx, char *buf, int buflen); 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); int variable_info(int idx, char *buf, int buflen);
double get_thermo(const char *keyword); double get_thermo(const char *keyword);