Scintilla changes: * Add Message::ChangeInsertion for programmatically setting input method. This is helpful on newer versions of macOS, where changing the input method is flaky. * Handle leading whitespace in XPM images in order to prevent crashes. * Fixed crash in upstream Scintilla that will ultimately be fixed. * Added Curses caret style from upstream Scintilla, which will be in the next release. diff -r 52d56f79dc0f gtk/ScintillaGTK.cxx --- a/gtk/ScintillaGTK.cxx Fri Apr 09 15:11:26 2021 +1000 +++ b/gtk/ScintillaGTK.cxx Tue Apr 13 16:36:00 2021 -0400 @@ -885,6 +887,11 @@ case Message::GetDirectPointer: return reinterpret_cast(this); + case Message::ChangeInsertion: + // Hijack this interface to programmatically set input method. + gtk_im_multicontext_set_context_id(GTK_IM_MULTICONTEXT(im_context), ConstCharPtrFromSPtr(lParam)); + break; + case Message::TargetAsUTF8: return TargetAsUTF8(CharPtrFromSPtr(lParam)); diff -r 22b6bbb36280 src/XPM.cxx --- a/src/XPM.cxx Sat Sep 05 07:55:08 2020 +1000 +++ b/src/XPM.cxx Fri Oct 02 20:32:13 2020 -0400 @@ -92,6 +92,9 @@ void XPM::Init(const char *textForm) { // Test done is two parts to avoid possibility of overstepping the memory // if memcmp implemented strangely. Must be 4 bytes at least at destination. + while (*textForm == ' ') { + textForm++; + } if ((0 == memcmp(textForm, "/* X", 4)) && (0 == memcmp(textForm, "/* XPM */", 9))) { // Build the lines form out of the text form std::vector linesForm = LinesFormFromTextForm(textForm); diff -r beeb51d2c645 gtk/ScintillaGTK.cxx --- a/gtk/ScintillaGTK.cxx Wed Sep 29 10:29:58 2021 +1000 +++ b/gtk/ScintillaGTK.cxx Thu Sep 30 16:28:06 2021 -0400 @@ -282,9 +282,6 @@ if (settingsHandlerId) { g_signal_handler_disconnect(settings, settingsHandlerId); } - if (settings) { - g_object_unref(settings); - } } void ScintillaGTK::RealizeThis(GtkWidget *widget) { diff -r beeb51d2c645 include/Scintilla.h --- a/include/Scintilla.h Wed Sep 29 10:29:58 2021 +1000 +++ b/include/Scintilla.h Sun Oct 24 11:02:34 2021 -0400 @@ -907,6 +907,7 @@ #define CARETSTYLE_BLOCK 2 #define CARETSTYLE_OVERSTRIKE_BAR 0 #define CARETSTYLE_OVERSTRIKE_BLOCK 0x10 +#define CARETSTYLE_CURSES 0x20 #define CARETSTYLE_INS_MASK 0xF #define CARETSTYLE_BLOCK_AFTER 0x100 #define SCI_SETCARETSTYLE 2512 diff -r beeb51d2c645 include/Scintilla.iface --- a/include/Scintilla.iface Wed Sep 29 10:29:58 2021 +1000 +++ b/include/Scintilla.iface Sun Oct 24 11:02:34 2021 -0400 @@ -2489,6 +2489,7 @@ val CARETSTYLE_BLOCK=2 val CARETSTYLE_OVERSTRIKE_BAR=0 val CARETSTYLE_OVERSTRIKE_BLOCK=0x10 +val CARETSTYLE_CURSES=0x20 val CARETSTYLE_INS_MASK=0xF val CARETSTYLE_BLOCK_AFTER=0x100 diff -r beeb51d2c645 include/ScintillaTypes.h --- a/include/ScintillaTypes.h Wed Sep 29 10:29:58 2021 +1000 +++ b/include/ScintillaTypes.h Sun Oct 24 11:02:34 2021 -0400 @@ -439,6 +439,7 @@ Block = 2, OverstrikeBar = 0, OverstrikeBlock = 0x10, + Curses = 0x20, InsMask = 0xF, BlockAfter = 0x100, }; diff -r beeb51d2c645 src/EditView.cxx --- a/src/EditView.cxx Wed Sep 29 10:29:58 2021 +1000 +++ b/src/EditView.cxx Sun Oct 24 11:02:34 2021 -0400 @@ -470,7 +470,7 @@ ll->positions[0] = 0; bool lastSegItalics = false; - BreakFinder bfLayout(ll, nullptr, Range(0, numCharsInLine), posLineStart, 0, false, model.pdoc, &model.reprs, nullptr); + BreakFinder bfLayout(ll, nullptr, Range(0, numCharsInLine), posLineStart, 0, BreakFinder::BreakFor::Text, model.pdoc, &model.reprs, nullptr); while (bfLayout.More()) { const TextSegment ts = bfLayout.Next(); @@ -1636,7 +1636,7 @@ } const bool caretBlinkState = (model.caret.active && model.caret.on) || (!additionalCaretsBlink && !mainCaret); const bool caretVisibleState = additionalCaretsVisible || mainCaret; - if ((xposCaret >= 0) && vsDraw.IsCaretVisible() && + if ((xposCaret >= 0) && vsDraw.IsCaretVisible(mainCaret) && (drawDrag || (caretBlinkState && caretVisibleState))) { bool canDrawBlockCaret = true; bool drawBlockCaret = false; @@ -1660,7 +1660,8 @@ if (xposCaret > 0) caretWidthOffset = 0.51f; // Move back so overlaps both character cells. xposCaret += xStart; - const ViewStyle::CaretShape caretShape = drawDrag ? ViewStyle::CaretShape::line : vsDraw.CaretShapeForMode(model.inOverstrike); + const ViewStyle::CaretShape caretShape = drawDrag ? ViewStyle::CaretShape::line : + vsDraw.CaretShapeForMode(model.inOverstrike, mainCaret); if (drawDrag) { /* Dragging text, use a line caret */ rcCaret.left = std::round(xposCaret - caretWidthOffset); @@ -1733,6 +1734,21 @@ } } +// On the curses platform, the terminal is drawing its own caret, so if the caret is within +// the main selection, do not draw the selection at that position. +// Use iDoc from DrawBackground and DrawForeground here because TextSegment has been adjusted +// such that, if the caret is inside the main selection, the beginning or end of that selection +// is at the end of a text segment. +// This function should only be called if iDoc is within the main selection. +static InSelection CharacterInCursesSelection(Sci::Position iDoc, const EditModel &model, const ViewStyle &vsDraw) { + const SelectionPosition &posCaret = model.sel.RangeMain().caret; + const bool caretAtStart = posCaret < model.sel.RangeMain().anchor && posCaret.Position() == iDoc; + const bool caretAtEnd = posCaret > model.sel.RangeMain().anchor && + vsDraw.DrawCaretInsideSelection(false, false) && + model.pdoc->MovePositionOutsideChar(posCaret.Position() - 1, -1) == iDoc; + return (caretAtStart || caretAtEnd) ? InSelection::inNone : InSelection::inMain; +} + void EditView::DrawBackground(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll, PRectangle rcLine, Range lineRange, Sci::Position posLineStart, int xStart, int subLine, std::optional background) const { @@ -1743,7 +1759,8 @@ // Does not take margin into account but not significant const XYPOSITION xStartVisible = static_cast(subLineStart-xStart); - BreakFinder bfBack(ll, &model.sel, lineRange, posLineStart, xStartVisible, selBackDrawn, model.pdoc, &model.reprs, nullptr); + const BreakFinder::BreakFor breakFor = selBackDrawn ? BreakFinder::BreakFor::Selection : BreakFinder::BreakFor::Text; + BreakFinder bfBack(ll, &model.sel, lineRange, posLineStart, xStartVisible, breakFor, model.pdoc, &model.reprs, &vsDraw); const bool drawWhitespaceBackground = vsDraw.WhitespaceBackgroundDrawn() && !background; @@ -1766,7 +1783,9 @@ if (rcSegment.right > rcLine.right) rcSegment.right = rcLine.right; - const InSelection inSelection = hideSelection ? InSelection::inNone : model.sel.CharacterInSelection(iDoc); + InSelection inSelection = hideSelection ? InSelection::inNone : model.sel.CharacterInSelection(iDoc); + if (FlagSet(vsDraw.caret.style, CaretStyle::Curses) && (inSelection == InSelection::inMain)) + inSelection = CharacterInCursesSelection(iDoc, model, vsDraw); const bool inHotspot = model.hotspot.Valid() && model.hotspot.ContainsCharacter(iDoc); ColourRGBA textBack = TextBackground(model, vsDraw, ll, background, inSelection, inHotspot, ll->styles[i], i); @@ -1959,8 +1978,9 @@ const XYPOSITION xStartVisible = static_cast(subLineStart-xStart); // Foreground drawing loop - BreakFinder bfFore(ll, &model.sel, lineRange, posLineStart, xStartVisible, - (((phasesDraw == PhasesDraw::One) && selBackDrawn) || vsDraw.SelectionTextDrawn()), model.pdoc, &model.reprs, &vsDraw); + const BreakFinder::BreakFor breakFor = (((phasesDraw == PhasesDraw::One) && selBackDrawn) || vsDraw.SelectionTextDrawn()) + ? BreakFinder::BreakFor::ForegroundAndSelection : BreakFinder::BreakFor::Foreground; + BreakFinder bfFore(ll, &model.sel, lineRange, posLineStart, xStartVisible, breakFor, model.pdoc, &model.reprs, &vsDraw); while (bfFore.More()) { @@ -2010,7 +2030,9 @@ } } } - const InSelection inSelection = hideSelection ? InSelection::inNone : model.sel.CharacterInSelection(iDoc); + InSelection inSelection = hideSelection ? InSelection::inNone : model.sel.CharacterInSelection(iDoc); + if (FlagSet(vsDraw.caret.style, CaretStyle::Curses) && (inSelection == InSelection::inMain)) + inSelection = CharacterInCursesSelection(iDoc, model, vsDraw); const std::optional selectionFore = SelectionForeground(model, vsDraw, inSelection); if (selectionFore) { textFore = *selectionFore; diff -r beeb51d2c645 src/Editor.cxx --- a/src/Editor.cxx Wed Sep 29 10:29:58 2021 +1000 +++ b/src/Editor.cxx Sun Oct 24 11:02:34 2021 -0400 @@ -7592,7 +7592,7 @@ return vs.ElementColour(Element::Caret)->OpaqueRGB(); case Message::SetCaretStyle: - if (static_cast(wParam) <= (CaretStyle::Block | CaretStyle::OverstrikeBlock | CaretStyle::BlockAfter)) + if (static_cast(wParam) <= (CaretStyle::Block | CaretStyle::OverstrikeBlock | CaretStyle::Curses | CaretStyle::BlockAfter)) vs.caret.style = static_cast(wParam); else /* Default to the line caret */ diff -r beeb51d2c645 src/PositionCache.cxx --- a/src/PositionCache.cxx Wed Sep 29 10:29:58 2021 +1000 +++ b/src/PositionCache.cxx Sun Oct 24 11:02:34 2021 -0400 @@ -662,7 +662,7 @@ } BreakFinder::BreakFinder(const LineLayout *ll_, const Selection *psel, Range lineRange_, Sci::Position posLineStart_, - XYPOSITION xStart, bool breakForSelection, const Document *pdoc_, const SpecialRepresentations *preprs_, const ViewStyle *pvsDraw) : + XYPOSITION xStart, BreakFor breakFor, const Document *pdoc_, const SpecialRepresentations *preprs_, const ViewStyle *pvsDraw) : ll(ll_), lineRange(lineRange_), posLineStart(posLineStart_), @@ -683,7 +683,7 @@ nextBreak--; } - if (breakForSelection) { + if (FlagSet(breakFor, BreakFor::Selection)) { const SelectionPosition posStart(posLineStart); const SelectionPosition posEnd(posLineStart + lineRange.end); const SelectionSegment segmentLine(posStart, posEnd); @@ -696,8 +696,23 @@ Insert(portion.end.Position() - posLineStart); } } + // On the curses platform, the terminal is drawing its own caret, so add breaks around the + // caret in the main selection in order to help prevent the selection from being drawn in + // the caret's cell. + if (FlagSet(pvsDraw->caret.style, CaretStyle::Curses) && !psel->RangeMain().Empty()) { + const Sci::Position caretPos = psel->RangeMain().caret.Position(); + const Sci::Position anchorPos = psel->RangeMain().anchor.Position(); + if (caretPos < anchorPos) { + const Sci::Position nextPos = pdoc->MovePositionOutsideChar(caretPos + 1, 1); + Insert(nextPos - posLineStart); + } else if (caretPos > anchorPos && pvsDraw->DrawCaretInsideSelection(false, false)) { + const Sci::Position prevPos = pdoc->MovePositionOutsideChar(caretPos - 1, -1); + if (prevPos > anchorPos) + Insert(prevPos - posLineStart); + } + } } - if (pvsDraw && pvsDraw->indicatorsSetFore) { + if (FlagSet(breakFor, BreakFor::Foreground) && pvsDraw->indicatorsSetFore) { for (const IDecoration *deco : pdoc->decorations->View()) { if (pvsDraw->indicators[deco->Indicator()].OverridesTextFore()) { Sci::Position startPos = deco->EndRun(posLineStart); diff -r beeb51d2c645 src/PositionCache.h --- a/src/PositionCache.h Wed Sep 29 10:29:58 2021 +1000 +++ b/src/PositionCache.h Sun Oct 24 11:02:34 2021 -0400 @@ -259,8 +259,14 @@ enum { lengthStartSubdivision = 300 }; // Try to make each subdivided run lengthEachSubdivision or shorter. enum { lengthEachSubdivision = 100 }; + enum class BreakFor { + Text = 0, + Selection = 1, + Foreground = 2, + ForegroundAndSelection = 3, + }; BreakFinder(const LineLayout *ll_, const Selection *psel, Range lineRange_, Sci::Position posLineStart_, - XYPOSITION xStart, bool breakForSelection, const Document *pdoc_, const SpecialRepresentations *preprs_, const ViewStyle *pvsDraw); + XYPOSITION xStart, BreakFor breakFor, const Document *pdoc_, const SpecialRepresentations *preprs_, const ViewStyle *pvsDraw); // Deleted so BreakFinder objects can not be copied. BreakFinder(const BreakFinder &) = delete; BreakFinder(BreakFinder &&) = delete; diff -r beeb51d2c645 src/ViewStyle.cxx --- a/src/ViewStyle.cxx Wed Sep 29 10:29:58 2021 +1000 +++ b/src/ViewStyle.cxx Sun Oct 24 11:02:34 2021 -0400 @@ -661,11 +661,14 @@ bool ViewStyle::IsBlockCaretStyle() const noexcept { return ((caret.style & CaretStyle::InsMask) == CaretStyle::Block) || - FlagSet(caret.style, CaretStyle::OverstrikeBlock); + FlagSet(caret.style, CaretStyle::OverstrikeBlock) || + FlagSet(caret.style, CaretStyle::Curses); } -bool ViewStyle::IsCaretVisible() const noexcept { - return caret.width > 0 && caret.style != CaretStyle::Invisible; +bool ViewStyle::IsCaretVisible(bool isMainSelection) const noexcept { + return caret.width > 0 && + ((caret.style & CaretStyle::InsMask) != CaretStyle::Invisible || + (FlagSet(caret.style, CaretStyle::Curses) && !isMainSelection)); // only draw additional selections in curses mode } bool ViewStyle::DrawCaretInsideSelection(bool inOverstrike, bool imeCaretBlockOverride) const noexcept { @@ -673,14 +676,19 @@ return false; return ((caret.style & CaretStyle::InsMask) == CaretStyle::Block) || (inOverstrike && FlagSet(caret.style, CaretStyle::OverstrikeBlock)) || - imeCaretBlockOverride; + imeCaretBlockOverride || + FlagSet(caret.style, CaretStyle::Curses); } -ViewStyle::CaretShape ViewStyle::CaretShapeForMode(bool inOverstrike) const noexcept { +ViewStyle::CaretShape ViewStyle::CaretShapeForMode(bool inOverstrike, bool isMainSelection) const noexcept { if (inOverstrike) { return (FlagSet(caret.style, CaretStyle::OverstrikeBlock)) ? CaretShape::block : CaretShape::bar; } + if (FlagSet(caret.style, CaretStyle::Curses) && !isMainSelection) { + return CaretShape::block; + } + const CaretStyle caretStyle = caret.style & CaretStyle::InsMask; return (caretStyle <= CaretStyle::Block) ? static_cast(caretStyle) : CaretShape::line; } diff -r beeb51d2c645 src/ViewStyle.h --- a/src/ViewStyle.h Wed Sep 29 10:29:58 2021 +1000 +++ b/src/ViewStyle.h Sun Oct 24 11:02:34 2021 -0400 @@ -235,9 +235,9 @@ enum class CaretShape { invisible, line, block, bar }; bool IsBlockCaretStyle() const noexcept; - bool IsCaretVisible() const noexcept; + bool IsCaretVisible(bool isMainSelection) const noexcept; bool DrawCaretInsideSelection(bool inOverstrike, bool imeCaretBlockOverride) const noexcept; - CaretShape CaretShapeForMode(bool inOverstrike) const noexcept; + CaretShape CaretShapeForMode(bool inOverstrike, bool isMainSelection) const noexcept; private: void AllocStyles(size_t sizeNew);