From f4d5c00424eff7c84b4152b4df4de5c0ba4b12bc Mon Sep 17 00:00:00 2001
From: Ferdinand Oeinck
Date: Tue, 4 Oct 2022 09:35:06 +1100
Subject: Feature [feature-requests:#1453] Added
SCI_STYLESETINVISIBLEREPRESENTATION to make it easier to edit around
invisible text. This also allows representing long lexemes with a single
character to provide a summarized view.
---
call/ScintillaCall.cxx | 12 ++++++++++++
doc/ScintillaDoc.html | 11 +++++++++++
doc/ScintillaHistory.html | 6 ++++++
include/Scintilla.h | 2 ++
include/Scintilla.iface | 6 ++++++
include/ScintillaCall.h | 3 +++
include/ScintillaMessages.h | 2 ++
src/EditView.cxx | 24 ++++++++++++++++++++++++
src/Editor.cxx | 17 +++++++++++++++++
src/Style.cxx | 3 ++-
src/Style.h | 1 +
test/simpleTests.py | 8 ++++++++
12 files changed, 94 insertions(+), 1 deletion(-)
diff --git a/call/ScintillaCall.cxx b/call/ScintillaCall.cxx
index 1f496c53d..434e3fdf0 100644
--- a/call/ScintillaCall.cxx
+++ b/call/ScintillaCall.cxx
@@ -647,6 +647,18 @@ bool ScintillaCall::StyleGetCheckMonospaced(int style) {
return Call(Message::StyleGetCheckMonospaced, style);
}
+void ScintillaCall::StyleSetInvisibleRepresentation(int style, const char *representation) {
+ CallString(Message::StyleSetInvisibleRepresentation, style, representation);
+}
+
+int ScintillaCall::StyleGetInvisibleRepresentation(int style, char *representation) {
+ return static_cast(CallPointer(Message::StyleGetInvisibleRepresentation, style, representation));
+}
+
+std::string ScintillaCall::StyleGetInvisibleRepresentation(int style) {
+ return CallReturnString(Message::StyleGetInvisibleRepresentation, style);
+}
+
void ScintillaCall::SetElementColour(Scintilla::Element element, ColourAlpha colourElement) {
Call(Message::SetElementColour, static_cast(element), colourElement);
}
diff --git a/doc/ScintillaDoc.html b/doc/ScintillaDoc.html
index a9a7e72ed..fb5d59dfd 100644
--- a/doc/ScintillaDoc.html
+++ b/doc/ScintillaDoc.html
@@ -3248,6 +3248,8 @@ struct Sci_TextToFindFull {
SCI_STYLESETCHECKMONOSPACED(int style, bool
checkMonospaced)
SCI_STYLEGETCHECKMONOSPACED(int style) → bool
+ SCI_STYLESETINVISIBLEREPRESENTATION(int style, const char *representation)
+ SCI_STYLEGETINVISIBLEREPRESENTATION(int style, char *representation NUL-terminated) → int
SCI_SETFONTLOCALE(<unused>, const char *localeName)
SCI_GETFONTLOCALE(<unused>, char *localeName) → int
@@ -3514,6 +3516,15 @@ struct Sci_TextToFindFull {
Applications may apply the 'check monospaced' attribute just to fonts known to be monospaced or on all fonts, leaving it to Scintilla to
reject fonts that are proportional.
+ SCI_STYLESETINVISIBLEREPRESENTATION(int style, const char *representation)
+ SCI_STYLEGETINVISIBLEREPRESENTATION(int style, char *representation NUL-terminated) → int
+ When a style is made invisible with SCI_STYLESETVISIBLE, text is difficult to edit as
+ the cursor can be at both sides of the invisible text segment. With these messages invisible text segements can be made visible with a single
+ UTF8 characater giving the user an indication if the cursor is left or right of the invisible text. The character is displayed using the current style.
+
+ The representation parameter is a zero terminated string holding the one character used to represent the invisible text segment. Only the first character
+ is used, the character is decoded as UTF-8.
+
SCI_SETFONTLOCALE(<unused>, const char *localeName)
SCI_GETFONTLOCALE(<unused>, char *localeName NUL-terminated) → int
These messages set the locale used for font selection with language-dependent glyphs.
diff --git a/doc/ScintillaHistory.html b/doc/ScintillaHistory.html
index abbf4f4ef..cac690226 100644
--- a/doc/ScintillaHistory.html
+++ b/doc/ScintillaHistory.html
@@ -573,6 +573,7 @@
Jacky Yang |
| Reinhard Nißl |
+ Ferdinand Oeinck |
Releases
@@ -584,6 +585,11 @@
Released 27 August 2022.
+ Added SCI_STYLESETINVISIBLEREPRESENTATION to make it easier to edit around invisible text.
+ This also allows representing long lexemes with a single character to provide a summarized view.
+ Feature #1453.
+
+
Removed NotifyLexerChanged notification from DocWatcher.
This is a private interface but could be used by independent platform layers
and was exposed by ScintillaDocument in the Qt implementation of ScintillaEdit.
diff --git a/include/Scintilla.h b/include/Scintilla.h
index 5781674e3..03302e293 100644
--- a/include/Scintilla.h
+++ b/include/Scintilla.h
@@ -280,6 +280,8 @@ typedef sptr_t (*SciFnDirectStatus)(sptr_t ptr, unsigned int iMessage, uptr_t wP
#define SCI_STYLESETHOTSPOT 2409
#define SCI_STYLESETCHECKMONOSPACED 2254
#define SCI_STYLEGETCHECKMONOSPACED 2255
+#define SCI_STYLESETINVISIBLEREPRESENTATION 2256
+#define SCI_STYLEGETINVISIBLEREPRESENTATION 2257
#define SC_ELEMENT_LIST 0
#define SC_ELEMENT_LIST_BACK 1
#define SC_ELEMENT_LIST_SELECTED 2
diff --git a/include/Scintilla.iface b/include/Scintilla.iface
index bd6cb76de..b03b8c8ca 100644
--- a/include/Scintilla.iface
+++ b/include/Scintilla.iface
@@ -694,6 +694,12 @@ set void StyleSetCheckMonospaced=2254(int style, bool checkMonospaced)
# Get whether a style may be monospaced.
get bool StyleGetCheckMonospaced=2255(int style,)
+# Set the invisible representation for a style.
+set void StyleSetInvisibleRepresentation=2256(int style, string representation)
+
+# Get the invisible representation for a style.
+get int StyleGetInvisibleRepresentation=2257(int style, stringresult representation)
+
enu Element=SC_ELEMENT_
val SC_ELEMENT_LIST=0
val SC_ELEMENT_LIST_BACK=1
diff --git a/include/ScintillaCall.h b/include/ScintillaCall.h
index 0e79e37db..4b8e3c732 100644
--- a/include/ScintillaCall.h
+++ b/include/ScintillaCall.h
@@ -200,6 +200,9 @@ public:
void StyleSetHotSpot(int style, bool hotspot);
void StyleSetCheckMonospaced(int style, bool checkMonospaced);
bool StyleGetCheckMonospaced(int style);
+ void StyleSetInvisibleRepresentation(int style, const char *representation);
+ int StyleGetInvisibleRepresentation(int style, char *representation);
+ std::string StyleGetInvisibleRepresentation(int style);
void SetElementColour(Scintilla::Element element, ColourAlpha colourElement);
ColourAlpha ElementColour(Scintilla::Element element);
void ResetElementColour(Scintilla::Element element);
diff --git a/include/ScintillaMessages.h b/include/ScintillaMessages.h
index 663d6e12c..118655689 100644
--- a/include/ScintillaMessages.h
+++ b/include/ScintillaMessages.h
@@ -136,6 +136,8 @@ enum class Message {
StyleSetHotSpot = 2409,
StyleSetCheckMonospaced = 2254,
StyleGetCheckMonospaced = 2255,
+ StyleSetInvisibleRepresentation = 2256,
+ StyleGetInvisibleRepresentation = 2257,
SetElementColour = 2753,
GetElementColour = 2754,
ResetElementColour = 2755,
diff --git a/src/EditView.cxx b/src/EditView.cxx
index 86e59b4dc..8a7a50a7e 100644
--- a/src/EditView.cxx
+++ b/src/EditView.cxx
@@ -435,6 +435,21 @@ void LayoutSegments(IPositionCache *pCache,
std::string_view(&ll->chars[ts.start], ts.length), &ll->positions[ts.start + 1], multiThreaded);
}
}
+ } else if (vstyle.styles[ll->styles[ts.start]].invisibleRepresentation[0]) {
+ const int styleInvisible = ll->styles[ts.start];
+ const std::string_view text = vstyle.styles[styleInvisible].invisibleRepresentation;
+ XYPOSITION positionsRepr[Representation::maxLength + 1];
+ // invisibleRepresentation is UTF-8 which only matches cache if document is UTF-8
+ // or it only contains ASCII which is a subset of all currently supported encodings.
+ if (textUnicode || ViewIsASCII(text)) {
+ pCache->MeasureWidths(surface, vstyle, styleInvisible, text, positionsRepr, multiThreaded);
+ } else {
+ surface->MeasureWidthsUTF8(vstyle.styles[styleInvisible].font.get(), text, positionsRepr);
+ }
+ const XYPOSITION representationWidth = positionsRepr[text.length() - 1];
+ for (int ii = 0; ii < ts.length; ii++) {
+ ll->positions[ts.start + 1 + ii] = representationWidth;
+ }
}
}
}
@@ -2230,6 +2245,15 @@ void EditView::DrawForeground(Surface *surface, const EditModel &model, const Vi
surface->DrawTextNoClip(rcSegment, textFont,
rcSegment.top + vsDraw.maxAscent, text, textFore, textBack);
}
+ } else if (vsDraw.styles[styleMain].invisibleRepresentation[0]) {
+ const std::string_view text = vsDraw.styles[styleMain].invisibleRepresentation;
+ if (phasesDraw != PhasesDraw::One) {
+ surface->DrawTextTransparentUTF8(rcSegment, textFont,
+ rcSegment.top + vsDraw.maxAscent, text, textFore);
+ } else {
+ surface->DrawTextNoClipUTF8(rcSegment, textFont,
+ rcSegment.top + vsDraw.maxAscent, text, textFore, textBack);
+ }
}
if (vsDraw.viewWhitespace != WhiteSpace::Invisible ||
(inIndentation && vsDraw.viewIndentationGuides != IndentView::None)) {
diff --git a/src/Editor.cxx b/src/Editor.cxx
index 97c141bae..35148299e 100644
--- a/src/Editor.cxx
+++ b/src/Editor.cxx
@@ -5832,6 +5832,19 @@ void Editor::StyleSetMessage(Message iMessage, uptr_t wParam, sptr_t lParam) {
case Message::StyleSetVisible:
vs.styles[wParam].visible = lParam != 0;
break;
+ case Message::StyleSetInvisibleRepresentation: {
+ const char *utf8 = ConstCharPtrFromSPtr(lParam);
+ char *rep = vs.styles[wParam].invisibleRepresentation;
+ const int classified = UTF8Classify(utf8);
+ if (!(classified & UTF8MaskInvalid)) {
+ // valid UTF-8
+ int len = classified & UTF8MaskWidth;
+ while (len--)
+ *rep++ = *utf8++;
+ }
+ *rep = 0;
+ break;
+ }
case Message::StyleSetChangeable:
vs.styles[wParam].changeable = lParam != 0;
break;
@@ -5878,6 +5891,8 @@ sptr_t Editor::StyleGetMessage(Message iMessage, uptr_t wParam, sptr_t lParam) {
return vs.styles[wParam].visible ? 1 : 0;
case Message::StyleGetChangeable:
return vs.styles[wParam].changeable ? 1 : 0;
+ case Message::StyleGetInvisibleRepresentation:
+ return StringResult(lParam, vs.styles[wParam].invisibleRepresentation);
case Message::StyleGetHotSpot:
return vs.styles[wParam].hotspot ? 1 : 0;
case Message::StyleGetCheckMonospaced:
@@ -7328,6 +7343,7 @@ sptr_t Editor::WndProc(Message iMessage, uptr_t wParam, sptr_t lParam) {
case Message::StyleSetChangeable:
case Message::StyleSetHotSpot:
case Message::StyleSetCheckMonospaced:
+ case Message::StyleSetInvisibleRepresentation:
StyleSetMessage(iMessage, wParam, lParam);
break;
@@ -7347,6 +7363,7 @@ sptr_t Editor::WndProc(Message iMessage, uptr_t wParam, sptr_t lParam) {
case Message::StyleGetChangeable:
case Message::StyleGetHotSpot:
case Message::StyleGetCheckMonospaced:
+ case Message::StyleGetInvisibleRepresentation:
return StyleGetMessage(iMessage, wParam, lParam);
case Message::StyleResetDefault:
diff --git a/src/Style.cxx b/src/Style.cxx
index 76ffa4942..a45072292 100644
--- a/src/Style.cxx
+++ b/src/Style.cxx
@@ -72,7 +72,8 @@ Style::Style(const char *fontName_) noexcept :
caseForce(CaseForce::mixed),
visible(true),
changeable(true),
- hotspot(false) {
+ hotspot(false),
+ invisibleRepresentation{} {
}
void Style::Copy(std::shared_ptr font_, const FontMeasurements &fm_) noexcept {
diff --git a/src/Style.h b/src/Style.h
index 6cef6bb60..da8159d6c 100644
--- a/src/Style.h
+++ b/src/Style.h
@@ -51,6 +51,7 @@ public:
bool visible;
bool changeable;
bool hotspot;
+ char invisibleRepresentation[5];
std::shared_ptr font;
diff --git a/test/simpleTests.py b/test/simpleTests.py
index 6c55c7cf4..92d6a92a7 100644
--- a/test/simpleTests.py
+++ b/test/simpleTests.py
@@ -1956,6 +1956,7 @@ class TestStyleAttributes(unittest.TestCase):
self.ed.EmptyUndoBuffer()
self.testColour = 0x171615
self.testFont = b"Georgia"
+ self.testRepresentation = "\N{BULLET}".encode("utf-8")
def tearDown(self):
self.ed.StyleResetDefault()
@@ -1971,6 +1972,13 @@ class TestStyleAttributes(unittest.TestCase):
self.ed.StyleSetSizeFractional(self.ed.STYLE_DEFAULT, 1234)
self.assertEquals(self.ed.StyleGetSizeFractional(self.ed.STYLE_DEFAULT), 1234)
+ def testInvisibleRepresentation(self):
+ self.assertEquals(self.ed.StyleGetInvisibleRepresentation(self.ed.STYLE_DEFAULT), b"")
+ self.ed.StyleSetInvisibleRepresentation(self.ed.STYLE_DEFAULT, self.testRepresentation)
+ self.assertEquals(self.ed.StyleGetInvisibleRepresentation(self.ed.STYLE_DEFAULT), self.testRepresentation)
+ self.ed.StyleSetInvisibleRepresentation(self.ed.STYLE_DEFAULT, b"\000")
+ self.assertEquals(self.ed.StyleGetInvisibleRepresentation(self.ed.STYLE_DEFAULT), b"")
+
def testBold(self):
self.ed.StyleSetBold(self.ed.STYLE_DEFAULT, 1)
self.assertEquals(self.ed.StyleGetBold(self.ed.STYLE_DEFAULT), 1)
--
cgit v1.2.3