diff -r -u src/Editor.cxx src/Editor.cxx --- a/src/Editor.cxx 2020-07-27 20:00:24.000000000 +0300 +++ b/src/Editor.cxx 2020-12-02 01:11:49.662556755 +0300 @@ -135,7 +135,15 @@ hotSpotClickPos = INVALID_POSITION; selectionUnit = TextUnit::character; - lastXChosen = 0; + /* + See . + Seems this is not fully correct for Cocoa, but it works good for Linux+GTK. + */ + lastXChosen = vs.leftMarginWidth; + for ( auto const & m : vs.ms ) { + lastXChosen += m.width; + } + lineAnchorPos = 0; originalAnchorPos = 0; wordSelectAnchorStartPos = 0; @@ -3147,13 +3155,36 @@ Point::FromInts(lastX - xOffset, static_cast(newY)), false, false, UserVirtualSpace()); if (direction < 0) { - // Line wrapping may lead to a location on the same line, so - // seek back if that is the case. - Point ptNew = LocationFromPosition(posNew.Position()); - while ((posNew.Position() > 0) && (pt.y == ptNew.y)) { - posNew.Add(-1); - posNew.SetVirtualSpace(0); - ptNew = LocationFromPosition(posNew.Position()); + if (UserVirtualSpace()) { + if (posNew > spStart) { + /* + Looks like SPositionFromLocation works incorrectly in the case when (1) line + wrapping is on, (2) the current document line is wrapped, (3) the caret is just + moved up, (3) the current display line is shorter than the previous, (4) + previous caret column was bigger than current display line length. In such a + case posNew is bigger than spStart. Do not move the caret in such a case. + */ + posNew = spStart; + } else if (posNew.Position() > 0 && posNew.Position() == StartEndDisplayLine(spStart.Position(), true)) { + /* + If line wrapping is on, the current display line may be shorter than the + previous. If the previous caret column was bigger than current display line + length, SPositionFromLocation returns position of the end of current display + line with appropriate amount of virtual spaces. Looks ok, but position of the + end of current display line also is the position of the beginning of the next + display line, and scintilla prefers the second interpretation of the position. + */ + posNew.SetPosition(posNew.Position() - 1); + } + } else { + // Line wrapping may lead to a location on the same line, so + // seek back if that is the case. + Point ptNew = LocationFromPosition(posNew.Position()); + while ((posNew.Position() > 0) && (pt.y == ptNew.y)) { + posNew.Add(-1); + posNew.SetVirtualSpace(0); + ptNew = LocationFromPosition(posNew.Position()); + } } } else if (direction > 0 && posNew.Position() != pdoc->Length()) { // There is an equivalent case when moving down which skips @@ -3218,14 +3249,24 @@ void Editor::ParaUpOrDown(int direction, Selection::selTypes selt) { Sci::Line lineDoc; - const Sci::Position savedPos = sel.MainCaret(); + auto savedPos = sel.Range(sel.Main()).caret; + auto x = UserVirtualSpace() ? XFromPosition(savedPos) : -1; do { - MovePositionTo(SelectionPosition(direction > 0 ? pdoc->ParaDown(sel.MainCaret()) : pdoc->ParaUp(sel.MainCaret())), selt); - lineDoc = pdoc->SciLineFromPosition(sel.MainCaret()); + auto newPos = direction > 0 ? pdoc->ParaDown(sel.MainCaret()) : pdoc->ParaUp(sel.MainCaret()); + lineDoc = pdoc->SciLineFromPosition(newPos); + if (x >= 0) { + MovePositionTo(SPositionFromLineX(lineDoc, x), selt); + } else { + MovePositionTo(newPos, selt); + } if (direction > 0) { if (sel.MainCaret() >= pdoc->Length() && !pcs->GetVisible(lineDoc)) { if (selt == Selection::noSel) { - MovePositionTo(SelectionPosition(pdoc->LineEndPosition(savedPos))); + if (x >= 0) { + MovePositionTo(savedPos); + } else { + MovePositionTo(SelectionPosition(pdoc->LineEndPosition(savedPos.Position()))); + } } break; } @@ -3771,22 +3812,62 @@ case SCI_LINEENDWRAPEXTEND: return HorizontalMove(iMessage); - case SCI_DOCUMENTSTART: - MovePositionTo(0); + case SCI_DOCUMENTSTART: { + SelectionPosition newPos; + if (UserVirtualSpace()) { + newPos = SPositionFromLineX(0, XFromPosition(sel.Range(sel.Main()).caret)); + } else { + newPos.SetPosition(0); + } + MovePositionTo(newPos); SetLastXChosen(); break; - case SCI_DOCUMENTSTARTEXTEND: - MovePositionTo(0, Selection::selStream); + } + case SCI_DOCUMENTSTARTEXTEND: { + SelectionPosition newPos; + if (UserVirtualSpace()) { + newPos = SPositionFromLineX(0, XFromPosition(sel.Range(sel.Main()).caret)); + } else { + newPos.SetPosition(0); + } + MovePositionTo(newPos, Selection::selStream); SetLastXChosen(); break; - case SCI_DOCUMENTEND: - MovePositionTo(pdoc->Length()); + } + case SCI_DOCUMENTEND: { + SelectionPosition newPos; + if (UserVirtualSpace()) { + auto newLine = pdoc->LinesTotal() - 1; + /* + This is not fully correct. If the last document line is wrapped, the caret will be + positioned on the first display line. It seems the last display line would be more + suitable. + */ + newPos = SPositionFromLineX(newLine, XFromPosition(sel.Range(sel.Main()).caret)); + } else { + newPos.SetPosition(pdoc->Length()); + } + MovePositionTo(newPos); SetLastXChosen(); break; - case SCI_DOCUMENTENDEXTEND: - MovePositionTo(pdoc->Length(), Selection::selStream); + } + case SCI_DOCUMENTENDEXTEND: { + SelectionPosition newPos; + if (UserVirtualSpace()) { + Sci::Line newLine = pdoc->LinesTotal() - 1; + /* + This is not fully correct. If the last document line is wrapped, the caret will be + positioned on the first display line. It seems the last display line would be more + suitable. + */ + newPos = SPositionFromLineX(newLine, XFromPosition(sel.Range(sel.Main()).caret)); + } else { + newPos.SetPosition(pdoc->Length()); + } + MovePositionTo(newPos, Selection::selStream); SetLastXChosen(); break; + } case SCI_STUTTEREDPAGEUP: PageMove(-1, Selection::noSel, true); break; @@ -3908,10 +3989,15 @@ } break; case SCI_LINEDELETE: { - const Sci::Line line = pdoc->SciLineFromPosition(sel.MainCaret()); + const auto caret = sel.Range(sel.Main()).caret; + const Sci::Line line = pdoc->LineFromPosition(caret.Position()); + const auto x = UserVirtualSpace() ? XFromPosition(caret) : -1; const Sci::Position start = pdoc->LineStart(line); const Sci::Position end = pdoc->LineStart(line + 1); pdoc->DeleteChars(start, end - start); + if (x >= 0) { + MovePositionTo(SPositionFromLineX(line, x)); + } } break; case SCI_LINETRANSPOSE: @@ -4182,9 +4268,14 @@ lineNo = pdoc->LinesTotal(); if (lineNo < 0) lineNo = 0; - SetEmptySelection(pdoc->LineStart(lineNo)); - ShowCaretAtCurrentPosition(); - EnsureCaretVisible(); + SelectionPosition newPos; + if (UserVirtualSpace()) { + newPos = SPositionFromLineX(lineNo, XFromPosition(sel.Range(sel.Main()).caret)); + } else { + newPos.SetPosition(pdoc->LineStart(lineNo)); + } + MovePositionTo(newPos); + SetLastXChosen(); } static bool Close(Point pt1, Point pt2, Point threshold) noexcept { diff -r -u src/EditView.cxx src/EditView.cxx --- a/src/EditView.cxx 2020-07-27 20:00:24.000000000 +0300 +++ b/src/EditView.cxx 2020-12-02 01:11:49.663556756 +0300 @@ -655,9 +655,16 @@ const Sci::Line lineDoc = model.pcs->DocFromDisplay(visibleLine); if (canReturnInvalid && (lineDoc < 0)) return SelectionPosition(INVALID_POSITION); - if (lineDoc >= model.pdoc->LinesTotal()) - return SelectionPosition(canReturnInvalid ? INVALID_POSITION : - model.pdoc->Length()); + if (lineDoc >= model.pdoc->LinesTotal()) { + if (canReturnInvalid) { + SelectionPosition(INVALID_POSITION); + } + if (virtualSpace) { + return SPositionFromLineX(surface, model, model.pdoc->LinesTotal() - 1, static_cast(pt.x), vs); + } else { + return SelectionPosition(model.pdoc->Length()); + } + } const Sci::Position posLineStart = model.pdoc->LineStart(lineDoc); AutoLineLayout ll(llc, RetrieveLineLayout(lineDoc, model)); if (surface && ll) {