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"