Index: src/view/kateviewhelpers.h =================================================================== --- src/view/kateviewhelpers.h +++ src/view/kateviewhelpers.h @@ -323,6 +323,28 @@ void leaveEvent(QEvent *event) override; void wheelEvent(QWheelEvent *e) override; + /** + * Try to unfold a folded range starting at @p line + * @return true when a range was unfolded, otherwise false + */ + bool unfoldLine(int line); + + /** + * Try to fold an unfolded range starting at @p line + * @return the new folded range on success, otherwise an unvalid range + */ + KTextEditor::Range foldLine(int line); + + /** + * Try to change all the foldings inside a folding range starting at @p line + * but not the range itself starting there. + * However, should the range itself be folded, will only the range unfolded + * and the containing ranges kept untouched. + * Should the range not contain other ranges will the range itself folded, + * @return true when any range was folded or unfolded, otherwise false + */ + bool toggleFoldingInRange(int line); + void showMarkMenu(uint line, const QPoint &pos); void hideAnnotationTooltip(); Index: src/view/kateviewhelpers.cpp =================================================================== --- src/view/kateviewhelpers.cpp +++ src/view/kateviewhelpers.cpp @@ -2440,28 +2440,12 @@ } if (area == FoldingMarkers) { - // ask the folding info for this line, if any folds are around! - QVector > startingRanges = m_view->textFolding().foldingRangesStartingOnLine(cursorOnLine); - bool anyFolded = false; - for (int i = 0; i < startingRanges.size(); ++i) - if (startingRanges[i].second & Kate::TextFolding::Folded) { - anyFolded = true; - } - - // fold or unfold all ranges, remember if any action happened! - bool actionDone = false; - for (int i = 0; i < startingRanges.size(); ++i) { - actionDone = (anyFolded ? m_view->textFolding().unfoldRange(startingRanges[i].first) : m_view->textFolding().foldRange(startingRanges[i].first)) || actionDone; - } - - // if no action done, try to fold it, create non-persistent folded range, if possible! - if (!actionDone) { - // either use the fold for this line or the range that is highlighted ATM if any! - KTextEditor::Range foldingRange = m_view->doc()->buffer().computeFoldingRangeForStartLine(cursorOnLine); - if (!foldingRange.isValid() && m_foldingRange) { - foldingRange = m_foldingRange->toRange(); - } - m_view->textFolding().newFoldingRange(foldingRange, Kate::TextFolding::Folded); + // Prefer the highlighted range over the exact clicked line + const int lineToToggle = m_foldingRange ? m_foldingRange->toRange().start().line() : cursorOnLine; + if (e->button() == Qt::LeftButton && !unfoldLine(lineToToggle)) { + foldLine(lineToToggle); + } else if (e->button() == Qt::RightButton) { + toggleFoldingInRange(lineToToggle); } delete m_foldingPreview; @@ -2482,6 +2466,83 @@ m_viewInternal->mouseReleaseEvent(&forward); } +KTextEditor::Range KateIconBorder::foldLine(int line) +{ + KTextEditor::Range foldingRange = m_view->doc()->buffer().computeFoldingRangeForStartLine(line); + if (!foldingRange.isValid()) { + return foldingRange; + } + + // Ensure not to fold the end marker to avoid a deceptive look, but only on token based folding + Kate::TextLine startTextLine = m_view->doc()->buffer().plainLine(line); + if (!startTextLine->markedAsFoldingStartIndentation()) { + const int adjustedLine = foldingRange.end().line() - 1; + foldingRange.setEnd(KTextEditor::Cursor(adjustedLine, m_view->doc()->buffer().plainLine(adjustedLine)->length())); + } + + // Don't try to fold a single line, which can happens due to adjustment above' + // FIXME Avoid to offer a such folding marker + if (!foldingRange.onSingleLine()) { + m_view->textFolding().newFoldingRange(foldingRange, Kate::TextFolding::Folded); + } + + return foldingRange; +} + +bool KateIconBorder::unfoldLine(int line) +{ + bool actionDone = false; + + // ask the folding info for this line, if any folds are around! + // auto = QVector> + auto startingRanges = m_view->textFolding().foldingRangesStartingOnLine(line); + for (int i = 0; i < startingRanges.size(); ++i) { + actionDone |= m_view->textFolding().unfoldRange(startingRanges[i].first); + } + + return actionDone; +} + +bool KateIconBorder::toggleFoldingInRange(int line) +{ + KTextEditor::Range foldingRange = m_view->doc()->buffer().computeFoldingRangeForStartLine(line); + if (!foldingRange.isValid()) { + return false; + } + + bool actionDone = false; // Track success + + // Don't be too eager but obliging! Only toggle containing ranges which are + // visible -> Be done when the range is folded + actionDone |= unfoldLine(line); + + if (!actionDone) { + // Unfold all in range, but not the range itself + for (int ln = foldingRange.start().line() + 1; ln < foldingRange.end().line(); ++ln) { + actionDone |= unfoldLine(ln); + } + } + + if (!actionDone) { + // Fold all in range, but not the range itself + for (int ln = foldingRange.start().line() + 1; ln < foldingRange.end().line(); ++ln) { + KTextEditor::Range fr = foldLine(ln); + if (fr.isValid()) { + ln = fr.end().line() - 1; + actionDone = true; + } + } + } + + if (!actionDone) { + // At this place was an unfolded range clicked which contains no "childs" + // We assume the user want to fold it by the wrong button, be obliging! + actionDone |= foldLine(line).isValid(); + } + + return actionDone; // Should now always be true, but anyway +} + void KateIconBorder::mouseDoubleClickEvent(QMouseEvent *e) { int cursorOnLine = m_viewInternal->yToKateTextLayout(e->y()).line(); Index: src/view/kateviewinternal.cpp =================================================================== --- src/view/kateviewinternal.cpp +++ src/view/kateviewinternal.cpp @@ -2646,6 +2646,14 @@ e->accept(); break; + case Qt::RightButton: + if (e->pos().x() == 0) { + // Special handling for folding by right click + placeCursor(e->pos()); + e->accept(); + } + break; + default: e->ignore(); break;