1 /*
2 SPDX-FileCopyrightText: 2019 Mark Nauwelaerts <mark.nauwelaerts@gmail.com>
3
4 SPDX-License-Identifier: MIT
5 */
6
7 #include "lspclientconfigpage.h"
8 #include "lspclientplugin.h"
9 #include "ui_lspconfigwidget.h"
10
11 #include <KLocalizedString>
12
13 #include <KSyntaxHighlighting/Definition>
14 #include <KSyntaxHighlighting/Repository>
15 #include <KSyntaxHighlighting/SyntaxHighlighter>
16 #include <KSyntaxHighlighting/Theme>
17
18 #include <KTextEditor/Editor>
19
20 #include <QJsonDocument>
21 #include <QJsonParseError>
22 #include <QPalette>
23
LSPClientConfigPage(QWidget * parent,LSPClientPlugin * plugin)24 LSPClientConfigPage::LSPClientConfigPage(QWidget *parent, LSPClientPlugin *plugin)
25 : KTextEditor::ConfigPage(parent)
26 , m_plugin(plugin)
27 {
28 ui = new Ui::LspConfigWidget();
29 ui->setupUi(this);
30
31 // fix-up our two text edits to be proper JSON file editors
32 updateHighlighters();
33
34 // ensure we update the highlighters if the repository is updated or theme is changed
35 connect(KTextEditor::Editor::instance(), &KTextEditor::Editor::repositoryReloaded, this, &LSPClientConfigPage::updateHighlighters);
36 connect(KTextEditor::Editor::instance(), &KTextEditor::Editor::configChanged, this, &LSPClientConfigPage::updateHighlighters);
37
38 // setup default json settings
39 QFile defaultConfigFile(QStringLiteral(":/lspclient/settings.json"));
40 defaultConfigFile.open(QIODevice::ReadOnly);
41 Q_ASSERT(defaultConfigFile.isOpen());
42 ui->defaultConfig->setPlainText(QString::fromUtf8(defaultConfigFile.readAll()));
43
44 // setup default config path as placeholder to show user where it is
45 ui->edtConfigPath->setPlaceholderText(m_plugin->m_defaultConfigPath.toLocalFile());
46
47 reset();
48
49 for (const auto &cb : {ui->chkSymbolDetails,
50 ui->chkSymbolExpand,
51 ui->chkSymbolSort,
52 ui->chkSymbolTree,
53 ui->chkComplDoc,
54 ui->chkRefDeclaration,
55 ui->chkComplParens,
56 ui->chkDiagnostics,
57 ui->chkDiagnosticsMark,
58 ui->chkDiagnosticsHover,
59 ui->chkMessages,
60 ui->chkOnTypeFormatting,
61 ui->chkIncrementalSync,
62 ui->chkHighlightGoto,
63 ui->chkSemanticHighlighting,
64 ui->chkAutoHover,
65 ui->chkSignatureHelp}) {
66 connect(cb, &QCheckBox::toggled, this, &LSPClientConfigPage::changed);
67 }
68 auto ch = [this](int) {
69 this->changed();
70 };
71 connect(ui->spinDiagnosticsSize, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, ch);
72 connect(ui->edtConfigPath, &KUrlRequester::textChanged, this, &LSPClientConfigPage::configUrlChanged);
73 connect(ui->edtConfigPath, &KUrlRequester::urlSelected, this, &LSPClientConfigPage::configUrlChanged);
74
75 auto cfgh = [this](int position, int added, int removed) {
76 Q_UNUSED(position);
77 // discard format change
78 // (e.g. due to syntax highlighting)
79 if (added || removed) {
80 configTextChanged();
81 }
82 };
83 connect(ui->userConfig->document(), &QTextDocument::contentsChange, this, cfgh);
84
85 // custom control logic
86 auto h = [this]() {
87 bool enabled = ui->chkDiagnostics->isChecked();
88 ui->chkDiagnosticsHighlight->setEnabled(enabled);
89 ui->chkDiagnosticsMark->setEnabled(enabled);
90 ui->chkDiagnosticsHover->setEnabled(enabled);
91 enabled = enabled && ui->chkDiagnosticsHover->isChecked();
92 ui->spinDiagnosticsSize->setEnabled(enabled);
93 enabled = ui->chkMessages->isChecked();
94 };
95 connect(this, &LSPClientConfigPage::changed, this, h);
96 }
97
~LSPClientConfigPage()98 LSPClientConfigPage::~LSPClientConfigPage()
99 {
100 delete ui;
101 }
102
name() const103 QString LSPClientConfigPage::name() const
104 {
105 return QString(i18n("LSP Client"));
106 }
107
fullName() const108 QString LSPClientConfigPage::fullName() const
109 {
110 return QString(i18n("LSP Client"));
111 }
112
icon() const113 QIcon LSPClientConfigPage::icon() const
114 {
115 return QIcon::fromTheme(QLatin1String("code-context"));
116 }
117
apply()118 void LSPClientConfigPage::apply()
119 {
120 m_plugin->m_symbolDetails = ui->chkSymbolDetails->isChecked();
121 m_plugin->m_symbolTree = ui->chkSymbolTree->isChecked();
122 m_plugin->m_symbolExpand = ui->chkSymbolExpand->isChecked();
123 m_plugin->m_symbolSort = ui->chkSymbolSort->isChecked();
124
125 m_plugin->m_complDoc = ui->chkComplDoc->isChecked();
126 m_plugin->m_refDeclaration = ui->chkRefDeclaration->isChecked();
127 m_plugin->m_complParens = ui->chkComplParens->isChecked();
128
129 m_plugin->m_diagnostics = ui->chkDiagnostics->isChecked();
130 m_plugin->m_diagnosticsHighlight = ui->chkDiagnosticsHighlight->isChecked();
131 m_plugin->m_diagnosticsMark = ui->chkDiagnosticsMark->isChecked();
132 m_plugin->m_diagnosticsHover = ui->chkDiagnosticsHover->isChecked();
133 m_plugin->m_diagnosticsSize = ui->spinDiagnosticsSize->value();
134
135 m_plugin->m_autoHover = ui->chkAutoHover->isChecked();
136 m_plugin->m_onTypeFormatting = ui->chkOnTypeFormatting->isChecked();
137 m_plugin->m_incrementalSync = ui->chkIncrementalSync->isChecked();
138 m_plugin->m_highlightGoto = ui->chkHighlightGoto->isChecked();
139 m_plugin->m_semanticHighlighting = ui->chkSemanticHighlighting->isChecked();
140 m_plugin->m_signatureHelp = ui->chkSignatureHelp->isChecked();
141
142 m_plugin->m_messages = ui->chkMessages->isChecked();
143
144 m_plugin->m_configPath = ui->edtConfigPath->url();
145
146 // own scope to ensure file is flushed before we signal below in writeConfig!
147 {
148 QFile configFile(m_plugin->configPath().toLocalFile());
149 configFile.open(QIODevice::WriteOnly);
150 if (configFile.isOpen()) {
151 configFile.write(ui->userConfig->toPlainText().toUtf8());
152 }
153 }
154
155 m_plugin->writeConfig();
156 }
157
reset()158 void LSPClientConfigPage::reset()
159 {
160 ui->chkSymbolDetails->setChecked(m_plugin->m_symbolDetails);
161 ui->chkSymbolTree->setChecked(m_plugin->m_symbolTree);
162 ui->chkSymbolExpand->setChecked(m_plugin->m_symbolExpand);
163 ui->chkSymbolSort->setChecked(m_plugin->m_symbolSort);
164
165 ui->chkComplDoc->setChecked(m_plugin->m_complDoc);
166 ui->chkRefDeclaration->setChecked(m_plugin->m_refDeclaration);
167 ui->chkComplParens->setChecked(m_plugin->m_complParens);
168
169 ui->chkDiagnostics->setChecked(m_plugin->m_diagnostics);
170 ui->chkDiagnosticsHighlight->setChecked(m_plugin->m_diagnosticsHighlight);
171 ui->chkDiagnosticsMark->setChecked(m_plugin->m_diagnosticsMark);
172 ui->chkDiagnosticsHover->setChecked(m_plugin->m_diagnosticsHover);
173 ui->spinDiagnosticsSize->setValue(m_plugin->m_diagnosticsSize);
174
175 ui->chkAutoHover->setChecked(m_plugin->m_autoHover);
176 ui->chkOnTypeFormatting->setChecked(m_plugin->m_onTypeFormatting);
177 ui->chkIncrementalSync->setChecked(m_plugin->m_incrementalSync);
178 ui->chkHighlightGoto->setChecked(m_plugin->m_highlightGoto);
179 ui->chkSemanticHighlighting->setChecked(m_plugin->m_semanticHighlighting);
180 ui->chkSignatureHelp->setChecked(m_plugin->m_signatureHelp);
181
182 ui->chkMessages->setChecked(m_plugin->m_messages);
183
184 ui->edtConfigPath->setUrl(m_plugin->m_configPath);
185
186 readUserConfig(m_plugin->configPath().toLocalFile());
187 }
188
defaults()189 void LSPClientConfigPage::defaults()
190 {
191 reset();
192 }
193
readUserConfig(const QString & fileName)194 void LSPClientConfigPage::readUserConfig(const QString &fileName)
195 {
196 QFile configFile(fileName);
197 configFile.open(QIODevice::ReadOnly);
198 if (configFile.isOpen()) {
199 ui->userConfig->setPlainText(QString::fromUtf8(configFile.readAll()));
200 } else {
201 ui->userConfig->clear();
202 }
203
204 updateConfigTextErrorState();
205 }
206
updateConfigTextErrorState()207 void LSPClientConfigPage::updateConfigTextErrorState()
208 {
209 const auto data = ui->userConfig->toPlainText().toUtf8();
210 if (data.isEmpty()) {
211 ui->userConfigError->setText(i18n("No JSON data to validate."));
212 return;
213 }
214
215 // check json validity
216 QJsonParseError error{};
217 auto json = QJsonDocument::fromJson(data, &error);
218 if (error.error == QJsonParseError::NoError) {
219 if (json.isObject()) {
220 ui->userConfigError->setText(i18n("JSON data is valid."));
221 } else {
222 ui->userConfigError->setText(i18n("JSON data is invalid: no JSON object"));
223 }
224 } else {
225 ui->userConfigError->setText(i18n("JSON data is invalid: %1", error.errorString()));
226 }
227 }
228
configTextChanged()229 void LSPClientConfigPage::configTextChanged()
230 {
231 // check for errors
232 updateConfigTextErrorState();
233
234 // remember changed
235 changed();
236 }
237
configUrlChanged()238 void LSPClientConfigPage::configUrlChanged()
239 {
240 // re-read config
241 readUserConfig(ui->edtConfigPath->url().isEmpty() ? m_plugin->m_defaultConfigPath.toLocalFile() : ui->edtConfigPath->url().toLocalFile());
242
243 // remember changed
244 changed();
245 }
246
updateHighlighters()247 void LSPClientConfigPage::updateHighlighters()
248 {
249 for (auto textEdit : {ui->userConfig, static_cast<QTextEdit *>(ui->defaultConfig)}) {
250 // setup JSON highlighter for the default json stuff
251 auto highlighter = new KSyntaxHighlighting::SyntaxHighlighter(textEdit->document());
252 highlighter->setDefinition(KTextEditor::Editor::instance()->repository().definitionForFileName(QStringLiteral("settings.json")));
253
254 // we want mono-spaced font
255 textEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
256
257 // we want to have the proper theme for the current palette
258 const auto theme = KTextEditor::Editor::instance()->theme();
259 auto pal = qApp->palette();
260 pal.setColor(QPalette::Base, QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::BackgroundColor)));
261 pal.setColor(QPalette::Highlight, QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::TextSelection)));
262 textEdit->setPalette(pal);
263 highlighter->setTheme(theme);
264 highlighter->rehighlight();
265 }
266 }
267