Qt: Check field autocomplete for syntactical validity
Currently the autocompletion engine always suggests a protocol field completion, even in places where it isn't syntactically valid. Fix that by compiling the preamble to the token under the cursor and checking the returned error. If it is DF_ERROR_UNEXPECTED_END that indicates a field or literal value was expected. Otherwise a field replacement is not valid in this position. Fixes #12811.
This commit is contained in:
parent
5853886d50
commit
967a3c3df9
|
@ -23,6 +23,8 @@ Wireshark is now better about generating valid UTF-8 output.
|
|||
|
||||
A new display filter feature for filtering raw bytes has been added.
|
||||
|
||||
Display filter autocomplete is smarter about not suggesting invalid syntax.
|
||||
|
||||
Many other improvements have been made.
|
||||
See the “New and Updated Features” section below for more details.
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ dfilter_expand(const char *expr, char **err_ret);
|
|||
*/
|
||||
|
||||
#define DF_ERROR_GENERIC -1
|
||||
#define DF_ERROR_UNEXPECTED_END -2
|
||||
|
||||
typedef struct _dfilter_loc {
|
||||
long col_start;
|
||||
|
|
|
@ -65,7 +65,7 @@ DIAG_ON_LEMON()
|
|||
any "error" symbols are shifted, if possible. */
|
||||
%syntax_error {
|
||||
if (!TOKEN) {
|
||||
dfilter_fail(dfw, DF_ERROR_GENERIC, NULL, "Unexpected end of filter expression.");
|
||||
dfilter_fail(dfw, DF_ERROR_UNEXPECTED_END, NULL, "Unexpected end of filter expression.");
|
||||
return;
|
||||
}
|
||||
FAIL(dfw, TOKEN, "\"%s\" was unexpected in this context.", stnode_token(TOKEN));
|
||||
|
|
|
@ -462,7 +462,7 @@ void CaptureFilterEdit::clearFilter()
|
|||
emit textEdited(text());
|
||||
}
|
||||
|
||||
void CaptureFilterEdit::buildCompletionList(const QString &primitive_word)
|
||||
void CaptureFilterEdit::buildCompletionList(const QString &primitive_word, const QString &preamble _U_)
|
||||
{
|
||||
if (primitive_word.length() < 1) {
|
||||
completion_model_->setStringList(QStringList());
|
||||
|
|
|
@ -66,7 +66,7 @@ private:
|
|||
CaptureFilterSyntaxWorker *syntax_worker_;
|
||||
QThread *syntax_thread_;
|
||||
|
||||
void buildCompletionList(const QString& primitive_word);
|
||||
void buildCompletionList(const QString &primitive_word, const QString &preamble);
|
||||
|
||||
signals:
|
||||
void captureFilterSyntaxChanged(bool valid);
|
||||
|
|
|
@ -71,7 +71,9 @@ DisplayFilterEdit::DisplayFilterEdit(QWidget *parent, DisplayFilterEditType type
|
|||
clear_button_(NULL),
|
||||
apply_button_(NULL),
|
||||
leftAlignActions_(false),
|
||||
last_applied_(QString())
|
||||
last_applied_(QString()),
|
||||
filter_word_preamble_(QString()),
|
||||
autocomplete_accepts_field_(true)
|
||||
{
|
||||
setAccessibleName(tr("Display filter entry"));
|
||||
|
||||
|
@ -472,7 +474,7 @@ void DisplayFilterEdit::updateBookmarkMenu()
|
|||
// - Recent and saved display filters in popup when editing first word.
|
||||
|
||||
// ui/gtk/filter_autocomplete.c:build_autocompletion_list
|
||||
void DisplayFilterEdit::buildCompletionList(const QString &field_word)
|
||||
void DisplayFilterEdit::buildCompletionList(const QString &field_word, const QString &preamble)
|
||||
{
|
||||
// Push a hint about the current field.
|
||||
if (syntaxState() == Valid) {
|
||||
|
@ -521,33 +523,57 @@ void DisplayFilterEdit::buildCompletionList(const QString &field_word)
|
|||
completion_model_->setStringList(complex_list);
|
||||
completer()->setCompletionPrefix(field_word);
|
||||
|
||||
void *proto_cookie;
|
||||
// Only add fields to completion if a field is valid at this position.
|
||||
// Try to compile preamble and check error message.
|
||||
if (preamble != filter_word_preamble_) {
|
||||
df_error_t *df_err = NULL;
|
||||
dfilter_t *test_df = NULL;
|
||||
if (preamble.size() > 0) {
|
||||
dfilter_compile_real(qUtf8Printable(preamble), &test_df, &df_err,
|
||||
DF_EXPAND_MACROS, __func__);
|
||||
}
|
||||
if (test_df == NULL || (df_err != NULL && df_err->code == DF_ERROR_UNEXPECTED_END)) {
|
||||
// Unexpected end of expression means "expected identifier (field) or literal".
|
||||
autocomplete_accepts_field_ = true;
|
||||
}
|
||||
else {
|
||||
autocomplete_accepts_field_ = false;
|
||||
}
|
||||
dfilter_free(test_df);
|
||||
dfilter_error_free(df_err);
|
||||
filter_word_preamble_ = preamble;
|
||||
}
|
||||
|
||||
QStringList field_list;
|
||||
int field_dots = static_cast<int>(field_word.count('.')); // Some protocol names (_ws.expert) contain periods.
|
||||
for (int proto_id = proto_get_first_protocol(&proto_cookie); proto_id != -1; proto_id = proto_get_next_protocol(&proto_cookie)) {
|
||||
protocol_t *protocol = find_protocol_by_id(proto_id);
|
||||
if (!proto_is_protocol_enabled(protocol)) continue;
|
||||
if (autocomplete_accepts_field_) {
|
||||
void *proto_cookie;
|
||||
|
||||
const QString pfname = proto_get_protocol_filter_name(proto_id);
|
||||
field_list << pfname;
|
||||
int field_dots = static_cast<int>(field_word.count('.')); // Some protocol names (_ws.expert) contain periods.
|
||||
for (int proto_id = proto_get_first_protocol(&proto_cookie); proto_id != -1; proto_id = proto_get_next_protocol(&proto_cookie)) {
|
||||
protocol_t *protocol = find_protocol_by_id(proto_id);
|
||||
if (!proto_is_protocol_enabled(protocol)) continue;
|
||||
|
||||
// Add fields only if we're past the protocol name and only for the
|
||||
// current protocol.
|
||||
if (field_dots > pfname.count('.')) {
|
||||
void *field_cookie;
|
||||
const QByteArray fw_ba = field_word.toUtf8(); // or toLatin1 or toStdString?
|
||||
const char *fw_utf8 = fw_ba.constData();
|
||||
gsize fw_len = (gsize) strlen(fw_utf8);
|
||||
for (header_field_info *hfinfo = proto_get_first_protocol_field(proto_id, &field_cookie); hfinfo; hfinfo = proto_get_next_protocol_field(proto_id, &field_cookie)) {
|
||||
if (hfinfo->same_name_prev_id != -1) continue; // Ignore duplicate names.
|
||||
const QString pfname = proto_get_protocol_filter_name(proto_id);
|
||||
field_list << pfname;
|
||||
|
||||
if (!g_ascii_strncasecmp(fw_utf8, hfinfo->abbrev, fw_len)) {
|
||||
if ((gsize) strlen(hfinfo->abbrev) != fw_len) field_list << hfinfo->abbrev;
|
||||
// Add fields only if we're past the protocol name and only for the
|
||||
// current protocol.
|
||||
if (field_dots > pfname.count('.')) {
|
||||
void *field_cookie;
|
||||
const QByteArray fw_ba = field_word.toUtf8(); // or toLatin1 or toStdString?
|
||||
const char *fw_utf8 = fw_ba.constData();
|
||||
gsize fw_len = (gsize) strlen(fw_utf8);
|
||||
for (header_field_info *hfinfo = proto_get_first_protocol_field(proto_id, &field_cookie); hfinfo; hfinfo = proto_get_next_protocol_field(proto_id, &field_cookie)) {
|
||||
if (hfinfo->same_name_prev_id != -1) continue; // Ignore duplicate names.
|
||||
|
||||
if (!g_ascii_strncasecmp(fw_utf8, hfinfo->abbrev, fw_len)) {
|
||||
if ((gsize) strlen(hfinfo->abbrev) != fw_len) field_list << hfinfo->abbrev;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
field_list.sort();
|
||||
}
|
||||
field_list.sort();
|
||||
|
||||
completion_model_->setStringList(complex_list + field_list);
|
||||
completer()->setCompletionPrefix(field_word);
|
||||
|
|
|
@ -76,9 +76,11 @@ private:
|
|||
StockIconToolButton *apply_button_;
|
||||
bool leftAlignActions_;
|
||||
QString last_applied_;
|
||||
QString filter_word_preamble_;
|
||||
bool autocomplete_accepts_field_;
|
||||
|
||||
void setDefaultPlaceholderText();
|
||||
void buildCompletionList(const QString& field_word);
|
||||
void buildCompletionList(const QString &field_word, const QString &preamble);
|
||||
|
||||
void createFilterTextDropMenu(QDropEvent *event, bool prepare, QString filterText = QString());
|
||||
|
||||
|
|
|
@ -125,7 +125,7 @@ void FieldFilterEdit::checkFilter(const QString& filter_text)
|
|||
// - Recent and saved display filters in popup when editing first word.
|
||||
|
||||
// ui/gtk/filter_autocomplete.c:build_autocompletion_list
|
||||
void FieldFilterEdit::buildCompletionList(const QString &field_word)
|
||||
void FieldFilterEdit::buildCompletionList(const QString &field_word, const QString &preamble _U_)
|
||||
{
|
||||
// Push a hint about the current field.
|
||||
if (syntaxState() == Valid) {
|
||||
|
|
|
@ -42,7 +42,7 @@ private:
|
|||
QString placeholder_text_;
|
||||
|
||||
void setDefaultPlaceholderText();
|
||||
void buildCompletionList(const QString& field_word);
|
||||
void buildCompletionList(const QString &field_word, const QString &preamble);
|
||||
|
||||
signals:
|
||||
void pushFilterSyntaxStatus(const QString&);
|
||||
|
|
|
@ -377,10 +377,9 @@ void SyntaxLineEdit::completionKeyPressEvent(QKeyEvent *event)
|
|||
return;
|
||||
}
|
||||
|
||||
QPoint token_coords(getTokenUnderCursor());
|
||||
|
||||
QString token_word = text().mid(token_coords.x(), token_coords.y());
|
||||
buildCompletionList(token_word);
|
||||
QStringList sentence(splitLineUnderCursor());
|
||||
Q_ASSERT(sentence.size() == 2); // (preamble, token)
|
||||
buildCompletionList(sentence[1] /* token */, sentence[0] /* preamble */);
|
||||
|
||||
if (completion_model_->stringList().length() < 1) {
|
||||
completer_->popup()->hide();
|
||||
|
@ -532,3 +531,19 @@ QPoint SyntaxLineEdit::getTokenUnderCursor()
|
|||
|
||||
return QPoint(start, len);
|
||||
}
|
||||
|
||||
QStringList SyntaxLineEdit::splitLineUnderCursor()
|
||||
{
|
||||
QPoint token_coords(getTokenUnderCursor());
|
||||
|
||||
// Split line into preamble and word under cursor.
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
QString preamble = text().first(token_coords.x()).trimmed();
|
||||
#else
|
||||
QString preamble = text().mid(0, token_coords.x()).trimmed();
|
||||
#endif
|
||||
// This should be trimmed already
|
||||
QString token_word = text().mid(token_coords.x(), token_coords.y());
|
||||
|
||||
return QStringList{ preamble, token_word };
|
||||
}
|
||||
|
|
|
@ -60,9 +60,11 @@ protected:
|
|||
QStringListModel *completion_model_;
|
||||
void setCompletionTokenChars(const QString &token_chars) { token_chars_ = token_chars; }
|
||||
bool isComplexFilter(const QString &filter);
|
||||
virtual void buildCompletionList(const QString&) { }
|
||||
virtual void buildCompletionList(const QString &field_word, const QString &preamble) { Q_UNUSED(field_word); Q_UNUSED(preamble); }
|
||||
// x = Start position, y = length
|
||||
QPoint getTokenUnderCursor();
|
||||
// Returns (preamble, token)
|
||||
QStringList splitLineUnderCursor();
|
||||
|
||||
virtual bool event(QEvent *event);
|
||||
void completionKeyPressEvent(QKeyEvent *event);
|
||||
|
|
Loading…
Reference in New Issue