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