diff --git a/src/dialogs/editconfigwidget.ui b/src/dialogs/editconfigwidget.ui
index ad6bc7ad..424711ca 100644
--- a/src/dialogs/editconfigwidget.ui
+++ b/src/dialogs/editconfigwidget.ui
@@ -1,212 +1,246 @@
EditConfigWidget
0
0
- 459
- 458
+ 545
+ 566
-
-
- 0
-
-
- 0
-
-
- 0
-
-
- 0
-
+
-
Static Word Wrap
-
<p>Automatically start a new line of text when the current line exceeds the length specified by the <b>Wrap words at:</b> option.</p><p>This option does not wrap existing lines of text - use the <b>Apply Static Word Wrap</b> option in the <b>Tools</b> menu for that purpose.</p><p>If you want lines to be <i>visually wrapped</i> instead, according to the width of the view, enable <b>Dynamic Word Wrap</b> in the <b>Appearance</b> config page.</p>
Enable static &word wrap
-
<p>If this option is checked, a vertical line will be drawn at the word wrap column as defined in the <strong>Editing</strong> properties.</p>
Show static word wra&p marker
-
0
0
0
0
-
Wra&p words at:
sbWordWrap
-
If the Word Wrap option is selected this entry determines the length (in characters) at which the editor will automatically start a new line.
20
200
76
-
Qt::Horizontal
1
0
-
Input Mode
-
Default input mode:
-
-
Qt::Horizontal
40
20
-
Auto Brackets
-
+
-
Enable automatic brackets
+ -
+
+
-
+
+
+ WhatsThisCursor
+
+
+
+
+
+ When there is some text selected will theses chars add in a way "Auto Bracket" do
+
+
+ Chars to enclose a selection:
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
-
Copy and Paste
-
Move selected text by drag and drop
-
Copy/Cut the current line if no selection
-
Paste by mouse at cursor position
-
Qt::Vertical
- 0
+ 8
1
KPluralHandlingSpinBox
QSpinBox
diff --git a/src/dialogs/katedialogs.cpp b/src/dialogs/katedialogs.cpp
index 56963d2f..26fdb108 100644
--- a/src/dialogs/katedialogs.cpp
+++ b/src/dialogs/katedialogs.cpp
@@ -1,1393 +1,1432 @@
/* This file is part of the KDE libraries
Copyright (C) 2002, 2003 Anders Lund
Copyright (C) 2003 Christoph Cullmann
Copyright (C) 2001 Joseph Wenninger
Copyright (C) 2006 Dominik Haumann
Copyright (C) 2007 Mirko Stocker
Copyright (C) 2009 Michel Ludwig
Copyright (C) 2009 Erlend Hamberg
Based on work of:
Copyright (C) 1999 Jochen Wilhelmy
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
//BEGIN Includes
#include "katedialogs.h"
#include
#include
#include "kateautoindent.h"
#include "katebuffer.h"
#include "kateconfig.h"
#include "katedocument.h"
#include "kateglobal.h"
#include "kateschema.h"
#include "katemodeconfigpage.h"
#include "kateview.h"
#include "spellcheck/spellcheck.h"
// auto generated ui files
#include "ui_textareaappearanceconfigwidget.h"
#include "ui_bordersappearanceconfigwidget.h"
#include "ui_navigationconfigwidget.h"
#include "ui_editconfigwidget.h"
#include "ui_indentationconfigwidget.h"
#include "ui_completionconfigtab.h"
#include "ui_opensaveconfigwidget.h"
#include "ui_opensaveconfigadvwidget.h"
#include "ui_spellcheckconfigwidget.h"
#include
#include
#include
#include
#include
#include
#include "katepartdebug.h"
#include "kateabstractinputmodefactory.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//END
//BEGIN KateIndentConfigTab
KateIndentConfigTab::KateIndentConfigTab(QWidget *parent)
: KateConfigPage(parent)
{
// This will let us have more separation between this page and
// the QTabWidget edge (ereslibre)
QVBoxLayout *layout = new QVBoxLayout;
QWidget *newWidget = new QWidget(this);
ui = new Ui::IndentationConfigWidget();
ui->setupUi(newWidget);
ui->cmbMode->addItems(KateAutoIndent::listModes());
// FIXME Give ui->label a more descriptive name, it's these "More..." info about tab key action
ui->label->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard);
connect(ui->label, &QLabel::linkActivated, this, &KateIndentConfigTab::showWhatsThis);
// "What's This?" help can be found in the ui file
reload();
observeChanges(ui->chkBackspaceUnindents);
observeChanges(ui->chkIndentPaste);
observeChanges(ui->chkKeepExtraSpaces);
observeChanges(ui->cmbMode);
observeChanges(ui->rbIndentMixed);
observeChanges(ui->rbIndentWithSpaces);
observeChanges(ui->rbIndentWithTabs);
connect(ui->rbIndentWithTabs, &QAbstractButton::toggled, ui->sbIndentWidth, &QWidget::setDisabled);
connect(ui->rbIndentWithTabs, &QAbstractButton::toggled, this, &KateIndentConfigTab::slotChanged); // FIXME See slot below
observeChanges(ui->rbTabAdvances);
observeChanges(ui->rbTabIndents);
observeChanges(ui->rbTabSmart);
observeChanges(ui->sbIndentWidth);
observeChanges(ui->sbTabWidth);
layout->addWidget(newWidget);
setLayout(layout);
}
KateIndentConfigTab::~KateIndentConfigTab()
{
delete ui;
}
void KateIndentConfigTab::slotChanged()
{
// FIXME Make it working without this quirk
// When the value is not copied it will silently set back to "Tabs & Spaces"
if (ui->rbIndentWithTabs->isChecked()) {
ui->sbIndentWidth->setValue(ui->sbTabWidth->value());
}
}
// NOTE Should we have more use of such info stuff, consider to make it part
// of KateConfigPage and add a similar function like observeChanges(..)
void KateIndentConfigTab::showWhatsThis(const QString &text)
{
QWhatsThis::showText(QCursor::pos(), text);
}
void KateIndentConfigTab::apply()
{
// nothing changed, no need to apply stuff
if (!hasChanged()) {
return;
}
m_changed = false;
KateDocumentConfig::global()->configStart();
KateDocumentConfig::global()->setBackspaceIndents(ui->chkBackspaceUnindents->isChecked());
KateDocumentConfig::global()->setIndentPastedText(ui->chkIndentPaste->isChecked());
KateDocumentConfig::global()->setIndentationMode(KateAutoIndent::modeName(ui->cmbMode->currentIndex()));
KateDocumentConfig::global()->setIndentationWidth(ui->sbIndentWidth->value());
KateDocumentConfig::global()->setKeepExtraSpaces(ui->chkKeepExtraSpaces->isChecked());
KateDocumentConfig::global()->setReplaceTabsDyn(ui->rbIndentWithSpaces->isChecked());
KateDocumentConfig::global()->setTabWidth(ui->sbTabWidth->value());
if (ui->rbTabAdvances->isChecked()) {
KateDocumentConfig::global()->setTabHandling(KateDocumentConfig::tabInsertsTab);
} else if (ui->rbTabIndents->isChecked()) {
KateDocumentConfig::global()->setTabHandling(KateDocumentConfig::tabIndents);
} else {
KateDocumentConfig::global()->setTabHandling(KateDocumentConfig::tabSmart);
}
KateDocumentConfig::global()->configEnd();
}
void KateIndentConfigTab::reload()
{
ui->chkBackspaceUnindents->setChecked(KateDocumentConfig::global()->backspaceIndents());
ui->chkIndentPaste->setChecked(KateDocumentConfig::global()->indentPastedText());
ui->chkKeepExtraSpaces->setChecked(KateDocumentConfig::global()->keepExtraSpaces());
ui->sbIndentWidth->setSuffix(ki18np(" character", " characters"));
ui->sbIndentWidth->setValue(KateDocumentConfig::global()->indentationWidth());
ui->sbTabWidth->setSuffix(ki18np(" character", " characters"));
ui->sbTabWidth->setValue(KateDocumentConfig::global()->tabWidth());
ui->rbTabAdvances->setChecked(KateDocumentConfig::global()->tabHandling() == KateDocumentConfig::tabInsertsTab);
ui->rbTabIndents->setChecked(KateDocumentConfig::global()->tabHandling() == KateDocumentConfig::tabIndents);
ui->rbTabSmart->setChecked(KateDocumentConfig::global()->tabHandling() == KateDocumentConfig::tabSmart);
ui->cmbMode->setCurrentIndex(KateAutoIndent::modeNumber(KateDocumentConfig::global()->indentationMode()));
if (KateDocumentConfig::global()->replaceTabsDyn()) {
ui->rbIndentWithSpaces->setChecked(true);
} else {
if (KateDocumentConfig::global()->indentationWidth() == KateDocumentConfig::global()->tabWidth()) {
ui->rbIndentWithTabs->setChecked(true);
} else {
ui->rbIndentMixed->setChecked(true);
}
}
ui->sbIndentWidth->setEnabled(!ui->rbIndentWithTabs->isChecked());
}
QString KateIndentConfigTab::name() const
{
return i18n("Indentation");
}
//END KateIndentConfigTab
//BEGIN KateCompletionConfigTab
KateCompletionConfigTab::KateCompletionConfigTab(QWidget *parent)
: KateConfigPage(parent)
{
// This will let us have more separation between this page and
// the QTabWidget edge (ereslibre)
QVBoxLayout *layout = new QVBoxLayout;
QWidget *newWidget = new QWidget(this);
ui = new Ui::CompletionConfigTab();
ui->setupUi(newWidget);
// "What's This?" help can be found in the ui file
reload();
observeChanges(ui->chkAutoCompletionEnabled);
observeChanges(ui->gbKeywordCompletion);
observeChanges(ui->gbWordCompletion);
observeChanges(ui->minimalWordLength);
observeChanges(ui->removeTail);
layout->addWidget(newWidget);
setLayout(layout);
}
KateCompletionConfigTab::~KateCompletionConfigTab()
{
delete ui;
}
void KateCompletionConfigTab::showWhatsThis(const QString &text) // NOTE Not used atm, remove? See also KateIndentConfigTab::showWhatsThis
{
QWhatsThis::showText(QCursor::pos(), text);
}
void KateCompletionConfigTab::apply()
{
// nothing changed, no need to apply stuff
if (!hasChanged()) {
return;
}
m_changed = false;
KateViewConfig::global()->configStart();
KateViewConfig::global()->setValue(KateViewConfig::AutomaticCompletionInvocation, ui->chkAutoCompletionEnabled->isChecked());
KateViewConfig::global()->setValue(KateViewConfig::KeywordCompletion, ui->gbKeywordCompletion->isChecked());
KateViewConfig::global()->setValue(KateViewConfig::WordCompletion, ui->gbWordCompletion->isChecked());
KateViewConfig::global()->setValue(KateViewConfig::WordCompletionMinimalWordLength, ui->minimalWordLength->value());
KateViewConfig::global()->setValue(KateViewConfig::WordCompletionRemoveTail, ui->removeTail->isChecked());
KateViewConfig::global()->configEnd();
}
void KateCompletionConfigTab::reload()
{
ui->chkAutoCompletionEnabled->setChecked(KateViewConfig::global()->automaticCompletionInvocation());
ui->gbKeywordCompletion->setChecked(KateViewConfig::global()->keywordCompletion());
ui->gbWordCompletion->setChecked(KateViewConfig::global()->wordCompletion());
ui->minimalWordLength->setValue(KateViewConfig::global()->wordCompletionMinimalWordLength());
ui->removeTail->setChecked(KateViewConfig::global()->wordCompletionRemoveTail());
}
QString KateCompletionConfigTab::name() const
{
return i18n("Auto Completion");
}
//END KateCompletionConfigTab
//BEGIN KateSpellCheckConfigTab
KateSpellCheckConfigTab::KateSpellCheckConfigTab(QWidget *parent)
: KateConfigPage(parent)
{
// This will let us have more separation between this page and
// the QTabWidget edge (ereslibre)
QVBoxLayout *layout = new QVBoxLayout;
QWidget *newWidget = new QWidget(this);
ui = new Ui::SpellCheckConfigWidget();
ui->setupUi(newWidget);
// "What's This?" help can be found in the ui file
reload();
m_sonnetConfigWidget = new Sonnet::ConfigWidget(this);
connect(m_sonnetConfigWidget, &Sonnet::ConfigWidget::configChanged, this, &KateSpellCheckConfigTab::slotChanged);
layout->addWidget(m_sonnetConfigWidget);
layout->addWidget(newWidget);
setLayout(layout);
}
KateSpellCheckConfigTab::~KateSpellCheckConfigTab()
{
delete ui;
}
void KateSpellCheckConfigTab::showWhatsThis(const QString &text) // NOTE Not used atm, remove? See also KateIndentConfigTab::showWhatsThis
{
QWhatsThis::showText(QCursor::pos(), text);
}
void KateSpellCheckConfigTab::apply()
{
if (!hasChanged()) {
// nothing changed, no need to apply stuff
return;
}
m_changed = false;
// WARNING: this is slightly hackish, but it's currently the only way to
// do it, see also the KTextEdit class
KateDocumentConfig::global()->configStart();
m_sonnetConfigWidget->save();
QSettings settings(QStringLiteral("KDE"), QStringLiteral("Sonnet"));
KateDocumentConfig::global()->setOnTheFlySpellCheck(settings.value(QStringLiteral("checkerEnabledByDefault"), false).toBool());
KateDocumentConfig::global()->configEnd();
foreach (KTextEditor::DocumentPrivate *doc, KTextEditor::EditorPrivate::self()->kateDocuments()) {
doc->refreshOnTheFlyCheck();
}
}
void KateSpellCheckConfigTab::reload()
{
// does nothing
}
QString KateSpellCheckConfigTab::name() const
{
return i18n("Spellcheck");
}
//END KateSpellCheckConfigTab
//BEGIN KateNavigationConfigTab
KateNavigationConfigTab::KateNavigationConfigTab(QWidget *parent)
: KateConfigPage(parent)
{
// This will let us having more separation between this page and
// the QTabWidget edge (ereslibre)
QVBoxLayout *layout = new QVBoxLayout;
QWidget *newWidget = new QWidget(this);
ui = new Ui::NavigationConfigWidget();
ui->setupUi(newWidget);
// "What's This?" help can be found in the ui file
reload();
observeChanges(ui->cbTextSelectionMode);
observeChanges(ui->chkBackspaceRemoveComposed);
observeChanges(ui->chkPagingMovesCursor);
observeChanges(ui->chkScrollPastEnd);
observeChanges(ui->chkSmartHome);
observeChanges(ui->sbAutoCenterCursor);
layout->addWidget(newWidget);
setLayout(layout);
}
KateNavigationConfigTab::~KateNavigationConfigTab()
{
delete ui;
}
void KateNavigationConfigTab::apply()
{
// nothing changed, no need to apply stuff
if (!hasChanged()) {
return;
}
m_changed = false;
KateViewConfig::global()->configStart();
KateDocumentConfig::global()->configStart();
KateDocumentConfig::global()->setPageUpDownMovesCursor(ui->chkPagingMovesCursor->isChecked());
KateDocumentConfig::global()->setSmartHome(ui->chkSmartHome->isChecked());
KateViewConfig::global()->setValue(KateViewConfig::AutoCenterLines, ui->sbAutoCenterCursor->value());
KateViewConfig::global()->setValue(KateViewConfig::BackspaceRemoveComposedCharacters, ui->chkBackspaceRemoveComposed->isChecked());
KateViewConfig::global()->setValue(KateViewConfig::PersistentSelection, ui->cbTextSelectionMode->currentIndex() == 1);
KateViewConfig::global()->setValue(KateViewConfig::ScrollPastEnd, ui->chkScrollPastEnd->isChecked());
KateDocumentConfig::global()->configEnd();
KateViewConfig::global()->configEnd();
}
void KateNavigationConfigTab::reload()
{
ui->cbTextSelectionMode->setCurrentIndex(KateViewConfig::global()->persistentSelection() ? 1 : 0);
ui->chkBackspaceRemoveComposed->setChecked(KateViewConfig::global()->backspaceRemoveComposed());
ui->chkPagingMovesCursor->setChecked(KateDocumentConfig::global()->pageUpDownMovesCursor());
ui->chkScrollPastEnd->setChecked(KateViewConfig::global()->scrollPastEnd());
ui->chkSmartHome->setChecked(KateDocumentConfig::global()->smartHome());
ui->sbAutoCenterCursor->setValue(KateViewConfig::global()->autoCenterLines());
}
QString KateNavigationConfigTab::name() const
{
return i18n("Text Navigation");
}
//END KateNavigationConfigTab
//BEGIN KateEditGeneralConfigTab
KateEditGeneralConfigTab::KateEditGeneralConfigTab(QWidget *parent)
: KateConfigPage(parent)
{
QVBoxLayout *layout = new QVBoxLayout;
QWidget *newWidget = new QWidget(this);
ui = new Ui::EditConfigWidget();
ui->setupUi(newWidget);
QList inputModes = KTextEditor::EditorPrivate::self()->inputModeFactories();
Q_FOREACH(KateAbstractInputModeFactory *fact, inputModes) {
ui->cmbInputMode->addItem(fact->name(), static_cast(fact->inputMode()));
}
// "What's This?" Help is in the ui-files
reload();
observeChanges(ui->chkAutoBrackets);
observeChanges(ui->chkMousePasteAtCursorPosition);
observeChanges(ui->chkShowStaticWordWrapMarker);
observeChanges(ui->chkTextDragAndDrop);
observeChanges(ui->chkSmartCopyCut);
observeChanges(ui->chkStaticWordWrap);
+ observeChanges(ui->cmbEncloseSelection);
+ connect(ui->cmbEncloseSelection->lineEdit(), &QLineEdit::editingFinished, [=] {
+ const int index = ui->cmbEncloseSelection->currentIndex();
+ const QString text = ui->cmbEncloseSelection->currentText();
+ // Text removed? Remove item, but don't remove default data!
+ if (index >= KateViewConfig::UserData && text.isEmpty()) {
+ ui->cmbEncloseSelection->removeItem(index);
+ slotChanged();
+
+ // Not already there? Add new item! For what ever reason isn't it's done automatically
+ } else if (ui->cmbEncloseSelection->findText(text) < 0) {
+ ui->cmbEncloseSelection->addItem(text);
+ slotChanged();
+ }
+ ui->cmbEncloseSelection->setCurrentIndex(ui->cmbEncloseSelection->findText(text));
+ });
observeChanges(ui->cmbInputMode);
observeChanges(ui->sbWordWrap);
layout->addWidget(newWidget);
setLayout(layout);
}
KateEditGeneralConfigTab::~KateEditGeneralConfigTab()
{
delete ui;
}
void KateEditGeneralConfigTab::apply()
{
// nothing changed, no need to apply stuff
if (!hasChanged()) {
return;
}
m_changed = false;
KateViewConfig::global()->configStart();
KateDocumentConfig::global()->configStart();
KateDocumentConfig::global()->setWordWrap(ui->chkStaticWordWrap->isChecked());
KateDocumentConfig::global()->setWordWrapAt(ui->sbWordWrap->value());
KateRendererConfig::global()->setWordWrapMarker(ui->chkShowStaticWordWrapMarker->isChecked());
KateViewConfig::global()->setValue(KateViewConfig::AutoBrackets, ui->chkAutoBrackets->isChecked());
+ KateViewConfig::global()->setValue(KateViewConfig::CharsToEncloseSelection, ui->cmbEncloseSelection->currentText());
+ QStringList userLetters; for (int i = KateViewConfig::UserData; i < ui->cmbEncloseSelection->count(); ++i) {
+ userLetters.append(ui->cmbEncloseSelection->itemText(i));
+ }
+ KateViewConfig::global()->setValue(KateViewConfig::UserSetsOfCharsToEncloseSelection, userLetters);
KateViewConfig::global()->setValue(KateViewConfig::InputMode, ui->cmbInputMode->currentData().toInt());
KateViewConfig::global()->setValue(KateViewConfig::MousePasteAtCursorPosition, ui->chkMousePasteAtCursorPosition->isChecked());
KateViewConfig::global()->setValue(KateViewConfig::TextDragAndDrop, ui->chkTextDragAndDrop->isChecked());
KateViewConfig::global()->setValue(KateViewConfig::SmartCopyCut, ui->chkSmartCopyCut->isChecked());
KateDocumentConfig::global()->configEnd();
KateViewConfig::global()->configEnd();
}
void KateEditGeneralConfigTab::reload()
{
ui->chkAutoBrackets->setChecked(KateViewConfig::global()->autoBrackets());
ui->chkMousePasteAtCursorPosition->setChecked(KateViewConfig::global()->mousePasteAtCursorPosition());
ui->chkShowStaticWordWrapMarker->setChecked(KateRendererConfig::global()->wordWrapMarker());
ui->chkTextDragAndDrop->setChecked(KateViewConfig::global()->textDragAndDrop());
ui->chkSmartCopyCut->setChecked(KateViewConfig::global()->smartCopyCut());
ui->chkStaticWordWrap->setChecked(KateDocumentConfig::global()->wordWrap());
ui->sbWordWrap->setSuffix(ki18ncp("Wrap words at (value is at 20 or larger)", " character", " characters"));
ui->sbWordWrap->setValue(KateDocumentConfig::global()->wordWrapAt());
+ ui->cmbEncloseSelection->clear();
+ ui->cmbEncloseSelection->lineEdit()->setClearButtonEnabled(true);
+ ui->cmbEncloseSelection->lineEdit()->setPlaceholderText(QStringLiteral("Feature is not active"));
+ ui->cmbEncloseSelection->addItem(QString(), KateViewConfig::None);
+ ui->cmbEncloseSelection->setItemData(0, i18n("Disable Feature"), Qt::ToolTipRole);
+ ui->cmbEncloseSelection->addItem(QStringLiteral("`*_~"), KateViewConfig::MarkDown);
+ ui->cmbEncloseSelection->setItemData(1, i18n("May handy with Markdown"), Qt::ToolTipRole);
+ ui->cmbEncloseSelection->addItem(QStringLiteral("<>(){}[]"), KateViewConfig::MirrorChar);
+ ui->cmbEncloseSelection->setItemData(2, i18n("Mirror characters, similar but not exact like auto brackets"), Qt::ToolTipRole);
+ ui->cmbEncloseSelection->addItem(QStringLiteral("´`_.:|#@~*!?$%/=,;-+^°§&"), KateViewConfig::NonLetters);
+ ui->cmbEncloseSelection->setItemData(3, i18n("Non letter character"), Qt::ToolTipRole);
+ const QStringList userLetters = KateViewConfig::global()->value(KateViewConfig::UserSetsOfCharsToEncloseSelection).toStringList();
+ for (int i = 0; i < userLetters.size(); ++i) {
+ ui->cmbEncloseSelection->addItem(userLetters.at(i), KateViewConfig::UserData + i);
+ }
+ ui->cmbEncloseSelection->setCurrentIndex(ui->cmbEncloseSelection->findText(KateViewConfig::global()->charsToEncloseSelection()));
+
const int id = static_cast(KateViewConfig::global()->inputMode());
ui->cmbInputMode->setCurrentIndex(ui->cmbInputMode->findData(id));
+
}
QString KateEditGeneralConfigTab::name() const
{
return i18n("General");
}
//END KateEditGeneralConfigTab
//BEGIN KateEditConfigTab
KateEditConfigTab::KateEditConfigTab(QWidget *parent)
: KateConfigPage(parent)
, editConfigTab(new KateEditGeneralConfigTab(this))
, navigationConfigTab(new KateNavigationConfigTab(this))
, indentConfigTab(new KateIndentConfigTab(this))
, completionConfigTab(new KateCompletionConfigTab(this))
, spellCheckConfigTab(new KateSpellCheckConfigTab(this))
{
QVBoxLayout *layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
QTabWidget *tabWidget = new QTabWidget(this);
// add all tabs
tabWidget->insertTab(0, editConfigTab, editConfigTab->name());
tabWidget->insertTab(1, navigationConfigTab, navigationConfigTab->name());
tabWidget->insertTab(2, indentConfigTab, indentConfigTab->name());
tabWidget->insertTab(3, completionConfigTab, completionConfigTab->name());
tabWidget->insertTab(4, spellCheckConfigTab, spellCheckConfigTab->name());
observeChanges(editConfigTab);
observeChanges(navigationConfigTab);
observeChanges(indentConfigTab);
observeChanges(completionConfigTab);
observeChanges(spellCheckConfigTab);
int i = tabWidget->count();
Q_FOREACH(KateAbstractInputModeFactory *factory, KTextEditor::EditorPrivate::self()->inputModeFactories()) {
KateConfigPage *tab = factory->createConfigPage(this);
if (tab) {
m_inputModeConfigTabs << tab;
tabWidget->insertTab(i, tab, tab->name());
observeChanges(tab);
i++;
}
}
layout->addWidget(tabWidget);
setLayout(layout);
}
KateEditConfigTab::~KateEditConfigTab()
{
qDeleteAll(m_inputModeConfigTabs);
}
void KateEditConfigTab::apply()
{
// try to update the rest of tabs
editConfigTab->apply();
navigationConfigTab->apply();
indentConfigTab->apply();
completionConfigTab->apply();
spellCheckConfigTab->apply();
Q_FOREACH(KateConfigPage *tab, m_inputModeConfigTabs) {
tab->apply();
}
}
void KateEditConfigTab::reload()
{
editConfigTab->reload();
navigationConfigTab->reload();
indentConfigTab->reload();
completionConfigTab->reload();
spellCheckConfigTab->reload();
Q_FOREACH(KateConfigPage *tab, m_inputModeConfigTabs) {
tab->reload();
}
}
void KateEditConfigTab::reset()
{
editConfigTab->reset();
navigationConfigTab->reset();
indentConfigTab->reset();
completionConfigTab->reset();
spellCheckConfigTab->reset();
Q_FOREACH(KateConfigPage *tab, m_inputModeConfigTabs) {
tab->reset();
}
}
void KateEditConfigTab::defaults()
{
editConfigTab->defaults();
navigationConfigTab->defaults();
indentConfigTab->defaults();
completionConfigTab->defaults();
spellCheckConfigTab->defaults();
Q_FOREACH(KateConfigPage *tab, m_inputModeConfigTabs) {
tab->defaults();
}
}
QString KateEditConfigTab::name() const
{
return i18n("Editing");
}
QString KateEditConfigTab::fullName() const
{
return i18n("Editing Options");
}
QIcon KateEditConfigTab::icon() const
{
return QIcon::fromTheme(QStringLiteral("accessories-text-editor"));
}
//END KateEditConfigTab
//BEGIN KateViewDefaultsConfig
KateViewDefaultsConfig::KateViewDefaultsConfig(QWidget *parent)
: KateConfigPage(parent)
, textareaUi(new Ui::TextareaAppearanceConfigWidget())
, bordersUi(new Ui::BordersAppearanceConfigWidget())
{
QLayout *layout = new QVBoxLayout(this);
QTabWidget *tabWidget = new QTabWidget(this);
layout->addWidget(tabWidget);
layout->setContentsMargins(0, 0, 0, 0);
QWidget *textareaTab = new QWidget(tabWidget);
textareaUi->setupUi(textareaTab);
tabWidget->addTab(textareaTab, i18n("General"));
QWidget *bordersTab = new QWidget(tabWidget);
bordersUi->setupUi(bordersTab);
tabWidget->addTab(bordersTab, i18n("Borders"));
textareaUi->cmbDynamicWordWrapIndicator->addItem(i18n("Off"));
textareaUi->cmbDynamicWordWrapIndicator->addItem(i18n("Follow Line Numbers"));
textareaUi->cmbDynamicWordWrapIndicator->addItem(i18n("Always On"));
// "What's This?" help is in the ui-file
reload();
observeChanges(textareaUi->chkAnimateBracketMatching);
observeChanges(textareaUi->chkDynWrapAtStaticMarker);
observeChanges(textareaUi->chkFoldFirstLine);
observeChanges(textareaUi->chkShowIndentationLines);
observeChanges(textareaUi->chkShowLineCount);
observeChanges(textareaUi->chkShowTabs);
observeChanges(textareaUi->chkShowWholeBracketExpression);
observeChanges(textareaUi->chkShowWordCount);
observeChanges(textareaUi->cmbDynamicWordWrapIndicator);
observeChanges(textareaUi->gbWordWrap);
observeChanges(textareaUi->sbDynamicWordWrapDepth);
observeChanges(textareaUi->sliSetMarkerSize);
observeChanges(textareaUi->spacesComboBox);
observeChanges(bordersUi->chkIconBorder);
observeChanges(bordersUi->chkLineNumbers);
observeChanges(bordersUi->chkScrollbarMarks);
observeChanges(bordersUi->chkScrollbarMiniMap);
observeChanges(bordersUi->chkScrollbarMiniMapAll);
bordersUi->chkScrollbarMiniMapAll->hide(); // this is temporary until the feature is done
observeChanges(bordersUi->chkScrollbarPreview);
observeChanges(bordersUi->chkShowFoldingMarkers);
observeChanges(bordersUi->chkShowFoldingPreview);
observeChanges(bordersUi->chkShowLineModification);
observeChanges(bordersUi->cmbShowScrollbars);
observeChanges(bordersUi->rbSortBookmarksByCreation);
observeChanges(bordersUi->rbSortBookmarksByPosition);
observeChanges(bordersUi->spBoxMiniMapWidth);
}
KateViewDefaultsConfig::~KateViewDefaultsConfig()
{
delete bordersUi;
delete textareaUi;
}
void KateViewDefaultsConfig::apply()
{
// nothing changed, no need to apply stuff
if (!hasChanged()) {
return;
}
m_changed = false;
KateViewConfig::global()->configStart();
KateRendererConfig::global()->configStart();
KateDocumentConfig::global()->setMarkerSize(textareaUi->sliSetMarkerSize->value());
KateDocumentConfig::global()->setShowSpaces(KateDocumentConfig::WhitespaceRendering(textareaUi->spacesComboBox->currentIndex()));
KateDocumentConfig::global()->setShowTabs(textareaUi->chkShowTabs->isChecked());
KateRendererConfig::global()->setAnimateBracketMatching(textareaUi->chkAnimateBracketMatching->isChecked());
KateRendererConfig::global()->setShowIndentationLines(textareaUi->chkShowIndentationLines->isChecked());
KateRendererConfig::global()->setShowWholeBracketExpression(textareaUi->chkShowWholeBracketExpression->isChecked());
KateViewConfig::global()->setDynWordWrap(textareaUi->gbWordWrap->isChecked());
KateViewConfig::global()->setShowWordCount(textareaUi->chkShowWordCount->isChecked());
KateViewConfig::global()->setValue(KateViewConfig::BookmarkSorting, bordersUi->rbSortBookmarksByPosition->isChecked() ? 0 : 1);
KateViewConfig::global()->setValue(KateViewConfig::DynWordWrapAlignIndent, textareaUi->sbDynamicWordWrapDepth->value());
KateViewConfig::global()->setValue(KateViewConfig::DynWordWrapIndicators, textareaUi->cmbDynamicWordWrapIndicator->currentIndex());
KateViewConfig::global()->setValue(KateViewConfig::DynWrapAtStaticMarker, textareaUi->chkDynWrapAtStaticMarker->isChecked());
KateViewConfig::global()->setValue(KateViewConfig::FoldFirstLine, textareaUi->chkFoldFirstLine->isChecked());
KateViewConfig::global()->setValue(KateViewConfig::ScrollBarMiniMapWidth, bordersUi->spBoxMiniMapWidth->value());
KateViewConfig::global()->setValue(KateViewConfig::ShowFoldingBar, bordersUi->chkShowFoldingMarkers->isChecked());
KateViewConfig::global()->setValue(KateViewConfig::ShowFoldingPreview, bordersUi->chkShowFoldingPreview->isChecked());
KateViewConfig::global()->setValue(KateViewConfig::ShowIconBar, bordersUi->chkIconBorder->isChecked());
KateViewConfig::global()->setValue(KateViewConfig::ShowLineCount, textareaUi->chkShowLineCount->isChecked());
KateViewConfig::global()->setValue(KateViewConfig::ShowLineModification, bordersUi->chkShowLineModification->isChecked());
KateViewConfig::global()->setValue(KateViewConfig::ShowLineNumbers, bordersUi->chkLineNumbers->isChecked());
KateViewConfig::global()->setValue(KateViewConfig::ShowScrollBarMarks, bordersUi->chkScrollbarMarks->isChecked());
KateViewConfig::global()->setValue(KateViewConfig::ShowScrollBarMiniMap, bordersUi->chkScrollbarMiniMap->isChecked());
KateViewConfig::global()->setValue(KateViewConfig::ShowScrollBarMiniMapAll, bordersUi->chkScrollbarMiniMapAll->isChecked());
KateViewConfig::global()->setValue(KateViewConfig::ShowScrollBarPreview, bordersUi->chkScrollbarPreview->isChecked());
KateViewConfig::global()->setValue(KateViewConfig::ShowScrollbars, bordersUi->cmbShowScrollbars->currentIndex());
KateRendererConfig::global()->configEnd();
KateViewConfig::global()->configEnd();
}
void KateViewDefaultsConfig::reload()
{
bordersUi->chkIconBorder->setChecked(KateViewConfig::global()->iconBar());
bordersUi->chkLineNumbers->setChecked(KateViewConfig::global()->lineNumbers());
bordersUi->chkScrollbarMarks->setChecked(KateViewConfig::global()->scrollBarMarks());
bordersUi->chkScrollbarMiniMap->setChecked(KateViewConfig::global()->scrollBarMiniMap());
bordersUi->chkScrollbarMiniMapAll->setChecked(KateViewConfig::global()->scrollBarMiniMapAll());
bordersUi->chkScrollbarPreview->setChecked(KateViewConfig::global()->scrollBarPreview());
bordersUi->chkShowFoldingMarkers->setChecked(KateViewConfig::global()->foldingBar());
bordersUi->chkShowFoldingPreview->setChecked(KateViewConfig::global()->foldingPreview());
bordersUi->chkShowLineModification->setChecked(KateViewConfig::global()->lineModification());
bordersUi->cmbShowScrollbars->setCurrentIndex(KateViewConfig::global()->showScrollbars());
bordersUi->rbSortBookmarksByCreation->setChecked(KateViewConfig::global()->bookmarkSort() == 1);
bordersUi->rbSortBookmarksByPosition->setChecked(KateViewConfig::global()->bookmarkSort() == 0);
bordersUi->spBoxMiniMapWidth->setValue(KateViewConfig::global()->scrollBarMiniMapWidth());
textareaUi->chkAnimateBracketMatching->setChecked(KateRendererConfig::global()->animateBracketMatching());
textareaUi->chkDynWrapAtStaticMarker->setChecked(KateViewConfig::global()->dynWrapAtStaticMarker());
textareaUi->chkFoldFirstLine->setChecked(KateViewConfig::global()->foldFirstLine());
textareaUi->chkShowIndentationLines->setChecked(KateRendererConfig::global()->showIndentationLines());
textareaUi->chkShowLineCount->setChecked(KateViewConfig::global()->showLineCount());
textareaUi->chkShowTabs->setChecked(KateDocumentConfig::global()->showTabs());
textareaUi->chkShowWholeBracketExpression->setChecked(KateRendererConfig::global()->showWholeBracketExpression());
textareaUi->chkShowWordCount->setChecked(KateViewConfig::global()->showWordCount());
textareaUi->cmbDynamicWordWrapIndicator->setCurrentIndex(KateViewConfig::global()->dynWordWrapIndicators());
textareaUi->gbWordWrap->setChecked(KateViewConfig::global()->dynWordWrap());
textareaUi->sbDynamicWordWrapDepth->setValue(KateViewConfig::global()->dynWordWrapAlignIndent());
textareaUi->sliSetMarkerSize->setValue(KateDocumentConfig::global()->markerSize());
textareaUi->spacesComboBox->setCurrentIndex(KateDocumentConfig::global()->showSpaces());
}
void KateViewDefaultsConfig::reset()
{
;
}
void KateViewDefaultsConfig::defaults()
{
;
}
QString KateViewDefaultsConfig::name() const
{
return i18n("Appearance");
}
QString KateViewDefaultsConfig::fullName() const
{
return i18n("Appearance");
}
QIcon KateViewDefaultsConfig::icon() const
{
return QIcon::fromTheme(QStringLiteral("preferences-desktop-theme"));
}
//END KateViewDefaultsConfig
//BEGIN KateSaveConfigTab
KateSaveConfigTab::KateSaveConfigTab(QWidget *parent)
: KateConfigPage(parent)
, modeConfigPage(new ModeConfigPage(this))
{
// FIXME: Is really needed to move all this code below to another class,
// since it is another tab itself on the config dialog. This means we should
// initialize, add and work with as we do with modeConfigPage (ereslibre)
QVBoxLayout *layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
QTabWidget *tabWidget = new QTabWidget(this);
QWidget *tmpWidget = new QWidget(tabWidget);
QVBoxLayout *internalLayout = new QVBoxLayout;
QWidget *newWidget = new QWidget(tabWidget);
ui = new Ui::OpenSaveConfigWidget();
ui->setupUi(newWidget);
QWidget *tmpWidget2 = new QWidget(tabWidget);
QVBoxLayout *internalLayout2 = new QVBoxLayout;
QWidget *newWidget2 = new QWidget(tabWidget);
uiadv = new Ui::OpenSaveConfigAdvWidget();
uiadv->setupUi(newWidget2);
// "What's This?" help can be found in the ui file
reload();
observeChanges(ui->cbRemoveTrailingSpaces);
observeChanges(ui->chkDetectEOL);
observeChanges(ui->chkEnableBOM);
observeChanges(ui->chkNewLineAtEof);
observeChanges(ui->cmbEOL);
observeChanges(ui->cmbEncoding);
observeChanges(ui->cmbEncodingDetection);
observeChanges(ui->cmbEncodingFallback);
observeChanges(ui->lineLengthLimit);
observeChanges(uiadv->chkBackupLocalFiles);
observeChanges(uiadv->chkBackupRemoteFiles);
observeChanges(uiadv->cmbSwapFileMode);
connect(uiadv->cmbSwapFileMode, QOverload::of(&QComboBox::currentIndexChanged), this, &KateSaveConfigTab::swapFileModeChanged);
observeChanges(uiadv->edtBackupPrefix);
observeChanges(uiadv->edtBackupSuffix);
observeChanges(uiadv->kurlSwapDirectory);
observeChanges(uiadv->spbSwapFileSync);
internalLayout->addWidget(newWidget);
tmpWidget->setLayout(internalLayout);
internalLayout2->addWidget(newWidget2);
tmpWidget2->setLayout(internalLayout2);
// add all tabs
tabWidget->insertTab(0, tmpWidget, i18n("General"));
tabWidget->insertTab(1, tmpWidget2, i18n("Advanced"));
tabWidget->insertTab(2, modeConfigPage, modeConfigPage->name());
observeChanges(modeConfigPage);
layout->addWidget(tabWidget);
setLayout(layout);
}
KateSaveConfigTab::~KateSaveConfigTab()
{
delete ui;
}
void KateSaveConfigTab::swapFileModeChanged(int idx)
{
const KateDocumentConfig::SwapFileMode mode = static_cast(idx);
switch (mode) {
case KateDocumentConfig::DisableSwapFile:
uiadv->lblSwapDirectory->setEnabled(false);
uiadv->kurlSwapDirectory->setEnabled(false);
uiadv->lblSwapFileSync->setEnabled(false);
uiadv->spbSwapFileSync->setEnabled(false);
break;
case KateDocumentConfig::EnableSwapFile:
uiadv->lblSwapDirectory->setEnabled(false);
uiadv->kurlSwapDirectory->setEnabled(false);
uiadv->lblSwapFileSync->setEnabled(true);
uiadv->spbSwapFileSync->setEnabled(true);
break;
case KateDocumentConfig::SwapFilePresetDirectory:
uiadv->lblSwapDirectory->setEnabled(true);
uiadv->kurlSwapDirectory->setEnabled(true);
uiadv->lblSwapFileSync->setEnabled(true);
uiadv->spbSwapFileSync->setEnabled(true);
break;
}
}
void KateSaveConfigTab::apply()
{
modeConfigPage->apply();
// nothing changed, no need to apply stuff
if (!hasChanged()) {
return;
}
m_changed = false;
KateGlobalConfig::global()->configStart();
KateDocumentConfig::global()->configStart();
if (uiadv->edtBackupSuffix->text().isEmpty() && uiadv->edtBackupPrefix->text().isEmpty()) {
KMessageBox::information(
this,
i18n("You did not provide a backup suffix or prefix. Using default suffix: '~'"),
i18n("No Backup Suffix or Prefix")
);
uiadv->edtBackupSuffix->setText(QStringLiteral("~"));
}
KateDocumentConfig::global()->setBackupOnSaveLocal(uiadv->chkBackupLocalFiles->isChecked());
KateDocumentConfig::global()->setBackupOnSaveRemote(uiadv->chkBackupRemoteFiles->isChecked());
KateDocumentConfig::global()->setBackupPrefix(uiadv->edtBackupPrefix->text());
KateDocumentConfig::global()->setBackupSuffix(uiadv->edtBackupSuffix->text());
KateDocumentConfig::global()->setSwapFileMode(uiadv->cmbSwapFileMode->currentIndex());
KateDocumentConfig::global()->setSwapDirectory(uiadv->kurlSwapDirectory->url().toLocalFile());
KateDocumentConfig::global()->setSwapSyncInterval(uiadv->spbSwapFileSync->value());
KateDocumentConfig::global()->setRemoveSpaces(ui->cbRemoveTrailingSpaces->currentIndex());
KateDocumentConfig::global()->setNewLineAtEof(ui->chkNewLineAtEof->isChecked());
// set both standard and fallback encoding
KateDocumentConfig::global()->setEncoding(KCharsets::charsets()->encodingForName(ui->cmbEncoding->currentText()));
KateGlobalConfig::global()->setProberType((KEncodingProber::ProberType)ui->cmbEncodingDetection->currentIndex());
KateGlobalConfig::global()->setFallbackEncoding(KCharsets::charsets()->encodingForName(ui->cmbEncodingFallback->currentText()));
KateDocumentConfig::global()->setEol(ui->cmbEOL->currentIndex());
KateDocumentConfig::global()->setAllowEolDetection(ui->chkDetectEOL->isChecked());
KateDocumentConfig::global()->setBom(ui->chkEnableBOM->isChecked());
KateDocumentConfig::global()->setLineLengthLimit(ui->lineLengthLimit->value());
KateDocumentConfig::global()->configEnd();
KateGlobalConfig::global()->configEnd();
}
void KateSaveConfigTab::reload()
{
modeConfigPage->reload();
// encodings
ui->cmbEncoding->clear();
ui->cmbEncodingFallback->clear();
QStringList encodings(KCharsets::charsets()->descriptiveEncodingNames());
int insert = 0;
for (int i = 0; i < encodings.count(); i++) {
bool found = false;
QTextCodec *codecForEnc = KCharsets::charsets()->codecForName(KCharsets::charsets()->encodingForName(encodings[i]), found);
if (found) {
ui->cmbEncoding->addItem(encodings[i]);
ui->cmbEncodingFallback->addItem(encodings[i]);
if (codecForEnc == KateDocumentConfig::global()->codec()) {
ui->cmbEncoding->setCurrentIndex(insert);
}
if (codecForEnc == KateGlobalConfig::global()->fallbackCodec()) {
// adjust index for fallback config, has no default!
ui->cmbEncodingFallback->setCurrentIndex(insert);
}
insert++;
}
}
// encoding detection
ui->cmbEncodingDetection->clear();
bool found = false;
for (int i = 0; !KEncodingProber::nameForProberType((KEncodingProber::ProberType) i).isEmpty(); ++i) {
ui->cmbEncodingDetection->addItem(KEncodingProber::nameForProberType((KEncodingProber::ProberType) i));
if (i == KateGlobalConfig::global()->proberType()) {
ui->cmbEncodingDetection->setCurrentIndex(ui->cmbEncodingDetection->count() - 1);
found = true;
}
}
if (!found) {
ui->cmbEncodingDetection->setCurrentIndex(KEncodingProber::Universal);
}
// eol
ui->cmbEOL->setCurrentIndex(KateDocumentConfig::global()->eol());
ui->chkDetectEOL->setChecked(KateDocumentConfig::global()->allowEolDetection());
ui->chkEnableBOM->setChecked(KateDocumentConfig::global()->bom());
ui->lineLengthLimit->setValue(KateDocumentConfig::global()->lineLengthLimit());
ui->cbRemoveTrailingSpaces->setCurrentIndex(KateDocumentConfig::global()->removeSpaces());
ui->chkNewLineAtEof->setChecked(KateDocumentConfig::global()->newLineAtEof());
// other stuff
uiadv->chkBackupLocalFiles->setChecked(KateDocumentConfig::global()->backupOnSaveLocal());
uiadv->chkBackupRemoteFiles->setChecked(KateDocumentConfig::global()->backupOnSaveRemote());
uiadv->edtBackupPrefix->setText(KateDocumentConfig::global()->backupPrefix());
uiadv->edtBackupSuffix->setText(KateDocumentConfig::global()->backupSuffix());
uiadv->cmbSwapFileMode->setCurrentIndex(KateDocumentConfig::global()->swapFileMode());
uiadv->kurlSwapDirectory->setUrl(QUrl::fromLocalFile(KateDocumentConfig::global()->swapDirectory()));
uiadv->spbSwapFileSync->setValue(KateDocumentConfig::global()->swapSyncInterval());
swapFileModeChanged(KateDocumentConfig::global()->swapFileMode());
}
void KateSaveConfigTab::reset()
{
modeConfigPage->reset();
}
void KateSaveConfigTab::defaults()
{
modeConfigPage->defaults();
ui->cbRemoveTrailingSpaces->setCurrentIndex(0);
uiadv->chkBackupLocalFiles->setChecked(true);
uiadv->chkBackupRemoteFiles->setChecked(false);
uiadv->edtBackupPrefix->setText(QString());
uiadv->edtBackupSuffix->setText(QStringLiteral("~"));
uiadv->cmbSwapFileMode->setCurrentIndex(1);
uiadv->kurlSwapDirectory->setDisabled(true);
uiadv->lblSwapDirectory->setDisabled(true);
uiadv->spbSwapFileSync->setValue(15);
}
QString KateSaveConfigTab::name() const
{
return i18n("Open/Save");
}
QString KateSaveConfigTab::fullName() const
{
return i18n("File Opening & Saving");
}
QIcon KateSaveConfigTab::icon() const
{
return QIcon::fromTheme(QStringLiteral("document-save"));
}
//END KateSaveConfigTab
//BEGIN KateGotoBar
KateGotoBar::KateGotoBar(KTextEditor::View *view, QWidget *parent)
: KateViewBarWidget(true, parent)
, m_view(view)
{
Q_ASSERT(m_view != nullptr); // this bar widget is pointless w/o a view
QHBoxLayout *topLayout = new QHBoxLayout(centralWidget());
topLayout->setContentsMargins(0, 0, 0, 0);
QToolButton *btn = new QToolButton(this);
btn->setAutoRaise(true);
btn->setMinimumSize(QSize(1, btn->minimumSizeHint().height()));
btn->setText(i18n("&Line:"));
btn->setToolTip(i18n("Go to line number from clipboard"));
connect(btn, &QToolButton::clicked, this, &KateGotoBar::gotoClipboard);
topLayout->addWidget(btn);
m_gotoRange = new QSpinBox(this);
m_gotoRange->setMinimum(1);
topLayout->addWidget(m_gotoRange, 1);
topLayout->setStretchFactor(m_gotoRange, 0);
btn = new QToolButton(this);
btn->setAutoRaise(true);
btn->setMinimumSize(QSize(1, btn->minimumSizeHint().height()));
btn->setText(i18n("Go to"));
btn->setIcon(QIcon::fromTheme(QStringLiteral("go-jump")));
btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
connect(btn, &QToolButton::clicked, this, &KateGotoBar::gotoLine);
topLayout->addWidget(btn);
btn = m_modifiedUp = new QToolButton(this);
btn->setAutoRaise(true);
btn->setMinimumSize(QSize(1, btn->minimumSizeHint().height()));
btn->setDefaultAction(m_view->action("modified_line_up"));
btn->setIcon(QIcon::fromTheme(QStringLiteral("go-up-search")));
btn->setText(QString());
btn->installEventFilter(this);
topLayout->addWidget(btn);
btn = m_modifiedDown = new QToolButton(this);
btn->setAutoRaise(true);
btn->setMinimumSize(QSize(1, btn->minimumSizeHint().height()));
btn->setDefaultAction(m_view->action("modified_line_down"));
btn->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search")));
btn->setText(QString());
btn->installEventFilter(this);
topLayout->addWidget(btn);
topLayout->addStretch();
setFocusProxy(m_gotoRange);
}
void KateGotoBar::showEvent(QShowEvent *event)
{
Q_UNUSED(event)
// Catch rare cases where the bar is visible while document is edited
connect(m_view->document(), &KTextEditor::Document::textChanged, this, &KateGotoBar::updateData);
}
void KateGotoBar::closed()
{
disconnect(m_view->document(), &KTextEditor::Document::textChanged, this, &KateGotoBar::updateData);
}
bool KateGotoBar::eventFilter(QObject *object, QEvent *event)
{
if (object == m_modifiedUp || object == m_modifiedDown) {
if (event->type() != QEvent::Wheel) {
return false;
}
int delta = static_cast(event)->delta();
// Reset m_wheelDelta when scroll direction change
if (m_wheelDelta != 0 && (m_wheelDelta < 0) != (delta < 0)) {
m_wheelDelta = 0;
}
m_wheelDelta += delta;
if (m_wheelDelta >= 120) {
m_wheelDelta = 0;
m_modifiedUp->click();
} else if (m_wheelDelta <= -120) {
m_wheelDelta = 0;
m_modifiedDown->click();
}
}
return false;
}
void KateGotoBar::gotoClipboard()
{
QRegularExpression rx(QStringLiteral("\\d+"));
int lineNo = rx.match(QApplication::clipboard()->text(QClipboard::Selection)).captured().toInt();
if (lineNo <= m_gotoRange->maximum() && lineNo >= 1) {
m_gotoRange->setValue(lineNo);
gotoLine();
} else {
QPointer message = new KTextEditor::Message(
i18n("No valid line number found in clipboard"));
message->setWordWrap(true);
message->setAutoHide(2000);
message->setPosition(KTextEditor::Message::BottomInView);
message->setView(m_view),
m_view->document()->postMessage(message);
}
}
void KateGotoBar::updateData()
{
m_gotoRange->setMaximum(m_view->document()->lines());
if (!isVisible()) {
m_gotoRange->setValue(m_view->cursorPosition().line() + 1);
m_gotoRange->adjustSize(); // ### does not respect the range :-(
}
m_gotoRange->selectAll();
}
void KateGotoBar::keyPressEvent(QKeyEvent *event)
{
int key = event->key();
if (key == Qt::Key_Return || key == Qt::Key_Enter) {
gotoLine();
return;
}
KateViewBarWidget::keyPressEvent(event);
}
void KateGotoBar::gotoLine()
{
KTextEditor::ViewPrivate *kv = qobject_cast(m_view);
if (kv && kv->selection() && !kv->config()->persistentSelection()) {
kv->clearSelection();
}
m_view->setCursorPosition(KTextEditor::Cursor(m_gotoRange->value() - 1, 0));
m_view->setFocus();
emit hideMe();
}
//END KateGotoBar
//BEGIN KateDictionaryBar
KateDictionaryBar::KateDictionaryBar(KTextEditor::ViewPrivate *view, QWidget *parent)
: KateViewBarWidget(true, parent)
, m_view(view)
{
Q_ASSERT(m_view != nullptr); // this bar widget is pointless w/o a view
QHBoxLayout *topLayout = new QHBoxLayout(centralWidget());
topLayout->setContentsMargins(0, 0, 0, 0);
//topLayout->setSpacing(spacingHint());
m_dictionaryComboBox = new Sonnet::DictionaryComboBox(centralWidget());
connect(m_dictionaryComboBox, SIGNAL(dictionaryChanged(QString)),
this, SLOT(dictionaryChanged(QString)));
connect(view->doc(), SIGNAL(defaultDictionaryChanged(KTextEditor::DocumentPrivate*)),
this, SLOT(updateData()));
QLabel *label = new QLabel(i18n("Dictionary:"), centralWidget());
label->setBuddy(m_dictionaryComboBox);
topLayout->addWidget(label);
topLayout->addWidget(m_dictionaryComboBox, 1);
topLayout->setStretchFactor(m_dictionaryComboBox, 0);
topLayout->addStretch();
}
KateDictionaryBar::~KateDictionaryBar()
{
}
void KateDictionaryBar::updateData()
{
KTextEditor::DocumentPrivate *document = m_view->doc();
QString dictionary = document->defaultDictionary();
if (dictionary.isEmpty()) {
dictionary = Sonnet::Speller().defaultLanguage();
}
m_dictionaryComboBox->setCurrentByDictionary(dictionary);
}
void KateDictionaryBar::dictionaryChanged(const QString &dictionary)
{
const KTextEditor::Range selection = m_view->selectionRange();
if (selection.isValid() && !selection.isEmpty()) {
const bool blockmode = m_view->blockSelection();
m_view->doc()->setDictionary(dictionary, selection, blockmode);
} else {
m_view->doc()->setDefaultDictionary(dictionary);
}
}
//END KateGotoBar
//BEGIN KateModOnHdPrompt
KateModOnHdPrompt::KateModOnHdPrompt(KTextEditor::DocumentPrivate *doc,
KTextEditor::ModificationInterface::ModifiedOnDiskReason modtype,
const QString &reason)
: QObject(doc)
, m_doc(doc)
, m_modtype(modtype)
, m_proc(nullptr)
, m_diffFile(nullptr)
, m_diffAction(nullptr)
{
m_message = new KTextEditor::Message(reason, KTextEditor::Message::Information);
m_message->setPosition(KTextEditor::Message::AboveView);
m_message->setWordWrap(true);
// If the file isn't deleted, present a diff button
const bool onDiskDeleted = modtype == KTextEditor::ModificationInterface::OnDiskDeleted;
if (!onDiskDeleted) {
QAction * aAutoReload = new QAction(i18n("Enable Auto Reload"), this);
aAutoReload->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
aAutoReload->setToolTip(i18n("Will never again warn about on disk changes but always reload."));
m_message->addAction(aAutoReload, false);
connect(aAutoReload, &QAction::triggered, this, &KateModOnHdPrompt::autoReloadTriggered);
if (!QStandardPaths::findExecutable(QStringLiteral("diff")).isEmpty()) {
m_diffAction = new QAction(i18n("View &Difference"), this);
m_diffAction->setToolTip(i18n("Shows a diff of the changes"));
m_message->addAction(m_diffAction, false);
connect(m_diffAction, SIGNAL(triggered()), this, SLOT(slotDiff()));
}
QAction * aReload = new QAction(i18n("&Reload"), this);
aReload->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
aReload->setToolTip(i18n("Reload the file from disk. Unsaved changes will be lost."));
m_message->addAction(aReload);
connect(aReload, SIGNAL(triggered()), this, SIGNAL(reloadTriggered()));
} else {
QAction * closeFile = new QAction(i18nc("@action:button closes the opened file", "&Close File"), this);
closeFile->setIcon(QIcon::fromTheme(QStringLiteral("document-close")));
closeFile->setToolTip(i18n("Close the file, discarding its content."));
m_message->addAction(closeFile, false);
connect(closeFile, &QAction::triggered, this, &KateModOnHdPrompt::closeTriggered);
QAction * aSaveAs = new QAction(i18n("&Save As..."), this);
aSaveAs->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as")));
aSaveAs->setToolTip(i18n("Lets you select a location and save the file again."));
m_message->addAction(aSaveAs, false);
connect(aSaveAs, SIGNAL(triggered()), this, SIGNAL(saveAsTriggered()));
}
QAction * aIgnore = new QAction(i18n("&Ignore"), this);
aIgnore->setToolTip(i18n("Ignores the changes on disk without any action."));
aIgnore->setIcon(KStandardGuiItem::overwrite().icon());
m_message->addAction(aIgnore);
connect(aIgnore, SIGNAL(triggered()), this, SIGNAL(ignoreTriggered()));
m_doc->postMessage(m_message);
}
KateModOnHdPrompt::~KateModOnHdPrompt()
{
delete m_proc;
m_proc = nullptr;
if (m_diffFile) {
m_diffFile->setAutoRemove(true);
delete m_diffFile;
m_diffFile = nullptr;
}
delete m_message;
}
void KateModOnHdPrompt::slotDiff()
{
if (m_diffFile) {
return;
}
m_diffFile = new QTemporaryFile();
m_diffFile->open();
// Start a KProcess that creates a diff
m_proc = new KProcess(this);
m_proc->setOutputChannelMode(KProcess::MergedChannels);
*m_proc << QStringLiteral("diff") << QLatin1String("-u")
<< QStringLiteral("-") << m_doc->url().toLocalFile();
connect(m_proc, SIGNAL(readyRead()), this, SLOT(slotDataAvailable()));
connect(m_proc, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotPDone()));
// disable the diff button, to hinder the user to run it twice.
m_diffAction->setEnabled(false);
m_proc->start();
QTextStream ts(m_proc);
int lastln = m_doc->lines() - 1;
for (int l = 0; l < lastln; ++l) {
ts << m_doc->line(l) << '\n';
}
ts << m_doc->line(lastln);
ts.flush();
m_proc->closeWriteChannel();
}
void KateModOnHdPrompt::slotDataAvailable()
{
m_diffFile->write(m_proc->readAll());
}
void KateModOnHdPrompt::slotPDone()
{
m_diffAction->setEnabled(true);
const QProcess::ExitStatus es = m_proc->exitStatus();
delete m_proc;
m_proc = nullptr;
if (es != QProcess::NormalExit) {
KMessageBox::sorry(nullptr,
i18n("The diff command failed. Please make sure that "
"diff(1) is installed and in your PATH."),
i18n("Error Creating Diff"));
delete m_diffFile;
m_diffFile = nullptr;
return;
}
if (m_diffFile->size() == 0) {
KMessageBox::information(nullptr,
i18n("The files are identical."),
i18n("Diff Output"));
delete m_diffFile;
m_diffFile = nullptr;
return;
}
m_diffFile->setAutoRemove(false);
QUrl url = QUrl::fromLocalFile(m_diffFile->fileName());
delete m_diffFile;
m_diffFile = nullptr;
// KRun::runUrl should delete the file, once the client exits
KRun::runUrl(url, QStringLiteral("text/x-patch"), nullptr, KRun::RunFlags(KRun::DeleteTemporaryFiles));
}
//END KateModOnHdPrompt
diff --git a/src/document/katedocument.cpp b/src/document/katedocument.cpp
index 42cb25eb..6a64fed9 100644
--- a/src/document/katedocument.cpp
+++ b/src/document/katedocument.cpp
@@ -1,6084 +1,6093 @@
/* This file is part of the KDE libraries
Copyright (C) 2001-2004 Christoph Cullmann
Copyright (C) 2001 Joseph Wenninger
Copyright (C) 1999 Jochen Wilhelmy
Copyright (C) 2006 Hamish Rodda
Copyright (C) 2007 Mirko Stocker
Copyright (C) 2009-2010 Michel Ludwig
Copyright (C) 2013 Gerald Senarclens de Grancy
Copyright (C) 2013 Andrey Matveyakin
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02111-13020, USA.
*/
//BEGIN includes
#include "config.h"
#include "katedocument.h"
#include "kateglobal.h"
#include "katedialogs.h"
#include "katehighlight.h"
#include "kateview.h"
#include "kateautoindent.h"
#include "katetextline.h"
#include "katerenderer.h"
#include "kateregexp.h"
#include "kateplaintextsearch.h"
#include "kateregexpsearch.h"
#include "kateconfig.h"
#include "katemodemanager.h"
#include "kateschema.h"
#include "katebuffer.h"
#include "kateundomanager.h"
#include "spellcheck/prefixstore.h"
#include "spellcheck/ontheflycheck.h"
#include "spellcheck/spellcheck.h"
#include "katescriptmanager.h"
#include "kateswapfile.h"
#include "katepartdebug.h"
#include "printing/kateprinter.h"
#include "kateabstractinputmode.h"
#include "katetemplatehandler.h"
#if EDITORCONFIG_FOUND
#include "editorconfig.h"
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if LIBGIT2_FOUND
#include
#include
#include
#endif
//END includes
#if 0
#define EDIT_DEBUG qCDebug(LOG_KTE)
#else
#define EDIT_DEBUG if (0) qCDebug(LOG_KTE)
#endif
template
static int indexOf(const std::initializer_list & list, const E& entry)
{
auto it = std::find(list.begin(), list.end(), entry);
return it == list.end() ? -1 : std::distance(list.begin(), it);
}
template
static bool contains(const std::initializer_list & list, const E& entry)
{
return indexOf(list, entry) >= 0;
}
static inline QChar matchingStartBracket(const QChar c)
{
switch (c.toLatin1()) {
case '}': return QLatin1Char('{');
case ']': return QLatin1Char('[');
case ')': return QLatin1Char('(');
}
return QChar();
}
static inline QChar matchingEndBracket(const QChar c, bool withQuotes = true)
{
switch (c.toLatin1()) {
case '{': return QLatin1Char('}');
case '[': return QLatin1Char(']');
case '(': return QLatin1Char(')');
case '\'': return withQuotes ? QLatin1Char('\'') : QChar();
case '"': return withQuotes ? QLatin1Char('"') : QChar();
}
return QChar();
}
static inline QChar matchingBracket(const QChar c)
{
QChar bracket = matchingStartBracket(c);
if (bracket.isNull()) {
bracket = matchingEndBracket(c, /*withQuotes=*/false);
}
return bracket;
}
static inline bool isStartBracket(const QChar c)
{
return ! matchingEndBracket(c, /*withQuotes=*/false).isNull();
}
static inline bool isEndBracket(const QChar c)
{
return ! matchingStartBracket(c).isNull();
}
static inline bool isBracket(const QChar c)
{
return isStartBracket(c) || isEndBracket(c);
}
/**
* normalize given url
* @param url input url
* @return normalized url
*/
static QUrl normalizeUrl (const QUrl &url)
{
/**
* only normalize local urls
*/
if (url.isEmpty() || !url.isLocalFile())
return url;
/**
* don't normalize if not existing!
* canonicalFilePath won't work!
*/
const QString normalizedUrl(QFileInfo(url.toLocalFile()).canonicalFilePath());
if (normalizedUrl.isEmpty())
return url;
/**
* else: use canonicalFilePath to normalize
*/
return QUrl::fromLocalFile(normalizedUrl);
}
//BEGIN d'tor, c'tor
//
// KTextEditor::DocumentPrivate Constructor
//
KTextEditor::DocumentPrivate::DocumentPrivate(bool bSingleViewMode,
bool bReadOnly, QWidget *parentWidget,
QObject *parent)
: KTextEditor::Document (this, parent),
m_bSingleViewMode(bSingleViewMode),
m_bReadOnly(bReadOnly),
m_undoManager(new KateUndoManager(this)),
m_buffer(new KateBuffer(this)),
m_indenter(new KateAutoIndent(this)),
m_docName(QStringLiteral("need init")),
m_fileType(QStringLiteral("Normal")),
m_config(new KateDocumentConfig(this))
{
/**
* no plugins from kparts here
*/
setPluginLoadingMode (DoNotLoadPlugins);
/**
* pass on our component data, do this after plugin loading is off
*/
setComponentData(KTextEditor::EditorPrivate::self()->aboutData());
/**
* avoid spamming plasma and other window managers with progress dialogs
* we show such stuff inline in the views!
*/
setProgressInfoEnabled(false);
// register doc at factory
KTextEditor::EditorPrivate::self()->registerDocument(this);
// normal hl
m_buffer->setHighlight(0);
// swap file
m_swapfile = (config()->swapFileMode() == KateDocumentConfig::DisableSwapFile) ? nullptr : new Kate::SwapFile(this);
// some nice signals from the buffer
connect(m_buffer, SIGNAL(tagLines(int,int)), this, SLOT(tagLines(int,int)));
// if the user changes the highlight with the dialog, notify the doc
connect(KateHlManager::self(), SIGNAL(changed()), SLOT(internalHlChanged()));
// signals for mod on hd
connect(KTextEditor::EditorPrivate::self()->dirWatch(), SIGNAL(dirty(QString)),
this, SLOT(slotModOnHdDirty(QString)));
connect(KTextEditor::EditorPrivate::self()->dirWatch(), SIGNAL(created(QString)),
this, SLOT(slotModOnHdCreated(QString)));
connect(KTextEditor::EditorPrivate::self()->dirWatch(), SIGNAL(deleted(QString)),
this, SLOT(slotModOnHdDeleted(QString)));
/**
* singleshot timer to handle updates of mod on hd state delayed
*/
m_modOnHdTimer.setSingleShot(true);
m_modOnHdTimer.setInterval(200);
connect(&m_modOnHdTimer, SIGNAL(timeout()), this, SLOT(slotDelayedHandleModOnHd()));
// Setup auto reload stuff
m_autoReloadMode = new KToggleAction(i18n("Auto Reload Document"), this);
m_autoReloadMode->setWhatsThis(i18n("Automatic reload the document when it was changed on disk"));
connect(m_autoReloadMode, &KToggleAction::triggered, this, &DocumentPrivate::autoReloadToggled);
// Prepare some reload amok protector...
m_autoReloadThrottle.setSingleShot(true);
//...but keep the value small in unit tests
m_autoReloadThrottle.setInterval(KTextEditor::EditorPrivate::self()->unitTestMode() ? 50 : 3000);
connect(&m_autoReloadThrottle, &QTimer::timeout, this, &DocumentPrivate::onModOnHdAutoReload);
/**
* load handling
* this is needed to ensure we signal the user if a file ist still loading
* and to disallow him to edit in that time
*/
connect(this, SIGNAL(started(KIO::Job*)), this, SLOT(slotStarted(KIO::Job*)));
connect(this, SIGNAL(completed()), this, SLOT(slotCompleted()));
connect(this, SIGNAL(canceled(QString)), this, SLOT(slotCanceled()));
connect(this, SIGNAL(urlChanged(QUrl)), this, SLOT(slotUrlChanged(QUrl)));
// update doc name
updateDocName();
// if single view mode, like in the konqui embedding, create a default view ;)
// be lazy, only create it now, if any parentWidget is given, otherwise widget()
// will create it on demand...
if (m_bSingleViewMode && parentWidget) {
KTextEditor::View *view = (KTextEditor::View *)createView(parentWidget);
insertChildClient(view);
view->setContextMenu(view->defaultContextMenu());
setWidget(view);
}
connect(m_undoManager, SIGNAL(undoChanged()), this, SIGNAL(undoChanged()));
connect(m_undoManager, SIGNAL(undoStart(KTextEditor::Document*)), this, SIGNAL(editingStarted(KTextEditor::Document*)));
connect(m_undoManager, SIGNAL(undoEnd(KTextEditor::Document*)), this, SIGNAL(editingFinished(KTextEditor::Document*)));
connect(m_undoManager, SIGNAL(redoStart(KTextEditor::Document*)), this, SIGNAL(editingStarted(KTextEditor::Document*)));
connect(m_undoManager, SIGNAL(redoEnd(KTextEditor::Document*)), this, SIGNAL(editingFinished(KTextEditor::Document*)));
connect(this, SIGNAL(sigQueryClose(bool*,bool*)), this, SLOT(slotQueryClose_save(bool*,bool*)));
connect(this, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(clearEditingPosStack()));
onTheFlySpellCheckingEnabled(config()->onTheFlySpellCheck());
// make sure correct defaults are set (indenter, ...)
updateConfig();
}
//
// KTextEditor::DocumentPrivate Destructor
//
KTextEditor::DocumentPrivate::~DocumentPrivate()
{
// delete pending mod-on-hd message, if applicable
delete m_modOnHdHandler;
/**
* we are about to delete cursors/ranges/...
*/
emit aboutToDeleteMovingInterfaceContent(this);
// kill it early, it has ranges!
delete m_onTheFlyChecker;
m_onTheFlyChecker = nullptr;
clearDictionaryRanges();
// Tell the world that we're about to close (== destruct)
// Apps must receive this in a direct signal-slot connection, and prevent
// any further use of interfaces once they return.
emit aboutToClose(this);
// remove file from dirwatch
deactivateDirWatch();
// thanks for offering, KPart, but we're already self-destructing
setAutoDeleteWidget(false);
setAutoDeletePart(false);
// clean up remaining views
qDeleteAll (m_views.keys());
m_views.clear();
// cu marks
for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) {
delete i.value();
}
m_marks.clear();
delete m_config;
KTextEditor::EditorPrivate::self()->deregisterDocument(this);
}
//END
void KTextEditor::DocumentPrivate::saveEditingPositions(const KTextEditor::Cursor &cursor)
{
if (m_editingStackPosition != m_editingStack.size() - 1) {
m_editingStack.resize(m_editingStackPosition);
}
// try to be clever: reuse existing cursors if possible
QSharedPointer mc;
// we might pop last one: reuse that
if (!m_editingStack.isEmpty() && cursor.line() == m_editingStack.top()->line()) {
mc = m_editingStack.pop();
}
// we might expire oldest one, reuse that one, if not already one there
// we prefer the other one for reuse, as already on the right line aka in the right block!
const int editingStackSizeLimit = 32;
if (m_editingStack.size() >= editingStackSizeLimit) {
if (mc) {
m_editingStack.removeFirst();
} else {
mc = m_editingStack.takeFirst();
}
}
// new cursor needed? or adjust existing one?
if (mc) {
mc->setPosition(cursor);
} else {
mc = QSharedPointer (newMovingCursor(cursor));
}
// add new one as top of stack
m_editingStack.push(mc);
m_editingStackPosition = m_editingStack.size() - 1;
}
KTextEditor::Cursor KTextEditor::DocumentPrivate::lastEditingPosition(EditingPositionKind nextOrPrev, KTextEditor::Cursor currentCursor)
{
if (m_editingStack.isEmpty()) {
return KTextEditor::Cursor::invalid();
}
auto targetPos = m_editingStack.at(m_editingStackPosition)->toCursor();
if (targetPos == currentCursor) {
if (nextOrPrev == Previous) {
m_editingStackPosition--;
}
else {
m_editingStackPosition++;
}
m_editingStackPosition = qBound(0, m_editingStackPosition, m_editingStack.size() - 1);
}
return m_editingStack.at(m_editingStackPosition)->toCursor();
}
void KTextEditor::DocumentPrivate::clearEditingPosStack()
{
m_editingStack.clear();
m_editingStackPosition = -1;
}
// on-demand view creation
QWidget *KTextEditor::DocumentPrivate::widget()
{
// no singleViewMode -> no widget()...
if (!singleViewMode()) {
return nullptr;
}
// does a widget exist already? use it!
if (KTextEditor::Document::widget()) {
return KTextEditor::Document::widget();
}
// create and return one...
KTextEditor::View *view = (KTextEditor::View *)createView(nullptr);
insertChildClient(view);
view->setContextMenu(view->defaultContextMenu());
setWidget(view);
return view;
}
//BEGIN KTextEditor::Document stuff
KTextEditor::View *KTextEditor::DocumentPrivate::createView(QWidget *parent, KTextEditor::MainWindow *mainWindow)
{
KTextEditor::ViewPrivate *newView = new KTextEditor::ViewPrivate(this, parent, mainWindow);
if (m_fileChangedDialogsActivated) {
connect(newView, SIGNAL(focusIn(KTextEditor::View*)), this, SLOT(slotModifiedOnDisk()));
}
emit viewCreated(this, newView);
// post existing messages to the new view, if no specific view is given
foreach (KTextEditor::Message *message, m_messageHash.keys()) {
if (!message->view()) {
newView->postMessage(message, m_messageHash[message]);
}
}
return newView;
}
KTextEditor::Range KTextEditor::DocumentPrivate::rangeOnLine(KTextEditor::Range range, int line) const
{
const int col1 = toVirtualColumn(range.start());
const int col2 = toVirtualColumn(range.end());
return KTextEditor::Range(line, fromVirtualColumn(line, col1), line, fromVirtualColumn(line, col2));
}
//BEGIN KTextEditor::EditInterface stuff
bool KTextEditor::DocumentPrivate::isEditingTransactionRunning() const
{
return editSessionNumber > 0;
}
QString KTextEditor::DocumentPrivate::text() const
{
return m_buffer->text();
}
QString KTextEditor::DocumentPrivate::text(const KTextEditor::Range &range, bool blockwise) const
{
if (!range.isValid()) {
qCWarning(LOG_KTE) << "Text requested for invalid range" << range;
return QString();
}
QString s;
if (range.start().line() == range.end().line()) {
if (range.start().column() > range.end().column()) {
return QString();
}
Kate::TextLine textLine = m_buffer->plainLine(range.start().line());
if (!textLine) {
return QString();
}
return textLine->string(range.start().column(), range.end().column() - range.start().column());
} else {
for (int i = range.start().line(); (i <= range.end().line()) && (i < m_buffer->count()); ++i) {
Kate::TextLine textLine = m_buffer->plainLine(i);
if (!blockwise) {
if (i == range.start().line()) {
s.append(textLine->string(range.start().column(), textLine->length() - range.start().column()));
} else if (i == range.end().line()) {
s.append(textLine->string(0, range.end().column()));
} else {
s.append(textLine->string());
}
} else {
KTextEditor::Range subRange = rangeOnLine(range, i);
s.append(textLine->string(subRange.start().column(), subRange.columnWidth()));
}
if (i < range.end().line()) {
s.append(QLatin1Char('\n'));
}
}
}
return s;
}
QChar KTextEditor::DocumentPrivate::characterAt(const KTextEditor::Cursor &position) const
{
Kate::TextLine textLine = m_buffer->plainLine(position.line());
if (!textLine) {
return QChar();
}
return textLine->at(position.column());
}
QString KTextEditor::DocumentPrivate::wordAt(const KTextEditor::Cursor &cursor) const
{
return text(wordRangeAt(cursor));
}
KTextEditor::Range KTextEditor::DocumentPrivate::wordRangeAt(const KTextEditor::Cursor &cursor) const
{
// get text line
const int line = cursor.line();
Kate::TextLine textLine = m_buffer->plainLine(line);
if (!textLine) {
return KTextEditor::Range::invalid();
}
// make sure the cursor is
const int lineLenth = textLine->length();
if (cursor.column() > lineLenth) {
return KTextEditor::Range::invalid();
}
int start = cursor.column();
int end = start;
while (start > 0 && highlight()->isInWord(textLine->at(start - 1), textLine->attribute(start - 1))) {
start--;
}
while (end < lineLenth && highlight()->isInWord(textLine->at(end), textLine->attribute(end))) {
end++;
}
return KTextEditor::Range(line, start, line, end);
}
bool KTextEditor::DocumentPrivate::isValidTextPosition(const KTextEditor::Cursor& cursor) const
{
const int ln = cursor.line();
const int col = cursor.column();
// cursor in document range?
if (ln < 0 || col < 0 || ln >= lines() || col > lineLength(ln)) {
return false;
}
const QString str = line(ln);
Q_ASSERT(str.length() >= col);
// cursor at end of line?
const int len = lineLength(ln);
if (col == 0 || col == len) {
return true;
}
// cursor in the middle of a valid utf32-surrogate?
return (! str.at(col).isLowSurrogate())
|| (! str.at(col-1).isHighSurrogate());
}
QStringList KTextEditor::DocumentPrivate::textLines(const KTextEditor::Range &range, bool blockwise) const
{
QStringList ret;
if (!range.isValid()) {
qCWarning(LOG_KTE) << "Text requested for invalid range" << range;
return ret;
}
if (blockwise && (range.start().column() > range.end().column())) {
return ret;
}
if (range.start().line() == range.end().line()) {
Q_ASSERT(range.start() <= range.end());
Kate::TextLine textLine = m_buffer->plainLine(range.start().line());
if (!textLine) {
return ret;
}
ret << textLine->string(range.start().column(), range.end().column() - range.start().column());
} else {
for (int i = range.start().line(); (i <= range.end().line()) && (i < m_buffer->count()); ++i) {
Kate::TextLine textLine = m_buffer->plainLine(i);
if (!blockwise) {
if (i == range.start().line()) {
ret << textLine->string(range.start().column(), textLine->length() - range.start().column());
} else if (i == range.end().line()) {
ret << textLine->string(0, range.end().column());
} else {
ret << textLine->string();
}
} else {
KTextEditor::Range subRange = rangeOnLine(range, i);
ret << textLine->string(subRange.start().column(), subRange.columnWidth());
}
}
}
return ret;
}
QString KTextEditor::DocumentPrivate::line(int line) const
{
Kate::TextLine l = m_buffer->plainLine(line);
if (!l) {
return QString();
}
return l->string();
}
bool KTextEditor::DocumentPrivate::setText(const QString &s)
{
if (!isReadWrite()) {
return false;
}
QList msave;
foreach (KTextEditor::Mark *mark, m_marks) {
msave.append(*mark);
}
editStart();
// delete the text
clear();
// insert the new text
insertText(KTextEditor::Cursor(), s);
editEnd();
foreach (KTextEditor::Mark mark, msave) {
setMark(mark.line, mark.type);
}
return true;
}
bool KTextEditor::DocumentPrivate::setText(const QStringList &text)
{
if (!isReadWrite()) {
return false;
}
QList msave;
foreach (KTextEditor::Mark *mark, m_marks) {
msave.append(*mark);
}
editStart();
// delete the text
clear();
// insert the new text
insertText(KTextEditor::Cursor::start(), text);
editEnd();
foreach (KTextEditor::Mark mark, msave) {
setMark(mark.line, mark.type);
}
return true;
}
bool KTextEditor::DocumentPrivate::clear()
{
if (!isReadWrite()) {
return false;
}
foreach (KTextEditor::ViewPrivate *view, m_views) {
view->clear();
view->tagAll();
view->update();
}
clearMarks();
emit aboutToInvalidateMovingInterfaceContent(this);
m_buffer->invalidateRanges();
emit aboutToRemoveText(documentRange());
return editRemoveLines(0, lastLine());
}
bool KTextEditor::DocumentPrivate::insertText(const KTextEditor::Cursor &position, const QString &text, bool block)
{
if (!isReadWrite()) {
return false;
}
if (text.isEmpty()) {
return true;
}
editStart();
int currentLine = position.line();
int currentLineStart = 0;
const int totalLength = text.length();
int insertColumn = position.column();
// pad with empty lines, if insert position is after last line
if (position.line() > lines()) {
int line = lines();
while (line <= position.line()) {
editInsertLine(line, QString());
line++;
}
}
// compute expanded column for block mode
int positionColumnExpanded = insertColumn;
const int tabWidth = config()->tabWidth();
if (block) {
if (auto l = plainKateTextLine(currentLine)) {
positionColumnExpanded = l->toVirtualColumn(insertColumn, tabWidth);
}
}
int pos = 0;
for (; pos < totalLength; pos++) {
const QChar &ch = text.at(pos);
if (ch == QLatin1Char('\n')) {
// Only perform the text insert if there is text to insert
if (currentLineStart < pos) {
editInsertText(currentLine, insertColumn, text.mid(currentLineStart, pos - currentLineStart));
}
if (!block) {
editWrapLine(currentLine, insertColumn + pos - currentLineStart);
insertColumn = 0;
}
currentLine++;
if (block) {
auto l = plainKateTextLine(currentLine);
if (currentLine == lastLine() + 1) {
editInsertLine(currentLine, QString());
}
insertColumn = positionColumnExpanded;
if (l) {
insertColumn = l->fromVirtualColumn(insertColumn, tabWidth);
}
}
currentLineStart = pos + 1;
}
}
// Only perform the text insert if there is text to insert
if (currentLineStart < pos) {
editInsertText(currentLine, insertColumn, text.mid(currentLineStart, pos - currentLineStart));
}
editEnd();
return true;
}
bool KTextEditor::DocumentPrivate::insertText(const KTextEditor::Cursor &position, const QStringList &textLines, bool block)
{
if (!isReadWrite()) {
return false;
}
// just reuse normal function
return insertText(position, textLines.join(QStringLiteral("\n")), block);
}
bool KTextEditor::DocumentPrivate::removeText(const KTextEditor::Range &_range, bool block)
{
KTextEditor::Range range = _range;
if (!isReadWrite()) {
return false;
}
// Should now be impossible to trigger with the new Range class
Q_ASSERT(range.start().line() <= range.end().line());
if (range.start().line() > lastLine()) {
return false;
}
if (!block) {
emit aboutToRemoveText(range);
}
editStart();
if (!block) {
if (range.end().line() > lastLine()) {
range.setEnd(KTextEditor::Cursor(lastLine() + 1, 0));
}
if (range.onSingleLine()) {
editRemoveText(range.start().line(), range.start().column(), range.columnWidth());
} else {
int from = range.start().line();
int to = range.end().line();
// remove last line
if (to <= lastLine()) {
editRemoveText(to, 0, range.end().column());
}
// editRemoveLines() will be called on first line (to remove bookmark)
if (range.start().column() == 0 && from > 0) {
--from;
}
// remove middle lines
editRemoveLines(from + 1, to - 1);
// remove first line if not already removed by editRemoveLines()
if (range.start().column() > 0 || range.start().line() == 0) {
editRemoveText(from, range.start().column(), m_buffer->plainLine(from)->length() - range.start().column());
editUnWrapLine(from);
}
}
} // if ( ! block )
else {
int startLine = qMax(0, range.start().line());
int vc1 = toVirtualColumn(range.start());
int vc2 = toVirtualColumn(range.end());
for (int line = qMin(range.end().line(), lastLine()); line >= startLine; --line) {
int col1 = fromVirtualColumn(line, vc1);
int col2 = fromVirtualColumn(line, vc2);
editRemoveText(line, qMin(col1, col2), qAbs(col2 - col1));
}
}
editEnd();
return true;
}
bool KTextEditor::DocumentPrivate::insertLine(int l, const QString &str)
{
if (!isReadWrite()) {
return false;
}
if (l < 0 || l > lines()) {
return false;
}
return editInsertLine(l, str);
}
bool KTextEditor::DocumentPrivate::insertLines(int line, const QStringList &text)
{
if (!isReadWrite()) {
return false;
}
if (line < 0 || line > lines()) {
return false;
}
bool success = true;
foreach (const QString &string, text) {
success &= editInsertLine(line++, string);
}
return success;
}
bool KTextEditor::DocumentPrivate::removeLine(int line)
{
if (!isReadWrite()) {
return false;
}
if (line < 0 || line > lastLine()) {
return false;
}
return editRemoveLine(line);
}
int KTextEditor::DocumentPrivate::totalCharacters() const
{
int l = 0;
for (int i = 0; i < m_buffer->count(); ++i) {
Kate::TextLine line = m_buffer->plainLine(i);
if (line) {
l += line->length();
}
}
return l;
}
int KTextEditor::DocumentPrivate::lines() const
{
return m_buffer->count();
}
int KTextEditor::DocumentPrivate::lineLength(int line) const
{
if (line < 0 || line > lastLine()) {
return -1;
}
Kate::TextLine l = m_buffer->plainLine(line);
if (!l) {
return -1;
}
return l->length();
}
bool KTextEditor::DocumentPrivate::isLineModified(int line) const
{
if (line < 0 || line >= lines()) {
return false;
}
Kate::TextLine l = m_buffer->plainLine(line);
Q_ASSERT(l);
return l->markedAsModified();
}
bool KTextEditor::DocumentPrivate::isLineSaved(int line) const
{
if (line < 0 || line >= lines()) {
return false;
}
Kate::TextLine l = m_buffer->plainLine(line);
Q_ASSERT(l);
return l->markedAsSavedOnDisk();
}
bool KTextEditor::DocumentPrivate::isLineTouched(int line) const
{
if (line < 0 || line >= lines()) {
return false;
}
Kate::TextLine l = m_buffer->plainLine(line);
Q_ASSERT(l);
return l->markedAsModified() || l->markedAsSavedOnDisk();
}
//END
//BEGIN KTextEditor::EditInterface internal stuff
//
// Starts an edit session with (or without) undo, update of view disabled during session
//
bool KTextEditor::DocumentPrivate::editStart()
{
editSessionNumber++;
if (editSessionNumber > 1) {
return false;
}
editIsRunning = true;
// no last change cursor at start
m_editLastChangeStartCursor = KTextEditor::Cursor::invalid();
m_undoManager->editStart();
foreach (KTextEditor::ViewPrivate *view, m_views) {
view->editStart();
}
m_buffer->editStart();
return true;
}
//
// End edit session and update Views
//
bool KTextEditor::DocumentPrivate::editEnd()
{
if (editSessionNumber == 0) {
Q_ASSERT(0);
return false;
}
// wrap the new/changed text, if something really changed!
if (m_buffer->editChanged() && (editSessionNumber == 1))
if (m_undoManager->isActive() && config()->wordWrap()) {
wrapText(m_buffer->editTagStart(), m_buffer->editTagEnd());
}
editSessionNumber--;
if (editSessionNumber > 0) {
return false;
}
// end buffer edit, will trigger hl update
// this will cause some possible adjustment of tagline start/end
m_buffer->editEnd();
m_undoManager->editEnd();
// edit end for all views !!!!!!!!!
foreach (KTextEditor::ViewPrivate *view, m_views) {
view->editEnd(m_buffer->editTagStart(), m_buffer->editTagEnd(), m_buffer->editTagFrom());
}
if (m_buffer->editChanged()) {
setModified(true);
emit textChanged(this);
}
// remember last change position in the stack, if any
// this avoid costly updates for longer editing transactions
// before we did that on textInsert/Removed
if (m_editLastChangeStartCursor.isValid())
saveEditingPositions(m_editLastChangeStartCursor);
editIsRunning = false;
return true;
}
void KTextEditor::DocumentPrivate::pushEditState()
{
editStateStack.push(editSessionNumber);
}
void KTextEditor::DocumentPrivate::popEditState()
{
if (editStateStack.isEmpty()) {
return;
}
int count = editStateStack.pop() - editSessionNumber;
while (count < 0) {
++count;
editEnd();
}
while (count > 0) {
--count;
editStart();
}
}
void KTextEditor::DocumentPrivate::inputMethodStart()
{
m_undoManager->inputMethodStart();
}
void KTextEditor::DocumentPrivate::inputMethodEnd()
{
m_undoManager->inputMethodEnd();
}
bool KTextEditor::DocumentPrivate::wrapText(int startLine, int endLine)
{
if (startLine < 0 || endLine < 0) {
return false;
}
if (!isReadWrite()) {
return false;
}
int col = config()->wordWrapAt();
if (col == 0) {
return false;
}
editStart();
for (int line = startLine; (line <= endLine) && (line < lines()); line++) {
Kate::TextLine l = kateTextLine(line);
if (!l) {
break;
}
//qCDebug(LOG_KTE) << "try wrap line: " << line;
if (l->virtualLength(m_buffer->tabWidth()) > col) {
Kate::TextLine nextl = kateTextLine(line + 1);
//qCDebug(LOG_KTE) << "do wrap line: " << line;
int eolPosition = l->length() - 1;
// take tabs into account here, too
int x = 0;
const QString &t = l->string();
int z2 = 0;
for (; z2 < l->length(); z2++) {
static const QChar tabChar(QLatin1Char('\t'));
if (t.at(z2) == tabChar) {
x += m_buffer->tabWidth() - (x % m_buffer->tabWidth());
} else {
x++;
}
if (x > col) {
break;
}
}
const int colInChars = qMin(z2, l->length() - 1);
int searchStart = colInChars;
// If where we are wrapping is an end of line and is a space we don't
// want to wrap there
if (searchStart == eolPosition && t.at(searchStart).isSpace()) {
searchStart--;
}
// Scan backwards looking for a place to break the line
// We are not interested in breaking at the first char
// of the line (if it is a space), but we are at the second
// anders: if we can't find a space, try breaking on a word
// boundary, using KateHighlight::canBreakAt().
// This could be a priority (setting) in the hl/filetype/document
int z = -1;
int nw = -1; // alternative position, a non word character
for (z = searchStart; z >= 0; z--) {
if (t.at(z).isSpace()) {
break;
}
if ((nw < 0) && highlight()->canBreakAt(t.at(z), l->attribute(z))) {
nw = z;
}
}
if (z >= 0) {
// So why don't we just remove the trailing space right away?
// Well, the (view's) cursor may be directly in front of that space
// (user typing text before the last word on the line), and if that
// happens, the cursor would be moved to the next line, which is not
// what we want (bug #106261)
z++;
} else {
// There was no space to break at so break at a nonword character if
// found, or at the wrapcolumn ( that needs be configurable )
// Don't try and add any white space for the break
if ((nw >= 0) && nw < colInChars) {
nw++; // break on the right side of the character
}
z = (nw >= 0) ? nw : colInChars;
}
if (nextl && !nextl->isAutoWrapped()) {
editWrapLine(line, z, true);
editMarkLineAutoWrapped(line + 1, true);
endLine++;
} else {
if (nextl && (nextl->length() > 0) && !nextl->at(0).isSpace() && ((l->length() < 1) || !l->at(l->length() - 1).isSpace())) {
editInsertText(line + 1, 0, QLatin1String(" "));
}
bool newLineAdded = false;
editWrapLine(line, z, false, &newLineAdded);
editMarkLineAutoWrapped(line + 1, true);
endLine++;
}
}
}
editEnd();
return true;
}
bool KTextEditor::DocumentPrivate::wrapParagraph(int first, int last)
{
if (first == last) {
return wrapText(first, last);
}
if (first < 0 || last < first) {
return false;
}
if (last >= lines() || first > last) {
return false;
}
if (!isReadWrite()) {
return false;
}
editStart();
// Because we shrink and expand lines, we need to track the working set by powerful "MovingStuff"
std::unique_ptr range(newMovingRange(KTextEditor::Range(first, 0, last, 0)));
std::unique_ptr curr(newMovingCursor(KTextEditor::Cursor(range->start())));
// Scan the selected range for paragraphs, whereas each empty line trigger a new paragraph
for (int line = first; line <= range->end().line(); ++line) {
// Is our first line a somehow filled line?
if(plainKateTextLine(first)->firstChar() < 0) {
// Fast forward to first non empty line
++first;
curr->setPosition(curr->line() + 1, 0);
continue;
}
// Is our current line a somehow filled line? If not, wrap the paragraph
if (plainKateTextLine(line)->firstChar() < 0) {
curr->setPosition(line, 0); // Set on empty line
joinLines(first, line - 1);
// Don't wrap twice! That may cause a bad result
if (!wordWrap()) {
wrapText(first, first);
}
first = curr->line() + 1;
line = first;
}
}
// If there was no paragraph, we need to wrap now
bool needWrap = (curr->line() != range->end().line());
if (needWrap && plainKateTextLine(first)->firstChar() != -1) {
joinLines(first, range->end().line());
// Don't wrap twice! That may cause a bad result
if (!wordWrap()) {
wrapText(first, first);
}
}
editEnd();
return true;
}
bool KTextEditor::DocumentPrivate::editInsertText(int line, int col, const QString &s)
{
// verbose debug
EDIT_DEBUG << "editInsertText" << line << col << s;
if (line < 0 || col < 0) {
return false;
}
if (!isReadWrite()) {
return false;
}
Kate::TextLine l = kateTextLine(line);
if (!l) {
return false;
}
// nothing to do, do nothing!
if (s.isEmpty()) {
return true;
}
editStart();
QString s2 = s;
int col2 = col;
if (col2 > l->length()) {
s2 = QString(col2 - l->length(), QLatin1Char(' ')) + s;
col2 = l->length();
}
m_undoManager->slotTextInserted(line, col2, s2);
// remember last change cursor
m_editLastChangeStartCursor = KTextEditor::Cursor(line, col2);
// insert text into line
m_buffer->insertText(m_editLastChangeStartCursor, s2);
emit textInserted(this, KTextEditor::Range(line, col2, line, col2 + s2.length()));
editEnd();
return true;
}
bool KTextEditor::DocumentPrivate::editRemoveText(int line, int col, int len)
{
// verbose debug
EDIT_DEBUG << "editRemoveText" << line << col << len;
if (line < 0 || col < 0 || len < 0) {
return false;
}
if (!isReadWrite()) {
return false;
}
Kate::TextLine l = kateTextLine(line);
if (!l) {
return false;
}
// nothing to do, do nothing!
if (len == 0) {
return true;
}
// wrong column
if (col >= l->text().size()) {
return false;
}
// don't try to remove what's not there
len = qMin(len, l->text().size() - col);
editStart();
QString oldText = l->string().mid(col, len);
m_undoManager->slotTextRemoved(line, col, oldText);
// remember last change cursor
m_editLastChangeStartCursor = KTextEditor::Cursor(line, col);
// remove text from line
m_buffer->removeText(KTextEditor::Range(m_editLastChangeStartCursor, KTextEditor::Cursor(line, col + len)));
emit textRemoved(this, KTextEditor::Range(line, col, line, col + len), oldText);
editEnd();
return true;
}
bool KTextEditor::DocumentPrivate::editMarkLineAutoWrapped(int line, bool autowrapped)
{
// verbose debug
EDIT_DEBUG << "editMarkLineAutoWrapped" << line << autowrapped;
if (line < 0) {
return false;
}
if (!isReadWrite()) {
return false;
}
Kate::TextLine l = kateTextLine(line);
if (!l) {
return false;
}
editStart();
m_undoManager->slotMarkLineAutoWrapped(line, autowrapped);
l->setAutoWrapped(autowrapped);
editEnd();
return true;
}
bool KTextEditor::DocumentPrivate::editWrapLine(int line, int col, bool newLine, bool *newLineAdded)
{
// verbose debug
EDIT_DEBUG << "editWrapLine" << line << col << newLine;
if (line < 0 || col < 0) {
return false;
}
if (!isReadWrite()) {
return false;
}
Kate::TextLine l = kateTextLine(line);
if (!l) {
return false;
}
editStart();
Kate::TextLine nextLine = kateTextLine(line + 1);
const int length = l->length();
m_undoManager->slotLineWrapped(line, col, length - col, (!nextLine || newLine));
if (!nextLine || newLine) {
m_buffer->wrapLine(KTextEditor::Cursor(line, col));
QList list;
for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) {
if (i.value()->line >= line) {
if ((col == 0) || (i.value()->line > line)) {
list.append(i.value());
}
}
}
for (int i = 0; i < list.size(); ++i) {
m_marks.take(list.at(i)->line);
}
for (int i = 0; i < list.size(); ++i) {
list.at(i)->line++;
m_marks.insert(list.at(i)->line, list.at(i));
}
if (!list.isEmpty()) {
emit marksChanged(this);
}
// yes, we added a new line !
if (newLineAdded) {
(*newLineAdded) = true;
}
} else {
m_buffer->wrapLine(KTextEditor::Cursor(line, col));
m_buffer->unwrapLine(line + 2);
// no, no new line added !
if (newLineAdded) {
(*newLineAdded) = false;
}
}
// remember last change cursor
m_editLastChangeStartCursor = KTextEditor::Cursor(line, col);
emit textInserted(this, KTextEditor::Range(line, col, line + 1, 0));
editEnd();
return true;
}
bool KTextEditor::DocumentPrivate::editUnWrapLine(int line, bool removeLine, int length)
{
// verbose debug
EDIT_DEBUG << "editUnWrapLine" << line << removeLine << length;
if (line < 0 || length < 0) {
return false;
}
if (!isReadWrite()) {
return false;
}
Kate::TextLine l = kateTextLine(line);
Kate::TextLine nextLine = kateTextLine(line + 1);
if (!l || !nextLine) {
return false;
}
editStart();
int col = l->length();
m_undoManager->slotLineUnWrapped(line, col, length, removeLine);
if (removeLine) {
m_buffer->unwrapLine(line + 1);
} else {
m_buffer->wrapLine(KTextEditor::Cursor(line + 1, length));
m_buffer->unwrapLine(line + 1);
}
QList list;
for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) {
if (i.value()->line >= line + 1) {
list.append(i.value());
}
if (i.value()->line == line + 1) {
KTextEditor::Mark *mark = m_marks.take(line);
if (mark) {
i.value()->type |= mark->type;
}
}
}
for (int i = 0; i < list.size(); ++i) {
m_marks.take(list.at(i)->line);
}
for (int i = 0; i < list.size(); ++i) {
list.at(i)->line--;
m_marks.insert(list.at(i)->line, list.at(i));
}
if (!list.isEmpty()) {
emit marksChanged(this);
}
// remember last change cursor
m_editLastChangeStartCursor = KTextEditor::Cursor(line, col);
emit textRemoved(this, KTextEditor::Range(line, col, line + 1, 0), QStringLiteral("\n"));
editEnd();
return true;
}
bool KTextEditor::DocumentPrivate::editInsertLine(int line, const QString &s)
{
// verbose debug
EDIT_DEBUG << "editInsertLine" << line << s;
if (line < 0) {
return false;
}
if (!isReadWrite()) {
return false;
}
if (line > lines()) {
return false;
}
editStart();
m_undoManager->slotLineInserted(line, s);
// wrap line
if (line > 0) {
Kate::TextLine previousLine = m_buffer->line(line - 1);
m_buffer->wrapLine(KTextEditor::Cursor(line - 1, previousLine->text().size()));
} else {
m_buffer->wrapLine(KTextEditor::Cursor(0, 0));
}
// insert text
m_buffer->insertText(KTextEditor::Cursor(line, 0), s);
Kate::TextLine tl = m_buffer->line(line);
QList list;
for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) {
if (i.value()->line >= line) {
list.append(i.value());
}
}
for (int i = 0; i < list.size(); ++i) {
m_marks.take(list.at(i)->line);
}
for (int i = 0; i < list.size(); ++i) {
list.at(i)->line++;
m_marks.insert(list.at(i)->line, list.at(i));
}
if (!list.isEmpty()) {
emit marksChanged(this);
}
KTextEditor::Range rangeInserted(line, 0, line, tl->length());
if (line) {
Kate::TextLine prevLine = plainKateTextLine(line - 1);
rangeInserted.setStart(KTextEditor::Cursor(line - 1, prevLine->length()));
} else {
rangeInserted.setEnd(KTextEditor::Cursor(line + 1, 0));
}
// remember last change cursor
m_editLastChangeStartCursor = rangeInserted.start();
emit textInserted(this, rangeInserted);
editEnd();
return true;
}
bool KTextEditor::DocumentPrivate::editRemoveLine(int line)
{
return editRemoveLines(line, line);
}
bool KTextEditor::DocumentPrivate::editRemoveLines(int from, int to)
{
// verbose debug
EDIT_DEBUG << "editRemoveLines" << from << to;
if (to < from || from < 0 || to > lastLine()) {
return false;
}
if (!isReadWrite()) {
return false;
}
if (lines() == 1) {
return editRemoveText(0, 0, kateTextLine(0)->length());
}
editStart();
QStringList oldText;
/**
* first remove text
*/
for (int line = to; line >= from; --line) {
Kate::TextLine tl = m_buffer->line(line);
oldText.prepend(this->line(line));
m_undoManager->slotLineRemoved(line, this->line(line));
m_buffer->removeText(KTextEditor::Range(KTextEditor::Cursor(line, 0), KTextEditor::Cursor(line, tl->text().size())));
}
/**
* then collapse lines
*/
for (int line = to; line >= from; --line) {
/**
* unwrap all lines, prefer to unwrap line behind, skip to wrap line 0
*/
if (line + 1 < m_buffer->lines()) {
m_buffer->unwrapLine(line + 1);
} else if (line) {
m_buffer->unwrapLine(line);
}
}
QList rmark;
QList list;
foreach (KTextEditor::Mark *mark, m_marks) {
int line = mark->line;
if (line > to) {
list << line;
} else if (line >= from) {
rmark << line;
}
}
foreach (int line, rmark) {
delete m_marks.take(line);
}
foreach (int line, list) {
KTextEditor::Mark *mark = m_marks.take(line);
mark->line -= to - from + 1;
m_marks.insert(mark->line, mark);
}
if (!list.isEmpty()) {
emit marksChanged(this);
}
KTextEditor::Range rangeRemoved(from, 0, to + 1, 0);
if (to == lastLine() + to - from + 1) {
rangeRemoved.setEnd(KTextEditor::Cursor(to, oldText.last().length()));
if (from > 0) {
Kate::TextLine prevLine = plainKateTextLine(from - 1);
rangeRemoved.setStart(KTextEditor::Cursor(from - 1, prevLine->length()));
}
}
// remember last change cursor
m_editLastChangeStartCursor = rangeRemoved.start();
emit textRemoved(this, rangeRemoved, oldText.join(QStringLiteral("\n")) + QLatin1Char('\n'));
editEnd();
return true;
}
//END
//BEGIN KTextEditor::UndoInterface stuff
uint KTextEditor::DocumentPrivate::undoCount() const
{
return m_undoManager->undoCount();
}
uint KTextEditor::DocumentPrivate::redoCount() const
{
return m_undoManager->redoCount();
}
void KTextEditor::DocumentPrivate::undo()
{
m_undoManager->undo();
}
void KTextEditor::DocumentPrivate::redo()
{
m_undoManager->redo();
}
//END
//BEGIN KTextEditor::SearchInterface stuff
QVector KTextEditor::DocumentPrivate::searchText(
const KTextEditor::Range &range,
const QString &pattern,
const KTextEditor::SearchOptions options) const
{
const bool escapeSequences = options.testFlag(KTextEditor::EscapeSequences);
const bool regexMode = options.testFlag(KTextEditor::Regex);
const bool backwards = options.testFlag(KTextEditor::Backwards);
const bool wholeWords = options.testFlag(KTextEditor::WholeWords);
const Qt::CaseSensitivity caseSensitivity = options.testFlag(KTextEditor::CaseInsensitive) ? Qt::CaseInsensitive : Qt::CaseSensitive;
if (regexMode) {
// regexp search
// escape sequences are supported by definition
KateRegExpSearch searcher(this, caseSensitivity);
return searcher.search(pattern, range, backwards);
}
if (escapeSequences) {
// escaped search
KatePlainTextSearch searcher(this, caseSensitivity, wholeWords);
KTextEditor::Range match = searcher.search(KateRegExpSearch::escapePlaintext(pattern), range, backwards);
QVector result;
result.append(match);
return result;
}
// plaintext search
KatePlainTextSearch searcher(this, caseSensitivity, wholeWords);
KTextEditor::Range match = searcher.search(pattern, range, backwards);
QVector result;
result.append(match);
return result;
}
//END
QWidget *KTextEditor::DocumentPrivate::dialogParent()
{
QWidget *w = widget();
if (!w) {
w = activeView();
if (!w) {
w = QApplication::activeWindow();
}
}
return w;
}
//BEGIN KTextEditor::HighlightingInterface stuff
bool KTextEditor::DocumentPrivate::setMode(const QString &name)
{
updateFileType(name);
return true;
}
KTextEditor::DefaultStyle KTextEditor::DocumentPrivate::defaultStyleAt(const KTextEditor::Cursor &position) const
{
// TODO, FIXME KDE5: in surrogate, use 2 bytes before
if (! isValidTextPosition(position)) {
return dsNormal;
}
int ds = const_cast(this)->
defStyleNum(position.line(), position.column());
if (ds < 0 || ds > static_cast(dsError)) {
return dsNormal;
}
return static_cast(ds);
}
QString KTextEditor::DocumentPrivate::mode() const
{
return m_fileType;
}
QStringList KTextEditor::DocumentPrivate::modes() const
{
QStringList m;
const QList &modeList = KTextEditor::EditorPrivate::self()->modeManager()->list();
foreach (KateFileType *type, modeList) {
m << type->name;
}
return m;
}
bool KTextEditor::DocumentPrivate::setHighlightingMode(const QString &name)
{
int mode = KateHlManager::self()->nameFind(name);
if (mode == -1) {
return false;
}
m_buffer->setHighlight(mode);
return true;
}
QString KTextEditor::DocumentPrivate::highlightingMode() const
{
return highlight()->name();
}
QStringList KTextEditor::DocumentPrivate::highlightingModes() const
{
QStringList hls;
for (const auto &hl : KateHlManager::self()->modeList()) {
hls << hl.name();
}
return hls;
}
QString KTextEditor::DocumentPrivate::highlightingModeSection(int index) const
{
return KateHlManager::self()->modeList().at(index).section();
}
QString KTextEditor::DocumentPrivate::modeSection(int index) const
{
return KTextEditor::EditorPrivate::self()->modeManager()->list().at(index)->section;
}
void KTextEditor::DocumentPrivate::bufferHlChanged()
{
// update all views
makeAttribs(false);
// deactivate indenter if necessary
m_indenter->checkRequiredStyle();
emit highlightingModeChanged(this);
}
void KTextEditor::DocumentPrivate::setDontChangeHlOnSave()
{
m_hlSetByUser = true;
}
void KTextEditor::DocumentPrivate::bomSetByUser()
{
m_bomSetByUser = true;
}
//END
//BEGIN KTextEditor::SessionConfigInterface and KTextEditor::ParameterizedSessionConfigInterface stuff
void KTextEditor::DocumentPrivate::readSessionConfig(const KConfigGroup &kconfig, const QSet &flags)
{
if (!flags.contains(QStringLiteral("SkipEncoding"))) {
// get the encoding
QString tmpenc = kconfig.readEntry("Encoding");
if (!tmpenc.isEmpty() && (tmpenc != encoding())) {
setEncoding(tmpenc);
}
}
if (!flags.contains(QStringLiteral("SkipUrl"))) {
// restore the url
QUrl url(kconfig.readEntry("URL"));
// open the file if url valid
if (!url.isEmpty() && url.isValid()) {
openUrl(url);
} else {
completed(); //perhaps this should be emitted at the end of this function
}
} else {
completed(); //perhaps this should be emitted at the end of this function
}
if (!flags.contains(QStringLiteral("SkipMode"))) {
// restore the filetype
if (kconfig.hasKey("Mode")) {
updateFileType(kconfig.readEntry("Mode", fileType()));
// restore if set by user, too!
m_fileTypeSetByUser = kconfig.readEntry("Mode Set By User", false);
}
}
if (!flags.contains(QStringLiteral("SkipHighlighting"))) {
// restore the hl stuff
if (kconfig.hasKey("Highlighting")) {
const int mode = KateHlManager::self()->nameFind(kconfig.readEntry("Highlighting"));
if (mode >= 0) {
m_buffer->setHighlight(mode);
// restore if set by user, too! see bug 332605, otherwise we loose the hl later again on save
m_hlSetByUser = kconfig.readEntry("Highlighting Set By User", false);
}
}
}
// indent mode
config()->setIndentationMode(kconfig.readEntry("Indentation Mode", config()->indentationMode()));
// Restore Bookmarks
const QList marks = kconfig.readEntry("Bookmarks", QList());
for (int i = 0; i < marks.count(); i++) {
addMark(marks.at(i), KTextEditor::DocumentPrivate::markType01);
}
}
void KTextEditor::DocumentPrivate::writeSessionConfig(KConfigGroup &kconfig, const QSet &flags)
{
if (this->url().isLocalFile()) {
const QString path = this->url().toLocalFile();
if (path.startsWith(QDir::tempPath())) {
return; // inside tmp resource, do not save
}
}
if (!flags.contains(QStringLiteral("SkipUrl"))) {
// save url
kconfig.writeEntry("URL", this->url().toString());
}
if (!flags.contains(QStringLiteral("SkipEncoding"))) {
// save encoding
kconfig.writeEntry("Encoding", encoding());
}
if (!flags.contains(QStringLiteral("SkipMode"))) {
// save file type
kconfig.writeEntry("Mode", m_fileType);
// save if set by user, too!
kconfig.writeEntry("Mode Set By User", m_fileTypeSetByUser);
}
if (!flags.contains(QStringLiteral("SkipHighlighting"))) {
// save hl
kconfig.writeEntry("Highlighting", highlight()->name());
// save if set by user, too! see bug 332605, otherwise we loose the hl later again on save
kconfig.writeEntry("Highlighting Set By User", m_hlSetByUser);
}
// indent mode
kconfig.writeEntry("Indentation Mode", config()->indentationMode());
// Save Bookmarks
QList marks;
for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i)
if (i.value()->type & KTextEditor::MarkInterface::markType01) {
marks << i.value()->line;
}
kconfig.writeEntry("Bookmarks", marks);
}
//END KTextEditor::SessionConfigInterface and KTextEditor::ParameterizedSessionConfigInterface stuff
uint KTextEditor::DocumentPrivate::mark(int line)
{
KTextEditor::Mark *m = m_marks.value(line);
if (!m) {
return 0;
}
return m->type;
}
void KTextEditor::DocumentPrivate::setMark(int line, uint markType)
{
clearMark(line);
addMark(line, markType);
}
void KTextEditor::DocumentPrivate::clearMark(int line)
{
if (line < 0 || line > lastLine()) {
return;
}
if (!m_marks.value(line)) {
return;
}
KTextEditor::Mark *mark = m_marks.take(line);
emit markChanged(this, *mark, MarkRemoved);
emit marksChanged(this);
delete mark;
tagLines(line, line);
repaintViews(true);
}
void KTextEditor::DocumentPrivate::addMark(int line, uint markType)
{
KTextEditor::Mark *mark;
if (line < 0 || line > lastLine()) {
return;
}
if (markType == 0) {
return;
}
if ((mark = m_marks.value(line))) {
// Remove bits already set
markType &= ~mark->type;
if (markType == 0) {
return;
}
// Add bits
mark->type |= markType;
} else {
mark = new KTextEditor::Mark;
mark->line = line;
mark->type = markType;
m_marks.insert(line, mark);
}
// Emit with a mark having only the types added.
KTextEditor::Mark temp;
temp.line = line;
temp.type = markType;
emit markChanged(this, temp, MarkAdded);
emit marksChanged(this);
tagLines(line, line);
repaintViews(true);
}
void KTextEditor::DocumentPrivate::removeMark(int line, uint markType)
{
if (line < 0 || line > lastLine()) {
return;
}
KTextEditor::Mark *mark = m_marks.value(line);
if (!mark) {
return;
}
// Remove bits not set
markType &= mark->type;
if (markType == 0) {
return;
}
// Subtract bits
mark->type &= ~markType;
// Emit with a mark having only the types removed.
KTextEditor::Mark temp;
temp.line = line;
temp.type = markType;
emit markChanged(this, temp, MarkRemoved);
if (mark->type == 0) {
m_marks.remove(line);
delete mark;
}
emit marksChanged(this);
tagLines(line, line);
repaintViews(true);
}
const QHash &KTextEditor::DocumentPrivate::marks()
{
return m_marks;
}
void KTextEditor::DocumentPrivate::requestMarkTooltip(int line, QPoint position)
{
KTextEditor::Mark *mark = m_marks.value(line);
if (!mark) {
return;
}
bool handled = false;
emit markToolTipRequested(this, *mark, position, handled);
}
bool KTextEditor::DocumentPrivate::handleMarkClick(int line)
{
bool handled = false;
KTextEditor::Mark *mark = m_marks.value(line);
if (!mark) {
emit markClicked(this, KTextEditor::Mark{line, 0}, handled);
} else {
emit markClicked(this, *mark, handled);
}
return handled;
}
bool KTextEditor::DocumentPrivate::handleMarkContextMenu(int line, QPoint position)
{
bool handled = false;
KTextEditor::Mark *mark = m_marks.value(line);
if (!mark) {
emit markContextMenuRequested(this, KTextEditor::Mark{line, 0}, position, handled);
} else {
emit markContextMenuRequested(this, *mark, position, handled);
}
return handled;
}
void KTextEditor::DocumentPrivate::clearMarks()
{
while (!m_marks.isEmpty()) {
QHash::iterator it = m_marks.begin();
KTextEditor::Mark mark = *it.value();
delete it.value();
m_marks.erase(it);
emit markChanged(this, mark, MarkRemoved);
tagLines(mark.line, mark.line);
}
m_marks.clear();
emit marksChanged(this);
repaintViews(true);
}
void KTextEditor::DocumentPrivate::setMarkPixmap(MarkInterface::MarkTypes type, const QPixmap &pixmap)
{
m_markPixmaps.insert(type, pixmap);
}
void KTextEditor::DocumentPrivate::setMarkDescription(MarkInterface::MarkTypes type, const QString &description)
{
m_markDescriptions.insert(type, description);
}
QPixmap KTextEditor::DocumentPrivate::markPixmap(MarkInterface::MarkTypes type) const
{
return m_markPixmaps.value(type, QPixmap());
}
QColor KTextEditor::DocumentPrivate::markColor(MarkInterface::MarkTypes type) const
{
uint reserved = (0x1 << KTextEditor::MarkInterface::reservedMarkersCount()) - 1;
if ((uint)type >= (uint)markType01 && (uint)type <= reserved) {
return KateRendererConfig::global()->lineMarkerColor(type);
} else {
return QColor();
}
}
QString KTextEditor::DocumentPrivate::markDescription(MarkInterface::MarkTypes type) const
{
return m_markDescriptions.value(type, QString());
}
void KTextEditor::DocumentPrivate::setEditableMarks(uint markMask)
{
m_editableMarks = markMask;
}
uint KTextEditor::DocumentPrivate::editableMarks() const
{
return m_editableMarks;
}
//END
//BEGIN KTextEditor::PrintInterface stuff
bool KTextEditor::DocumentPrivate::print()
{
return KatePrinter::print(this);
}
void KTextEditor::DocumentPrivate::printPreview()
{
KatePrinter::printPreview(this);
}
//END KTextEditor::PrintInterface stuff
//BEGIN KTextEditor::DocumentInfoInterface (### unfinished)
QString KTextEditor::DocumentPrivate::mimeType()
{
/**
* collect first 4k of text
* only heuristic
*/
QByteArray buf;
for (int i = 0; (i < lines()) && (buf.size() <= 4096); ++i) {
buf.append(line(i).toUtf8());
buf.append('\n');
}
// use path of url, too, if set
if (!url().path().isEmpty()) {
return QMimeDatabase().mimeTypeForFileNameAndData(url().path(), buf).name();
}
// else only use the content
return QMimeDatabase().mimeTypeForData(buf).name();
}
//END KTextEditor::DocumentInfoInterface
//BEGIN: error
void KTextEditor::DocumentPrivate::showAndSetOpeningErrorAccess()
{
QPointer message
= new KTextEditor::Message(i18n("The file %1 could not be loaded, as it was not possible to read from it.
Check if you have read access to this file.", this->url().toDisplayString(QUrl::PreferLocalFile)),
KTextEditor::Message::Error);
message->setWordWrap(true);
QAction *tryAgainAction = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("translators: you can also translate 'Try Again' with 'Reload'", "Try Again"), nullptr);
connect(tryAgainAction, SIGNAL(triggered()), SLOT(documentReload()), Qt::QueuedConnection);
QAction *closeAction = new QAction(QIcon::fromTheme(QStringLiteral("window-close")), i18n("&Close"), nullptr);
closeAction->setToolTip(i18n("Close message"));
// add try again and close actions
message->addAction(tryAgainAction);
message->addAction(closeAction);
// finally post message
postMessage(message);
// remember error
m_openingError = true;
m_openingErrorMessage = i18n("The file %1 could not be loaded, as it was not possible to read from it.\n\nCheck if you have read access to this file.", this->url().toDisplayString(QUrl::PreferLocalFile));
}
//END: error
void KTextEditor::DocumentPrivate::openWithLineLengthLimitOverride()
{
// raise line length limit to the next power of 2
const int longestLine = m_buffer->longestLineLoaded();
int newLimit = pow(2, ceil(log2(longestLine)));
if (newLimit <= longestLine) {
newLimit *= 2;
}
// do the raise
config()->setLineLengthLimit(newLimit);
// just reload
m_buffer->clear();
openFile();
if (!m_openingError) {
setReadWrite(true);
m_readWriteStateBeforeLoading = true;
}
}
int KTextEditor::DocumentPrivate::lineLengthLimit() const
{
return config()->lineLengthLimit();
}
//BEGIN KParts::ReadWrite stuff
bool KTextEditor::DocumentPrivate::openFile()
{
/**
* we are about to invalidate all cursors/ranges/.. => m_buffer->openFile will do so
*/
emit aboutToInvalidateMovingInterfaceContent(this);
// no open errors until now...
m_openingError = false;
m_openingErrorMessage.clear ();
// add new m_file to dirwatch
activateDirWatch();
// remember current encoding
QString currentEncoding = encoding();
//
// mime type magic to get encoding right
//
QString mimeType = arguments().mimeType();
int pos = mimeType.indexOf(QLatin1Char(';'));
if (pos != -1 && !(m_reloading && m_userSetEncodingForNextReload)) {
setEncoding(mimeType.mid(pos + 1));
}
// update file type, we do this here PRE-LOAD, therefore pass file name for reading from
updateFileType(KTextEditor::EditorPrivate::self()->modeManager()->fileType(this, localFilePath()));
// read dir config (if possible and wanted)
// do this PRE-LOAD to get encoding info!
readDirConfig();
// perhaps we need to re-set again the user encoding
if (m_reloading && m_userSetEncodingForNextReload && (currentEncoding != encoding())) {
setEncoding(currentEncoding);
}
bool success = m_buffer->openFile(localFilePath(), (m_reloading && m_userSetEncodingForNextReload));
//
// yeah, success
// read variables
//
if (success) {
readVariables();
}
//
// update views
//
foreach (KTextEditor::ViewPrivate *view, m_views) {
// This is needed here because inserting the text moves the view's start position (it is a MovingCursor)
view->setCursorPosition(KTextEditor::Cursor());
view->updateView(true);
}
// Inform that the text has changed (required as we're not inside the usual editStart/End stuff)
emit textChanged(this);
emit loaded(this);
//
// to houston, we are not modified
//
if (m_modOnHd) {
m_modOnHd = false;
m_modOnHdReason = OnDiskUnmodified;
m_prevModOnHdReason = OnDiskUnmodified;
emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason);
}
//
// display errors
//
if (!success) {
showAndSetOpeningErrorAccess();
}
// warn: broken encoding
if (m_buffer->brokenEncoding()) {
// this file can't be saved again without killing it
setReadWrite(false);
m_readWriteStateBeforeLoading=false;
QPointer message
= new KTextEditor::Message(i18n("The file %1 was opened with %2 encoding but contained invalid characters.
"
"It is set to read-only mode, as saving might destroy its content.
"
"Either reopen the file with the correct encoding chosen or enable the read-write mode again in the tools menu to be able to edit it.", this->url().toDisplayString(QUrl::PreferLocalFile),
QString::fromLatin1(m_buffer->textCodec()->name())),
KTextEditor::Message::Warning);
message->setWordWrap(true);
postMessage(message);
// remember error
m_openingError = true;
m_openingErrorMessage = i18n("The file %1 was opened with %2 encoding but contained invalid characters."
" It is set to read-only mode, as saving might destroy its content."
" Either reopen the file with the correct encoding chosen or enable the read-write mode again in the tools menu to be able to edit it.", this->url().toDisplayString(QUrl::PreferLocalFile), QString::fromLatin1(m_buffer->textCodec()->name()));
}
// warn: too long lines
if (m_buffer->tooLongLinesWrapped()) {
// this file can't be saved again without modifications
setReadWrite(false);
m_readWriteStateBeforeLoading = false;
QPointer message
= new KTextEditor::Message(i18n("The file %1 was opened and contained lines longer than the configured Line Length Limit (%2 characters).
"
"The longest of those lines was %3 characters long
"
"Those lines were wrapped and the document is set to read-only mode, as saving will modify its content.",
this->url().toDisplayString(QUrl::PreferLocalFile), config()->lineLengthLimit(), m_buffer->longestLineLoaded()),
KTextEditor::Message::Warning);
QAction *increaseAndReload = new QAction(i18n("Temporarily raise limit and reload file"), message);
connect(increaseAndReload, SIGNAL(triggered()), this, SLOT(openWithLineLengthLimitOverride()));
message->addAction(increaseAndReload, true);
message->addAction(new QAction(i18n("Close"), message), true);
message->setWordWrap(true);
postMessage(message);
// remember error
m_openingError = true;
m_openingErrorMessage = i18n("The file %1 was opened and contained lines longer than the configured Line Length Limit (%2 characters).
"
"The longest of those lines was %3 characters long
"
"Those lines were wrapped and the document is set to read-only mode, as saving will modify its content.", this->url().toDisplayString(QUrl::PreferLocalFile), config()->lineLengthLimit(),m_buffer->longestLineLoaded());
}
//
// return the success
//
return success;
}
bool KTextEditor::DocumentPrivate::saveFile()
{
// delete pending mod-on-hd message if applicable.
delete m_modOnHdHandler;
// some warnings, if file was changed by the outside!
if (!url().isEmpty()) {
if (m_fileChangedDialogsActivated && m_modOnHd) {
QString str = reasonedMOHString() + QLatin1String("\n\n");
if (!isModified()) {
if (KMessageBox::warningContinueCancel(dialogParent(),
str + i18n("Do you really want to save this unmodified file? You could overwrite changed data in the file on disk."), i18n("Trying to Save Unmodified File"), KGuiItem(i18n("Save Nevertheless"))) != KMessageBox::Continue) {
return false;
}
} else {
if (KMessageBox::warningContinueCancel(dialogParent(),
str + i18n("Do you really want to save this file? Both your open file and the file on disk were changed. There could be some data lost."), i18n("Possible Data Loss"), KGuiItem(i18n("Save Nevertheless"))) != KMessageBox::Continue) {
return false;
}
}
}
}
//
// can we encode it if we want to save it ?
//
if (!m_buffer->canEncode()
&& (KMessageBox::warningContinueCancel(dialogParent(),
i18n("The selected encoding cannot encode every Unicode character in this document. Do you really want to save it? There could be some data lost."), i18n("Possible Data Loss"), KGuiItem(i18n("Save Nevertheless"))) != KMessageBox::Continue)) {
return false;
}
/**
* create a backup file or abort if that fails!
* if no backup file wanted, this routine will just return true
*/
if (!createBackupFile())
return false;
// update file type, pass no file path, read file type content from this document
QString oldPath = m_dirWatchFile;
// only update file type if path has changed so that variables are not overridden on normal save
if (oldPath != localFilePath()) {
updateFileType(KTextEditor::EditorPrivate::self()->modeManager()->fileType(this, QString()));
if (url().isLocalFile()) {
// if file is local then read dir config for new path
readDirConfig();
}
}
// read our vars
readVariables();
// remove file from dirwatch
deactivateDirWatch();
// remove all trailing spaces in the document (as edit actions)
// NOTE: we need this as edit actions, since otherwise the edit actions
// in the swap file recovery may happen at invalid cursor positions
removeTrailingSpaces();
//
// try to save
//
if (!m_buffer->saveFile(localFilePath())) {
// add m_file again to dirwatch
activateDirWatch(oldPath);
KMessageBox::error(dialogParent(), i18n("The document could not be saved, as it was not possible to write to %1.\nCheck that you have write access to this file or that enough disk space is available.\nThe original file may be lost or damaged. Don't quit the application until the file is successfully written.", this->url().toDisplayString(QUrl::PreferLocalFile)));
return false;
}
// update the checksum
createDigest();
// add m_file again to dirwatch
activateDirWatch();
//
// we are not modified
//
if (m_modOnHd) {
m_modOnHd = false;
m_modOnHdReason = OnDiskUnmodified;
m_prevModOnHdReason = OnDiskUnmodified;
emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason);
}
// (dominik) mark last undo group as not mergeable, otherwise the next
// edit action might be merged and undo will never stop at the saved state
m_undoManager->undoSafePoint();
m_undoManager->updateLineModifications();
//
// return success
//
return true;
}
bool KTextEditor::DocumentPrivate::createBackupFile()
{
/**
* backup for local or remote files wanted?
*/
const bool backupLocalFiles = config()->backupOnSaveLocal();
const bool backupRemoteFiles = config()->backupOnSaveRemote();
/**
* early out, before mount check: backup wanted at all?
* => if not, all fine, just return
*/
if (!backupLocalFiles && !backupRemoteFiles) {
return true;
}
/**
* decide if we need backup based on locality
* skip that, if we always want backups, as currentMountPoints is not that fast
*/
QUrl u(url());
bool needBackup = backupLocalFiles && backupRemoteFiles;
if (!needBackup) {
bool slowOrRemoteFile = !u.isLocalFile();
if (!slowOrRemoteFile) {
// could be a mounted remote filesystem (e.g. nfs, sshfs, cifs)
// we have the early out above to skip this, if we want no backup, which is the default
KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByDevice(u.toLocalFile());
slowOrRemoteFile = (mountPoint && mountPoint->probablySlow());
}
needBackup = (!slowOrRemoteFile && backupLocalFiles) || (slowOrRemoteFile && backupRemoteFiles);
}
/**
* no backup needed? be done
*/
if (!needBackup) {
return true;
}
/**
* else: try to backup
*/
if (config()->backupPrefix().contains(QDir::separator())) {
/**
* replace complete path, as prefix is a path!
*/
u.setPath(config()->backupPrefix() + u.fileName() + config()->backupSuffix());
} else {
/**
* replace filename in url
*/
const QString fileName = u.fileName();
u = u.adjusted(QUrl::RemoveFilename);
u.setPath(u.path() + config()->backupPrefix() + fileName + config()->backupSuffix());
}
qCDebug(LOG_KTE) << "backup src file name: " << url();
qCDebug(LOG_KTE) << "backup dst file name: " << u;
// handle the backup...
bool backupSuccess = false;
// local file mode, no kio
if (u.isLocalFile()) {
if (QFile::exists(url().toLocalFile())) {
// first: check if backupFile is already there, if true, unlink it
QFile backupFile(u.toLocalFile());
if (backupFile.exists()) {
backupFile.remove();
}
backupSuccess = QFile::copy(url().toLocalFile(), u.toLocalFile());
} else {
backupSuccess = true;
}
} else { // remote file mode, kio
// get the right permissions, start with safe default
KIO::StatJob *statJob = KIO::stat(url(), KIO::StatJob::SourceSide, 2);
KJobWidgets::setWindow(statJob, QApplication::activeWindow());
if (statJob->exec()) {
// do a evil copy which will overwrite target if possible
KFileItem item(statJob->statResult(), url());
KIO::FileCopyJob *job = KIO::file_copy(url(), u, item.permissions(), KIO::Overwrite);
KJobWidgets::setWindow(job, QApplication::activeWindow());
backupSuccess = job->exec();
} else {
backupSuccess = true;
}
}
// backup has failed, ask user how to proceed
if (!backupSuccess && (KMessageBox::warningContinueCancel(dialogParent()
, i18n("For file %1 no backup copy could be created before saving."
" If an error occurs while saving, you might lose the data of this file."
" A reason could be that the media you write to is full or the directory of the file is read-only for you.", url().toDisplayString(QUrl::PreferLocalFile))
, i18n("Failed to create backup copy.")
, KGuiItem(i18n("Try to Save Nevertheless"))
, KStandardGuiItem::cancel(), QStringLiteral("Backup Failed Warning")) != KMessageBox::Continue)) {
return false;
}
return true;
}
void KTextEditor::DocumentPrivate::readDirConfig()
{
if (!url().isLocalFile()) {
return;
}
/**
* first search .kateconfig upwards
* with recursion guard
*/
QSet seenDirectories;
QDir dir (QFileInfo(localFilePath()).absolutePath());
while (!seenDirectories.contains (dir.absolutePath ())) {
/**
* fill recursion guard
*/
seenDirectories.insert (dir.absolutePath ());
// try to open config file in this dir
QFile f(dir.absolutePath () + QLatin1String("/.kateconfig"));
if (f.open(QIODevice::ReadOnly)) {
QTextStream stream(&f);
uint linesRead = 0;
QString line = stream.readLine();
while ((linesRead < 32) && !line.isNull()) {
readVariableLine(line);
line = stream.readLine();
linesRead++;
}
return;
}
/**
* else: cd up, if possible or abort
*/
if (!dir.cdUp()) {
break;
}
}
#if EDITORCONFIG_FOUND
// if there wasn’t any .kateconfig file and KTextEditor was compiled with
// EditorConfig support, try to load document config from a .editorconfig
// file, if such is provided
EditorConfig editorConfig(this);
editorConfig.parse();
#endif
}
void KTextEditor::DocumentPrivate::activateDirWatch(const QString &useFileName)
{
QString fileToUse = useFileName;
if (fileToUse.isEmpty()) {
fileToUse = localFilePath();
}
QFileInfo fileInfo = QFileInfo(fileToUse);
if (fileInfo.isSymLink()) {
// Monitor the actual data and not the symlink
fileToUse = fileInfo.canonicalFilePath();
}
// same file as we are monitoring, return
if (fileToUse == m_dirWatchFile) {
return;
}
// remove the old watched file
deactivateDirWatch();
// add new file if needed
if (url().isLocalFile() && !fileToUse.isEmpty()) {
KTextEditor::EditorPrivate::self()->dirWatch()->addFile(fileToUse);
m_dirWatchFile = fileToUse;
}
}
void KTextEditor::DocumentPrivate::deactivateDirWatch()
{
if (!m_dirWatchFile.isEmpty()) {
KTextEditor::EditorPrivate::self()->dirWatch()->removeFile(m_dirWatchFile);
}
m_dirWatchFile.clear();
}
bool KTextEditor::DocumentPrivate::openUrl(const QUrl &url)
{
if (!m_reloading) {
// Reset filetype when opening url
m_fileTypeSetByUser = false;
}
bool res = KTextEditor::Document::openUrl(normalizeUrl(url));
updateDocName();
return res;
}
bool KTextEditor::DocumentPrivate::closeUrl()
{
//
// file mod on hd
//
if (!m_reloading && !url().isEmpty()) {
if (m_fileChangedDialogsActivated && m_modOnHd) {
// make sure to not forget pending mod-on-hd handler
delete m_modOnHdHandler;
QWidget *parentWidget(dialogParent());
if (!(KMessageBox::warningContinueCancel(
parentWidget,
reasonedMOHString() + QLatin1String("\n\n") + i18n("Do you really want to continue to close this file? Data loss may occur."),
i18n("Possible Data Loss"), KGuiItem(i18n("Close Nevertheless")), KStandardGuiItem::cancel(),
QStringLiteral("kate_close_modonhd_%1").arg(m_modOnHdReason)) == KMessageBox::Continue)) {
/**
* reset reloading
*/
m_reloading = false;
return false;
}
}
}
//
// first call the normal kparts implementation
//
if (!KParts::ReadWritePart::closeUrl()) {
/**
* reset reloading
*/
m_reloading = false;
return false;
}
// Tell the world that we're about to go ahead with the close
if (!m_reloading) {
emit aboutToClose(this);
}
/**
* delete all KTE::Messages
*/
if (!m_messageHash.isEmpty()) {
QList keys = m_messageHash.keys();
foreach (KTextEditor::Message *message, keys) {
delete message;
}
}
/**
* we are about to invalidate all cursors/ranges/.. => m_buffer->clear will do so
*/
emit aboutToInvalidateMovingInterfaceContent(this);
// remove file from dirwatch
deactivateDirWatch();
//
// empty url + fileName
//
setUrl(QUrl());
setLocalFilePath(QString());
// we are not modified
if (m_modOnHd) {
m_modOnHd = false;
m_modOnHdReason = OnDiskUnmodified;
m_prevModOnHdReason = OnDiskUnmodified;
emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason);
}
// remove all marks
clearMarks();
// clear the buffer
m_buffer->clear();
// clear undo/redo history
m_undoManager->clearUndo();
m_undoManager->clearRedo();
// no, we are no longer modified
setModified(false);
// we have no longer any hl
m_buffer->setHighlight(0);
// update all our views
foreach (KTextEditor::ViewPrivate *view, m_views) {
view->clearSelection(); // fix bug #118588
view->clear();
}
// purge swap file
if (m_swapfile) {
m_swapfile->fileClosed();
}
// success
return true;
}
bool KTextEditor::DocumentPrivate::isDataRecoveryAvailable() const
{
return m_swapfile && m_swapfile->shouldRecover();
}
void KTextEditor::DocumentPrivate::recoverData()
{
if (isDataRecoveryAvailable()) {
m_swapfile->recover();
}
}
void KTextEditor::DocumentPrivate::discardDataRecovery()
{
if (isDataRecoveryAvailable()) {
m_swapfile->discard();
}
}
void KTextEditor::DocumentPrivate::setReadWrite(bool rw)
{
if (isReadWrite() == rw) {
return;
}
KParts::ReadWritePart::setReadWrite(rw);
foreach (KTextEditor::ViewPrivate *view, m_views) {
view->slotUpdateUndo();
view->slotReadWriteChanged();
}
emit readWriteChanged(this);
}
void KTextEditor::DocumentPrivate::setModified(bool m)
{
if (isModified() != m) {
KParts::ReadWritePart::setModified(m);
foreach (KTextEditor::ViewPrivate *view, m_views) {
view->slotUpdateUndo();
}
emit modifiedChanged(this);
}
m_undoManager->setModified(m);
}
//END
//BEGIN Kate specific stuff ;)
void KTextEditor::DocumentPrivate::makeAttribs(bool needInvalidate)
{
foreach (KTextEditor::ViewPrivate *view, m_views) {
view->renderer()->updateAttributes();
}
if (needInvalidate) {
m_buffer->invalidateHighlighting();
}
foreach (KTextEditor::ViewPrivate *view, m_views) {
view->tagAll();
view->updateView(true);
}
}
// the attributes of a hl have changed, update
void KTextEditor::DocumentPrivate::internalHlChanged()
{
makeAttribs();
}
void KTextEditor::DocumentPrivate::addView(KTextEditor::View *view)
{
Q_ASSERT (!m_views.contains(view));
m_views.insert(view, static_cast(view));
m_viewsCache.append(view);
// apply the view & renderer vars from the file type
if (!m_fileType.isEmpty()) {
readVariableLine(KTextEditor::EditorPrivate::self()->modeManager()->fileType(m_fileType).varLine, true);
}
// apply the view & renderer vars from the file
readVariables(true);
setActiveView(view);
}
void KTextEditor::DocumentPrivate::removeView(KTextEditor::View *view)
{
Q_ASSERT (m_views.contains(view));
m_views.remove(view);
m_viewsCache.removeAll(view);
if (activeView() == view) {
setActiveView(nullptr);
}
}
void KTextEditor::DocumentPrivate::setActiveView(KTextEditor::View *view)
{
if (m_activeView == view) {
return;
}
m_activeView = static_cast(view);
}
bool KTextEditor::DocumentPrivate::ownedView(KTextEditor::ViewPrivate *view)
{
// do we own the given view?
return (m_views.contains(view));
}
int KTextEditor::DocumentPrivate::toVirtualColumn(int line, int column) const
{
Kate::TextLine textLine = m_buffer->plainLine(line);
if (textLine) {
return textLine->toVirtualColumn(column, config()->tabWidth());
} else {
return 0;
}
}
int KTextEditor::DocumentPrivate::toVirtualColumn(const KTextEditor::Cursor &cursor) const
{
return toVirtualColumn(cursor.line(), cursor.column());
}
int KTextEditor::DocumentPrivate::fromVirtualColumn(int line, int column) const
{
Kate::TextLine textLine = m_buffer->plainLine(line);
if (textLine) {
return textLine->fromVirtualColumn(column, config()->tabWidth());
} else {
return 0;
}
}
int KTextEditor::DocumentPrivate::fromVirtualColumn(const KTextEditor::Cursor &cursor) const
{
return fromVirtualColumn(cursor.line(), cursor.column());
}
bool KTextEditor::DocumentPrivate::typeChars(KTextEditor::ViewPrivate *view, const QString &realChars)
{
/**
* filter out non-printable chars (convert to utf-32 to support surrogate pairs)
*/
const auto realUcs4Chars = realChars.toUcs4();
QVector ucs4Chars;
Q_FOREACH (auto c, realUcs4Chars)
if (QChar::isPrint(c) || c == QChar::fromLatin1('\t') || c == QChar::fromLatin1('\n') || c == QChar::fromLatin1('\r')) {
ucs4Chars.append(c);
}
/**
* no printable chars => nothing to insert!
*/
QString chars = QString::fromUcs4(ucs4Chars.data(), ucs4Chars.size());
if (chars.isEmpty()) {
return false;
}
// auto bracket handling
QChar closingBracket;
if (view->config()->autoBrackets()) {
// Check if entered closing bracket is already balanced
const QChar typedChar = chars.at(0);
const QChar openBracket = matchingStartBracket(typedChar);
if (!openBracket.isNull()) {
KTextEditor::Cursor curPos = view->cursorPosition();
if ((characterAt(curPos) == typedChar) && findMatchingBracket(curPos, 123/*Which value may best?*/).isValid()) {
// Do nothing
view->cursorRight();
return true;
}
}
// for newly inserted text: remember if we should auto add some bracket
if (chars.size() == 1) {
// we inserted a bracket? => remember the matching closing one
closingBracket = matchingEndBracket(typedChar);
// closing bracket for the autobracket we inserted earlier?
if (m_currentAutobraceClosingChar == typedChar && m_currentAutobraceRange) {
// do nothing
m_currentAutobraceRange.reset(nullptr);
view->cursorRight();
return true;
}
}
}
+ // Treat some char also as "auto bracket" only when we have a selection
+ if (view->selection() && closingBracket.isNull() && view->config()->encloseSelectionInChars()) {
+ const QChar typedChar = chars.at(0);
+ if (view->config()->charsToEncloseSelection().contains(typedChar)) {
+ // Always the mirrored cause no harm, but allow funny brackets
+ closingBracket = typedChar.mirroredChar();
+ }
+ }
+
editStart();
/**
* special handling if we want to add auto brackets to a selection
*/
if (view->selection() && !closingBracket.isNull()) {
std::unique_ptr selectionRange(newMovingRange(view->selectionRange()));
const int startLine = qMax(0, selectionRange->start().line());
const int endLine = qMin(selectionRange->end().line(), lastLine());
const bool blockMode = view->blockSelection() && (startLine != endLine);
if (blockMode) {
if (selectionRange->start().column() > selectionRange->end().column()) {
// Selection was done from right->left, requires special setting to ensure the new
// added brackets will not be part of the selection
selectionRange->setInsertBehaviors(MovingRange::ExpandLeft | MovingRange::ExpandRight);
}
// Add brackets to each line of the block
const int startColumn = qMin(selectionRange->start().column(), selectionRange->end().column());
const int endColumn = qMax(selectionRange->start().column(), selectionRange->end().column());
const KTextEditor::Range workingRange(startLine, startColumn, endLine, endColumn);
for (int line = startLine; line <= endLine; ++line) {
const KTextEditor::Range r(rangeOnLine(workingRange, line));
insertText(r.end(), QString(closingBracket));
view->slotTextInserted(view, r.end(), QString(closingBracket));
insertText(r.start(), chars);
view->slotTextInserted(view, r.start(), chars);
}
} else {
// No block, just add to start & end of selection
insertText(selectionRange->end(), QString(closingBracket));
view->slotTextInserted(view, selectionRange->end(), QString(closingBracket));
insertText(selectionRange->start(), chars);
view->slotTextInserted(view, selectionRange->start(), chars);
}
// Refesh selection
view->setSelection(selectionRange->toRange());
view->setCursorPosition(selectionRange->end());
editEnd();
return true;
}
/**
* normal handling
*/
if (!view->config()->persistentSelection() && view->selection()) {
view->removeSelectedText();
}
const KTextEditor::Cursor oldCur(view->cursorPosition());
const bool multiLineBlockMode = view->blockSelection() && view->selection();
if (view->currentInputMode()->overwrite()) {
// blockmode multiline selection case: remove chars in every line
const KTextEditor::Range selectionRange = view->selectionRange();
const int startLine = multiLineBlockMode ? qMax(0, selectionRange.start().line()) : view->cursorPosition().line();
const int endLine = multiLineBlockMode ? qMin(selectionRange.end().line(), lastLine()) : startLine;
const int virtualColumn = toVirtualColumn(multiLineBlockMode ? selectionRange.end() : view->cursorPosition());
for (int line = endLine; line >= startLine; --line) {
Kate::TextLine textLine = m_buffer->plainLine(line);
Q_ASSERT(textLine);
const int column = fromVirtualColumn(line, virtualColumn);
KTextEditor::Range r = KTextEditor::Range(KTextEditor::Cursor(line, column), qMin(chars.length(),
textLine->length() - column));
// replace mode needs to know what was removed so it can be restored with backspace
if (oldCur.column() < lineLength(line)) {
QChar removed = characterAt(KTextEditor::Cursor(line, column));
view->currentInputMode()->overwrittenChar(removed);
}
removeText(r);
}
}
chars = eventuallyReplaceTabs(view->cursorPosition(), chars);
if (multiLineBlockMode) {
KTextEditor::Range selectionRange = view->selectionRange();
const int startLine = qMax(0, selectionRange.start().line());
const int endLine = qMin(selectionRange.end().line(), lastLine());
const int column = toVirtualColumn(selectionRange.end());
for (int line = endLine; line >= startLine; --line) {
editInsertText(line, fromVirtualColumn(line, column), chars);
}
int newSelectionColumn = toVirtualColumn(view->cursorPosition());
selectionRange.setRange(KTextEditor::Cursor(selectionRange.start().line(), fromVirtualColumn(selectionRange.start().line(), newSelectionColumn))
, KTextEditor::Cursor(selectionRange.end().line(), fromVirtualColumn(selectionRange.end().line(), newSelectionColumn)));
view->setSelection(selectionRange);
} else {
insertText(view->cursorPosition(), chars);
}
/**
* auto bracket handling for newly inserted text
* we inserted a bracket?
* => add the matching closing one to the view + input chars
* try to preserve the cursor position
*/
bool skipAutobrace = closingBracket == QLatin1Char('\'');
if (highlight() && skipAutobrace) {
// skip adding ' in spellchecked areas, because those are text
skipAutobrace = highlight()->spellCheckingRequiredForLocation(this, view->cursorPosition() - Cursor{0, 1});
}
const auto cursorPos(view->cursorPosition());
if (!skipAutobrace && (closingBracket == QLatin1Char('\''))) {
// skip auto quotes when these looks already balanced, bug 405089
Kate::TextLine textLine = m_buffer->plainLine(cursorPos.line());
// RegEx match quote, but not excaped quote, thanks to https://stackoverflow.com/a/11819111
const int count = textLine->text().left(cursorPos.column()).count(QRegularExpression(QStringLiteral("(?plainLine(cursorPos.line());
const int count = textLine->text().left(cursorPos.column()).count(QRegularExpression(QStringLiteral("(?document()->text({cursorPos, cursorPos + Cursor{0, 1}}).trimmed();
if (nextChar.isEmpty() || !nextChar.at(0).isLetterOrNumber()) {
insertText(view->cursorPosition(), QString(closingBracket));
const auto insertedAt(view->cursorPosition());
view->setCursorPosition(cursorPos);
m_currentAutobraceRange.reset(newMovingRange({cursorPos - Cursor{0, 1}, insertedAt},
KTextEditor::MovingRange::DoNotExpand));
connect(view, &View::cursorPositionChanged,
this, &DocumentPrivate::checkCursorForAutobrace, Qt::UniqueConnection);
// add bracket to chars inserted! needed for correct signals + indent
chars.append(closingBracket);
}
m_currentAutobraceClosingChar = closingBracket;
}
// end edit session here, to have updated HL in userTypedChar!
editEnd();
// trigger indentation
KTextEditor::Cursor b(view->cursorPosition());
m_indenter->userTypedChar(view, b, chars.isEmpty() ? QChar() : chars.at(chars.length() - 1));
/**
* inform the view about the original inserted chars
*/
view->slotTextInserted(view, oldCur, chars);
return true;
}
void KTextEditor::DocumentPrivate::checkCursorForAutobrace(KTextEditor::View*, const KTextEditor::Cursor& newPos) {
if ( m_currentAutobraceRange && ! m_currentAutobraceRange->toRange().contains(newPos) ) {
m_currentAutobraceRange.clear();
}
}
void KTextEditor::DocumentPrivate::newLine(KTextEditor::ViewPrivate *v,
KTextEditor::DocumentPrivate::NewLineIndent indent)
{
editStart();
if (!v->config()->persistentSelection() && v->selection()) {
v->removeSelectedText();
v->clearSelection();
}
// query cursor position
KTextEditor::Cursor c = v->cursorPosition();
if (c.line() > lastLine()) {
c.setLine(lastLine());
}
if (c.line() < 0) {
c.setLine(0);
}
int ln = c.line();
Kate::TextLine textLine = plainKateTextLine(ln);
if (c.column() > textLine->length()) {
c.setColumn(textLine->length());
}
// first: wrap line
editWrapLine(c.line(), c.column());
// end edit session here, to have updated HL in userTypedChar!
editEnd();
// second: if "indent" is true, indent the new line, if needed...
if (indent == KTextEditor::DocumentPrivate::Indent) {
m_indenter->userTypedChar(v, v->cursorPosition(), QLatin1Char('\n'));
}
}
void KTextEditor::DocumentPrivate::transpose(const KTextEditor::Cursor &cursor)
{
Kate::TextLine textLine = m_buffer->plainLine(cursor.line());
if (!textLine || (textLine->length() < 2)) {
return;
}
uint col = cursor.column();
if (col > 0) {
col--;
}
if ((textLine->length() - col) < 2) {
return;
}
uint line = cursor.line();
QString s;
//clever swap code if first character on the line swap right&left
//otherwise left & right
s.append(textLine->at(col + 1));
s.append(textLine->at(col));
//do the swap
// do it right, never ever manipulate a textline
editStart();
editRemoveText(line, col, 2);
editInsertText(line, col, s);
editEnd();
}
void KTextEditor::DocumentPrivate::backspace(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &c)
{
if (!view->config()->persistentSelection() && view->selection()) {
KTextEditor::Range range = view->selectionRange();
editStart(); // Avoid bad selection in case of undo
if (view->blockSelection() && view->selection() && range.start().column() > 0
&& toVirtualColumn(range.start()) == toVirtualColumn(range.end())) {
// Remove one character before vertical selection line by expanding the selection
range.setStart(KTextEditor::Cursor(range.start().line(), range.start().column() - 1));
view->setSelection(range);
}
view->removeSelectedText();
editEnd();
return;
}
uint col = qMax(c.column(), 0);
uint line = qMax(c.line(), 0);
if ((col == 0) && (line == 0)) {
return;
}
const Kate::TextLine textLine = m_buffer->plainLine(line);
// don't forget this check!!!! really!!!!
if (!textLine) {
return;
}
if (col > 0) {
bool useNextBlock = false;
if (config()->backspaceIndents()) {
// backspace indents: erase to next indent position
int colX = textLine->toVirtualColumn(col, config()->tabWidth());
int pos = textLine->firstChar();
if (pos > 0) {
pos = textLine->toVirtualColumn(pos, config()->tabWidth());
}
if (pos < 0 || pos >= (int)colX) {
// only spaces on left side of cursor
indent(KTextEditor::Range(line, 0, line, 0), -1);
} else {
useNextBlock = true;
}
}
if (!config()->backspaceIndents() || useNextBlock) {
KTextEditor::Cursor beginCursor(line, 0);
KTextEditor::Cursor endCursor(line, col);
if (!view->config()->backspaceRemoveComposed()) { // Normal backspace behavior
beginCursor.setColumn(col - 1);
// move to left of surrogate pair
if (!isValidTextPosition(beginCursor)) {
Q_ASSERT(col >= 2);
beginCursor.setColumn(col - 2);
}
} else {
beginCursor.setColumn(view->textLayout(c)->previousCursorPosition(c.column()));
}
removeText(KTextEditor::Range(beginCursor, endCursor));
// in most cases cursor is moved by removeText, but we should do it manually
// for past-end-of-line cursors in block mode
view->setCursorPosition(beginCursor);
}
} else {
// col == 0: wrap to previous line
const Kate::TextLine textLine = m_buffer->plainLine(line - 1);
if (line > 0 && textLine) {
if (config()->wordWrap() && textLine->endsWith(QLatin1String(" "))) {
// gg: in hard wordwrap mode, backspace must also eat the trailing space
removeText(KTextEditor::Range(line - 1, textLine->length() - 1, line, 0));
} else {
removeText(KTextEditor::Range(line - 1, textLine->length(), line, 0));
}
}
}
if (m_currentAutobraceRange) {
const auto r = m_currentAutobraceRange->toRange();
if (r.columnWidth() == 1 && view->cursorPosition() == r.start()) {
// start parenthesis removed and range length is 1, remove end as well
del(view, view->cursorPosition());
m_currentAutobraceRange.clear();
}
}
}
void KTextEditor::DocumentPrivate::del(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &c)
{
if (!view->config()->persistentSelection() && view->selection()) {
KTextEditor::Range range = view->selectionRange();
editStart(); // Avoid bad selection in case of undo
if (view->blockSelection() && toVirtualColumn(range.start()) == toVirtualColumn(range.end())) {
// Remove one character after vertical selection line by expanding the selection
range.setEnd(KTextEditor::Cursor(range.end().line(), range.end().column() + 1));
view->setSelection(range);
}
view->removeSelectedText();
editEnd();
return;
}
if (c.column() < (int) m_buffer->plainLine(c.line())->length()) {
KTextEditor::Cursor endCursor(c.line(), view->textLayout(c)->nextCursorPosition(c.column()));
removeText(KTextEditor::Range(c, endCursor));
} else if (c.line() < lastLine()) {
removeText(KTextEditor::Range(c.line(), c.column(), c.line() + 1, 0));
}
}
void KTextEditor::DocumentPrivate::paste(KTextEditor::ViewPrivate *view, const QString &text)
{
static const QChar newLineChar(QLatin1Char('\n'));
QString s = text;
if (s.isEmpty()) {
return;
}
int lines = s.count(newLineChar);
m_undoManager->undoSafePoint();
editStart();
KTextEditor::Cursor pos = view->cursorPosition();
if (!view->config()->persistentSelection() && view->selection()) {
pos = view->selectionRange().start();
if (view->blockSelection()) {
pos = rangeOnLine(view->selectionRange(), pos.line()).start();
if (lines == 0) {
s += newLineChar;
s = s.repeated(view->selectionRange().numberOfLines() + 1);
s.chop(1);
}
}
view->removeSelectedText();
}
if (config()->ovr()) {
QStringList pasteLines = s.split(newLineChar);
if (!view->blockSelection()) {
int endColumn = (pasteLines.count() == 1 ? pos.column() : 0) + pasteLines.last().length();
removeText(KTextEditor::Range(pos,
pos.line() + pasteLines.count() - 1, endColumn));
} else {
int maxi = qMin(pos.line() + pasteLines.count(), this->lines());
for (int i = pos.line(); i < maxi; ++i) {
int pasteLength = pasteLines.at(i - pos.line()).length();
removeText(KTextEditor::Range(i, pos.column(),
i, qMin(pasteLength + pos.column(), lineLength(i))));
}
}
}
insertText(pos, s, view->blockSelection());
editEnd();
// move cursor right for block select, as the user is moved right internal
// even in that case, but user expects other behavior in block selection
// mode !
// just let cursor stay, that was it before I changed to moving ranges!
if (view->blockSelection()) {
view->setCursorPositionInternal(pos);
}
if (config()->indentPastedText()) {
KTextEditor::Range range = KTextEditor::Range(KTextEditor::Cursor(pos.line(), 0),
KTextEditor::Cursor(pos.line() + lines, 0));
m_indenter->indent(view, range);
}
if (!view->blockSelection()) {
emit charactersSemiInteractivelyInserted(pos, s);
}
m_undoManager->undoSafePoint();
}
void KTextEditor::DocumentPrivate::indent(KTextEditor::Range range, int change)
{
if (!isReadWrite()) {
return;
}
editStart();
m_indenter->changeIndent(range, change);
editEnd();
}
void KTextEditor::DocumentPrivate::align(KTextEditor::ViewPrivate *view, const KTextEditor::Range &range)
{
m_indenter->indent(view, range);
}
void KTextEditor::DocumentPrivate::insertTab(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &)
{
if (!isReadWrite()) {
return;
}
int lineLen = line(view->cursorPosition().line()).length();
KTextEditor::Cursor c = view->cursorPosition();
editStart();
if (!view->config()->persistentSelection() && view->selection()) {
view->removeSelectedText();
} else if (view->currentInputMode()->overwrite() && c.column() < lineLen) {
KTextEditor::Range r = KTextEditor::Range(view->cursorPosition(), 1);
// replace mode needs to know what was removed so it can be restored with backspace
QChar removed = line(view->cursorPosition().line()).at(r.start().column());
view->currentInputMode()->overwrittenChar(removed);
removeText(r);
}
c = view->cursorPosition();
editInsertText(c.line(), c.column(), QStringLiteral("\t"));
editEnd();
}
/*
Remove a given string at the beginning
of the current line.
*/
bool KTextEditor::DocumentPrivate::removeStringFromBeginning(int line, const QString &str)
{
Kate::TextLine textline = m_buffer->plainLine(line);
KTextEditor::Cursor cursor(line, 0);
bool there = textline->startsWith(str);
if (!there) {
cursor.setColumn(textline->firstChar());
there = textline->matchesAt(cursor.column(), str);
}
if (there) {
// Remove some chars
removeText(KTextEditor::Range(cursor, str.length()));
}
return there;
}
/*
Remove a given string at the end
of the current line.
*/
bool KTextEditor::DocumentPrivate::removeStringFromEnd(int line, const QString &str)
{
Kate::TextLine textline = m_buffer->plainLine(line);
KTextEditor::Cursor cursor(line, 0);
bool there = textline->endsWith(str);
if (there) {
cursor.setColumn(textline->length() - str.length());
} else {
cursor.setColumn(textline->lastChar() - str.length() + 1);
there = textline->matchesAt(cursor.column(), str);
}
if (there) {
// Remove some chars
removeText(KTextEditor::Range(cursor, str.length()));
}
return there;
}
/*
Replace tabs by spaces in the given string, if enabled.
*/
QString KTextEditor::DocumentPrivate::eventuallyReplaceTabs(const KTextEditor::Cursor &cursorPos, const QString &str) const
{
const bool replacetabs = config()->replaceTabsDyn();
if ( ! replacetabs ) {
return str;
}
const int indentWidth = config()->indentationWidth();
static const QLatin1Char tabChar('\t');
int column = cursorPos.column();
// The result will always be at least as long as the input
QString result;
result.reserve(str.size());
Q_FOREACH (const QChar ch, str) {
if (ch == tabChar) {
// Insert only enough spaces to align to the next indentWidth column
// This fixes bug #340212
int spacesToInsert = indentWidth - (column % indentWidth);
result += QStringLiteral(" ").repeated(spacesToInsert);
column += spacesToInsert;
} else {
// Just keep all other typed characters as-is
result += ch;
++column;
}
}
return result;
}
/*
Add to the current line a comment line mark at the beginning.
*/
void KTextEditor::DocumentPrivate::addStartLineCommentToSingleLine(int line, int attrib)
{
QString commentLineMark = highlight()->getCommentSingleLineStart(attrib);
int pos = -1;
if (highlight()->getCommentSingleLinePosition(attrib) == KSyntaxHighlighting::CommentPosition::StartOfLine) {
pos = 0;
commentLineMark += QLatin1Char(' ');
} else {
const Kate::TextLine l = kateTextLine(line);
pos = l->firstChar();
}
if (pos >= 0) {
insertText(KTextEditor::Cursor(line, pos), commentLineMark);
}
}
/*
Remove from the current line a comment line mark at
the beginning if there is one.
*/
bool KTextEditor::DocumentPrivate::removeStartLineCommentFromSingleLine(int line, int attrib)
{
const QString shortCommentMark = highlight()->getCommentSingleLineStart(attrib);
const QString longCommentMark = shortCommentMark + QLatin1Char(' ');
editStart();
// Try to remove the long comment mark first
bool removed = (removeStringFromBeginning(line, longCommentMark)
|| removeStringFromBeginning(line, shortCommentMark));
editEnd();
return removed;
}
/*
Add to the current line a start comment mark at the
beginning and a stop comment mark at the end.
*/
void KTextEditor::DocumentPrivate::addStartStopCommentToSingleLine(int line, int attrib)
{
const QString startCommentMark = highlight()->getCommentStart(attrib) + QLatin1Char(' ');
const QString stopCommentMark = QLatin1Char(' ') + highlight()->getCommentEnd(attrib);
editStart();
// Add the start comment mark
insertText(KTextEditor::Cursor(line, 0), startCommentMark);
// Go to the end of the line
const int col = m_buffer->plainLine(line)->length();
// Add the stop comment mark
insertText(KTextEditor::Cursor(line, col), stopCommentMark);
editEnd();
}
/*
Remove from the current line a start comment mark at
the beginning and a stop comment mark at the end.
*/
bool KTextEditor::DocumentPrivate::removeStartStopCommentFromSingleLine(int line, int attrib)
{
const QString shortStartCommentMark = highlight()->getCommentStart(attrib);
const QString longStartCommentMark = shortStartCommentMark + QLatin1Char(' ');
const QString shortStopCommentMark = highlight()->getCommentEnd(attrib);
const QString longStopCommentMark = QLatin1Char(' ') + shortStopCommentMark;
editStart();
// Try to remove the long start comment mark first
const bool removedStart = (removeStringFromBeginning(line, longStartCommentMark)
|| removeStringFromBeginning(line, shortStartCommentMark));
// Try to remove the long stop comment mark first
const bool removedStop = removedStart
&& (removeStringFromEnd(line, longStopCommentMark) || removeStringFromEnd(line, shortStopCommentMark));
editEnd();
return (removedStart || removedStop);
}
/*
Add to the current selection a start comment mark at the beginning
and a stop comment mark at the end.
*/
void KTextEditor::DocumentPrivate::addStartStopCommentToSelection(KTextEditor::ViewPrivate *view, int attrib)
{
const QString startComment = highlight()->getCommentStart(attrib);
const QString endComment = highlight()->getCommentEnd(attrib);
KTextEditor::Range range = view->selectionRange();
if ((range.end().column() == 0) && (range.end().line() > 0)) {
range.setEnd(KTextEditor::Cursor(range.end().line() - 1, lineLength(range.end().line() - 1)));
}
editStart();
if (!view->blockSelection()) {
insertText(range.end(), endComment);
insertText(range.start(), startComment);
} else {
for (int line = range.start().line(); line <= range.end().line(); line++) {
KTextEditor::Range subRange = rangeOnLine(range, line);
insertText(subRange.end(), endComment);
insertText(subRange.start(), startComment);
}
}
editEnd();
// selection automatically updated (MovingRange)
}
/*
Add to the current selection a comment line mark at the beginning of each line.
*/
void KTextEditor::DocumentPrivate::addStartLineCommentToSelection(KTextEditor::ViewPrivate *view, int attrib)
{
//const QString commentLineMark = highlight()->getCommentSingleLineStart(attrib) + QLatin1Char(' ');
int sl = view->selectionRange().start().line();
int el = view->selectionRange().end().line();
// if end of selection is in column 0 in last line, omit the last line
if ((view->selectionRange().end().column() == 0) && (el > 0)) {
el--;
}
editStart();
// For each line of the selection
for (int z = el; z >= sl; z--) {
//insertText (z, 0, commentLineMark);
addStartLineCommentToSingleLine(z, attrib);
}
editEnd();
// selection automatically updated (MovingRange)
}
bool KTextEditor::DocumentPrivate::nextNonSpaceCharPos(int &line, int &col)
{
for (; line < (int)m_buffer->count(); line++) {
Kate::TextLine textLine = m_buffer->plainLine(line);
if (!textLine) {
break;
}
col = textLine->nextNonSpaceChar(col);
if (col != -1) {
return true; // Next non-space char found
}
col = 0;
}
// No non-space char found
line = -1;
col = -1;
return false;
}
bool KTextEditor::DocumentPrivate::previousNonSpaceCharPos(int &line, int &col)
{
while (true) {
Kate::TextLine textLine = m_buffer->plainLine(line);
if (!textLine) {
break;
}
col = textLine->previousNonSpaceChar(col);
if (col != -1) {
return true;
}
if (line == 0) {
return false;
}
--line;
col = textLine->length();
}
// No non-space char found
line = -1;
col = -1;
return false;
}
/*
Remove from the selection a start comment mark at
the beginning and a stop comment mark at the end.
*/
bool KTextEditor::DocumentPrivate::removeStartStopCommentFromSelection(KTextEditor::ViewPrivate *view, int attrib)
{
const QString startComment = highlight()->getCommentStart(attrib);
const QString endComment = highlight()->getCommentEnd(attrib);
int sl = qMax (0, view->selectionRange().start().line());
int el = qMin (view->selectionRange().end().line(), lastLine());
int sc = view->selectionRange().start().column();
int ec = view->selectionRange().end().column();
// The selection ends on the char before selectEnd
if (ec != 0) {
--ec;
} else if (el > 0) {
--el;
ec = m_buffer->plainLine(el)->length() - 1;
}
const int startCommentLen = startComment.length();
const int endCommentLen = endComment.length();
// had this been perl or sed: s/^\s*$startComment(.+?)$endComment\s*/$2/
bool remove = nextNonSpaceCharPos(sl, sc)
&& m_buffer->plainLine(sl)->matchesAt(sc, startComment)
&& previousNonSpaceCharPos(el, ec)
&& ((ec - endCommentLen + 1) >= 0)
&& m_buffer->plainLine(el)->matchesAt(ec - endCommentLen + 1, endComment);
if (remove) {
editStart();
removeText(KTextEditor::Range(el, ec - endCommentLen + 1, el, ec + 1));
removeText(KTextEditor::Range(sl, sc, sl, sc + startCommentLen));
editEnd();
// selection automatically updated (MovingRange)
}
return remove;
}
bool KTextEditor::DocumentPrivate::removeStartStopCommentFromRegion(const KTextEditor::Cursor &start, const KTextEditor::Cursor &end, int attrib)
{
const QString startComment = highlight()->getCommentStart(attrib);
const QString endComment = highlight()->getCommentEnd(attrib);
const int startCommentLen = startComment.length();
const int endCommentLen = endComment.length();
const bool remove = m_buffer->plainLine(start.line())->matchesAt(start.column(), startComment)
&& m_buffer->plainLine(end.line())->matchesAt(end.column() - endCommentLen, endComment);
if (remove) {
editStart();
removeText(KTextEditor::Range(end.line(), end.column() - endCommentLen, end.line(), end.column()));
removeText(KTextEditor::Range(start, startCommentLen));
editEnd();
}
return remove;
}
/*
Remove from the beginning of each line of the
selection a start comment line mark.
*/
bool KTextEditor::DocumentPrivate::removeStartLineCommentFromSelection(KTextEditor::ViewPrivate *view, int attrib)
{
const QString shortCommentMark = highlight()->getCommentSingleLineStart(attrib);
const QString longCommentMark = shortCommentMark + QLatin1Char(' ');
int sl = view->selectionRange().start().line();
int el = view->selectionRange().end().line();
if ((view->selectionRange().end().column() == 0) && (el > 0)) {
el--;
}
bool removed = false;
editStart();
// For each line of the selection
for (int z = el; z >= sl; z--) {
// Try to remove the long comment mark first
removed = (removeStringFromBeginning(z, longCommentMark)
|| removeStringFromBeginning(z, shortCommentMark)
|| removed);
}
editEnd();
// selection automatically updated (MovingRange)
return removed;
}
/*
Comment or uncomment the selection or the current
line if there is no selection.
*/
void KTextEditor::DocumentPrivate::comment(KTextEditor::ViewPrivate *v, uint line, uint column, int change)
{
// skip word wrap bug #105373
const bool skipWordWrap = wordWrap();
if (skipWordWrap) {
setWordWrap(false);
}
bool hassel = v->selection();
int c = 0;
if (hassel) {
c = v->selectionRange().start().column();
}
int startAttrib = 0;
Kate::TextLine ln = kateTextLine(line);
if (c < ln->length()) {
startAttrib = ln->attribute(c);
} else if (!ln->attributesList().isEmpty()) {
startAttrib = ln->attributesList().back().attributeValue;
}
bool hasStartLineCommentMark = !(highlight()->getCommentSingleLineStart(startAttrib).isEmpty());
bool hasStartStopCommentMark = (!(highlight()->getCommentStart(startAttrib).isEmpty())
&& !(highlight()->getCommentEnd(startAttrib).isEmpty()));
if (change > 0) { // comment
if (!hassel) {
if (hasStartLineCommentMark) {
addStartLineCommentToSingleLine(line, startAttrib);
} else if (hasStartStopCommentMark) {
addStartStopCommentToSingleLine(line, startAttrib);
}
} else {
// anders: prefer single line comment to avoid nesting probs
// If the selection starts after first char in the first line
// or ends before the last char of the last line, we may use
// multiline comment markers.
// TODO We should try to detect nesting.
// - if selection ends at col 0, most likely she wanted that
// line ignored
const KTextEditor::Range sel = v->selectionRange();
if (hasStartStopCommentMark &&
(!hasStartLineCommentMark || (
(sel.start().column() > m_buffer->plainLine(sel.start().line())->firstChar()) ||
(sel.end().column() > 0 &&
sel.end().column() < (m_buffer->plainLine(sel.end().line())->length()))
))) {
addStartStopCommentToSelection(v, startAttrib);
} else if (hasStartLineCommentMark) {
addStartLineCommentToSelection(v, startAttrib);
}
}
} else { // uncomment
bool removed = false;
if (!hassel) {
removed = (hasStartLineCommentMark
&& removeStartLineCommentFromSingleLine(line, startAttrib))
|| (hasStartStopCommentMark
&& removeStartStopCommentFromSingleLine(line, startAttrib));
} else {
// anders: this seems like it will work with above changes :)
removed = (hasStartStopCommentMark
&& removeStartStopCommentFromSelection(v, startAttrib))
|| (hasStartLineCommentMark
&& removeStartLineCommentFromSelection(v, startAttrib));
}
// recursive call for toggle comment
if (!removed && change == 0) {
comment(v, line, column, 1);
}
}
if (skipWordWrap) {
setWordWrap(true); // see begin of function ::comment (bug #105373)
}
}
void KTextEditor::DocumentPrivate::transform(KTextEditor::ViewPrivate *v, const KTextEditor::Cursor &c,
KTextEditor::DocumentPrivate::TextTransform t)
{
if (v->selection()) {
editStart();
// cache the selection and cursor, so we can be sure to restore.
KTextEditor::Range selection = v->selectionRange();
KTextEditor::Range range(selection.start(), 0);
while (range.start().line() <= selection.end().line()) {
int start = 0;
int end = lineLength(range.start().line());
if (range.start().line() == selection.start().line() || v->blockSelection()) {
start = selection.start().column();
}
if (range.start().line() == selection.end().line() || v->blockSelection()) {
end = selection.end().column();
}
if (start > end) {
int swapCol = start;
start = end;
end = swapCol;
}
range.setStart(KTextEditor::Cursor(range.start().line(), start));
range.setEnd(KTextEditor::Cursor(range.end().line(), end));
QString s = text(range);
QString old = s;
if (t == Uppercase) {
s = s.toUpper();
} else if (t == Lowercase) {
s = s.toLower();
} else { // Capitalize
Kate::TextLine l = m_buffer->plainLine(range.start().line());
int p(0);
while (p < s.length()) {
// If bol or the character before is not in a word, up this one:
// 1. if both start and p is 0, upper char.
// 2. if blockselect or first line, and p == 0 and start-1 is not in a word, upper
// 3. if p-1 is not in a word, upper.
if ((! range.start().column() && ! p) ||
((range.start().line() == selection.start().line() || v->blockSelection()) &&
! p && ! highlight()->isInWord(l->at(range.start().column() - 1))) ||
(p && ! highlight()->isInWord(s.at(p - 1)))
) {
s[p] = s.at(p).toUpper();
}
p++;
}
}
if (s != old) {
removeText(range);
insertText(range.start(), s);
}
range.setBothLines(range.start().line() + 1);
}
editEnd();
// restore selection & cursor
v->setSelection(selection);
v->setCursorPosition(c);
} else { // no selection
editStart();
// get cursor
KTextEditor::Cursor cursor = c;
QString old = text(KTextEditor::Range(cursor, 1));
QString s;
switch (t) {
case Uppercase:
s = old.toUpper();
break;
case Lowercase:
s = old.toLower();
break;
case Capitalize: {
Kate::TextLine l = m_buffer->plainLine(cursor.line());
while (cursor.column() > 0 && highlight()->isInWord(l->at(cursor.column() - 1), l->attribute(cursor.column() - 1))) {
cursor.setColumn(cursor.column() - 1);
}
old = text(KTextEditor::Range(cursor, 1));
s = old.toUpper();
}
break;
default:
break;
}
removeText(KTextEditor::Range(cursor, 1));
insertText(cursor, s);
editEnd();
}
}
void KTextEditor::DocumentPrivate::joinLines(uint first, uint last)
{
// if ( first == last ) last += 1;
editStart();
int line(first);
while (first < last) {
// Normalize the whitespace in the joined lines by making sure there's
// always exactly one space between the joined lines
// This cannot be done in editUnwrapLine, because we do NOT want this
// behavior when deleting from the start of a line, just when explicitly
// calling the join command
Kate::TextLine l = kateTextLine(line);
Kate::TextLine tl = kateTextLine(line + 1);
if (!l || !tl) {
editEnd();
return;
}
int pos = tl->firstChar();
if (pos >= 0) {
if (pos != 0) {
editRemoveText(line + 1, 0, pos);
}
if (!(l->length() == 0 || l->at(l->length() - 1).isSpace())) {
editInsertText(line + 1, 0, QLatin1String(" "));
}
} else {
// Just remove the whitespace and let Kate handle the rest
editRemoveText(line + 1, 0, tl->length());
}
editUnWrapLine(line);
first++;
}
editEnd();
}
void KTextEditor::DocumentPrivate::tagLines(int start, int end)
{
foreach (KTextEditor::ViewPrivate *view, m_views) {
view->tagLines(start, end, true);
}
}
void KTextEditor::DocumentPrivate::repaintViews(bool paintOnlyDirty)
{
foreach (KTextEditor::ViewPrivate *view, m_views) {
view->repaintText(paintOnlyDirty);
}
}
/*
Bracket matching uses the following algorithm:
If in overwrite mode, match the bracket currently underneath the cursor.
Otherwise, if the character to the left is a bracket,
match it. Otherwise if the character to the right of the cursor is a
bracket, match it. Otherwise, don't match anything.
*/
KTextEditor::Range KTextEditor::DocumentPrivate::findMatchingBracket(const KTextEditor::Cursor &start, int maxLines)
{
if (maxLines < 0) {
return KTextEditor::Range::invalid();
}
Kate::TextLine textLine = m_buffer->plainLine(start.line());
if (!textLine) {
return KTextEditor::Range::invalid();
}
KTextEditor::Range range(start, start);
const QChar right = textLine->at(range.start().column());
const QChar left = textLine->at(range.start().column() - 1);
QChar bracket;
if (config()->ovr()) {
if (isBracket(right)) {
bracket = right;
} else {
return KTextEditor::Range::invalid();
}
} else if (isBracket(right)) {
bracket = right;
} else if (isBracket(left)) {
range.setStart(KTextEditor::Cursor(range.start().line(), range.start().column() - 1));
bracket = left;
} else {
return KTextEditor::Range::invalid();
}
const QChar opposite = matchingBracket(bracket);
if (opposite.isNull()) {
return KTextEditor::Range::invalid();
}
const int searchDir = isStartBracket(bracket) ? 1 : -1;
uint nesting = 0;
const int minLine = qMax(range.start().line() - maxLines, 0);
const int maxLine = qMin(range.start().line() + maxLines, documentEnd().line());
range.setEnd(range.start());
KTextEditor::DocumentCursor cursor(this);
cursor.setPosition(range.start());
int validAttr = kateTextLine(cursor.line())->attribute(cursor.column());
while (cursor.line() >= minLine && cursor.line() <= maxLine) {
if (!cursor.move(searchDir)) {
return KTextEditor::Range::invalid();
}
Kate::TextLine textLine = kateTextLine(cursor.line());
if (textLine->attribute(cursor.column()) == validAttr) {
// Check for match
QChar c = textLine->at(cursor.column());
if (c == opposite) {
if (nesting == 0) {
if (searchDir > 0) { // forward
range.setEnd(cursor.toCursor());
} else {
range.setStart(cursor.toCursor());
}
return range;
}
nesting--;
} else if (c == bracket) {
nesting++;
}
}
}
return KTextEditor::Range::invalid();
}
// helper: remove \r and \n from visible document name (bug #170876)
inline static QString removeNewLines(const QString &str)
{
QString tmp(str);
return tmp.replace(QLatin1String("\r\n"), QLatin1String(" "))
.replace(QLatin1Char('\r'), QLatin1Char(' '))
.replace(QLatin1Char('\n'), QLatin1Char(' '));
}
void KTextEditor::DocumentPrivate::updateDocName()
{
// if the name is set, and starts with FILENAME, it should not be changed!
if (! url().isEmpty()
&& (m_docName == removeNewLines(url().fileName()) ||
m_docName.startsWith(removeNewLines(url().fileName()) + QLatin1String(" (")))) {
return;
}
int count = -1;
foreach (KTextEditor::DocumentPrivate *doc, KTextEditor::EditorPrivate::self()->kateDocuments()) {
if ((doc != this) && (doc->url().fileName() == url().fileName()))
if (doc->m_docNameNumber > count) {
count = doc->m_docNameNumber;
}
}
m_docNameNumber = count + 1;
QString oldName = m_docName;
m_docName = removeNewLines(url().fileName());
m_isUntitled = m_docName.isEmpty();
if (m_isUntitled) {
m_docName = i18n("Untitled");
}
if (m_docNameNumber > 0) {
m_docName = QString(m_docName + QLatin1String(" (%1)")).arg(m_docNameNumber + 1);
}
/**
* avoid to emit this, if name doesn't change!
*/
if (oldName != m_docName) {
emit documentNameChanged(this);
}
}
void KTextEditor::DocumentPrivate::slotModifiedOnDisk(KTextEditor::View * /*v*/)
{
if (url().isEmpty() || !m_modOnHd) {
return;
}
if (!isModified() && isAutoReload()) {
onModOnHdAutoReload();
return;
}
if (!m_fileChangedDialogsActivated || m_modOnHdHandler) {
return;
}
// don't ask the user again and again the same thing
if (m_modOnHdReason == m_prevModOnHdReason) {
return;
}
m_prevModOnHdReason = m_modOnHdReason;
m_modOnHdHandler = new KateModOnHdPrompt(this, m_modOnHdReason, reasonedMOHString());
connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::saveAsTriggered, this, &DocumentPrivate::onModOnHdSaveAs);
connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::closeTriggered, this, &DocumentPrivate::onModOnHdClose);
connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::reloadTriggered, this, &DocumentPrivate::onModOnHdReload);
connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::autoReloadTriggered, this, &DocumentPrivate::onModOnHdAutoReload);
connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::ignoreTriggered, this, &DocumentPrivate::onModOnHdIgnore);
}
void KTextEditor::DocumentPrivate::onModOnHdSaveAs()
{
m_modOnHd = false;
QWidget *parentWidget(dialogParent());
const QUrl res = QFileDialog::getSaveFileUrl(parentWidget, i18n("Save File"), url());
if (!res.isEmpty()) {
if (! saveAs(res)) {
KMessageBox::error(parentWidget, i18n("Save failed"));
m_modOnHd = true;
} else {
delete m_modOnHdHandler;
m_prevModOnHdReason = OnDiskUnmodified;
emit modifiedOnDisk(this, false, OnDiskUnmodified);
}
} else { // the save as dialog was canceled, we are still modified on disk
m_modOnHd = true;
}
}
void KTextEditor::DocumentPrivate::onModOnHdClose()
{
// avoid prompt in closeUrl()
m_fileChangedDialogsActivated = false;
// close the file without prompt confirmation
closeUrl();
// Useful for kate only
closeDocumentInApplication();
}
void KTextEditor::DocumentPrivate::onModOnHdReload()
{
m_modOnHd = false;
m_prevModOnHdReason = OnDiskUnmodified;
emit modifiedOnDisk(this, false, OnDiskUnmodified);
documentReload();
delete m_modOnHdHandler;
}
void KTextEditor::DocumentPrivate::autoReloadToggled(bool b)
{
m_autoReloadMode->setChecked(b);
if (b) {
connect(&m_modOnHdTimer, &QTimer::timeout, this, &DocumentPrivate::onModOnHdAutoReload);
} else {
disconnect(&m_modOnHdTimer, &QTimer::timeout, this, &DocumentPrivate::onModOnHdAutoReload);
}
}
bool KTextEditor::DocumentPrivate::isAutoReload()
{
return m_autoReloadMode->isChecked();
}
void KTextEditor::DocumentPrivate::delayAutoReload()
{
if (isAutoReload()) {
m_autoReloadThrottle.start();
}
}
void KTextEditor::DocumentPrivate::onModOnHdAutoReload()
{
if (m_modOnHdHandler) {
delete m_modOnHdHandler;
autoReloadToggled(true);
}
if (!isAutoReload()) {
return;
}
if (m_modOnHd && !m_reloading && !m_autoReloadThrottle.isActive()) {
m_modOnHd = false;
m_prevModOnHdReason = OnDiskUnmodified;
emit modifiedOnDisk(this, false, OnDiskUnmodified);
documentReload();
m_autoReloadThrottle.start();
}
}
void KTextEditor::DocumentPrivate::onModOnHdIgnore()
{
// ignore as long as m_prevModOnHdReason == m_modOnHdReason
delete m_modOnHdHandler;
}
void KTextEditor::DocumentPrivate::setModifiedOnDisk(ModifiedOnDiskReason reason)
{
m_modOnHdReason = reason;
m_modOnHd = (reason != OnDiskUnmodified);
emit modifiedOnDisk(this, (reason != OnDiskUnmodified), reason);
}
class KateDocumentTmpMark
{
public:
QString line;
KTextEditor::Mark mark;
};
void KTextEditor::DocumentPrivate::setModifiedOnDiskWarning(bool on)
{
m_fileChangedDialogsActivated = on;
}
bool KTextEditor::DocumentPrivate::documentReload()
{
if (url().isEmpty()) {
return false;
}
// typically, the message for externally modified files is visible. Since it
// does not make sense showing an additional dialog, just hide the message.
delete m_modOnHdHandler;
emit aboutToReload(this);
QList tmp;
for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) {
KateDocumentTmpMark m;
m.line = line(i.value()->line);
m.mark = *i.value();
tmp.append(m);
}
const QString oldMode = mode();
const bool byUser = m_fileTypeSetByUser;
const QString hl_mode = highlightingMode();
m_storedVariables.clear();
// save cursor positions for all views
QHash cursorPositions;
for (auto it = m_views.constBegin(); it != m_views.constEnd(); ++it) {
auto v = it.value();
cursorPositions.insert(v, v->cursorPosition());
}
m_reloading = true;
KTextEditor::DocumentPrivate::openUrl(url());
// reset some flags only valid for one reload!
m_userSetEncodingForNextReload = false;
// restore cursor positions for all views
for (auto it = m_views.constBegin(); it != m_views.constEnd(); ++it) {
auto v = it.value();
setActiveView(v);
v->setCursorPosition(cursorPositions.value(v));
if (v->isVisible()) {
v->repaintText(false);
}
}
for (int z = 0; z < tmp.size(); z++) {
if (z < lines()) {
if (line(tmp.at(z).mark.line) == tmp.at(z).line) {
setMark(tmp.at(z).mark.line, tmp.at(z).mark.type);
}
}
}
if (byUser) {
setMode(oldMode);
}
setHighlightingMode(hl_mode);
emit reloaded(this);
return true;
}
bool KTextEditor::DocumentPrivate::documentSave()
{
if (!url().isValid() || !isReadWrite()) {
return documentSaveAs();
}
return save();
}
bool KTextEditor::DocumentPrivate::documentSaveAs()
{
const QUrl saveUrl = QFileDialog::getSaveFileUrl(dialogParent(), i18n("Save File"), url());
if (saveUrl.isEmpty()) {
return false;
}
return saveAs(saveUrl);
}
bool KTextEditor::DocumentPrivate::documentSaveAsWithEncoding(const QString &encoding)
{
const QUrl saveUrl = QFileDialog::getSaveFileUrl(dialogParent(), i18n("Save File"), url());
if (saveUrl.isEmpty()) {
return false;
}
setEncoding(encoding);
return saveAs(saveUrl);
}
bool KTextEditor::DocumentPrivate::documentSaveCopyAs()
{
const QUrl saveUrl = QFileDialog::getSaveFileUrl(dialogParent(), i18n("Save Copy of File"), url());
if (saveUrl.isEmpty()) {
return false;
}
QTemporaryFile file;
if (!file.open()) {
return false;
}
if (!m_buffer->saveFile(file.fileName())) {
KMessageBox::error(dialogParent(), i18n("The document could not be saved, as it was not possible to write to %1.\n\nCheck that you have write access to this file or that enough disk space is available.", this->url().toDisplayString(QUrl::PreferLocalFile)));
return false;
}
// get the right permissions, start with safe default
KIO::StatJob *statJob = KIO::stat(url(), KIO::StatJob::SourceSide, 2);
KJobWidgets::setWindow(statJob, QApplication::activeWindow());
int permissions = -1;
if (statJob->exec()) {
permissions = KFileItem(statJob->statResult(), url()).permissions();
}
// KIO move, important: allow overwrite, we checked above!
KIO::FileCopyJob *job = KIO::file_copy(QUrl::fromLocalFile(file.fileName()), saveUrl, permissions, KIO::Overwrite);
KJobWidgets::setWindow(job, QApplication::activeWindow());
return job->exec();
}
void KTextEditor::DocumentPrivate::setWordWrap(bool on)
{
config()->setWordWrap(on);
}
bool KTextEditor::DocumentPrivate::wordWrap() const
{
return config()->wordWrap();
}
void KTextEditor::DocumentPrivate::setWordWrapAt(uint col)
{
config()->setWordWrapAt(col);
}
unsigned int KTextEditor::DocumentPrivate::wordWrapAt() const
{
return config()->wordWrapAt();
}
void KTextEditor::DocumentPrivate::setPageUpDownMovesCursor(bool on)
{
config()->setPageUpDownMovesCursor(on);
}
bool KTextEditor::DocumentPrivate::pageUpDownMovesCursor() const
{
return config()->pageUpDownMovesCursor();
}
//END
bool KTextEditor::DocumentPrivate::setEncoding(const QString &e)
{
return m_config->setEncoding(e);
}
QString KTextEditor::DocumentPrivate::encoding() const
{
return m_config->encoding();
}
void KTextEditor::DocumentPrivate::updateConfig()
{
m_undoManager->updateConfig();
// switch indenter if needed and update config....
m_indenter->setMode(m_config->indentationMode());
m_indenter->updateConfig();
// set tab width there, too
m_buffer->setTabWidth(config()->tabWidth());
// update all views, does tagAll and updateView...
foreach (KTextEditor::ViewPrivate *view, m_views) {
view->updateDocumentConfig();
}
// update on-the-fly spell checking as spell checking defaults might have changes
if (m_onTheFlyChecker) {
m_onTheFlyChecker->updateConfig();
}
emit configChanged();
}
//BEGIN Variable reader
// "local variable" feature by anders, 2003
/* TODO
add config options (how many lines to read, on/off)
add interface for plugins/apps to set/get variables
add view stuff
*/
void KTextEditor::DocumentPrivate::readVariables(bool onlyViewAndRenderer)
{
if (!onlyViewAndRenderer) {
m_config->configStart();
}
// views!
KTextEditor::ViewPrivate *v;
foreach (v, m_views) {
v->config()->configStart();
v->renderer()->config()->configStart();
}
// read a number of lines in the top/bottom of the document
for (int i = 0; i < qMin(9, lines()); ++i) {
readVariableLine(line(i), onlyViewAndRenderer);
}
if (lines() > 10) {
for (int i = qMax(10, lines() - 10); i < lines(); i++) {
readVariableLine(line(i), onlyViewAndRenderer);
}
}
if (!onlyViewAndRenderer) {
m_config->configEnd();
}
foreach (v, m_views) {
v->config()->configEnd();
v->renderer()->config()->configEnd();
}
}
void KTextEditor::DocumentPrivate::readVariableLine(QString t, bool onlyViewAndRenderer)
{
static const QRegularExpression kvLine(QStringLiteral("kate:(.*)"));
static const QRegularExpression kvLineWildcard(QStringLiteral("kate-wildcard\\((.*)\\):(.*)"));
static const QRegularExpression kvLineMime(QStringLiteral("kate-mimetype\\((.*)\\):(.*)"));
static const QRegularExpression kvVar(QStringLiteral("([\\w\\-]+)\\s+([^;]+)"));
// simple check first, no regex
// no kate inside, no vars, simple...
if (!t.contains(QLatin1String("kate"))) {
return;
}
// found vars, if any
QString s;
// now, try first the normal ones
auto match = kvLine.match(t);
if (match.hasMatch()) {
s = match.captured(1);
//qCDebug(LOG_KTE) << "normal variable line kate: matched: " << s;
} else if ((match = kvLineWildcard.match(t)).hasMatch()) { // regex given
const QStringList wildcards(match.captured(1).split(QLatin1Char(';'), QString::SkipEmptyParts));
const QString nameOfFile = url().fileName();
bool found = false;
foreach (const QString &pattern, wildcards) {
QRegExp wildcard(pattern, Qt::CaseSensitive, QRegExp::Wildcard);
found = wildcard.exactMatch(nameOfFile);
if (found) {
break;
}
}
// nothing usable found...
if (!found) {
return;
}
s = match.captured(2);
//qCDebug(LOG_KTE) << "guarded variable line kate-wildcard: matched: " << s;
} else if ((match = kvLineMime.match(t)).hasMatch()) { // mime-type given
const QStringList types(match.captured(1).split(QLatin1Char(';'), QString::SkipEmptyParts));
// no matching type found
if (!types.contains(mimeType())) {
return;
}
s = match.captured(2);
//qCDebug(LOG_KTE) << "guarded variable line kate-mimetype: matched: " << s;
} else { // nothing found
return;
}
// view variable names
static const auto vvl = {
QLatin1String("dynamic-word-wrap")
, QLatin1String("dynamic-word-wrap-indicators")
, QLatin1String("line-numbers")
, QLatin1String("icon-border")
, QLatin1String("folding-markers")
, QLatin1String("folding-preview")
, QLatin1String("bookmark-sorting")
, QLatin1String("auto-center-lines")
, QLatin1String("icon-bar-color")
, QLatin1String("scrollbar-minimap")
, QLatin1String("scrollbar-preview")
// renderer
, QLatin1String("background-color")
, QLatin1String("selection-color")
, QLatin1String("current-line-color")
, QLatin1String("bracket-highlight-color")
, QLatin1String("word-wrap-marker-color")
, QLatin1String("font")
, QLatin1String("font-size")
, QLatin1String("scheme")
};
int spaceIndent = -1; // for backward compatibility; see below
bool replaceTabsSet = false;
int startPos(0);
QString var, val;
while ((match = kvVar.match(s, startPos)).hasMatch()) {
startPos = match.capturedEnd(0);
var = match.captured(1);
val = match.captured(2).trimmed();
bool state; // store booleans here
int n; // store ints here
// only apply view & renderer config stuff
if (onlyViewAndRenderer) {
if (contains(vvl, var)) { // FIXME define above
setViewVariable(var, val);
}
} else {
// BOOL SETTINGS
if (var == QLatin1String("word-wrap") && checkBoolValue(val, &state)) {
setWordWrap(state); // ??? FIXME CHECK
}
// KateConfig::configFlags
// FIXME should this be optimized to only a few calls? how?
else if (var == QLatin1String("backspace-indents") && checkBoolValue(val, &state)) {
m_config->setBackspaceIndents(state);
} else if (var == QLatin1String("indent-pasted-text") && checkBoolValue(val, &state)) {
m_config->setIndentPastedText(state);
} else if (var == QLatin1String("replace-tabs") && checkBoolValue(val, &state)) {
m_config->setReplaceTabsDyn(state);
replaceTabsSet = true; // for backward compatibility; see below
} else if (var == QLatin1String("remove-trailing-space") && checkBoolValue(val, &state)) {
qCWarning(LOG_KTE) << i18n("Using deprecated modeline 'remove-trailing-space'. "
"Please replace with 'remove-trailing-spaces modified;', see "
"https://docs.kde.org/stable5/en/applications/katepart/config-variables.html#variable-remove-trailing-spaces");
m_config->setRemoveSpaces(state ? 1 : 0);
} else if (var == QLatin1String("replace-trailing-space-save") && checkBoolValue(val, &state)) {
qCWarning(LOG_KTE) << i18n("Using deprecated modeline 'replace-trailing-space-save'. "
"Please replace with 'remove-trailing-spaces all;', see "
"https://docs.kde.org/stable5/en/applications/katepart/config-variables.html#variable-remove-trailing-spaces");
m_config->setRemoveSpaces(state ? 2 : 0);
} else if (var == QLatin1String("overwrite-mode") && checkBoolValue(val, &state)) {
m_config->setOvr(state);
} else if (var == QLatin1String("keep-extra-spaces") && checkBoolValue(val, &state)) {
m_config->setKeepExtraSpaces(state);
} else if (var == QLatin1String("tab-indents") && checkBoolValue(val, &state)) {
m_config->setTabIndents(state);
} else if (var == QLatin1String("show-tabs") && checkBoolValue(val, &state)) {
m_config->setShowTabs(state);
} else if (var == QLatin1String("show-trailing-spaces") && checkBoolValue(val, &state)) {
m_config->setShowSpaces(state ? KateDocumentConfig::Trailing : KateDocumentConfig::None);
} else if (var == QLatin1String("space-indent") && checkBoolValue(val, &state)) {
// this is for backward compatibility; see below
spaceIndent = state;
} else if (var == QLatin1String("smart-home") && checkBoolValue(val, &state)) {
m_config->setSmartHome(state);
} else if (var == QLatin1String("newline-at-eof") && checkBoolValue(val, &state)) {
m_config->setNewLineAtEof(state);
}
// INTEGER SETTINGS
else if (var == QLatin1String("tab-width") && checkIntValue(val, &n)) {
m_config->setTabWidth(n);
} else if (var == QLatin1String("indent-width") && checkIntValue(val, &n)) {
m_config->setIndentationWidth(n);
} else if (var == QLatin1String("indent-mode")) {
m_config->setIndentationMode(val);
} else if (var == QLatin1String("word-wrap-column") && checkIntValue(val, &n) && n > 0) { // uint, but hard word wrap at 0 will be no fun ;)
m_config->setWordWrapAt(n);
}
// STRING SETTINGS
else if (var == QLatin1String("eol") || var == QLatin1String("end-of-line")) {
const auto l = { QLatin1String("unix"), QLatin1String("dos"), QLatin1String("mac") };
if ((n = indexOf(l, val.toLower())) != -1) {
/**
* set eol + avoid that it is overwritten by auto-detection again!
* this fixes e.g. .kateconfig files with // kate: eol dos; to work, bug 365705
*/
m_config->setEol(n);
m_config->setAllowEolDetection(false);
}
} else if (var == QLatin1String("bom") || var == QLatin1String("byte-order-mark") || var == QLatin1String("byte-order-marker")) {
if (checkBoolValue(val, &state)) {
m_config->setBom(state);
}
} else if (var == QLatin1String("remove-trailing-spaces")) {
val = val.toLower();
if (val == QLatin1String("1") || val == QLatin1String("modified") || val == QLatin1String("mod") || val == QLatin1String("+")) {
m_config->setRemoveSpaces(1);
} else if (val == QLatin1String("2") || val == QLatin1String("all") || val == QLatin1String("*")) {
m_config->setRemoveSpaces(2);
} else {
m_config->setRemoveSpaces(0);
}
} else if (var == QLatin1String("syntax") || var == QLatin1String("hl")) {
setHighlightingMode(val);
} else if (var == QLatin1String("mode")) {
setMode(val);
} else if (var == QLatin1String("encoding")) {
setEncoding(val);
} else if (var == QLatin1String("default-dictionary")) {
setDefaultDictionary(val);
} else if (var == QLatin1String("automatic-spell-checking") && checkBoolValue(val, &state)) {
onTheFlySpellCheckingEnabled(state);
}
// VIEW SETTINGS
else if (contains(vvl, var)) {
setViewVariable(var, val);
} else {
m_storedVariables.insert(var, val);
}
}
}
// Backward compatibility
// If space-indent was set, but replace-tabs was not set, we assume
// that the user wants to replace tabulators and set that flag.
// If both were set, replace-tabs has precedence.
// At this point spaceIndent is -1 if it was never set,
// 0 if it was set to off, and 1 if it was set to on.
// Note that if onlyViewAndRenderer was requested, spaceIndent is -1.
if (!replaceTabsSet && spaceIndent >= 0) {
m_config->setReplaceTabsDyn(spaceIndent > 0);
}
}
void KTextEditor::DocumentPrivate::setViewVariable(QString var, QString val)
{
KTextEditor::ViewPrivate *v;
bool state;
int n;
QColor c;
foreach (v, m_views) {
// First, try the new config interface
QVariant help(val); // Special treatment to catch "on"/"off"
if (checkBoolValue(val, &state)) {
help = state;
}
if (v->config()->setValue(var, help)) {
} else if (v->renderer()->config()->setValue(var, help)) {
// No success? Go the old way
} else if (var == QLatin1String("dynamic-word-wrap") && checkBoolValue(val, &state)) {
v->config()->setDynWordWrap(state);
} else if (var == QLatin1String("block-selection") && checkBoolValue(val, &state)) {
v->setBlockSelection(state);
//else if ( var = "dynamic-word-wrap-indicators" )
} else if (var == QLatin1String("icon-bar-color") && checkColorValue(val, c)) {
v->renderer()->config()->setIconBarColor(c);
}
// RENDERER
else if (var == QLatin1String("background-color") && checkColorValue(val, c)) {
v->renderer()->config()->setBackgroundColor(c);
} else if (var == QLatin1String("selection-color") && checkColorValue(val, c)) {
v->renderer()->config()->setSelectionColor(c);
} else if (var == QLatin1String("current-line-color") && checkColorValue(val, c)) {
v->renderer()->config()->setHighlightedLineColor(c);
} else if (var == QLatin1String("bracket-highlight-color") && checkColorValue(val, c)) {
v->renderer()->config()->setHighlightedBracketColor(c);
} else if (var == QLatin1String("word-wrap-marker-color") && checkColorValue(val, c)) {
v->renderer()->config()->setWordWrapMarkerColor(c);
} else if (var == QLatin1String("font") || (checkIntValue(val, &n) && var == QLatin1String("font-size"))) {
QFont _f(v->renderer()->config()->font());
if (var == QLatin1String("font")) {
_f.setFamily(val);
_f.setFixedPitch(QFont(val).fixedPitch());
} else {
_f.setPointSize(n);
}
v->renderer()->config()->setFont(_f);
} else if (var == QLatin1String("scheme")) {
v->renderer()->config()->setSchema(val);
}
}
}
bool KTextEditor::DocumentPrivate::checkBoolValue(QString val, bool *result)
{
val = val.trimmed().toLower();
static const auto trueValues = { QLatin1String("1"), QLatin1String("on"), QLatin1String("true") };
if (contains(trueValues, val)) {
*result = true;
return true;
}
static const auto falseValues = { QLatin1String("0"), QLatin1String("off"), QLatin1String("false") };
if (contains(falseValues, val)) {
*result = false;
return true;
}
return false;
}
bool KTextEditor::DocumentPrivate::checkIntValue(QString val, int *result)
{
bool ret(false);
*result = val.toInt(&ret);
return ret;
}
bool KTextEditor::DocumentPrivate::checkColorValue(QString val, QColor &c)
{
c.setNamedColor(val);
return c.isValid();
}
// KTextEditor::variable
QString KTextEditor::DocumentPrivate::variable(const QString &name) const
{
return m_storedVariables.value(name, QString());
}
void KTextEditor::DocumentPrivate::setVariable(const QString &name, const QString &value)
{
QString s = QStringLiteral("kate: ");
s.append(name);
s.append(QLatin1Char(' '));
s.append(value);
readVariableLine(s);
}
//END
void KTextEditor::DocumentPrivate::slotModOnHdDirty(const QString &path)
{
if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskModified)) {
m_modOnHd = true;
m_modOnHdReason = OnDiskModified;
if (!m_modOnHdTimer.isActive()) {
m_modOnHdTimer.start();
}
}
}
void KTextEditor::DocumentPrivate::slotModOnHdCreated(const QString &path)
{
if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskCreated)) {
m_modOnHd = true;
m_modOnHdReason = OnDiskCreated;
if (!m_modOnHdTimer.isActive()) {
m_modOnHdTimer.start();
}
}
}
void KTextEditor::DocumentPrivate::slotModOnHdDeleted(const QString &path)
{
if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskDeleted)) {
m_modOnHd = true;
m_modOnHdReason = OnDiskDeleted;
if (!m_modOnHdTimer.isActive()) {
m_modOnHdTimer.start();
}
}
}
void KTextEditor::DocumentPrivate::slotDelayedHandleModOnHd()
{
// compare git hash with the one we have (if we have one)
const QByteArray oldDigest = checksum();
if (!oldDigest.isEmpty() && !url().isEmpty() && url().isLocalFile()) {
/**
* if current checksum == checksum of new file => unmodified
*/
if (m_modOnHdReason != OnDiskDeleted && createDigest() && oldDigest == checksum()) {
m_modOnHd = false;
m_modOnHdReason = OnDiskUnmodified;
m_prevModOnHdReason = OnDiskUnmodified;
}
#if LIBGIT2_FOUND
/**
* if still modified, try to take a look at git
* skip that, if document is modified!
* only do that, if the file is still there, else reload makes no sense!
*/
if (m_modOnHd && !isModified() && QFile::exists(url().toLocalFile())) {
/**
* try to discover the git repo of this file
* libgit2 docs state that UTF-8 is the right encoding, even on windows
* I hope that is correct!
*/
git_repository *repository = nullptr;
const QByteArray utf8Path = url().toLocalFile().toUtf8();
if (git_repository_open_ext(&repository, utf8Path.constData(), 0, nullptr) == 0) {
/**
* if we have repo, convert the git hash to an OID
*/
git_oid oid;
if (git_oid_fromstr(&oid, oldDigest.toHex().data()) == 0) {
/**
* finally: is there a blob for this git hash?
*/
git_blob *blob = nullptr;
if (git_blob_lookup(&blob, repository, &oid) == 0) {
/**
* this hash exists still in git => just reload
*/
m_modOnHd = false;
m_modOnHdReason = OnDiskUnmodified;
m_prevModOnHdReason = OnDiskUnmodified;
documentReload();
}
git_blob_free(blob);
}
}
git_repository_free(repository);
}
#endif
}
/**
* emit our signal to the outside!
*/
emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason);
}
QByteArray KTextEditor::DocumentPrivate::checksum() const
{
return m_buffer->digest();
}
bool KTextEditor::DocumentPrivate::createDigest()
{
QByteArray digest;
if (url().isLocalFile()) {
QFile f(url().toLocalFile());
if (f.open(QIODevice::ReadOnly)) {
// init the hash with the git header
QCryptographicHash crypto(QCryptographicHash::Sha1);
const QString header = QStringLiteral("blob %1").arg(f.size());
crypto.addData(header.toLatin1() + '\0');
while (!f.atEnd()) {
crypto.addData(f.read(256 * 1024));
}
digest = crypto.result();
}
}
/**
* set new digest
*/
m_buffer->setDigest(digest);
return !digest.isEmpty();
}
QString KTextEditor::DocumentPrivate::reasonedMOHString() const
{
// squeeze path
const QString str = KStringHandler::csqueeze(url().toDisplayString(QUrl::PreferLocalFile));
switch (m_modOnHdReason) {
case OnDiskModified:
return i18n("The file '%1' was modified by another program.", str);
break;
case OnDiskCreated:
return i18n("The file '%1' was created by another program.", str);
break;
case OnDiskDeleted:
return i18n("The file '%1' was deleted by another program.", str);
break;
default:
return QString();
}
Q_UNREACHABLE();
return QString();
}
void KTextEditor::DocumentPrivate::removeTrailingSpaces()
{
const int remove = config()->removeSpaces();
if (remove == 0) {
return;
}
// temporarily disable static word wrap (see bug #328900)
const bool wordWrapEnabled = config()->wordWrap();
if (wordWrapEnabled) {
setWordWrap(false);
}
editStart();
for (int line = 0; line < lines(); ++line) {
Kate::TextLine textline = plainKateTextLine(line);
// remove trailing spaces in entire document, remove = 2
// remove trailing spaces of touched lines, remove = 1
// remove trailing spaces of lines saved on disk, remove = 1
if (remove == 2 || textline->markedAsModified() || textline->markedAsSavedOnDisk()) {
const int p = textline->lastChar() + 1;
const int l = textline->length() - p;
if (l > 0) {
editRemoveText(line, p, l);
}
}
}
editEnd();
// enable word wrap again, if it was enabled (see bug #328900)
if (wordWrapEnabled) {
setWordWrap(true); // see begin of this function
}
}
void KTextEditor::DocumentPrivate::updateFileType(const QString &newType, bool user)
{
if (user || !m_fileTypeSetByUser) {
if (!newType.isEmpty()) {
// remember that we got set by user
m_fileTypeSetByUser = user;
m_fileType = newType;
m_config->configStart();
// NOTE: if the user changes the Mode, the Highlighting also changes.
// m_hlSetByUser avoids resetting the highlight when saving the document, if
// the current hl isn't stored (eg, in sftp:// or fish:// files) (see bug #407763)
if ((user || !m_hlSetByUser) && !KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).hl.isEmpty()) {
int hl(KateHlManager::self()->nameFind(KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).hl));
if (hl >= 0) {
m_buffer->setHighlight(hl);
}
}
/**
* set the indentation mode, if any in the mode...
* and user did not set it before!
* NOTE: KateBuffer::setHighlight() also sets the indentation.
*/
if (!m_indenterSetByUser && !KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).indenter.isEmpty()) {
config()->setIndentationMode(KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).indenter);
}
// views!
KTextEditor::ViewPrivate *v;
foreach (v, m_views) {
v->config()->configStart();
v->renderer()->config()->configStart();
}
bool bom_settings = false;
if (m_bomSetByUser) {
bom_settings = m_config->bom();
}
readVariableLine(KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).varLine);
if (m_bomSetByUser) {
m_config->setBom(bom_settings);
}
m_config->configEnd();
foreach (v, m_views) {
v->config()->configEnd();
v->renderer()->config()->configEnd();
}
}
}
// fixme, make this better...
emit modeChanged(this);
}
void KTextEditor::DocumentPrivate::slotQueryClose_save(bool *handled, bool *abortClosing)
{
*handled = true;
*abortClosing = true;
if (this->url().isEmpty()) {
QWidget *parentWidget(dialogParent());
const QUrl res = QFileDialog::getSaveFileUrl(parentWidget, i18n("Save File"));
if (res.isEmpty()) {
*abortClosing = true;
return;
}
saveAs(res);
*abortClosing = false;
} else {
save();
*abortClosing = false;
}
}
//BEGIN KTextEditor::ConfigInterface
// BEGIN ConfigInterface stff
QStringList KTextEditor::DocumentPrivate::configKeys() const
{
/**
* expose all internally registered keys of the KateDocumentConfig
*/
return m_config->configKeys();
}
QVariant KTextEditor::DocumentPrivate::configValue(const QString &key)
{
/**
* just dispatch to internal key => value lookup
*/
return m_config->value(key);
}
void KTextEditor::DocumentPrivate::setConfigValue(const QString &key, const QVariant &value)
{
/**
* just dispatch to internal key + value set
*/
m_config->setValue(key, value);
}
//END KTextEditor::ConfigInterface
KTextEditor::Cursor KTextEditor::DocumentPrivate::documentEnd() const
{
return KTextEditor::Cursor(lastLine(), lineLength(lastLine()));
}
bool KTextEditor::DocumentPrivate::replaceText(const KTextEditor::Range &range, const QString &s, bool block)
{
// TODO more efficient?
editStart();
bool changed = removeText(range, block);
changed |= insertText(range.start(), s, block);
editEnd();
return changed;
}
KateHighlighting *KTextEditor::DocumentPrivate::highlight() const
{
return m_buffer->highlight();
}
Kate::TextLine KTextEditor::DocumentPrivate::kateTextLine(int i)
{
m_buffer->ensureHighlighted(i);
return m_buffer->plainLine(i);
}
Kate::TextLine KTextEditor::DocumentPrivate::plainKateTextLine(int i)
{
return m_buffer->plainLine(i);
}
bool KTextEditor::DocumentPrivate::isEditRunning() const
{
return editIsRunning;
}
void KTextEditor::DocumentPrivate::setUndoMergeAllEdits(bool merge)
{
if (merge && m_undoMergeAllEdits) {
// Don't add another undo safe point: it will override our current one,
// meaning we'll need two undo's to get back there - which defeats the object!
return;
}
m_undoManager->undoSafePoint();
m_undoManager->setAllowComplexMerge(merge);
m_undoMergeAllEdits = merge;
}
//BEGIN KTextEditor::MovingInterface
KTextEditor::MovingCursor *KTextEditor::DocumentPrivate::newMovingCursor(const KTextEditor::Cursor &position, KTextEditor::MovingCursor::InsertBehavior insertBehavior)
{
return new Kate::TextCursor(buffer(), position, insertBehavior);
}
KTextEditor::MovingRange *KTextEditor::DocumentPrivate::newMovingRange(const KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors, KTextEditor::MovingRange::EmptyBehavior emptyBehavior)
{
return new Kate::TextRange(buffer(), range, insertBehaviors, emptyBehavior);
}
qint64 KTextEditor::DocumentPrivate::revision() const
{
return m_buffer->history().revision();
}
qint64 KTextEditor::DocumentPrivate::lastSavedRevision() const
{
return m_buffer->history().lastSavedRevision();
}
void KTextEditor::DocumentPrivate::lockRevision(qint64 revision)
{
m_buffer->history().lockRevision(revision);
}
void KTextEditor::DocumentPrivate::unlockRevision(qint64 revision)
{
m_buffer->history().unlockRevision(revision);
}
void KTextEditor::DocumentPrivate::transformCursor(int &line, int &column, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision)
{
m_buffer->history().transformCursor(line, column, insertBehavior, fromRevision, toRevision);
}
void KTextEditor::DocumentPrivate::transformCursor(KTextEditor::Cursor &cursor, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision)
{
int line = cursor.line(), column = cursor.column();
m_buffer->history().transformCursor(line, column, insertBehavior, fromRevision, toRevision);
cursor.setLine(line);
cursor.setColumn(column);
}
void KTextEditor::DocumentPrivate::transformRange(KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors, KTextEditor::MovingRange::EmptyBehavior emptyBehavior, qint64 fromRevision, qint64 toRevision)
{
m_buffer->history().transformRange(range, insertBehaviors, emptyBehavior, fromRevision, toRevision);
}
//END
//BEGIN KTextEditor::AnnotationInterface
void KTextEditor::DocumentPrivate::setAnnotationModel(KTextEditor::AnnotationModel *model)
{
KTextEditor::AnnotationModel *oldmodel = m_annotationModel;
m_annotationModel = model;
emit annotationModelChanged(oldmodel, m_annotationModel);
}
KTextEditor::AnnotationModel *KTextEditor::DocumentPrivate::annotationModel() const
{
return m_annotationModel;
}
//END KTextEditor::AnnotationInterface
//TAKEN FROM kparts.h
bool KTextEditor::DocumentPrivate::queryClose()
{
if (!isReadWrite() || !isModified()) {
return true;
}
QString docName = documentName();
int res = KMessageBox::warningYesNoCancel(dialogParent(),
i18n("The document \"%1\" has been modified.\n"
"Do you want to save your changes or discard them?", docName),
i18n("Close Document"), KStandardGuiItem::save(), KStandardGuiItem::discard());
bool abortClose = false;
bool handled = false;
switch (res) {
case KMessageBox::Yes :
sigQueryClose(&handled, &abortClose);
if (!handled) {
if (url().isEmpty()) {
QUrl url = QFileDialog::getSaveFileUrl(dialogParent());
if (url.isEmpty()) {
return false;
}
saveAs(url);
} else {
save();
}
} else if (abortClose) {
return false;
}
return waitSaveComplete();
case KMessageBox::No :
return true;
default : // case KMessageBox::Cancel :
return false;
}
}
void KTextEditor::DocumentPrivate::slotStarted(KIO::Job *job)
{
/**
* if we are idle before, we are now loading!
*/
if (m_documentState == DocumentIdle) {
m_documentState = DocumentLoading;
}
/**
* if loading:
* - remember pre loading read-write mode
* if remote load:
* - set to read-only
* - trigger possible message
*/
if (m_documentState == DocumentLoading) {
/**
* remember state
*/
m_readWriteStateBeforeLoading = isReadWrite();
/**
* perhaps show loading message, but wait one second
*/
if (job) {
/**
* only read only if really remote file!
*/
setReadWrite(false);
/**
* perhaps some message about loading in one second!
* remember job pointer, we want to be able to kill it!
*/
m_loadingJob = job;
QTimer::singleShot(1000, this, SLOT(slotTriggerLoadingMessage()));
}
}
}
void KTextEditor::DocumentPrivate::slotCompleted()
{
/**
* if were loading, reset back to old read-write mode before loading
* and kill the possible loading message
*/
if (m_documentState == DocumentLoading) {
setReadWrite(m_readWriteStateBeforeLoading);
delete m_loadingMessage;
}
/**
* Emit signal that we saved the document, if needed
*/
if (m_documentState == DocumentSaving || m_documentState == DocumentSavingAs) {
emit documentSavedOrUploaded(this, m_documentState == DocumentSavingAs);
}
/**
* back to idle mode
*/
m_documentState = DocumentIdle;
m_reloading = false;
}
void KTextEditor::DocumentPrivate::slotCanceled()
{
/**
* if were loading, reset back to old read-write mode before loading
* and kill the possible loading message
*/
if (m_documentState == DocumentLoading) {
setReadWrite(m_readWriteStateBeforeLoading);
delete m_loadingMessage;
showAndSetOpeningErrorAccess();
updateDocName();
}
/**
* back to idle mode
*/
m_documentState = DocumentIdle;
m_reloading = false;
}
void KTextEditor::DocumentPrivate::slotTriggerLoadingMessage()
{
/**
* no longer loading?
* no message needed!
*/
if (m_documentState != DocumentLoading) {
return;
}
/**
* create message about file loading in progress
*/
delete m_loadingMessage;
m_loadingMessage = new KTextEditor::Message(i18n("The file %2 is still loading.", url().toDisplayString(QUrl::PreferLocalFile), url().fileName()));
m_loadingMessage->setPosition(KTextEditor::Message::TopInView);
/**
* if around job: add cancel action
*/
if (m_loadingJob) {
QAction *cancel = new QAction(i18n("&Abort Loading"), nullptr);
connect(cancel, SIGNAL(triggered()), this, SLOT(slotAbortLoading()));
m_loadingMessage->addAction(cancel);
}
/**
* really post message
*/
postMessage(m_loadingMessage);
}
void KTextEditor::DocumentPrivate::slotAbortLoading()
{
/**
* no job, no work
*/
if (!m_loadingJob) {
return;
}
/**
* abort loading if any job
* signal results!
*/
m_loadingJob->kill(KJob::EmitResult);
m_loadingJob = nullptr;
}
void KTextEditor::DocumentPrivate::slotUrlChanged(const QUrl &url)
{
if (m_reloading) {
// the URL is temporarily unset and then reset to the previous URL during reload
// we do not want to notify the outside about this
return;
}
Q_UNUSED(url);
updateDocName();
emit documentUrlChanged(this);
}
bool KTextEditor::DocumentPrivate::save()
{
/**
* no double save/load
* we need to allow DocumentPreSavingAs here as state, as save is called in saveAs!
*/
if ((m_documentState != DocumentIdle) && (m_documentState != DocumentPreSavingAs)) {
return false;
}
/**
* if we are idle, we are now saving
*/
if (m_documentState == DocumentIdle) {
m_documentState = DocumentSaving;
} else {
m_documentState = DocumentSavingAs;
}
/**
* call back implementation for real work
*/
return KTextEditor::Document::save();
}
bool KTextEditor::DocumentPrivate::saveAs(const QUrl &url)
{
/**
* abort on bad URL
* that is done in saveAs below, too
* but we must check it here already to avoid messing up
* as no signals will be send, then
*/
if (!url.isValid()) {
return false;
}
/**
* no double save/load
*/
if (m_documentState != DocumentIdle) {
return false;
}
/**
* we enter the pre save as phase
*/
m_documentState = DocumentPreSavingAs;
/**
* call base implementation for real work
*/
return KTextEditor::Document::saveAs(normalizeUrl(url));
}
QString KTextEditor::DocumentPrivate::defaultDictionary() const
{
return m_defaultDictionary;
}
QList > KTextEditor::DocumentPrivate::dictionaryRanges() const
{
return m_dictionaryRanges;
}
void KTextEditor::DocumentPrivate::clearDictionaryRanges()
{
for (QList >::iterator i = m_dictionaryRanges.begin();
i != m_dictionaryRanges.end(); ++i) {
delete(*i).first;
}
m_dictionaryRanges.clear();
if (m_onTheFlyChecker) {
m_onTheFlyChecker->refreshSpellCheck();
}
emit dictionaryRangesPresent(false);
}
void KTextEditor::DocumentPrivate::setDictionary(const QString &newDictionary, const KTextEditor::Range &range, bool blockmode)
{
if (blockmode) {
for (int i = range.start().line(); i <= range.end().line(); ++i) {
setDictionary(newDictionary, rangeOnLine(range, i));
}
} else {
setDictionary(newDictionary, range);
}
emit dictionaryRangesPresent(!m_dictionaryRanges.isEmpty());
}
void KTextEditor::DocumentPrivate::setDictionary(const QString &newDictionary, const KTextEditor::Range &range)
{
KTextEditor::Range newDictionaryRange = range;
if (!newDictionaryRange.isValid() || newDictionaryRange.isEmpty()) {
return;
}
QList > newRanges;
// all ranges is 'm_dictionaryRanges' are assumed to be mutually disjoint
for (QList >::iterator i = m_dictionaryRanges.begin();
i != m_dictionaryRanges.end();) {
qCDebug(LOG_KTE) << "new iteration" << newDictionaryRange;
if (newDictionaryRange.isEmpty()) {
break;
}
QPair pair = *i;
QString dictionarySet = pair.second;
KTextEditor::MovingRange *dictionaryRange = pair.first;
qCDebug(LOG_KTE) << *dictionaryRange << dictionarySet;
if (dictionaryRange->contains(newDictionaryRange) && newDictionary == dictionarySet) {
qCDebug(LOG_KTE) << "dictionaryRange contains newDictionaryRange";
return;
}
if (newDictionaryRange.contains(*dictionaryRange)) {
delete dictionaryRange;
i = m_dictionaryRanges.erase(i);
qCDebug(LOG_KTE) << "newDictionaryRange contains dictionaryRange";
continue;
}
KTextEditor::Range intersection = dictionaryRange->toRange().intersect(newDictionaryRange);
if (!intersection.isEmpty() && intersection.isValid()) {
if (dictionarySet == newDictionary) { // we don't have to do anything for 'intersection'
// except cut off the intersection
QList remainingRanges = KateSpellCheckManager::rangeDifference(newDictionaryRange, intersection);
Q_ASSERT(remainingRanges.size() == 1);
newDictionaryRange = remainingRanges.first();
++i;
qCDebug(LOG_KTE) << "dictionarySet == newDictionary";
continue;
}
QList remainingRanges = KateSpellCheckManager::rangeDifference(*dictionaryRange, intersection);
for (QList::iterator j = remainingRanges.begin(); j != remainingRanges.end(); ++j) {
KTextEditor::MovingRange *remainingRange = newMovingRange(*j,
KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight);
remainingRange->setFeedback(this);
newRanges.push_back(QPair(remainingRange, dictionarySet));
}
i = m_dictionaryRanges.erase(i);
delete dictionaryRange;
} else {
++i;
}
}
m_dictionaryRanges += newRanges;
if (!newDictionaryRange.isEmpty() && !newDictionary.isEmpty()) { // we don't add anything for the default dictionary
KTextEditor::MovingRange *newDictionaryMovingRange = newMovingRange(newDictionaryRange,
KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight);
newDictionaryMovingRange->setFeedback(this);
m_dictionaryRanges.push_back(QPair(newDictionaryMovingRange, newDictionary));
}
if (m_onTheFlyChecker && !newDictionaryRange.isEmpty()) {
m_onTheFlyChecker->refreshSpellCheck(newDictionaryRange);
}
}
void KTextEditor::DocumentPrivate::setDefaultDictionary(const QString &dict)
{
if (m_defaultDictionary == dict) {
return;
}
m_defaultDictionary = dict;
if (m_onTheFlyChecker) {
m_onTheFlyChecker->updateConfig();
refreshOnTheFlyCheck();
}
emit defaultDictionaryChanged(this);
}
void KTextEditor::DocumentPrivate::onTheFlySpellCheckingEnabled(bool enable)
{
if (isOnTheFlySpellCheckingEnabled() == enable) {
return;
}
if (enable) {
Q_ASSERT(m_onTheFlyChecker == nullptr);
m_onTheFlyChecker = new KateOnTheFlyChecker(this);
} else {
delete m_onTheFlyChecker;
m_onTheFlyChecker = nullptr;
}
foreach (KTextEditor::ViewPrivate *view, m_views) {
view->reflectOnTheFlySpellCheckStatus(enable);
}
}
bool KTextEditor::DocumentPrivate::isOnTheFlySpellCheckingEnabled() const
{
return m_onTheFlyChecker != nullptr;
}
QString KTextEditor::DocumentPrivate::dictionaryForMisspelledRange(const KTextEditor::Range &range) const
{
if (!m_onTheFlyChecker) {
return QString();
} else {
return m_onTheFlyChecker->dictionaryForMisspelledRange(range);
}
}
void KTextEditor::DocumentPrivate::clearMisspellingForWord(const QString &word)
{
if (m_onTheFlyChecker) {
m_onTheFlyChecker->clearMisspellingForWord(word);
}
}
void KTextEditor::DocumentPrivate::refreshOnTheFlyCheck(const KTextEditor::Range &range)
{
if (m_onTheFlyChecker) {
m_onTheFlyChecker->refreshSpellCheck(range);
}
}
void KTextEditor::DocumentPrivate::rangeInvalid(KTextEditor::MovingRange *movingRange)
{
deleteDictionaryRange(movingRange);
}
void KTextEditor::DocumentPrivate::rangeEmpty(KTextEditor::MovingRange *movingRange)
{
deleteDictionaryRange(movingRange);
}
void KTextEditor::DocumentPrivate::deleteDictionaryRange(KTextEditor::MovingRange *movingRange)
{
qCDebug(LOG_KTE) << "deleting" << movingRange;
auto finder = [=] (const QPair& item) -> bool {
return item.first == movingRange;
};
auto it = std::find_if(m_dictionaryRanges.begin(), m_dictionaryRanges.end(), finder);
if (it != m_dictionaryRanges.end()) {
m_dictionaryRanges.erase(it);
delete movingRange;
}
Q_ASSERT(std::find_if(m_dictionaryRanges.begin(), m_dictionaryRanges.end(), finder) == m_dictionaryRanges.end());
}
bool KTextEditor::DocumentPrivate::containsCharacterEncoding(const KTextEditor::Range &range)
{
KateHighlighting *highlighting = highlight();
Kate::TextLine textLine;
const int rangeStartLine = range.start().line();
const int rangeStartColumn = range.start().column();
const int rangeEndLine = range.end().line();
const int rangeEndColumn = range.end().column();
for (int line = range.start().line(); line <= rangeEndLine; ++line) {
textLine = kateTextLine(line);
int startColumn = (line == rangeStartLine) ? rangeStartColumn : 0;
int endColumn = (line == rangeEndLine) ? rangeEndColumn : textLine->length();
for (int col = startColumn; col < endColumn; ++col) {
int attr = textLine->attribute(col);
const KatePrefixStore &prefixStore = highlighting->getCharacterEncodingsPrefixStore(attr);
if (!prefixStore.findPrefix(textLine, col).isEmpty()) {
return true;
}
}
}
return false;
}
int KTextEditor::DocumentPrivate::computePositionWrtOffsets(const OffsetList &offsetList, int pos)
{
int previousOffset = 0;
for (OffsetList::const_iterator i = offsetList.begin(); i != offsetList.end(); ++i) {
if ((*i).first > pos) {
break;
}
previousOffset = (*i).second;
}
return pos + previousOffset;
}
QString KTextEditor::DocumentPrivate::decodeCharacters(const KTextEditor::Range &range, KTextEditor::DocumentPrivate::OffsetList &decToEncOffsetList,
KTextEditor::DocumentPrivate::OffsetList &encToDecOffsetList)
{
QString toReturn;
KTextEditor::Cursor previous = range.start();
int decToEncCurrentOffset = 0, encToDecCurrentOffset = 0;
int i = 0;
int newI = 0;
KateHighlighting *highlighting = highlight();
Kate::TextLine textLine;
const int rangeStartLine = range.start().line();
const int rangeStartColumn = range.start().column();
const int rangeEndLine = range.end().line();
const int rangeEndColumn = range.end().column();
for (int line = range.start().line(); line <= rangeEndLine; ++line) {
textLine = kateTextLine(line);
int startColumn = (line == rangeStartLine) ? rangeStartColumn : 0;
int endColumn = (line == rangeEndLine) ? rangeEndColumn : textLine->length();
for (int col = startColumn; col < endColumn;) {
int attr = textLine->attribute(col);
const KatePrefixStore &prefixStore = highlighting->getCharacterEncodingsPrefixStore(attr);
const QHash &characterEncodingsHash = highlighting->getCharacterEncodings(attr);
QString matchingPrefix = prefixStore.findPrefix(textLine, col);
if (!matchingPrefix.isEmpty()) {
toReturn += text(KTextEditor::Range(previous, KTextEditor::Cursor(line, col)));
const QChar &c = characterEncodingsHash.value(matchingPrefix);
const bool isNullChar = c.isNull();
if (!c.isNull()) {
toReturn += c;
}
i += matchingPrefix.length();
col += matchingPrefix.length();
previous = KTextEditor::Cursor(line, col);
decToEncCurrentOffset = decToEncCurrentOffset - (isNullChar ? 0 : 1) + matchingPrefix.length();
encToDecCurrentOffset = encToDecCurrentOffset - matchingPrefix.length() + (isNullChar ? 0 : 1);
newI += (isNullChar ? 0 : 1);
decToEncOffsetList.push_back(QPair(newI, decToEncCurrentOffset));
encToDecOffsetList.push_back(QPair(i, encToDecCurrentOffset));
continue;
}
++col;
++i;
++newI;
}
++i;
++newI;
}
if (previous < range.end()) {
toReturn += text(KTextEditor::Range(previous, range.end()));
}
return toReturn;
}
void KTextEditor::DocumentPrivate::replaceCharactersByEncoding(const KTextEditor::Range &range)
{
KateHighlighting *highlighting = highlight();
Kate::TextLine textLine;
const int rangeStartLine = range.start().line();
const int rangeStartColumn = range.start().column();
const int rangeEndLine = range.end().line();
const int rangeEndColumn = range.end().column();
for (int line = range.start().line(); line <= rangeEndLine; ++line) {
textLine = kateTextLine(line);
int startColumn = (line == rangeStartLine) ? rangeStartColumn : 0;
int endColumn = (line == rangeEndLine) ? rangeEndColumn : textLine->length();
for (int col = startColumn; col < endColumn;) {
int attr = textLine->attribute(col);
const QHash &reverseCharacterEncodingsHash = highlighting->getReverseCharacterEncodings(attr);
QHash::const_iterator it = reverseCharacterEncodingsHash.find(textLine->at(col));
if (it != reverseCharacterEncodingsHash.end()) {
replaceText(KTextEditor::Range(line, col, line, col + 1), *it);
col += (*it).length();
continue;
}
++col;
}
}
}
//
// Highlighting information
//
KTextEditor::Attribute::Ptr KTextEditor::DocumentPrivate::attributeAt(const KTextEditor::Cursor &position)
{
KTextEditor::Attribute::Ptr attrib(new KTextEditor::Attribute());
KTextEditor::ViewPrivate *view = m_views.empty() ? nullptr : m_views.begin().value();
if (!view) {
qCWarning(LOG_KTE) << "ATTENTION: cannot access lineAttributes() without any View (will be fixed eventually)";
return attrib;
}
Kate::TextLine kateLine = kateTextLine(position.line());
if (!kateLine) {
return attrib;
}
*attrib = *view->renderer()->attribute(kateLine->attribute(position.column()));
return attrib;
}
QStringList KTextEditor::DocumentPrivate::embeddedHighlightingModes() const
{
return highlight()->getEmbeddedHighlightingModes();
}
QString KTextEditor::DocumentPrivate::highlightingModeAt(const KTextEditor::Cursor &position)
{
return highlight()->higlightingModeForLocation(this, position);
}
Kate::SwapFile *KTextEditor::DocumentPrivate::swapFile()
{
return m_swapfile;
}
/**
* \return \c -1 if \c line or \c column invalid, otherwise one of
* standard style attribute number
*/
int KTextEditor::DocumentPrivate::defStyleNum(int line, int column)
{
// Validate parameters to prevent out of range access
if (line < 0 || line >= lines() || column < 0) {
return -1;
}
// get highlighted line
Kate::TextLine tl = kateTextLine(line);
// make sure the textline is a valid pointer
if (!tl) {
return -1;
}
/**
* either get char attribute or attribute of context still active at end of line
*/
int attribute = 0;
if (column < tl->length()) {
attribute = tl->attribute(column);
} else if (column == tl->length()) {
if (!tl->attributesList().isEmpty()) {
attribute = tl->attributesList().back().attributeValue;
} else {
return -1;
}
} else {
return -1;
}
return highlight()->defaultStyleForAttribute(attribute);
}
bool KTextEditor::DocumentPrivate::isComment(int line, int column)
{
const int defaultStyle = defStyleNum(line, column);
return defaultStyle == KTextEditor::dsComment;
}
int KTextEditor::DocumentPrivate::findTouchedLine(int startLine, bool down)
{
const int offset = down ? 1 : -1;
const int lineCount = lines();
while (startLine >= 0 && startLine < lineCount) {
Kate::TextLine tl = m_buffer->plainLine(startLine);
if (tl && (tl->markedAsModified() || tl->markedAsSavedOnDisk())) {
return startLine;
}
startLine += offset;
}
return -1;
}
void KTextEditor::DocumentPrivate::setActiveTemplateHandler(KateTemplateHandler* handler)
{
// delete any active template handler
delete m_activeTemplateHandler.data();
m_activeTemplateHandler = handler;
}
//BEGIN KTextEditor::MessageInterface
bool KTextEditor::DocumentPrivate::postMessage(KTextEditor::Message *message)
{
// no message -> cancel
if (!message) {
return false;
}
// make sure the desired view belongs to this document
if (message->view() && message->view()->document() != this) {
qCWarning(LOG_KTE) << "trying to post a message to a view of another document:" << message->text();
return false;
}
message->setParent(this);
message->setDocument(this);
// if there are no actions, add a close action by default if widget does not auto-hide
if (message->actions().count() == 0 && message->autoHide() < 0) {
QAction *closeAction = new QAction(QIcon::fromTheme(QStringLiteral("window-close")), i18n("&Close"), nullptr);
closeAction->setToolTip(i18n("Close message"));
message->addAction(closeAction);
}
// make sure the message is registered even if no actions and no views exist
m_messageHash[message] = QList >();
// reparent actions, as we want full control over when they are deleted
foreach (QAction *action, message->actions()) {
action->setParent(nullptr);
m_messageHash[message].append(QSharedPointer(action));
}
// post message to requested view, or to all views
if (KTextEditor::ViewPrivate *view = qobject_cast(message->view())) {
view->postMessage(message, m_messageHash[message]);
} else {
foreach (KTextEditor::ViewPrivate *view, m_views) {
view->postMessage(message, m_messageHash[message]);
}
}
// also catch if the user manually calls delete message
connect(message, SIGNAL(closed(KTextEditor::Message*)), SLOT(messageDestroyed(KTextEditor::Message*)));
return true;
}
void KTextEditor::DocumentPrivate::messageDestroyed(KTextEditor::Message *message)
{
// KTE:Message is already in destructor
Q_ASSERT(m_messageHash.contains(message));
m_messageHash.remove(message);
}
//END KTextEditor::MessageInterface
void KTextEditor::DocumentPrivate::closeDocumentInApplication()
{
KTextEditor::EditorPrivate::self()->application()->closeDocument(this);
}
diff --git a/src/utils/kateconfig.cpp b/src/utils/kateconfig.cpp
index e9b4b10e..1043eff4 100644
--- a/src/utils/kateconfig.cpp
+++ b/src/utils/kateconfig.cpp
@@ -1,1460 +1,1460 @@
/* This file is part of the KDE libraries
Copyright (C) 2007, 2008 Matthew Woehlke
Copyright (C) 2003 Christoph Cullmann
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kateconfig.h"
#include "kateglobal.h"
#include "katedefaultcolors.h"
#include "katerenderer.h"
#include "kateview.h"
#include "katedocument.h"
#include "kateschema.h"
#include "katepartdebug.h"
#include
#include
#include
#include
#include
#include
//BEGIN KateConfig
KateConfig::KateConfig(const KateConfig *parent)
: m_parent(parent)
, m_configKeys(m_parent ? nullptr : new QStringList())
, m_configKeyToEntry(m_parent ? nullptr : new QHash())
{
}
KateConfig::~KateConfig()
{
}
void KateConfig::addConfigEntry(ConfigEntry &&entry)
{
/**
* shall only be called for toplevel config
*/
Q_ASSERT(isGlobal());
/**
* there shall be no gaps in the entries
* we might later want to use a vector
*/
Q_ASSERT(m_configEntries.size() == static_cast(entry.enumKey));
/**
* add new element
*/
m_configEntries.emplace(entry.enumKey, entry);
}
void KateConfig::finalizeConfigEntries()
{
/**
* shall only be called for toplevel config
*/
Q_ASSERT(isGlobal());
/**
* compute list of all config keys + register map from key => config entry
*
* we skip entries without a command name, these config entries are not exposed ATM
*
*/
for (const auto &entry : m_configEntries) {
if (!entry.second.commandName.isEmpty()) {
Q_ASSERT_X(!m_configKeys->contains(entry.second.commandName), "finalizeConfigEntries", (QLatin1String("KEY NOT UNIQUE: ") + entry.second.commandName).toLocal8Bit().constData());
m_configKeys->append(entry.second.commandName);
m_configKeyToEntry->insert(entry.second.commandName, &entry.second);
}
}
}
void KateConfig::readConfigEntries(const KConfigGroup &config)
{
configStart();
// read all config entries, even the ones ATM not set in this config object but known in the toplevel one
for (const auto &entry : fullConfigEntries()) {
setValue(entry.second.enumKey, config.readEntry(entry.second.configKey, entry.second.defaultValue));
}
configEnd();
}
void KateConfig::writeConfigEntries(KConfigGroup &config) const
{
// write all config entries, even the ones ATM not set in this config object but known in the toplevel one
for (const auto &entry : fullConfigEntries()) {
config.writeEntry(entry.second.configKey, value(entry.second.enumKey));
}
}
void KateConfig::configStart()
{
configSessionNumber++;
if (configSessionNumber > 1) {
return;
}
configIsRunning = true;
}
void KateConfig::configEnd()
{
if (configSessionNumber == 0) {
return;
}
configSessionNumber--;
if (configSessionNumber > 0) {
return;
}
configIsRunning = false;
updateConfig();
}
QVariant KateConfig::value(const int key) const
{
// first: local lookup
const auto it = m_configEntries.find(key);
if (it != m_configEntries.end()) {
return it->second.value;
}
// else: fallback to parent config, if any
if (m_parent) {
return m_parent->value(key);
}
// if we arrive here, the key was invalid! => programming error
// for release builds, we just return invalid variant
Q_ASSERT(false);
return QVariant();
}
bool KateConfig::setValue(const int key, const QVariant &value)
{
// check: is this key known at all?
const auto &knownEntries = fullConfigEntries();
const auto knownIt = knownEntries.find(key);
if (knownIt == knownEntries.end()) {
// if we arrive here, the key was invalid! => programming error
// for release builds, we just fail to set the value
Q_ASSERT(false);
return false;
}
// validator set? use it, if not accepting, abort setting
if (knownIt->second.validator && !knownIt->second.validator(value)) {
return false;
}
// check if value already there for this config
auto valueIt = m_configEntries.find(key);
if (valueIt != m_configEntries.end()) {
// skip any work if value is equal
if (valueIt->second.value == value) {
return true;
}
// else: alter value and be done
configStart();
valueIt->second.value = value;
configEnd();
return true;
}
// if not in this hash, we must copy the known entry and adjust the value
configStart();
auto res = m_configEntries.emplace(key, knownIt->second);
res.first->second.value = value;
configEnd();
return true;
}
QVariant KateConfig::value(const QString &key) const
{
/**
* check if we know this key, if not, return invalid variant
*/
const auto &knownEntries = fullConfigKeyToEntry();
const auto it = knownEntries.find(key);
if (it == knownEntries.end()) {
return QVariant();
}
/**
* key known, dispatch to normal value() function with enum
*/
return value(it.value()->enumKey);
}
bool KateConfig::setValue(const QString &key, const QVariant &value)
{
/**
* check if we know this key, if not, ignore the set
*/
const auto &knownEntries = fullConfigKeyToEntry();
const auto it = knownEntries.find(key);
if (it == knownEntries.end()) {
return false;
}
/**
* key known, dispatch to normal setValue() function with enum
*/
return setValue(it.value()->enumKey, value);
}
//END
//BEGIN HelperFunctions
KateGlobalConfig *KateGlobalConfig::s_global = nullptr;
KateDocumentConfig *KateDocumentConfig::s_global = nullptr;
KateViewConfig *KateViewConfig::s_global = nullptr;
KateRendererConfig *KateRendererConfig::s_global = nullptr;
/**
* validate if an encoding is ok
* @param name encoding name
* @return encoding ok?
*/
static bool isEncodingOk(const QString &name)
{
bool found = false;
auto codec = KCharsets::charsets()->codecForName(name, found);
return found && codec;
}
static bool inBounds(const int min, const QVariant &value, const int max)
{
const int val = value.toInt();
return (val >= min) && (val <= max);
}
static bool isPositive(const QVariant &value)
{
bool ok;
value.toUInt(&ok);
return ok;
}
//END
//BEGIN KateGlobalConfig
KateGlobalConfig::KateGlobalConfig()
{
/**
* register this as our global instance
*/
Q_ASSERT(isGlobal());
s_global = this;
/**
* init all known config entries
*/
addConfigEntry(ConfigEntry(EncodingProberType, "Encoding Prober Type", QString(), KEncodingProber::Universal));
addConfigEntry(ConfigEntry(FallbackEncoding, "Fallback Encoding", QString(), QStringLiteral("ISO 8859-15"), [](const QVariant &value) { return isEncodingOk(value.toString()); }));
/**
* finalize the entries, e.g. hashs them
*/
finalizeConfigEntries();
/**
* init with defaults from config or really hardcoded ones
*/
KConfigGroup cg(KTextEditor::EditorPrivate::config(), "KTextEditor Editor");
readConfig(cg);
}
void KateGlobalConfig::readConfig(const KConfigGroup &config)
{
/**
* start config update group
*/
configStart();
/**
* read generic entries
*/
readConfigEntries(config);
/**
* end config update group, might trigger updateConfig()
*/
configEnd();
}
void KateGlobalConfig::writeConfig(KConfigGroup &config)
{
/**
* write generic entries
*/
writeConfigEntries(config);
}
void KateGlobalConfig::updateConfig()
{
// write config
KConfigGroup cg(KTextEditor::EditorPrivate::config(), "KTextEditor Editor");
writeConfig(cg);
KTextEditor::EditorPrivate::config()->sync();
}
QTextCodec *KateGlobalConfig::fallbackCodec() const
{
/**
* query stored encoding, always fallback to ISO 8859-15 if nothing valid set
*/
const auto encoding = value(FallbackEncoding).toString();
if (encoding.isEmpty()) {
return QTextCodec::codecForName("ISO 8859-15");
}
/**
* use configured encoding
*/
return KCharsets::charsets()->codecForName(encoding);
}
//END
//BEGIN KateDocumentConfig
KateDocumentConfig::KateDocumentConfig()
{
/**
* register this as our global instance
*/
Q_ASSERT(isGlobal());
s_global = this;
/**
* init all known config entries
*/
addConfigEntry(ConfigEntry(TabWidth, "Tab Width", QStringLiteral("tab-width"), 4, [](const QVariant &value) { return value.toInt() >= 1; }));
addConfigEntry(ConfigEntry(IndentationWidth, "Indentation Width", QStringLiteral("indent-width"), 4, [](const QVariant &value) { return value.toInt() >= 1; }));
addConfigEntry(ConfigEntry(OnTheFlySpellCheck, "On-The-Fly Spellcheck", QStringLiteral("on-the-fly-spellcheck"), false));
addConfigEntry(ConfigEntry(IndentOnTextPaste, "Indent On Text Paste", QStringLiteral("indent-pasted-text"), false));
addConfigEntry(ConfigEntry(ReplaceTabsWithSpaces, "ReplaceTabsDyn", QStringLiteral("replace-tabs"), true));
addConfigEntry(ConfigEntry(BackupOnSaveLocal, "Backup Local", QStringLiteral("backup-on-save-local"), false));
addConfigEntry(ConfigEntry(BackupOnSaveRemote, "Backup Remote", QStringLiteral("backup-on-save-remote"), false));
addConfigEntry(ConfigEntry(BackupOnSavePrefix, "Backup Prefix", QStringLiteral("backup-on-save-prefix"), QString()));
addConfigEntry(ConfigEntry(BackupOnSaveSuffix, "Backup Suffix", QStringLiteral("backup-on-save-suffix"), QStringLiteral("~")));
addConfigEntry(ConfigEntry(IndentationMode, "Indentation Mode", QString(), QStringLiteral("normal")));
addConfigEntry(ConfigEntry(TabHandlingMode, "Tab Handling", QString(), KateDocumentConfig::tabSmart));
addConfigEntry(ConfigEntry(StaticWordWrap, "Word Wrap", QString(), false));
addConfigEntry(ConfigEntry(StaticWordWrapColumn, "Word Wrap Column", QString(), 80, [](const QVariant &value) { return value.toInt() >= 1; }));
addConfigEntry(ConfigEntry(PageUpDownMovesCursor, "PageUp/PageDown Moves Cursor", QString(), false));
addConfigEntry(ConfigEntry(SmartHome, "Smart Home", QString(), true));
addConfigEntry(ConfigEntry(ShowTabs, "Show Tabs", QString(), true));
addConfigEntry(ConfigEntry(IndentOnTab, "Indent On Tab", QString(), true));
addConfigEntry(ConfigEntry(KeepExtraSpaces, "Keep Extra Spaces", QString(), false));
addConfigEntry(ConfigEntry(BackspaceIndents, "Indent On Backspace", QString(), true));
addConfigEntry(ConfigEntry(ShowSpacesMode, "Show Spaces", QString(), KateDocumentConfig::None));
addConfigEntry(ConfigEntry(TrailingMarkerSize, "Trailing Marker Size", QString(), 1));
addConfigEntry(ConfigEntry(RemoveSpacesMode, "Remove Spaces", QString(), 0));
addConfigEntry(ConfigEntry(NewlineAtEOF, "Newline at End of File", QString(), true));
addConfigEntry(ConfigEntry(OverwriteMode, "Overwrite Mode", QString(), false));
addConfigEntry(ConfigEntry(Encoding, "Encoding", QString(), QStringLiteral("UTF-8"), [](const QVariant &value) { return isEncodingOk(value.toString()); }));
addConfigEntry(ConfigEntry(EndOfLine, "End of Line", QString(), 0));
addConfigEntry(ConfigEntry(AllowEndOfLineDetection, "Allow End of Line Detection", QString(), true));
addConfigEntry(ConfigEntry(ByteOrderMark, "BOM", QString(), false));
addConfigEntry(ConfigEntry(SwapFile, "Swap File Mode", QString(), KateDocumentConfig::EnableSwapFile));
addConfigEntry(ConfigEntry(SwapFileDirectory, "Swap Directory", QString(), QString()));
addConfigEntry(ConfigEntry(SwapFileSyncInterval, "Swap Sync Interval", QString(), 15));
addConfigEntry(ConfigEntry(LineLengthLimit, "Line Length Limit", QString(), 10000));
/**
* finalize the entries, e.g. hashs them
*/
finalizeConfigEntries();
/**
* init with defaults from config or really hardcoded ones
*/
KConfigGroup cg(KTextEditor::EditorPrivate::config(), "KTextEditor Document");
readConfig(cg);
}
KateDocumentConfig::KateDocumentConfig(KTextEditor::DocumentPrivate *doc)
: KateConfig(s_global),
m_doc(doc)
{
/**
* per document config doesn't read stuff per default
*/
}
void KateDocumentConfig::readConfig(const KConfigGroup &config)
{
/**
* start config update group
*/
configStart();
/**
* read generic entries
*/
readConfigEntries(config);
/**
* fixup sonnet config, see KateSpellCheckConfigTab::apply(), too
* WARNING: this is slightly hackish, but it's currently the only way to
* do it, see also the KTextEdit class
*/
if (isGlobal()) {
const QSettings settings(QStringLiteral("KDE"), QStringLiteral("Sonnet"));
setOnTheFlySpellCheck(settings.value(QStringLiteral("checkerEnabledByDefault"), false).toBool());
}
/**
* backwards compatibility mappings
* convert stuff, old entries deleted in writeConfig
*/
if (const int backupFlags = config.readEntry("Backup Flags", 0)) {
setBackupOnSaveLocal(backupFlags & 0x1);
setBackupOnSaveRemote(backupFlags & 0x2);
}
/**
* end config update group, might trigger updateConfig()
*/
configEnd();
}
void KateDocumentConfig::writeConfig(KConfigGroup &config)
{
/**
* write generic entries
*/
writeConfigEntries(config);
/**
* backwards compatibility mappings
* here we remove old entries we converted on readConfig
*/
config.deleteEntry("Backup Flags");
}
void KateDocumentConfig::updateConfig()
{
if (m_doc) {
m_doc->updateConfig();
return;
}
if (isGlobal()) {
for (int z = 0; z < KTextEditor::EditorPrivate::self()->kateDocuments().size(); ++z) {
(KTextEditor::EditorPrivate::self()->kateDocuments())[z]->updateConfig();
}
// write config
KConfigGroup cg(KTextEditor::EditorPrivate::config(), "KTextEditor Document");
writeConfig(cg);
KTextEditor::EditorPrivate::config()->sync();
}
}
QTextCodec *KateDocumentConfig::codec() const
{
/**
* query stored encoding, always fallback to UTF-8 if nothing valid set
*/
const auto encoding = value(Encoding).toString();
if (encoding.isEmpty()) {
return QTextCodec::codecForName("UTF-8");
}
/**
* use configured encoding
*/
return KCharsets::charsets()->codecForName(encoding);
}
QString KateDocumentConfig::eolString()
{
switch(eol()) {
case KateDocumentConfig::eolDos:
return QStringLiteral("\r\n");
case KateDocumentConfig::eolMac:
return QStringLiteral("\r");
default:
return QStringLiteral("\n");
}
}
//END
//BEGIN KateViewConfig
KateViewConfig::KateViewConfig()
{
s_global = this;
// Init all known config entries
// NOTE: Ensure to keep the same order as listed in enum ConfigEntryTypes or it will later assert!
// addConfigEntry(ConfigEntry(, , , , []))
addConfigEntry(ConfigEntry(AllowMarkMenu, "Allow Mark Menu", QStringLiteral("allow-mark-menu"), true));
addConfigEntry(ConfigEntry(AutoBrackets, "Auto Brackets", QStringLiteral("auto-brackets"), false));
addConfigEntry(ConfigEntry(AutoCenterLines, "Auto Center Lines", QStringLiteral("auto-center-lines"), 0));
addConfigEntry(ConfigEntry(AutomaticCompletionInvocation, "Auto Completion", QString(), true));
addConfigEntry(ConfigEntry(BackspaceRemoveComposedCharacters, "Backspace Remove Composed Characters", QString(), false));
addConfigEntry(ConfigEntry(BookmarkSorting, "Bookmark Menu Sorting", QString(), 0));
- addConfigEntry(ConfigEntry(CharsToEncloseSelection, "Chars To Enclose Selection", QString(), QString()));
+ addConfigEntry(ConfigEntry(CharsToEncloseSelection, "Chars To Enclose Selection", QStringLiteral("enclose-selection"), QString()));
addConfigEntry(ConfigEntry(DefaultMarkType, "Default Mark Type", QStringLiteral("default-mark-type"), KTextEditor::MarkInterface::markType01, [](const QVariant &value) { return isPositive(value); }));
addConfigEntry(ConfigEntry(DynWordWrapAlignIndent, "Dynamic Word Wrap Align Indent", QString(), 80, [](const QVariant &value) { return inBounds(1, value, 100); }));
addConfigEntry(ConfigEntry(DynWordWrapIndicators, "Dynamic Word Wrap Indicators", QString(), 1, [](const QVariant &value) { return inBounds(1, value, 3); }));
addConfigEntry(ConfigEntry(DynWrapAtStaticMarker, "Dynamic Word Wrap At Static Marker", QString(), false));
addConfigEntry(ConfigEntry(DynamicWordWrap, "Dynamic Word Wrap", QStringLiteral("dynamic-word-wrap"), true));
addConfigEntry(ConfigEntry(FoldFirstLine, "Fold First Line", QString(), false));
addConfigEntry(ConfigEntry(InputMode, "Input Mode", QString(), 0, [](const QVariant &value) { return isPositive(value); }));
addConfigEntry(ConfigEntry(KeywordCompletion, "Keyword Completion", QStringLiteral("keyword-completion"), true));
addConfigEntry(ConfigEntry(MaxHistorySize, "Maximum Search History Size", QString(), 100, [](const QVariant &value) { return inBounds(0, value, 999); }));
addConfigEntry(ConfigEntry(MousePasteAtCursorPosition, "Mouse Paste At Cursor Position", QString(), false));
addConfigEntry(ConfigEntry(PersistentSelection, "Persistent Selection", QStringLiteral("persistent-selectionq"), false));
addConfigEntry(ConfigEntry(ScrollBarMiniMapWidth, "Scroll Bar Mini Map Width", QString(), 60, [](const QVariant &value) { return inBounds(0, value, 999); }));
addConfigEntry(ConfigEntry(ScrollPastEnd, "Scroll Past End", QString(), false));
addConfigEntry(ConfigEntry(SearchFlags, "Search/Replace Flags", QString(), IncFromCursor | PowerMatchCase | PowerModePlainText));
addConfigEntry(ConfigEntry(ShowFoldingBar, "Folding Bar", QStringLiteral("folding-bar"), true));
addConfigEntry(ConfigEntry(ShowFoldingPreview, "Folding Preview", QStringLiteral("folding-preview"), true));
addConfigEntry(ConfigEntry(ShowIconBar, "Icon Bar", QStringLiteral("icon-bar"), false));
addConfigEntry(ConfigEntry(ShowLineCount, "Show Line Count", QString(), false));
addConfigEntry(ConfigEntry(ShowLineModification, "Line Modification", QStringLiteral("modification-markers"), false));
addConfigEntry(ConfigEntry(ShowLineNumbers, "Line Numbers", QStringLiteral("line-numbers"), false));
addConfigEntry(ConfigEntry(ShowScrollBarMarks, "Scroll Bar Marks", QString(), false));
addConfigEntry(ConfigEntry(ShowScrollBarMiniMap, "Scroll Bar MiniMap", QStringLiteral("scrollbar-minimap"), true));
addConfigEntry(ConfigEntry(ShowScrollBarMiniMapAll, "Scroll Bar Mini Map All", QString(), true));
addConfigEntry(ConfigEntry(ShowScrollBarPreview, "Scroll Bar Preview", QStringLiteral("scrollbar-preview"), true));
addConfigEntry(ConfigEntry(ShowScrollbars, "Show Scrollbars", QString(), AlwaysOn, [](const QVariant &value) { return inBounds(0, value, 2); }));
addConfigEntry(ConfigEntry(ShowWordCount, "Show Word Count", QString(), false));
addConfigEntry(ConfigEntry(TextDragAndDrop, "Text Drag And Drop", QString(), true));
addConfigEntry(ConfigEntry(SmartCopyCut, "Smart Copy Cut", QString(), false));
+ addConfigEntry(ConfigEntry(UserSetsOfCharsToEncloseSelection, "User Sets Of Chars To Enclose Selection", QString(), QStringList()));
addConfigEntry(ConfigEntry(ViInputModeStealKeys, "Vi Input Mode Steal Keys", QString(), false));
addConfigEntry(ConfigEntry(ViRelativeLineNumbers, "Vi Relative Line Numbers", QString(), false));
addConfigEntry(ConfigEntry(WordCompletion, "Word Completion", QString(), true));
addConfigEntry(ConfigEntry(WordCompletionMinimalWordLength, "Word Completion Minimal Word Length", QString(), 3, [](const QVariant &value) { return inBounds(0, value, 99); }));
addConfigEntry(ConfigEntry(WordCompletionRemoveTail, "Word Completion Remove Tail", QString(), true));
// Never forget to finalize or the becomes not available
finalizeConfigEntries();
// init with defaults from config or really hardcoded ones
KConfigGroup config(KTextEditor::EditorPrivate::config(), "KTextEditor View");
readConfig(config);
}
KateViewConfig::KateViewConfig(KTextEditor::ViewPrivate *view)
: KateConfig(s_global)
, m_view(view)
{
}
KateViewConfig::~KateViewConfig()
{
}
void KateViewConfig::readConfig(const KConfigGroup &config)
{
configStart();
// read generic entries
readConfigEntries(config);
configEnd();
}
void KateViewConfig::writeConfig(KConfigGroup &config)
{
// write generic entries
writeConfigEntries(config);
}
void KateViewConfig::updateConfig()
{
if (m_view) {
m_view->updateConfig();
return;
}
if (isGlobal()) {
foreach (KTextEditor::ViewPrivate* view, KTextEditor::EditorPrivate::self()->views()) {
view->updateConfig();
}
// write config
KConfigGroup cg(KTextEditor::EditorPrivate::config(), "KTextEditor View");
writeConfig(cg);
KTextEditor::EditorPrivate::config()->sync();
}
}
-
//END
//BEGIN KateRendererConfig
KateRendererConfig::KateRendererConfig()
: m_fontMetrics(QFont()),
m_lineMarkerColor(KTextEditor::MarkInterface::reservedMarkersCount()),
m_schemaSet(false),
m_fontSet(false),
m_wordWrapMarkerSet(false),
m_showIndentationLinesSet(false),
m_showWholeBracketExpressionSet(false),
m_backgroundColorSet(false),
m_selectionColorSet(false),
m_highlightedLineColorSet(false),
m_highlightedBracketColorSet(false),
m_wordWrapMarkerColorSet(false),
m_tabMarkerColorSet(false),
m_indentationLineColorSet(false),
m_iconBarColorSet(false),
m_foldingColorSet(false),
m_lineNumberColorSet(false),
m_currentLineNumberColorSet(false),
m_separatorColorSet(false),
m_spellingMistakeLineColorSet(false),
m_templateColorsSet(false),
m_modifiedLineColorSet(false),
m_savedLineColorSet(false),
m_searchHighlightColorSet(false),
m_replaceHighlightColorSet(false),
m_lineMarkerColorSet(m_lineMarkerColor.size())
{
// init bitarray
m_lineMarkerColorSet.fill(true);
s_global = this;
// init with defaults from config or really hardcoded ones
KConfigGroup config(KTextEditor::EditorPrivate::config(), "KTextEditor Renderer");
readConfig(config);
}
KateRendererConfig::KateRendererConfig(KateRenderer *renderer)
: KateConfig(s_global),
m_fontMetrics(QFont()),
m_lineMarkerColor(KTextEditor::MarkInterface::reservedMarkersCount()),
m_schemaSet(false),
m_fontSet(false),
m_wordWrapMarkerSet(false),
m_showIndentationLinesSet(false),
m_showWholeBracketExpressionSet(false),
m_backgroundColorSet(false),
m_selectionColorSet(false),
m_highlightedLineColorSet(false),
m_highlightedBracketColorSet(false),
m_wordWrapMarkerColorSet(false),
m_tabMarkerColorSet(false),
m_indentationLineColorSet(false),
m_iconBarColorSet(false),
m_foldingColorSet(false),
m_lineNumberColorSet(false),
m_currentLineNumberColorSet(false),
m_separatorColorSet(false),
m_spellingMistakeLineColorSet(false),
m_templateColorsSet(false),
m_modifiedLineColorSet(false),
m_savedLineColorSet(false),
m_searchHighlightColorSet(false),
m_replaceHighlightColorSet(false),
m_lineMarkerColorSet(m_lineMarkerColor.size()),
m_renderer(renderer)
{
// init bitarray
m_lineMarkerColorSet.fill(false);
}
KateRendererConfig::~KateRendererConfig()
{
}
namespace
{
const char KEY_SCHEMA[] = "Schema";
const char KEY_WORD_WRAP_MARKER[] = "Word Wrap Marker";
const char KEY_SHOW_INDENTATION_LINES[] = "Show Indentation Lines";
const char KEY_SHOW_WHOLE_BRACKET_EXPRESSION[] = "Show Whole Bracket Expression";
const char KEY_ANIMATE_BRACKET_MATCHING[] = "Animate Bracket Matching";
}
void KateRendererConfig::readConfig(const KConfigGroup &config)
{
configStart();
// read generic entries
readConfigEntries(config);
// "Normal" Schema MUST BE THERE, see global kateschemarc
setSchema(config.readEntry(KEY_SCHEMA, "Normal"));
setWordWrapMarker(config.readEntry(KEY_WORD_WRAP_MARKER, false));
setShowIndentationLines(config.readEntry(KEY_SHOW_INDENTATION_LINES, false));
setShowWholeBracketExpression(config.readEntry(KEY_SHOW_WHOLE_BRACKET_EXPRESSION, false));
setAnimateBracketMatching(config.readEntry(KEY_ANIMATE_BRACKET_MATCHING, false));
configEnd();
}
void KateRendererConfig::writeConfig(KConfigGroup &config)
{
// write generic entries
writeConfigEntries(config);
config.writeEntry(KEY_SCHEMA, schema());
config.writeEntry(KEY_WORD_WRAP_MARKER, wordWrapMarker());
config.writeEntry(KEY_SHOW_INDENTATION_LINES, showIndentationLines());
config.writeEntry(KEY_SHOW_WHOLE_BRACKET_EXPRESSION, showWholeBracketExpression());
config.writeEntry(KEY_ANIMATE_BRACKET_MATCHING, animateBracketMatching());
}
void KateRendererConfig::updateConfig()
{
if (m_renderer) {
m_renderer->updateConfig();
return;
}
if (isGlobal()) {
for (int z = 0; z < KTextEditor::EditorPrivate::self()->views().size(); ++z) {
(KTextEditor::EditorPrivate::self()->views())[z]->renderer()->updateConfig();
}
// write config
KConfigGroup cg(KTextEditor::EditorPrivate::config(), "KTextEditor Renderer");
writeConfig(cg);
KTextEditor::EditorPrivate::config()->sync();
}
}
const QString &KateRendererConfig::schema() const
{
if (m_schemaSet || isGlobal()) {
return m_schema;
}
return s_global->schema();
}
void KateRendererConfig::setSchema(const QString &schema)
{
if (m_schemaSet && m_schema == schema) {
return;
}
configStart();
m_schemaSet = true;
m_schema = schema;
setSchemaInternal(schema);
configEnd();
}
void KateRendererConfig::reloadSchema()
{
if (isGlobal()) {
setSchemaInternal(m_schema);
foreach (KTextEditor::ViewPrivate *view, KTextEditor::EditorPrivate::self()->views()) {
view->renderer()->config()->reloadSchema();
}
}
else if (m_renderer && m_schemaSet) {
setSchemaInternal(m_schema);
}
// trigger renderer/view update
if (m_renderer) {
m_renderer->updateConfig();
}
}
void KateRendererConfig::setSchemaInternal(const QString &schema)
{
m_schemaSet = true;
m_schema = schema;
KConfigGroup config = KTextEditor::EditorPrivate::self()->schemaManager()->schema(schema);
// use global color instance, creation is expensive!
const KateDefaultColors &colors(KTextEditor::EditorPrivate::self()->defaultColors());
m_backgroundColor = config.readEntry("Color Background", colors.color(Kate::Background));
m_backgroundColorSet = true;
m_selectionColor = config.readEntry("Color Selection", colors.color(Kate::SelectionBackground));
m_selectionColorSet = true;
m_highlightedLineColor = config.readEntry("Color Highlighted Line", colors.color(Kate::HighlightedLineBackground));
m_highlightedLineColorSet = true;
m_highlightedBracketColor = config.readEntry("Color Highlighted Bracket", colors.color(Kate::HighlightedBracket));
m_highlightedBracketColorSet = true;
m_wordWrapMarkerColor = config.readEntry("Color Word Wrap Marker", colors.color(Kate::WordWrapMarker));
m_wordWrapMarkerColorSet = true;
m_tabMarkerColor = config.readEntry("Color Tab Marker", colors.color(Kate::TabMarker));
m_tabMarkerColorSet = true;
m_indentationLineColor = config.readEntry("Color Indentation Line", colors.color(Kate::IndentationLine));
m_indentationLineColorSet = true;
m_iconBarColor = config.readEntry("Color Icon Bar", colors.color(Kate::IconBar));
m_iconBarColorSet = true;
m_foldingColor = config.readEntry("Color Code Folding", colors.color(Kate::CodeFolding));
m_foldingColorSet = true;
m_lineNumberColor = config.readEntry("Color Line Number", colors.color(Kate::LineNumber));
m_lineNumberColorSet = true;
m_currentLineNumberColor = config.readEntry("Color Current Line Number", colors.color(Kate::CurrentLineNumber));
m_currentLineNumberColorSet = true;
m_separatorColor = config.readEntry("Color Separator", colors.color(Kate::Separator));
m_separatorColorSet = true;
m_spellingMistakeLineColor = config.readEntry("Color Spelling Mistake Line", colors.color(Kate::SpellingMistakeLine));
m_spellingMistakeLineColorSet = true;
m_modifiedLineColor = config.readEntry("Color Modified Lines", colors.color(Kate::ModifiedLine));
m_modifiedLineColorSet = true;
m_savedLineColor = config.readEntry("Color Saved Lines", colors.color(Kate::SavedLine));
m_savedLineColorSet = true;
m_searchHighlightColor = config.readEntry("Color Search Highlight", colors.color(Kate::SearchHighlight));
m_searchHighlightColorSet = true;
m_replaceHighlightColor = config.readEntry("Color Replace Highlight", colors.color(Kate::ReplaceHighlight));
m_replaceHighlightColorSet = true;
for (int i = Kate::FIRST_MARK; i <= Kate::LAST_MARK; i++) {
QColor col = config.readEntry(QStringLiteral("Color MarkType %1").arg(i + 1), colors.mark(i));
m_lineMarkerColorSet[i] = true;
m_lineMarkerColor[i] = col;
}
setFontWithDroppedStyleName(config.readEntry("Font", QFontDatabase::systemFont(QFontDatabase::FixedFont)));
m_templateBackgroundColor = config.readEntry(QStringLiteral("Color Template Background"), colors.color(Kate::TemplateBackground));
m_templateFocusedEditablePlaceholderColor = config.readEntry(QStringLiteral("Color Template Focused Editable Placeholder"),
colors.color(Kate::TemplateFocusedEditablePlaceholder));
m_templateEditablePlaceholderColor = config.readEntry(QStringLiteral("Color Template Editable Placeholder"),
colors.color(Kate::TemplateEditablePlaceholder));
m_templateNotEditablePlaceholderColor = config.readEntry(QStringLiteral("Color Template Not Editable Placeholder"),
colors.color(Kate::TemplateNotEditablePlaceholder));
m_templateColorsSet = true;
}
const QFont &KateRendererConfig::font() const
{
if (m_fontSet || isGlobal()) {
return m_font;
}
return s_global->font();
}
const QFontMetricsF &KateRendererConfig::fontMetrics() const
{
if (m_fontSet || isGlobal()) {
return m_fontMetrics;
}
return s_global->fontMetrics();
}
void KateRendererConfig::setFont(const QFont &font)
{
if (m_fontSet && m_font == font) {
return;
}
configStart();
setFontWithDroppedStyleName(font);
configEnd();
}
void KateRendererConfig::setFontWithDroppedStyleName(const QFont &font)
{
/**
* Drop styleName, otherwise stuff like bold/italic/... won't work as style!
*/
m_font = font;
m_font.setStyleName(QString());
m_fontMetrics = QFontMetricsF(m_font);
m_fontSet = true;
}
bool KateRendererConfig::wordWrapMarker() const
{
if (m_wordWrapMarkerSet || isGlobal()) {
return m_wordWrapMarker;
}
return s_global->wordWrapMarker();
}
void KateRendererConfig::setWordWrapMarker(bool on)
{
if (m_wordWrapMarkerSet && m_wordWrapMarker == on) {
return;
}
configStart();
m_wordWrapMarkerSet = true;
m_wordWrapMarker = on;
configEnd();
}
const QColor &KateRendererConfig::backgroundColor() const
{
if (m_backgroundColorSet || isGlobal()) {
return m_backgroundColor;
}
return s_global->backgroundColor();
}
void KateRendererConfig::setBackgroundColor(const QColor &col)
{
if (m_backgroundColorSet && m_backgroundColor == col) {
return;
}
configStart();
m_backgroundColorSet = true;
m_backgroundColor = col;
configEnd();
}
const QColor &KateRendererConfig::selectionColor() const
{
if (m_selectionColorSet || isGlobal()) {
return m_selectionColor;
}
return s_global->selectionColor();
}
void KateRendererConfig::setSelectionColor(const QColor &col)
{
if (m_selectionColorSet && m_selectionColor == col) {
return;
}
configStart();
m_selectionColorSet = true;
m_selectionColor = col;
configEnd();
}
const QColor &KateRendererConfig::highlightedLineColor() const
{
if (m_highlightedLineColorSet || isGlobal()) {
return m_highlightedLineColor;
}
return s_global->highlightedLineColor();
}
void KateRendererConfig::setHighlightedLineColor(const QColor &col)
{
if (m_highlightedLineColorSet && m_highlightedLineColor == col) {
return;
}
configStart();
m_highlightedLineColorSet = true;
m_highlightedLineColor = col;
configEnd();
}
const QColor &KateRendererConfig::lineMarkerColor(KTextEditor::MarkInterface::MarkTypes type) const
{
int index = 0;
if (type > 0) {
while ((type >> index++) ^ 1) {}
}
index -= 1;
if (index < 0 || index >= KTextEditor::MarkInterface::reservedMarkersCount()) {
static QColor dummy;
return dummy;
}
if (m_lineMarkerColorSet[index] || isGlobal()) {
return m_lineMarkerColor[index];
}
return s_global->lineMarkerColor(type);
}
void KateRendererConfig::setLineMarkerColor(const QColor &col, KTextEditor::MarkInterface::MarkTypes type)
{
int index = static_cast(log(static_cast(type)) / log(2.0));
Q_ASSERT(index >= 0 && index < KTextEditor::MarkInterface::reservedMarkersCount());
if (m_lineMarkerColorSet[index] && m_lineMarkerColor[index] == col) {
return;
}
configStart();
m_lineMarkerColorSet[index] = true;
m_lineMarkerColor[index] = col;
configEnd();
}
const QColor &KateRendererConfig::highlightedBracketColor() const
{
if (m_highlightedBracketColorSet || isGlobal()) {
return m_highlightedBracketColor;
}
return s_global->highlightedBracketColor();
}
void KateRendererConfig::setHighlightedBracketColor(const QColor &col)
{
if (m_highlightedBracketColorSet && m_highlightedBracketColor == col) {
return;
}
configStart();
m_highlightedBracketColorSet = true;
m_highlightedBracketColor = col;
configEnd();
}
const QColor &KateRendererConfig::wordWrapMarkerColor() const
{
if (m_wordWrapMarkerColorSet || isGlobal()) {
return m_wordWrapMarkerColor;
}
return s_global->wordWrapMarkerColor();
}
void KateRendererConfig::setWordWrapMarkerColor(const QColor &col)
{
if (m_wordWrapMarkerColorSet && m_wordWrapMarkerColor == col) {
return;
}
configStart();
m_wordWrapMarkerColorSet = true;
m_wordWrapMarkerColor = col;
configEnd();
}
const QColor &KateRendererConfig::tabMarkerColor() const
{
if (m_tabMarkerColorSet || isGlobal()) {
return m_tabMarkerColor;
}
return s_global->tabMarkerColor();
}
void KateRendererConfig::setTabMarkerColor(const QColor &col)
{
if (m_tabMarkerColorSet && m_tabMarkerColor == col) {
return;
}
configStart();
m_tabMarkerColorSet = true;
m_tabMarkerColor = col;
configEnd();
}
const QColor &KateRendererConfig::indentationLineColor() const
{
if (m_indentationLineColorSet || isGlobal()) {
return m_indentationLineColor;
}
return s_global->indentationLineColor();
}
void KateRendererConfig::setIndentationLineColor(const QColor &col)
{
if (m_indentationLineColorSet && m_indentationLineColor == col) {
return;
}
configStart();
m_indentationLineColorSet = true;
m_indentationLineColor = col;
configEnd();
}
const QColor &KateRendererConfig::iconBarColor() const
{
if (m_iconBarColorSet || isGlobal()) {
return m_iconBarColor;
}
return s_global->iconBarColor();
}
void KateRendererConfig::setIconBarColor(const QColor &col)
{
if (m_iconBarColorSet && m_iconBarColor == col) {
return;
}
configStart();
m_iconBarColorSet = true;
m_iconBarColor = col;
configEnd();
}
const QColor &KateRendererConfig::foldingColor() const
{
if (m_foldingColorSet || isGlobal()) {
return m_foldingColor;
}
return s_global->foldingColor();
}
void KateRendererConfig::setFoldingColor(const QColor &col)
{
if (m_foldingColorSet && m_foldingColor == col) {
return;
}
configStart();
m_foldingColorSet = true;
m_foldingColor = col;
configEnd();
}
const QColor &KateRendererConfig::templateBackgroundColor() const
{
if (m_templateColorsSet || isGlobal()) {
return m_templateBackgroundColor;
}
return s_global->templateBackgroundColor();
}
const QColor &KateRendererConfig::templateEditablePlaceholderColor() const
{
if (m_templateColorsSet || isGlobal()) {
return m_templateEditablePlaceholderColor;
}
return s_global->templateEditablePlaceholderColor();
}
const QColor &KateRendererConfig::templateFocusedEditablePlaceholderColor() const
{
if (m_templateColorsSet || isGlobal()) {
return m_templateFocusedEditablePlaceholderColor;
}
return s_global->templateFocusedEditablePlaceholderColor();
}
const QColor &KateRendererConfig::templateNotEditablePlaceholderColor() const
{
if (m_templateColorsSet || isGlobal()) {
return m_templateNotEditablePlaceholderColor;
}
return s_global->templateNotEditablePlaceholderColor();
}
const QColor &KateRendererConfig::lineNumberColor() const
{
if (m_lineNumberColorSet || isGlobal()) {
return m_lineNumberColor;
}
return s_global->lineNumberColor();
}
void KateRendererConfig::setLineNumberColor(const QColor &col)
{
if (m_lineNumberColorSet && m_lineNumberColor == col) {
return;
}
configStart();
m_lineNumberColorSet = true;
m_lineNumberColor = col;
configEnd();
}
const QColor &KateRendererConfig::currentLineNumberColor() const
{
if (m_currentLineNumberColorSet || isGlobal()) {
return m_currentLineNumberColor;
}
return s_global->currentLineNumberColor();
}
void KateRendererConfig::setCurrentLineNumberColor(const QColor &col)
{
if (m_currentLineNumberColorSet && m_currentLineNumberColor == col) {
return;
}
configStart();
m_currentLineNumberColorSet = true;
m_currentLineNumberColor = col;
configEnd();
}
const QColor &KateRendererConfig::separatorColor() const
{
if (m_separatorColorSet || isGlobal()) {
return m_separatorColor;
}
return s_global->separatorColor();
}
void KateRendererConfig::setSeparatorColor(const QColor &col)
{
if (m_separatorColorSet && m_separatorColor == col) {
return;
}
configStart();
m_separatorColorSet = true;
m_separatorColor = col;
configEnd();
}
const QColor &KateRendererConfig::spellingMistakeLineColor() const
{
if (m_spellingMistakeLineColorSet || isGlobal()) {
return m_spellingMistakeLineColor;
}
return s_global->spellingMistakeLineColor();
}
void KateRendererConfig::setSpellingMistakeLineColor(const QColor &col)
{
if (m_spellingMistakeLineColorSet && m_spellingMistakeLineColor == col) {
return;
}
configStart();
m_spellingMistakeLineColorSet = true;
m_spellingMistakeLineColor = col;
configEnd();
}
const QColor &KateRendererConfig::modifiedLineColor() const
{
if (m_modifiedLineColorSet || isGlobal()) {
return m_modifiedLineColor;
}
return s_global->modifiedLineColor();
}
void KateRendererConfig::setModifiedLineColor(const QColor &col)
{
if (m_modifiedLineColorSet && m_modifiedLineColor == col) {
return;
}
configStart();
m_modifiedLineColorSet = true;
m_modifiedLineColor = col;
configEnd();
}
const QColor &KateRendererConfig::savedLineColor() const
{
if (m_savedLineColorSet || isGlobal()) {
return m_savedLineColor;
}
return s_global->savedLineColor();
}
void KateRendererConfig::setSavedLineColor(const QColor &col)
{
if (m_savedLineColorSet && m_savedLineColor == col) {
return;
}
configStart();
m_savedLineColorSet = true;
m_savedLineColor = col;
configEnd();
}
const QColor &KateRendererConfig::searchHighlightColor() const
{
if (m_searchHighlightColorSet || isGlobal()) {
return m_searchHighlightColor;
}
return s_global->searchHighlightColor();
}
void KateRendererConfig::setSearchHighlightColor(const QColor &col)
{
if (m_searchHighlightColorSet && m_searchHighlightColor == col) {
return;
}
configStart();
m_searchHighlightColorSet = true;
m_searchHighlightColor = col;
configEnd();
}
const QColor &KateRendererConfig::replaceHighlightColor() const
{
if (m_replaceHighlightColorSet || isGlobal()) {
return m_replaceHighlightColor;
}
return s_global->replaceHighlightColor();
}
void KateRendererConfig::setReplaceHighlightColor(const QColor &col)
{
if (m_replaceHighlightColorSet && m_replaceHighlightColor == col) {
return;
}
configStart();
m_replaceHighlightColorSet = true;
m_replaceHighlightColor = col;
configEnd();
}
bool KateRendererConfig::showIndentationLines() const
{
if (m_showIndentationLinesSet || isGlobal()) {
return m_showIndentationLines;
}
return s_global->showIndentationLines();
}
void KateRendererConfig::setShowIndentationLines(bool on)
{
if (m_showIndentationLinesSet && m_showIndentationLines == on) {
return;
}
configStart();
m_showIndentationLinesSet = true;
m_showIndentationLines = on;
configEnd();
}
bool KateRendererConfig::showWholeBracketExpression() const
{
if (m_showWholeBracketExpressionSet || isGlobal()) {
return m_showWholeBracketExpression;
}
return s_global->showWholeBracketExpression();
}
void KateRendererConfig::setShowWholeBracketExpression(bool on)
{
if (m_showWholeBracketExpressionSet && m_showWholeBracketExpression == on) {
return;
}
configStart();
m_showWholeBracketExpressionSet = true;
m_showWholeBracketExpression = on;
configEnd();
}
bool KateRendererConfig::animateBracketMatching() const
{
return s_global->m_animateBracketMatching;
}
void KateRendererConfig::setAnimateBracketMatching(bool on)
{
if (!isGlobal()) {
s_global->setAnimateBracketMatching(on);
} else if (on != m_animateBracketMatching) {
configStart();
m_animateBracketMatching = on;
configEnd();
}
}
//END
diff --git a/src/utils/kateconfig.h b/src/utils/kateconfig.h
index 31fabc44..2cc5a393 100644
--- a/src/utils/kateconfig.h
+++ b/src/utils/kateconfig.h
@@ -1,1456 +1,1475 @@
/* This file is part of the KDE libraries
Copyright (C) 2003 Christoph Cullmann
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KATE_CONFIG_H
#define KATE_CONFIG_H
#include
#include
#include "ktexteditor/view.h"
#include
#include
#include