diff --git a/src/data/katepart5ui.rc b/src/data/katepart5ui.rc --- a/src/data/katepart5ui.rc +++ b/src/data/katepart5ui.rc @@ -1,5 +1,5 @@ - + &File @@ -78,8 +78,8 @@ &Code Folding - - + + diff --git a/src/document/katebuffer.h b/src/document/katebuffer.h --- a/src/document/katebuffer.h +++ b/src/document/katebuffer.h @@ -228,7 +228,8 @@ * For a given line, compute the folding range that starts there * to be used to fold e.g. from the icon border * @param startLine start line - * @return folding range starting at the given line or invalid range + * @return folding range starting at the given line or invalid range when + * there is no folding start or @p startLine is not valid */ KTextEditor::Range computeFoldingRangeForStartLine(int startLine); diff --git a/src/document/katebuffer.cpp b/src/document/katebuffer.cpp --- a/src/document/katebuffer.cpp +++ b/src/document/katebuffer.cpp @@ -454,8 +454,9 @@ /** * ensure valid input */ - Q_ASSERT(startLine >= 0); - Q_ASSERT(startLine < lines()); + if (startLine < 0 || startLine >= lines()) { + return KTextEditor::Range::invalid(); + } /** * no highlighting, no folding, ATM diff --git a/src/view/kateview.h b/src/view/kateview.h --- a/src/view/kateview.h +++ b/src/view/kateview.h @@ -203,17 +203,32 @@ public: /** - * Try to fold starting at the given line. - * This will both try to fold existing folding ranges of this line and to query the highlighting what to fold. - * @param startLine start line to fold at + * Try to fold an unfolded range starting at @p line + * @return the new folded range on success, otherwise an unvalid range */ - void foldLine(int startLine); + KTextEditor::Range foldLine(int line); /** - * Try to unfold all foldings starting at the given line. - * @param startLine start line to unfold at - */ - void unfoldLine(int startLine); + * 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 toggle the folding state of a range starting at line @p line + * @return true when the line was toggled, false when not + */ + bool toggleFoldingOfLine(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 toggleFoldingsInRange(int line); // // KTextEditor::CodeCompletionInterface2 @@ -695,8 +710,8 @@ public Q_SLOTS: void slotFoldToplevelNodes(); void slotExpandToplevelNodes(); - void slotCollapseLocal(); - void slotExpandLocal(); + void slotToggleFolding(); + void slotToggleFoldingsInRange(); private: void setupLayout(); diff --git a/src/view/kateview.cpp b/src/view/kateview.cpp --- a/src/view/kateview.cpp +++ b/src/view/kateview.cpp @@ -1194,13 +1194,13 @@ a->setText(i18n("Fold Multiline Comments")); connect(a, SIGNAL(triggered(bool)), doc()->foldingTree(), SLOT(collapseAll_dsComments())); */ - a = ac->addAction(QStringLiteral("folding_collapselocal")); - a->setText(i18n("Fold Current Node")); - connect(a, SIGNAL(triggered(bool)), SLOT(slotCollapseLocal())); + a = ac->addAction(QStringLiteral("folding_toggle_current")); + a->setText(i18n("Toggle Current Node")); + connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::slotToggleFolding); - a = ac->addAction(QStringLiteral("folding_expandlocal")); - a->setText(i18n("Unfold Current Node")); - connect(a, SIGNAL(triggered(bool)), SLOT(slotExpandLocal())); + a = ac->addAction(QStringLiteral("folding_toggle_in_current")); + a->setText(i18n("Toggle Contained Nodes")); + connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::slotToggleFoldingsInRange); } void KTextEditor::ViewPrivate::slotFoldToplevelNodes() @@ -1220,45 +1220,113 @@ } } -void KTextEditor::ViewPrivate::slotCollapseLocal() +void KTextEditor::ViewPrivate::slotToggleFolding() { - foldLine(cursorPosition().line()); + int line = cursorPosition().line(); + bool actionDone = false; + while (!actionDone && (line > -1)) { + actionDone = unfoldLine(line); + if (!actionDone) { + actionDone = foldLine(line--).isValid(); + } + } } -void KTextEditor::ViewPrivate::slotExpandLocal() +void KTextEditor::ViewPrivate::slotToggleFoldingsInRange() { - unfoldLine(cursorPosition().line()); + int line = cursorPosition().line(); + while (!toggleFoldingsInRange(line) && (line > -1)) { + --line; + } } -void KTextEditor::ViewPrivate::foldLine(int startLine) +KTextEditor::Range KTextEditor::ViewPrivate::foldLine(int line) { - // only for valid lines - if (startLine < 0 || startLine >= doc()->buffer().lines()) { - return; + KTextEditor::Range foldingRange = 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 = doc()->buffer().plainLine(line); + if (!startTextLine->markedAsFoldingStartIndentation()) { + const int adjustedLine = foldingRange.end().line() - 1; + foldingRange.setEnd(KTextEditor::Cursor(adjustedLine, doc()->buffer().plainLine(adjustedLine)->length())); + } + + // Don't try to fold a single line, which can happens due to adjustment above + // FIXME Avoid to offer such a folding marker + if (!foldingRange.onSingleLine()) { + textFolding().newFoldingRange(foldingRange, Kate::TextFolding::Folded); } - // try to fold all known ranges - QVector > startingRanges = textFolding().foldingRangesStartingOnLine(startLine); + return foldingRange; +} + +bool KTextEditor::ViewPrivate::unfoldLine(int line) +{ + bool actionDone = false; + + // ask the folding info for this line, if any folds are around! + // auto = QVector> + auto startingRanges = textFolding().foldingRangesStartingOnLine(line); for (int i = 0; i < startingRanges.size(); ++i) { - textFolding().foldRange(startingRanges[i].first); + actionDone |= textFolding().unfoldRange(startingRanges[i].first); } - // try if the highlighting can help us and create a fold - textFolding().newFoldingRange(doc()->buffer().computeFoldingRangeForStartLine(startLine), Kate::TextFolding::Folded); + return actionDone; } -void KTextEditor::ViewPrivate::unfoldLine(int startLine) +bool KTextEditor::ViewPrivate::toggleFoldingOfLine(int line) { - // only for valid lines - if (startLine < 0 || startLine >= doc()->buffer().lines()) { - return; + bool actionDone = unfoldLine(line); + if (!actionDone) { + actionDone = foldLine(line).isValid(); } - // try to unfold all known ranges - QVector > startingRanges = textFolding().foldingRangesStartingOnLine(startLine); - for (int i = 0; i < startingRanges.size(); ++i) { - textFolding().unfoldRange(startingRanges[i].first); + return actionDone; +} + +bool KTextEditor::ViewPrivate::toggleFoldingsInRange(int line) +{ + KTextEditor::Range foldingRange = doc()->buffer().computeFoldingRangeForStartLine(line); + if (!foldingRange.isValid()) { + // Either line is not valid or there is no start range + 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 point 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(); + } + + // At this point we should be always true + return actionDone; } KTextEditor::View::ViewMode KTextEditor::ViewPrivate::viewMode() const @@ -1987,8 +2055,8 @@ const QStringList l = { QStringLiteral("folding_toplevel") , QStringLiteral("folding_expandtoplevel") - , QStringLiteral("folding_collapselocal") - , QStringLiteral("folding_expandlocal") + , QStringLiteral("folding_toggle_current") + , QStringLiteral("folding_toggle_in_current") }; QAction *a = 0; diff --git a/src/view/kateviewhelpers.cpp b/src/view/kateviewhelpers.cpp --- a/src/view/kateviewhelpers.cpp +++ b/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) { + m_view->toggleFoldingOfLine(lineToToggle); + } else if (e->button() == Qt::RightButton) { + m_view->toggleFoldingsInRange(lineToToggle); } delete m_foldingPreview; diff --git a/src/view/kateviewinternal.cpp b/src/view/kateviewinternal.cpp --- a/src/view/kateviewinternal.cpp +++ b/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;