Wrap aware SCI_SCROLLVERTICAL
Neil <[email protected]> Tue, 27 May 2025 19:50:05 -0700 (PDT)
| Newsgroups | gmane.comp.lib.scintilla.devel |
|---|---|
| Message-ID | <[email protected]> |
Text wrapping in Scintilla is a background activity that can take a long
time to fully wrap a document. This may take seconds or even minutes for a
really huge document or slow computer. It also means that applications
manipulating the editor may encounter partially wrapped situations where
calls do not have their intended behaviour.
An example is an application setting the scroll position to match the
previous time that document was showing. Wrapping may move that scroll
position out of view. There are several issues on the trackers referencing
this problem such as this issue.
https://sourceforge.net/p/scintilla/bugs/1395/
A possible solution is to defer scrolling vertically until after wrapping
has completed or, at least passed the desired scroll position so that the
view is stable. This is the purpose of a proposed API
SCI_SCROLLVERTICAL(docLine, subLine) which bases scroll positioning on a
combination of a document line and a sub-line within that document line.
This is more stable than using a visual line as it better handles changes
in view width.
This API will first synchronously set the scroll position based on no
wrapping information which is correct when wrap is off and an OK first
approximation when wrapping is on. It remembers the desired (docLine,
subLine) scroll position and uses that inside the wrapping code to maintain
a stable view. The desired scroll position is forgotten once the document
has been fully wrapped or the user manually scrolls.
If the user's manual scrolling did not forget the desired position then the
effect is to scroll back to the desired position repeatedly which is
frustrating. A problem then is to work out when a scroll is user-initiated.
This is now done by treating every SetVerticalScrollPos call as user
initiated except for those performed inside WrapLines. Earlier attempts
tried to intercept the different ways a user can cause a scroll but the
number of places grew and more would have to be added for each platform.
This does require platform layers to call the superclass
Editor::SetVerticalScrollPos() inside their own SetVerticalScrollPos
implementation. There is a good chance that the user-initiated scrolling
detection will need refinement.
Another approach would be to store the byte index (position) of the first
character in the view as the scrolling anchor. That may ensure scroll
restoration shows more of the initial text when the window changed width
since saving but it relies on more expensive calls.
From the application side, SciTE, for example has code like this when
saving the scroll position.
ScrollDocWithOffset SciTEBase::GetCurrentScrollPosition() {
const SA::Line lineDisplayTop = wEditor.FirstVisibleLine();
const SA::Line lineDocTop = wEditor.DocLineFromVisible(lineDisplayTop);
const SA::Line subLineTop = lineDisplayTop -
wEditor.VisibleFromDocLine(lineDocTop);
return { lineDocTop, subLineTop };
}
Here is SciTE's code when restoring a scroll position.
wEditor.ScrollVertical(fp.scrollPosition.lineDoc,
fp.scrollPosition.subLine);
A patch implementing SCI_SCROLLVERTICAL is attached.
Applications will have to choose to use this new API for it to have any
effect and platform layers will also need to add the one call mentioned
above.
Neil
--
You received this message because you are subscribed to the Google Groups "scintilla-interest" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To view this discussion visit https://groups.google.com/d/msgid/scintilla-interest/3c23667f-f426-4e75-9af9-5898f0b9872fn%40googlegroups.com.
ScrolVerticalEditor.patch
(text/plain, 7.7 KB)
diff -r 4efb64d60440 call/ScintillaCall.cxx
--- a/call/ScintillaCall.cxx Sun May 25 10:14:33 2025 +1000
+++ b/call/ScintillaCall.cxx Tue May 27 10:36:17 2025 +1000
@@ -1375,6 +1375,10 @@
Call(Message::LineScroll, columns, lines);
}
+void ScintillaCall::ScrollVertical(Line docLine, Line subLine) {
+ Call(Message::ScrollVertical, docLine, subLine);
+}
+
void ScintillaCall::ScrollCaret() {
Call(Message::ScrollCaret);
}
diff -r 4efb64d60440 cocoa/ScintillaCocoa.mm
--- a/cocoa/ScintillaCocoa.mm Sun May 25 10:14:33 2025 +1000
+++ b/cocoa/ScintillaCocoa.mm Tue May 27 10:36:17 2025 +1000
@@ -1951,6 +1951,7 @@
* Modifies the vertical scroll position to make the current top line show up as such.
*/
void ScintillaCocoa::SetVerticalScrollPos() {
+ Editor::SetVerticalScrollPos();
NSScrollView *scrollView = ScrollContainer();
if (scrollView) {
NSClipView *clipView = scrollView.contentView;
diff -r 4efb64d60440 gtk/ScintillaGTK.cxx
--- a/gtk/ScintillaGTK.cxx Sun May 25 10:14:33 2025 +1000
+++ b/gtk/ScintillaGTK.cxx Tue May 27 10:36:17 2025 +1000
@@ -1102,6 +1102,7 @@
}
void ScintillaGTK::SetVerticalScrollPos() {
+ Editor::SetVerticalScrollPos();
DwellEnd(true);
gtk_adjustment_set_value(GTK_ADJUSTMENT(adjustmentv), static_cast<gdouble>(topLine));
}
diff -r 4efb64d60440 include/Scintilla.h
--- a/include/Scintilla.h Sun May 25 10:14:33 2025 +1000
+++ b/include/Scintilla.h Tue May 27 10:36:17 2025 +1000
@@ -561,6 +561,7 @@
#define SCI_LINEFROMPOSITION 2166
#define SCI_POSITIONFROMLINE 2167
#define SCI_LINESCROLL 2168
+#define SCI_SCROLLVERTICAL 2817
#define SCI_SCROLLCARET 2169
#define SCI_SCROLLRANGE 2569
#define SCI_REPLACESEL 2170
diff -r 4efb64d60440 include/Scintilla.iface
--- a/include/Scintilla.iface Sun May 25 10:14:33 2025 +1000
+++ b/include/Scintilla.iface Tue May 27 10:36:17 2025 +1000
@@ -1422,6 +1422,9 @@
# Scroll horizontally and vertically.
fun void LineScroll=2168(position columns, line lines)
+# Scroll vertically with allowance for wrapping.
+fun void ScrollVertical=2817(line docLine, line subLine)
+
# Ensure the caret is visible.
fun void ScrollCaret=2169(,)
diff -r 4efb64d60440 include/ScintillaCall.h
--- a/include/ScintillaCall.h Sun May 25 10:14:33 2025 +1000
+++ b/include/ScintillaCall.h Tue May 27 10:36:17 2025 +1000
@@ -389,6 +389,7 @@
Line LineFromPosition(Position pos);
Position PositionFromLine(Line line);
void LineScroll(Position columns, Line lines);
+ void ScrollVertical(Line docLine, Line subLine);
void ScrollCaret();
void ScrollRange(Position secondary, Position primary);
void ReplaceSel(const char *text);
diff -r 4efb64d60440 include/ScintillaMessages.h
--- a/include/ScintillaMessages.h Sun May 25 10:14:33 2025 +1000
+++ b/include/ScintillaMessages.h Tue May 27 10:36:17 2025 +1000
@@ -311,6 +311,7 @@
LineFromPosition = 2166,
PositionFromLine = 2167,
LineScroll = 2168,
+ ScrollVertical = 2817,
ScrollCaret = 2169,
ScrollRange = 2569,
ReplaceSel = 2170,
diff -r 4efb64d60440 qt/ScintillaEditBase/ScintillaQt.cpp
--- a/qt/ScintillaEditBase/ScintillaQt.cpp Sun May 25 10:14:33 2025 +1000
+++ b/qt/ScintillaEditBase/ScintillaQt.cpp Tue May 27 10:36:17 2025 +1000
@@ -280,6 +280,7 @@
void ScintillaQt::SetVerticalScrollPos()
{
+ Editor::SetVerticalScrollPos();
scrollArea->verticalScrollBar()->setValue(topLine);
emit verticalScrolled(topLine);
}
diff -r 4efb64d60440 src/Editor.cxx
--- a/src/Editor.cxx Sun May 25 10:14:33 2025 +1000
+++ b/src/Editor.cxx Tue May 27 10:36:17 2025 +1000
@@ -202,6 +202,8 @@
recordingMacro = false;
foldAutomatic = AutomaticFold::None;
+ insideWrapScroll = false;
+
convertPastes = true;
SetRepresentations();
@@ -992,6 +994,11 @@
}
}
+Sci::Line Editor::DisplayLineFromDocSub(LineDocSub line) const noexcept {
+ return pcs->DisplayFromDoc(line.lineDoc) + std::min(
+ line.subLine, static_cast<Sci::Line>(pcs->GetHeight(line.lineDoc) - 1));
+}
+
void Editor::ScrollText(Sci::Line /* linesToMove */) {
//Platform::DebugPrintf("Editor::ScrollText %d\n", linesToMove);
Redraw();
@@ -1679,8 +1686,15 @@
// Decide where to start wrapping
Sci::Line lineToWrap = wrapPending.start;
Sci::Line lineToWrapEnd = std::min(wrapPending.end, pdoc->LinesTotal());
+
const Sci::Line lineDocTop = pcs->DocFromDisplay(topLine);
- const Sci::Line subLineTop = topLine - pcs->DisplayFromDoc(lineDocTop);
+ LineDocSub lineScrollTo;
+ if (scrollToAfterWrap) {
+ lineScrollTo = scrollToAfterWrap.value();
+ } else {
+ const Sci::Line subLineTop = topLine - pcs->DisplayFromDoc(lineDocTop);
+ lineScrollTo = { lineDocTop, subLineTop };
+ }
if (ws == WrapScope::wsVisible) {
lineToWrap = std::clamp(lineDocTop-5, wrapPending.start, pdoc->LinesTotal());
// Priority wrap to just after visible area.
@@ -1731,21 +1745,23 @@
wrapOccurred = WrapBlock(surface, lineToWrap, lineToWrapEnd);
- goodTopLine = pcs->DisplayFromDoc(lineDocTop) + std::min(
- subLineTop, static_cast<Sci::Line>(pcs->GetHeight(lineDocTop)-1));
+ goodTopLine = DisplayLineFromDocSub(lineScrollTo);
}
}
// If wrapping is done, bring it to resting position
if (wrapPending.start >= lineEndNeedWrap) {
wrapPending.Reset();
+ scrollToAfterWrap.reset();
}
}
if (wrapOccurred) {
+ insideWrapScroll = true;
SetScrollBars();
SetTopLine(std::clamp<Sci::Line>(goodTopLine, 0, MaxScrollPos()));
SetVerticalScrollPos();
+ insideWrapScroll = false;
}
return wrapOccurred;
@@ -1990,6 +2006,12 @@
return 1;
}
+void Editor::SetVerticalScrollPos() {
+ if (!insideWrapScroll) {
+ scrollToAfterWrap.reset();
+ }
+}
+
// Empty method is overridden on GTK+ to show / hide scrollbars
void Editor::ReconfigureScrollBars() {}
@@ -5545,6 +5567,8 @@
SetRepresentations();
+ scrollToAfterWrap.reset();
+
// Reset the contraction state to fully shown.
pcs->Clear();
pcs->InsertLines(0, pdoc->LinesTotal() - 1);
@@ -6554,6 +6578,15 @@
HorizontalScrollTo(xOffset + static_cast<int>(static_cast<int>(wParam) * vs.spaceWidth));
return 1;
+ case Message::ScrollVertical:
+ if (Wrapping()) {
+ scrollToAfterWrap.emplace(wParam, lParam);
+ } else {
+ scrollToAfterWrap.reset();
+ }
+ ScrollTo(DisplayLineFromDocSub({static_cast<Sci::Line>(wParam), lParam}));
+ break;
+
case Message::SetXOffset:
xOffset = static_cast<int>(wParam);
ContainerNeedsUpdate(Update::HScroll);
diff -r 4efb64d60440 src/Editor.h
--- a/src/Editor.h Sun May 25 10:14:33 2025 +1000
+++ b/src/Editor.h Tue May 27 10:36:17 2025 +1000
@@ -283,6 +283,12 @@
// Wrapping support
WrapPending wrapPending;
ActionDuration durationWrapOneByte;
+ bool insideWrapScroll;
+ struct LineDocSub {
+ Scintilla::Line lineDoc = 0;
+ Scintilla::Line subLine = 0;
+ };
+ std::optional<LineDocSub> scrollToAfterWrap;
bool convertPastes;
@@ -373,6 +379,7 @@
void RememberCurrentSelectionForRedoOntoStack();
void ScrollTo(Sci::Line line, bool moveThumb=true);
+ Sci::Line DisplayLineFromDocSub(LineDocSub line) const noexcept;
virtual void ScrollText(Sci::Line linesToMove);
void HorizontalScrollTo(int xPos);
void VerticalCentreCaret();
@@ -417,7 +424,7 @@
Sci::Position FormatRange(Scintilla::Message iMessage, Scintilla::uptr_t wParam, Scintilla::sptr_t lParam);
long TextWidth(Scintilla::uptr_t style, const char *text);
- virtual void SetVerticalScrollPos() = 0;
+ virtual void SetVerticalScrollPos();
virtual void SetHorizontalScrollPos() = 0;
virtual bool ModifyScrollBars(Sci::Line nMax, Sci::Line nPage) = 0;
virtual void ReconfigureScrollBars();
diff -r 4efb64d60440 win32/ScintillaWin.cxx
--- a/win32/ScintillaWin.cxx Sun May 25 10:14:33 2025 +1000
+++ b/win32/ScintillaWin.cxx Tue May 27 10:36:17 2025 +1000
@@ -2700,6 +2700,7 @@
}
void ScintillaWin::SetVerticalScrollPos() {
+ Editor::SetVerticalScrollPos();
ChangeScrollPos(SB_VERT, topLine);
}