File indexing completed on 2024-04-28 11:45:01
0001 /* 0002 SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org> 0003 SPDX-FileCopyrightText: 2002-2004 Christoph Cullmann <cullmann@kde.org> 0004 SPDX-FileCopyrightText: 2007 Mirko Stocker <me@misto.ch> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "katebuffer.h" 0010 #include "kateautoindent.h" 0011 #include "kateconfig.h" 0012 #include "katedocument.h" 0013 #include "kateglobal.h" 0014 #include "katehighlight.h" 0015 #include "katepartdebug.h" 0016 #include "katesyntaxmanager.h" 0017 0018 #include <KLocalizedString> 0019 0020 #include <QDate> 0021 #include <QFile> 0022 #include <QFileInfo> 0023 #include <QTextCodec> 0024 #include <QTextStream> 0025 #include <QTimer> 0026 0027 /** 0028 * Initial value for m_maxDynamicContexts 0029 */ 0030 static const int KATE_MAX_DYNAMIC_CONTEXTS = 512; 0031 0032 /** 0033 * Create an empty buffer. (with one block with one empty line) 0034 */ 0035 KateBuffer::KateBuffer(KTextEditor::DocumentPrivate *doc) 0036 : Kate::TextBuffer(doc) 0037 , m_doc(doc) 0038 , m_brokenEncoding(false) 0039 , m_tooLongLinesWrapped(false) 0040 , m_longestLineLoaded(0) 0041 , m_highlight(nullptr) 0042 , m_tabWidth(8) 0043 , m_lineHighlighted(0) 0044 , m_maxDynamicContexts(KATE_MAX_DYNAMIC_CONTEXTS) 0045 { 0046 } 0047 0048 /** 0049 * Cleanup on destruction 0050 */ 0051 KateBuffer::~KateBuffer() = default; 0052 0053 void KateBuffer::editStart() 0054 { 0055 if (!startEditing()) { 0056 return; 0057 } 0058 } 0059 0060 void KateBuffer::editEnd() 0061 { 0062 // not finished, do nothing 0063 if (!finishEditing()) { 0064 return; 0065 } 0066 0067 // nothing change, OK 0068 if (!editingChangedBuffer()) { 0069 return; 0070 } 0071 0072 // if we arrive here, line changed should be OK 0073 Q_ASSERT(editingMinimalLineChanged() != -1); 0074 Q_ASSERT(editingMaximalLineChanged() != -1); 0075 Q_ASSERT(editingMinimalLineChanged() <= editingMaximalLineChanged()); 0076 0077 updateHighlighting(); 0078 } 0079 0080 void KateBuffer::updateHighlighting() 0081 { 0082 // no highlighting, nothing to do 0083 if (!m_highlight) { 0084 return; 0085 } 0086 0087 // if we don't touch the highlighted area => fine 0088 if (editingMinimalLineChanged() > m_lineHighlighted) { 0089 return; 0090 } 0091 0092 // really update highlighting 0093 // look one line too far, needed for linecontinue stuff 0094 doHighlight(editingMinimalLineChanged(), editingMaximalLineChanged() + 1, true); 0095 } 0096 0097 void KateBuffer::clear() 0098 { 0099 // call original clear function 0100 Kate::TextBuffer::clear(); 0101 0102 // reset the state 0103 m_brokenEncoding = false; 0104 m_tooLongLinesWrapped = false; 0105 m_longestLineLoaded = 0; 0106 0107 // back to line 0 with hl 0108 m_lineHighlighted = 0; 0109 } 0110 0111 bool KateBuffer::openFile(const QString &m_file, bool enforceTextCodec) 0112 { 0113 // first: setup fallback and normal encoding 0114 setEncodingProberType(KateGlobalConfig::global()->proberType()); 0115 setFallbackTextCodec(KateGlobalConfig::global()->fallbackCodec()); 0116 setTextCodec(m_doc->config()->codec()); 0117 0118 // setup eol 0119 setEndOfLineMode((EndOfLineMode)m_doc->config()->eol()); 0120 0121 // NOTE: we do not remove trailing spaces on load. This was discussed 0122 // over the years again and again. bugs: 306926, 239077, ... 0123 0124 // line length limit 0125 setLineLengthLimit(m_doc->lineLengthLimit()); 0126 0127 // then, try to load the file 0128 m_brokenEncoding = false; 0129 m_tooLongLinesWrapped = false; 0130 m_longestLineLoaded = 0; 0131 0132 // allow non-existent files without error, if local file! 0133 // will allow to do "kate newfile.txt" without error messages but still fail if e.g. you mistype a url 0134 // and it can't be fetched via fish:// or other strange things in kio happen... 0135 // just clear() + exit with success! 0136 0137 QFileInfo fileInfo(m_file); 0138 if (m_doc->url().isLocalFile() && !fileInfo.exists()) { 0139 clear(); 0140 KTextEditor::Message *message = new KTextEditor::Message(i18nc("short translation, user created new file", "New file"), KTextEditor::Message::Warning); 0141 message->setPosition(KTextEditor::Message::TopInView); 0142 message->setAutoHide(1000); 0143 m_doc->postMessage(message); 0144 0145 // remember error 0146 m_doc->m_openingError = true; 0147 m_doc->m_openingErrorMessage = i18n("The file %1 does not exist.", m_doc->url().toString()); 0148 return true; 0149 } 0150 0151 // check if this is a normal file or not, avoids to open char devices or directories! 0152 // else clear buffer and break out with error 0153 if (!fileInfo.isFile()) { 0154 clear(); 0155 return false; 0156 } 0157 0158 // try to load 0159 if (!load(m_file, m_brokenEncoding, m_tooLongLinesWrapped, m_longestLineLoaded, enforceTextCodec)) { 0160 return false; 0161 } 0162 0163 // save back encoding 0164 m_doc->config()->setEncoding(QString::fromLatin1(textCodec()->name())); 0165 0166 // set eol mode, if a eol char was found 0167 if (m_doc->config()->allowEolDetection()) { 0168 m_doc->config()->setEol(endOfLineMode()); 0169 } 0170 0171 // generate a bom? 0172 if (generateByteOrderMark()) { 0173 m_doc->config()->setBom(true); 0174 } 0175 0176 // okay, loading did work 0177 return true; 0178 } 0179 0180 bool KateBuffer::canEncode() 0181 { 0182 QTextCodec *codec = m_doc->config()->codec(); 0183 0184 // hardcode some Unicode encodings which can encode all chars 0185 if ((QString::fromLatin1(codec->name()) == QLatin1String("UTF-8")) || (QString::fromLatin1(codec->name()) == QLatin1String("ISO-10646-UCS-2"))) { 0186 return true; 0187 } 0188 0189 for (int i = 0; i < lines(); i++) { 0190 if (!codec->canEncode(line(i)->text())) { 0191 qCDebug(LOG_KTE) << QLatin1String("ENC NAME: ") << codec->name(); 0192 qCDebug(LOG_KTE) << QLatin1String("STRING LINE: ") << line(i)->text(); 0193 qCDebug(LOG_KTE) << QLatin1String("ENC WORKING: FALSE"); 0194 0195 return false; 0196 } 0197 } 0198 0199 return true; 0200 } 0201 0202 bool KateBuffer::saveFile(const QString &m_file) 0203 { 0204 // first: setup fallback and normal encoding 0205 setEncodingProberType(KateGlobalConfig::global()->proberType()); 0206 setFallbackTextCodec(KateGlobalConfig::global()->fallbackCodec()); 0207 setTextCodec(m_doc->config()->codec()); 0208 0209 // setup eol 0210 setEndOfLineMode((EndOfLineMode)m_doc->config()->eol()); 0211 0212 // generate bom? 0213 setGenerateByteOrderMark(m_doc->config()->bom()); 0214 0215 // try to save 0216 if (!save(m_file)) { 0217 return false; 0218 } 0219 0220 // no longer broken encoding, or we don't care 0221 m_brokenEncoding = false; 0222 m_tooLongLinesWrapped = false; 0223 m_longestLineLoaded = 0; 0224 0225 // okay 0226 return true; 0227 } 0228 0229 void KateBuffer::ensureHighlighted(int line, int lookAhead) 0230 { 0231 // valid line at all? 0232 if (line < 0 || line >= lines()) { 0233 return; 0234 } 0235 0236 // already hl up-to-date for this line? 0237 if (line < m_lineHighlighted) { 0238 return; 0239 } 0240 0241 // update hl until this line + max lookAhead 0242 int end = qMin(line + lookAhead, lines() - 1); 0243 0244 // ensure we have enough highlighted 0245 doHighlight(m_lineHighlighted, end, false); 0246 } 0247 0248 void KateBuffer::wrapLine(const KTextEditor::Cursor position) 0249 { 0250 // call original 0251 Kate::TextBuffer::wrapLine(position); 0252 0253 if (m_lineHighlighted > position.line() + 1) { 0254 m_lineHighlighted++; 0255 } 0256 } 0257 0258 void KateBuffer::unwrapLine(int line) 0259 { 0260 // reimplemented, so first call original 0261 Kate::TextBuffer::unwrapLine(line); 0262 0263 if (m_lineHighlighted > line) { 0264 --m_lineHighlighted; 0265 } 0266 } 0267 0268 void KateBuffer::setTabWidth(int w) 0269 { 0270 if ((m_tabWidth != w) && (m_tabWidth > 0)) { 0271 m_tabWidth = w; 0272 0273 if (m_highlight && m_highlight->foldingIndentationSensitive()) { 0274 invalidateHighlighting(); 0275 } 0276 } 0277 } 0278 0279 void KateBuffer::setHighlight(int hlMode) 0280 { 0281 KateHighlighting *h = KateHlManager::self()->getHl(hlMode); 0282 0283 // aha, hl will change 0284 if (h != m_highlight) { 0285 bool invalidate = !h->noHighlighting(); 0286 0287 if (m_highlight) { 0288 invalidate = true; 0289 } 0290 0291 m_highlight = h; 0292 0293 if (invalidate) { 0294 invalidateHighlighting(); 0295 } 0296 0297 // inform the document that the hl was really changed 0298 // needed to update attributes and more ;) 0299 m_doc->bufferHlChanged(); 0300 0301 // try to set indentation 0302 if (!h->indentation().isEmpty()) { 0303 m_doc->config()->setIndentationMode(h->indentation()); 0304 } 0305 } 0306 } 0307 0308 void KateBuffer::invalidateHighlighting() 0309 { 0310 m_lineHighlighted = 0; 0311 } 0312 0313 void KateBuffer::doHighlight(int startLine, int endLine, bool invalidate) 0314 { 0315 // no hl around, no stuff to do 0316 if (!m_highlight || m_highlight->noHighlighting()) { 0317 return; 0318 } 0319 0320 #ifdef BUFFER_DEBUGGING 0321 QTime t; 0322 t.start(); 0323 qCDebug(LOG_KTE) << "HIGHLIGHTED START --- NEED HL, LINESTART: " << startLine << " LINEEND: " << endLine; 0324 qCDebug(LOG_KTE) << "HL UNTIL LINE: " << m_lineHighlighted; 0325 #endif 0326 0327 // if possible get previous line, otherwise create 0 line. 0328 Kate::TextLine prevLine = (startLine >= 1) ? plainLine(startLine - 1) : Kate::TextLine(); 0329 0330 // here we are atm, start at start line in the block 0331 int current_line = startLine; 0332 int start_spellchecking = -1; 0333 int last_line_spellchecking = -1; 0334 bool ctxChanged = false; 0335 // loop over the lines of the block, from startline to endline or end of block 0336 // if stillcontinue forces us to do so 0337 for (; current_line < qMin(endLine + 1, lines()); ++current_line) { 0338 // handle one line 0339 ctxChanged = false; 0340 Kate::TextLine textLine = plainLine(current_line); 0341 m_highlight->doHighlight(prevLine.get(), textLine.get(), ctxChanged); 0342 prevLine = textLine; 0343 0344 #ifdef BUFFER_DEBUGGING 0345 // debug stuff 0346 qCDebug(LOG_KTE) << "current line to hl: " << current_line; 0347 qCDebug(LOG_KTE) << "text length: " << textLine->length() << " attribute list size: " << textLine->attributesList().size(); 0348 0349 const QVector<int> &ml(textLine->attributesList()); 0350 for (int i = 2; i < ml.size(); i += 3) { 0351 qCDebug(LOG_KTE) << "start: " << ml.at(i - 2) << " len: " << ml.at(i - 1) << " at: " << ml.at(i) << " "; 0352 } 0353 qCDebug(LOG_KTE); 0354 #endif 0355 0356 // need we to continue ? 0357 bool stillcontinue = ctxChanged; 0358 if (stillcontinue && start_spellchecking < 0) { 0359 start_spellchecking = current_line; 0360 } else if (!stillcontinue && start_spellchecking >= 0) { 0361 last_line_spellchecking = current_line; 0362 } 0363 } 0364 0365 // perhaps we need to adjust the maximal highlighted line 0366 int oldHighlighted = m_lineHighlighted; 0367 if (ctxChanged || current_line > m_lineHighlighted) { 0368 m_lineHighlighted = current_line; 0369 } 0370 0371 // tag the changed lines ! 0372 if (invalidate) { 0373 #ifdef BUFFER_DEBUGGING 0374 qCDebug(LOG_KTE) << "HIGHLIGHTED TAG LINES: " << startLine << current_line; 0375 #endif 0376 0377 Q_EMIT tagLines({startLine, qMax(current_line, oldHighlighted)}); 0378 0379 if (start_spellchecking >= 0 && lines() > 0) { 0380 Q_EMIT respellCheckBlock(start_spellchecking, 0381 qMin(lines() - 1, (last_line_spellchecking == -1) ? qMax(current_line, oldHighlighted) : last_line_spellchecking)); 0382 } 0383 } 0384 0385 #ifdef BUFFER_DEBUGGING 0386 qCDebug(LOG_KTE) << "HIGHLIGHTED END --- NEED HL, LINESTART: " << startLine << " LINEEND: " << endLine; 0387 qCDebug(LOG_KTE) << "HL UNTIL LINE: " << m_lineHighlighted; 0388 qCDebug(LOG_KTE) << "HL DYN COUNT: " << KateHlManager::self()->countDynamicCtxs() << " MAX: " << m_maxDynamicContexts; 0389 qCDebug(LOG_KTE) << "TIME TAKEN: " << t.elapsed(); 0390 #endif 0391 } 0392 0393 std::pair<bool, bool> KateBuffer::isFoldingStartingOnLine(int startLine) 0394 { 0395 // ensure valid input 0396 if (startLine < 0 || startLine >= lines()) { 0397 return {false, false}; 0398 } 0399 0400 // no highlighting, no folding, ATM 0401 if (!m_highlight || m_highlight->noHighlighting()) { 0402 return {false, false}; 0403 } 0404 0405 // first: get the wanted start line highlighted 0406 ensureHighlighted(startLine); 0407 const auto startTextLine = plainLine(startLine); 0408 0409 // we prefer token based folding 0410 if (startTextLine->markedAsFoldingStartAttribute()) { 0411 return {true, false}; 0412 } 0413 0414 // check for indentation based folding 0415 if (m_highlight->foldingIndentationSensitive() && (tabWidth() > 0) && startTextLine->highlightingState().indentationBasedFoldingEnabled() 0416 && !m_highlight->isEmptyLine(startTextLine.get())) { 0417 // do some look ahead if this line might be a folding start 0418 // we limit this to avoid runtime disaster 0419 int linesVisited = 0; 0420 while (const auto nextLine = plainLine(++startLine)) { 0421 if (!m_highlight->isEmptyLine(nextLine.get())) { 0422 const bool foldingStart = startTextLine->indentDepth(tabWidth()) < nextLine->indentDepth(tabWidth()); 0423 return {foldingStart, foldingStart}; 0424 } 0425 0426 // ensure some sensible limit of look ahead 0427 constexpr int lookAheadLimit = 64; 0428 if (++linesVisited > lookAheadLimit) { 0429 break; 0430 } 0431 } 0432 } 0433 0434 // no folding start of any kind 0435 return {false, false}; 0436 } 0437 0438 KTextEditor::Range KateBuffer::computeFoldingRangeForStartLine(int startLine) 0439 { 0440 // check for start, will trigger highlighting, too, and rule out bad lines 0441 const auto [foldingStart, foldingIndentationSensitive] = isFoldingStartingOnLine(startLine); 0442 if (!foldingStart) { 0443 return KTextEditor::Range::invalid(); 0444 } 0445 const auto startTextLine = plainLine(startLine); 0446 0447 // now: decided if indentation based folding or not! 0448 if (foldingIndentationSensitive) { 0449 // get our start indentation level 0450 const int startIndentation = startTextLine->indentDepth(tabWidth()); 0451 0452 // search next line with indentation level <= our one 0453 int lastLine = startLine + 1; 0454 for (; lastLine < lines(); ++lastLine) { 0455 // get line 0456 Kate::TextLine textLine = plainLine(lastLine); 0457 0458 // indentation higher than our start line? continue 0459 if (startIndentation < textLine->indentDepth(tabWidth())) { 0460 continue; 0461 } 0462 0463 // empty line? continue 0464 if (m_highlight->isEmptyLine(textLine.get())) { 0465 continue; 0466 } 0467 0468 // else, break 0469 break; 0470 } 0471 0472 // lastLine is always one too much 0473 --lastLine; 0474 0475 // backtrack all empty lines, we don't want to add them to the fold! 0476 while (lastLine > startLine) { 0477 if (m_highlight->isEmptyLine(plainLine(lastLine).get())) { 0478 --lastLine; 0479 } else { 0480 break; 0481 } 0482 } 0483 0484 // we shall not fold one-liners 0485 if (lastLine == startLine) { 0486 return KTextEditor::Range::invalid(); 0487 } 0488 0489 // be done now 0490 return KTextEditor::Range(KTextEditor::Cursor(startLine, 0), KTextEditor::Cursor(lastLine, plainLine(lastLine)->length())); 0491 } 0492 0493 // 'normal' attribute based folding, aka token based like '{' BLUB '}' 0494 0495 // first step: search the first region type, that stays open for the start line 0496 short openedRegionType = 0; 0497 int openedRegionOffset = -1; 0498 { 0499 // mapping of type to "first" offset of it and current number of not matched openings 0500 QHash<short, QPair<int, int>> foldingStartToOffsetAndCount; 0501 0502 // walk over all attributes of the line and compute the matchings 0503 const auto &startLineAttributes = startTextLine->foldings(); 0504 for (size_t i = 0; i < startLineAttributes.size(); ++i) { 0505 // folding close? 0506 if (startLineAttributes[i].foldingValue < 0) { 0507 // search for this type, try to decrement counter, perhaps erase element! 0508 QHash<short, QPair<int, int>>::iterator end = foldingStartToOffsetAndCount.find(-startLineAttributes[i].foldingValue); 0509 if (end != foldingStartToOffsetAndCount.end()) { 0510 if (end.value().second > 1) { 0511 --(end.value().second); 0512 } else { 0513 foldingStartToOffsetAndCount.erase(end); 0514 } 0515 } 0516 } 0517 0518 // folding open? 0519 if (startLineAttributes[i].foldingValue > 0) { 0520 // search for this type, either insert it, with current offset or increment counter! 0521 QHash<short, QPair<int, int>>::iterator start = foldingStartToOffsetAndCount.find(startLineAttributes[i].foldingValue); 0522 if (start != foldingStartToOffsetAndCount.end()) { 0523 ++(start.value().second); 0524 } else { 0525 foldingStartToOffsetAndCount.insert(startLineAttributes[i].foldingValue, qMakePair(startLineAttributes[i].offset, 1)); 0526 } 0527 } 0528 } 0529 0530 // compute first type with offset 0531 QHashIterator<short, QPair<int, int>> hashIt(foldingStartToOffsetAndCount); 0532 while (hashIt.hasNext()) { 0533 hashIt.next(); 0534 if (openedRegionOffset == -1 || hashIt.value().first < openedRegionOffset) { 0535 openedRegionType = hashIt.key(); 0536 openedRegionOffset = hashIt.value().first; 0537 } 0538 } 0539 } 0540 0541 // no opening region found, bad, nothing to do 0542 if (openedRegionType == 0) { 0543 return KTextEditor::Range::invalid(); 0544 } 0545 0546 // second step: search for matching end region marker! 0547 int countOfOpenRegions = 1; 0548 for (int line = startLine + 1; line < lines(); ++line) { 0549 // ensure line is highlighted 0550 ensureHighlighted(line); 0551 Kate::TextLine textLine = plainLine(line); 0552 0553 // search for matching end marker 0554 const auto &lineAttributes = textLine->foldings(); 0555 for (size_t i = 0; i < lineAttributes.size(); ++i) { 0556 // matching folding close? 0557 if (lineAttributes[i].foldingValue == -openedRegionType) { 0558 --countOfOpenRegions; 0559 0560 // end reached? 0561 // compute resulting range! 0562 if (countOfOpenRegions == 0) { 0563 // Don't return a valid range without content! 0564 if (line - startLine == 1) { 0565 return KTextEditor::Range::invalid(); 0566 } 0567 0568 // return computed range 0569 return KTextEditor::Range(KTextEditor::Cursor(startLine, openedRegionOffset), KTextEditor::Cursor(line, lineAttributes[i].offset)); 0570 } 0571 } 0572 0573 // matching folding open? 0574 if (lineAttributes[i].foldingValue == openedRegionType) { 0575 ++countOfOpenRegions; 0576 } 0577 } 0578 } 0579 0580 // if we arrive here, the opened range spans to the end of the document! 0581 return KTextEditor::Range(KTextEditor::Cursor(startLine, openedRegionOffset), KTextEditor::Cursor(lines() - 1, plainLine(lines() - 1)->length())); 0582 } 0583 0584 #include "moc_katebuffer.cpp"