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 "lspclientplugin.h" 0008 #include "lspclientconfigpage.h" 0009 #include "lspclientpluginview.h" 0010 #include "lspclientservermanager.h" 0011 0012 #include "lspclient_debug.h" 0013 0014 #include <KConfigGroup> 0015 #include <KLocalizedString> 0016 #include <KPluginFactory> 0017 #include <KSharedConfig> 0018 0019 #include <QApplication> 0020 #include <QDir> 0021 #include <QMessageBox> 0022 #include <QStandardPaths> 0023 #include <QTimer> 0024 0025 static const QString CONFIG_LSPCLIENT{QStringLiteral("lspclient")}; 0026 static const QString CONFIG_SYMBOL_DETAILS{QStringLiteral("SymbolDetails")}; 0027 static const QString CONFIG_SYMBOL_TREE{QStringLiteral("SymbolTree")}; 0028 static const QString CONFIG_SYMBOL_EXPAND{QStringLiteral("SymbolExpand")}; 0029 static const QString CONFIG_SYMBOL_SORT{QStringLiteral("SymbolSort")}; 0030 static const QString CONFIG_COMPLETION_DOC{QStringLiteral("CompletionDocumentation")}; 0031 static const QString CONFIG_REFERENCES_DECLARATION{QStringLiteral("ReferencesDeclaration")}; 0032 static const QString CONFIG_COMPLETION_PARENS{QStringLiteral("CompletionParens")}; 0033 static const QString CONFIG_AUTO_HOVER{QStringLiteral("AutoHover")}; 0034 static const QString CONFIG_TYPE_FORMATTING{QStringLiteral("TypeFormatting")}; 0035 static const QString CONFIG_INCREMENTAL_SYNC{QStringLiteral("IncrementalSync")}; 0036 static const QString CONFIG_HIGHLIGHT_GOTO{QStringLiteral("HighlightGoto")}; 0037 static const QString CONFIG_DIAGNOSTICS{QStringLiteral("Diagnostics")}; 0038 static const QString CONFIG_DIAGNOSTICS_HIGHLIGHT{QStringLiteral("DiagnosticsHighlight")}; 0039 static const QString CONFIG_DIAGNOSTICS_MARK{QStringLiteral("DiagnosticsMark")}; 0040 static const QString CONFIG_DIAGNOSTICS_HOVER{QStringLiteral("DiagnosticsHover")}; 0041 static const QString CONFIG_DIAGNOSTICS_SIZE{QStringLiteral("DiagnosticsSize")}; 0042 static const QString CONFIG_MESSAGES{QStringLiteral("Messages")}; 0043 static const QString CONFIG_SERVER_CONFIG{QStringLiteral("ServerConfiguration")}; 0044 static const QString CONFIG_SEMANTIC_HIGHLIGHTING{QStringLiteral("SemanticHighlighting")}; 0045 static const QString CONFIG_SIGNATURE_HELP{QStringLiteral("SignatureHelp")}; 0046 static const QString CONFIG_AUTO_IMPORT{QStringLiteral("AutoImport")}; 0047 static const QString CONFIG_ALLOWED_COMMANDS{QStringLiteral("AllowedServerCommandLines")}; 0048 static const QString CONFIG_BLOCKED_COMMANDS{QStringLiteral("BlockedServerCommandLines")}; 0049 static const QString CONFIG_FORMAT_ON_SAVE{QStringLiteral("FormatOnSave")}; 0050 static const QString CONFIG_INLAY_HINT{QStringLiteral("InlayHints")}; 0051 0052 K_PLUGIN_FACTORY_WITH_JSON(LSPClientPluginFactory, "lspclientplugin.json", registerPlugin<LSPClientPlugin>();) 0053 0054 /** 0055 * ensure we don't spam the user with debug output per-default 0056 */ 0057 static const bool debug = (qgetenv("LSPCLIENT_DEBUG") == QByteArray("1")); 0058 static QLoggingCategory::CategoryFilter oldCategoryFilter = nullptr; 0059 void myCategoryFilter(QLoggingCategory *category) 0060 { 0061 // deactivate info and debug if not debug mode 0062 if (qstrcmp(category->categoryName(), "katelspclientplugin") == 0) { 0063 category->setEnabled(QtInfoMsg, debug); 0064 category->setEnabled(QtDebugMsg, debug); 0065 } else if (oldCategoryFilter) { 0066 oldCategoryFilter(category); 0067 } 0068 } 0069 0070 LSPClientPlugin::LSPClientPlugin(QObject *parent, const QVariantList &) 0071 : KTextEditor::Plugin(parent) 0072 , m_settingsPath(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + QStringLiteral("/lspclient")) 0073 , m_defaultConfigPath(QUrl::fromLocalFile(m_settingsPath + QStringLiteral("/settings.json"))) 0074 , m_debugMode(debug) 0075 { 0076 // ensure settings path exist, for e.g. local settings.json 0077 QDir().mkpath(m_settingsPath); 0078 0079 // ensure we don't spam the user with debug messages per default, do this just once 0080 if (!oldCategoryFilter) { 0081 oldCategoryFilter = QLoggingCategory::installFilter(myCategoryFilter); 0082 } 0083 0084 // apply our config 0085 readConfig(); 0086 } 0087 0088 LSPClientPlugin::~LSPClientPlugin() 0089 { 0090 } 0091 0092 QObject *LSPClientPlugin::createView(KTextEditor::MainWindow *mainWindow) 0093 { 0094 if (!m_serverManager) { 0095 m_serverManager = LSPClientServerManager::new_(this); 0096 } 0097 return LSPClientPluginView::new_(this, mainWindow, m_serverManager); 0098 } 0099 0100 int LSPClientPlugin::configPages() const 0101 { 0102 return 1; 0103 } 0104 0105 KTextEditor::ConfigPage *LSPClientPlugin::configPage(int number, QWidget *parent) 0106 { 0107 if (number != 0) { 0108 return nullptr; 0109 } 0110 0111 return new LSPClientConfigPage(parent, this); 0112 } 0113 0114 void LSPClientPlugin::readConfig() 0115 { 0116 KConfigGroup config(KSharedConfig::openConfig(), CONFIG_LSPCLIENT); 0117 m_symbolDetails = config.readEntry(CONFIG_SYMBOL_DETAILS, false); 0118 m_symbolTree = config.readEntry(CONFIG_SYMBOL_TREE, true); 0119 m_symbolExpand = config.readEntry(CONFIG_SYMBOL_EXPAND, true); 0120 m_symbolSort = config.readEntry(CONFIG_SYMBOL_SORT, false); 0121 m_complDoc = config.readEntry(CONFIG_COMPLETION_DOC, true); 0122 m_refDeclaration = config.readEntry(CONFIG_REFERENCES_DECLARATION, true); 0123 m_complParens = config.readEntry(CONFIG_COMPLETION_PARENS, true); 0124 m_autoHover = config.readEntry(CONFIG_AUTO_HOVER, true); 0125 m_onTypeFormatting = config.readEntry(CONFIG_TYPE_FORMATTING, false); 0126 m_incrementalSync = config.readEntry(CONFIG_INCREMENTAL_SYNC, false); 0127 m_highlightGoto = config.readEntry(CONFIG_HIGHLIGHT_GOTO, true); 0128 m_diagnostics = config.readEntry(CONFIG_DIAGNOSTICS, true); 0129 m_messages = config.readEntry(CONFIG_MESSAGES, true); 0130 m_configPath = config.readEntry(CONFIG_SERVER_CONFIG, QUrl()); 0131 m_semanticHighlighting = config.readEntry(CONFIG_SEMANTIC_HIGHLIGHTING, true); 0132 m_signatureHelp = config.readEntry(CONFIG_SIGNATURE_HELP, true); 0133 m_autoImport = config.readEntry(CONFIG_AUTO_IMPORT, true); 0134 m_fmtOnSave = config.readEntry(CONFIG_FORMAT_ON_SAVE, false); 0135 m_inlayHints = config.readEntry(CONFIG_INLAY_HINT, false); 0136 0137 // read allow + block lists as two separate keys, let block always win 0138 const auto allowed = config.readEntry(CONFIG_ALLOWED_COMMANDS, QStringList()); 0139 const auto blocked = config.readEntry(CONFIG_BLOCKED_COMMANDS, QStringList()); 0140 m_serverCommandLineToAllowedState.clear(); 0141 for (const auto &cmd : allowed) { 0142 m_serverCommandLineToAllowedState[cmd] = true; 0143 } 0144 for (const auto &cmd : blocked) { 0145 m_serverCommandLineToAllowedState[cmd] = false; 0146 } 0147 0148 Q_EMIT update(); 0149 } 0150 0151 void LSPClientPlugin::writeConfig() const 0152 { 0153 KConfigGroup config(KSharedConfig::openConfig(), CONFIG_LSPCLIENT); 0154 config.writeEntry(CONFIG_SYMBOL_DETAILS, m_symbolDetails); 0155 config.writeEntry(CONFIG_SYMBOL_TREE, m_symbolTree); 0156 config.writeEntry(CONFIG_SYMBOL_EXPAND, m_symbolExpand); 0157 config.writeEntry(CONFIG_SYMBOL_SORT, m_symbolSort); 0158 config.writeEntry(CONFIG_COMPLETION_DOC, m_complDoc); 0159 config.writeEntry(CONFIG_REFERENCES_DECLARATION, m_refDeclaration); 0160 config.writeEntry(CONFIG_COMPLETION_PARENS, m_complParens); 0161 config.writeEntry(CONFIG_AUTO_HOVER, m_autoHover); 0162 config.writeEntry(CONFIG_TYPE_FORMATTING, m_onTypeFormatting); 0163 config.writeEntry(CONFIG_INCREMENTAL_SYNC, m_incrementalSync); 0164 config.writeEntry(CONFIG_HIGHLIGHT_GOTO, m_highlightGoto); 0165 config.writeEntry(CONFIG_DIAGNOSTICS, m_diagnostics); 0166 config.writeEntry(CONFIG_MESSAGES, m_messages); 0167 config.writeEntry(CONFIG_SERVER_CONFIG, m_configPath); 0168 config.writeEntry(CONFIG_SEMANTIC_HIGHLIGHTING, m_semanticHighlighting); 0169 config.writeEntry(CONFIG_SIGNATURE_HELP, m_signatureHelp); 0170 config.writeEntry(CONFIG_AUTO_IMPORT, m_autoImport); 0171 config.writeEntry(CONFIG_FORMAT_ON_SAVE, m_fmtOnSave); 0172 config.writeEntry(CONFIG_INLAY_HINT, m_inlayHints); 0173 0174 // write allow + block lists as two separate keys 0175 QStringList allowed, blocked; 0176 for (const auto &it : m_serverCommandLineToAllowedState) { 0177 if (it.second) { 0178 allowed.push_back(it.first); 0179 } else { 0180 blocked.push_back(it.first); 0181 } 0182 } 0183 config.writeEntry(CONFIG_ALLOWED_COMMANDS, allowed); 0184 config.writeEntry(CONFIG_BLOCKED_COMMANDS, blocked); 0185 0186 Q_EMIT update(); 0187 } 0188 0189 bool LSPClientPlugin::isCommandLineAllowed(const QStringList &cmdline) 0190 { 0191 // check our allow list 0192 // if we already have stored some value, perfect, just use that one 0193 const QString fullCommandLineString = cmdline.join(QStringLiteral(" ")); 0194 if (const auto it = m_serverCommandLineToAllowedState.find(fullCommandLineString); it != m_serverCommandLineToAllowedState.end()) { 0195 return it->second; 0196 } 0197 0198 // else: queue asking the user async, will ensure we don't have duplicate dialogs 0199 // tell the launching it is forbidden ATM 0200 QTimer::singleShot(0, this, [this, fullCommandLineString]() { 0201 askForCommandLinePermission(fullCommandLineString); 0202 }); 0203 return false; 0204 } 0205 0206 void LSPClientPlugin::askForCommandLinePermission(const QString &fullCommandLineString) 0207 { 0208 // did we already store a new result for the given command line? just use that 0209 if (const auto it = m_serverCommandLineToAllowedState.find(fullCommandLineString); it != m_serverCommandLineToAllowedState.end()) { 0210 // we need to trigger config update if the command is not blocked any longer 0211 if (it->second) { 0212 Q_EMIT update(); 0213 } 0214 return; 0215 } 0216 0217 // is this command line request already open? => skip the new request, dialog is already there 0218 if (!m_currentActiveCommandLineDialogs.insert(fullCommandLineString).second) { 0219 return; 0220 } 0221 0222 // ask user if the start should be allowed 0223 QPointer<QMessageBox> msgBox(new QMessageBox(QApplication::activeWindow())); 0224 msgBox->setWindowTitle(i18n("LSP server start requested")); 0225 msgBox->setTextFormat(Qt::RichText); 0226 msgBox->setText( 0227 i18n("Do you want the LSP server to be started?<br><br>The full command line is:<br><br><b>%1</b><br><br>The choice can be altered via the config page " 0228 "of the plugin.", 0229 fullCommandLineString.toHtmlEscaped())); 0230 msgBox->setStandardButtons(QMessageBox::Yes | QMessageBox::No); 0231 msgBox->setDefaultButton(QMessageBox::Yes); 0232 const bool allowed = (msgBox->exec() == QMessageBox::Yes); 0233 0234 // store new configured value 0235 m_serverCommandLineToAllowedState.emplace(fullCommandLineString, allowed); 0236 0237 // inform the user if it was forbidden! do this here to just emit this once 0238 if (!allowed) { 0239 Q_EMIT showMessage(KTextEditor::Message::Information, 0240 i18n("User permanently blocked start of: '%1'.\nUse the config page of the plugin to undo this block.", fullCommandLineString)); 0241 } 0242 0243 // purge dialog entry 0244 m_currentActiveCommandLineDialogs.erase(fullCommandLineString); 0245 0246 // flush config, will trigger update() 0247 writeConfig(); 0248 } 0249 0250 #include "lspclientplugin.moc" 0251 #include "moc_lspclientplugin.cpp"