1 | /* |
2 | SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org> |
3 | SPDX-FileCopyrightText: 2002-2004 Christoph Cullmann <cullmann@kde.org> |
4 | SPDX-FileCopyrightText: 2007 Mirko Stocker <me@misto.ch> |
5 | |
6 | SPDX-License-Identifier: LGPL-2.0-or-later |
7 | */ |
8 | |
9 | #include "katebuffer.h" |
10 | #include "kateautoindent.h" |
11 | #include "kateconfig.h" |
12 | #include "katedocument.h" |
13 | #include "kateglobal.h" |
14 | #include "katehighlight.h" |
15 | #include "katepartdebug.h" |
16 | #include "katesyntaxmanager.h" |
17 | #include "ktexteditor/message.h" |
18 | |
19 | #include <KEncodingProber> |
20 | #include <KLocalizedString> |
21 | |
22 | #include <QDate> |
23 | #include <QFile> |
24 | #include <QFileInfo> |
25 | #include <QStringEncoder> |
26 | #include <QTextStream> |
27 | |
28 | /** |
29 | * Create an empty buffer. (with one block with one empty line) |
30 | */ |
31 | KateBuffer::KateBuffer(KTextEditor::DocumentPrivate *doc) |
32 | : Kate::TextBuffer(doc) |
33 | , m_doc(doc) |
34 | , m_brokenEncoding(false) |
35 | , m_tooLongLinesWrapped(false) |
36 | , m_longestLineLoaded(0) |
37 | , m_highlight(nullptr) |
38 | , m_tabWidth(8) |
39 | , m_lineHighlighted(0) |
40 | { |
41 | } |
42 | |
43 | /** |
44 | * Cleanup on destruction |
45 | */ |
46 | KateBuffer::~KateBuffer() = default; |
47 | |
48 | void KateBuffer::editStart() |
49 | { |
50 | if (!startEditing()) { |
51 | return; |
52 | } |
53 | } |
54 | |
55 | void KateBuffer::editEnd() |
56 | { |
57 | // not finished, do nothing |
58 | if (!finishEditing()) { |
59 | return; |
60 | } |
61 | |
62 | // nothing change, OK |
63 | if (!editingChangedBuffer()) { |
64 | return; |
65 | } |
66 | |
67 | // if we arrive here, line changed should be OK |
68 | Q_ASSERT(editingMinimalLineChanged() != -1); |
69 | Q_ASSERT(editingMaximalLineChanged() != -1); |
70 | Q_ASSERT(editingMinimalLineChanged() <= editingMaximalLineChanged()); |
71 | |
72 | updateHighlighting(); |
73 | } |
74 | |
75 | void KateBuffer::updateHighlighting() |
76 | { |
77 | // no highlighting, nothing to do |
78 | if (!m_highlight) { |
79 | return; |
80 | } |
81 | |
82 | // if we don't touch the highlighted area => fine |
83 | if (editingMinimalLineChanged() > m_lineHighlighted) { |
84 | return; |
85 | } |
86 | |
87 | // really update highlighting |
88 | // look one line too far, needed for linecontinue stuff |
89 | doHighlight(from: editingMinimalLineChanged(), to: editingMaximalLineChanged() + 1, invalidate: true); |
90 | } |
91 | |
92 | void KateBuffer::clear() |
93 | { |
94 | // call original clear function |
95 | Kate::TextBuffer::clear(); |
96 | |
97 | // reset the state |
98 | m_brokenEncoding = false; |
99 | m_tooLongLinesWrapped = false; |
100 | m_longestLineLoaded = 0; |
101 | |
102 | // back to line 0 with hl |
103 | m_lineHighlighted = 0; |
104 | } |
105 | |
106 | bool KateBuffer::openFile(const QString &m_file, bool enforceTextCodec) |
107 | { |
108 | // first: setup fallback and normal encoding |
109 | const auto proberType = (KEncodingProber::ProberType)KateGlobalConfig::global()->value(key: KateGlobalConfig::EncodingProberType).toInt(); |
110 | setEncodingProberType(proberType); |
111 | setFallbackTextCodec(KateGlobalConfig::global()->fallbackEncoding()); |
112 | setTextCodec(m_doc->config()->encoding()); |
113 | |
114 | // setup eol |
115 | setEndOfLineMode((EndOfLineMode)m_doc->config()->eol()); |
116 | |
117 | // NOTE: we do not remove trailing spaces on load. This was discussed |
118 | // over the years again and again. bugs: 306926, 239077, ... |
119 | |
120 | // line length limit |
121 | setLineLengthLimit(m_doc->lineLengthLimit()); |
122 | |
123 | // then, try to load the file |
124 | m_brokenEncoding = false; |
125 | m_tooLongLinesWrapped = false; |
126 | m_longestLineLoaded = 0; |
127 | |
128 | // allow non-existent files without error, if local file! |
129 | // will allow to do "kate newfile.txt" without error messages but still fail if e.g. you mistype a url |
130 | // and it can't be fetched via fish:// or other strange things in kio happen... |
131 | // just clear() + exit with success! |
132 | |
133 | QFileInfo fileInfo(m_file); |
134 | if (m_doc->url().isLocalFile() && !fileInfo.exists()) { |
135 | clear(); |
136 | KTextEditor::Message *message = new KTextEditor::Message(i18nc("short translation, user created new file" , "New file" ), KTextEditor::Message::Warning); |
137 | message->setPosition(KTextEditor::Message::TopInView); |
138 | message->setAutoHide(1000); |
139 | m_doc->postMessage(message); |
140 | |
141 | // remember error |
142 | m_doc->m_openingError = true; |
143 | return true; |
144 | } |
145 | |
146 | // check if this is a normal file or not, avoids to open char devices or directories! |
147 | // else clear buffer and break out with error |
148 | if (!fileInfo.isFile()) { |
149 | clear(); |
150 | return false; |
151 | } |
152 | |
153 | // try to load |
154 | if (!load(filename: m_file, encodingErrors&: m_brokenEncoding, tooLongLinesWrapped&: m_tooLongLinesWrapped, longestLineLoaded&: m_longestLineLoaded, enforceTextCodec)) { |
155 | return false; |
156 | } |
157 | |
158 | // save back encoding |
159 | m_doc->config()->setEncoding(textCodec()); |
160 | |
161 | // set eol mode, if a eol char was found |
162 | if (m_doc->config()->allowEolDetection()) { |
163 | m_doc->config()->setEol(endOfLineMode()); |
164 | } |
165 | |
166 | // generate a bom? |
167 | if (generateByteOrderMark()) { |
168 | m_doc->config()->setBom(true); |
169 | } |
170 | |
171 | // okay, loading did work |
172 | return true; |
173 | } |
174 | |
175 | bool KateBuffer::canEncode() |
176 | { |
177 | // hardcode some Unicode encodings which can encode all chars |
178 | if (const auto setEncoding = QStringConverter::encodingForName(name: m_doc->config()->encoding().toUtf8().constData())) { |
179 | for (const auto encoding : {QStringConverter::Utf8, |
180 | QStringConverter::Utf16, |
181 | QStringConverter::Utf16BE, |
182 | QStringConverter::Utf16LE, |
183 | QStringConverter::Utf32, |
184 | QStringConverter::Utf32BE, |
185 | QStringConverter::Utf32LE}) { |
186 | if (setEncoding == encoding) { |
187 | return true; |
188 | } |
189 | } |
190 | } |
191 | |
192 | QStringEncoder encoder(m_doc->config()->encoding().toUtf8().constData()); |
193 | for (int i = 0; i < lines(); i++) { |
194 | { |
195 | // actual encoding happens not during the call to encode() but |
196 | // during the conversion to QByteArray, so we need to force it |
197 | QByteArray result = encoder.encode(str: line(line: i).text()); |
198 | Q_UNUSED(result); |
199 | } |
200 | if (encoder.hasError()) { |
201 | qCDebug(LOG_KTE) << QLatin1String("ENC NAME: " ) << m_doc->config()->encoding(); |
202 | qCDebug(LOG_KTE) << QLatin1String("STRING LINE: " ) << line(line: i).text(); |
203 | qCDebug(LOG_KTE) << QLatin1String("ENC WORKING: FALSE" ); |
204 | |
205 | return false; |
206 | } |
207 | } |
208 | |
209 | return true; |
210 | } |
211 | |
212 | bool KateBuffer::saveFile(const QString &m_file) |
213 | { |
214 | // first: setup fallback and normal encoding |
215 | const auto proberType = (KEncodingProber::ProberType)KateGlobalConfig::global()->value(key: KateGlobalConfig::EncodingProberType).toInt(); |
216 | setEncodingProberType(proberType); |
217 | setFallbackTextCodec(KateGlobalConfig::global()->fallbackEncoding()); |
218 | setTextCodec(m_doc->config()->encoding()); |
219 | |
220 | // setup eol |
221 | setEndOfLineMode((EndOfLineMode)m_doc->config()->eol()); |
222 | |
223 | // generate bom? |
224 | setGenerateByteOrderMark(m_doc->config()->bom()); |
225 | |
226 | // try to save |
227 | if (!save(filename: m_file)) { |
228 | return false; |
229 | } |
230 | |
231 | // no longer broken encoding, or we don't care |
232 | m_brokenEncoding = false; |
233 | m_tooLongLinesWrapped = false; |
234 | m_longestLineLoaded = 0; |
235 | |
236 | // okay |
237 | return true; |
238 | } |
239 | |
240 | void KateBuffer::ensureHighlighted(int line, int lookAhead) |
241 | { |
242 | // valid line at all? |
243 | if (line < 0 || line >= lines()) { |
244 | return; |
245 | } |
246 | |
247 | // already hl up-to-date for this line? |
248 | if (line < m_lineHighlighted) { |
249 | return; |
250 | } |
251 | |
252 | // update hl until this line + max lookAhead |
253 | int end = qMin(a: line + lookAhead, b: lines() - 1); |
254 | |
255 | // ensure we have enough highlighted |
256 | doHighlight(from: m_lineHighlighted, to: end, invalidate: false); |
257 | } |
258 | |
259 | void KateBuffer::wrapLine(const KTextEditor::Cursor position) |
260 | { |
261 | // call original |
262 | Kate::TextBuffer::wrapLine(position); |
263 | |
264 | if (m_lineHighlighted > position.line() + 1) { |
265 | m_lineHighlighted++; |
266 | } |
267 | } |
268 | |
269 | void KateBuffer::unwrapLine(int line) |
270 | { |
271 | // reimplemented, so first call original |
272 | Kate::TextBuffer::unwrapLine(line); |
273 | |
274 | if (m_lineHighlighted > line) { |
275 | --m_lineHighlighted; |
276 | } |
277 | } |
278 | |
279 | void KateBuffer::setTabWidth(int w) |
280 | { |
281 | if ((m_tabWidth != w) && (m_tabWidth > 0)) { |
282 | m_tabWidth = w; |
283 | |
284 | if (m_highlight && m_highlight->foldingIndentationSensitive()) { |
285 | invalidateHighlighting(); |
286 | } |
287 | } |
288 | } |
289 | |
290 | void KateBuffer::setHighlight(int hlMode) |
291 | { |
292 | KateHighlighting *h = KateHlManager::self()->getHl(n: hlMode); |
293 | |
294 | // aha, hl will change |
295 | if (h != m_highlight) { |
296 | bool invalidate = !h->noHighlighting(); |
297 | |
298 | if (m_highlight) { |
299 | invalidate = true; |
300 | } |
301 | |
302 | m_highlight = h; |
303 | |
304 | if (invalidate) { |
305 | invalidateHighlighting(); |
306 | } |
307 | |
308 | // inform the document that the hl was really changed |
309 | // needed to update attributes and more ;) |
310 | m_doc->bufferHlChanged(); |
311 | |
312 | // try to set indentation |
313 | if (!h->indentation().isEmpty()) { |
314 | m_doc->config()->setIndentationMode(h->indentation()); |
315 | } |
316 | } |
317 | } |
318 | |
319 | void KateBuffer::invalidateHighlighting() |
320 | { |
321 | m_lineHighlighted = 0; |
322 | } |
323 | |
324 | void KateBuffer::doHighlight(int startLine, int endLine, bool invalidate) |
325 | { |
326 | // no hl around, no stuff to do |
327 | if (!m_highlight || m_highlight->noHighlighting()) { |
328 | return; |
329 | } |
330 | |
331 | #ifdef BUFFER_DEBUGGING |
332 | QTime t; |
333 | t.start(); |
334 | qCDebug(LOG_KTE) << "HIGHLIGHTED START --- NEED HL, LINESTART: " << startLine << " LINEEND: " << endLine; |
335 | qCDebug(LOG_KTE) << "HL UNTIL LINE: " << m_lineHighlighted; |
336 | #endif |
337 | |
338 | // if possible get previous line, otherwise create 0 line. |
339 | Kate::TextLine prevLine = (startLine >= 1) ? plainLine(lineno: startLine - 1) : Kate::TextLine(); |
340 | |
341 | // here we are atm, start at start line in the block |
342 | int current_line = startLine; |
343 | int start_spellchecking = -1; |
344 | int last_line_spellchecking = -1; |
345 | bool ctxChanged = false; |
346 | // loop over the lines of the block, from startline to endline or end of block |
347 | // if stillcontinue forces us to do so |
348 | for (; current_line < qMin(a: endLine + 1, b: lines()); ++current_line) { |
349 | // handle one line |
350 | ctxChanged = false; |
351 | Kate::TextLine textLine = plainLine(lineno: current_line); |
352 | m_highlight->doHighlight(prevLine: (current_line >= 1) ? &prevLine : nullptr, textLine: &textLine, ctxChanged); |
353 | prevLine = textLine; |
354 | |
355 | // write back the computed info to the textline stored in the buffer |
356 | setLineMetaData(line: current_line, textLine); |
357 | |
358 | #ifdef BUFFER_DEBUGGING |
359 | // debug stuff |
360 | qCDebug(LOG_KTE) << "current line to hl: " << current_line; |
361 | qCDebug(LOG_KTE) << "text length: " << textLine->length() << " attribute list size: " << textLine->attributesList().size(); |
362 | |
363 | const QList<int> &ml(textLine->attributesList()); |
364 | for (int i = 2; i < ml.size(); i += 3) { |
365 | qCDebug(LOG_KTE) << "start: " << ml.at(i - 2) << " len: " << ml.at(i - 1) << " at: " << ml.at(i) << " " ; |
366 | } |
367 | qCDebug(LOG_KTE); |
368 | #endif |
369 | |
370 | // need we to continue ? |
371 | bool stillcontinue = ctxChanged; |
372 | if (stillcontinue && start_spellchecking < 0) { |
373 | start_spellchecking = current_line; |
374 | } else if (!stillcontinue && start_spellchecking >= 0) { |
375 | last_line_spellchecking = current_line; |
376 | } |
377 | } |
378 | |
379 | // perhaps we need to adjust the maximal highlighted line |
380 | int oldHighlighted = m_lineHighlighted; |
381 | if (ctxChanged || current_line > m_lineHighlighted) { |
382 | m_lineHighlighted = current_line; |
383 | } |
384 | |
385 | // tag the changed lines ! |
386 | if (invalidate) { |
387 | #ifdef BUFFER_DEBUGGING |
388 | qCDebug(LOG_KTE) << "HIGHLIGHTED TAG LINES: " << startLine << current_line; |
389 | #endif |
390 | |
391 | Q_EMIT tagLines(lineRange: {startLine, qMax(a: current_line, b: oldHighlighted)}); |
392 | |
393 | if (start_spellchecking >= 0 && lines() > 0) { |
394 | Q_EMIT respellCheckBlock(start: start_spellchecking, |
395 | end: qMin(a: lines() - 1, b: (last_line_spellchecking == -1) ? qMax(a: current_line, b: oldHighlighted) : last_line_spellchecking)); |
396 | } |
397 | } |
398 | |
399 | #ifdef BUFFER_DEBUGGING |
400 | qCDebug(LOG_KTE) << "HIGHLIGHTED END --- NEED HL, LINESTART: " << startLine << " LINEEND: " << endLine; |
401 | qCDebug(LOG_KTE) << "HL UNTIL LINE: " << m_lineHighlighted; |
402 | qCDebug(LOG_KTE) << "HL DYN COUNT: " << KateHlManager::self()->countDynamicCtxs() << " MAX: " << m_maxDynamicContexts; |
403 | qCDebug(LOG_KTE) << "TIME TAKEN: " << t.elapsed(); |
404 | #endif |
405 | } |
406 | |
407 | KateHighlighting::Foldings KateBuffer::computeFoldings(int line) |
408 | { |
409 | // no hightlighting, no work |
410 | KateHighlighting::Foldings foldings; |
411 | if (!m_highlight || m_highlight->noHighlighting()) { |
412 | return foldings; |
413 | } |
414 | |
415 | // ensure we did highlight at least until the previous line |
416 | if (line > 0) { |
417 | ensureHighlighted(line: line - 1, lookAhead: 0); |
418 | } |
419 | |
420 | // highlight the given line with passed foldings vector to fill |
421 | Kate::TextLine prevLine = (line >= 1) ? plainLine(lineno: line - 1) : Kate::TextLine(); |
422 | Kate::TextLine textLine = plainLine(lineno: line); |
423 | bool ctxChanged = false; |
424 | m_highlight->doHighlight(prevLine: (line >= 1) ? &prevLine : nullptr, textLine: &textLine, ctxChanged, foldings: &foldings); |
425 | return foldings; |
426 | } |
427 | |
428 | std::pair<bool, bool> KateBuffer::isFoldingStartingOnLine(int startLine) |
429 | { |
430 | // ensure valid input |
431 | if (startLine < 0 || startLine >= lines()) { |
432 | return {false, false}; |
433 | } |
434 | |
435 | // no highlighting, no folding, ATM |
436 | if (!m_highlight || m_highlight->noHighlighting()) { |
437 | return {false, false}; |
438 | } |
439 | |
440 | // first: get the wanted start line highlighted |
441 | ensureHighlighted(line: startLine); |
442 | const auto startTextLine = plainLine(lineno: startLine); |
443 | |
444 | // we prefer token based folding |
445 | if (startTextLine.markedAsFoldingStartAttribute()) { |
446 | return {true, false}; |
447 | } |
448 | |
449 | // check for indentation based folding |
450 | if (m_highlight->foldingIndentationSensitive() && (tabWidth() > 0) && startTextLine.highlightingState().indentationBasedFoldingEnabled() |
451 | && !m_highlight->isEmptyLine(textline: &startTextLine)) { |
452 | // do some look ahead if this line might be a folding start |
453 | // we limit this to avoid runtime disaster |
454 | int linesVisited = 0; |
455 | while (startLine + 1 < lines()) { |
456 | const auto nextLine = plainLine(lineno: ++startLine); |
457 | if (!m_highlight->isEmptyLine(textline: &nextLine)) { |
458 | const bool foldingStart = startTextLine.indentDepth(tabWidth: tabWidth()) < nextLine.indentDepth(tabWidth: tabWidth()); |
459 | return {foldingStart, foldingStart}; |
460 | } |
461 | |
462 | // ensure some sensible limit of look ahead |
463 | constexpr int lookAheadLimit = 64; |
464 | if (++linesVisited > lookAheadLimit) { |
465 | break; |
466 | } |
467 | } |
468 | } |
469 | |
470 | // no folding start of any kind |
471 | return {false, false}; |
472 | } |
473 | |
474 | KTextEditor::Range KateBuffer::computeFoldingRangeForStartLine(int startLine) |
475 | { |
476 | // check for start, will trigger highlighting, too, and rule out bad lines |
477 | const auto [foldingStart, foldingIndentationSensitive] = isFoldingStartingOnLine(startLine); |
478 | if (!foldingStart) { |
479 | return KTextEditor::Range::invalid(); |
480 | } |
481 | |
482 | // now: decided if indentation based folding or not! |
483 | if (foldingIndentationSensitive) { |
484 | // get our start indentation level |
485 | const auto startTextLine = plainLine(lineno: startLine); |
486 | const int startIndentation = startTextLine.indentDepth(tabWidth: tabWidth()); |
487 | |
488 | // search next line with indentation level <= our one |
489 | int lastLine = startLine + 1; |
490 | for (; lastLine < lines(); ++lastLine) { |
491 | // get line |
492 | Kate::TextLine textLine = plainLine(lineno: lastLine); |
493 | |
494 | // indentation higher than our start line? continue |
495 | if (startIndentation < textLine.indentDepth(tabWidth: tabWidth())) { |
496 | continue; |
497 | } |
498 | |
499 | // empty line? continue |
500 | if (m_highlight->isEmptyLine(textline: &textLine)) { |
501 | continue; |
502 | } |
503 | |
504 | // else, break |
505 | break; |
506 | } |
507 | |
508 | // lastLine is always one too much |
509 | --lastLine; |
510 | |
511 | // backtrack all empty lines, we don't want to add them to the fold! |
512 | while (lastLine > startLine) { |
513 | const auto l = plainLine(lineno: lastLine); |
514 | if (m_highlight->isEmptyLine(textline: &l)) { |
515 | --lastLine; |
516 | } else { |
517 | break; |
518 | } |
519 | } |
520 | |
521 | // we shall not fold one-liners |
522 | if (lastLine == startLine) { |
523 | return KTextEditor::Range::invalid(); |
524 | } |
525 | |
526 | // be done now |
527 | return KTextEditor::Range(KTextEditor::Cursor(startLine, 0), KTextEditor::Cursor(lastLine, plainLine(lineno: lastLine).length())); |
528 | } |
529 | |
530 | // 'normal' attribute based folding, aka token based like '{' BLUB '}' |
531 | |
532 | // first step: search the first region type, that stays open for the start line |
533 | int openedRegionType = -1; |
534 | int openedRegionOffset = -1; |
535 | { |
536 | // mapping of type to "first" offset of it and current number of not matched openings |
537 | QHash<int, QPair<int, int>> foldingStartToOffsetAndCount; |
538 | |
539 | // walk over all attributes of the line and compute the matchings |
540 | const auto startLineAttributes = computeFoldings(line: startLine); |
541 | for (size_t i = 0; i < startLineAttributes.size(); ++i) { |
542 | // folding close? |
543 | if (startLineAttributes[i].foldingRegion.type() == KSyntaxHighlighting::FoldingRegion::End) { |
544 | // search for this type, try to decrement counter, perhaps erase element! |
545 | auto end = foldingStartToOffsetAndCount.find(key: startLineAttributes[i].foldingRegion.id()); |
546 | if (end != foldingStartToOffsetAndCount.end()) { |
547 | if (end.value().second > 1) { |
548 | --(end.value().second); |
549 | } else { |
550 | foldingStartToOffsetAndCount.erase(it: end); |
551 | } |
552 | } |
553 | } |
554 | |
555 | // folding open? |
556 | if (startLineAttributes[i].foldingRegion.type() == KSyntaxHighlighting::FoldingRegion::Begin) { |
557 | // search for this type, either insert it, with current offset or increment counter! |
558 | auto start = foldingStartToOffsetAndCount.find(key: startLineAttributes[i].foldingRegion.id()); |
559 | if (start != foldingStartToOffsetAndCount.end()) { |
560 | ++(start.value().second); |
561 | } else { |
562 | foldingStartToOffsetAndCount.insert(key: startLineAttributes[i].foldingRegion.id(), value: qMakePair(value1: startLineAttributes[i].offset, value2: 1)); |
563 | } |
564 | } |
565 | } |
566 | |
567 | // compute first type with offset |
568 | QHashIterator<int, QPair<int, int>> hashIt(foldingStartToOffsetAndCount); |
569 | while (hashIt.hasNext()) { |
570 | hashIt.next(); |
571 | if (openedRegionOffset == -1 || hashIt.value().first < openedRegionOffset) { |
572 | openedRegionType = hashIt.key(); |
573 | openedRegionOffset = hashIt.value().first; |
574 | } |
575 | } |
576 | } |
577 | |
578 | // no opening region found, bad, nothing to do |
579 | if (openedRegionType == -1) { |
580 | return KTextEditor::Range::invalid(); |
581 | } |
582 | |
583 | // second step: search for matching end region marker! |
584 | int countOfOpenRegions = 1; |
585 | for (int line = startLine + 1; line < lines(); ++line) { |
586 | // search for matching end marker |
587 | const auto lineAttributes = computeFoldings(line); |
588 | for (size_t i = 0; i < lineAttributes.size(); ++i) { |
589 | // matching folding close? |
590 | if (lineAttributes[i].foldingRegion.type() == KSyntaxHighlighting::FoldingRegion::End && lineAttributes[i].foldingRegion.id() == openedRegionType) { |
591 | --countOfOpenRegions; |
592 | |
593 | // end reached? |
594 | // compute resulting range! |
595 | if (countOfOpenRegions == 0) { |
596 | // Don't return a valid range without content! |
597 | if (line - startLine == 1) { |
598 | return KTextEditor::Range::invalid(); |
599 | } |
600 | |
601 | // return computed range |
602 | return KTextEditor::Range(KTextEditor::Cursor(startLine, openedRegionOffset), KTextEditor::Cursor(line, lineAttributes[i].offset)); |
603 | } |
604 | } |
605 | |
606 | // matching folding open? |
607 | if (lineAttributes[i].foldingRegion.type() == KSyntaxHighlighting::FoldingRegion::Begin |
608 | && lineAttributes[i].foldingRegion.id() == openedRegionType) { |
609 | ++countOfOpenRegions; |
610 | } |
611 | } |
612 | } |
613 | |
614 | // if we arrive here, the opened range spans to the end of the document! |
615 | return KTextEditor::Range(KTextEditor::Cursor(startLine, openedRegionOffset), KTextEditor::Cursor(lines() - 1, plainLine(lineno: lines() - 1).length())); |
616 | } |
617 | |
618 | #include "moc_katebuffer.cpp" |
619 | |