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 | |
19 | class ArgumentHighlighter : public QSyntaxHighlighter |
20 | { |
21 | public: |
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 | |
43 | static 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 | |
67 | ArgumentHintWidget::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 | |
114 | void 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 | |
129 | void 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 | |
145 | void 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 | |
165 | void 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 | |
189 | void ArgumentHintWidget::positionAndShow() |
190 | { |
191 | updateGeometry(); |
192 | show(); |
193 | } |
194 | |
195 | void ArgumentHintWidget::clearAndHide() |
196 | { |
197 | m_current = -1; |
198 | m_currentIndicator->clear(); |
199 | m_view->clear(); |
200 | hide(); |
201 | } |
202 | |