15#include "katedocument.h"
17#include "kateabstractinputmode.h"
18#include "kateautoindent.h"
19#include "katebuffer.h"
20#include "katecompletionwidget.h"
21#include "kateconfig.h"
22#include "katedialogs.h"
23#include "kateglobal.h"
24#include "katehighlight.h"
25#include "kateindentdetecter.h"
26#include "katemodemanager.h"
27#include "katepartdebug.h"
28#include "kateplaintextsearch.h"
29#include "kateregexpsearch.h"
30#include "katerenderer.h"
31#include "katescriptmanager.h"
32#include "kateswapfile.h"
33#include "katesyntaxmanager.h"
34#include "katetemplatehandler.h"
35#include "kateundomanager.h"
36#include "katevariableexpansionmanager.h"
38#include "printing/kateprinter.h"
39#include "spellcheck/ontheflycheck.h"
40#include "spellcheck/prefixstore.h"
41#include "spellcheck/spellcheck.h"
46#include "editorconfig.h"
49#include <KTextEditor/Attribute>
50#include <KTextEditor/DocumentCursor>
51#include <ktexteditor/message.h>
53#include <KConfigGroup>
56#include <KIO/FileCopyJob>
57#include <KIO/JobUiDelegate>
62#include <KNetworkMounts>
63#include <KParts/OpenUrlArguments>
64#include <KStringHandler>
65#include <KToggleAction>
66#include <KXMLGUIFactory>
68#include <QApplication>
70#include <QCryptographicHash>
74#include <QMimeDatabase>
76#include <QRegularExpression>
77#include <QStandardPaths>
78#include <QTemporaryFile>
86#define EDIT_DEBUG qCDebug(LOG_KTE)
93template<
class C,
class E>
94static int indexOf(
const std::initializer_list<C> &list,
const E &entry)
100template<
class C,
class E>
101static bool contains(
const std::initializer_list<C> &list,
const E &entry)
103 return indexOf(list, entry) >= 0;
106static inline QChar matchingStartBracket(
const QChar c)
119static inline QChar matchingEndBracket(
const QChar c,
bool withQuotes =
true)
136static inline QChar matchingBracket(
const QChar c)
138 QChar bracket = matchingStartBracket(c);
140 bracket = matchingEndBracket(c,
false);
145static inline bool isStartBracket(
const QChar c)
147 return !matchingEndBracket(c,
false).
isNull();
150static inline bool isEndBracket(
const QChar c)
152 return !matchingStartBracket(c).
isNull();
155static inline bool isBracket(
const QChar c)
157 return isStartBracket(c) || isEndBracket(c);
164KTextEditor::DocumentPrivate::DocumentPrivate(
const KPluginMetaData &data,
bool bSingleViewMode,
bool bReadOnly,
QWidget *parentWidget,
QObject *parent)
166 , m_bSingleViewMode(bSingleViewMode)
167 , m_bReadOnly(bReadOnly)
177 m_docName(QStringLiteral(
"need init"))
180 m_fileType(QStringLiteral(
"Normal"))
183 m_config(new KateDocumentConfig(this))
187 const auto &aboutData = EditorPrivate::self()->aboutData();
188 setComponentName(aboutData.componentName(), aboutData.displayName());
192 setProgressInfoEnabled(
false);
198 m_buffer->setHighlight(0);
201 m_swapfile = (config()->swapFileMode() == KateDocumentConfig::DisableSwapFile) ?
nullptr : new Kate::SwapFile(this);
207 connect(KateHlManager::self(), &KateHlManager::changed,
this, &KTextEditor::DocumentPrivate::internalHlChanged);
217 m_modOnHdTimer.setSingleShot(
true);
218 m_modOnHdTimer.setInterval(200);
219 connect(&m_modOnHdTimer, &
QTimer::timeout,
this, &KTextEditor::DocumentPrivate::slotDelayedHandleModOnHd);
223 m_autoReloadMode->setWhatsThis(
i18n(
"Automatic reload the document when it was changed on disk"));
226 m_autoReloadThrottle.setSingleShot(
true);
228 m_autoReloadThrottle.setInterval(QStandardPaths::isTestModeEnabled() ? 50 : 3000);
245 if (m_bSingleViewMode && parentWidget) {
247 insertChildClient(view);
255 onTheFlySpellCheckingEnabled(config()->onTheFlySpellCheck());
260 m_autoSaveTimer.setSingleShot(
true);
262 if (isModified() && url().isLocalFile()) {
271KTextEditor::DocumentPrivate::~DocumentPrivate()
278 delete m_modOnHdHandler;
284 delete m_onTheFlyChecker;
285 m_onTheFlyChecker =
nullptr;
287 clearDictionaryRanges();
295 deactivateDirWatch();
306 for (
auto &
mark : std::as_const(m_marks)) {
321 if (m_editingStackPosition != m_editingStack.size() - 1) {
322 m_editingStack.resize(m_editingStackPosition);
326 std::shared_ptr<KTextEditor::MovingCursor> mc;
329 if (!m_editingStack.isEmpty() && cursor.
line() == m_editingStack.top()->line()) {
330 mc = m_editingStack.pop();
335 const int editingStackSizeLimit = 32;
336 if (m_editingStack.size() >= editingStackSizeLimit) {
338 m_editingStack.removeFirst();
340 mc = m_editingStack.takeFirst();
346 mc->setPosition(cursor);
348 mc = std::shared_ptr<KTextEditor::MovingCursor>(newMovingCursor(cursor));
352 m_editingStack.push(mc);
353 m_editingStackPosition = m_editingStack.size() - 1;
358 if (m_editingStack.isEmpty()) {
361 auto targetPos = m_editingStack.at(m_editingStackPosition)->toCursor();
362 if (targetPos == currentCursor) {
363 if (nextOrPrev == Previous) {
364 m_editingStackPosition--;
366 m_editingStackPosition++;
368 m_editingStackPosition = qBound(0, m_editingStackPosition, m_editingStack.size() - 1);
370 return m_editingStack.at(m_editingStackPosition)->toCursor();
375 m_editingStack.clear();
376 m_editingStackPosition = -1;
383 if (!singleViewMode()) {
394 insertChildClient(view);
404 KTextEditor::ViewPrivate *newView =
new KTextEditor::ViewPrivate(
this, parent, mainWindow);
406 if (m_fileChangedDialogsActivated) {
410 Q_EMIT viewCreated(
this, newView);
413 const auto keys = m_messageHash.keys();
415 if (!message->view()) {
416 newView->postMessage(message, m_messageHash[message]);
425 const int col1 = toVirtualColumn(range.
start());
426 const int col2 = toVirtualColumn(range.
end());
427 return KTextEditor::Range(line, fromVirtualColumn(line, col1), line, fromVirtualColumn(line, col2));
434 return editSessionNumber > 0;
439 return m_buffer->text();
445 qCWarning(LOG_KTE) <<
"Text requested for invalid range" << range;
459 for (
int i = range.
start().
line(); (i <= range.
end().
line()) && (i < m_buffer->lines()); ++i) {
464 }
else if (i == range.
end().
line()) {
486 return textLine.
at(position.
column());
491 return text(wordRangeAt(cursor));
497 const int line = cursor.
line();
501 const int lineLenth = textLine.
length();
502 if (cursor.
column() > lineLenth) {
512 while (end < lineLenth && highlight()->isInWord(textLine.
at(end), textLine.
attribute(end))) {
521 const int ln = cursor.
line();
522 const int col = cursor.
column();
524 if (ln < 0 || col < 0 || ln >= lines() || col > lineLength(ln)) {
529 Q_ASSERT(str.
length() >= col);
532 const int len = lineLength(ln);
533 if (col == 0 || col == len) {
546 qCWarning(LOG_KTE) <<
"Text requested for invalid range" << range;
555 Q_ASSERT(range.
start() <= range.
end());
560 for (
int i = range.
start().
line(); (i <= range.
end().
line()) && (i < m_buffer->lines()); ++i) {
565 }
else if (i == range.
end().
line()) {
568 ret << textLine.
text();
586bool KTextEditor::DocumentPrivate::setText(
const QString &s)
588 if (!isReadWrite()) {
592 std::vector<KTextEditor::Mark> msave;
593 msave.reserve(m_marks.size());
594 std::transform(m_marks.cbegin(), m_marks.cend(), std::back_inserter(msave), [](
KTextEditor::Mark *mark) {
598 for (
auto v : std::as_const(m_views)) {
599 static_cast<KTextEditor::ViewPrivate *
>(v)->completionWidget()->setIgnoreBufferSignals(
true);
612 for (
auto v : std::as_const(m_views)) {
613 static_cast<KTextEditor::ViewPrivate *
>(v)->completionWidget()->setIgnoreBufferSignals(
false);
617 setMark(mark.line, mark.type);
623bool KTextEditor::DocumentPrivate::setText(
const QStringList &text)
625 if (!isReadWrite()) {
629 std::vector<KTextEditor::Mark> msave;
630 msave.reserve(m_marks.size());
631 std::transform(m_marks.cbegin(), m_marks.cend(), std::back_inserter(msave), [](
KTextEditor::Mark *mark) {
635 for (
auto v : std::as_const(m_views)) {
636 static_cast<KTextEditor::ViewPrivate *
>(v)->completionWidget()->setIgnoreBufferSignals(
true);
649 for (
auto v : std::as_const(m_views)) {
650 static_cast<KTextEditor::ViewPrivate *
>(v)->completionWidget()->setIgnoreBufferSignals(
false);
654 setMark(mark.line, mark.type);
660bool KTextEditor::DocumentPrivate::clear()
662 if (!isReadWrite()) {
666 for (
auto view : std::as_const(m_views)) {
667 static_cast<ViewPrivate *
>(view)->
clear();
668 static_cast<ViewPrivate *
>(view)->tagAll();
674 Q_EMIT aboutToInvalidateMovingInterfaceContent(
this);
675 m_buffer->invalidateRanges();
677 Q_EMIT aboutToRemoveText(documentRange());
679 return editRemoveLines(0, lastLine());
684 if (!isReadWrite()) {
698 auto insertStart = position;
699 int currentLine = position.
line();
700 int currentLineStart = 0;
701 const int totalLength = text.
length();
702 int insertColumn = position.
column();
705 if (position.
line() >= lines()) {
707 while (line <= position.
line()) {
708 editInsertLine(line,
QString(),
false);
711 if (insertStart == position) {
712 insertStart = m_editLastChangeStartCursor;
720 int positionColumnExpanded = insertColumn;
721 const int tabWidth = config()->tabWidth();
723 if (currentLine < lines()) {
724 positionColumnExpanded = plainKateTextLine(currentLine).toVirtualColumn(insertColumn, tabWidth);
730 for (; pos < totalLength; pos++) {
731 const QChar &ch = text.
at(pos);
735 if (currentLineStart < pos) {
736 editInsertText(currentLine, insertColumn, text.
mid(currentLineStart, pos - currentLineStart), notify);
737 endCol = insertColumn + (pos - currentLineStart);
742 const auto wrapColumn = insertColumn + pos - currentLineStart;
743 const auto currentLineLength = lineLength(currentLine);
744 if (wrapColumn > currentLineLength) {
745 editInsertText(currentLine, currentLineLength,
QString(wrapColumn - currentLineLength,
QLatin1Char(
' ')), notify);
749 editWrapLine(currentLine, wrapColumn,
true,
nullptr, notify);
757 auto l = currentLine < lines();
758 if (currentLine == lastLine() + 1) {
759 editInsertLine(currentLine,
QString(), notify);
762 insertColumn = positionColumnExpanded;
764 insertColumn = plainKateTextLine(currentLine).fromVirtualColumn(insertColumn, tabWidth);
768 currentLineStart = pos + 1;
773 if (currentLineStart < pos) {
774 editInsertText(currentLine, insertColumn, text.
mid(currentLineStart, pos - currentLineStart), notify);
775 endCol = insertColumn + (pos - currentLineStart);
780 Q_EMIT textInsertedRange(
this, insertedRange);
788 if (!isReadWrite()) {
800 if (!isReadWrite()) {
812 Q_EMIT aboutToRemoveText(range);
818 if (range.
end().
line() > lastLine()) {
829 if (to <= lastLine()) {
830 editRemoveText(to, 0, range.
end().
column());
839 editRemoveLines(from + 1, to - 1);
843 editRemoveText(from, range.
start().
column(), m_buffer->plainLine(from).length() - range.
start().
column());
844 editUnWrapLine(from);
849 int startLine = qMax(0, range.
start().
line());
850 int vc1 = toVirtualColumn(range.
start());
851 int vc2 = toVirtualColumn(range.
end());
852 for (
int line = qMin(range.
end().
line(), lastLine()); line >= startLine; --line) {
853 int col1 = fromVirtualColumn(line, vc1);
854 int col2 = fromVirtualColumn(line, vc2);
855 editRemoveText(line, qMin(col1, col2), qAbs(col2 - col1));
863bool KTextEditor::DocumentPrivate::insertLine(
int l,
const QString &str)
865 if (!isReadWrite()) {
869 if (l < 0 || l > lines()) {
873 return editInsertLine(l, str);
876bool KTextEditor::DocumentPrivate::insertLines(
int line,
const QStringList &text)
878 if (!isReadWrite()) {
882 if (line < 0 || line > lines()) {
887 for (
const QString &
string : text) {
888 success &= editInsertLine(line++,
string);
894bool KTextEditor::DocumentPrivate::removeLine(
int line)
896 if (!isReadWrite()) {
900 if (line < 0 || line > lastLine()) {
904 return editRemoveLine(line);
910 for (
int i = 0; i < m_buffer->lines(); ++i) {
911 l += m_buffer->lineLength(i);
918 return m_buffer->lines();
923 return m_buffer->lineLength(line);
928 return m_buffer->cursorToOffset(c);
933 return m_buffer->offsetToCursor(offset);
938 if (line < 0 || line >= lines()) {
943 return l.markedAsModified();
948 if (line < 0 || line >= lines()) {
953 return l.markedAsSavedOnDisk();
958 if (line < 0 || line >= lines()) {
963 return l.markedAsModified() || l.markedAsSavedOnDisk();
975 if (editSessionNumber > 1) {
979 editIsRunning =
true;
984 m_undoManager->editStart();
986 for (
auto view : std::as_const(m_views)) {
987 static_cast<ViewPrivate *
>(view)->editStart();
990 m_buffer->editStart();
999 if (editSessionNumber == 0) {
1005 if (m_buffer->editChanged() && (editSessionNumber == 1)) {
1006 if (m_undoManager->isActive() && config()->wordWrap()) {
1007 wrapText(m_buffer->editTagStart(), m_buffer->editTagEnd());
1011 editSessionNumber--;
1013 if (editSessionNumber > 0) {
1019 m_buffer->editEnd();
1021 m_undoManager->editEnd();
1024 for (
auto view : std::as_const(m_views)) {
1025 static_cast<ViewPrivate *
>(view)->editEnd(m_buffer->editTagStart(), m_buffer->editTagEnd(), m_buffer->editTagFrom());
1028 if (m_buffer->editChanged()) {
1030 Q_EMIT textChanged(
this);
1036 if (m_editLastChangeStartCursor.isValid()) {
1037 saveEditingPositions(m_editLastChangeStartCursor);
1040 if (config()->autoSave() && config()->autoSaveInterval() > 0) {
1041 m_autoSaveTimer.start();
1044 editIsRunning =
false;
1048void KTextEditor::DocumentPrivate::pushEditState()
1050 editStateStack.push(editSessionNumber);
1053void KTextEditor::DocumentPrivate::popEditState()
1055 if (editStateStack.isEmpty()) {
1059 int count = editStateStack.pop() - editSessionNumber;
1070void KTextEditor::DocumentPrivate::inputMethodStart()
1072 m_undoManager->inputMethodStart();
1075void KTextEditor::DocumentPrivate::inputMethodEnd()
1077 m_undoManager->inputMethodEnd();
1082 if (startLine < 0 || endLine < 0) {
1086 if (!isReadWrite()) {
1090 int col = config()->wordWrapAt();
1098 for (
int line = startLine; (line <= endLine) && (line < lines()); line++) {
1104 bool nextlValid = line + 1 < lines();
1109 int eolPosition = l.
length() - 1;
1115 for (; z2 < l.
length(); z2++) {
1117 if (t.
at(z2) == tabChar) {
1118 x += m_buffer->tabWidth() - (x % m_buffer->tabWidth());
1128 const int colInChars = qMin(z2, l.
length() - 1);
1129 int searchStart = colInChars;
1133 if (searchStart == eolPosition && t.
at(searchStart).
isSpace()) {
1145 for (z = searchStart; z >= 0; z--) {
1149 if ((nw < 0) && highlight()->canBreakAt(t.
at(z), l.
attribute(z))) {
1165 if ((nw >= 0) && nw < colInChars) {
1168 z = (nw >= 0) ? nw : colInChars;
1172 editWrapLine(line, z,
true);
1173 editMarkLineAutoWrapped(line + 1,
true);
1178 editInsertText(line + 1, 0, QStringLiteral(
" "));
1181 bool newLineAdded =
false;
1182 editWrapLine(line, z,
false, &newLineAdded);
1184 editMarkLineAutoWrapped(line + 1,
true);
1198 if (first == last) {
1199 return wrapText(first, last);
1202 if (first < 0 || last < first) {
1206 if (last >= lines() || first > last) {
1210 if (!isReadWrite()) {
1217 std::unique_ptr<KTextEditor::MovingRange> range(newMovingRange(
KTextEditor::Range(first, 0, last, 0)));
1218 std::unique_ptr<KTextEditor::MovingCursor> curr(newMovingCursor(
KTextEditor::Cursor(range->start())));
1221 for (
int line = first; line <= range->end().line(); ++line) {
1223 if (plainKateTextLine(first).firstChar() < 0) {
1226 curr->setPosition(curr->line() + 1, 0);
1231 if (plainKateTextLine(line).firstChar() < 0) {
1232 curr->setPosition(line, 0);
1233 joinLines(first, line - 1);
1236 wrapText(first, first);
1238 first = curr->line() + 1;
1244 bool needWrap = (curr->line() != range->end().line());
1245 if (needWrap && plainKateTextLine(first).firstChar() != -1) {
1246 joinLines(first, range->end().line());
1249 wrapText(first, first);
1260 EDIT_DEBUG <<
"editInsertText" << line << col << s;
1262 if (line < 0 || col < 0) {
1271 if (!isReadWrite()) {
1275 auto l = plainKateTextLine(line);
1276 int length = l.length();
1285 if (col2 > length) {
1290 m_undoManager->slotTextInserted(line, col2, s2, l);
1296 m_buffer->insertText(m_editLastChangeStartCursor, s2);
1309 EDIT_DEBUG <<
"editRemoveText" << line << col << len;
1311 if (line < 0 || line >= lines() || col < 0 || len < 0) {
1315 if (!isReadWrite()) {
1332 len = qMin(len, l.
text().
size() - col);
1338 m_undoManager->slotTextRemoved(line, col, oldText, l);
1356 EDIT_DEBUG <<
"editMarkLineAutoWrapped" << line << autowrapped;
1358 if (line < 0 || line >= lines()) {
1362 if (!isReadWrite()) {
1368 m_undoManager->slotMarkLineAutoWrapped(line, autowrapped);
1372 m_buffer->setLineMetaData(line, l);
1382 EDIT_DEBUG <<
"editWrapLine" << line << col << newLine;
1384 if (line < 0 || line >= lines() || col < 0) {
1388 if (!isReadWrite()) {
1392 const auto tl = plainKateTextLine(line);
1396 const bool nextLineValid = lineLength(line + 1) >= 0;
1398 m_undoManager->slotLineWrapped(line, col, tl.length() - col, (!nextLineValid || newLine), tl);
1400 if (!nextLineValid || newLine) {
1404 for (
const auto &mark : std::as_const(m_marks)) {
1405 if (mark->line >= line) {
1406 if ((col == 0) || (mark->line > line)) {
1412 for (
const auto &mark : list) {
1413 m_marks.take(mark->line);
1416 for (
const auto &mark : list) {
1418 m_marks.insert(mark->line, mark);
1421 if (!list.
empty()) {
1422 Q_EMIT marksChanged(
this);
1427 (*newLineAdded) =
true;
1431 m_buffer->unwrapLine(line + 2);
1435 (*newLineAdded) =
false;
1454 EDIT_DEBUG <<
"editUnWrapLine" << line << removeLine << length;
1456 if (line < 0 || line >= lines() || line + 1 >= lines() || length < 0) {
1460 if (!isReadWrite()) {
1470 m_undoManager->slotLineUnWrapped(line, col, length, removeLine, tl, nextLine);
1473 m_buffer->unwrapLine(line + 1);
1476 m_buffer->unwrapLine(line + 1);
1480 for (
const auto &mark : std::as_const(m_marks)) {
1481 if (mark->line >= line + 1) {
1485 if (mark->line == line + 1) {
1486 auto m = m_marks.take(line);
1488 mark->type |= m->type;
1494 for (
const auto &mark : list) {
1495 m_marks.take(mark->line);
1498 for (
const auto &mark : list) {
1500 m_marks.insert(mark->line, mark);
1504 Q_EMIT marksChanged(
this);
1510 Q_EMIT textRemoved(
this,
KTextEditor::Range(line, col, line + 1, 0), QStringLiteral(
"\n"));
1520 EDIT_DEBUG <<
"editInsertLine" << line << s;
1526 if (!isReadWrite()) {
1530 if (line > lines()) {
1536 m_undoManager->slotLineInserted(line, s);
1550 for (
const auto &mark : std::as_const(m_marks)) {
1551 if (mark->line >= line) {
1556 for (
const auto &mark : list) {
1557 m_marks.take(mark->line);
1560 for (
const auto &mark : list) {
1562 m_marks.insert(mark->line, mark);
1566 Q_EMIT marksChanged(
this);
1572 int prevLineLength = lineLength(line - 1);
1579 m_editLastChangeStartCursor = rangeInserted.
start();
1582 Q_EMIT textInsertedRange(
this, rangeInserted);
1592 return editRemoveLines(line, line);
1595bool KTextEditor::DocumentPrivate::editRemoveLines(
int from,
int to)
1598 EDIT_DEBUG <<
"editRemoveLines" << from << to;
1600 if (to < from || from < 0 || to > lastLine()) {
1604 if (!isReadWrite()) {
1609 return editRemoveText(0, 0, lineLength(0));
1616 for (
int line = to; line >= from; --line) {
1619 m_undoManager->slotLineRemoved(line, l.
text(), l);
1625 for (
int line = to; line >= from; --line) {
1627 if (line + 1 < m_buffer->lines()) {
1628 m_buffer->unwrapLine(line + 1);
1630 m_buffer->unwrapLine(line);
1638 int line = mark->line;
1641 }
else if (line >= from) {
1646 for (
int line : rmark) {
1647 delete m_marks.take(line);
1650 for (
auto mark : list) {
1651 m_marks.take(mark->line);
1654 for (
auto mark : list) {
1655 mark->line -= to - from + 1;
1656 m_marks.insert(mark->line, mark);
1660 Q_EMIT marksChanged(
this);
1665 if (to == lastLine() + to - from + 1) {
1668 int prevLineLength = lineLength(from - 1);
1674 m_editLastChangeStartCursor = rangeRemoved.start();
1685uint KTextEditor::DocumentPrivate::undoCount()
const
1687 return m_undoManager->undoCount();
1690uint KTextEditor::DocumentPrivate::redoCount()
const
1692 return m_undoManager->redoCount();
1695void KTextEditor::DocumentPrivate::undo()
1697 for (
auto v : std::as_const(m_views)) {
1698 static_cast<KTextEditor::ViewPrivate *
>(v)->completionWidget()->setIgnoreBufferSignals(
true);
1701 m_undoManager->undo();
1703 for (
auto v : std::as_const(m_views)) {
1704 static_cast<KTextEditor::ViewPrivate *
>(v)->completionWidget()->setIgnoreBufferSignals(
false);
1708void KTextEditor::DocumentPrivate::redo()
1710 for (
auto v : std::as_const(m_views)) {
1711 static_cast<KTextEditor::ViewPrivate *
>(v)->completionWidget()->setIgnoreBufferSignals(
true);
1714 m_undoManager->redo();
1716 for (
auto v : std::as_const(m_views)) {
1717 static_cast<KTextEditor::ViewPrivate *
>(v)->completionWidget()->setIgnoreBufferSignals(
false);
1740 return searcher.search(pattern, range, backwards, patternOptions);
1743 if (escapeSequences) {
1763QWidget *KTextEditor::DocumentPrivate::dialogParent()
1780QUrl KTextEditor::DocumentPrivate::getSaveFileUrl(
const QString &dialogTitle)
1788 return updateFileType(name);
1807 for (KateFileType *type : modeList) {
1816 int mode = KateHlManager::self()->nameFind(name);
1820 m_buffer->setHighlight(mode);
1826 return highlight()->name();
1831 const auto modeList = KateHlManager::self()->modeList();
1834 for (
const auto &hl : modeList) {
1842 return KateHlManager::self()->modeList().at(index).section();
1850void KTextEditor::DocumentPrivate::bufferHlChanged()
1856 m_indenter->checkRequiredStyle();
1858 Q_EMIT highlightingModeChanged(
this);
1863 m_hlSetByUser =
true;
1868 m_bomSetByUser =
true;
1875 if (!flags.
contains(QStringLiteral(
"SkipEncoding"))) {
1878 if (!tmpenc.
isEmpty() && (tmpenc != encoding())) {
1879 setEncoding(tmpenc);
1883 if (!flags.
contains(QStringLiteral(
"SkipUrl"))) {
1898 if (!flags.
contains(QStringLiteral(
"SkipMode")) && kconfig.
hasKey(
"Mode Set By User")) {
1899 updateFileType(kconfig.
readEntry(
"Mode"),
true );
1902 if (!flags.
contains(QStringLiteral(
"SkipHighlighting"))) {
1904 if (kconfig.
hasKey(
"Highlighting Set By User")) {
1905 const int mode = KateHlManager::self()->nameFind(kconfig.
readEntry(
"Highlighting"));
1906 m_hlSetByUser =
true;
1909 m_buffer->setHighlight(mode);
1916 if (!userSetIndentMode.
isEmpty()) {
1917 config()->setIndentationMode(userSetIndentMode);
1922 for (
int i = 0; i < marks.
count(); i++) {
1932 if (this->url().isLocalFile()) {
1933 const QString path = this->url().toLocalFile();
1939 if (!flags.
contains(QStringLiteral(
"SkipUrl"))) {
1941 kconfig.
writeEntry(
"URL", this->url().toString());
1945 if (encoding() !=
QLatin1String(
"UTF-8") && !m_buffer->brokenEncoding() && !flags.
contains(QStringLiteral(
"SkipEncoding"))) {
1951 if (m_fileTypeSetByUser && !flags.
contains(QStringLiteral(
"SkipMode"))) {
1952 kconfig.
writeEntry(
"Mode Set By User",
true);
1956 if (m_hlSetByUser && !flags.
contains(QStringLiteral(
"SkipHighlighting"))) {
1958 kconfig.
writeEntry(
"Highlighting", highlight()->name());
1961 kconfig.
writeEntry(
"Highlighting Set By User", m_hlSetByUser);
1965 if (m_indenterSetByUser) {
1966 kconfig.
writeEntry(
"Indentation Mode", config()->indentationMode());
1971 for (
const auto &mark : std::as_const(m_marks)) {
1994void KTextEditor::DocumentPrivate::setMark(
int line, uint markType)
1997 addMark(line, markType);
2000void KTextEditor::DocumentPrivate::clearMark(
int line)
2002 if (line < 0 || line > lastLine()) {
2006 if (
auto mark = m_marks.take(line)) {
2007 Q_EMIT markChanged(
this, *mark, MarkRemoved);
2008 Q_EMIT marksChanged(
this);
2015void KTextEditor::DocumentPrivate::addMark(
int line, uint markType)
2019 if (line < 0 || line > lastLine()) {
2023 if (markType == 0) {
2027 if ((mark = m_marks.value(line))) {
2029 markType &= ~mark->type;
2031 if (markType == 0) {
2036 mark->
type |= markType;
2040 mark->
type = markType;
2041 m_marks.insert(line, mark);
2047 temp.
type = markType;
2048 Q_EMIT markChanged(
this, temp, MarkAdded);
2050 Q_EMIT marksChanged(
this);
2055void KTextEditor::DocumentPrivate::removeMark(
int line, uint markType)
2057 if (line < 0 || line > lastLine()) {
2061 auto it = m_marks.find(line);
2062 if (it == m_marks.end()) {
2068 markType &= mark->
type;
2070 if (markType == 0) {
2075 mark->
type &= ~markType;
2080 temp.
type = markType;
2081 Q_EMIT markChanged(
this, temp, MarkRemoved);
2083 if (mark->
type == 0) {
2088 Q_EMIT marksChanged(
this);
2098void KTextEditor::DocumentPrivate::requestMarkTooltip(
int line,
QPoint position)
2105 bool handled =
false;
2106 Q_EMIT markToolTipRequested(
this, *mark, position, handled);
2111 bool handled =
false;
2116 Q_EMIT markClicked(
this, *mark, handled);
2124 bool handled =
false;
2127 Q_EMIT markContextMenuRequested(
this,
KTextEditor::Mark{.line = line, .type = 0}, position, handled);
2129 Q_EMIT markContextMenuRequested(
this, *mark, position, handled);
2144 for (
const auto &m : marksCopy) {
2145 Q_EMIT markChanged(
this, *m, MarkRemoved);
2150 Q_EMIT marksChanged(
this);
2156 m_markDescriptions.insert(type, description);
2162 if ((uint)type >= (uint)markType01 && (uint)type <= reserved) {
2163 return KateRendererConfig::global()->lineMarkerColor(type);
2171 return m_markDescriptions.value(type,
QString());
2174void KTextEditor::DocumentPrivate::setEditableMarks(uint markMask)
2176 m_editableMarks = markMask;
2181 return m_editableMarks;
2187 m_markIcons.insert(markType, icon);
2192 return m_markIcons.value(markType,
QIcon());
2196bool KTextEditor::DocumentPrivate::print()
2198 return KatePrinter::print(
this);
2201void KTextEditor::DocumentPrivate::printPreview()
2203 KatePrinter::printPreview(
this);
2210 if (!m_modOnHd && url().isLocalFile()) {
2218 for (
int i = 0; (i < lines()) && (buf.
size() <= 4096); ++i) {
2219 buf.
append(line(i).toUtf8());
2224 if (!url().path().isEmpty()) {
2234void KTextEditor::DocumentPrivate::showAndSetOpeningErrorAccess()
2237 i18n(
"The file %1 could not be loaded, as it was not possible to read from it.<br />Check if you have read access to this file.",
2240 message->setWordWrap(
true);
2242 i18nc(
"translators: you can also translate 'Try Again' with 'Reload'",
"Try Again"),
2247 closeAction->
setToolTip(
i18nc(
"Close the message being displayed",
"Close message"));
2250 message->addAction(tryAgainAction);
2251 message->addAction(closeAction);
2254 postMessage(message);
2257 m_openingError =
true;
2261void KTextEditor::DocumentPrivate::openWithLineLengthLimitOverride()
2264 const int longestLine = m_buffer->longestLineLoaded();
2265 int newLimit = pow(2, ceil(log2(longestLine)));
2266 if (newLimit <= longestLine) {
2271 config()->setLineLengthLimit(newLimit);
2276 if (!m_openingError) {
2278 m_readWriteStateBeforeLoading =
true;
2284 return config()->lineLengthLimit();
2291 Q_EMIT aboutToInvalidateMovingInterfaceContent(
this);
2294 m_openingError =
false;
2300 QString currentEncoding = encoding();
2305 QString mimeType = arguments().mimeType();
2307 if (pos != -1 && !(m_reloading && m_userSetEncodingForNextReload)) {
2308 setEncoding(mimeType.
mid(pos + 1));
2319 if (m_reloading && m_userSetEncodingForNextReload && (currentEncoding != encoding())) {
2320 setEncoding(currentEncoding);
2323 bool success = m_buffer->openFile(localFilePath(), (m_reloading && m_userSetEncodingForNextReload));
2336 for (
auto view : std::as_const(m_views)) {
2339 static_cast<ViewPrivate *
>(view)->updateView(
true);
2343 Q_EMIT textChanged(
this);
2344 Q_EMIT loaded(
this);
2351 m_modOnHdReason = OnDiskUnmodified;
2352 m_prevModOnHdReason = OnDiskUnmodified;
2353 Q_EMIT modifiedOnDisk(
this, m_modOnHd, m_modOnHdReason);
2358 if (!isEmpty() && config()->autoDetectIndent() && !config()->isSet(KateDocumentConfig::IndentationWidth)
2359 && !config()->isSet(KateDocumentConfig::ReplaceTabsWithSpaces)) {
2361 auto result = detecter.detect(config()->indentationWidth(), config()->replaceTabsDyn());
2362 config()->setIndentationWidth(result.indentWidth);
2363 config()->setReplaceTabsDyn(result.indentUsingSpaces);
2370 showAndSetOpeningErrorAccess();
2374 if (m_buffer->brokenEncoding()) {
2376 setReadWrite(
false);
2377 m_readWriteStateBeforeLoading =
false;
2379 i18n(
"The file %1 was opened with %2 encoding but contained invalid characters.<br />"
2380 "It is set to read-only mode, as saving might destroy its content.<br />"
2381 "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.",
2383 m_buffer->textCodec()),
2385 message->setWordWrap(
true);
2386 postMessage(message);
2389 m_openingError =
true;
2393 if (m_buffer->tooLongLinesWrapped()) {
2395 setReadWrite(
false);
2396 m_readWriteStateBeforeLoading =
false;
2398 new KTextEditor::Message(
i18n(
"The file %1 was opened and contained lines longer than the configured Line Length Limit (%2 characters).<br />"
2399 "The longest of those lines was %3 characters long<br/>"
2400 "Those lines were wrapped and the document is set to read-only mode, as saving will modify its content.",
2402 config()->lineLengthLimit(),
2403 m_buffer->longestLineLoaded()),
2405 QAction *increaseAndReload =
new QAction(
i18n(
"Temporarily raise limit and reload file"), message);
2406 connect(increaseAndReload, &
QAction::triggered,
this, &KTextEditor::DocumentPrivate::openWithLineLengthLimitOverride);
2407 message->addAction(increaseAndReload,
true);
2408 message->addAction(
new QAction(
i18n(
"Close"), message),
true);
2409 message->setWordWrap(
true);
2410 postMessage(message);
2413 m_openingError =
true;
2425 delete m_modOnHdHandler;
2428 if (!url().isEmpty()) {
2429 if (m_fileChangedDialogsActivated && m_modOnHd) {
2432 if (!isModified()) {
2435 str +
i18n(
"Do you really want to save this unmodified file? You could overwrite changed data in the file on disk."),
2436 i18n(
"Trying to Save Unmodified File"),
2446 "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."),
2447 i18n(
"Possible Data Loss"),
2459 if (!m_buffer->canEncode()
2461 i18n(
"The selected encoding cannot encode every Unicode character in this document. Do you really want to save "
2462 "it? There could be some data lost."),
2463 i18n(
"Possible Data Loss"),
2471 if (!createBackupFile()) {
2476 QString oldPath = m_dirWatchFile;
2479 if (oldPath != localFilePath()) {
2482 if (url().isLocalFile()) {
2489 const bool variablesWereRead = readVariables();
2495 if (!variablesWereRead) {
2496 for (
auto *view : std::as_const(m_views)) {
2497 auto v =
static_cast<ViewPrivate *
>(view);
2498 if (v->isVisible()) {
2499 const auto range = v->visibleRange();
2501 bool repaint =
false;
2503 if (isLineModified(i)) {
2510 v->updateView(
true);
2517 deactivateDirWatch();
2522 removeTrailingSpacesAndAddNewLineAtEof();
2527 if (!m_buffer->saveFile(localFilePath())) {
2529 activateDirWatch(oldPath);
2531 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 "
2532 "that enough disk space is available.\nThe original file may be lost or damaged. "
2533 "Don't quit the application until the file is successfully written.",
2549 m_modOnHdReason = OnDiskUnmodified;
2550 m_prevModOnHdReason = OnDiskUnmodified;
2551 Q_EMIT modifiedOnDisk(
this, m_modOnHd, m_modOnHdReason);
2556 m_undoManager->undoSafePoint();
2557 m_undoManager->updateLineModifications();
2565bool KTextEditor::DocumentPrivate::createBackupFile()
2568 const bool backupLocalFiles = config()->backupOnSaveLocal();
2569 const bool backupRemoteFiles = config()->backupOnSaveRemote();
2573 if (!backupLocalFiles && !backupRemoteFiles) {
2580 bool needBackup = backupLocalFiles && backupRemoteFiles;
2582 bool slowOrRemoteFile = !u.isLocalFile();
2583 if (!slowOrRemoteFile) {
2587 slowOrRemoteFile = (mountPoint && mountPoint->probablySlow());
2589 needBackup = (!slowOrRemoteFile && backupLocalFiles) || (slowOrRemoteFile && backupRemoteFiles);
2600 if (backupPrefix.isEmpty() && backupSuffix.isEmpty()) {
2607 u.setPath(backupPrefix + u.fileName() + backupSuffix);
2610 const QString fileName = u.fileName();
2612 u.setPath(u.path() + backupPrefix + fileName + backupSuffix);
2615 qCDebug(LOG_KTE) <<
"backup src file name: " << url();
2616 qCDebug(LOG_KTE) <<
"backup dst file name: " << u;
2619 bool backupSuccess =
false;
2622 if (u.isLocalFile()) {
2625 QFile backupFile(u.toLocalFile());
2626 if (backupFile.exists()) {
2627 backupFile.remove();
2630 backupSuccess =
QFile::copy(url().toLocalFile(), u.toLocalFile());
2632 backupSuccess =
true;
2638 if (statJob->
exec()) {
2643 backupSuccess = job->
exec();
2645 backupSuccess =
true;
2652 i18n(
"For file %1 no backup copy could be created before saving."
2653 " If an error occurs while saving, you might lose the data of this file."
2654 " A reason could be that the media you write to is full or the directory of the file is read-only for you.",
2656 i18n(
"Failed to create backup copy."),
2659 QStringLiteral(
"Backup Failed Warning"))
2667void KTextEditor::DocumentPrivate::readDirConfig(KTextEditor::ViewPrivate *v)
2677 while (!seenDirectories.
contains(
dir.absolutePath())) {
2679 seenDirectories.
insert(
dir.absolutePath());
2687 QString line = stream.readLine();
2688 while ((linesRead < 32) && !line.
isNull()) {
2689 readVariableLine(line, v);
2691 line = stream.readLine();
2705#if EDITORCONFIG_FOUND
2707 if (!v && config()->value(KateDocumentConfig::UseEditorConfig).toBool()) {
2711 EditorConfig editorConfig(
this);
2712 editorConfig.parse();
2717void KTextEditor::DocumentPrivate::activateDirWatch(
const QString &useFileName)
2719 QString fileToUse = useFileName;
2721 fileToUse = localFilePath();
2735 if (fileToUse == m_dirWatchFile) {
2740 deactivateDirWatch();
2743 if (url().isLocalFile() && !fileToUse.
isEmpty()) {
2745 m_dirWatchFile = fileToUse;
2749void KTextEditor::DocumentPrivate::deactivateDirWatch()
2751 if (!m_dirWatchFile.isEmpty()) {
2755 m_dirWatchFile.clear();
2758bool KTextEditor::DocumentPrivate::openUrl(
const QUrl &url)
2762 m_fileTypeSetByUser =
false;
2769bool KTextEditor::DocumentPrivate::closeUrl()
2774 if (!m_reloading && !url().isEmpty()) {
2775 if (m_fileChangedDialogsActivated && m_modOnHd) {
2777 delete m_modOnHdHandler;
2779 QWidget *parentWidget(dialogParent());
2782 +
i18n(
"Do you really want to continue to close this file? Data loss may occur."),
2783 i18n(
"Possible Data Loss"),
2786 QStringLiteral(
"kate_close_modonhd_%1").arg(m_modOnHdReason))
2789 m_reloading =
false;
2800 m_reloading =
false;
2806 Q_EMIT aboutToClose(
this);
2810 if (!m_messageHash.isEmpty()) {
2811 const auto keys = m_messageHash.keys();
2818 Q_EMIT aboutToInvalidateMovingInterfaceContent(
this);
2821 deactivateDirWatch();
2829 m_modOnHdReason = OnDiskUnmodified;
2830 m_prevModOnHdReason = OnDiskUnmodified;
2831 Q_EMIT modifiedOnDisk(
this, m_modOnHd, m_modOnHdReason);
2841 m_undoManager->clearUndo();
2842 m_undoManager->clearRedo();
2848 m_buffer->setHighlight(0);
2851 for (
auto view : std::as_const(m_views)) {
2852 static_cast<ViewPrivate *
>(view)->clearSelection();
2853 static_cast<ViewPrivate *
>(view)->
clear();
2858 m_swapfile->fileClosed();
2867 return m_swapfile && m_swapfile->shouldRecover();
2872 if (isDataRecoveryAvailable()) {
2873 m_swapfile->recover();
2879 if (isDataRecoveryAvailable()) {
2880 m_swapfile->discard();
2884void KTextEditor::DocumentPrivate::setReadWrite(
bool rw)
2886 if (isReadWrite() == rw) {
2892 for (
auto v : std::as_const(m_views)) {
2893 auto view =
static_cast<ViewPrivate *
>(v);
2894 view->slotUpdateUndo();
2895 view->slotReadWriteChanged();
2898 Q_EMIT readWriteChanged(
this);
2903 if (isModified() != m) {
2906 for (
auto view : std::as_const(m_views)) {
2907 static_cast<ViewPrivate *
>(view)->slotUpdateUndo();
2910 Q_EMIT modifiedChanged(
this);
2913 m_undoManager->setModified(m);
2919void KTextEditor::DocumentPrivate::makeAttribs(
bool needInvalidate)
2921 for (
auto view : std::as_const(m_views)) {
2922 static_cast<ViewPrivate *
>(view)->renderer()->updateAttributes();
2925 if (needInvalidate) {
2926 m_buffer->invalidateHighlighting();
2929 for (
auto v : std::as_const(m_views)) {
2930 auto view =
static_cast<ViewPrivate *
>(v);
2932 view->updateView(
true);
2937void KTextEditor::DocumentPrivate::internalHlChanged()
2944 Q_ASSERT(!m_views.contains(view));
2945 m_views.append(view);
2946 auto *v =
static_cast<KTextEditor::ViewPrivate *
>(view);
2949 if (!m_fileType.isEmpty()) {
2958 setActiveView(view);
2963 Q_ASSERT(m_views.contains(view));
2964 m_views.removeAll(view);
2966 if (activeView() == view) {
2967 setActiveView(
nullptr);
2973 if (m_activeView == view) {
2977 m_activeView =
static_cast<KTextEditor::ViewPrivate *
>(view);
2980bool KTextEditor::DocumentPrivate::ownedView(KTextEditor::ViewPrivate *view)
2983 return (m_views.contains(view));
2986int KTextEditor::DocumentPrivate::toVirtualColumn(
int line,
int column)
const
2994 return toVirtualColumn(cursor.
line(), cursor.
column());
2997int KTextEditor::DocumentPrivate::fromVirtualColumn(
int line,
int column)
const
3003int KTextEditor::DocumentPrivate::fromVirtualColumn(
const KTextEditor::Cursor cursor)
const
3005 return fromVirtualColumn(cursor.
line(), cursor.
column());
3014 bool skipAutobrace = closingBracket ==
QLatin1Char(
'\'');
3015 if (highlight() && skipAutobrace) {
3017 skipAutobrace = highlight()->spellCheckingRequiredForLocation(
this, pos - Cursor{0, 1});
3020 if (!skipAutobrace && (closingBracket ==
QLatin1Char(
'\''))) {
3026 skipAutobrace = (count % 2 == 0) ?
true : false;
3028 if (!skipAutobrace && (closingBracket ==
QLatin1Char(
'\"'))) {
3033 skipAutobrace = (count % 2 == 0) ?
true : false;
3035 return skipAutobrace;
3046 QChar closingBracket;
3047 if (view->config()->autoBrackets()) {
3049 const QChar typedChar = chars.
at(0);
3050 const QChar openBracket = matchingStartBracket(typedChar);
3051 if (!openBracket.
isNull()) {
3053 if ((characterAt(curPos) == typedChar) && findMatchingBracket(curPos, 123 ).isValid()) {
3055 view->cursorRight();
3061 if (chars.
size() == 1) {
3063 closingBracket = matchingEndBracket(typedChar);
3066 if (m_currentAutobraceClosingChar == typedChar && m_currentAutobraceRange) {
3068 m_currentAutobraceRange.reset(
nullptr);
3069 view->cursorRight();
3076 if (view->selection() && closingBracket.
isNull() && view->config()->encloseSelectionInChars()) {
3077 const QChar typedChar = chars.
at(0);
3078 if (view->config()->charsToEncloseSelection().
contains(typedChar)) {
3087 if (view->selection() && !closingBracket.
isNull()) {
3088 std::unique_ptr<KTextEditor::MovingRange> selectionRange(newMovingRange(view->selectionRange()));
3089 const int startLine = qMax(0, selectionRange->start().line());
3090 const int endLine = qMin(selectionRange->end().line(), lastLine());
3091 const bool blockMode = view->blockSelection() && (startLine != endLine);
3093 if (selectionRange->start().column() > selectionRange->end().column()) {
3099 const int startColumn = qMin(selectionRange->start().column(), selectionRange->end().column());
3100 const int endColumn = qMax(selectionRange->start().column(), selectionRange->end().column());
3102 for (
int line = startLine; line <= endLine; ++line) {
3104 insertText(r.
end(),
QString(closingBracket));
3105 view->slotTextInserted(view, r.
end(),
QString(closingBracket));
3106 insertText(r.
start(), chars);
3107 view->slotTextInserted(view, r.
start(), chars);
3111 for (
const auto &cursor : view->secondaryCursors()) {
3112 if (!cursor.range) {
3115 const auto &currSelectionRange = cursor.range;
3116 auto expandBehaviour = currSelectionRange->insertBehaviors();
3118 insertText(currSelectionRange->end(),
QString(closingBracket));
3119 insertText(currSelectionRange->start(), chars);
3120 currSelectionRange->setInsertBehaviors(expandBehaviour);
3121 cursor.pos->setPosition(currSelectionRange->end());
3122 auto mutableCursor =
const_cast<KTextEditor::ViewPrivate::SecondaryCursor *
>(&cursor);
3123 mutableCursor->anchor = currSelectionRange->start().toCursor();
3127 insertText(selectionRange->end(),
QString(closingBracket));
3128 view->slotTextInserted(view, selectionRange->end(),
QString(closingBracket));
3129 insertText(selectionRange->start(), chars);
3130 view->slotTextInserted(view, selectionRange->start(), chars);
3134 view->setSelection(selectionRange->toRange());
3135 view->setCursorPosition(selectionRange->end());
3142 if (!view->config()->persistentSelection() && view->selection()) {
3143 view->removeSelectedText();
3148 const bool multiLineBlockMode = view->blockSelection() && view->selection();
3149 if (view->currentInputMode()->overwrite()) {
3152 const int startLine = multiLineBlockMode ? qMax(0, selectionRange.
start().
line()) : view->cursorPosition().
line();
3153 const int endLine = multiLineBlockMode ? qMin(selectionRange.
end().
line(), lastLine()) : startLine;
3154 const int virtualColumn = toVirtualColumn(multiLineBlockMode ? selectionRange.
end() : view->cursorPosition());
3156 for (
int line = endLine; line >= startLine; --line) {
3158 const int column = fromVirtualColumn(line, virtualColumn);
3162 if (oldCur.
column() < lineLength(line)) {
3164 view->currentInputMode()->overwrittenChar(removed);
3171 chars = eventuallyReplaceTabs(view->cursorPosition(), chars);
3173 if (multiLineBlockMode) {
3175 const int startLine = qMax(0, selectionRange.
start().
line());
3176 const int endLine = qMin(selectionRange.
end().
line(), lastLine());
3177 const int column = toVirtualColumn(selectionRange.
end());
3178 for (
int line = endLine; line >= startLine; --line) {
3179 editInsertText(line, fromVirtualColumn(line, column), chars);
3181 int newSelectionColumn = toVirtualColumn(view->cursorPosition());
3184 view->setSelection(selectionRange);
3189 view->completionWidget()->setIgnoreBufferSignals(
true);
3190 const auto &sc = view->secondaryCursors();
3192 const bool hasClosingBracket = !closingBracket.
isNull();
3193 const QString closingChar = closingBracket;
3195 std::vector<std::pair<Kate::TextCursor *, KTextEditor::Cursor>> freezedCursors;
3196 for (
auto it = sc.begin(); it != sc.end(); ++it) {
3197 auto pos = it->cursor();
3198 if (it != sc.begin() && pos == std::prev(it)->cursor()) {
3199 freezedCursors.push_back({std::prev(it)->pos.get(), std::prev(it)->cursor()});
3202 lastInsertionCursor = pos;
3203 insertText(pos, chars);
3206 if (it->cursor() == view->cursorPosition()) {
3207 freezedCursors.push_back({it->pos.get(), it->cursor()});
3210 const auto nextChar = view->document()->
text({pos, pos +
Cursor{0, 1}}).trimmed();
3211 if (hasClosingBracket && !skipAutoBrace(closingBracket, pos) && (nextChar.isEmpty() || !nextChar.at(0).isLetterOrNumber())) {
3212 insertText(it->cursor(), closingChar);
3213 it->pos->setPosition(pos);
3217 view->completionWidget()->setIgnoreBufferSignals(
false);
3219 insertText(view->cursorPosition(), chars);
3221 for (
auto &freezed : freezedCursors) {
3222 freezed.first->setPosition(freezed.second);
3230 if (!closingBracket.
isNull() && !skipAutoBrace(closingBracket, view->cursorPosition())) {
3232 const auto cursorPos = view->cursorPosition();
3233 const auto nextChar = view->document()->
text({cursorPos, cursorPos +
Cursor{0, 1}}).trimmed();
3234 if (nextChar.isEmpty() || !nextChar.at(0).isLetterOrNumber()) {
3235 insertText(view->cursorPosition(),
QString(closingBracket));
3236 const auto insertedAt(view->cursorPosition());
3237 view->setCursorPosition(cursorPos);
3242 chars.
append(closingBracket);
3244 m_currentAutobraceClosingChar = closingBracket;
3251 const auto &secondaryCursors = view->secondaryCursors();
3252 for (
const auto &c : secondaryCursors) {
3253 m_indenter->userTypedChar(view, c.cursor(), chars.
isEmpty() ?
QChar() : chars.
at(chars.
length() - 1));
3261 view->slotTextInserted(view, oldCur, chars);
3266 if (m_currentAutobraceRange && !m_currentAutobraceRange->toRange().contains(newPos)) {
3267 m_currentAutobraceRange.reset();
3271void KTextEditor::DocumentPrivate::newLine(KTextEditor::ViewPrivate *v, KTextEditor::DocumentPrivate::NewLineIndent indent, NewLinePos newLinePos)
3275 if (!v->config()->persistentSelection() && v->selection()) {
3276 v->removeSelectedText();
3277 v->clearSelection();
3281 if (c.line() > lastLine()) {
3282 c.setLine(lastLine());
3291 int len = lineLength(ln);
3293 if (c.column() > len) {
3298 editWrapLine(c.line(), c.column());
3301 m_buffer->updateHighlighting();
3308 bool moveCursorToTop =
false;
3309 if (newLinePos == Above) {
3310 if (pos.
line() <= 0) {
3313 moveCursorToTop =
true;
3318 }
else if (newLinePos == Below) {
3319 int lastCol = lineLength(pos.
line());
3322 return std::pair{pos, moveCursorToTop};
3326 const auto &secondaryCursors = v->secondaryCursors();
3327 if (!secondaryCursors.empty()) {
3330 for (
const auto &c : secondaryCursors) {
3331 const auto [newPos, moveCursorToTop] = adjustCusorPos(c.cursor());
3332 c.pos->setPosition(newPos);
3333 insertNewLine(c.cursor());
3334 if (moveCursorToTop) {
3335 c.pos->setPosition({0, 0});
3338 if (indent == KTextEditor::DocumentPrivate::Indent) {
3342 v->setCursorPosition(c.cursor());
3343 m_indenter->userTypedChar(v, c.cursor(),
QLatin1Char(
'\n'));
3345 c.pos->setPosition(v->cursorPosition());
3349 v->setCursorPosition(savedPrimary.toCursor());
3352 const auto [newPos, moveCursorToTop] = adjustCusorPos(v->cursorPosition());
3353 v->setCursorPosition(newPos);
3354 insertNewLine(v->cursorPosition());
3355 if (moveCursorToTop) {
3356 v->setCursorPosition({0, 0});
3359 if (indent == KTextEditor::DocumentPrivate::Indent) {
3360 m_indenter->userTypedChar(v, v->cursorPosition(),
QLatin1Char(
'\n'));
3369 if (textLine.
length() < 2) {
3373 uint col = cursor.
column();
3379 if ((textLine.
length() - col) < 2) {
3383 uint line = cursor.
line();
3394 editRemoveText(line, col, 2);
3395 editInsertText(line, col, s);
3402 Q_ASSERT(!firstWord.
overlaps(secondWord));
3410 const QString tempString = text(secondWord);
3413 replaceText(secondWord, text(firstWord));
3414 replaceText(firstWord, tempString);
3420 int col = qMax(c.
column(), 0);
3421 int line = qMax(c.
line(), 0);
3422 if ((col == 0) && (line == 0)) {
3425 if (line >= lines()) {
3432 bool useNextBlock =
false;
3433 if (config()->backspaceIndents()) {
3440 if (pos < 0 || pos >= (
int)colX) {
3442 if ((
int)col > textLine.
length()) {
3448 useNextBlock =
true;
3451 if (!config()->backspaceIndents() || useNextBlock) {
3454 if (!view->config()->backspaceRemoveComposed()) {
3455 beginCursor.setColumn(col - 1);
3457 if (!isValidTextPosition(beginCursor)) {
3459 beginCursor.setColumn(col - 2);
3462 if (
auto l = view->textLayout(c)) {
3463 beginCursor.setColumn(l->previousCursorPosition(c.
column()));
3478 if (config()->wordWrap() && textLine.
endsWith(QStringLiteral(
" "))) {
3491void KTextEditor::DocumentPrivate::backspace(KTextEditor::ViewPrivate *view)
3493 if (!view->config()->persistentSelection() && view->hasSelections()) {
3497 if (view->blockSelection() && view->selection() && range.
start().
column() > 0 && toVirtualColumn(range.
start()) == toVirtualColumn(range.
end())) {
3500 view->setSelection(range);
3502 view->removeSelectedText();
3503 view->ensureUniqueCursors();
3511 const auto &multiCursors = view->secondaryCursors();
3512 view->completionWidget()->setIgnoreBufferSignals(
true);
3513 for (
const auto &c : multiCursors) {
3514 const auto newPos = backspaceAtCursor(view, c.cursor());
3519 view->completionWidget()->setIgnoreBufferSignals(
false);
3522 auto newPos = backspaceAtCursor(view, view->cursorPosition());
3524 view->setCursorPosition(newPos);
3527 view->ensureUniqueCursors();
3532 if (m_currentAutobraceRange) {
3533 const auto r = m_currentAutobraceRange->toRange();
3534 if (r.columnWidth() == 1 && view->cursorPosition() == r.
start()) {
3536 del(view, view->cursorPosition());
3537 m_currentAutobraceRange.reset();
3542void KTextEditor::DocumentPrivate::del(KTextEditor::ViewPrivate *view,
const KTextEditor::Cursor c)
3544 if (!view->config()->persistentSelection() && view->selection()) {
3547 if (view->blockSelection() && toVirtualColumn(range.
start()) == toVirtualColumn(range.
end())) {
3550 view->setSelection(range);
3552 view->removeSelectedText();
3557 if (c.
column() < m_buffer->lineLength(c.
line())) {
3560 }
else if (c.
line() < lastLine()) {
3565bool KTextEditor::DocumentPrivate::multiPaste(KTextEditor::ViewPrivate *view,
const QStringList &texts)
3567 if (texts.
isEmpty() || view->isMulticursorNotAllowed() || view->secondaryCursors().size() + 1 != (
size_t)texts.
size()) {
3571 m_undoManager->undoSafePoint();
3574 if (view->selection()) {
3575 view->removeSelectedText();
3578 auto plainSecondaryCursors = view->plainSecondaryCursors();
3579 KTextEditor::ViewPrivate::PlainSecondaryCursor primary;
3580 primary.pos = view->cursorPosition();
3581 primary.range = view->selectionRange();
3582 plainSecondaryCursors.append(primary);
3583 std::sort(plainSecondaryCursors.begin(), plainSecondaryCursors.end());
3587 for (
int i = texts.
size() - 1; i >= 0; --i) {
3589 text.
replace(re, QStringLiteral(
"\n"));
3592 insertText(pos, text,
false);
3600void KTextEditor::DocumentPrivate::paste(KTextEditor::ViewPrivate *view,
const QString &text)
3613 const bool isSingleLine = lines == 0;
3615 m_undoManager->undoSafePoint();
3621 bool skipIndentOnPaste =
false;
3623 const int length = lineLength(pos.
line());
3625 skipIndentOnPaste = length > 0;
3628 if (!view->config()->persistentSelection() && view->selection()) {
3629 pos = view->selectionRange().
start();
3630 if (view->blockSelection()) {
3631 pos = rangeOnLine(view->selectionRange(), pos.
line()).
start();
3638 view->removeSelectedText();
3641 if (config()->ovr()) {
3644 if (!view->blockSelection()) {
3645 int endColumn = (pasteLines.count() == 1 ? pos.
column() : 0) + pasteLines.last().length();
3648 int maxi = qMin(pos.
line() + pasteLines.count(), this->lines());
3650 for (
int i = pos.
line(); i < maxi; ++i) {
3651 int pasteLength = pasteLines.at(i - pos.
line()).length();
3657 insertText(pos, s, view->blockSelection());
3664 if (view->blockSelection()) {
3665 view->setCursorPositionInternal(pos);
3668 if (config()->indentPastedText()) {
3670 if (!skipIndentOnPaste) {
3671 m_indenter->indent(view, range);
3675 if (!view->blockSelection()) {
3676 Q_EMIT charactersSemiInteractivelyInserted(pos, s);
3678 m_undoManager->undoSafePoint();
3683 if (!isReadWrite()) {
3688 m_indenter->changeIndent(range, change);
3692void KTextEditor::DocumentPrivate::align(KTextEditor::ViewPrivate *view,
KTextEditor::Range range)
3694 m_indenter->indent(view, range);
3701 if (lines.
size() < 2) {
3707 int selectionStartColumn = range.
start().
column();
3709 for (
const auto &line : lines) {
3711 if (!
match.hasMatch()) {
3712 patternStartColumns.
append(-1);
3713 }
else if (
match.lastCapturedIndex() == 0) {
3714 patternStartColumns.
append(
match.capturedStart(0) + (blockwise ? selectionStartColumn : 0));
3716 patternStartColumns.
append(
match.capturedStart(1) + (blockwise ? selectionStartColumn : 0));
3719 if (!blockwise && patternStartColumns[0] != -1) {
3720 patternStartColumns[0] += selectionStartColumn;
3723 int maxColumn = *std::max_element(patternStartColumns.
cbegin(), patternStartColumns.
cend());
3726 for (
int i = 0; i < lines.size(); ++i) {
3727 if (patternStartColumns[i] != -1) {
3734void KTextEditor::DocumentPrivate::insertTab(KTextEditor::ViewPrivate *view,
const KTextEditor::Cursor)
3736 if (!isReadWrite()) {
3740 int lineLen = line(view->cursorPosition().
line()).length();
3745 if (!view->config()->persistentSelection() && view->selection()) {
3746 view->removeSelectedText();
3747 }
else if (view->currentInputMode()->overwrite() && c.
column() < lineLen) {
3752 view->currentInputMode()->overwrittenChar(removed);
3756 c = view->cursorPosition();
3757 editInsertText(c.
line(), c.
column(), QStringLiteral(
"\t"));
3766bool KTextEditor::DocumentPrivate::removeStringFromBeginning(
int line,
const QString &str)
3790bool KTextEditor::DocumentPrivate::removeStringFromEnd(
int line,
const QString &str)
3795 bool there = textline.
endsWith(str);
3817 const bool replacetabs = config()->replaceTabsDyn();
3821 const int indentWidth = config()->indentationWidth();
3824 int column = cursorPos.
column();
3830 for (
const QChar ch : str) {
3831 if (ch == tabChar) {
3834 int spacesToInsert = indentWidth - (column % indentWidth);
3836 column += spacesToInsert;
3849void KTextEditor::DocumentPrivate::addStartLineCommentToSingleLine(
int line,
int attrib)
3851 const QString commentLineMark = highlight()->getCommentSingleLineStart(attrib) +
QLatin1Char(
' ');
3854 if (highlight()->getCommentSingleLinePosition(attrib) == KSyntaxHighlighting::CommentPosition::AfterWhitespace) {
3865bool KTextEditor::DocumentPrivate::removeStartLineCommentFromSingleLine(
int line,
int attrib)
3867 const QString shortCommentMark = highlight()->getCommentSingleLineStart(attrib);
3873 bool removed = (removeStringFromBeginning(line, longCommentMark) || removeStringFromBeginning(line, shortCommentMark));
3884void KTextEditor::DocumentPrivate::addStartStopCommentToSingleLine(
int line,
int attrib)
3886 const QString startCommentMark = highlight()->getCommentStart(attrib) +
QLatin1Char(
' ');
3887 const QString stopCommentMark =
QLatin1Char(
' ') + highlight()->getCommentEnd(attrib);
3895 const int col = m_buffer->lineLength(line);
3907bool KTextEditor::DocumentPrivate::removeStartStopCommentFromSingleLine(
int line,
int attrib)
3909 const QString shortStartCommentMark = highlight()->getCommentStart(attrib);
3911 const QString shortStopCommentMark = highlight()->getCommentEnd(attrib);
3917 const bool removedStart = (removeStringFromBeginning(line, longStartCommentMark) || removeStringFromBeginning(line, shortStartCommentMark));
3920 const bool removedStop = removedStart && (removeStringFromEnd(line, longStopCommentMark) || removeStringFromEnd(line, shortStopCommentMark));
3924 return (removedStart || removedStop);
3931void KTextEditor::DocumentPrivate::addStartStopCommentToSelection(
KTextEditor::Range selection,
bool blockSelection,
int attrib)
3933 const QString startComment = highlight()->getCommentStart(attrib);
3934 const QString endComment = highlight()->getCommentEnd(attrib);
3944 if (!blockSelection) {
3945 insertText(range.
end(), endComment);
3946 insertText(range.
start(), startComment);
3948 for (
int line = range.
start().
line(); line <= range.
end().
line(); line++) {
3950 insertText(subRange.
end(), endComment);
3951 insertText(subRange.
start(), startComment);
3962void KTextEditor::DocumentPrivate::addStartLineCommentToSelection(
KTextEditor::Range selection,
int attrib)
3965 int el = selection.
end().
line();
3968 if ((selection.
end().
column() == 0) && (el > 0)) {
3972 if (sl < 0 || el < 0 || sl >= lines() || el >= lines()) {
3978 const QString commentLineMark = highlight()->getCommentSingleLineStart(attrib) +
QLatin1Char(
' ');
3981 if (highlight()->getCommentSingleLinePosition(attrib) == KSyntaxHighlighting::CommentPosition::AfterWhitespace) {
3989 col = std::numeric_limits<int>::max();
3991 for (
int l = el; l >= sl; l--) {
3992 const auto line = plainKateTextLine(l);
3993 if (line.length() == 0) {
3996 col = qMin(col, qMax(0, line.firstChar()));
4003 if (col == std::numeric_limits<int>::max()) {
4010 for (
int l = el; l >= sl; l--) {
4017bool KTextEditor::DocumentPrivate::nextNonSpaceCharPos(
int &line,
int &col)
4019 for (; line >= 0 && line < m_buffer->lines(); line++) {
4033bool KTextEditor::DocumentPrivate::previousNonSpaceCharPos(
int &line,
int &col)
4035 while (line >= 0 && line < m_buffer->lines()) {
4057bool KTextEditor::DocumentPrivate::removeStartStopCommentFromSelection(
KTextEditor::Range selection,
int attrib)
4059 const QString startComment = highlight()->getCommentStart(attrib);
4060 const QString endComment = highlight()->getCommentEnd(attrib);
4062 int sl = qMax<int>(0, selection.
start().
line());
4063 int el = qMin<int>(selection.
end().
line(), lastLine());
4070 }
else if (el > 0) {
4072 ec = m_buffer->lineLength(el) - 1;
4075 const int startCommentLen = startComment.
length();
4076 const int endCommentLen = endComment.
length();
4080 bool remove = nextNonSpaceCharPos(sl, sc) && m_buffer->plainLine(sl).matchesAt(sc, startComment) && previousNonSpaceCharPos(el, ec)
4081 && ((ec - endCommentLen + 1) >= 0) && m_buffer->plainLine(el).matchesAt(ec - endCommentLen + 1, endComment);
4098 const QString startComment = highlight()->getCommentStart(attrib);
4099 const QString endComment = highlight()->getCommentEnd(attrib);
4100 const int startCommentLen = startComment.
length();
4101 const int endCommentLen = endComment.
length();
4103 const bool remove = m_buffer->plainLine(
start.line()).matchesAt(
start.column(), startComment)
4104 && m_buffer->plainLine(
end.line()).matchesAt(
end.column() - endCommentLen, endComment);
4118bool KTextEditor::DocumentPrivate::removeStartLineCommentFromSelection(
KTextEditor::Range selection,
int attrib,
bool toggleComment)
4120 const QString shortCommentMark = highlight()->getCommentSingleLineStart(attrib);
4123 const int startLine = selection.
start().
line();
4124 int endLine = selection.
end().
line();
4126 if ((selection.
end().
column() == 0) && (endLine > 0)) {
4130 bool removed =
false;
4136 if (toggleComment) {
4137 bool allLinesAreCommented =
true;
4138 for (
int line = endLine; line >= startLine; line--) {
4139 const auto ln = m_buffer->plainLine(line);
4140 const QString &text = ln.text();
4147 textView = textView.trimmed();
4148 if (!textView.startsWith(shortCommentMark) && !textView.startsWith(longCommentMark)) {
4149 allLinesAreCommented =
false;
4153 if (!allLinesAreCommented) {
4161 for (
int z = endLine; z >= startLine; z--) {
4163 removed = (removeStringFromBeginning(z, longCommentMark) || removeStringFromBeginning(z, shortCommentMark) || removed);
4174 const bool hasSelection = !selection.
isEmpty();
4175 int selectionCol = 0;
4180 const int line = c.
line();
4182 int startAttrib = 0;
4185 if (selectionCol < ln.
length()) {
4186 startAttrib = ln.
attribute(selectionCol);
4191 bool hasStartLineCommentMark = !(highlight()->getCommentSingleLineStart(startAttrib).isEmpty());
4192 bool hasStartStopCommentMark = (!(highlight()->getCommentStart(startAttrib).isEmpty()) && !(highlight()->getCommentEnd(startAttrib).isEmpty()));
4194 if (changeType == Comment) {
4195 if (!hasSelection) {
4196 if (hasStartLineCommentMark) {
4197 addStartLineCommentToSingleLine(line, startAttrib);
4198 }
else if (hasStartStopCommentMark) {
4199 addStartStopCommentToSingleLine(line, startAttrib);
4210 if (hasStartStopCommentMark
4211 && (!hasStartLineCommentMark
4214 addStartStopCommentToSelection(selection, blockSelect, startAttrib);
4215 }
else if (hasStartLineCommentMark) {
4216 addStartLineCommentToSelection(selection, startAttrib);
4220 bool removed =
false;
4221 const bool toggleComment = changeType == ToggleComment;
4222 if (!hasSelection) {
4223 removed = (hasStartLineCommentMark && removeStartLineCommentFromSingleLine(line, startAttrib))
4224 || (hasStartStopCommentMark && removeStartStopCommentFromSingleLine(line, startAttrib));
4227 removed = (hasStartStopCommentMark && removeStartStopCommentFromSelection(selection, startAttrib))
4228 || (hasStartLineCommentMark && removeStartLineCommentFromSelection(selection, startAttrib, toggleComment));
4232 if (!removed && toggleComment) {
4233 commentSelection(selection, c, blockSelect, Comment);
4242void KTextEditor::DocumentPrivate::comment(KTextEditor::ViewPrivate *v, uint line, uint column, CommentType change)
4245 const bool skipWordWrap = wordWrap();
4252 if (v->selection()) {
4253 const auto &cursors = v->secondaryCursors();
4254 for (
const auto &c : cursors) {
4258 commentSelection(c.range->toRange(), c.cursor(),
false, change);
4261 commentSelection(v->selectionRange(), c, v->blockSelection(), change);
4263 const auto &cursors = v->secondaryCursors();
4264 for (
const auto &c : cursors) {
4265 commentSelection({}, c.cursor(),
false, change);
4277void KTextEditor::DocumentPrivate::transformCursorOrRange(KTextEditor::ViewPrivate *v,
4280 KTextEditor::DocumentPrivate::TextTransform t)
4282 if (v->selection()) {
4294 if (range.
start().
line() == selection.
end().
line() || v->blockSelection()) {
4299 int swapCol =
start;
4309 if (t == Uppercase) {
4312 }
else if (t == Lowercase) {
4325 && !highlight()->isInWord(l.
at(range.
start().
column() - 1)))
4326 || (p && !highlight()->isInWord(s.
at(p - 1)))) {
4335 insertText(range.
start(), s);
4370 insertText(cursor, s);
4380 if (v->selection()) {
4381 const auto &cursors = v->secondaryCursors();
4382 for (
const auto &c : cursors) {
4386 auto pos = c.pos->toCursor();
4387 transformCursorOrRange(v, c.anchor, c.range->toRange(), t);
4391 const auto selRange = v->selectionRange();
4392 transformCursorOrRange(v, c, v->selectionRange(), t);
4393 v->setSelection(selRange);
4394 v->setCursorPosition(c);
4396 const auto &secondaryCursors = v->secondaryCursors();
4397 for (
const auto &c : secondaryCursors) {
4398 transformCursorOrRange(v, c.cursor(), {}, t);
4400 transformCursorOrRange(v, c, {}, t);
4411 while (first < last) {
4412 if (line >= lines() || line + 1 >= lines()) {
4428 editRemoveText(line + 1, 0, pos);
4431 editInsertText(line + 1, 0, QStringLiteral(
" "));
4435 editRemoveText(line + 1, 0, tl.
length());
4438 editUnWrapLine(line);
4446 for (
auto view : std::as_const(m_views)) {
4447 static_cast<ViewPrivate *
>(view)->tagLines(lineRange,
true);
4451void KTextEditor::DocumentPrivate::tagLine(
int line)
4453 tagLines({line, line});
4456void KTextEditor::DocumentPrivate::repaintViews(
bool paintOnlyDirty)
4458 for (
auto view : std::as_const(m_views)) {
4459 static_cast<ViewPrivate *
>(view)->repaintText(paintOnlyDirty);
4472 if (maxLines < 0 ||
start.line() < 0 ||
start.line() >= lines()) {
4482 if (config()->ovr()) {
4483 if (isBracket(right)) {
4488 }
else if (isBracket(right)) {
4490 }
else if (isBracket(left)) {
4497 const QChar opposite = matchingBracket(bracket);
4502 const int searchDir = isStartBracket(bracket) ? 1 : -1;
4505 const int minLine = qMax(range.
start().
line() - maxLines, 0);
4506 const int maxLine = qMin(range.
start().
line() + maxLines, documentEnd().line());
4511 int validAttr = kateTextLine(cursor.
line()).attribute(cursor.
column());
4513 while (cursor.
line() >= minLine && cursor.
line() <= maxLine) {
4514 if (!cursor.move(searchDir)) {
4522 if (c == opposite) {
4524 if (searchDir > 0) {
4525 range.
setEnd(cursor.toCursor());
4532 }
else if (c == bracket) {
4548void KTextEditor::DocumentPrivate::updateDocName()
4551 if (!url().isEmpty() && (m_docName == removeNewLines(url().fileName()) || m_docName.
startsWith(removeNewLines(url().fileName()) +
QLatin1String(
" (")))) {
4557 std::vector<KTextEditor::DocumentPrivate *> docsWithSameName;
4562 if ((doc !=
this) && (doc->url().fileName() == url().fileName())) {
4563 if (doc->m_docNameNumber > count) {
4564 count = doc->m_docNameNumber;
4566 docsWithSameName.push_back(doc);
4570 m_docNameNumber = count + 1;
4573 m_docName = removeNewLines(url().fileName());
4575 m_isUntitled = m_docName.isEmpty();
4577 if (!m_isUntitled && !docsWithSameName.empty()) {
4578 docsWithSameName.push_back(
this);
4579 uniquifyDocNames(docsWithSameName);
4584 m_docName =
i18n(
"Untitled");
4587 if (m_docNameNumber > 0) {
4592 if (oldName != m_docName) {
4593 Q_EMIT documentNameChanged(
this);
4611 int lastSlash = url.lastIndexOf(
QLatin1Char(
'/'));
4612 if (lastSlash == -1) {
4616 int fileNameStart = lastSlash;
4619 lastSlash = url.lastIndexOf(
QLatin1Char(
'/'), lastSlash);
4620 if (lastSlash == -1) {
4623 return url.mid(lastSlash, fileNameStart);
4628 urlv = urlv.
mid(lastSlash);
4630 for (
size_t i = 0; i < urls.size(); ++i) {
4631 if (urls[i] == url) {
4635 if (urls[i].endsWith(urlv)) {
4636 lastSlash = url.lastIndexOf(
QLatin1Char(
'/'), lastSlash - 1);
4637 if (lastSlash <= 0) {
4639 return url.mid(0, fileNameStart);
4642 urlv = urlView.
mid(lastSlash);
4647 return url.mid(lastSlash + 1, fileNameStart - (lastSlash + 1));
4650void KTextEditor::DocumentPrivate::uniquifyDocNames(
const std::vector<KTextEditor::DocumentPrivate *> &docs)
4652 std::vector<QString> paths;
4653 paths.reserve(docs.size());
4655 return d->url().toString(QUrl::NormalizePathSegments | QUrl::PreferLocalFile);
4658 for (
const auto doc : docs) {
4659 const QString prefix = shortestPrefix(paths, doc);
4661 const QString oldName = doc->m_docName;
4664 doc->m_docName = fileName + QStringLiteral(
" - ") + prefix;
4666 doc->m_docName = fileName;
4669 if (doc->m_docName != oldName) {
4677 if (url().isEmpty() || !m_modOnHd) {
4681 if (!isModified() && isAutoReload()) {
4682 onModOnHdAutoReload();
4686 if (!m_fileChangedDialogsActivated || m_modOnHdHandler) {
4691 if (m_modOnHdReason == m_prevModOnHdReason) {
4694 m_prevModOnHdReason = m_modOnHdReason;
4696 m_modOnHdHandler =
new KateModOnHdPrompt(
this, m_modOnHdReason, reasonedMOHString());
4697 connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::saveAsTriggered,
this, &DocumentPrivate::onModOnHdSaveAs);
4698 connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::closeTriggered,
this, &DocumentPrivate::onModOnHdClose);
4699 connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::reloadTriggered,
this, &DocumentPrivate::onModOnHdReload);
4700 connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::autoReloadTriggered,
this, &DocumentPrivate::onModOnHdAutoReload);
4701 connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::ignoreTriggered,
this, &DocumentPrivate::onModOnHdIgnore);
4704void KTextEditor::DocumentPrivate::onModOnHdSaveAs()
4707 const QUrl res = getSaveFileUrl(
i18n(
"Save File"));
4713 delete m_modOnHdHandler;
4714 m_prevModOnHdReason = OnDiskUnmodified;
4715 Q_EMIT modifiedOnDisk(
this,
false, OnDiskUnmodified);
4722void KTextEditor::DocumentPrivate::onModOnHdClose()
4728 m_fileChangedDialogsActivated =
false;
4733 m_fileChangedDialogsActivated =
true;
4738void KTextEditor::DocumentPrivate::onModOnHdReload()
4741 m_prevModOnHdReason = OnDiskUnmodified;
4742 Q_EMIT modifiedOnDisk(
this,
false, OnDiskUnmodified);
4748 m_undoManager->clearUndo();
4749 m_undoManager->clearRedo();
4752 delete m_modOnHdHandler;
4755void KTextEditor::DocumentPrivate::autoReloadToggled(
bool b)
4757 m_autoReloadMode->setChecked(b);
4761 disconnect(&m_modOnHdTimer, &
QTimer::timeout,
this, &DocumentPrivate::onModOnHdAutoReload);
4765bool KTextEditor::DocumentPrivate::isAutoReload()
4767 return m_autoReloadMode->isChecked();
4770void KTextEditor::DocumentPrivate::delayAutoReload()
4772 if (isAutoReload()) {
4773 m_autoReloadThrottle.start();
4777void KTextEditor::DocumentPrivate::onModOnHdAutoReload()
4779 if (m_modOnHdHandler) {
4780 delete m_modOnHdHandler;
4781 autoReloadToggled(
true);
4784 if (!isAutoReload()) {
4788 if (m_modOnHd && !m_reloading && !m_autoReloadThrottle.isActive()) {
4790 m_prevModOnHdReason = OnDiskUnmodified;
4791 Q_EMIT modifiedOnDisk(
this,
false, OnDiskUnmodified);
4795 m_undoManager->clearUndo();
4796 m_undoManager->clearRedo();
4799 m_autoReloadThrottle.start();
4803void KTextEditor::DocumentPrivate::onModOnHdIgnore()
4806 delete m_modOnHdHandler;
4811 m_modOnHdReason = reason;
4812 m_modOnHd = (reason != OnDiskUnmodified);
4813 Q_EMIT modifiedOnDisk(
this, (reason != OnDiskUnmodified), reason);
4816class KateDocumentTmpMark
4825 m_fileChangedDialogsActivated = on;
4830 if (url().isEmpty()) {
4842 m_undoManager->clearUndo();
4843 m_undoManager->clearRedo();
4848 delete m_modOnHdHandler;
4850 Q_EMIT aboutToReload(
this);
4854 std::transform(m_marks.cbegin(), m_marks.cend(), std::back_inserter(tmp), [
this](
KTextEditor::Mark *mark) {
4855 return KateDocumentTmpMark{.line = line(mark->line), .mark = *mark};
4859 const QString oldMode = mode();
4860 const bool modeByUser = m_fileTypeSetByUser;
4861 const QString oldHlMode = highlightingMode();
4862 const bool hlByUser = m_hlSetByUser;
4864 m_storedVariables.clear();
4868 std::transform(m_views.cbegin(), m_views.cend(), std::back_inserter(cursorPositions), [](
KTextEditor::View *v) {
4869 return std::pair<KTextEditor::ViewPrivate *, KTextEditor::Cursor>(static_cast<ViewPrivate *>(v), v->cursorPosition());
4874 for (
auto *view : m_views) {
4875 static_cast<ViewPrivate *
>(view)->clearSecondaryCursors();
4878 static_cast<ViewPrivate *
>(view)->clearFoldingState();
4883 KTextEditor::DocumentPrivate::openUrl(url());
4886 m_userSetEncodingForNextReload =
false;
4889 for (
auto v : std::as_const(m_views)) {
4891 auto it = std::find_if(cursorPositions.
cbegin(), cursorPositions.
cend(), [v](
const std::pair<KTextEditor::ViewPrivate *, KTextEditor::Cursor> &p) {
4892 return p.first == v;
4894 v->setCursorPosition(it->second);
4900 const int lines = this->lines();
4901 for (
const auto &tmpMark : tmp) {
4902 if (tmpMark.mark.line < lines) {
4903 if (tmpMark.line == line(tmpMark.mark.line)) {
4904 setMark(tmpMark.mark.line, tmpMark.mark.type);
4911 updateFileType(oldMode,
true);
4914 setHighlightingMode(oldHlMode);
4917 Q_EMIT reloaded(
this);
4922bool KTextEditor::DocumentPrivate::documentSave()
4924 if (!url().
isValid() || !isReadWrite()) {
4925 return documentSaveAs();
4931bool KTextEditor::DocumentPrivate::documentSaveAs()
4933 const QUrl saveUrl = getSaveFileUrl(
i18n(
"Save File"));
4941bool KTextEditor::DocumentPrivate::documentSaveAsWithEncoding(
const QString &encoding)
4943 const QUrl saveUrl = getSaveFileUrl(
i18n(
"Save File"));
4948 setEncoding(encoding);
4952void KTextEditor::DocumentPrivate::documentSaveCopyAs()
4954 const QUrl saveUrl = getSaveFileUrl(
i18n(
"Save Copy of File"));
4960 if (!file->
open()) {
4964 if (!m_buffer->saveFile(file->
fileName())) {
4966 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 "
4967 "that enough disk space is available.",
4975 const auto url = this->url();
4977 if (
auto sj = qobject_cast<KIO::StatJob *>(j)) {
4988void KTextEditor::DocumentPrivate::setWordWrap(
bool on)
4990 config()->setWordWrap(on);
4993bool KTextEditor::DocumentPrivate::wordWrap()
const
4995 return config()->wordWrap();
4998void KTextEditor::DocumentPrivate::setWordWrapAt(uint col)
5000 config()->setWordWrapAt(col);
5003unsigned int KTextEditor::DocumentPrivate::wordWrapAt()
const
5005 return config()->wordWrapAt();
5008void KTextEditor::DocumentPrivate::setPageUpDownMovesCursor(
bool on)
5010 config()->setPageUpDownMovesCursor(on);
5013bool KTextEditor::DocumentPrivate::pageUpDownMovesCursor()
const
5015 return config()->pageUpDownMovesCursor();
5021 return m_config->setEncoding(e);
5026 return m_config->encoding();
5029void KTextEditor::DocumentPrivate::updateConfig()
5031 m_undoManager->updateConfig();
5034 m_indenter->setMode(m_config->indentationMode());
5035 m_indenter->updateConfig();
5038 m_buffer->setTabWidth(config()->tabWidth());
5041 for (
auto view : std::as_const(m_views)) {
5042 static_cast<ViewPrivate *
>(view)->updateDocumentConfig();
5046 if (m_onTheFlyChecker) {
5047 m_onTheFlyChecker->updateConfig();
5050 if (config()->autoSave()) {
5051 int interval = config()->autoSaveInterval();
5052 if (interval == 0) {
5053 m_autoSaveTimer.stop();
5055 m_autoSaveTimer.setInterval(interval * 1000);
5057 m_autoSaveTimer.start();
5062 Q_EMIT configChanged(
this);
5072bool KTextEditor::DocumentPrivate::readVariables(KTextEditor::ViewPrivate *view)
5074 const bool hasVariableline = [
this] {
5077 for (
int i = qMax(10, lines() - 10); i < lines(); ++i) {
5078 if (line(i).contains(s)) {
5083 for (
int i = 0; i < qMin(9, lines()); ++i) {
5084 if (line(i).contains(s)) {
5090 if (!hasVariableline) {
5095 m_config->configStart();
5096 for (
auto view : std::as_const(m_views)) {
5097 auto v =
static_cast<ViewPrivate *
>(view);
5108 for (
int i = 0; i < qMin(9, lines()); ++i) {
5109 readVariableLine(line(i), view);
5112 for (
int i = qMax(10, lines() - 10); i < lines(); i++) {
5113 readVariableLine(line(i), view);
5118 m_config->configEnd();
5119 for (
auto view : std::as_const(m_views)) {
5120 auto v =
static_cast<ViewPrivate *
>(view);
5132void KTextEditor::DocumentPrivate::readVariableLine(
const QString &t, KTextEditor::ViewPrivate *view)
5135 static const QRegularExpression kvLineWildcard(QStringLiteral(
"kate-wildcard\\((.*)\\):(.*)"));
5136 static const QRegularExpression kvLineMime(QStringLiteral(
"kate-mimetype\\((.*)\\):(.*)"));
5149 auto match = kvLine.match(t);
5150 if (
match.hasMatch()) {
5151 s =
match.captured(1);
5154 }
else if ((match = kvLineWildcard.match(t)).hasMatch()) {
5160 for (
const QString &pattern : wildcards) {
5167 found = wildcard.match(matchPath ? pathOfFile : nameOfFile).hasMatch();
5178 s =
match.captured(2);
5181 }
else if ((match = kvLineMime.match(t)).hasMatch()) {
5189 s =
match.captured(2);
5197 static const auto vvl = {
5221 int spaceIndent = -1;
5222 bool replaceTabsSet =
false;
5227 while ((match = kvVar.match(s, startPos)).hasMatch()) {
5228 startPos =
match.capturedEnd(0);
5229 var =
match.captured(1);
5230 val =
match.captured(2).trimmed();
5236 if (contains(vvl, var)) {
5238 setViewVariable(var, val, {&v, 1});
5242 if (var ==
QLatin1String(
"word-wrap") && checkBoolValue(val, &state)) {
5247 else if (var ==
QLatin1String(
"backspace-indents") && checkBoolValue(val, &state)) {
5248 m_config->setBackspaceIndents(state);
5249 }
else if (var ==
QLatin1String(
"indent-pasted-text") && checkBoolValue(val, &state)) {
5250 m_config->setIndentPastedText(state);
5251 }
else if (var ==
QLatin1String(
"replace-tabs") && checkBoolValue(val, &state)) {
5252 m_config->setReplaceTabsDyn(state);
5253 replaceTabsSet =
true;
5254 }
else if (var ==
QLatin1String(
"remove-trailing-space") && checkBoolValue(val, &state)) {
5255 qCWarning(LOG_KTE) <<
i18n(
5256 "Using deprecated modeline 'remove-trailing-space'. "
5257 "Please replace with 'remove-trailing-spaces modified;', see "
5258 "https://docs.kde.org/?application=katepart&branch=stable5&path=config-variables.html#variable-remove-trailing-spaces");
5259 m_config->setRemoveSpaces(state ? 1 : 0);
5260 }
else if (var ==
QLatin1String(
"replace-trailing-space-save") && checkBoolValue(val, &state)) {
5261 qCWarning(LOG_KTE) <<
i18n(
5262 "Using deprecated modeline 'replace-trailing-space-save'. "
5263 "Please replace with 'remove-trailing-spaces all;', see "
5264 "https://docs.kde.org/?application=katepart&branch=stable5&path=config-variables.html#variable-remove-trailing-spaces");
5265 m_config->setRemoveSpaces(state ? 2 : 0);
5266 }
else if (var ==
QLatin1String(
"overwrite-mode") && checkBoolValue(val, &state)) {
5267 m_config->setOvr(state);
5268 }
else if (var ==
QLatin1String(
"keep-extra-spaces") && checkBoolValue(val, &state)) {
5269 m_config->setKeepExtraSpaces(state);
5270 }
else if (var ==
QLatin1String(
"tab-indents") && checkBoolValue(val, &state)) {
5271 m_config->setTabIndents(state);
5272 }
else if (var ==
QLatin1String(
"show-tabs") && checkBoolValue(val, &state)) {
5273 m_config->setShowTabs(state);
5274 }
else if (var ==
QLatin1String(
"show-trailing-spaces") && checkBoolValue(val, &state)) {
5275 m_config->setShowSpaces(state ? KateDocumentConfig::Trailing : KateDocumentConfig::None);
5276 }
else if (var ==
QLatin1String(
"space-indent") && checkBoolValue(val, &state)) {
5278 spaceIndent = state;
5279 }
else if (var ==
QLatin1String(
"smart-home") && checkBoolValue(val, &state)) {
5280 m_config->setSmartHome(state);
5281 }
else if (var ==
QLatin1String(
"newline-at-eof") && checkBoolValue(val, &state)) {
5282 m_config->setNewLineAtEof(state);
5286 else if (var ==
QLatin1String(
"tab-width") && checkIntValue(val, &n)) {
5287 m_config->setTabWidth(n);
5288 }
else if (var ==
QLatin1String(
"indent-width") && checkIntValue(val, &n)) {
5289 m_config->setIndentationWidth(n);
5291 m_config->setIndentationMode(val);
5292 }
else if (var ==
QLatin1String(
"word-wrap-column") && checkIntValue(val, &n) && n > 0) {
5293 m_config->setWordWrapAt(n);
5299 if ((n = indexOf(l, val.
toLower())) != -1) {
5302 m_config->setEol(n);
5303 m_config->setAllowEolDetection(
false);
5306 if (checkBoolValue(val, &state)) {
5307 m_config->setBom(state);
5309 }
else if (var ==
QLatin1String(
"remove-trailing-spaces")) {
5312 m_config->setRemoveSpaces(1);
5314 m_config->setRemoveSpaces(2);
5316 m_config->setRemoveSpaces(0);
5319 setHighlightingMode(val);
5325 setDefaultDictionary(val);
5326 }
else if (var ==
QLatin1String(
"automatic-spell-checking") && checkBoolValue(val, &state)) {
5327 onTheFlySpellCheckingEnabled(state);
5331 else if (contains(vvl, var)) {
5332 setViewVariable(var, val, m_views);
5334 m_storedVariables[var] = val;
5346 if (!replaceTabsSet && spaceIndent >= 0) {
5347 m_config->setReplaceTabsDyn(spaceIndent > 0);
5351void KTextEditor::DocumentPrivate::setViewVariable(
const QString &var,
const QString &val, std::span<KTextEditor::View *> views)
5356 for (
auto view : views) {
5357 auto v =
static_cast<ViewPrivate *
>(view);
5360 if (checkBoolValue(val, &state)) {
5363 if (v->config()->
setValue(var, help)) {
5364 }
else if (v->rendererConfig()->
setValue(var, help)) {
5366 }
else if (var ==
QLatin1String(
"dynamic-word-wrap") && checkBoolValue(val, &state)) {
5367 v->config()->setDynWordWrap(state);
5368 }
else if (var ==
QLatin1String(
"block-selection") && checkBoolValue(val, &state)) {
5369 v->setBlockSelection(state);
5372 }
else if (var ==
QLatin1String(
"icon-bar-color") && checkColorValue(val, c)) {
5373 v->rendererConfig()->setIconBarColor(c);
5376 else if (var ==
QLatin1String(
"background-color") && checkColorValue(val, c)) {
5377 v->rendererConfig()->setBackgroundColor(c);
5378 }
else if (var ==
QLatin1String(
"selection-color") && checkColorValue(val, c)) {
5379 v->rendererConfig()->setSelectionColor(c);
5380 }
else if (var ==
QLatin1String(
"current-line-color") && checkColorValue(val, c)) {
5381 v->rendererConfig()->setHighlightedLineColor(c);
5382 }
else if (var ==
QLatin1String(
"bracket-highlight-color") && checkColorValue(val, c)) {
5383 v->rendererConfig()->setHighlightedBracketColor(c);
5384 }
else if (var ==
QLatin1String(
"word-wrap-marker-color") && checkColorValue(val, c)) {
5385 v->rendererConfig()->setWordWrapMarkerColor(c);
5391 _f.setFixedPitch(
QFont(val).fixedPitch());
5396 v->rendererConfig()->setFont(_f);
5398 v->rendererConfig()->setSchema(val);
5403bool KTextEditor::DocumentPrivate::checkBoolValue(
QString val,
bool *result)
5407 if (contains(trueValues, val)) {
5413 if (contains(falseValues, val)) {
5420bool KTextEditor::DocumentPrivate::checkIntValue(
const QString &val,
int *result)
5423 *result = val.
toInt(&ret);
5427bool KTextEditor::DocumentPrivate::checkColorValue(
const QString &val,
QColor &c)
5436 auto it = m_storedVariables.find(name);
5437 if (it == m_storedVariables.end()) {
5445 QString s = QStringLiteral(
"kate: ");
5449 readVariableLine(s);
5454void KTextEditor::DocumentPrivate::slotModOnHdDirty(
const QString &path)
5456 if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskModified)) {
5458 m_modOnHdReason = OnDiskModified;
5460 if (!m_modOnHdTimer.isActive()) {
5461 m_modOnHdTimer.start();
5466void KTextEditor::DocumentPrivate::slotModOnHdCreated(
const QString &path)
5468 if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskCreated)) {
5470 m_modOnHdReason = OnDiskCreated;
5472 if (!m_modOnHdTimer.isActive()) {
5473 m_modOnHdTimer.start();
5478void KTextEditor::DocumentPrivate::slotModOnHdDeleted(
const QString &path)
5480 if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskDeleted)) {
5482 m_modOnHdReason = OnDiskDeleted;
5484 if (!m_modOnHdTimer.isActive()) {
5485 m_modOnHdTimer.start();
5490void KTextEditor::DocumentPrivate::slotDelayedHandleModOnHd()
5494 if (!oldDigest.
isEmpty() && !url().isEmpty() && url().isLocalFile()) {
5496 if (m_modOnHdReason != OnDiskDeleted && m_modOnHdReason != OnDiskCreated && createDigest() && oldDigest == checksum()) {
5498 m_modOnHdReason = OnDiskUnmodified;
5499 m_prevModOnHdReason = OnDiskUnmodified;
5506 if (m_modOnHd && !isModified() &&
QFile::exists(url().toLocalFile())
5507 && config()->value(KateDocumentConfig::AutoReloadIfStateIsInVersionControl).toBool()) {
5514 git.
start(fullGitPath, args);
5521 m_modOnHdReason = OnDiskUnmodified;
5522 m_prevModOnHdReason = OnDiskUnmodified;
5532 Q_EMIT modifiedOnDisk(
this, m_modOnHd, m_modOnHdReason);
5537 return m_buffer->digest();
5540bool KTextEditor::DocumentPrivate::createDigest()
5544 if (url().isLocalFile()) {
5545 QFile f(url().toLocalFile());
5549 const QString header = QStringLiteral(
"blob %1").
arg(f.size());
5552 digest = crypto.result();
5557 m_buffer->setDigest(digest);
5561QString KTextEditor::DocumentPrivate::reasonedMOHString()
const
5566 switch (m_modOnHdReason) {
5567 case OnDiskModified:
5568 return i18n(
"The file '%1' was modified on disk.", str);
5571 return i18n(
"The file '%1' was created on disk.", str);
5574 return i18n(
"The file '%1' was deleted or moved on disk.", str);
5583void KTextEditor::DocumentPrivate::removeTrailingSpacesAndAddNewLineAtEof()
5586 const int remove = config()->removeSpaces();
5587 const bool newLineAtEof = config()->newLineAtEof();
5588 if (remove == 0 && !newLineAtEof) {
5593 const bool wordWrapEnabled = config()->wordWrap();
5594 if (wordWrapEnabled) {
5601 const int lines = this->lines();
5603 for (
int line = 0; line < lines; ++line) {
5609 if (remove == 2 || textline.markedAsModified() || textline.markedAsSavedOnDisk()) {
5610 const int p = textline.
lastChar() + 1;
5611 const int l = textline.
length() - p;
5613 editRemoveText(line, p, l);
5622 Q_ASSERT(lines > 0);
5623 const auto length = lineLength(lines - 1);
5627 const auto oldEndOfDocumentCursor = documentEnd();
5628 std::vector<KTextEditor::ViewPrivate *> viewsToRestoreCursors;
5629 for (
auto view : std::as_const(m_views)) {
5630 auto v =
static_cast<ViewPrivate *
>(view);
5631 if (v->cursorPosition() == oldEndOfDocumentCursor) {
5632 viewsToRestoreCursors.push_back(v);
5637 editWrapLine(lines - 1, length);
5640 for (
auto v : viewsToRestoreCursors) {
5641 v->setCursorPosition(oldEndOfDocumentCursor);
5649 if (wordWrapEnabled) {
5657 const int lines = this->lines();
5658 for (
int line = 0; line < lines; ++line) {
5660 const int p = textline.
lastChar() + 1;
5661 const int l = textline.
length() - p;
5663 editRemoveText(line, p, l);
5671 if (user || !m_fileTypeSetByUser) {
5677 if (fileType.name.
isEmpty()) {
5682 m_fileTypeSetByUser = user;
5684 m_fileType = newType;
5686 m_config->configStart();
5691 if ((user || !m_hlSetByUser) && !fileType.hl.
isEmpty()) {
5692 int hl(KateHlManager::self()->nameFind(fileType.hl));
5695 m_buffer->setHighlight(hl);
5702 if (!m_indenterSetByUser && !fileType.indenter.
isEmpty()) {
5703 config()->setIndentationMode(fileType.indenter);
5707 for (
auto view : std::as_const(m_views)) {
5708 auto v =
static_cast<ViewPrivate *
>(view);
5713 bool bom_settings =
false;
5714 if (m_bomSetByUser) {
5715 bom_settings = m_config->bom();
5717 readVariableLine(fileType.varLine);
5718 if (m_bomSetByUser) {
5719 m_config->setBom(bom_settings);
5721 m_config->configEnd();
5722 for (
auto view : std::as_const(m_views)) {
5723 auto v =
static_cast<ViewPrivate *
>(view);
5730 Q_EMIT modeChanged(
this);
5734void KTextEditor::DocumentPrivate::slotQueryClose_save(
bool *handled,
bool *abortClosing)
5737 *abortClosing =
true;
5738 if (url().isEmpty()) {
5739 const QUrl res = getSaveFileUrl(
i18n(
"Save File"));
5741 *abortClosing =
true;
5745 *abortClosing =
false;
5748 *abortClosing =
false;
5758 return m_config->configKeys();
5764 return m_config->
value(key);
5770 m_config->setValue(key, value);
5784 bool changed = removeText(range, block);
5785 changed |= insertText(range.
start(), s, block);
5790KateHighlighting *KTextEditor::DocumentPrivate::highlight()
const
5792 return m_buffer->highlight();
5797 m_buffer->ensureHighlighted(i);
5798 return m_buffer->plainLine(i);