File indexing completed on 2024-04-21 03:57:20

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 }