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