1/*
2 SPDX-FileCopyrightText: 2024 Waqar Ahmed <waqar.17a@gmail.com>
3 SPDX-License-Identifier: LGPL-2.0-or-later
4*/
5
6#include "kateargumenthinttree.h"
7
8#include "kateargumenthintmodel.h"
9#include "katecompletionwidget.h"
10
11#include <QHBoxLayout>
12#include <QLabel>
13#include <QModelIndex>
14#include <QPlainTextEdit>
15#include <QSyntaxHighlighter>
16#include <QTextBlock>
17#include <QToolButton>
18
19class ArgumentHighlighter : public QSyntaxHighlighter
20{
21public:
22 ArgumentHighlighter(QTextDocument *doc)
23 : QSyntaxHighlighter(doc)
24 {
25 }
26
27 void highlightBlock(const QString &) override
28 {
29 for (const auto &f : std::as_const(t&: formats)) {
30 QTextCharFormat fmt = f.format;
31 if (fmt.fontWeight() == QFont::Bold || fmt.fontItalic()) {
32 // bold doesn't work with some fonts for whatever reason
33 // so we just underline as well for fonts where bold won't work
34 fmt.setFontUnderline(true);
35 }
36 setFormat(start: f.start, count: f.length, format: fmt);
37 }
38 }
39
40 QVector<QTextLayout::FormatRange> formats;
41};
42
43static QList<QTextLayout::FormatRange> highlightingFromVariantList(const QList<QVariant> &customHighlights)
44{
45 QList<QTextLayout::FormatRange> ret;
46
47 for (int i = 0; i + 2 < customHighlights.count(); i += 3) {
48 if (!customHighlights[i].canConvert<int>() || !customHighlights[i + 1].canConvert<int>() || !customHighlights[i + 2].canConvert<QTextFormat>()) {
49 continue;
50 }
51
52 QTextLayout::FormatRange format;
53 format.start = customHighlights[i].toInt();
54 format.length = customHighlights[i + 1].toInt();
55 format.format = customHighlights[i + 2].value<QTextFormat>().toCharFormat();
56
57 if (!format.format.isValid()) {
58 qWarning() << "Format is not valid";
59 continue;
60 }
61
62 ret << format;
63 }
64 return ret;
65}
66
67ArgumentHintWidget::ArgumentHintWidget(KateArgumentHintModel *model, const QFont &font, KateCompletionWidget *completion, QWidget *parent)
68 : QFrame(parent)
69 , m_completionWidget(completion)
70 , m_view(new QPlainTextEdit(this))
71 , m_currentIndicator(new QLabel(this))
72 , m_model(model)
73 , m_highlighter(new ArgumentHighlighter(m_view->document()))
74 , m_leftSide(new QWidget(this))
75{
76 setAutoFillBackground(true);
77 // we have only 1 top level frame
78 setFrameStyle(QFrame::Box | QFrame::Raised);
79 m_view->setFrameStyle(QFrame::NoFrame);
80
81 auto upButton = new QToolButton(this);
82 upButton->setAutoRaise(true);
83 upButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up")));
84 connect(sender: upButton, signal: &QAbstractButton::clicked, context: this, slot: &ArgumentHintWidget::selectPrevious);
85
86 auto downButton = new QToolButton(this);
87 downButton->setAutoRaise(true);
88 downButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down")));
89 connect(sender: downButton, signal: &QAbstractButton::clicked, context: this, slot: &ArgumentHintWidget::selectNext);
90
91 auto vLayout = new QVBoxLayout(m_leftSide);
92 vLayout->setContentsMargins({});
93 vLayout->setAlignment(Qt::AlignCenter);
94 vLayout->addWidget(upButton);
95 vLayout->addWidget(m_currentIndicator);
96 vLayout->addWidget(downButton);
97
98 auto layout = new QHBoxLayout(this);
99 layout->setContentsMargins({});
100 layout->setSpacing(0);
101 layout->addWidget(m_leftSide);
102 layout->addWidget(m_view);
103 setFixedWidth(380);
104 m_view->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
105 m_view->document()->setDefaultFont(font);
106
107 connect(sender: m_model, signal: &QAbstractItemModel::modelReset, context: this, slot: [this]() {
108 m_current = -1;
109 selectNext();
110 });
111 setVisible(false);
112}
113
114void ArgumentHintWidget::selectNext()
115{
116 int rowCount = m_model->rowCount();
117 if (rowCount == 0) {
118 clearAndHide();
119 return;
120 }
121 m_current = m_current + 1;
122 if (m_current >= rowCount) {
123 m_current = 0;
124 }
125
126 activateHint(i: m_current, rowCount);
127}
128
129void ArgumentHintWidget::selectPrevious()
130{
131 int rowCount = m_model->rowCount();
132 if (rowCount == 0) {
133 clearAndHide();
134 return;
135 }
136
137 m_current = m_current - 1;
138 if (m_current <= 0) {
139 m_current = rowCount - 1;
140 }
141
142 activateHint(i: m_current, rowCount);
143}
144
145void ArgumentHintWidget::activateHint(int i, int rowCount)
146{
147 const auto index = m_model->index(row: i);
148 const auto list = index.data(arole: KTextEditor::CodeCompletionModel::CustomHighlight).toList();
149 const auto highlights = highlightingFromVariantList(customHighlights: list);
150 m_highlighter->formats = highlights;
151
152 if (rowCount == 1) {
153 m_leftSide->setVisible(false);
154 } else {
155 if (m_leftSide->isHidden()) {
156 m_leftSide->setVisible(true);
157 }
158 m_currentIndicator->setText(QStringLiteral("%1/%2").arg(a: i + 1).arg(a: rowCount));
159 }
160
161 m_view->setPlainText(index.data().toString());
162 updateGeometry();
163}
164
165void ArgumentHintWidget::updateGeometry()
166{
167 auto block = m_view->document()->begin();
168 int lines = 0;
169 QFontMetrics fm(m_view->document()->defaultFont());
170 int maxWidth = 0;
171 while (block.isValid()) {
172 maxWidth = std::max(a: (int)block.layout()->maximumWidth(), b: maxWidth);
173 lines += block.layout()->lineCount();
174 block = block.next();
175 }
176 setFixedHeight((std::max(a: lines,b: 1) * fm.height()) + 10 + m_view->document()->documentMargin());
177 // limit the width to between 400 - 600
178 int width = std::max(a: maxWidth, b: 400);
179 width = std::min(a: width, b: 600);
180 setFixedWidth(width);
181
182 QPoint pos = m_completionWidget->pos();
183 pos.ry() -= this->height();
184 pos.ry() -= fm.height();
185 pos.ry() -= 4;
186 move(pos);
187}
188
189void ArgumentHintWidget::positionAndShow()
190{
191 updateGeometry();
192 show();
193}
194
195void ArgumentHintWidget::clearAndHide()
196{
197 m_current = -1;
198 m_currentIndicator->clear();
199 m_view->clear();
200 hide();
201}
202

source code of ktexteditor/src/completion/kateargumenthinttree.cpp