File indexing completed on 2024-04-28 05:48:56
0001 /* 0002 SPDX-FileCopyrightText: 2019 Mark Nauwelaerts <mark.nauwelaerts@gmail.com> 0003 0004 SPDX-License-Identifier: MIT 0005 */ 0006 0007 #include "lspclientconfigpage.h" 0008 #include "lspclientplugin.h" 0009 #include "ui_lspconfigwidget.h" 0010 0011 #include <KLocalizedString> 0012 0013 #include <KSyntaxHighlighting/Definition> 0014 #include <KSyntaxHighlighting/Repository> 0015 #include <KSyntaxHighlighting/SyntaxHighlighter> 0016 #include <KSyntaxHighlighting/Theme> 0017 0018 #include <KTextEditor/Editor> 0019 0020 #include <QJsonDocument> 0021 #include <QJsonParseError> 0022 #include <QMenu> 0023 #include <QPalette> 0024 0025 LSPClientConfigPage::LSPClientConfigPage(QWidget *parent, LSPClientPlugin *plugin) 0026 : KTextEditor::ConfigPage(parent) 0027 , m_plugin(plugin) 0028 { 0029 ui = new Ui::LspConfigWidget(); 0030 ui->setupUi(this); 0031 ui->tabWidget->setDocumentMode(true); 0032 0033 // fix-up our two text edits to be proper JSON file editors 0034 updateHighlighters(); 0035 0036 // ensure we update the highlighters if the repository is updated or theme is changed 0037 connect(KTextEditor::Editor::instance(), &KTextEditor::Editor::repositoryReloaded, this, &LSPClientConfigPage::updateHighlighters); 0038 connect(KTextEditor::Editor::instance(), &KTextEditor::Editor::configChanged, this, &LSPClientConfigPage::updateHighlighters); 0039 0040 // setup default json settings 0041 QFile defaultConfigFile(QStringLiteral(":/lspclient/settings.json")); 0042 defaultConfigFile.open(QIODevice::ReadOnly); 0043 Q_ASSERT(defaultConfigFile.isOpen()); 0044 ui->defaultConfig->setPlainText(QString::fromUtf8(defaultConfigFile.readAll())); 0045 0046 // setup default config path as placeholder to show user where it is 0047 ui->edtConfigPath->setPlaceholderText(m_plugin->m_defaultConfigPath.toLocalFile()); 0048 0049 reset(); 0050 0051 for (const auto &cb : {ui->chkSymbolDetails, 0052 ui->chkSymbolExpand, 0053 ui->chkSymbolSort, 0054 ui->chkSymbolTree, 0055 ui->chkComplDoc, 0056 ui->chkRefDeclaration, 0057 ui->chkComplParens, 0058 ui->chkMessages, 0059 ui->chkDiagnostics, 0060 ui->chkOnTypeFormatting, 0061 ui->chkIncrementalSync, 0062 ui->chkHighlightGoto, 0063 ui->chkSemanticHighlighting, 0064 ui->chkAutoHover, 0065 ui->chkSignatureHelp, 0066 ui->chkAutoImport, 0067 ui->chkFmtOnSave, 0068 ui->chkInlayHint}) { 0069 connect(cb, &QCheckBox::toggled, this, &LSPClientConfigPage::changed); 0070 } 0071 0072 connect(ui->edtConfigPath, &KUrlRequester::textChanged, this, &LSPClientConfigPage::configUrlChanged); 0073 connect(ui->edtConfigPath, &KUrlRequester::urlSelected, this, &LSPClientConfigPage::configUrlChanged); 0074 0075 connect(ui->allowedAndBlockedServers, &QListWidget::itemChanged, this, &LSPClientConfigPage::changed); 0076 0077 // own context menu to delete entries 0078 ui->allowedAndBlockedServers->setContextMenuPolicy(Qt::CustomContextMenu); 0079 connect(ui->allowedAndBlockedServers, &QWidget::customContextMenuRequested, this, &LSPClientConfigPage::showContextMenuAllowedBlocked); 0080 0081 auto cfgh = [this](int position, int added, int removed) { 0082 Q_UNUSED(position); 0083 // discard format change 0084 // (e.g. due to syntax highlighting) 0085 if (added || removed) { 0086 configTextChanged(); 0087 } 0088 }; 0089 connect(ui->userConfig->document(), &QTextDocument::contentsChange, this, cfgh); 0090 } 0091 0092 LSPClientConfigPage::~LSPClientConfigPage() 0093 { 0094 delete ui; 0095 } 0096 0097 QString LSPClientConfigPage::name() const 0098 { 0099 return QString(i18n("LSP Client")); 0100 } 0101 0102 QString LSPClientConfigPage::fullName() const 0103 { 0104 return QString(i18n("LSP Client")); 0105 } 0106 0107 QIcon LSPClientConfigPage::icon() const 0108 { 0109 return QIcon::fromTheme(QLatin1String("format-text-code")); 0110 } 0111 0112 void LSPClientConfigPage::apply() 0113 { 0114 m_plugin->m_symbolDetails = ui->chkSymbolDetails->isChecked(); 0115 m_plugin->m_symbolTree = ui->chkSymbolTree->isChecked(); 0116 m_plugin->m_symbolExpand = ui->chkSymbolExpand->isChecked(); 0117 m_plugin->m_symbolSort = ui->chkSymbolSort->isChecked(); 0118 0119 m_plugin->m_complDoc = ui->chkComplDoc->isChecked(); 0120 m_plugin->m_refDeclaration = ui->chkRefDeclaration->isChecked(); 0121 m_plugin->m_complParens = ui->chkComplParens->isChecked(); 0122 0123 m_plugin->m_autoHover = ui->chkAutoHover->isChecked(); 0124 m_plugin->m_onTypeFormatting = ui->chkOnTypeFormatting->isChecked(); 0125 m_plugin->m_incrementalSync = ui->chkIncrementalSync->isChecked(); 0126 m_plugin->m_highlightGoto = ui->chkHighlightGoto->isChecked(); 0127 m_plugin->m_semanticHighlighting = ui->chkSemanticHighlighting->isChecked(); 0128 m_plugin->m_signatureHelp = ui->chkSignatureHelp->isChecked(); 0129 m_plugin->m_autoImport = ui->chkAutoImport->isChecked(); 0130 m_plugin->m_fmtOnSave = ui->chkFmtOnSave->isChecked(); 0131 m_plugin->m_inlayHints = ui->chkInlayHint->isChecked(); 0132 0133 m_plugin->m_diagnostics = ui->chkDiagnostics->isChecked(); 0134 m_plugin->m_messages = ui->chkMessages->isChecked(); 0135 0136 m_plugin->m_configPath = ui->edtConfigPath->url(); 0137 0138 m_plugin->m_serverCommandLineToAllowedState.clear(); 0139 for (int i = 0; i < ui->allowedAndBlockedServers->count(); ++i) { 0140 const auto item = ui->allowedAndBlockedServers->item(i); 0141 m_plugin->m_serverCommandLineToAllowedState.emplace(item->text(), item->checkState() == Qt::Checked); 0142 } 0143 0144 // own scope to ensure file is flushed before we signal below in writeConfig! 0145 { 0146 QFile configFile(m_plugin->configPath().toLocalFile()); 0147 configFile.open(QIODevice::WriteOnly); 0148 if (configFile.isOpen()) { 0149 configFile.write(ui->userConfig->toPlainText().toUtf8()); 0150 } 0151 } 0152 0153 m_plugin->writeConfig(); 0154 } 0155 0156 void LSPClientConfigPage::reset() 0157 { 0158 ui->chkSymbolDetails->setChecked(m_plugin->m_symbolDetails); 0159 ui->chkSymbolTree->setChecked(m_plugin->m_symbolTree); 0160 ui->chkSymbolExpand->setChecked(m_plugin->m_symbolExpand); 0161 ui->chkSymbolSort->setChecked(m_plugin->m_symbolSort); 0162 0163 ui->chkComplDoc->setChecked(m_plugin->m_complDoc); 0164 ui->chkRefDeclaration->setChecked(m_plugin->m_refDeclaration); 0165 ui->chkComplParens->setChecked(m_plugin->m_complParens); 0166 0167 ui->chkAutoHover->setChecked(m_plugin->m_autoHover); 0168 ui->chkOnTypeFormatting->setChecked(m_plugin->m_onTypeFormatting); 0169 ui->chkIncrementalSync->setChecked(m_plugin->m_incrementalSync); 0170 ui->chkHighlightGoto->setChecked(m_plugin->m_highlightGoto); 0171 ui->chkSemanticHighlighting->setChecked(m_plugin->m_semanticHighlighting); 0172 ui->chkSignatureHelp->setChecked(m_plugin->m_signatureHelp); 0173 ui->chkAutoImport->setChecked(m_plugin->m_autoImport); 0174 ui->chkFmtOnSave->setChecked(m_plugin->m_fmtOnSave); 0175 ui->chkInlayHint->setChecked(m_plugin->m_inlayHints); 0176 0177 ui->chkDiagnostics->setChecked(m_plugin->m_diagnostics); 0178 ui->chkMessages->setChecked(m_plugin->m_messages); 0179 0180 ui->edtConfigPath->setUrl(m_plugin->m_configPath); 0181 0182 readUserConfig(m_plugin->configPath().toLocalFile()); 0183 0184 // fill in the allowed and blocked servers 0185 ui->allowedAndBlockedServers->clear(); 0186 for (const auto &it : m_plugin->m_serverCommandLineToAllowedState) { 0187 auto item = new QListWidgetItem(it.first, ui->allowedAndBlockedServers); 0188 item->setCheckState(it.second ? Qt::Checked : Qt::Unchecked); 0189 } 0190 } 0191 0192 void LSPClientConfigPage::defaults() 0193 { 0194 reset(); 0195 } 0196 0197 void LSPClientConfigPage::readUserConfig(const QString &fileName) 0198 { 0199 QFile configFile(fileName); 0200 configFile.open(QIODevice::ReadOnly); 0201 if (configFile.isOpen()) { 0202 ui->userConfig->setPlainText(QString::fromUtf8(configFile.readAll())); 0203 } else { 0204 ui->userConfig->clear(); 0205 } 0206 0207 updateConfigTextErrorState(); 0208 } 0209 0210 void LSPClientConfigPage::updateConfigTextErrorState() 0211 { 0212 const auto data = ui->userConfig->toPlainText().toUtf8(); 0213 if (data.isEmpty()) { 0214 ui->userConfigError->setText(i18n("No JSON data to validate.")); 0215 return; 0216 } 0217 0218 // check json validity 0219 QJsonParseError error{}; 0220 auto json = QJsonDocument::fromJson(data, &error); 0221 if (error.error == QJsonParseError::NoError) { 0222 if (json.isObject()) { 0223 ui->userConfigError->setText(i18n("JSON data is valid.")); 0224 } else { 0225 ui->userConfigError->setText(i18n("JSON data is invalid: no JSON object")); 0226 } 0227 } else { 0228 ui->userConfigError->setText(i18n("JSON data is invalid: %1", error.errorString())); 0229 } 0230 } 0231 0232 void LSPClientConfigPage::configTextChanged() 0233 { 0234 // check for errors 0235 updateConfigTextErrorState(); 0236 0237 // remember changed 0238 changed(); 0239 } 0240 0241 void LSPClientConfigPage::configUrlChanged() 0242 { 0243 // re-read config 0244 readUserConfig(ui->edtConfigPath->url().isEmpty() ? m_plugin->m_defaultConfigPath.toLocalFile() : ui->edtConfigPath->url().toLocalFile()); 0245 0246 // remember changed 0247 changed(); 0248 } 0249 0250 void LSPClientConfigPage::updateHighlighters() 0251 { 0252 for (auto textEdit : {ui->userConfig, ui->defaultConfig}) { 0253 // setup JSON highlighter for the default json stuff 0254 auto highlighter = new KSyntaxHighlighting::SyntaxHighlighter(textEdit->document()); 0255 highlighter->setDefinition(KTextEditor::Editor::instance()->repository().definitionForFileName(QStringLiteral("settings.json"))); 0256 0257 // we want mono-spaced font 0258 textEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); 0259 0260 // we want to have the proper theme for the current palette 0261 const auto theme = KTextEditor::Editor::instance()->theme(); 0262 auto pal = qApp->palette(); 0263 pal.setColor(QPalette::Base, QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::BackgroundColor))); 0264 pal.setColor(QPalette::Highlight, QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::TextSelection))); 0265 textEdit->setPalette(pal); 0266 highlighter->setTheme(theme); 0267 highlighter->rehighlight(); 0268 } 0269 } 0270 0271 void LSPClientConfigPage::showContextMenuAllowedBlocked(const QPoint &pos) 0272 { 0273 // allow deletion of stuff 0274 QMenu myMenu(this); 0275 0276 auto currentDelete = myMenu.addAction(i18n("Delete selected entries"), this, [this]() { 0277 qDeleteAll(ui->allowedAndBlockedServers->selectedItems()); 0278 }); 0279 currentDelete->setEnabled(!ui->allowedAndBlockedServers->selectedItems().isEmpty()); 0280 0281 auto allDelete = myMenu.addAction(i18n("Delete all entries"), this, [this]() { 0282 ui->allowedAndBlockedServers->clear(); 0283 }); 0284 allDelete->setEnabled(ui->allowedAndBlockedServers->count() > 0); 0285 0286 myMenu.exec(ui->allowedAndBlockedServers->mapToGlobal(pos)); 0287 } 0288 0289 #include "moc_lspclientconfigpage.cpp"