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)
1783 QUrl startUrl = url();
1793 const auto views = mainWindow->views();
1794 for (
auto view : views) {
1810 return updateFileType(name);
1829 for (KateFileType *type : modeList) {
1838 int mode = KateHlManager::self()->nameFind(name);
1842 m_buffer->setHighlight(mode);
1848 return highlight()->name();
1853 const auto modeList = KateHlManager::self()->modeList();
1856 for (
const auto &hl : modeList) {
1864 return KateHlManager::self()->modeList().at(index).section();
1872void KTextEditor::DocumentPrivate::bufferHlChanged()
1878 m_indenter->checkRequiredStyle();
1880 Q_EMIT highlightingModeChanged(
this);
1885 m_hlSetByUser =
true;
1890 m_bomSetByUser =
true;
1897 if (!flags.
contains(QStringLiteral(
"SkipEncoding"))) {
1900 if (!tmpenc.
isEmpty() && (tmpenc != encoding())) {
1901 setEncoding(tmpenc);
1905 if (!flags.
contains(QStringLiteral(
"SkipUrl"))) {
1920 if (!flags.
contains(QStringLiteral(
"SkipMode")) && kconfig.
hasKey(
"Mode Set By User")) {
1921 updateFileType(kconfig.
readEntry(
"Mode"),
true );
1924 if (!flags.
contains(QStringLiteral(
"SkipHighlighting"))) {
1926 if (kconfig.
hasKey(
"Highlighting Set By User")) {
1927 const int mode = KateHlManager::self()->nameFind(kconfig.
readEntry(
"Highlighting"));
1928 m_hlSetByUser =
true;
1931 m_buffer->setHighlight(mode);
1938 if (!userSetIndentMode.
isEmpty()) {
1939 config()->setIndentationMode(userSetIndentMode);
1944 for (
int i = 0; i < marks.
count(); i++) {
1954 if (this->url().isLocalFile()) {
1955 const QString path = this->url().toLocalFile();
1961 if (!flags.
contains(QStringLiteral(
"SkipUrl"))) {
1963 kconfig.
writeEntry(
"URL", this->url().toString());
1967 if (encoding() !=
QLatin1String(
"UTF-8") && !m_buffer->brokenEncoding() && !flags.
contains(QStringLiteral(
"SkipEncoding"))) {
1973 if (m_fileTypeSetByUser && !flags.
contains(QStringLiteral(
"SkipMode"))) {
1974 kconfig.
writeEntry(
"Mode Set By User",
true);
1978 if (m_hlSetByUser && !flags.
contains(QStringLiteral(
"SkipHighlighting"))) {
1980 kconfig.
writeEntry(
"Highlighting", highlight()->name());
1983 kconfig.
writeEntry(
"Highlighting Set By User", m_hlSetByUser);
1987 if (m_indenterSetByUser) {
1988 kconfig.
writeEntry(
"Indentation Mode", config()->indentationMode());
1993 for (
const auto &mark : std::as_const(m_marks)) {
2016void KTextEditor::DocumentPrivate::setMark(
int line, uint markType)
2019 addMark(line, markType);
2022void KTextEditor::DocumentPrivate::clearMark(
int line)
2024 if (line < 0 || line > lastLine()) {
2028 if (
auto mark = m_marks.take(line)) {
2029 Q_EMIT markChanged(
this, *mark, MarkRemoved);
2030 Q_EMIT marksChanged(
this);
2037void KTextEditor::DocumentPrivate::addMark(
int line, uint markType)
2041 if (line < 0 || line > lastLine()) {
2045 if (markType == 0) {
2049 if ((mark = m_marks.value(line))) {
2051 markType &= ~mark->type;
2053 if (markType == 0) {
2058 mark->
type |= markType;
2062 mark->
type = markType;
2063 m_marks.insert(line, mark);
2069 temp.
type = markType;
2070 Q_EMIT markChanged(
this, temp, MarkAdded);
2072 Q_EMIT marksChanged(
this);
2077void KTextEditor::DocumentPrivate::removeMark(
int line, uint markType)
2079 if (line < 0 || line > lastLine()) {
2083 auto it = m_marks.find(line);
2084 if (it == m_marks.end()) {
2090 markType &= mark->
type;
2092 if (markType == 0) {
2097 mark->
type &= ~markType;
2102 temp.
type = markType;
2103 Q_EMIT markChanged(
this, temp, MarkRemoved);
2105 if (mark->
type == 0) {
2110 Q_EMIT marksChanged(
this);
2120void KTextEditor::DocumentPrivate::requestMarkTooltip(
int line,
QPoint position)
2127 bool handled =
false;
2128 Q_EMIT markToolTipRequested(
this, *mark, position, handled);
2133 bool handled =
false;
2138 Q_EMIT markClicked(
this, *mark, handled);
2146 bool handled =
false;
2149 Q_EMIT markContextMenuRequested(
this,
KTextEditor::Mark{.line = line, .type = 0}, position, handled);
2151 Q_EMIT markContextMenuRequested(
this, *mark, position, handled);
2166 for (
const auto &m : marksCopy) {
2167 Q_EMIT markChanged(
this, *m, MarkRemoved);
2172 Q_EMIT marksChanged(
this);
2178 m_markDescriptions.insert(type, description);
2184 if ((uint)type >= (uint)markType01 && (uint)type <= reserved) {
2185 return KateRendererConfig::global()->lineMarkerColor(type);
2193 return m_markDescriptions.value(type,
QString());
2196void KTextEditor::DocumentPrivate::setEditableMarks(uint markMask)
2198 m_editableMarks = markMask;
2203 return m_editableMarks;
2209 m_markIcons.insert(markType, icon);
2214 return m_markIcons.value(markType,
QIcon());
2218bool KTextEditor::DocumentPrivate::print()
2220 return KatePrinter::print(
this);
2223void KTextEditor::DocumentPrivate::printPreview()
2225 KatePrinter::printPreview(
this);
2232 if (!m_modOnHd && url().isLocalFile()) {
2240 for (
int i = 0; (i < lines()) && (buf.
size() <= 4096); ++i) {
2241 buf.
append(line(i).toUtf8());
2246 if (!url().path().isEmpty()) {
2256void KTextEditor::DocumentPrivate::showAndSetOpeningErrorAccess()
2259 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.",
2262 message->setWordWrap(
true);
2264 i18nc(
"translators: you can also translate 'Try Again' with 'Reload'",
"Try Again"),
2269 closeAction->
setToolTip(
i18nc(
"Close the message being displayed",
"Close message"));
2272 message->addAction(tryAgainAction);
2273 message->addAction(closeAction);
2276 postMessage(message);
2279 m_openingError =
true;
2283void KTextEditor::DocumentPrivate::openWithLineLengthLimitOverride()
2286 const int longestLine = m_buffer->longestLineLoaded();
2287 int newLimit = pow(2, ceil(log2(longestLine)));
2288 if (newLimit <= longestLine) {
2293 config()->setLineLengthLimit(newLimit);
2298 if (!m_openingError) {
2300 m_readWriteStateBeforeLoading =
true;
2306 return config()->lineLengthLimit();
2313 Q_EMIT aboutToInvalidateMovingInterfaceContent(
this);
2316 m_openingError =
false;
2322 QString currentEncoding = encoding();
2327 QString mimeType = arguments().mimeType();
2329 if (pos != -1 && !(m_reloading && m_userSetEncodingForNextReload)) {
2330 setEncoding(mimeType.
mid(pos + 1));
2341 if (m_reloading && m_userSetEncodingForNextReload && (currentEncoding != encoding())) {
2342 setEncoding(currentEncoding);
2345 bool success = m_buffer->openFile(localFilePath(), (m_reloading && m_userSetEncodingForNextReload));
2358 for (
auto view : std::as_const(m_views)) {
2361 static_cast<ViewPrivate *
>(view)->updateView(
true);
2365 Q_EMIT textChanged(
this);
2366 Q_EMIT loaded(
this);
2373 m_modOnHdReason = OnDiskUnmodified;
2374 m_prevModOnHdReason = OnDiskUnmodified;
2375 Q_EMIT modifiedOnDisk(
this, m_modOnHd, m_modOnHdReason);
2380 if (!isEmpty() && config()->autoDetectIndent() && !config()->isSet(KateDocumentConfig::IndentationWidth)
2381 && !config()->isSet(KateDocumentConfig::ReplaceTabsWithSpaces)) {
2383 auto result = detecter.detect(config()->indentationWidth(), config()->replaceTabsDyn());
2384 config()->setIndentationWidth(result.indentWidth);
2385 config()->setReplaceTabsDyn(result.indentUsingSpaces);
2392 showAndSetOpeningErrorAccess();
2396 if (m_buffer->brokenEncoding()) {
2398 setReadWrite(
false);
2399 m_readWriteStateBeforeLoading =
false;
2401 i18n(
"The file %1 was opened with %2 encoding but contained invalid characters.<br />"
2402 "It is set to read-only mode, as saving might destroy its content.<br />"
2403 "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.",
2405 m_buffer->textCodec()),
2407 message->setWordWrap(
true);
2408 postMessage(message);
2411 m_openingError =
true;
2415 if (m_buffer->tooLongLinesWrapped()) {
2417 setReadWrite(
false);
2418 m_readWriteStateBeforeLoading =
false;
2420 new KTextEditor::Message(
i18n(
"The file %1 was opened and contained lines longer than the configured Line Length Limit (%2 characters).<br />"
2421 "The longest of those lines was %3 characters long<br/>"
2422 "Those lines were wrapped and the document is set to read-only mode, as saving will modify its content.",
2424 config()->lineLengthLimit(),
2425 m_buffer->longestLineLoaded()),
2427 QAction *increaseAndReload =
new QAction(
i18n(
"Temporarily raise limit and reload file"), message);
2428 connect(increaseAndReload, &
QAction::triggered,
this, &KTextEditor::DocumentPrivate::openWithLineLengthLimitOverride);
2429 message->addAction(increaseAndReload,
true);
2430 message->addAction(
new QAction(
i18n(
"Close"), message),
true);
2431 message->setWordWrap(
true);
2432 postMessage(message);
2435 m_openingError =
true;
2447 delete m_modOnHdHandler;
2450 if (!url().isEmpty()) {
2451 if (m_fileChangedDialogsActivated && m_modOnHd) {
2454 if (!isModified()) {
2457 str +
i18n(
"Do you really want to save this unmodified file? You could overwrite changed data in the file on disk."),
2458 i18n(
"Trying to Save Unmodified File"),
2468 "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."),
2469 i18n(
"Possible Data Loss"),
2481 if (!m_buffer->canEncode()
2483 i18n(
"The selected encoding cannot encode every Unicode character in this document. Do you really want to save "
2484 "it? There could be some data lost."),
2485 i18n(
"Possible Data Loss"),
2493 if (!createBackupFile()) {
2498 QString oldPath = m_dirWatchFile;
2501 if (oldPath != localFilePath()) {
2504 if (url().isLocalFile()) {
2511 const bool variablesWereRead = readVariables();
2517 if (!variablesWereRead) {
2518 for (
auto *view : std::as_const(m_views)) {
2519 auto v =
static_cast<ViewPrivate *
>(view);
2520 if (v->isVisible()) {
2521 const auto range = v->visibleRange();
2523 bool repaint =
false;
2525 if (isLineModified(i)) {
2532 v->updateView(
true);
2539 deactivateDirWatch();
2544 removeTrailingSpacesAndAddNewLineAtEof();
2549 if (!m_buffer->saveFile(localFilePath())) {
2551 activateDirWatch(oldPath);
2553 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 "
2554 "that enough disk space is available.\nThe original file may be lost or damaged. "
2555 "Don't quit the application until the file is successfully written.",
2571 m_modOnHdReason = OnDiskUnmodified;
2572 m_prevModOnHdReason = OnDiskUnmodified;
2573 Q_EMIT modifiedOnDisk(
this, m_modOnHd, m_modOnHdReason);
2578 m_undoManager->undoSafePoint();
2579 m_undoManager->updateLineModifications();
2587bool KTextEditor::DocumentPrivate::createBackupFile()
2590 const bool backupLocalFiles = config()->backupOnSaveLocal();
2591 const bool backupRemoteFiles = config()->backupOnSaveRemote();
2595 if (!backupLocalFiles && !backupRemoteFiles) {
2602 bool needBackup = backupLocalFiles && backupRemoteFiles;
2604 bool slowOrRemoteFile = !u.isLocalFile();
2605 if (!slowOrRemoteFile) {
2609 slowOrRemoteFile = (mountPoint && mountPoint->probablySlow());
2611 needBackup = (!slowOrRemoteFile && backupLocalFiles) || (slowOrRemoteFile && backupRemoteFiles);
2622 if (backupPrefix.isEmpty() && backupSuffix.isEmpty()) {
2629 u.setPath(backupPrefix + u.fileName() + backupSuffix);
2632 const QString fileName = u.fileName();
2634 u.setPath(u.path() + backupPrefix + fileName + backupSuffix);
2637 qCDebug(LOG_KTE) <<
"backup src file name: " << url();
2638 qCDebug(LOG_KTE) <<
"backup dst file name: " << u;
2641 bool backupSuccess =
false;
2644 if (u.isLocalFile()) {
2647 QFile backupFile(u.toLocalFile());
2648 if (backupFile.exists()) {
2649 backupFile.remove();
2652 backupSuccess =
QFile::copy(url().toLocalFile(), u.toLocalFile());
2654 backupSuccess =
true;
2660 if (statJob->
exec()) {
2665 backupSuccess = job->
exec();
2667 backupSuccess =
true;
2674 i18n(
"For file %1 no backup copy could be created before saving."
2675 " If an error occurs while saving, you might lose the data of this file."
2676 " A reason could be that the media you write to is full or the directory of the file is read-only for you.",
2678 i18n(
"Failed to create backup copy."),
2681 QStringLiteral(
"Backup Failed Warning"))
2689void KTextEditor::DocumentPrivate::readDirConfig(KTextEditor::ViewPrivate *v)
2699 while (!seenDirectories.
contains(
dir.absolutePath())) {
2701 seenDirectories.
insert(
dir.absolutePath());
2709 QString line = stream.readLine();
2710 while ((linesRead < 32) && !line.
isNull()) {
2711 readVariableLine(line, v);
2713 line = stream.readLine();
2727#if EDITORCONFIG_FOUND
2729 if (!v && config()->value(KateDocumentConfig::UseEditorConfig).toBool()) {
2733 EditorConfig editorConfig(
this);
2734 editorConfig.parse();
2739void KTextEditor::DocumentPrivate::activateDirWatch(
const QString &useFileName)
2741 QString fileToUse = useFileName;
2743 fileToUse = localFilePath();
2757 if (fileToUse == m_dirWatchFile) {
2762 deactivateDirWatch();
2765 if (url().isLocalFile() && !fileToUse.
isEmpty()) {
2767 m_dirWatchFile = fileToUse;
2771void KTextEditor::DocumentPrivate::deactivateDirWatch()
2773 if (!m_dirWatchFile.isEmpty()) {
2777 m_dirWatchFile.clear();
2780bool KTextEditor::DocumentPrivate::openUrl(
const QUrl &url)
2784 m_fileTypeSetByUser =
false;
2791bool KTextEditor::DocumentPrivate::closeUrl()
2796 if (!m_reloading && !url().isEmpty()) {
2797 if (m_fileChangedDialogsActivated && m_modOnHd) {
2799 delete m_modOnHdHandler;
2801 QWidget *parentWidget(dialogParent());
2804 +
i18n(
"Do you really want to continue to close this file? Data loss may occur."),
2805 i18n(
"Possible Data Loss"),
2808 QStringLiteral(
"kate_close_modonhd_%1").arg(m_modOnHdReason))
2811 m_reloading =
false;
2822 m_reloading =
false;
2828 Q_EMIT aboutToClose(
this);
2832 if (!m_messageHash.isEmpty()) {
2833 const auto keys = m_messageHash.keys();
2840 Q_EMIT aboutToInvalidateMovingInterfaceContent(
this);
2843 deactivateDirWatch();
2851 m_modOnHdReason = OnDiskUnmodified;
2852 m_prevModOnHdReason = OnDiskUnmodified;
2853 Q_EMIT modifiedOnDisk(
this, m_modOnHd, m_modOnHdReason);
2863 m_undoManager->clearUndo();
2864 m_undoManager->clearRedo();
2870 m_buffer->setHighlight(0);
2873 for (
auto view : std::as_const(m_views)) {
2874 static_cast<ViewPrivate *
>(view)->clearSelection();
2875 static_cast<ViewPrivate *
>(view)->
clear();
2880 m_swapfile->fileClosed();
2889 return m_swapfile && m_swapfile->shouldRecover();
2894 if (isDataRecoveryAvailable()) {
2895 m_swapfile->recover();
2901 if (isDataRecoveryAvailable()) {
2902 m_swapfile->discard();
2906void KTextEditor::DocumentPrivate::setReadWrite(
bool rw)
2908 if (isReadWrite() == rw) {
2914 for (
auto v : std::as_const(m_views)) {
2915 auto view =
static_cast<ViewPrivate *
>(v);
2916 view->slotUpdateUndo();
2917 view->slotReadWriteChanged();
2920 Q_EMIT readWriteChanged(
this);
2925 if (isModified() != m) {
2928 for (
auto view : std::as_const(m_views)) {
2929 static_cast<ViewPrivate *
>(view)->slotUpdateUndo();
2932 Q_EMIT modifiedChanged(
this);
2935 m_undoManager->setModified(m);
2941void KTextEditor::DocumentPrivate::makeAttribs(
bool needInvalidate)
2943 for (
auto view : std::as_const(m_views)) {
2944 static_cast<ViewPrivate *
>(view)->renderer()->updateAttributes();
2947 if (needInvalidate) {
2948 m_buffer->invalidateHighlighting();
2951 for (
auto v : std::as_const(m_views)) {
2952 auto view =
static_cast<ViewPrivate *
>(v);
2954 view->updateView(
true);
2959void KTextEditor::DocumentPrivate::internalHlChanged()
2966 Q_ASSERT(!m_views.contains(view));
2967 m_views.append(view);
2968 auto *v =
static_cast<KTextEditor::ViewPrivate *
>(view);
2971 if (!m_fileType.isEmpty()) {
2980 setActiveView(view);
2985 Q_ASSERT(m_views.contains(view));
2986 m_views.removeAll(view);
2988 if (activeView() == view) {
2989 setActiveView(
nullptr);
2995 if (m_activeView == view) {
2999 m_activeView =
static_cast<KTextEditor::ViewPrivate *
>(view);
3002bool KTextEditor::DocumentPrivate::ownedView(KTextEditor::ViewPrivate *view)
3005 return (m_views.contains(view));
3008int KTextEditor::DocumentPrivate::toVirtualColumn(
int line,
int column)
const
3016 return toVirtualColumn(cursor.
line(), cursor.
column());
3019int KTextEditor::DocumentPrivate::fromVirtualColumn(
int line,
int column)
const
3025int KTextEditor::DocumentPrivate::fromVirtualColumn(
const KTextEditor::Cursor cursor)
const
3027 return fromVirtualColumn(cursor.
line(), cursor.
column());
3036 bool skipAutobrace = closingBracket ==
QLatin1Char(
'\'');
3037 if (highlight() && skipAutobrace) {
3039 skipAutobrace = highlight()->spellCheckingRequiredForLocation(
this, pos - Cursor{0, 1});
3042 if (!skipAutobrace && (closingBracket ==
QLatin1Char(
'\''))) {
3048 skipAutobrace = (count % 2 == 0) ?
true : false;
3050 if (!skipAutobrace && (closingBracket ==
QLatin1Char(
'\"'))) {
3055 skipAutobrace = (count % 2 == 0) ?
true : false;
3057 return skipAutobrace;
3068 QChar closingBracket;
3069 if (view->config()->autoBrackets()) {
3071 const QChar typedChar = chars.
at(0);
3072 const QChar openBracket = matchingStartBracket(typedChar);
3073 if (!openBracket.
isNull()) {
3075 if ((characterAt(curPos) == typedChar) && findMatchingBracket(curPos, 123 ).isValid()) {
3077 view->cursorRight();
3083 if (chars.
size() == 1) {
3085 closingBracket = matchingEndBracket(typedChar);
3088 if (m_currentAutobraceClosingChar == typedChar && m_currentAutobraceRange) {
3090 m_currentAutobraceRange.reset(
nullptr);
3091 view->cursorRight();
3098 if (view->selection() && closingBracket.
isNull() && view->config()->encloseSelectionInChars()) {
3099 const QChar typedChar = chars.
at(0);
3100 if (view->config()->charsToEncloseSelection().
contains(typedChar)) {
3109 if (view->selection() && !closingBracket.
isNull()) {
3110 std::unique_ptr<KTextEditor::MovingRange> selectionRange(newMovingRange(view->selectionRange()));
3111 const int startLine = qMax(0, selectionRange->start().line());
3112 const int endLine = qMin(selectionRange->end().line(), lastLine());
3113 const bool blockMode = view->blockSelection() && (startLine != endLine);
3115 if (selectionRange->start().column() > selectionRange->end().column()) {
3121 const int startColumn = qMin(selectionRange->start().column(), selectionRange->end().column());
3122 const int endColumn = qMax(selectionRange->start().column(), selectionRange->end().column());
3124 for (
int line = startLine; line <= endLine; ++line) {
3126 insertText(r.
end(),
QString(closingBracket));
3127 view->slotTextInserted(view, r.
end(),
QString(closingBracket));
3128 insertText(r.
start(), chars);
3129 view->slotTextInserted(view, r.
start(), chars);
3133 for (
const auto &cursor : view->secondaryCursors()) {
3134 if (!cursor.range) {
3137 const auto &currSelectionRange = cursor.range;
3138 auto expandBehaviour = currSelectionRange->insertBehaviors();
3140 insertText(currSelectionRange->end(),
QString(closingBracket));
3141 insertText(currSelectionRange->start(), chars);
3142 currSelectionRange->setInsertBehaviors(expandBehaviour);
3143 cursor.pos->setPosition(currSelectionRange->end());
3144 auto mutableCursor =
const_cast<KTextEditor::ViewPrivate::SecondaryCursor *
>(&cursor);
3145 mutableCursor->anchor = currSelectionRange->start().toCursor();
3149 insertText(selectionRange->end(),
QString(closingBracket));
3150 view->slotTextInserted(view, selectionRange->end(),
QString(closingBracket));
3151 insertText(selectionRange->start(), chars);
3152 view->slotTextInserted(view, selectionRange->start(), chars);
3156 view->setSelection(selectionRange->toRange());
3157 view->setCursorPosition(selectionRange->end());
3164 if (!view->config()->persistentSelection() && view->selection()) {
3165 view->removeSelectedText();
3170 const bool multiLineBlockMode = view->blockSelection() && view->selection();
3171 if (view->currentInputMode()->overwrite()) {
3174 const int startLine = multiLineBlockMode ? qMax(0, selectionRange.
start().
line()) : view->cursorPosition().
line();
3175 const int endLine = multiLineBlockMode ? qMin(selectionRange.
end().
line(), lastLine()) : startLine;
3176 const int virtualColumn = toVirtualColumn(multiLineBlockMode ? selectionRange.
end() : view->cursorPosition());
3178 for (
int line = endLine; line >= startLine; --line) {
3180 const int column = fromVirtualColumn(line, virtualColumn);
3184 if (oldCur.
column() < lineLength(line)) {
3186 view->currentInputMode()->overwrittenChar(removed);
3193 chars = eventuallyReplaceTabs(view->cursorPosition(), chars);
3195 if (multiLineBlockMode) {
3197 const int startLine = qMax(0, selectionRange.
start().
line());
3198 const int endLine = qMin(selectionRange.
end().
line(), lastLine());
3199 const int column = toVirtualColumn(selectionRange.
end());
3200 for (
int line = endLine; line >= startLine; --line) {
3201 editInsertText(line, fromVirtualColumn(line, column), chars);
3203 int newSelectionColumn = toVirtualColumn(view->cursorPosition());
3206 view->setSelection(selectionRange);
3211 view->completionWidget()->setIgnoreBufferSignals(
true);
3212 const auto &sc = view->secondaryCursors();
3214 const bool hasClosingBracket = !closingBracket.
isNull();
3215 const QString closingChar = closingBracket;
3217 std::vector<std::pair<Kate::TextCursor *, KTextEditor::Cursor>> freezedCursors;
3218 for (
auto it = sc.begin(); it != sc.end(); ++it) {
3219 auto pos = it->cursor();
3220 if (it != sc.begin() && pos == std::prev(it)->cursor()) {
3221 freezedCursors.push_back({std::prev(it)->pos.get(), std::prev(it)->cursor()});
3224 lastInsertionCursor = pos;
3225 insertText(pos, chars);
3228 if (it->cursor() == view->cursorPosition()) {
3229 freezedCursors.push_back({it->pos.get(), it->cursor()});
3232 const auto nextChar = view->document()->
text({pos, pos +
Cursor{0, 1}}).trimmed();
3233 if (hasClosingBracket && !skipAutoBrace(closingBracket, pos) && (nextChar.isEmpty() || !nextChar.at(0).isLetterOrNumber())) {
3234 insertText(it->cursor(), closingChar);
3235 it->pos->setPosition(pos);
3239 view->completionWidget()->setIgnoreBufferSignals(
false);
3241 insertText(view->cursorPosition(), chars);
3243 for (
auto &freezed : freezedCursors) {
3244 freezed.first->setPosition(freezed.second);
3252 if (!closingBracket.
isNull() && !skipAutoBrace(closingBracket, view->cursorPosition())) {
3254 const auto cursorPos = view->cursorPosition();
3255 const auto nextChar = view->document()->
text({cursorPos, cursorPos +
Cursor{0, 1}}).trimmed();
3256 if (nextChar.isEmpty() || !nextChar.at(0).isLetterOrNumber()) {
3257 insertText(view->cursorPosition(),
QString(closingBracket));
3258 const auto insertedAt(view->cursorPosition());
3259 view->setCursorPosition(cursorPos);
3264 chars.
append(closingBracket);
3266 m_currentAutobraceClosingChar = closingBracket;
3273 const auto &secondaryCursors = view->secondaryCursors();
3274 for (
const auto &c : secondaryCursors) {
3275 m_indenter->userTypedChar(view, c.cursor(), chars.
isEmpty() ?
QChar() : chars.
at(chars.
length() - 1));
3283 view->slotTextInserted(view, oldCur, chars);
3288 if (m_currentAutobraceRange && !m_currentAutobraceRange->toRange().contains(newPos)) {
3289 m_currentAutobraceRange.reset();
3293void KTextEditor::DocumentPrivate::newLine(KTextEditor::ViewPrivate *v, KTextEditor::DocumentPrivate::NewLineIndent indent, NewLinePos newLinePos)
3297 if (!v->config()->persistentSelection() && v->selection()) {
3298 v->removeSelectedText();
3299 v->clearSelection();
3303 if (c.line() > lastLine()) {
3304 c.setLine(lastLine());
3313 int len = lineLength(ln);
3315 if (c.column() > len) {
3320 editWrapLine(c.line(), c.column());
3323 m_buffer->updateHighlighting();
3330 bool moveCursorToTop =
false;
3331 if (newLinePos == Above) {
3332 if (pos.
line() <= 0) {
3335 moveCursorToTop =
true;
3340 }
else if (newLinePos == Below) {
3341 int lastCol = lineLength(pos.
line());
3344 return std::pair{pos, moveCursorToTop};
3348 const auto &secondaryCursors = v->secondaryCursors();
3349 if (!secondaryCursors.empty()) {
3352 for (
const auto &c : secondaryCursors) {
3353 const auto [newPos, moveCursorToTop] = adjustCusorPos(c.cursor());
3354 c.pos->setPosition(newPos);
3355 insertNewLine(c.cursor());
3356 if (moveCursorToTop) {
3357 c.pos->setPosition({0, 0});
3360 if (indent == KTextEditor::DocumentPrivate::Indent) {
3364 v->setCursorPosition(c.cursor());
3365 m_indenter->userTypedChar(v, c.cursor(),
QLatin1Char(
'\n'));
3367 c.pos->setPosition(v->cursorPosition());
3371 v->setCursorPosition(savedPrimary.toCursor());
3374 const auto [newPos, moveCursorToTop] = adjustCusorPos(v->cursorPosition());
3375 v->setCursorPosition(newPos);
3376 insertNewLine(v->cursorPosition());
3377 if (moveCursorToTop) {
3378 v->setCursorPosition({0, 0});
3381 if (indent == KTextEditor::DocumentPrivate::Indent) {
3382 m_indenter->userTypedChar(v, v->cursorPosition(),
QLatin1Char(
'\n'));
3391 if (textLine.
length() < 2) {
3395 uint col = cursor.
column();
3401 if ((textLine.
length() - col) < 2) {
3405 uint line = cursor.
line();
3416 editRemoveText(line, col, 2);
3417 editInsertText(line, col, s);
3424 Q_ASSERT(!firstWord.
overlaps(secondWord));
3432 const QString tempString = text(secondWord);
3435 replaceText(secondWord, text(firstWord));
3436 replaceText(firstWord, tempString);
3442 int col = qMax(c.
column(), 0);
3443 int line = qMax(c.
line(), 0);
3444 if ((col == 0) && (line == 0)) {
3447 if (line >= lines()) {
3454 bool useNextBlock =
false;
3455 if (config()->backspaceIndents()) {
3462 if (pos < 0 || pos >= (
int)colX) {
3464 if ((
int)col > textLine.
length()) {
3470 useNextBlock =
true;
3473 if (!config()->backspaceIndents() || useNextBlock) {
3476 if (!view->config()->backspaceRemoveComposed()) {
3477 beginCursor.setColumn(col - 1);
3479 if (!isValidTextPosition(beginCursor)) {
3481 beginCursor.setColumn(col - 2);
3484 if (
auto l = view->textLayout(c)) {
3485 beginCursor.setColumn(l->previousCursorPosition(c.
column()));
3500 if (config()->wordWrap() && textLine.
endsWith(QStringLiteral(
" "))) {
3513void KTextEditor::DocumentPrivate::backspace(KTextEditor::ViewPrivate *view)
3515 if (!view->config()->persistentSelection() && view->hasSelections()) {
3519 if (view->blockSelection() && view->selection() && range.
start().
column() > 0 && toVirtualColumn(range.
start()) == toVirtualColumn(range.
end())) {
3522 view->setSelection(range);
3524 view->removeSelectedText();
3525 view->ensureUniqueCursors();
3533 const auto &multiCursors = view->secondaryCursors();
3534 view->completionWidget()->setIgnoreBufferSignals(
true);
3535 for (
const auto &c : multiCursors) {
3536 const auto newPos = backspaceAtCursor(view, c.cursor());
3541 view->completionWidget()->setIgnoreBufferSignals(
false);
3544 auto newPos = backspaceAtCursor(view, view->cursorPosition());
3546 view->setCursorPosition(newPos);
3549 view->ensureUniqueCursors();
3554 if (m_currentAutobraceRange) {
3555 const auto r = m_currentAutobraceRange->toRange();
3556 if (r.columnWidth() == 1 && view->cursorPosition() == r.
start()) {
3558 del(view, view->cursorPosition());
3559 m_currentAutobraceRange.reset();
3564void KTextEditor::DocumentPrivate::del(KTextEditor::ViewPrivate *view,
const KTextEditor::Cursor c)
3566 if (!view->config()->persistentSelection() && view->selection()) {
3569 if (view->blockSelection() && toVirtualColumn(range.
start()) == toVirtualColumn(range.
end())) {
3572 view->setSelection(range);
3574 view->removeSelectedText();
3579 if (c.
column() < m_buffer->lineLength(c.
line())) {
3582 }
else if (c.
line() < lastLine()) {
3587bool KTextEditor::DocumentPrivate::multiPaste(KTextEditor::ViewPrivate *view,
const QStringList &texts)
3589 if (texts.
isEmpty() || view->isMulticursorNotAllowed() || view->secondaryCursors().size() + 1 != (
size_t)texts.
size()) {
3593 m_undoManager->undoSafePoint();
3596 if (view->selection()) {
3597 view->removeSelectedText();
3600 auto plainSecondaryCursors = view->plainSecondaryCursors();
3601 KTextEditor::ViewPrivate::PlainSecondaryCursor primary;
3602 primary.pos = view->cursorPosition();
3603 primary.range = view->selectionRange();
3604 plainSecondaryCursors.append(primary);
3605 std::sort(plainSecondaryCursors.begin(), plainSecondaryCursors.end());
3609 for (
int i = texts.
size() - 1; i >= 0; --i) {
3611 text.
replace(re, QStringLiteral(
"\n"));
3614 insertText(pos, text,
false);
3622void KTextEditor::DocumentPrivate::paste(KTextEditor::ViewPrivate *view,
const QString &text)
3635 const bool isSingleLine = lines == 0;
3637 m_undoManager->undoSafePoint();
3643 bool skipIndentOnPaste =
false;
3645 const int length = lineLength(pos.
line());
3647 skipIndentOnPaste = length > 0;
3650 if (!view->config()->persistentSelection() && view->selection()) {
3651 pos = view->selectionRange().
start();
3652 if (view->blockSelection()) {
3653 pos = rangeOnLine(view->selectionRange(), pos.
line()).
start();
3660 view->removeSelectedText();
3663 if (config()->ovr()) {
3666 if (!view->blockSelection()) {
3667 int endColumn = (pasteLines.count() == 1 ? pos.
column() : 0) + pasteLines.last().length();
3670 int maxi = qMin(pos.
line() + pasteLines.count(), this->lines());
3672 for (
int i = pos.
line(); i < maxi; ++i) {
3673 int pasteLength = pasteLines.at(i - pos.
line()).length();
3679 insertText(pos, s, view->blockSelection());
3686 if (view->blockSelection()) {
3687 view->setCursorPositionInternal(pos);
3690 if (config()->indentPastedText()) {
3692 if (!skipIndentOnPaste) {
3693 m_indenter->indent(view, range);
3697 if (!view->blockSelection()) {
3698 Q_EMIT charactersSemiInteractivelyInserted(pos, s);
3700 m_undoManager->undoSafePoint();
3705 if (!isReadWrite()) {
3710 m_indenter->changeIndent(range, change);
3714void KTextEditor::DocumentPrivate::align(KTextEditor::ViewPrivate *view,
KTextEditor::Range range)
3716 m_indenter->indent(view, range);
3723 if (lines.
size() < 2) {
3729 int selectionStartColumn = range.
start().
column();
3731 for (
const auto &line : lines) {
3733 if (!
match.hasMatch()) {
3734 patternStartColumns.
append(-1);
3735 }
else if (
match.lastCapturedIndex() == 0) {
3736 patternStartColumns.
append(
match.capturedStart(0) + (blockwise ? selectionStartColumn : 0));
3738 patternStartColumns.
append(
match.capturedStart(1) + (blockwise ? selectionStartColumn : 0));
3741 if (!blockwise && patternStartColumns[0] != -1) {
3742 patternStartColumns[0] += selectionStartColumn;
3745 int maxColumn = *std::max_element(patternStartColumns.
cbegin(), patternStartColumns.
cend());
3748 for (
int i = 0; i < lines.size(); ++i) {
3749 if (patternStartColumns[i] != -1) {
3756void KTextEditor::DocumentPrivate::insertTab(KTextEditor::ViewPrivate *view,
const KTextEditor::Cursor)
3758 if (!isReadWrite()) {
3762 int lineLen = line(view->cursorPosition().
line()).length();
3767 if (!view->config()->persistentSelection() && view->selection()) {
3768 view->removeSelectedText();
3769 }
else if (view->currentInputMode()->overwrite() && c.
column() < lineLen) {
3774 view->currentInputMode()->overwrittenChar(removed);
3778 c = view->cursorPosition();
3779 editInsertText(c.
line(), c.
column(), QStringLiteral(
"\t"));
3788bool KTextEditor::DocumentPrivate::removeStringFromBeginning(
int line,
const QString &str)
3812bool KTextEditor::DocumentPrivate::removeStringFromEnd(
int line,
const QString &str)
3817 bool there = textline.
endsWith(str);
3839 const bool replacetabs = config()->replaceTabsDyn();
3843 const int indentWidth = config()->indentationWidth();
3846 int column = cursorPos.
column();
3852 for (
const QChar ch : str) {
3853 if (ch == tabChar) {
3856 int spacesToInsert = indentWidth - (column % indentWidth);
3858 column += spacesToInsert;
3871void KTextEditor::DocumentPrivate::addStartLineCommentToSingleLine(
int line,
int attrib)
3873 const QString commentLineMark = highlight()->getCommentSingleLineStart(attrib) +
QLatin1Char(
' ');
3876 if (highlight()->getCommentSingleLinePosition(attrib) == KSyntaxHighlighting::CommentPosition::AfterWhitespace) {
3887bool KTextEditor::DocumentPrivate::removeStartLineCommentFromSingleLine(
int line,
int attrib)
3889 const QString shortCommentMark = highlight()->getCommentSingleLineStart(attrib);
3895 bool removed = (removeStringFromBeginning(line, longCommentMark) || removeStringFromBeginning(line, shortCommentMark));
3906void KTextEditor::DocumentPrivate::addStartStopCommentToSingleLine(
int line,
int attrib)
3908 const QString startCommentMark = highlight()->getCommentStart(attrib) +
QLatin1Char(
' ');
3909 const QString stopCommentMark =
QLatin1Char(
' ') + highlight()->getCommentEnd(attrib);
3917 const int col = m_buffer->lineLength(line);
3929bool KTextEditor::DocumentPrivate::removeStartStopCommentFromSingleLine(
int line,
int attrib)
3931 const QString shortStartCommentMark = highlight()->getCommentStart(attrib);
3933 const QString shortStopCommentMark = highlight()->getCommentEnd(attrib);
3939 const bool removedStart = (removeStringFromBeginning(line, longStartCommentMark) || removeStringFromBeginning(line, shortStartCommentMark));
3942 const bool removedStop = removedStart && (removeStringFromEnd(line, longStopCommentMark) || removeStringFromEnd(line, shortStopCommentMark));
3946 return (removedStart || removedStop);
3953void KTextEditor::DocumentPrivate::addStartStopCommentToSelection(
KTextEditor::Range selection,
bool blockSelection,
int attrib)
3955 const QString startComment = highlight()->getCommentStart(attrib);
3956 const QString endComment = highlight()->getCommentEnd(attrib);
3966 if (!blockSelection) {
3967 insertText(range.
end(), endComment);
3968 insertText(range.
start(), startComment);
3970 for (
int line = range.
start().
line(); line <= range.
end().
line(); line++) {
3972 insertText(subRange.
end(), endComment);
3973 insertText(subRange.
start(), startComment);
3984void KTextEditor::DocumentPrivate::addStartLineCommentToSelection(
KTextEditor::Range selection,
int attrib)
3987 int el = selection.
end().
line();
3990 if ((selection.
end().
column() == 0) && (el > 0)) {
3994 if (sl < 0 || el < 0 || sl >= lines() || el >= lines()) {
4000 const QString commentLineMark = highlight()->getCommentSingleLineStart(attrib) +
QLatin1Char(
' ');
4003 if (highlight()->getCommentSingleLinePosition(attrib) == KSyntaxHighlighting::CommentPosition::AfterWhitespace) {
4011 col = std::numeric_limits<int>::max();
4013 for (
int l = el; l >= sl; l--) {
4014 const auto line = plainKateTextLine(l);
4015 if (line.length() == 0) {
4018 col = qMin(col, qMax(0, line.firstChar()));
4025 if (col == std::numeric_limits<int>::max()) {
4032 for (
int l = el; l >= sl; l--) {
4039bool KTextEditor::DocumentPrivate::nextNonSpaceCharPos(
int &line,
int &col)
4041 for (; line >= 0 && line < m_buffer->lines(); line++) {
4055bool KTextEditor::DocumentPrivate::previousNonSpaceCharPos(
int &line,
int &col)
4057 while (line >= 0 && line < m_buffer->lines()) {
4079bool KTextEditor::DocumentPrivate::removeStartStopCommentFromSelection(
KTextEditor::Range selection,
int attrib)
4081 const QString startComment = highlight()->getCommentStart(attrib);
4082 const QString endComment = highlight()->getCommentEnd(attrib);
4084 int sl = qMax<int>(0, selection.
start().
line());
4085 int el = qMin<int>(selection.
end().
line(), lastLine());
4092 }
else if (el > 0) {
4094 ec = m_buffer->lineLength(el) - 1;
4097 const int startCommentLen = startComment.
length();
4098 const int endCommentLen = endComment.
length();
4102 bool remove = nextNonSpaceCharPos(sl, sc) && m_buffer->plainLine(sl).matchesAt(sc, startComment) && previousNonSpaceCharPos(el, ec)
4103 && ((ec - endCommentLen + 1) >= 0) && m_buffer->plainLine(el).matchesAt(ec - endCommentLen + 1, endComment);
4120 const QString startComment = highlight()->getCommentStart(attrib);
4121 const QString endComment = highlight()->getCommentEnd(attrib);
4122 const int startCommentLen = startComment.
length();
4123 const int endCommentLen = endComment.
length();
4125 const bool remove = m_buffer->plainLine(
start.line()).matchesAt(
start.column(), startComment)
4126 && m_buffer->plainLine(
end.line()).matchesAt(
end.column() - endCommentLen, endComment);
4140bool KTextEditor::DocumentPrivate::removeStartLineCommentFromSelection(
KTextEditor::Range selection,
int attrib,
bool toggleComment)
4142 const QString shortCommentMark = highlight()->getCommentSingleLineStart(attrib);
4145 const int startLine = selection.
start().
line();
4146 int endLine = selection.
end().
line();
4148 if ((selection.
end().
column() == 0) && (endLine > 0)) {
4152 bool removed =
false;
4158 if (toggleComment) {
4159 bool allLinesAreCommented =
true;
4160 for (
int line = endLine; line >= startLine; line--) {
4161 const auto ln = m_buffer->plainLine(line);
4162 const QString &text = ln.text();
4169 textView = textView.trimmed();
4170 if (!textView.startsWith(shortCommentMark) && !textView.startsWith(longCommentMark)) {
4171 allLinesAreCommented =
false;
4175 if (!allLinesAreCommented) {
4183 for (
int z = endLine; z >= startLine; z--) {
4185 removed = (removeStringFromBeginning(z, longCommentMark) || removeStringFromBeginning(z, shortCommentMark) || removed);
4196 const bool hasSelection = !selection.
isEmpty();
4197 int selectionCol = 0;
4202 const int line = c.
line();
4204 int startAttrib = 0;
4207 if (selectionCol < ln.
length()) {
4208 startAttrib = ln.
attribute(selectionCol);
4213 bool hasStartLineCommentMark = !(highlight()->getCommentSingleLineStart(startAttrib).isEmpty());
4214 bool hasStartStopCommentMark = (!(highlight()->getCommentStart(startAttrib).isEmpty()) && !(highlight()->getCommentEnd(startAttrib).isEmpty()));
4216 if (changeType == Comment) {
4217 if (!hasSelection) {
4218 if (hasStartLineCommentMark) {
4219 addStartLineCommentToSingleLine(line, startAttrib);
4220 }
else if (hasStartStopCommentMark) {
4221 addStartStopCommentToSingleLine(line, startAttrib);
4232 if (hasStartStopCommentMark
4233 && (!hasStartLineCommentMark
4236 addStartStopCommentToSelection(selection, blockSelect, startAttrib);
4237 }
else if (hasStartLineCommentMark) {
4238 addStartLineCommentToSelection(selection, startAttrib);
4242 bool removed =
false;
4243 const bool toggleComment = changeType == ToggleComment;
4244 if (!hasSelection) {
4245 removed = (hasStartLineCommentMark && removeStartLineCommentFromSingleLine(line, startAttrib))
4246 || (hasStartStopCommentMark && removeStartStopCommentFromSingleLine(line, startAttrib));
4249 removed = (hasStartStopCommentMark && removeStartStopCommentFromSelection(selection, startAttrib))
4250 || (hasStartLineCommentMark && removeStartLineCommentFromSelection(selection, startAttrib, toggleComment));
4254 if (!removed && toggleComment) {
4255 commentSelection(selection, c, blockSelect, Comment);
4264void KTextEditor::DocumentPrivate::comment(KTextEditor::ViewPrivate *v, uint line, uint column, CommentType change)
4267 const bool skipWordWrap = wordWrap();
4274 if (v->selection()) {
4275 const auto &cursors = v->secondaryCursors();
4276 for (
const auto &c : cursors) {
4280 commentSelection(c.range->toRange(), c.cursor(),
false, change);
4283 commentSelection(v->selectionRange(), c, v->blockSelection(), change);
4285 const auto &cursors = v->secondaryCursors();
4286 for (
const auto &c : cursors) {
4287 commentSelection({}, c.cursor(),
false, change);
4299void KTextEditor::DocumentPrivate::transformCursorOrRange(KTextEditor::ViewPrivate *v,
4302 KTextEditor::DocumentPrivate::TextTransform t)
4304 if (v->selection()) {
4316 if (range.
start().
line() == selection.
end().
line() || v->blockSelection()) {
4321 int swapCol =
start;
4331 if (t == Uppercase) {
4334 }
else if (t == Lowercase) {
4347 && !highlight()->isInWord(l.
at(range.
start().
column() - 1)))
4348 || (p && !highlight()->isInWord(s.
at(p - 1)))) {
4357 insertText(range.
start(), s);
4392 insertText(cursor, s);
4402 if (v->selection()) {
4403 const auto &cursors = v->secondaryCursors();
4404 for (
const auto &c : cursors) {
4408 auto pos = c.pos->toCursor();
4409 transformCursorOrRange(v, c.anchor, c.range->toRange(), t);
4413 const auto selRange = v->selectionRange();
4414 transformCursorOrRange(v, c, v->selectionRange(), t);
4415 v->setSelection(selRange);
4416 v->setCursorPosition(c);
4418 const auto &secondaryCursors = v->secondaryCursors();
4419 for (
const auto &c : secondaryCursors) {
4420 transformCursorOrRange(v, c.cursor(), {}, t);
4422 transformCursorOrRange(v, c, {}, t);
4433 while (first < last) {
4434 if (line >= lines() || line + 1 >= lines()) {
4450 editRemoveText(line + 1, 0, pos);
4453 editInsertText(line + 1, 0, QStringLiteral(
" "));
4457 editRemoveText(line + 1, 0, tl.
length());
4460 editUnWrapLine(line);
4468 for (
auto view : std::as_const(m_views)) {
4469 static_cast<ViewPrivate *
>(view)->tagLines(lineRange,
true);
4473void KTextEditor::DocumentPrivate::tagLine(
int line)
4475 tagLines({line, line});
4478void KTextEditor::DocumentPrivate::repaintViews(
bool paintOnlyDirty)
4480 for (
auto view : std::as_const(m_views)) {
4481 static_cast<ViewPrivate *
>(view)->repaintText(paintOnlyDirty);
4494 if (maxLines < 0 ||
start.line() < 0 ||
start.line() >= lines()) {
4504 if (config()->ovr()) {
4505 if (isBracket(right)) {
4510 }
else if (isBracket(right)) {
4512 }
else if (isBracket(left)) {
4519 const QChar opposite = matchingBracket(bracket);
4524 const int searchDir = isStartBracket(bracket) ? 1 : -1;
4527 const int minLine = qMax(range.
start().
line() - maxLines, 0);
4528 const int maxLine = qMin(range.
start().
line() + maxLines, documentEnd().line());
4533 int validAttr = kateTextLine(cursor.
line()).attribute(cursor.
column());
4535 while (cursor.
line() >= minLine && cursor.
line() <= maxLine) {
4536 if (!cursor.move(searchDir)) {
4544 if (c == opposite) {
4546 if (searchDir > 0) {
4547 range.
setEnd(cursor.toCursor());
4554 }
else if (c == bracket) {
4570void KTextEditor::DocumentPrivate::updateDocName()
4573 if (!url().isEmpty() && (m_docName == removeNewLines(url().fileName()) || m_docName.
startsWith(removeNewLines(url().fileName()) +
QLatin1String(
" (")))) {
4579 std::vector<KTextEditor::DocumentPrivate *> docsWithSameName;
4584 if ((doc !=
this) && (doc->url().fileName() == url().fileName())) {
4585 if (doc->m_docNameNumber > count) {
4586 count = doc->m_docNameNumber;
4588 docsWithSameName.push_back(doc);
4592 m_docNameNumber = count + 1;
4595 m_docName = removeNewLines(url().fileName());
4597 m_isUntitled = m_docName.isEmpty();
4599 if (!m_isUntitled && !docsWithSameName.empty()) {
4600 docsWithSameName.push_back(
this);
4601 uniquifyDocNames(docsWithSameName);
4606 m_docName =
i18n(
"Untitled");
4609 if (m_docNameNumber > 0) {
4614 if (oldName != m_docName) {
4615 Q_EMIT documentNameChanged(
this);
4633 int lastSlash = url.lastIndexOf(
QLatin1Char(
'/'));
4634 if (lastSlash == -1) {
4638 int fileNameStart = lastSlash;
4641 lastSlash = url.lastIndexOf(
QLatin1Char(
'/'), lastSlash);
4642 if (lastSlash == -1) {
4645 return url.mid(lastSlash, fileNameStart);
4650 urlv = urlv.
mid(lastSlash);
4652 for (
size_t i = 0; i < urls.size(); ++i) {
4653 if (urls[i] == url) {
4657 if (urls[i].endsWith(urlv)) {
4658 lastSlash = url.lastIndexOf(
QLatin1Char(
'/'), lastSlash - 1);
4659 if (lastSlash <= 0) {
4661 return url.mid(0, fileNameStart);
4664 urlv = urlView.
mid(lastSlash);
4669 return url.mid(lastSlash + 1, fileNameStart - (lastSlash + 1));
4672void KTextEditor::DocumentPrivate::uniquifyDocNames(
const std::vector<KTextEditor::DocumentPrivate *> &docs)
4674 std::vector<QString> paths;
4675 paths.reserve(docs.size());
4677 return d->url().toString(QUrl::NormalizePathSegments | QUrl::PreferLocalFile);
4680 for (
const auto doc : docs) {
4681 const QString prefix = shortestPrefix(paths, doc);
4683 const QString oldName = doc->m_docName;
4686 doc->m_docName = fileName + QStringLiteral(
" - ") + prefix;
4688 doc->m_docName = fileName;
4691 if (doc->m_docName != oldName) {
4699 if (url().isEmpty() || !m_modOnHd) {
4703 if (!isModified() && isAutoReload()) {
4704 onModOnHdAutoReload();
4708 if (!m_fileChangedDialogsActivated || m_modOnHdHandler) {
4713 if (m_modOnHdReason == m_prevModOnHdReason) {
4716 m_prevModOnHdReason = m_modOnHdReason;
4718 m_modOnHdHandler =
new KateModOnHdPrompt(
this, m_modOnHdReason, reasonedMOHString());
4719 connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::saveAsTriggered,
this, &DocumentPrivate::onModOnHdSaveAs);
4720 connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::closeTriggered,
this, &DocumentPrivate::onModOnHdClose);
4721 connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::reloadTriggered,
this, &DocumentPrivate::onModOnHdReload);
4722 connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::autoReloadTriggered,
this, &DocumentPrivate::onModOnHdAutoReload);
4723 connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::ignoreTriggered,
this, &DocumentPrivate::onModOnHdIgnore);
4726void KTextEditor::DocumentPrivate::onModOnHdSaveAs()
4729 const QUrl res = getSaveFileUrl(
i18n(
"Save File"));
4735 delete m_modOnHdHandler;
4736 m_prevModOnHdReason = OnDiskUnmodified;
4737 Q_EMIT modifiedOnDisk(
this,
false, OnDiskUnmodified);
4744void KTextEditor::DocumentPrivate::onModOnHdClose()
4747 m_fileChangedDialogsActivated =
false;
4760void KTextEditor::DocumentPrivate::onModOnHdReload()
4763 m_prevModOnHdReason = OnDiskUnmodified;
4764 Q_EMIT modifiedOnDisk(
this,
false, OnDiskUnmodified);
4770 m_undoManager->clearUndo();
4771 m_undoManager->clearRedo();
4774 delete m_modOnHdHandler;
4777void KTextEditor::DocumentPrivate::autoReloadToggled(
bool b)
4779 m_autoReloadMode->setChecked(b);
4783 disconnect(&m_modOnHdTimer, &
QTimer::timeout,
this, &DocumentPrivate::onModOnHdAutoReload);
4787bool KTextEditor::DocumentPrivate::isAutoReload()
4789 return m_autoReloadMode->isChecked();
4792void KTextEditor::DocumentPrivate::delayAutoReload()
4794 if (isAutoReload()) {
4795 m_autoReloadThrottle.start();
4799void KTextEditor::DocumentPrivate::onModOnHdAutoReload()
4801 if (m_modOnHdHandler) {
4802 delete m_modOnHdHandler;
4803 autoReloadToggled(
true);
4806 if (!isAutoReload()) {
4810 if (m_modOnHd && !m_reloading && !m_autoReloadThrottle.isActive()) {
4812 m_prevModOnHdReason = OnDiskUnmodified;
4813 Q_EMIT modifiedOnDisk(
this,
false, OnDiskUnmodified);
4817 m_undoManager->clearUndo();
4818 m_undoManager->clearRedo();
4821 m_autoReloadThrottle.start();
4825void KTextEditor::DocumentPrivate::onModOnHdIgnore()
4828 delete m_modOnHdHandler;
4833 m_modOnHdReason = reason;
4834 m_modOnHd = (reason != OnDiskUnmodified);
4835 Q_EMIT modifiedOnDisk(
this, (reason != OnDiskUnmodified), reason);
4838class KateDocumentTmpMark
4847 m_fileChangedDialogsActivated = on;
4852 if (url().isEmpty()) {
4864 m_undoManager->clearUndo();
4865 m_undoManager->clearRedo();
4870 delete m_modOnHdHandler;
4872 Q_EMIT aboutToReload(
this);
4876 std::transform(m_marks.cbegin(), m_marks.cend(), std::back_inserter(tmp), [
this](
KTextEditor::Mark *mark) {
4877 return KateDocumentTmpMark{.line = line(mark->line), .mark = *mark};
4881 const QString oldMode = mode();
4882 const bool modeByUser = m_fileTypeSetByUser;
4883 const QString oldHlMode = highlightingMode();
4884 const bool hlByUser = m_hlSetByUser;
4886 m_storedVariables.clear();
4890 std::transform(m_views.cbegin(), m_views.cend(), std::back_inserter(cursorPositions), [](
KTextEditor::View *v) {
4891 return std::pair<KTextEditor::ViewPrivate *, KTextEditor::Cursor>(static_cast<ViewPrivate *>(v), v->cursorPosition());
4896 for (
auto *view : m_views) {
4897 static_cast<ViewPrivate *
>(view)->clearSecondaryCursors();
4900 static_cast<ViewPrivate *
>(view)->clearFoldingState();
4905 KTextEditor::DocumentPrivate::openUrl(url());
4908 m_userSetEncodingForNextReload =
false;
4911 for (
auto v : std::as_const(m_views)) {
4913 auto it = std::find_if(cursorPositions.
cbegin(), cursorPositions.
cend(), [v](
const std::pair<KTextEditor::ViewPrivate *, KTextEditor::Cursor> &p) {
4914 return p.first == v;
4916 v->setCursorPosition(it->second);
4922 const int lines = this->lines();
4923 for (
const auto &tmpMark : tmp) {
4924 if (tmpMark.mark.line < lines) {
4925 if (tmpMark.line == line(tmpMark.mark.line)) {
4926 setMark(tmpMark.mark.line, tmpMark.mark.type);
4933 updateFileType(oldMode,
true);
4936 setHighlightingMode(oldHlMode);
4939 Q_EMIT reloaded(
this);
4944bool KTextEditor::DocumentPrivate::documentSave()
4946 if (!url().
isValid() || !isReadWrite()) {
4947 return documentSaveAs();
4953bool KTextEditor::DocumentPrivate::documentSaveAs()
4955 const QUrl saveUrl = getSaveFileUrl(
i18n(
"Save File"));
4963bool KTextEditor::DocumentPrivate::documentSaveAsWithEncoding(
const QString &encoding)
4965 const QUrl saveUrl = getSaveFileUrl(
i18n(
"Save File"));
4970 setEncoding(encoding);
4974void KTextEditor::DocumentPrivate::documentSaveCopyAs()
4976 const QUrl saveUrl = getSaveFileUrl(
i18n(
"Save Copy of File"));
4982 if (!file->
open()) {
4986 if (!m_buffer->saveFile(file->
fileName())) {
4988 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 "
4989 "that enough disk space is available.",
4997 const auto url = this->url();
4999 if (
auto sj = qobject_cast<KIO::StatJob *>(j)) {
5010void KTextEditor::DocumentPrivate::setWordWrap(
bool on)
5012 config()->setWordWrap(on);
5015bool KTextEditor::DocumentPrivate::wordWrap()
const
5017 return config()->wordWrap();
5020void KTextEditor::DocumentPrivate::setWordWrapAt(uint col)
5022 config()->setWordWrapAt(col);
5025unsigned int KTextEditor::DocumentPrivate::wordWrapAt()
const
5027 return config()->wordWrapAt();
5030void KTextEditor::DocumentPrivate::setPageUpDownMovesCursor(
bool on)
5032 config()->setPageUpDownMovesCursor(on);
5035bool KTextEditor::DocumentPrivate::pageUpDownMovesCursor()
const
5037 return config()->pageUpDownMovesCursor();
5043 return m_config->setEncoding(e);
5048 return m_config->encoding();
5051void KTextEditor::DocumentPrivate::updateConfig()
5053 m_undoManager->updateConfig();
5056 m_indenter->setMode(m_config->indentationMode());
5057 m_indenter->updateConfig();
5060 m_buffer->setTabWidth(config()->tabWidth());
5063 for (
auto view : std::as_const(m_views)) {
5064 static_cast<ViewPrivate *
>(view)->updateDocumentConfig();
5068 if (m_onTheFlyChecker) {
5069 m_onTheFlyChecker->updateConfig();
5072 if (config()->autoSave()) {
5073 int interval = config()->autoSaveInterval();
5074 if (interval == 0) {
5075 m_autoSaveTimer.stop();
5077 m_autoSaveTimer.setInterval(interval * 1000);
5079 m_autoSaveTimer.start();
5084 Q_EMIT configChanged(
this);
5094bool KTextEditor::DocumentPrivate::readVariables(KTextEditor::ViewPrivate *view)
5096 const bool hasVariableline = [
this] {
5099 for (
int i = qMax(10, lines() - 10); i < lines(); ++i) {
5100 if (line(i).contains(s)) {
5105 for (
int i = 0; i < qMin(9, lines()); ++i) {
5106 if (line(i).contains(s)) {
5112 if (!hasVariableline) {
5117 m_config->configStart();
5118 for (
auto view : std::as_const(m_views)) {
5119 auto v =
static_cast<ViewPrivate *
>(view);
5130 for (
int i = 0; i < qMin(9, lines()); ++i) {
5131 readVariableLine(line(i), view);
5134 for (
int i = qMax(10, lines() - 10); i < lines(); i++) {
5135 readVariableLine(line(i), view);
5140 m_config->configEnd();
5141 for (
auto view : std::as_const(m_views)) {
5142 auto v =
static_cast<ViewPrivate *
>(view);
5154void KTextEditor::DocumentPrivate::readVariableLine(
const QString &t, KTextEditor::ViewPrivate *view)
5157 static const QRegularExpression kvLineWildcard(QStringLiteral(
"kate-wildcard\\((.*)\\):(.*)"));
5158 static const QRegularExpression kvLineMime(QStringLiteral(
"kate-mimetype\\((.*)\\):(.*)"));
5171 auto match = kvLine.match(t);
5172 if (
match.hasMatch()) {
5173 s =
match.captured(1);
5176 }
else if ((match = kvLineWildcard.match(t)).hasMatch()) {
5182 for (
const QString &pattern : wildcards) {
5189 found = wildcard.match(matchPath ? pathOfFile : nameOfFile).hasMatch();
5200 s =
match.captured(2);
5203 }
else if ((match = kvLineMime.match(t)).hasMatch()) {
5211 s =
match.captured(2);
5219 static const auto vvl = {
5243 int spaceIndent = -1;
5244 bool replaceTabsSet =
false;
5249 while ((match = kvVar.match(s, startPos)).hasMatch()) {
5250 startPos =
match.capturedEnd(0);
5251 var =
match.captured(1);
5252 val =
match.captured(2).trimmed();
5258 if (contains(vvl, var)) {
5260 setViewVariable(var, val, {&v, 1});
5264 if (var ==
QLatin1String(
"word-wrap") && checkBoolValue(val, &state)) {
5269 else if (var ==
QLatin1String(
"backspace-indents") && checkBoolValue(val, &state)) {
5270 m_config->setBackspaceIndents(state);
5271 }
else if (var ==
QLatin1String(
"indent-pasted-text") && checkBoolValue(val, &state)) {
5272 m_config->setIndentPastedText(state);
5273 }
else if (var ==
QLatin1String(
"replace-tabs") && checkBoolValue(val, &state)) {
5274 m_config->setReplaceTabsDyn(state);
5275 replaceTabsSet =
true;
5276 }
else if (var ==
QLatin1String(
"remove-trailing-space") && checkBoolValue(val, &state)) {
5277 qCWarning(LOG_KTE) <<
i18n(
5278 "Using deprecated modeline 'remove-trailing-space'. "
5279 "Please replace with 'remove-trailing-spaces modified;', see "
5280 "https://docs.kde.org/?application=katepart&branch=stable5&path=config-variables.html#variable-remove-trailing-spaces");
5281 m_config->setRemoveSpaces(state ? 1 : 0);
5282 }
else if (var ==
QLatin1String(
"replace-trailing-space-save") && checkBoolValue(val, &state)) {
5283 qCWarning(LOG_KTE) <<
i18n(
5284 "Using deprecated modeline 'replace-trailing-space-save'. "
5285 "Please replace with 'remove-trailing-spaces all;', see "
5286 "https://docs.kde.org/?application=katepart&branch=stable5&path=config-variables.html#variable-remove-trailing-spaces");
5287 m_config->setRemoveSpaces(state ? 2 : 0);
5288 }
else if (var ==
QLatin1String(
"overwrite-mode") && checkBoolValue(val, &state)) {
5289 m_config->setOvr(state);
5290 }
else if (var ==
QLatin1String(
"keep-extra-spaces") && checkBoolValue(val, &state)) {
5291 m_config->setKeepExtraSpaces(state);
5292 }
else if (var ==
QLatin1String(
"tab-indents") && checkBoolValue(val, &state)) {
5293 m_config->setTabIndents(state);
5294 }
else if (var ==
QLatin1String(
"show-tabs") && checkBoolValue(val, &state)) {
5295 m_config->setShowTabs(state);
5296 }
else if (var ==
QLatin1String(
"show-trailing-spaces") && checkBoolValue(val, &state)) {
5297 m_config->setShowSpaces(state ? KateDocumentConfig::Trailing : KateDocumentConfig::None);
5298 }
else if (var ==
QLatin1String(
"space-indent") && checkBoolValue(val, &state)) {
5300 spaceIndent = state;
5301 }
else if (var ==
QLatin1String(
"smart-home") && checkBoolValue(val, &state)) {
5302 m_config->setSmartHome(state);
5303 }
else if (var ==
QLatin1String(
"newline-at-eof") && checkBoolValue(val, &state)) {
5304 m_config->setNewLineAtEof(state);
5308 else if (var ==
QLatin1String(
"tab-width") && checkIntValue(val, &n)) {
5309 m_config->setTabWidth(n);
5310 }
else if (var ==
QLatin1String(
"indent-width") && checkIntValue(val, &n)) {
5311 m_config->setIndentationWidth(n);
5313 m_config->setIndentationMode(val);
5314 }
else if (var ==
QLatin1String(
"word-wrap-column") && checkIntValue(val, &n) && n > 0) {
5315 m_config->setWordWrapAt(n);
5321 if ((n = indexOf(l, val.
toLower())) != -1) {
5324 m_config->setEol(n);
5325 m_config->setAllowEolDetection(
false);
5328 if (checkBoolValue(val, &state)) {
5329 m_config->setBom(state);
5331 }
else if (var ==
QLatin1String(
"remove-trailing-spaces")) {
5334 m_config->setRemoveSpaces(1);
5336 m_config->setRemoveSpaces(2);
5338 m_config->setRemoveSpaces(0);
5341 setHighlightingMode(val);
5347 setDefaultDictionary(val);
5348 }
else if (var ==
QLatin1String(
"automatic-spell-checking") && checkBoolValue(val, &state)) {
5349 onTheFlySpellCheckingEnabled(state);
5353 else if (contains(vvl, var)) {
5354 setViewVariable(var, val, m_views);
5356 m_storedVariables[var] = val;
5368 if (!replaceTabsSet && spaceIndent >= 0) {
5369 m_config->setReplaceTabsDyn(spaceIndent > 0);
5373void KTextEditor::DocumentPrivate::setViewVariable(
const QString &var,
const QString &val, std::span<KTextEditor::View *> views)
5378 for (
auto view : views) {
5379 auto v =
static_cast<ViewPrivate *
>(view);
5382 if (checkBoolValue(val, &state)) {
5385 if (v->config()->
setValue(var, help)) {
5386 }
else if (v->rendererConfig()->
setValue(var, help)) {
5388 }
else if (var ==
QLatin1String(
"dynamic-word-wrap") && checkBoolValue(val, &state)) {
5389 v->config()->setDynWordWrap(state);
5390 }
else if (var ==
QLatin1String(
"block-selection") && checkBoolValue(val, &state)) {
5391 v->setBlockSelection(state);
5394 }
else if (var ==
QLatin1String(
"icon-bar-color") && checkColorValue(val, c)) {
5395 v->rendererConfig()->setIconBarColor(c);
5398 else if (var ==
QLatin1String(
"background-color") && checkColorValue(val, c)) {
5399 v->rendererConfig()->setBackgroundColor(c);
5400 }
else if (var ==
QLatin1String(
"selection-color") && checkColorValue(val, c)) {
5401 v->rendererConfig()->setSelectionColor(c);
5402 }
else if (var ==
QLatin1String(
"current-line-color") && checkColorValue(val, c)) {
5403 v->rendererConfig()->setHighlightedLineColor(c);
5404 }
else if (var ==
QLatin1String(
"bracket-highlight-color") && checkColorValue(val, c)) {
5405 v->rendererConfig()->setHighlightedBracketColor(c);
5406 }
else if (var ==
QLatin1String(
"word-wrap-marker-color") && checkColorValue(val, c)) {
5407 v->rendererConfig()->setWordWrapMarkerColor(c);
5413 _f.setFixedPitch(
QFont(val).fixedPitch());
5418 v->rendererConfig()->setFont(_f);
5420 v->rendererConfig()->setSchema(val);
5425bool KTextEditor::DocumentPrivate::checkBoolValue(
QString val,
bool *result)
5429 if (contains(trueValues, val)) {
5435 if (contains(falseValues, val)) {
5442bool KTextEditor::DocumentPrivate::checkIntValue(
const QString &val,
int *result)
5445 *result = val.
toInt(&ret);
5449bool KTextEditor::DocumentPrivate::checkColorValue(
const QString &val,
QColor &c)
5458 auto it = m_storedVariables.find(name);
5459 if (it == m_storedVariables.end()) {
5467 QString s = QStringLiteral(
"kate: ");
5471 readVariableLine(s);
5476void KTextEditor::DocumentPrivate::slotModOnHdDirty(
const QString &path)
5478 if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskModified)) {
5480 m_modOnHdReason = OnDiskModified;
5482 if (!m_modOnHdTimer.isActive()) {
5483 m_modOnHdTimer.start();
5488void KTextEditor::DocumentPrivate::slotModOnHdCreated(
const QString &path)
5490 if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskCreated)) {
5492 m_modOnHdReason = OnDiskCreated;
5494 if (!m_modOnHdTimer.isActive()) {
5495 m_modOnHdTimer.start();
5500void KTextEditor::DocumentPrivate::slotModOnHdDeleted(
const QString &path)
5502 if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskDeleted)) {
5504 m_modOnHdReason = OnDiskDeleted;
5506 if (!m_modOnHdTimer.isActive()) {
5507 m_modOnHdTimer.start();
5512void KTextEditor::DocumentPrivate::slotDelayedHandleModOnHd()
5516 if (!oldDigest.
isEmpty() && !url().isEmpty() && url().isLocalFile()) {
5518 if (m_modOnHdReason != OnDiskDeleted && m_modOnHdReason != OnDiskCreated && createDigest() && oldDigest == checksum()) {
5520 m_modOnHdReason = OnDiskUnmodified;
5521 m_prevModOnHdReason = OnDiskUnmodified;
5528 if (m_modOnHd && !isModified() &&
QFile::exists(url().toLocalFile())
5529 && config()->value(KateDocumentConfig::AutoReloadIfStateIsInVersionControl).toBool()) {
5536 git.
start(fullGitPath, args);
5543 m_modOnHdReason = OnDiskUnmodified;
5544 m_prevModOnHdReason = OnDiskUnmodified;
5554 Q_EMIT modifiedOnDisk(
this, m_modOnHd, m_modOnHdReason);
5559 return m_buffer->digest();
5562bool KTextEditor::DocumentPrivate::createDigest()
5566 if (url().isLocalFile()) {
5567 QFile f(url().toLocalFile());
5571 const QString header = QStringLiteral(
"blob %1").
arg(f.size());
5574 digest = crypto.result();
5579 m_buffer->setDigest(digest);
5583QString KTextEditor::DocumentPrivate::reasonedMOHString()
const
5588 switch (m_modOnHdReason) {
5589 case OnDiskModified:
5590 return i18n(
"The file '%1' was modified on disk.", str);
5593 return i18n(
"The file '%1' was created on disk.", str);
5596 return i18n(
"The file '%1' was deleted or moved on disk.", str);
5605void KTextEditor::DocumentPrivate::removeTrailingSpacesAndAddNewLineAtEof()
5608 const int remove = config()->removeSpaces();
5609 const bool newLineAtEof = config()->newLineAtEof();
5610 if (remove == 0 && !newLineAtEof) {
5615 const bool wordWrapEnabled = config()->wordWrap();
5616 if (wordWrapEnabled) {
5623 const int lines = this->lines();
5625 for (
int line = 0; line < lines; ++line) {
5631 if (remove == 2 || textline.markedAsModified() || textline.markedAsSavedOnDisk()) {
5632 const int p = textline.
lastChar() + 1;
5633 const int l = textline.
length() - p;
5635 editRemoveText(line, p, l);
5644 Q_ASSERT(lines > 0);
5645 const auto length = lineLength(lines - 1);
5649 const auto oldEndOfDocumentCursor = documentEnd();
5650 std::vector<KTextEditor::ViewPrivate *> viewsToRestoreCursors;
5651 for (
auto view : std::as_const(m_views)) {
5652 auto v =
static_cast<ViewPrivate *
>(view);
5653 if (v->cursorPosition() == oldEndOfDocumentCursor) {
5654 viewsToRestoreCursors.push_back(v);
5659 editWrapLine(lines - 1, length);
5662 for (
auto v : viewsToRestoreCursors) {
5663 v->setCursorPosition(oldEndOfDocumentCursor);
5671 if (wordWrapEnabled) {
5679 const int lines = this->lines();
5680 for (
int line = 0; line < lines; ++line) {
5682 const int p = textline.
lastChar() + 1;
5683 const int l = textline.
length() - p;
5685 editRemoveText(line, p, l);
5693 if (user || !m_fileTypeSetByUser) {
5699 if (fileType.name.
isEmpty()) {
5704 m_fileTypeSetByUser = user;
5706 m_fileType = newType;
5708 m_config->configStart();
5713 if ((user || !m_hlSetByUser) && !fileType.hl.
isEmpty()) {
5714 int hl(KateHlManager::self()->nameFind(fileType.hl));
5717 m_buffer->setHighlight(hl);
5724 if (!m_indenterSetByUser && !fileType.indenter.
isEmpty()) {
5725 config()->setIndentationMode(fileType.indenter);
5729 for (
auto view : std::as_const(m_views)) {
5730 auto v =
static_cast<ViewPrivate *
>(view);
5735 bool bom_settings =
false;
5736 if (m_bomSetByUser) {
5737 bom_settings = m_config->bom();
5739 readVariableLine(fileType.varLine);
5740 if (m_bomSetByUser) {
5741 m_config->setBom(bom_settings);
5743 m_config->configEnd();
5744 for (
auto view : std::as_const(m_views)) {
5745 auto v =
static_cast<ViewPrivate *
>(view);
5752 Q_EMIT modeChanged(
this);
5756void KTextEditor::DocumentPrivate::slotQueryClose_save(
bool *handled,
bool *abortClosing)
5759 *abortClosing =
true;
5760 if (url().isEmpty()) {
5761 const QUrl res = getSaveFileUrl(
i18n(
"Save File"));
5763 *abortClosing =
true;
5767 *abortClosing =
false;
5770 *abortClosing =
false;
5780 return m_config->configKeys();
5786 return m_config->
value(key);
5792 m_config->setValue(key, value);