File indexing completed on 2024-05-05 07:59:17
0001 /* 0002 SPDX-FileCopyrightText: 2024 Waqar Ahmed <waqar.17a@gmail.com> 0003 SPDX-License-Identifier: LGPL-2.0-or-later 0004 */ 0005 0006 #include "kateargumenthinttree.h" 0007 0008 #include "kateargumenthintmodel.h" 0009 #include "katecompletionwidget.h" 0010 0011 #include <QHBoxLayout> 0012 #include <QLabel> 0013 #include <QModelIndex> 0014 #include <QPlainTextEdit> 0015 #include <QSyntaxHighlighter> 0016 #include <QTextBlock> 0017 #include <QToolButton> 0018 0019 class ArgumentHighlighter : public QSyntaxHighlighter 0020 { 0021 public: 0022 ArgumentHighlighter(QTextDocument *doc) 0023 : QSyntaxHighlighter(doc) 0024 { 0025 } 0026 0027 void highlightBlock(const QString &) override 0028 { 0029 for (const auto &f : std::as_const(formats)) { 0030 QTextCharFormat fmt = f.format; 0031 if (fmt.fontWeight() == QFont::Bold || fmt.fontItalic()) { 0032 // bold doesn't work with some fonts for whatever reason 0033 // so we just underline as well for fonts where bold won't work 0034 fmt.setFontUnderline(true); 0035 } 0036 setFormat(f.start, f.length, fmt); 0037 } 0038 } 0039 0040 QVector<QTextLayout::FormatRange> formats; 0041 }; 0042 0043 static QList<QTextLayout::FormatRange> highlightingFromVariantList(const QList<QVariant> &customHighlights) 0044 { 0045 QList<QTextLayout::FormatRange> ret; 0046 0047 for (int i = 0; i + 2 < customHighlights.count(); i += 3) { 0048 if (!customHighlights[i].canConvert<int>() || !customHighlights[i + 1].canConvert<int>() || !customHighlights[i + 2].canConvert<QTextFormat>()) { 0049 continue; 0050 } 0051 0052 QTextLayout::FormatRange format; 0053 format.start = customHighlights[i].toInt(); 0054 format.length = customHighlights[i + 1].toInt(); 0055 format.format = customHighlights[i + 2].value<QTextFormat>().toCharFormat(); 0056 0057 if (!format.format.isValid()) { 0058 qWarning() << "Format is not valid"; 0059 continue; 0060 } 0061 0062 ret << format; 0063 } 0064 return ret; 0065 } 0066 0067 ArgumentHintWidget::ArgumentHintWidget(KateArgumentHintModel *model, const QFont &font, KateCompletionWidget *completion, QWidget *parent) 0068 : QFrame(parent) 0069 , m_completionWidget(completion) 0070 , m_view(new QPlainTextEdit(this)) 0071 , m_currentIndicator(new QLabel(this)) 0072 , m_model(model) 0073 , m_highlighter(new ArgumentHighlighter(m_view->document())) 0074 , m_leftSide(new QWidget(this)) 0075 { 0076 setAutoFillBackground(true); 0077 // we have only 1 top level frame 0078 setFrameStyle(QFrame::Box | QFrame::Raised); 0079 m_view->setFrameStyle(QFrame::NoFrame); 0080 0081 auto upButton = new QToolButton(this); 0082 upButton->setAutoRaise(true); 0083 upButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up"))); 0084 connect(upButton, &QAbstractButton::clicked, this, &ArgumentHintWidget::selectPrevious); 0085 0086 auto downButton = new QToolButton(this); 0087 downButton->setAutoRaise(true); 0088 downButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down"))); 0089 connect(downButton, &QAbstractButton::clicked, this, &ArgumentHintWidget::selectNext); 0090 0091 auto vLayout = new QVBoxLayout(m_leftSide); 0092 vLayout->setContentsMargins({}); 0093 vLayout->setAlignment(Qt::AlignCenter); 0094 vLayout->addWidget(upButton); 0095 vLayout->addWidget(m_currentIndicator); 0096 vLayout->addWidget(downButton); 0097 0098 auto layout = new QHBoxLayout(this); 0099 layout->setContentsMargins({}); 0100 layout->setSpacing(0); 0101 layout->addWidget(m_leftSide); 0102 layout->addWidget(m_view); 0103 setFixedWidth(380); 0104 m_view->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); 0105 m_view->document()->setDefaultFont(font); 0106 0107 connect(m_model, &QAbstractItemModel::modelReset, this, [this]() { 0108 m_current = -1; 0109 selectNext(); 0110 }); 0111 setVisible(false); 0112 } 0113 0114 void ArgumentHintWidget::selectNext() 0115 { 0116 int rowCount = m_model->rowCount(); 0117 if (rowCount == 0) { 0118 clearAndHide(); 0119 return; 0120 } 0121 m_current = m_current + 1; 0122 if (m_current >= rowCount) { 0123 m_current = 0; 0124 } 0125 0126 activateHint(m_current, rowCount); 0127 } 0128 0129 void ArgumentHintWidget::selectPrevious() 0130 { 0131 int rowCount = m_model->rowCount(); 0132 if (rowCount == 0) { 0133 clearAndHide(); 0134 return; 0135 } 0136 0137 m_current = m_current - 1; 0138 if (m_current <= 0) { 0139 m_current = rowCount - 1; 0140 } 0141 0142 activateHint(m_current, rowCount); 0143 } 0144 0145 void ArgumentHintWidget::activateHint(int i, int rowCount) 0146 { 0147 const auto index = m_model->index(i); 0148 const auto list = index.data(KTextEditor::CodeCompletionModel::CustomHighlight).toList(); 0149 const auto highlights = highlightingFromVariantList(list); 0150 m_highlighter->formats = highlights; 0151 0152 if (rowCount == 1) { 0153 m_leftSide->setVisible(false); 0154 } else { 0155 if (m_leftSide->isHidden()) { 0156 m_leftSide->setVisible(true); 0157 } 0158 m_currentIndicator->setText(QStringLiteral("%1/%2").arg(i + 1).arg(rowCount)); 0159 } 0160 0161 m_view->setPlainText(index.data().toString()); 0162 updateGeometry(); 0163 } 0164 0165 void ArgumentHintWidget::updateGeometry() 0166 { 0167 int lines = 1; 0168 auto block = m_view->document()->begin(); 0169 QFontMetrics fm(m_view->document()->defaultFont()); 0170 int maxWidth = 0; 0171 while (block.isValid()) { 0172 maxWidth = std::max((int)block.layout()->maximumWidth(), maxWidth); 0173 lines += block.layout()->lineCount(); 0174 block = block.next(); 0175 } 0176 setFixedHeight((lines * fm.height()) + 10 + m_view->document()->documentMargin()); 0177 // limit the width to between 400 - 600 0178 int width = std::max(maxWidth, 400); 0179 width = std::min(width, 600); 0180 setFixedWidth(width); 0181 0182 QPoint pos = m_completionWidget->pos(); 0183 pos.ry() -= this->height(); 0184 pos.ry() -= 4; 0185 move(pos); 0186 } 0187 0188 void ArgumentHintWidget::positionAndShow() 0189 { 0190 updateGeometry(); 0191 show(); 0192 } 0193 0194 void ArgumentHintWidget::clearAndHide() 0195 { 0196 m_current = -1; 0197 m_currentIndicator->clear(); 0198 m_view->clear(); 0199 hide(); 0200 }