1 /*
2     SPDX-FileCopyrightText: 2001-2010 Christoph Cullmann <cullmann@kde.org>
3     SPDX-FileCopyrightText: 2009 Erlend Hamberg <ehamberg@gmail.com>
4 
5     SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "kateglobal.h"
9 #include "config.h"
10 
11 #include <ktexteditor_version.h>
12 
13 #include "katebuffer.h"
14 #include "katecmd.h"
15 #include "katecmds.h"
16 #include "kateconfig.h"
17 #include "katedialogs.h"
18 #include "katedocument.h"
19 #include "katehighlightingcmds.h"
20 #include "katekeywordcompletion.h"
21 #include "katemodemanager.h"
22 #include "katescriptmanager.h"
23 #include "katesedcmd.h"
24 #include "katesyntaxmanager.h"
25 #include "katethemeconfig.h"
26 #include "katevariableexpansionmanager.h"
27 #include "kateview.h"
28 #include "katewordcompletion.h"
29 #include "spellcheck/spellcheck.h"
30 
31 #include "katenormalinputmodefactory.h"
32 #include "kateviinputmodefactory.h"
33 
34 #include <KConfigGroup>
35 #include <KDirWatch>
36 #include <KLocalizedString>
37 #include <KPageDialog>
38 
39 #include <QApplication>
40 #include <QBoxLayout>
41 #include <QClipboard>
42 #include <QFrame>
43 #include <QPushButton>
44 #include <QStringListModel>
45 #include <QTimer>
46 
47 // BEGIN unit test mode
48 static bool kateUnitTestMode = false;
49 
enableUnitTestMode()50 void KTextEditor::EditorPrivate::enableUnitTestMode()
51 {
52     kateUnitTestMode = true;
53 }
54 
unitTestMode()55 bool KTextEditor::EditorPrivate::unitTestMode()
56 {
57     return kateUnitTestMode;
58 }
59 // END unit test mode
60 
EditorPrivate(QPointer<KTextEditor::EditorPrivate> & staticInstance)61 KTextEditor::EditorPrivate::EditorPrivate(QPointer<KTextEditor::EditorPrivate> &staticInstance)
62     : KTextEditor::Editor(this)
63     , m_aboutData(QStringLiteral("katepart"),
64                   i18n("Kate Part"),
65                   QStringLiteral(KTEXTEDITOR_VERSION_STRING),
66                   i18n("Embeddable editor component"),
67                   KAboutLicense::LGPL_V2,
68                   i18n("(c) 2000-2021 The Kate Authors"),
69                   QString(),
70                   QStringLiteral("https://kate-editor.org"))
71     , m_dummyApplication(nullptr)
72     , m_application(&m_dummyApplication)
73     , m_dummyMainWindow(nullptr)
74     , m_searchHistoryModel(nullptr)
75     , m_replaceHistoryModel(nullptr)
76 {
77     // remember this
78     staticInstance = this;
79 
80     // register some datatypes
81     qRegisterMetaType<KTextEditor::Cursor>("KTextEditor::Cursor");
82     qRegisterMetaType<KTextEditor::Document *>("KTextEditor::Document*");
83     qRegisterMetaType<KTextEditor::View *>("KTextEditor::View*");
84 
85     //
86     // fill about data
87     //
88     m_aboutData.addAuthor(i18n("Christoph Cullmann"), i18n("Maintainer"), QStringLiteral("cullmann@kde.org"), QStringLiteral("https://cullmann.io"));
89     m_aboutData.addAuthor(i18n("Dominik Haumann"), i18n("Core Developer"), QStringLiteral("dhaumann@kde.org"));
90     m_aboutData.addAuthor(i18n("Milian Wolff"), i18n("Core Developer"), QStringLiteral("mail@milianw.de"), QStringLiteral("https://milianw.de/"));
91     m_aboutData.addAuthor(i18n("Joseph Wenninger"),
92                           i18n("Core Developer"),
93                           QStringLiteral("jowenn@kde.org"),
94                           QStringLiteral("http://stud3.tuwien.ac.at/~e9925371"));
95     m_aboutData.addAuthor(i18n("Erlend Hamberg"), i18n("Vi Input Mode"), QStringLiteral("ehamberg@gmail.com"), QStringLiteral("https://hamberg.no/erlend"));
96     m_aboutData.addAuthor(i18n("Bernhard Beschow"),
97                           i18n("Developer"),
98                           QStringLiteral("bbeschow@cs.tu-berlin.de"),
99                           QStringLiteral("https://user.cs.tu-berlin.de/~bbeschow"));
100     m_aboutData.addAuthor(i18n("Anders Lund"), i18n("Core Developer"), QStringLiteral("anders@alweb.dk"), QStringLiteral("https://alweb.dk"));
101     m_aboutData.addAuthor(i18n("Michel Ludwig"), i18n("On-the-fly spell checking"), QStringLiteral("michel.ludwig@kdemail.net"));
102     m_aboutData.addAuthor(i18n("Pascal Létourneau"), i18n("Large scale bug fixing"), QStringLiteral("pascal.letourneau@gmail.com"));
103     m_aboutData.addAuthor(i18n("Hamish Rodda"), i18n("Core Developer"), QStringLiteral("rodda@kde.org"));
104     m_aboutData.addAuthor(i18n("Waldo Bastian"), i18n("The cool buffersystem"), QStringLiteral("bastian@kde.org"));
105     m_aboutData.addAuthor(i18n("Charles Samuels"), i18n("The Editing Commands"), QStringLiteral("charles@kde.org"));
106     m_aboutData.addAuthor(i18n("Matt Newell"), i18n("Testing, ..."), QStringLiteral("newellm@proaxis.com"));
107     m_aboutData.addAuthor(i18n("Michael Bartl"), i18n("Former Core Developer"), QStringLiteral("michael.bartl1@chello.at"));
108     m_aboutData.addAuthor(i18n("Michael McCallum"), i18n("Core Developer"), QStringLiteral("gholam@xtra.co.nz"));
109     m_aboutData.addAuthor(i18n("Michael Koch"), i18n("KWrite port to KParts"), QStringLiteral("koch@kde.org"));
110     m_aboutData.addAuthor(i18n("Christian Gebauer"), QString(), QStringLiteral("gebauer@kde.org"));
111     m_aboutData.addAuthor(i18n("Simon Hausmann"), QString(), QStringLiteral("hausmann@kde.org"));
112     m_aboutData.addAuthor(i18n("Glen Parker"), i18n("KWrite Undo History, Kspell integration"), QStringLiteral("glenebob@nwlink.com"));
113     m_aboutData.addAuthor(i18n("Scott Manson"), i18n("KWrite XML Syntax highlighting support"), QStringLiteral("sdmanson@alltel.net"));
114     m_aboutData.addAuthor(i18n("John Firebaugh"), i18n("Patches and more"), QStringLiteral("jfirebaugh@kde.org"));
115     m_aboutData.addAuthor(i18n("Andreas Kling"), i18n("Developer"), QStringLiteral("kling@impul.se"));
116     m_aboutData.addAuthor(i18n("Mirko Stocker"), i18n("Various bugfixes"), QStringLiteral("me@misto.ch"), QStringLiteral("https://misto.ch/"));
117     m_aboutData.addAuthor(i18n("Matthew Woehlke"), i18n("Selection, KColorScheme integration"), QStringLiteral("mw_triad@users.sourceforge.net"));
118     m_aboutData.addAuthor(i18n("Sebastian Pipping"),
119                           i18n("Search bar back- and front-end"),
120                           QStringLiteral("webmaster@hartwork.org"),
121                           QStringLiteral("https://hartwork.org/"));
122     m_aboutData.addAuthor(i18n("Jochen Wilhelmy"), i18n("Original KWrite Author"), QStringLiteral("digisnap@cs.tu-berlin.de"));
123     m_aboutData.addAuthor(i18n("Gerald Senarclens de Grancy"),
124                           i18n("QA and Scripting"),
125                           QStringLiteral("oss@senarclens.eu"),
126                           QStringLiteral("http://find-santa.eu/"));
127 
128     m_aboutData.addCredit(i18n("Matteo Merli"), i18n("Highlighting for RPM Spec-Files, Perl, Diff and more"), QStringLiteral("merlim@libero.it"));
129     m_aboutData.addCredit(i18n("Rocky Scaletta"), i18n("Highlighting for VHDL"), QStringLiteral("rocky@purdue.edu"));
130     m_aboutData.addCredit(i18n("Yury Lebedev"), i18n("Highlighting for SQL"), QString());
131     m_aboutData.addCredit(i18n("Chris Ross"), i18n("Highlighting for Ferite"), QString());
132     m_aboutData.addCredit(i18n("Nick Roux"), i18n("Highlighting for ILERPG"), QString());
133     m_aboutData.addCredit(i18n("Carsten Niehaus"), i18n("Highlighting for LaTeX"), QString());
134     m_aboutData.addCredit(i18n("Per Wigren"), i18n("Highlighting for Makefiles, Python"), QString());
135     m_aboutData.addCredit(i18n("Jan Fritz"), i18n("Highlighting for Python"), QString());
136     m_aboutData.addCredit(i18n("Daniel Naber"));
137     m_aboutData.addCredit(i18n("Roland Pabel"), i18n("Highlighting for Scheme"), QString());
138     m_aboutData.addCredit(i18n("Cristi Dumitrescu"), i18n("PHP Keyword/Datatype list"), QString());
139     m_aboutData.addCredit(i18n("Carsten Pfeiffer"), i18n("Very nice help"), QString());
140     m_aboutData.addCredit(i18n("Bruno Massa"), i18n("Highlighting for Lua"), QStringLiteral("brmassa@gmail.com"));
141 
142     m_aboutData.addCredit(i18n("All people who have contributed and I have forgotten to mention"));
143 
144     m_aboutData.setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), i18nc("EMAIL OF TRANSLATORS", "Your emails"));
145 
146     // set proper Kate icon for our about dialog
147     m_aboutData.setProgramLogo(QIcon(QStringLiteral(":/ktexteditor/kate.svg")));
148 
149     //
150     // dir watch
151     //
152     m_dirWatch = new KDirWatch();
153 
154     //
155     // command manager
156     //
157     m_cmdManager = new KateCmd();
158 
159     //
160     // variable expansion manager
161     //
162     m_variableExpansionManager = new KateVariableExpansionManager(this);
163 
164     //
165     // hl manager
166     //
167     m_hlManager = new KateHlManager();
168 
169     //
170     // mode man
171     //
172     m_modeManager = new KateModeManager();
173 
174     //
175     // input mode factories
176     //
177     Q_ASSERT(m_inputModeFactories.size() == KTextEditor::View::ViInputMode + 1);
178     m_inputModeFactories[KTextEditor::View::NormalInputMode].reset(new KateNormalInputModeFactory());
179     m_inputModeFactories[KTextEditor::View::ViInputMode].reset(new KateViInputModeFactory());
180 
181     //
182     // spell check manager
183     //
184     m_spellCheckManager = new KateSpellCheckManager();
185 
186     // config objects
187     m_globalConfig = new KateGlobalConfig();
188     m_documentConfig = new KateDocumentConfig();
189     m_viewConfig = new KateViewConfig();
190     m_rendererConfig = new KateRendererConfig();
191 
192     // create script manager (search scripts)
193     m_scriptManager = KateScriptManager::self();
194 
195     //
196     // init the cmds
197     //
198     m_cmds.push_back(KateCommands::CoreCommands::self());
199     m_cmds.push_back(KateCommands::Character::self());
200     m_cmds.push_back(KateCommands::Date::self());
201     m_cmds.push_back(KateCommands::SedReplace::self());
202     m_cmds.push_back(KateCommands::Highlighting::self());
203 
204     // global word completion model
205     m_wordCompletionModel = new KateWordCompletionModel(this);
206 
207     // global keyword completion model
208     m_keywordCompletionModel = new KateKeywordCompletionModel(this);
209 
210     // tap to QApplication object for color palette changes
211     qApp->installEventFilter(this);
212 }
213 
~EditorPrivate()214 KTextEditor::EditorPrivate::~EditorPrivate()
215 {
216     delete m_globalConfig;
217     delete m_documentConfig;
218     delete m_viewConfig;
219     delete m_rendererConfig;
220 
221     delete m_modeManager;
222 
223     delete m_dirWatch;
224 
225     // cu managers
226     delete m_scriptManager;
227     delete m_hlManager;
228 
229     delete m_spellCheckManager;
230 
231     // cu model
232     delete m_wordCompletionModel;
233 
234     // delete variable expansion manager
235     delete m_variableExpansionManager;
236     m_variableExpansionManager = nullptr;
237 
238     // delete the commands before we delete the cmd manager
239     qDeleteAll(m_cmds);
240     delete m_cmdManager;
241 }
242 
createDocument(QObject * parent)243 KTextEditor::Document *KTextEditor::EditorPrivate::createDocument(QObject *parent)
244 {
245     KTextEditor::DocumentPrivate *doc = new KTextEditor::DocumentPrivate(false, false, nullptr, parent);
246 
247     Q_EMIT documentCreated(this, doc);
248 
249     return doc;
250 }
251 
252 // END KTextEditor::Editor config stuff
253 
configDialog(QWidget * parent)254 void KTextEditor::EditorPrivate::configDialog(QWidget *parent)
255 {
256     QPointer<KPageDialog> kd = new KPageDialog(parent);
257 
258     kd->setWindowTitle(i18n("Configure"));
259     kd->setFaceType(KPageDialog::List);
260     kd->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply | QDialogButtonBox::Help);
261 
262     QList<KTextEditor::ConfigPage *> editorPages;
263     editorPages.reserve(configPages());
264     for (int i = 0; i < configPages(); ++i) {
265         QFrame *page = new QFrame();
266         KTextEditor::ConfigPage *cp = configPage(i, page);
267 
268         KPageWidgetItem *item = kd->addPage(page, cp->name());
269         item->setHeader(cp->fullName());
270         item->setIcon(cp->icon());
271 
272         QVBoxLayout *topLayout = new QVBoxLayout(page);
273         topLayout->setContentsMargins(0, 0, 0, 0);
274 
275         connect(kd->button(QDialogButtonBox::Apply), &QPushButton::clicked, cp, &KTextEditor::ConfigPage::apply);
276         topLayout->addWidget(cp);
277         editorPages.append(cp);
278     }
279 
280     if (kd->exec() && kd) {
281         KateGlobalConfig::global()->configStart();
282         KateDocumentConfig::global()->configStart();
283         KateViewConfig::global()->configStart();
284         KateRendererConfig::global()->configStart();
285 
286         for (int i = 0; i < editorPages.count(); ++i) {
287             editorPages.at(i)->apply();
288         }
289 
290         KateGlobalConfig::global()->configEnd();
291         KateDocumentConfig::global()->configEnd();
292         KateViewConfig::global()->configEnd();
293         KateRendererConfig::global()->configEnd();
294     }
295 
296     delete kd;
297 }
298 
configPages() const299 int KTextEditor::EditorPrivate::configPages() const
300 {
301     return 4;
302 }
303 
configPage(int number,QWidget * parent)304 KTextEditor::ConfigPage *KTextEditor::EditorPrivate::configPage(int number, QWidget *parent)
305 {
306     switch (number) {
307     case 0:
308         return new KateViewDefaultsConfig(parent);
309 
310     case 1:
311         return new KateThemeConfigPage(parent);
312 
313     case 2:
314         return new KateEditConfigTab(parent);
315 
316     case 3:
317         return new KateSaveConfigTab(parent);
318 
319     default:
320         break;
321     }
322 
323     return nullptr;
324 }
325 
326 /**
327  * Cleanup the KTextEditor::EditorPrivate during QCoreApplication shutdown
328  */
cleanupGlobal()329 static void cleanupGlobal()
330 {
331     // delete if there
332     delete KTextEditor::EditorPrivate::self();
333 }
334 
self()335 KTextEditor::EditorPrivate *KTextEditor::EditorPrivate::self()
336 {
337     // remember the static instance in a QPointer
338     static bool inited = false;
339     static QPointer<KTextEditor::EditorPrivate> staticInstance;
340 
341     // just return it, if already inited
342     if (inited) {
343         return staticInstance.data();
344     }
345 
346     // start init process
347     inited = true;
348 
349     // now create the object and store it
350     new KTextEditor::EditorPrivate(staticInstance);
351 
352     // register cleanup
353     // let use be deleted during QCoreApplication shutdown
354     qAddPostRoutine(cleanupGlobal);
355 
356     // return instance
357     return staticInstance.data();
358 }
359 
registerDocument(KTextEditor::DocumentPrivate * doc)360 void KTextEditor::EditorPrivate::registerDocument(KTextEditor::DocumentPrivate *doc)
361 {
362     Q_ASSERT(!m_documents.contains(doc));
363     m_documents.insert(doc, doc);
364 }
365 
deregisterDocument(KTextEditor::DocumentPrivate * doc)366 void KTextEditor::EditorPrivate::deregisterDocument(KTextEditor::DocumentPrivate *doc)
367 {
368     Q_ASSERT(m_documents.contains(doc));
369     m_documents.remove(doc);
370 }
371 
registerView(KTextEditor::ViewPrivate * view)372 void KTextEditor::EditorPrivate::registerView(KTextEditor::ViewPrivate *view)
373 {
374     Q_ASSERT(!m_views.contains(view));
375     m_views.insert(view);
376 }
377 
deregisterView(KTextEditor::ViewPrivate * view)378 void KTextEditor::EditorPrivate::deregisterView(KTextEditor::ViewPrivate *view)
379 {
380     Q_ASSERT(m_views.contains(view));
381     m_views.remove(view);
382 }
383 
queryCommand(const QString & cmd) const384 KTextEditor::Command *KTextEditor::EditorPrivate::queryCommand(const QString &cmd) const
385 {
386     return m_cmdManager->queryCommand(cmd);
387 }
388 
commands() const389 QList<KTextEditor::Command *> KTextEditor::EditorPrivate::commands() const
390 {
391     return m_cmdManager->commands();
392 }
393 
commandList() const394 QStringList KTextEditor::EditorPrivate::commandList() const
395 {
396     return m_cmdManager->commandList();
397 }
398 
variableExpansionManager()399 KateVariableExpansionManager *KTextEditor::EditorPrivate::variableExpansionManager()
400 {
401     return m_variableExpansionManager;
402 }
403 
updateColorPalette()404 void KTextEditor::EditorPrivate::updateColorPalette()
405 {
406     // reload the global schema (triggers reload for every view as well)
407     // might trigger selection of better matching theme for new palette
408     m_rendererConfig->reloadSchema();
409 
410     // force full update of all view caches and colors
411     m_rendererConfig->updateConfig();
412 }
413 
copyToClipboard(const QString & text)414 void KTextEditor::EditorPrivate::copyToClipboard(const QString &text)
415 {
416     // empty => nop
417     if (text.isEmpty()) {
418         return;
419     }
420 
421     // move to clipboard
422     QApplication::clipboard()->setText(text, QClipboard::Clipboard);
423 
424     // LRU, kill potential duplicated, move new entry to top
425     // cut after 10 entries
426     m_clipboardHistory.removeOne(text);
427     m_clipboardHistory.prepend(text);
428     if (m_clipboardHistory.size() > 10) {
429         m_clipboardHistory.removeLast();
430     }
431 
432     // notify about change
433     Q_EMIT clipboardHistoryChanged();
434 }
435 
eventFilter(QObject * obj,QEvent * event)436 bool KTextEditor::EditorPrivate::eventFilter(QObject *obj, QEvent *event)
437 {
438     if (obj == qApp && event->type() == QEvent::ApplicationPaletteChange) {
439         // only update the color once for the event that belongs to the qApp
440         updateColorPalette();
441     }
442 
443     return false; // always continue processing
444 }
445 
searchHistoryModel()446 QStringListModel *KTextEditor::EditorPrivate::searchHistoryModel()
447 {
448     if (!m_searchHistoryModel) {
449         KConfigGroup cg(KSharedConfig::openConfig(), "KTextEditor::Search");
450         const QStringList history = cg.readEntry(QStringLiteral("Search History"), QStringList());
451         m_searchHistoryModel = new QStringListModel(history, this);
452     }
453     return m_searchHistoryModel;
454 }
455 
replaceHistoryModel()456 QStringListModel *KTextEditor::EditorPrivate::replaceHistoryModel()
457 {
458     if (!m_replaceHistoryModel) {
459         KConfigGroup cg(KSharedConfig::openConfig(), "KTextEditor::Search");
460         const QStringList history = cg.readEntry(QStringLiteral("Replace History"), QStringList());
461         m_replaceHistoryModel = new QStringListModel(history, this);
462     }
463     return m_replaceHistoryModel;
464 }
465 
saveSearchReplaceHistoryModels()466 void KTextEditor::EditorPrivate::saveSearchReplaceHistoryModels()
467 {
468     KConfigGroup cg(KSharedConfig::openConfig(), "KTextEditor::Search");
469     if (m_searchHistoryModel) {
470         cg.writeEntry(QStringLiteral("Search History"), m_searchHistoryModel->stringList());
471     }
472     if (m_replaceHistoryModel) {
473         cg.writeEntry(QStringLiteral("Replace History"), m_replaceHistoryModel->stringList());
474     }
475 }
476 
config()477 KSharedConfigPtr KTextEditor::EditorPrivate::config()
478 {
479     // use dummy config for unit tests!
480     if (KTextEditor::EditorPrivate::unitTestMode()) {
481         return KSharedConfig::openConfig(QStringLiteral("katepartrc-unittest"), KConfig::SimpleConfig, QStandardPaths::TempLocation);
482     }
483 
484     // else: use application configuration, but try to transfer global settings on first use
485     auto applicationConfig = KSharedConfig::openConfig();
486     if (!KConfigGroup(applicationConfig, QStringLiteral("KTextEditor Editor")).exists()) {
487         auto globalConfig = KSharedConfig::openConfig(QStringLiteral("katepartrc"));
488         for (const auto &group : {QStringLiteral("Editor"), QStringLiteral("Document"), QStringLiteral("View"), QStringLiteral("Renderer")}) {
489             KConfigGroup origin(globalConfig, group);
490             KConfigGroup destination(applicationConfig, QStringLiteral("KTextEditor ") + group);
491             origin.copyTo(&destination);
492         }
493     }
494     return applicationConfig;
495 }
496 
triggerConfigChanged()497 void KTextEditor::EditorPrivate::triggerConfigChanged()
498 {
499     // trigger delayed emission, will collapse multiple events to one signal emission
500     m_configWasChanged = true;
501     QTimer::singleShot(0, this, &KTextEditor::EditorPrivate::emitConfigChanged);
502 }
503 
emitConfigChanged()504 void KTextEditor::EditorPrivate::emitConfigChanged()
505 {
506     // emit only once, if still needed
507     if (m_configWasChanged) {
508         m_configWasChanged = false;
509         Q_EMIT configChanged(this);
510     }
511 }
512