1 /*
2 SPDX-FileCopyrightText: 2009 David Nolden <david.nolden.kdevelop@art-master.de>
3
4 SPDX-License-Identifier: LGPL-2.0-only
5 */
6
7 #include "problemnavigationcontext.h"
8
9 #include <debug.h>
10
11 #include <QBuffer>
12 #include <QStyle>
13 #include <QApplication>
14
15 #include <KLocalizedString>
16
17 #include <language/duchain/declaration.h>
18 #include <language/duchain/duchainlock.h>
19 #include <language/duchain/duchainutils.h>
20 #include <language/duchain/problem.h>
21
22 #include <language/duchain/duchain.h>
23 #include <language/editor/documentrange.h>
24
25 using namespace KDevelop;
26
27 namespace {
KEY_INVOKE_ACTION(int num)28 QString KEY_INVOKE_ACTION(int num) { return QStringLiteral("invoke_action_%1").arg(num); }
29
htmlImg(const QIcon & icon,QStyle::PixelMetric metric)30 QString htmlImg(const QIcon& icon, QStyle::PixelMetric metric)
31 {
32 const int size = qApp->style()->pixelMetric(metric, nullptr, nullptr);
33 const QPixmap pixmap = icon.pixmap(size, size);
34 QByteArray pngBytes;
35 QBuffer buffer(&pngBytes);
36 buffer.open(QIODevice::WriteOnly);
37 pixmap.save(&buffer, "PNG", 100);
38
39 const QString imgTag = QStringLiteral("<img width='%1' height='%1' src='data:image/png;base64, %2'/>")
40 .arg(size)
41 .arg(QString::fromLatin1(pngBytes.toBase64()));
42 return imgTag;
43 }
44 }
45
ProblemNavigationContext(const QVector<IProblem::Ptr> & problems,const Flags flags)46 ProblemNavigationContext::ProblemNavigationContext(const QVector<IProblem::Ptr>& problems, const Flags flags)
47 : m_problems(problems)
48 , m_flags(flags)
49 , m_widget(nullptr)
50 {
51 // Sort problems vector:
52 // 1) By severity
53 // 2) By sourceString, if severities are equals
54 std::sort(m_problems.begin(), m_problems.end(), [](const IProblem::Ptr& a, const IProblem::Ptr& b) {
55 if (a->severity() < b->severity())
56 return true;
57
58 if (a->severity() > b->severity())
59 return false;
60
61 if (a->sourceString() < b->sourceString())
62 return true;
63
64 return false;
65 });
66 }
67
~ProblemNavigationContext()68 ProblemNavigationContext::~ProblemNavigationContext()
69 {
70 delete m_widget;
71 }
72
widget() const73 QWidget* ProblemNavigationContext::widget() const
74 {
75 return m_widget;
76 }
77
isWidgetMaximized() const78 bool ProblemNavigationContext::isWidgetMaximized() const
79 {
80 return false;
81 }
82
name() const83 QString ProblemNavigationContext::name() const
84 {
85 return i18n("Problem");
86 }
87
escapedHtml(const QString & text) const88 QString ProblemNavigationContext::escapedHtml(const QString& text) const
89 {
90 const QString htmlStart = QStringLiteral("<html>");
91 const QString htmlEnd = QStringLiteral("</html>");
92
93 QString result = text.trimmed();
94
95 if (!result.startsWith(htmlStart))
96 return result.toHtmlEscaped();
97
98 result.remove(htmlStart);
99 result.remove(htmlEnd);
100
101 return result;
102 }
103
html(IProblem::Ptr problem)104 void ProblemNavigationContext::html(IProblem::Ptr problem)
105 {
106 modifyHtml() += QStringLiteral("<table><tr>");
107
108 modifyHtml() += QStringLiteral("<td valign=\"middle\">%1</td>")
109 .arg(htmlImg(IProblem::iconForSeverity(problem->severity()), QStyle::PM_LargeIconSize));
110
111 // BEGIN: right column
112 modifyHtml() += QStringLiteral("<td>");
113
114 modifyHtml() += i18n("Problem in <i>%1</i>", problem->sourceString());
115 modifyHtml() += QStringLiteral("<br/>");
116
117 if (m_flags & ShowLocation) {
118 modifyHtml() += labelHighlight(i18n("Location: "));
119 makeLink(QStringLiteral("%1 :%2")
120 .arg(problem->finalLocation().document.toUrl().fileName())
121 .arg(problem->finalLocation().start().line() + 1),
122 QString(),
123 NavigationAction(problem->finalLocation().document.toUrl(), problem->finalLocation().start())
124 );
125
126 modifyHtml() += QStringLiteral("<br/>");
127 }
128
129 QString description = escapedHtml(problem->description());
130 QString explanation = escapedHtml(problem->explanation());
131
132 modifyHtml() += description;
133
134 // Add only non-empty explanation which differs from the problem description.
135 // Skip this if we have more than one problem.
136 if (m_problems.size() == 1 && !explanation.isEmpty() && explanation != description)
137 modifyHtml() += QLatin1String("<p><i style=\"white-space:pre-wrap\">") + explanation +
138 QLatin1String("</i></p>");
139
140 modifyHtml() += QStringLiteral("</td>");
141 // END: right column
142
143 modifyHtml() += QStringLiteral("</tr></table>");
144
145 const auto diagnostics = problem->diagnostics();
146 if (!diagnostics.isEmpty()) {
147 DUChainReadLocker lock;
148 for (auto diagnostic : diagnostics) {
149 modifyHtml() += QStringLiteral("<p>");
150 modifyHtml() += labelHighlight(QStringLiteral("%1: ").arg(diagnostic->severityString()));
151 modifyHtml() += escapedHtml(diagnostic->description());
152
153 const DocumentRange range = diagnostic->finalLocation();
154 Declaration* declaration = DUChainUtils::itemUnderCursor(range.document.toUrl(), range.start()).declaration;
155 if (declaration) {
156 modifyHtml() += i18n("<br>See: ");
157 makeLink(declaration->toString(), DeclarationPointer(declaration),
158 NavigationAction::NavigateDeclaration);
159 modifyHtml() += i18n(" in ");
160 const auto url = declaration->url().toUrl();
161 makeLink(QStringLiteral("%1 :%2")
162 .arg(url.fileName())
163 .arg(declaration->rangeInCurrentRevision().start().line() + 1),
164 url.toDisplayString(QUrl::PreferLocalFile), NavigationAction(url, declaration->rangeInCurrentRevision().start()));
165 } else if (range.start().isValid()) {
166 modifyHtml() += i18n("<br>See: ");
167 const auto url = range.document.toUrl();
168 makeLink(QStringLiteral("%1 :%2")
169 .arg(url.fileName())
170 .arg(range.start().line() + 1),
171 url.toDisplayString(QUrl::PreferLocalFile), NavigationAction(url, range.start()));
172 }
173
174 modifyHtml() += QStringLiteral("</p>");
175 }
176 }
177
178 auto assistant = problem->solutionAssistant();
179 if (assistant && !assistant->actions().isEmpty()) {
180 modifyHtml() +=
181 QStringLiteral("<table width='100%' style='border: 1px solid black; background-color: %1;'>").arg(QStringLiteral(
182 "#b3d4ff"));
183 modifyHtml() +=
184 QStringLiteral("<tr><td valign='middle'>%1</td><td width='100%'>")
185 .arg(htmlImg(QIcon::fromTheme(QStringLiteral("dialog-ok-apply")), QStyle::PM_LargeIconSize));
186
187 const int startIndex = m_assistantsActions.size();
188 int currentIndex = startIndex;
189 const auto assistantActions = assistant->actions();
190 for (auto& assistantAction : assistantActions) {
191 m_assistantsActions.append(assistantAction);
192
193 if (currentIndex != startIndex)
194 modifyHtml() += QStringLiteral("<br/>");
195
196 makeLink(i18n("Solution (%1)", currentIndex + 1), KEY_INVOKE_ACTION(currentIndex),
197 NavigationAction(KEY_INVOKE_ACTION(currentIndex)));
198 modifyHtml() += QLatin1String(": ") + assistantAction->description().toHtmlEscaped();
199
200 ++currentIndex;
201 }
202
203 modifyHtml() += QStringLiteral("</td></tr>");
204 modifyHtml() += QStringLiteral("</table>");
205 }
206 }
207
html(bool shorten)208 QString ProblemNavigationContext::html(bool shorten)
209 {
210 AbstractNavigationContext::html(shorten);
211
212 clear();
213 m_assistantsActions.clear();
214
215 int problemIndex = 0;
216 for (auto& problem : qAsConst(m_problems)) {
217 html(problem);
218
219 if (++problemIndex != m_problems.size())
220 modifyHtml() += QStringLiteral("<hr>");
221 }
222
223 return currentHtml();
224 }
225
executeKeyAction(const QString & key)226 NavigationContextPointer ProblemNavigationContext::executeKeyAction(const QString& key)
227 {
228 const QLatin1String invokeActionPrefix("invoke_action_");
229 if (key.startsWith(invokeActionPrefix)) {
230 const int index = key.midRef(invokeActionPrefix.size()).toInt();
231 executeAction(index);
232 }
233
234 return {};
235 }
236
executeAction(int index)237 void ProblemNavigationContext::executeAction(int index)
238 {
239 if (index < 0 || index >= m_assistantsActions.size())
240 return;
241
242 auto action = m_assistantsActions.at(index);
243 Q_ASSERT(action);
244
245 if (action) {
246 action->execute();
247 if (topContext())
248 DUChain::self()->updateContextForUrl(topContext()->url(), TopDUContext::ForceUpdate);
249 } else {
250 qCWarning(LANGUAGE()) << "No such action";
251 return;
252 }
253 }
254