File indexing completed on 2024-04-14 03:55:12

0001 /*
0002     SPDX-FileCopyrightText: 2019-2020 Nibaldo González S. <nibgonz@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 #include "katemodemenulist.h"
0007 
0008 #include "kateconfig.h"
0009 #include "katedocument.h"
0010 #include "kateglobal.h"
0011 #include "katemodemanager.h"
0012 #include "katepartdebug.h"
0013 
0014 #include <QAbstractItemView>
0015 #include <QApplication>
0016 #include <QFrame>
0017 #include <QHBoxLayout>
0018 #include <QVBoxLayout>
0019 #include <QWidgetAction>
0020 
0021 #include <KLocalizedString>
0022 
0023 namespace
0024 {
0025 /**
0026  * Detect words delimiters:
0027  *      ! " # $ % & ' ( ) * + , - . / : ;
0028  *      < = > ? [ \ ] ^ ` { | } ~ « »
0029  */
0030 static bool isDelimiter(const ushort c)
0031 {
0032     return (c <= 126 && c >= 33 && (c >= 123 || c <= 47 || (c <= 96 && c >= 58 && c != 95 && (c >= 91 || c <= 63)))) || c == 171 || c == 187;
0033 }
0034 
0035 /**
0036  * Overlay scroll bar on the list according to the operating system
0037  * and/or the desktop environment. In some desktop themes the scroll bar
0038  * isn't transparent, so it's better not to overlap it on the list.
0039  * NOTE: Currently, in the Breeze theme, the scroll bar does not overlap
0040  * the content. See: https://phabricator.kde.org/T9126
0041  */
0042 inline static bool overlapScrollBar()
0043 {
0044     return false;
0045 }
0046 }
0047 
0048 void KateModeMenuList::init()
0049 {
0050     connect(this, &QMenu::aboutToShow, this, &KateModeMenuList::onAboutToShowMenu);
0051 }
0052 
0053 void KateModeMenuList::onAboutToShowMenu()
0054 {
0055     if (m_initialized) {
0056         return;
0057     }
0058     /*
0059      * Fix font size & font style: display the font correctly when changing it from the
0060      * KDE Plasma preferences. For example, the font type "Menu" is displayed, but "font()"
0061      * and "fontMetrics()" return the font type "General". Therefore, this overwrites the
0062      * "General" font. This makes it possible to correctly apply word wrapping on items,
0063      * when changing the font or its size.
0064      */
0065     QFont font = this->font();
0066     font.setFamily(font.family());
0067     font.setStyle(font.style());
0068     font.setStyleName(font.styleName());
0069     font.setBold(font.bold());
0070     font.setItalic(font.italic());
0071     font.setUnderline(font.underline());
0072     font.setStrikeOut(font.strikeOut());
0073     font.setPointSize(font.pointSize());
0074     setFont(font);
0075 
0076     /*
0077      * Calculate the size of the list and the checkbox icon (in pixels) according
0078      * to the font size. From font 12pt to 26pt increase the list size.
0079      */
0080     int menuWidth = 266;
0081     int menuHeight = 428;
0082     const int fontSize = font.pointSize();
0083     if (fontSize >= 12) {
0084         const int increaseSize = (fontSize - 11) * 10;
0085         if (increaseSize >= 150) { // Font size: 26pt
0086             menuWidth += 150;
0087             menuHeight += 150;
0088         } else {
0089             menuWidth += increaseSize;
0090             menuHeight += increaseSize;
0091         }
0092 
0093         if (fontSize >= 22) {
0094             m_iconSize = 32;
0095         } else if (fontSize >= 18) {
0096             m_iconSize = 24;
0097         } else if (fontSize >= 14) {
0098             m_iconSize = 22;
0099         } else if (fontSize >= 12) {
0100             m_iconSize = 18;
0101         }
0102     }
0103 
0104     // Create list and search bar
0105     m_list = KateModeMenuListData::Factory::createListView(this);
0106     m_searchBar = KateModeMenuListData::Factory::createSearchLine(this);
0107 
0108     // Empty icon for items.
0109     QPixmap emptyIconPixmap(m_iconSize, m_iconSize);
0110     emptyIconPixmap.fill(Qt::transparent);
0111     m_emptyIcon = QIcon(emptyIconPixmap);
0112 
0113     /*
0114      * Load list widget, scroll bar and items.
0115      */
0116     if (overlapScrollBar()) {
0117         // The vertical scroll bar will be added in another layout
0118         m_scroll = new QScrollBar(Qt::Vertical, this);
0119         m_list->setVerticalScrollBar(m_scroll);
0120         m_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0121         m_list->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0122     } else {
0123         m_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0124         m_list->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
0125     }
0126     m_list->setIconSize(QSize(m_iconSize, m_iconSize));
0127     m_list->setResizeMode(QListView::Adjust);
0128     // Size of the list widget and search bar.
0129     setSizeList(menuHeight, menuWidth);
0130 
0131     // Data model (items).
0132     // couple model to view to let it be deleted with the view
0133     m_model = new QStandardItemModel(0, 0, m_list);
0134     loadHighlightingModel();
0135 
0136     /*
0137      * Search bar widget.
0138      */
0139     m_searchBar->setPlaceholderText(i18nc("Placeholder in search bar", "Search..."));
0140     m_searchBar->setToolTip(i18nc("ToolTip of the search bar of modes of syntax highlighting",
0141                                   "Search for syntax highlighting modes by language name or file extension (for example, C++ or .cpp)"));
0142     m_searchBar->setMaxLength(200);
0143 
0144     m_list->setFocusProxy(m_searchBar);
0145 
0146     /*
0147      * Set layouts and widgets.
0148      * container (QWidget)
0149      * └── layoutContainer (QVBoxLayout)
0150      *      ├── m_layoutList (QGridLayout)
0151      *      │   ├── m_list (ListView)
0152      *      │   ├── layoutScrollBar (QHBoxLayout) --> m_scroll (QScrollBar)
0153      *      │   └── m_emptyListMsg (QLabel)
0154      *      └── layoutSearchBar (QHBoxLayout) --> m_searchBar (SearchLine)
0155      */
0156     QWidget *container = new QWidget(this);
0157     QVBoxLayout *layoutContainer = new QVBoxLayout(container);
0158     m_layoutList = new QGridLayout();
0159     QHBoxLayout *layoutSearchBar = new QHBoxLayout();
0160 
0161     m_layoutList->addWidget(m_list, 0, 0, Qt::AlignLeft);
0162 
0163     // Add scroll bar and set margin.
0164     // Overlap scroll bar above the list widget.
0165     if (overlapScrollBar()) {
0166         QHBoxLayout *layoutScrollBar = new QHBoxLayout();
0167         layoutScrollBar->addWidget(m_scroll);
0168         layoutScrollBar->setContentsMargins(1, 2, 2, 2); // ScrollBar Margin = 2, Also see: KateModeMenuListData::ListView::getContentWidth()
0169         m_layoutList->addLayout(layoutScrollBar, 0, 0, Qt::AlignRight);
0170     }
0171 
0172     layoutSearchBar->addWidget(m_searchBar);
0173     layoutContainer->addLayout(m_layoutList);
0174     layoutContainer->addLayout(layoutSearchBar);
0175 
0176     QWidgetAction *widAct = new QWidgetAction(this);
0177     widAct->setDefaultWidget(container);
0178     addAction(widAct);
0179 
0180     /*
0181      * Detect selected item with one click.
0182      * This also applies to double-clicks.
0183      */
0184     connect(m_list, &KateModeMenuListData::ListView::clicked, this, &KateModeMenuList::selectHighlighting);
0185 
0186     m_initialized = true;
0187 }
0188 
0189 void KateModeMenuList::reloadItems()
0190 {
0191     // We aren't initialized, nothing to reload
0192     if (!m_initialized) {
0193         return;
0194     }
0195 
0196     const QString searchText = m_searchBar->text().trimmed();
0197     m_searchBar->m_bestResults.clear();
0198     if (!isHidden()) {
0199         hide();
0200     }
0201     /*
0202      * Clear model.
0203      * NOTE: This deletes the item objects and widgets indexed to items.
0204      * That is, the QLabel & QFrame objects of the section titles are also deleted.
0205      * See: QAbstractItemView::setIndexWidget(), QObject::deleteLater()
0206      */
0207     m_model->clear();
0208     m_list->selectionModel()->clear();
0209     m_selectedItem = nullptr;
0210 
0211     loadHighlightingModel();
0212 
0213     // Restore search text, if there is.
0214     m_searchBar->m_bSearchStateAutoScroll = false;
0215     if (!searchText.isEmpty()) {
0216         selectHighlightingFromExternal();
0217         m_searchBar->updateSearch(searchText);
0218         m_searchBar->setText(searchText);
0219     }
0220 }
0221 
0222 void KateModeMenuList::loadHighlightingModel()
0223 {
0224     m_list->setModel(m_model);
0225 
0226     QString *prevHlSection = nullptr;
0227     /*
0228      * The width of the text container in the item, in pixels. This is used to make
0229      * a custom word wrap and prevent the item's text from passing under the scroll bar.
0230      * NOTE: 8 = Icon margin
0231      */
0232     const int maxWidthText = m_list->getContentWidth(1, 8) - m_iconSize - 8;
0233 
0234     // Transparent color used as background in the sections.
0235     QPixmap transparentPixmap = QPixmap(m_iconSize / 2, m_iconSize / 2);
0236     transparentPixmap.fill(Qt::transparent);
0237     QBrush transparentBrush(transparentPixmap);
0238 
0239     /*
0240      * The first item on the list is the "Best Search Matches" section,
0241      * which will remain hidden and will only be shown when necessary.
0242      */
0243     createSectionList(QString(), transparentBrush, false);
0244     m_defaultHeightItemSection = m_list->visualRect(m_model->index(0, 0)).height();
0245     m_list->setRowHidden(0, true);
0246 
0247     /*
0248      * Get list of modes from KateModeManager::list().
0249      * We assume that the modes are arranged according to sections, alphabetically;
0250      * and the attribute "translatedSection" isn't empty if "section" has a value.
0251      */
0252     for (auto *hl : KTextEditor::EditorPrivate::self()->modeManager()->list()) {
0253         if (hl->name.isEmpty()) {
0254             continue;
0255         }
0256 
0257         // Detects a new section.
0258         if (!hl->translatedSection.isEmpty() && (prevHlSection == nullptr || hl->translatedSection != *prevHlSection)) {
0259             createSectionList(hl->sectionTranslated(), transparentBrush);
0260         }
0261         prevHlSection = hl->translatedSection.isNull() ? nullptr : &hl->translatedSection;
0262 
0263         // Create item in the list with the language name.
0264         KateModeMenuListData::ListItem *item = KateModeMenuListData::Factory::createListItem();
0265         /*
0266          * NOTE:
0267          *  - (If the scroll bar is not overlapped) In QListView::setWordWrap(),
0268          *    when the scroll bar is hidden, the word wrap changes, but the size
0269          *    of the items is keeped, causing display problems in some items.
0270          *    KateModeMenuList::setWordWrap() applies a fixed word wrap.
0271          *  - Search names generated in: KateModeMenuListData::SearchLine::updateSearch()
0272          */
0273         item->setText(setWordWrap(hl->nameTranslated(), maxWidthText, m_list->fontMetrics()));
0274         item->setMode(hl);
0275 
0276         item->setIcon(m_emptyIcon);
0277         item->setEditable(false);
0278         // Add item
0279         m_model->appendRow(item);
0280     }
0281 }
0282 
0283 KateModeMenuListData::ListItem *KateModeMenuList::createSectionList(const QString &sectionName, const QBrush &background, bool bSeparator, int modelPosition)
0284 {
0285     /*
0286      * Add a separator to the list.
0287      */
0288     if (bSeparator) {
0289         KateModeMenuListData::ListItem *separator = KateModeMenuListData::Factory::createListItem();
0290         separator->setFlags(Qt::NoItemFlags);
0291         separator->setEnabled(false);
0292         separator->setEditable(false);
0293         separator->setSelectable(false);
0294 
0295         separator->setSizeHint(QSize(separator->sizeHint().width() - 2, 4));
0296         separator->setBackground(background);
0297 
0298         QFrame *line = new QFrame(m_list);
0299         line->setFrameStyle(QFrame::HLine);
0300 
0301         if (modelPosition < 0) {
0302             m_model->appendRow(separator);
0303         } else {
0304             m_model->insertRow(modelPosition, separator);
0305         }
0306         m_list->setIndexWidget(m_model->index(separator->row(), 0), line);
0307         m_list->selectionModel()->select(separator->index(), QItemSelectionModel::Deselect);
0308     }
0309 
0310     /*
0311      * Add the section name to the list.
0312      */
0313     KateModeMenuListData::ListItem *section = KateModeMenuListData::Factory::createListItem();
0314     section->setFlags(Qt::NoItemFlags);
0315     section->setEnabled(false);
0316     section->setEditable(false);
0317     section->setSelectable(false);
0318 
0319     QLabel *label = new QLabel(sectionName, m_list);
0320     if (m_list->layoutDirection() == Qt::RightToLeft) {
0321         label->setAlignment(Qt::AlignRight);
0322     }
0323     label->setTextFormat(Qt::PlainText);
0324     label->setIndent(6);
0325 
0326     /*
0327      * NOTE: Names of sections in bold. The font color
0328      * should change according to Kate's color theme.
0329      */
0330     QFont font = label->font();
0331     font.setWeight(QFont::Bold);
0332     label->setFont(font);
0333 
0334     section->setBackground(background);
0335 
0336     if (modelPosition < 0) {
0337         m_model->appendRow(section);
0338     } else {
0339         m_model->insertRow(modelPosition + 1, section);
0340     }
0341     m_list->setIndexWidget(m_model->index(section->row(), 0), label);
0342     m_list->selectionModel()->select(section->index(), QItemSelectionModel::Deselect);
0343 
0344     // Apply word wrap in sections, for long labels.
0345     const int containerTextWidth = m_list->getContentWidth(2, 4);
0346     int heightSectionMargin = m_list->visualRect(m_model->index(section->row(), 0)).height() - label->sizeHint().height();
0347 
0348     if (label->sizeHint().width() > containerTextWidth) {
0349         label->setText(setWordWrap(label->text(), containerTextWidth - label->indent(), label->fontMetrics()));
0350         if (heightSectionMargin < 2) {
0351             heightSectionMargin = 2;
0352         }
0353         section->setSizeHint(QSize(section->sizeHint().width(), label->sizeHint().height() + heightSectionMargin));
0354     } else if (heightSectionMargin < 2) {
0355         section->setSizeHint(QSize(section->sizeHint().width(), label->sizeHint().height() + 2));
0356     }
0357 
0358     return section;
0359 }
0360 
0361 void KateModeMenuList::setButton(QPushButton *button, AlignmentHButton positionX, AlignmentVButton positionY, AutoUpdateTextButton autoUpdateTextButton)
0362 {
0363     if (positionX == AlignHInverse) {
0364         if (layoutDirection() == Qt::RightToLeft) {
0365             m_positionX = KateModeMenuList::AlignLeft;
0366         } else {
0367             m_positionX = KateModeMenuList::AlignRight;
0368         }
0369     } else if (positionX == AlignLeft && layoutDirection() != Qt::RightToLeft) {
0370         m_positionX = KateModeMenuList::AlignHDefault;
0371     } else {
0372         m_positionX = positionX;
0373     }
0374 
0375     m_positionY = positionY;
0376     m_pushButton = button;
0377     m_autoUpdateTextButton = autoUpdateTextButton;
0378 }
0379 
0380 void KateModeMenuList::setSizeList(const int height, const int width)
0381 {
0382     m_list->setSizeList(height, width);
0383     m_searchBar->setWidth(width);
0384 }
0385 
0386 void KateModeMenuList::autoScroll()
0387 {
0388     if (m_selectedItem && m_autoScroll == ScrollToSelectedItem) {
0389         m_list->setCurrentItem(m_selectedItem->row());
0390         m_list->scrollToItem(m_selectedItem->row(), QAbstractItemView::PositionAtCenter);
0391     } else {
0392         m_list->scrollToFirstItem();
0393     }
0394 }
0395 
0396 void KateModeMenuList::showEvent(QShowEvent *event)
0397 {
0398     Q_UNUSED(event);
0399     /*
0400      * TODO: Put the menu on the bottom-edge of the window if the status bar is hidden,
0401      * to show the menu with keyboard shortcuts. To do this, it's preferable to add a new
0402      * function/slot to display the menu, correcting the position. If the trigger button
0403      * isn't set or is destroyed, there may be problems detecting Right-to-left layouts.
0404      */
0405 
0406     // Set the menu position
0407     if (m_pushButton && m_pushButton->isVisible()) {
0408         /*
0409          * Get vertical position.
0410          * NOTE: In KDE Plasma with Wayland, the reference point of the position
0411          * is the main window, not the desktop. Therefore, if the window is vertically
0412          * smaller than the menu, it will be positioned on the upper edge of the window.
0413          */
0414         int newMenu_y; // New vertical menu position
0415         if (m_positionY == AlignTop) {
0416             newMenu_y = m_pushButton->mapToGlobal(QPoint(0, 0)).y() - geometry().height();
0417             if (newMenu_y < 0) {
0418                 newMenu_y = 0;
0419             }
0420         } else {
0421             newMenu_y = pos().y();
0422         }
0423 
0424         // Set horizontal position.
0425         if (m_positionX == AlignRight) {
0426             // New horizontal menu position
0427             int newMenu_x = pos().x() - geometry().width() + m_pushButton->geometry().width();
0428             // Get position of the right edge of the toggle button
0429             const int buttonPositionRight = m_pushButton->mapToGlobal(QPoint(0, 0)).x() + m_pushButton->geometry().width();
0430             if (newMenu_x < 0) {
0431                 newMenu_x = 0;
0432             } else if (newMenu_x + geometry().width() < buttonPositionRight) {
0433                 newMenu_x = buttonPositionRight - geometry().width();
0434             }
0435             move(newMenu_x, newMenu_y);
0436         } else if (m_positionX == AlignLeft) {
0437             move(m_pushButton->mapToGlobal(QPoint(0, 0)).x(), newMenu_y);
0438         } else if (m_positionY == AlignTop) {
0439             // Set vertical position, use the default horizontal position
0440             move(pos().x(), newMenu_y);
0441         }
0442     }
0443 
0444     // Select text from the search bar
0445     if (!m_searchBar->text().isEmpty()) {
0446         if (m_searchBar->text().trimmed().isEmpty()) {
0447             m_searchBar->clear();
0448         } else {
0449             m_searchBar->selectAll();
0450         }
0451     }
0452 
0453     // Set focus on the list. The list widget uses focus proxy to the search bar.
0454     m_list->setFocus(Qt::ActiveWindowFocusReason);
0455 
0456     KTextEditor::DocumentPrivate *doc = m_doc;
0457     if (!doc) {
0458         return;
0459     }
0460 
0461     // First show or if an external changed the current syntax highlighting.
0462     if (!m_selectedItem || (m_selectedItem->hasMode() && m_selectedItem->getMode()->name != doc->fileType())) {
0463         if (!selectHighlightingFromExternal(doc->fileType())) {
0464             // Strange case: if the current syntax highlighting does not exist in the list.
0465             if (m_selectedItem) {
0466                 m_selectedItem->setIcon(m_emptyIcon);
0467             }
0468             if ((m_selectedItem || !m_list->currentItem()) && m_searchBar->text().isEmpty()) {
0469                 m_list->scrollToFirstItem();
0470             }
0471             m_selectedItem = nullptr;
0472         }
0473     }
0474 }
0475 
0476 void KateModeMenuList::updateSelectedItem(KateModeMenuListData::ListItem *item)
0477 {
0478     // Change the previously selected item to empty icon
0479     if (m_selectedItem) {
0480         m_selectedItem->setIcon(m_emptyIcon);
0481     }
0482 
0483     // Update the selected item
0484     item->setIcon(m_checkIcon);
0485     m_selectedItem = item;
0486     m_list->setCurrentItem(item->row());
0487 
0488     // Change text of the trigger button
0489     if (bool(m_autoUpdateTextButton) && m_pushButton && item->hasMode()) {
0490         m_pushButton->setText(item->getMode()->nameTranslated());
0491     }
0492 }
0493 
0494 void KateModeMenuList::selectHighlightingSetVisibility(QStandardItem *pItem, const bool bHideMenu)
0495 {
0496     if (!pItem || !pItem->isSelectable() || !pItem->isEnabled()) {
0497         return;
0498     }
0499 
0500     KateModeMenuListData::ListItem *item = static_cast<KateModeMenuListData::ListItem *>(pItem);
0501 
0502     if (!item->text().isEmpty()) {
0503         updateSelectedItem(item);
0504     }
0505     if (bHideMenu) {
0506         hide();
0507     }
0508 
0509     // Apply syntax highlighting
0510     KTextEditor::DocumentPrivate *doc = m_doc;
0511     if (doc && item->hasMode()) {
0512         doc->updateFileType(item->getMode()->name, true);
0513     }
0514 }
0515 
0516 void KateModeMenuList::selectHighlighting(const QModelIndex &index)
0517 {
0518     selectHighlightingSetVisibility(m_model->item(index.row(), 0), true);
0519 }
0520 
0521 bool KateModeMenuList::selectHighlightingFromExternal(const QString &nameMode)
0522 {
0523     for (int i = 0; i < m_model->rowCount(); ++i) {
0524         KateModeMenuListData::ListItem *item = static_cast<KateModeMenuListData::ListItem *>(m_model->item(i, 0));
0525 
0526         if (!item->hasMode() || m_model->item(i, 0)->text().isEmpty()) {
0527             continue;
0528         }
0529         if (item->getMode()->name == nameMode || (nameMode.isEmpty() && item->getMode()->name == QLatin1String("Normal"))) {
0530             updateSelectedItem(item);
0531 
0532             // Clear search
0533             if (!m_searchBar->text().isEmpty()) {
0534                 // Prevent the empty list message from being seen over the items for a short time
0535                 if (m_emptyListMsg) {
0536                     m_emptyListMsg->hide();
0537                 }
0538 
0539                 // NOTE: This calls updateSearch(), it's scrolled to the selected item or the first item.
0540                 m_searchBar->clear();
0541             } else if (m_autoScroll == ScrollToSelectedItem) {
0542                 m_list->scrollToItem(i);
0543             } else {
0544                 // autoScroll()
0545                 m_list->scrollToFirstItem();
0546             }
0547             return true;
0548         }
0549     }
0550     return false;
0551 }
0552 
0553 bool KateModeMenuList::selectHighlightingFromExternal()
0554 {
0555     KTextEditor::DocumentPrivate *doc = m_doc;
0556     if (doc) {
0557         return selectHighlightingFromExternal(doc->fileType());
0558     }
0559     return false;
0560 }
0561 
0562 void KateModeMenuList::loadEmptyMsg()
0563 {
0564     m_emptyListMsg = new QLabel(i18nc("A search yielded no results", "No items matching your search"), this);
0565     m_emptyListMsg->setMargin(15);
0566     m_emptyListMsg->setWordWrap(true);
0567 
0568     const int fontSize = font().pointSize() > 10 ? font().pointSize() + 4 : 14;
0569 
0570     QColor color = m_emptyListMsg->palette().color(QPalette::Text);
0571     m_emptyListMsg->setStyleSheet(QLatin1String("font-size: ") + QString::number(fontSize) + QLatin1String("pt; color: rgba(") + QString::number(color.red())
0572                                   + QLatin1Char(',') + QString::number(color.green()) + QLatin1Char(',') + QString::number(color.blue())
0573                                   + QLatin1String(", 0.3);"));
0574 
0575     m_emptyListMsg->setAlignment(Qt::AlignCenter);
0576     m_layoutList->addWidget(m_emptyListMsg, 0, 0, Qt::AlignCenter);
0577 }
0578 
0579 QString KateModeMenuList::setWordWrap(const QString &text, const int maxWidth, const QFontMetrics &fontMetrics) const
0580 {
0581     // Get the length of the text, in pixels, and compare it with the container
0582     if (fontMetrics.horizontalAdvance(text) <= maxWidth) {
0583         return text;
0584     }
0585 
0586     // Add line breaks in the text to fit in the container
0587     QStringList words = text.split(QLatin1Char(' '));
0588     if (words.count() < 1) {
0589         return text;
0590     }
0591     QString newText = QString();
0592     QString tmpLineText = QString();
0593 
0594     for (int i = 0; i < words.count() - 1; ++i) {
0595         // Elide mode in long words
0596         if (fontMetrics.horizontalAdvance(words[i]) > maxWidth) {
0597             if (!tmpLineText.isEmpty()) {
0598                 newText += tmpLineText + QLatin1Char('\n');
0599                 tmpLineText.clear();
0600             }
0601             newText +=
0602                 fontMetrics.elidedText(words[i], m_list->layoutDirection() == Qt::RightToLeft ? Qt::ElideLeft : Qt::ElideRight, maxWidth) + QLatin1Char('\n');
0603             continue;
0604         } else {
0605             tmpLineText += words[i];
0606         }
0607 
0608         // This prevents the last line of text from having only one word with 1 or 2 chars
0609         if (i == words.count() - 3 && words[i + 2].length() <= 2
0610             && fontMetrics.horizontalAdvance(tmpLineText + QLatin1Char(' ') + words[i + 1] + QLatin1Char(' ') + words[i + 2]) > maxWidth) {
0611             newText += tmpLineText + QLatin1Char('\n');
0612             tmpLineText.clear();
0613         }
0614         // Add line break if the maxWidth is exceeded with the next word
0615         else if (fontMetrics.horizontalAdvance(tmpLineText + QLatin1Char(' ') + words[i + 1]) > maxWidth) {
0616             newText += tmpLineText + QLatin1Char('\n');
0617             tmpLineText.clear();
0618         } else {
0619             tmpLineText.append(QLatin1Char(' '));
0620         }
0621     }
0622 
0623     // Add line breaks in delimiters, if the last word is greater than the container
0624     bool bElidedLastWord = false;
0625     if (fontMetrics.horizontalAdvance(words[words.count() - 1]) > maxWidth) {
0626         bElidedLastWord = true;
0627         const int lastw = words.count() - 1;
0628         for (int c = words[lastw].length() - 1; c >= 0; --c) {
0629             if (isDelimiter(words[lastw][c].unicode()) && fontMetrics.horizontalAdvance(words[lastw].mid(0, c + 1)) <= maxWidth) {
0630                 bElidedLastWord = false;
0631                 if (fontMetrics.horizontalAdvance(words[lastw].mid(c + 1)) > maxWidth) {
0632                     words[lastw] = words[lastw].mid(0, c + 1) + QLatin1Char('\n')
0633                         + fontMetrics.elidedText(words[lastw].mid(c + 1),
0634                                                  m_list->layoutDirection() == Qt::RightToLeft ? Qt::ElideLeft : Qt::ElideRight,
0635                                                  maxWidth);
0636                 } else {
0637                     words[lastw].insert(c + 1, QLatin1Char('\n'));
0638                 }
0639                 break;
0640             }
0641         }
0642     }
0643 
0644     if (!tmpLineText.isEmpty()) {
0645         newText += tmpLineText;
0646     }
0647     if (bElidedLastWord) {
0648         newText += fontMetrics.elidedText(words[words.count() - 1], m_list->layoutDirection() == Qt::RightToLeft ? Qt::ElideLeft : Qt::ElideRight, maxWidth);
0649     } else {
0650         newText += words[words.count() - 1];
0651     }
0652     return newText;
0653 }
0654 
0655 void KateModeMenuListData::SearchLine::setWidth(const int width)
0656 {
0657     setMinimumWidth(width);
0658     setMaximumWidth(width);
0659 }
0660 
0661 void KateModeMenuListData::ListView::setSizeList(const int height, const int width)
0662 {
0663     setMinimumWidth(width);
0664     setMaximumWidth(width);
0665     setMinimumHeight(height);
0666     setMaximumHeight(height);
0667 }
0668 
0669 int KateModeMenuListData::ListView::getWidth() const
0670 {
0671     // Equivalent to: sizeHint().width()
0672     // But "sizeHint().width()" returns an incorrect value when the menu is large.
0673     return size().width() - 4;
0674 }
0675 
0676 int KateModeMenuListData::ListView::getContentWidth(const int overlayScrollbarMargin, const int classicScrollbarMargin) const
0677 {
0678     if (overlapScrollBar()) {
0679         return getWidth() - m_parentMenu->m_scroll->sizeHint().width() - 2 - overlayScrollbarMargin; // ScrollBar Margin = 2
0680     }
0681     return getWidth() - verticalScrollBar()->sizeHint().width() - classicScrollbarMargin;
0682 }
0683 
0684 int KateModeMenuListData::ListView::getContentWidth() const
0685 {
0686     return getContentWidth(0, 0);
0687 }
0688 
0689 bool KateModeMenuListData::ListItem::generateSearchName(const QString &itemName)
0690 {
0691     QString searchName = QString(itemName);
0692     bool bNewName = false;
0693 
0694     // Replace word delimiters with spaces
0695     for (int i = searchName.length() - 1; i >= 0; --i) {
0696         if (isDelimiter(searchName[i].unicode())) {
0697             searchName.replace(i, 1, QLatin1Char(' '));
0698             if (!bNewName) {
0699                 bNewName = true;
0700             }
0701         }
0702         // Avoid duplicate delimiters/spaces
0703         if (bNewName && i < searchName.length() - 1 && searchName[i].isSpace() && searchName[i + 1].isSpace()) {
0704             searchName.remove(i + 1, 1);
0705         }
0706     }
0707 
0708     if (bNewName) {
0709         if (searchName[searchName.length() - 1].isSpace()) {
0710             searchName.remove(searchName.length() - 1, 1);
0711         }
0712         if (searchName[0].isSpace()) {
0713             searchName.remove(0, 1);
0714         }
0715         m_searchName = searchName;
0716         return true;
0717     } else {
0718         m_searchName = itemName;
0719     }
0720     return false;
0721 }
0722 
0723 bool KateModeMenuListData::ListItem::matchExtension(const QString &text) const
0724 {
0725     if (!hasMode() || m_type->wildcards.count() == 0) {
0726         return false;
0727     }
0728 
0729     /*
0730      * Only file extensions and full names are matched. Files like "Kconfig*"
0731      * aren't considered. It's also assumed that "text" doesn't contain '*'.
0732      */
0733     for (const auto &ext : m_type->wildcards) {
0734         // File extension
0735         if (ext.startsWith(QLatin1String("*."))) {
0736             if (text.length() == ext.length() - 2 && text.compare(QStringView(ext).mid(2), Qt::CaseInsensitive) == 0) {
0737                 return true;
0738             }
0739         } else if (text.length() != ext.length() || ext.endsWith(QLatin1Char('*'))) {
0740             continue;
0741             // Full name
0742         } else if (text.compare(ext, Qt::CaseInsensitive) == 0) {
0743             return true;
0744         }
0745     }
0746     return false;
0747 }
0748 
0749 void KateModeMenuListData::ListView::keyPressEvent(QKeyEvent *event)
0750 {
0751     // Ctrl/Alt/Shift/Meta + Return/Enter selects an item, but without hiding the menu
0752     if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)
0753         && (event->modifiers().testFlag(Qt::ControlModifier) || event->modifiers().testFlag(Qt::AltModifier) || event->modifiers().testFlag(Qt::ShiftModifier)
0754             || event->modifiers().testFlag(Qt::MetaModifier))) {
0755         m_parentMenu->selectHighlightingSetVisibility(m_parentMenu->m_list->currentItem(), false);
0756     }
0757     // Return/Enter selects an item and hide the menu
0758     else if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
0759         m_parentMenu->selectHighlightingSetVisibility(m_parentMenu->m_list->currentItem(), true);
0760     } else {
0761         QListView::keyPressEvent(event);
0762     }
0763 }
0764 
0765 void KateModeMenuListData::SearchLine::keyPressEvent(QKeyEvent *event)
0766 {
0767     if (m_parentMenu->m_list
0768         && (event->matches(QKeySequence::MoveToNextLine) || event->matches(QKeySequence::SelectNextLine) || event->matches(QKeySequence::MoveToPreviousLine)
0769             || event->matches(QKeySequence::SelectPreviousLine) || event->matches(QKeySequence::MoveToNextPage) || event->matches(QKeySequence::SelectNextPage)
0770             || event->matches(QKeySequence::MoveToPreviousPage) || event->matches(QKeySequence::SelectPreviousPage) || event->key() == Qt::Key_Return
0771             || event->key() == Qt::Key_Enter)) {
0772         QApplication::sendEvent(m_parentMenu->m_list, event);
0773     } else {
0774         QLineEdit::keyPressEvent(event);
0775     }
0776 }
0777 
0778 void KateModeMenuListData::SearchLine::init()
0779 {
0780     connect(this, &KateModeMenuListData::SearchLine::textChanged, this, &KateModeMenuListData::SearchLine::_k_queueSearch);
0781 
0782     setEnabled(true);
0783     setClearButtonEnabled(true);
0784 }
0785 
0786 void KateModeMenuListData::SearchLine::clear()
0787 {
0788     m_queuedSearches = 0;
0789     m_bSearchStateAutoScroll = (text().trimmed().isEmpty()) ? false : true;
0790     /*
0791      * NOTE: This calls "SearchLine::_k_queueSearch()" with an empty string.
0792      * The search clearing should be done without delays.
0793      */
0794     QLineEdit::clear();
0795 }
0796 
0797 void KateModeMenuListData::SearchLine::_k_queueSearch(const QString &s)
0798 {
0799     m_queuedSearches++;
0800     m_search = s;
0801 
0802     if (m_search.isEmpty()) {
0803         _k_activateSearch(); // Clear search without delay
0804     } else {
0805         QTimer::singleShot(m_searchDelay, this, &KateModeMenuListData::SearchLine::_k_activateSearch);
0806     }
0807 }
0808 
0809 void KateModeMenuListData::SearchLine::_k_activateSearch()
0810 {
0811     m_queuedSearches--;
0812 
0813     if (m_queuedSearches <= 0) {
0814         updateSearch(m_search);
0815         m_queuedSearches = 0;
0816     }
0817 }
0818 
0819 void KateModeMenuListData::SearchLine::updateSearch(const QString &s)
0820 {
0821     if (m_parentMenu->m_emptyListMsg) {
0822         m_parentMenu->m_emptyListMsg->hide();
0823     }
0824     if (m_parentMenu->m_scroll && m_parentMenu->m_scroll->isHidden()) {
0825         m_parentMenu->m_scroll->show();
0826     }
0827 
0828     KateModeMenuListData::ListView *listView = m_parentMenu->m_list;
0829     QStandardItemModel *listModel = m_parentMenu->m_model;
0830 
0831     const QString searchText = (s.isNull() ? text() : s).simplified();
0832 
0833     /*
0834      * Clean "Best Search Matches" section, move items to their original places.
0835      */
0836     if (!listView->isRowHidden(0)) {
0837         listView->setRowHidden(0, true);
0838     }
0839     if (!m_bestResults.isEmpty()) {
0840         const int sizeBestResults = m_bestResults.size();
0841         for (int i = 0; i < sizeBestResults; ++i) {
0842             listModel->takeRow(m_bestResults.at(i).first->index().row());
0843             listModel->insertRow(m_bestResults.at(i).second + sizeBestResults - i - 1, m_bestResults.at(i).first);
0844         }
0845         m_bestResults.clear();
0846     }
0847 
0848     /*
0849      * Empty search bar.
0850      * Show all items and scroll to the selected item or to the first item.
0851      */
0852     if (searchText.isEmpty() || (searchText.size() == 1 && searchText[0].isSpace())) {
0853         for (int i = 1; i < listModel->rowCount(); ++i) {
0854             if (listView->isRowHidden(i)) {
0855                 listView->setRowHidden(i, false);
0856             }
0857         }
0858 
0859         // Don't auto-scroll if the search is already clear
0860         if (m_bSearchStateAutoScroll) {
0861             m_parentMenu->autoScroll();
0862         }
0863         m_bSearchStateAutoScroll = false;
0864         return;
0865     }
0866 
0867     /*
0868      * Prepare item filter.
0869      */
0870     int lastItem = -1;
0871     int lastSection = -1;
0872     int firstSection = -1;
0873     bool bEmptySection = true;
0874     bool bSectionSeparator = false;
0875     bool bSectionName = false;
0876     bool bNotShowBestResults = false;
0877     bool bSearchExtensions = true;
0878     bool bExactMatch = false; // If the search name will not be used
0879     /*
0880      * It's used for two purposes, it's true if searchText is a
0881      * single alphanumeric character or if it starts with a point.
0882      * Both cases don't conflict, so a single bool is used.
0883      */
0884     bool bIsAlphaOrPointExt = false;
0885 
0886     /*
0887      * Don't search for extensions if the search text has only one character,
0888      * to avoid unwanted results. In this case, the items that start with
0889      * that character are displayed.
0890      */
0891     if (searchText.length() < 2) {
0892         bSearchExtensions = false;
0893         if (searchText[0].isLetterOrNumber()) {
0894             bIsAlphaOrPointExt = true;
0895         }
0896     }
0897     // If the search text has a point at the beginning, match extensions
0898     else if (searchText.length() > 1 && searchText[0].toLatin1() == 46) {
0899         bIsAlphaOrPointExt = true;
0900         bSearchExtensions = true;
0901         bExactMatch = true;
0902     }
0903     // Two characters: search using the normal name of the items
0904     else if (searchText.length() == 2) {
0905         bExactMatch = true;
0906         // if it contains the '*' character, don't match extensions
0907         if (searchText[1].toLatin1() == 42 || searchText[0].toLatin1() == 42) {
0908             bSearchExtensions = false;
0909         }
0910     }
0911     /*
0912      * Don't use the search name if the search text has delimiters.
0913      * Don't search in extensions if it contains the '*' character.
0914      */
0915     else {
0916         QString::const_iterator srcText = searchText.constBegin();
0917         QString::const_iterator endText = searchText.constEnd();
0918 
0919         for (int it = 0; it < searchText.length() / 2 + searchText.length() % 2; ++it) {
0920             --endText;
0921             const ushort ucsrc = srcText->unicode();
0922             const ushort ucend = endText->unicode();
0923 
0924             // If searchText contains "*"
0925             if (ucsrc == 42 || ucend == 42) {
0926                 bSearchExtensions = false;
0927                 bExactMatch = true;
0928                 break;
0929             }
0930             if (!bExactMatch && (isDelimiter(ucsrc) || (ucsrc != ucend && isDelimiter(ucend)))) {
0931                 bExactMatch = true;
0932             }
0933             ++srcText;
0934         }
0935     }
0936 
0937     /*
0938      * Filter items.
0939      */
0940     for (int i = 1; i < listModel->rowCount(); ++i) {
0941         QString itemName = listModel->item(i, 0)->text();
0942 
0943         /*
0944          * Hide/show the name of the section. If the text of the item
0945          * is empty, then it corresponds to the name of the section.
0946          */
0947         if (itemName.isEmpty()) {
0948             listView->setRowHidden(i, false);
0949 
0950             if (bSectionSeparator) {
0951                 bSectionName = true;
0952             } else {
0953                 bSectionSeparator = true;
0954             }
0955 
0956             /*
0957              * This hides the name of the previous section
0958              * (and the separator) if this section has no items.
0959              */
0960             if (bSectionName && bEmptySection && lastSection > 0) {
0961                 listView->setRowHidden(lastSection, true);
0962                 listView->setRowHidden(lastSection - 1, true);
0963             }
0964 
0965             // Find the section name
0966             if (bSectionName) {
0967                 bSectionName = false;
0968                 bSectionSeparator = false;
0969                 bEmptySection = true;
0970                 lastSection = i;
0971             }
0972             continue;
0973         }
0974 
0975         /*
0976          * Start filtering items.
0977          */
0978         KateModeMenuListData::ListItem *item = static_cast<KateModeMenuListData::ListItem *>(listModel->item(i, 0));
0979 
0980         if (!item->hasMode()) {
0981             listView->setRowHidden(i, true);
0982             continue;
0983         }
0984         if (item->getSearchName().isEmpty()) {
0985             item->generateSearchName(item->getMode()->translatedName.isEmpty() ? item->getMode()->name : item->getMode()->translatedName);
0986         }
0987 
0988         /*
0989          * Add item to the "Best Search Matches" section if there is an exact match in the search.
0990          * However, if the "exact match" is already the first search result, that section will not
0991          * be displayed, as it isn't necessary.
0992          */
0993         if (!bNotShowBestResults
0994             && (item->getSearchName().compare(searchText, m_caseSensitivity) == 0
0995                 || (bExactMatch && item->getMode()->nameTranslated().compare(searchText, m_caseSensitivity) == 0))) {
0996             if (lastItem == -1) {
0997                 bNotShowBestResults = true;
0998             } else {
0999                 m_bestResults.append(qMakePair(item, i));
1000                 continue;
1001             }
1002         }
1003 
1004         // Only a character is written in the search bar
1005         if (searchText.length() == 1) {
1006             if (bIsAlphaOrPointExt) {
1007                 /*
1008                  * Add item to the "Best Search Matches" section, if there is a single letter.
1009                  * Also look for coincidence in the raw name, some translations use delimiters
1010                  * instead of spaces and this can lead to inaccurate results.
1011                  */
1012                 bool bMatchCharDel = true;
1013                 if (item->getMode()->name.startsWith(searchText + QLatin1Char(' '), m_caseSensitivity)) {
1014                     if (QString(QLatin1Char(' ') + item->getSearchName() + QLatin1Char(' '))
1015                             .contains(QLatin1Char(' ') + searchText + QLatin1Char(' '), m_caseSensitivity)) {
1016                         m_bestResults.append(qMakePair(item, i));
1017                         continue;
1018                     } else {
1019                         bMatchCharDel = false;
1020                     }
1021                 }
1022 
1023                 // CASE 1: All the items that start with that character will be displayed.
1024                 if (item->getSearchName().startsWith(searchText, m_caseSensitivity)) {
1025                     setSearchResult(i, bEmptySection, lastSection, firstSection, lastItem);
1026                     continue;
1027                 }
1028 
1029                 // CASE 2: Matches considering delimiters. For example, when writing "c",
1030                 //         "Objective-C" will be displayed in the results, but not "Yacc/Bison".
1031                 if (bMatchCharDel
1032                     && QString(QLatin1Char(' ') + item->getSearchName() + QLatin1Char(' '))
1033                            .contains(QLatin1Char(' ') + searchText + QLatin1Char(' '), m_caseSensitivity)) {
1034                     setSearchResult(i, bEmptySection, lastSection, firstSection, lastItem);
1035                     continue;
1036                 }
1037             }
1038             // CASE 3: The character isn't a letter or number, do an exact search.
1039             else if (item->getMode()->nameTranslated().contains(searchText[0], m_caseSensitivity)) {
1040                 setSearchResult(i, bEmptySection, lastSection, firstSection, lastItem);
1041                 continue;
1042             }
1043         }
1044         // CASE 4: Search text, using the search name or the normal name.
1045         else if (!bExactMatch && item->getSearchName().contains(searchText, m_caseSensitivity)) {
1046             setSearchResult(i, bEmptySection, lastSection, firstSection, lastItem);
1047             continue;
1048         } else if (bExactMatch && item->getMode()->nameTranslated().contains(searchText, m_caseSensitivity)) {
1049             setSearchResult(i, bEmptySection, lastSection, firstSection, lastItem);
1050             continue;
1051         }
1052 
1053         // CASE 5: Exact matches in extensions.
1054         if (bSearchExtensions) {
1055             if (bIsAlphaOrPointExt && item->matchExtension(searchText.mid(1))) {
1056                 setSearchResult(i, bEmptySection, lastSection, firstSection, lastItem);
1057                 continue;
1058             } else if (item->matchExtension(searchText)) {
1059                 setSearchResult(i, bEmptySection, lastSection, firstSection, lastItem);
1060                 continue;
1061             }
1062         }
1063 
1064         // Item not found, hide
1065         listView->setRowHidden(i, true);
1066     }
1067 
1068     // Remove last section name, if it's empty.
1069     if (bEmptySection && lastSection > 0 && !listModel->item(listModel->rowCount() - 1, 0)->text().isEmpty()) {
1070         listView->setRowHidden(lastSection, true);
1071         listView->setRowHidden(lastSection - 1, true);
1072     }
1073 
1074     // Hide the separator line in the name of the first section.
1075     if (m_bestResults.isEmpty()) {
1076         listView->setRowHidden(0, true);
1077         if (firstSection > 0) {
1078             listView->setRowHidden(firstSection - 1, true);
1079         }
1080     } else {
1081         /*
1082          * Show "Best Search Matches" section, if there are items.
1083          */
1084 
1085         // Show title in singular or plural, depending on the number of items.
1086         QLabel *labelSection = static_cast<QLabel *>(listView->indexWidget(listModel->index(0, 0)));
1087         if (m_bestResults.size() == 1) {
1088             labelSection->setText(
1089                 i18nc("Title (in singular) of the best result in an item search. Please, that the translation doesn't have more than 34 characters, since the "
1090                       "menu where it's displayed is small and fixed.",
1091                       "Best Search Match"));
1092         } else {
1093             labelSection->setText(
1094                 i18nc("Title (in plural) of the best results in an item search. Please, that the translation doesn't have more than 34 characters, since the "
1095                       "menu where it's displayed is small and fixed.",
1096                       "Best Search Matches"));
1097         }
1098 
1099         int heightSectionMargin = m_parentMenu->m_defaultHeightItemSection - labelSection->sizeHint().height();
1100         if (heightSectionMargin < 2) {
1101             heightSectionMargin = 2;
1102         }
1103         int maxWidthText = listView->getContentWidth(1, 3);
1104         // NOTE: labelSection->sizeHint().width() == labelSection->indent() + labelSection->fontMetrics().horizontalAdvance(labelSection->text())
1105         const bool bSectionMultiline = labelSection->sizeHint().width() > maxWidthText;
1106         maxWidthText -= labelSection->indent();
1107         if (!bSectionMultiline) {
1108             listModel->item(0, 0)->setSizeHint(QSize(listModel->item(0, 0)->sizeHint().width(), labelSection->sizeHint().height() + heightSectionMargin));
1109             listView->setRowHidden(0, false);
1110         }
1111 
1112         /*
1113          * Show items in "Best Search Matches" section.
1114          */
1115         int rowModelBestResults = 0; // New position in the model
1116 
1117         // Special Case: always show the "R Script" mode first by typing "r" in the search box
1118         if (searchText.length() == 1 && searchText.compare(QLatin1String("r"), m_caseSensitivity) == 0) {
1119             for (const QPair<ListItem *, int> &itemBestResults : std::as_const(m_bestResults)) {
1120                 listModel->takeRow(itemBestResults.second);
1121                 ++rowModelBestResults;
1122                 if (itemBestResults.first->getMode()->name == QLatin1String("R Script")) {
1123                     listModel->insertRow(1, itemBestResults.first);
1124                     listView->setRowHidden(1, false);
1125                 } else {
1126                     listModel->insertRow(rowModelBestResults, itemBestResults.first);
1127                     listView->setRowHidden(rowModelBestResults, false);
1128                 }
1129             }
1130         } else {
1131             // Move items to the "Best Search Matches" section
1132             for (const QPair<ListItem *, int> &itemBestResults : std::as_const(m_bestResults)) {
1133                 listModel->takeRow(itemBestResults.second);
1134                 listModel->insertRow(++rowModelBestResults, itemBestResults.first);
1135                 listView->setRowHidden(rowModelBestResults, false);
1136             }
1137         }
1138         if (lastItem == -1) {
1139             lastItem = rowModelBestResults;
1140         }
1141 
1142         // Add word wrap in long section titles.
1143         if (bSectionMultiline) {
1144             if (listView->visualRect(listModel->index(lastItem, 0)).bottom() + labelSection->sizeHint().height() + heightSectionMargin
1145                     > listView->geometry().height()
1146                 || labelSection->sizeHint().width() > listView->getWidth() - 1) {
1147                 labelSection->setText(m_parentMenu->setWordWrap(labelSection->text(), maxWidthText, labelSection->fontMetrics()));
1148             }
1149             listModel->item(0, 0)->setSizeHint(QSize(listModel->item(0, 0)->sizeHint().width(), labelSection->sizeHint().height() + heightSectionMargin));
1150             listView->setRowHidden(0, false);
1151         }
1152 
1153         m_parentMenu->m_list->setCurrentItem(1);
1154     }
1155 
1156     listView->scrollToTop();
1157 
1158     // Show message of empty list
1159     if (lastItem == -1) {
1160         if (m_parentMenu->m_emptyListMsg == nullptr) {
1161             m_parentMenu->loadEmptyMsg();
1162         }
1163         if (m_parentMenu->m_scroll) {
1164             m_parentMenu->m_scroll->hide();
1165         }
1166         m_parentMenu->m_emptyListMsg->show();
1167     }
1168     // Hide scroll bar if it isn't necessary
1169     else if (m_parentMenu->m_scroll && listView->visualRect(listModel->index(lastItem, 0)).bottom() <= listView->geometry().height()) {
1170         m_parentMenu->m_scroll->hide();
1171     }
1172 
1173     m_bSearchStateAutoScroll = true;
1174 }
1175 
1176 void KateModeMenuListData::SearchLine::setSearchResult(const int rowItem, bool &bEmptySection, int &lastSection, int &firstSection, int &lastItem)
1177 {
1178     if (lastItem == -1) {
1179         /*
1180          * Detect the first result of the search and "select" it.
1181          * This allows you to scroll through the list using
1182          * the Up/Down keys after entering a search.
1183          */
1184         m_parentMenu->m_list->setCurrentItem(rowItem);
1185 
1186         // Position of the first section visible.
1187         if (lastSection > 0) {
1188             firstSection = lastSection;
1189         }
1190     }
1191     if (bEmptySection) {
1192         bEmptySection = false;
1193     }
1194 
1195     lastItem = rowItem;
1196     if (m_parentMenu->m_list->isRowHidden(rowItem)) {
1197         m_parentMenu->m_list->setRowHidden(rowItem, false);
1198     }
1199 }
1200 
1201 void KateModeMenuList::updateMenu(KTextEditor::Document *doc)
1202 {
1203     m_doc = static_cast<KTextEditor::DocumentPrivate *>(doc);
1204 }