aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--gtk/ScintillaGTK.cxx52
-rw-r--r--src/Editor.cxx53
-rw-r--r--src/Editor.h4
-rw-r--r--src/PositionCache.cxx1
-rw-r--r--src/ScintillaBase.cxx1
-rw-r--r--test/simpleTests.py62
-rw-r--r--win32/ScintillaWin.cxx93
7 files changed, 252 insertions, 14 deletions
diff --git a/gtk/ScintillaGTK.cxx b/gtk/ScintillaGTK.cxx
index 1f57258c1..f4e762832 100644
--- a/gtk/ScintillaGTK.cxx
+++ b/gtk/ScintillaGTK.cxx
@@ -195,6 +195,7 @@ private:
void NotifyKey(int key, int modifiers);
void NotifyURIDropped(const char *list);
const char *CharacterSetID() const;
+ virtual std::string CaseMapString(const std::string &s, int caseMapping);
virtual int KeyDefault(int key, int modifiers);
virtual void CopyToClipboard(const SelectionText &selectedText);
virtual void Copy();
@@ -892,6 +893,7 @@ void ScintillaGTK::StartDrag() {
#ifdef USE_CONVERTER
static char *ConvertText(int *lenResult, char *s, size_t len, const char *charSetDest,
const char *charSetSource, bool transliterations) {
+ // s is not const because of different versions of iconv disagreeing about const
*lenResult = 0;
char *destForm = 0;
Converter conv(charSetDest, charSetSource, transliterations);
@@ -1336,6 +1338,56 @@ const char *ScintillaGTK::CharacterSetID() const {
return ::CharacterSetID(vs.styles[STYLE_DEFAULT].characterSet);
}
+std::string ScintillaGTK::CaseMapString(const std::string &s, int caseMapping) {
+#if GTK_MAJOR_VERSION < 2
+ return Editor::CaseMapString(s, caseMapping);
+#else
+ if (s.size() == 0)
+ return std::string();
+
+ if (caseMapping == cmSame)
+ return s;
+
+ const char *needsFree1 = 0; // Must be freed with delete []
+ const char *charSetBuffer = CharacterSetID();
+ const char *sUTF8 = s.c_str();
+ int rangeBytes = s.size();
+
+ int convertedLength = rangeBytes;
+ // Change text to UTF-8
+ if (!IsUnicodeMode()) {
+ // Need to convert
+ if (*charSetBuffer) {
+ sUTF8 = ConvertText(&convertedLength, const_cast<char *>(s.c_str()), rangeBytes,
+ "UTF-8", charSetBuffer, false);
+ needsFree1 = sUTF8;
+ }
+ }
+ gchar *mapped; // Must be freed with g_free
+ if (caseMapping == cmUpper) {
+ mapped = g_utf8_strup(sUTF8, convertedLength);
+ } else {
+ mapped = g_utf8_strdown(sUTF8, convertedLength);
+ }
+ int mappedLength = strlen(mapped);
+ char *mappedBack = mapped;
+
+ char *needsFree2 = 0; // Must be freed with delete []
+ if (!IsUnicodeMode()) {
+ if (*charSetBuffer) {
+ mappedBack = ConvertText(&mappedLength, mapped, mappedLength, charSetBuffer, "UTF-8", false);
+ needsFree2 = mappedBack;
+ }
+ }
+
+ std::string ret(mappedBack, mappedLength);
+ g_free(mapped);
+ delete []needsFree1;
+ delete []needsFree2;
+ return ret;
+#endif
+}
+
int ScintillaGTK::KeyDefault(int key, int modifiers) {
if (!(modifiers & SCI_CTRL) && !(modifiers & SCI_ALT)) {
if (key < 256) {
diff --git a/src/Editor.cxx b/src/Editor.cxx
index ad0ca635b..4bdbecda8 100644
--- a/src/Editor.cxx
+++ b/src/Editor.cxx
@@ -4536,14 +4536,36 @@ void Editor::PageMove(int direction, Selection::selTypes selt, bool stuttered) {
}
}
-void Editor::ChangeCaseOfSelection(bool makeUpperCase) {
+void Editor::ChangeCaseOfSelection(int caseMapping) {
UndoGroup ug(pdoc);
for (size_t r=0; r<sel.Count(); r++) {
SelectionRange current = sel.Range(r);
- pdoc->ChangeCase(Range(current.Start().Position(), current.End().Position()),
- makeUpperCase);
- // Automatic movement cuts off last character so reset to exactly the same as it was.
- sel.Range(r) = current;
+ SelectionRange currentNoVS = current;
+ currentNoVS.ClearVirtualSpace();
+ char *text = CopyRange(currentNoVS.Start().Position(), currentNoVS.End().Position());
+ size_t rangeBytes = currentNoVS.Length();
+ if (rangeBytes > 0) {
+ std::string sText(text, rangeBytes);
+
+ std::string sMapped = CaseMapString(sText, caseMapping);
+
+ if (sMapped != sText) {
+ size_t firstDifference = 0;
+ while (sMapped[firstDifference] == sText[firstDifference])
+ firstDifference++;
+ size_t lastDifference = sMapped.size() - 1;
+ while (sMapped[lastDifference] == sText[lastDifference])
+ lastDifference--;
+ size_t endSame = sMapped.size() - 1 - lastDifference;
+ pdoc->DeleteChars(currentNoVS.Start().Position() + firstDifference,
+ rangeBytes - firstDifference - endSame);
+ pdoc->InsertString(currentNoVS.Start().Position() + firstDifference,
+ sMapped.c_str() + firstDifference, lastDifference - firstDifference + 1);
+ // Automatic movement changes selection so reset to exactly the same as it was.
+ sel.Range(r) = current;
+ }
+ }
+ delete []text;
}
}
@@ -5137,10 +5159,10 @@ int Editor::KeyCommand(unsigned int iMessage) {
Duplicate(false);
break;
case SCI_LOWERCASE:
- ChangeCaseOfSelection(false);
+ ChangeCaseOfSelection(cmLower);
break;
case SCI_UPPERCASE:
- ChangeCaseOfSelection(true);
+ ChangeCaseOfSelection(cmUpper);
break;
case SCI_WORDPARTLEFT:
MovePositionTo(MovePositionSoVisible(pdoc->WordPartLeft(sel.MainCaret()), -1));
@@ -5366,6 +5388,23 @@ long Editor::SearchText(
return pos;
}
+std::string Editor::CaseMapString(const std::string &s, int caseMapping) {
+ std::string ret(s);
+ for (size_t i=0; i<ret.size(); i++) {
+ switch (caseMapping) {
+ case cmUpper:
+ if (ret[i] >= 'a' && ret[i] <= 'z')
+ ret[i] = static_cast<char>(ret[i] - 'a' + 'A');
+ break;
+ case cmLower:
+ if (ret[i] >= 'A' && ret[i] <= 'Z')
+ ret[i] = static_cast<char>(ret[i] - 'A' + 'a');
+ break;
+ }
+ }
+ return ret;
+}
+
/**
* Search for text in the target range of the document.
* @return The position of the found text, -1 if not found.
diff --git a/src/Editor.h b/src/Editor.h
index 6e6c25f0f..053b10a9e 100644
--- a/src/Editor.h
+++ b/src/Editor.h
@@ -405,7 +405,9 @@ protected: // ScintillaBase subclass needs access to much of Editor
void NotifyMacroRecord(unsigned int iMessage, uptr_t wParam, sptr_t lParam);
void PageMove(int direction, Selection::selTypes sel=Selection::noSel, bool stuttered = false);
- void ChangeCaseOfSelection(bool makeUpperCase);
+ enum { cmSame, cmUpper, cmLower } caseMap;
+ virtual std::string CaseMapString(const std::string &s, int caseMapping);
+ void ChangeCaseOfSelection(int caseMapping);
void LineTranspose();
void Duplicate(bool forLine);
virtual void CancelModes();
diff --git a/src/PositionCache.cxx b/src/PositionCache.cxx
index 6b48f1859..d27205b5f 100644
--- a/src/PositionCache.cxx
+++ b/src/PositionCache.cxx
@@ -10,6 +10,7 @@
#include <stdio.h>
#include <ctype.h>
+#include <string>
#include <vector>
#include "Platform.h"
diff --git a/src/ScintillaBase.cxx b/src/ScintillaBase.cxx
index 714e989e7..cecfc0952 100644
--- a/src/ScintillaBase.cxx
+++ b/src/ScintillaBase.cxx
@@ -10,6 +10,7 @@
#include <stdio.h>
#include <ctype.h>
+#include <string>
#include <vector>
#include "Platform.h"
diff --git a/test/simpleTests.py b/test/simpleTests.py
index 99f25afd6..f9a67f59b 100644
--- a/test/simpleTests.py
+++ b/test/simpleTests.py
@@ -1047,6 +1047,68 @@ class TestMultiSelection(unittest.TestCase):
self.assertEquals(self.ed.GetSelectionNCaret(0), 3)
self.assertEquals(self.ed.GetSelectionNCaretVirtualSpace(0), 0)
+class TestCaseMapping(unittest.TestCase):
+ def setUp(self):
+ self.xite = XiteWin.xiteFrame
+ self.ed = self.xite.ed
+ self.ed.ClearAll()
+ self.ed.EmptyUndoBuffer()
+
+ def tearDown(self):
+ self.ed.SetCodePage(0)
+ self.ed.StyleSetCharacterSet(self.ed.STYLE_DEFAULT, self.ed.SC_CHARSET_DEFAULT)
+
+ def testEmpty(self):
+ # Trying to upper case an empty string caused a crash at one stage
+ t = b"x"
+ self.ed.SetText(len(t), t)
+ self.ed.UpperCase()
+ self.assertEquals(self.ed.Contents(), b"x")
+
+ def testASCII(self):
+ t = b"x"
+ self.ed.SetText(len(t), t)
+ self.ed.SetSel(0,1)
+ self.ed.UpperCase()
+ self.assertEquals(self.ed.Contents(), b"X")
+
+ def testLatin1(self):
+ t = "å".encode("Latin-1")
+ r = "Å".encode("Latin-1")
+ self.ed.SetText(len(t), t)
+ self.ed.SetSel(0,1)
+ self.ed.UpperCase()
+ self.assertEquals(self.ed.Contents(), r)
+
+ def testRussian(self):
+ self.ed.StyleSetCharacterSet(self.ed.STYLE_DEFAULT, self.ed.SC_CHARSET_RUSSIAN)
+ t = "Б".encode("Windows-1251")
+ r = "б".encode("Windows-1251")
+ self.ed.SetText(len(t), t)
+ self.ed.SetSel(0,1)
+ self.ed.LowerCase()
+ self.assertEquals(self.ed.Contents(), r)
+
+ def testUTF(self):
+ self.ed.SetCodePage(65001)
+ t = "å".encode("UTF-8")
+ r = "Å".encode("UTF-8")
+ self.ed.SetText(len(t), t)
+ self.ed.SetSel(0,2)
+ self.ed.UpperCase()
+ self.assertEquals(self.ed.Contents(), r)
+
+ def testUTFDifferentLength(self):
+ self.ed.SetCodePage(65001)
+ t = "ı".encode("UTF-8")
+ r = "I".encode("UTF-8")
+ self.ed.SetText(len(t), t)
+ self.assertEquals(self.ed.Length, 2)
+ self.ed.SetSel(0,2)
+ self.ed.UpperCase()
+ self.assertEquals(self.ed.Length, 1)
+ self.assertEquals(self.ed.Contents(), r)
+
class TestLexer(unittest.TestCase):
def setUp(self):
self.xite = XiteWin.xiteFrame
diff --git a/win32/ScintillaWin.cxx b/win32/ScintillaWin.cxx
index d0f99c532..a509ef0ec 100644
--- a/win32/ScintillaWin.cxx
+++ b/win32/ScintillaWin.cxx
@@ -197,6 +197,7 @@ class ScintillaWin :
virtual void StartDrag();
sptr_t WndPaint(uptr_t wParam);
sptr_t HandleComposition(uptr_t wParam, sptr_t lParam);
+ UINT CodePageOfDocument();
virtual bool ValidCodePage(int codePage) const;
virtual sptr_t DefWndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam);
virtual bool SetIdle(bool on);
@@ -215,6 +216,7 @@ class ScintillaWin :
virtual int GetCtrlID();
virtual void NotifyParent(SCNotification scn);
virtual void NotifyDoubleClick(Point pt, bool shift, bool ctrl, bool alt);
+ virtual std::string CaseMapString(const std::string &s, int caseMapping);
virtual void Copy();
virtual void CopyAllowLine();
virtual bool CanPaste();
@@ -587,6 +589,10 @@ static unsigned int SciMessageFromEM(unsigned int iMessage) {
}
static UINT CodePageFromCharSet(DWORD characterSet, UINT documentCodePage) {
+ if (documentCodePage == SC_CP_UTF8) {
+ // The system calls here are a little slow so avoid if known case.
+ return SC_CP_UTF8;
+ }
CHARSETINFO ci = { 0, 0, { { 0, 0, 0, 0 }, { 0, 0 } } };
BOOL bci = ::TranslateCharsetInfo((DWORD*)characterSet,
&ci, TCI_SRCCHARSET);
@@ -604,6 +610,10 @@ static UINT CodePageFromCharSet(DWORD characterSet, UINT documentCodePage) {
return cp;
}
+UINT ScintillaWin::CodePageOfDocument() {
+ return CodePageFromCharSet(vs.styles[STYLE_DEFAULT].characterSet, pdoc->dbcsCodePage);
+}
+
sptr_t ScintillaWin::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
try {
//Platform::DebugPrintf("S M:%x WP:%x L:%x\n", iMessage, wParam, lParam);
@@ -800,8 +810,7 @@ sptr_t ScintillaWin::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam
UTF8FromUTF16(wcs, 1, utfval, len);
AddCharUTF(utfval, len);
} else {
- UINT cpDest = CodePageFromCharSet(
- vs.styles[STYLE_DEFAULT].characterSet, pdoc->dbcsCodePage);
+ UINT cpDest = CodePageOfDocument();
char inBufferCP[20];
int size = ::WideCharToMultiByte(cpDest,
0, wcs, 1, inBufferCP, sizeof(inBufferCP) - 1, 0, 0);
@@ -1289,6 +1298,80 @@ void ScintillaWin::NotifyDoubleClick(Point pt, bool shift, bool ctrl, bool alt)
MAKELPARAM(pt.x, pt.y));
}
+std::string ScintillaWin::CaseMapString(const std::string &s, int caseMapping) {
+ if (s.size() == 0)
+ return std::string();
+
+ if (caseMapping == cmSame)
+ return s;
+
+ UINT cpDoc = CodePageOfDocument();
+
+ unsigned int lengthUTF16 = ::MultiByteToWideChar(cpDoc, 0, s.c_str(), s.size(), NULL, NULL);
+ if (lengthUTF16 == 0) // Failed to convert
+ return s;
+
+ DWORD mapFlags = LCMAP_LINGUISTIC_CASING |
+ ((caseMapping == cmUpper) ? LCMAP_UPPERCASE : LCMAP_LOWERCASE);
+
+ // Many conversions performed by search function are short so optimize this case.
+ enum { shortSize=20 };
+
+ if (s.size() > shortSize) {
+ // Use dynamic allocations for long strings
+
+ // Change text to UTF-16
+ std::vector<wchar_t> vwcText(lengthUTF16);
+ ::MultiByteToWideChar(cpDoc, 0, s.c_str(), s.size(), &vwcText[0], lengthUTF16);
+
+ // Change case
+ int charsConverted = ::LCMapStringW(LOCALE_SYSTEM_DEFAULT, mapFlags,
+ &vwcText[0], lengthUTF16, NULL, 0);
+ std::vector<wchar_t> vwcConverted(charsConverted);
+ ::LCMapStringW(LOCALE_SYSTEM_DEFAULT, mapFlags,
+ &vwcText[0], lengthUTF16, &vwcConverted[0], charsConverted);
+
+ // Change back to document encoding
+ unsigned int lengthConverted = ::WideCharToMultiByte(cpDoc, 0,
+ &vwcConverted[0], vwcConverted.size(),
+ NULL, 0, NULL, 0);
+ std::vector<char> vcConverted(lengthConverted);
+ ::WideCharToMultiByte(cpDoc, 0,
+ &vwcConverted[0], vwcConverted.size(),
+ &vcConverted[0], vcConverted.size(), NULL, 0);
+
+ return std::string(&vcConverted[0], vcConverted.size());
+
+ } else {
+ // Use static allocations for short strings as much faster
+ // A factor of 15 for single character strings
+
+ // Change text to UTF-16
+ wchar_t vwcText[shortSize];
+ ::MultiByteToWideChar(cpDoc, 0, s.c_str(), s.size(), vwcText, lengthUTF16);
+
+ // Change case
+ int charsConverted = ::LCMapStringW(LOCALE_SYSTEM_DEFAULT, mapFlags,
+ vwcText, lengthUTF16, NULL, 0);
+ // Full mapping may produce up to 3 characters per input character
+ wchar_t vwcConverted[shortSize*3];
+ ::LCMapStringW(LOCALE_SYSTEM_DEFAULT, mapFlags, vwcText, lengthUTF16,
+ vwcConverted, charsConverted);
+
+ // Change back to document encoding
+ unsigned int lengthConverted = ::WideCharToMultiByte(cpDoc, 0,
+ vwcConverted, charsConverted,
+ NULL, 0, NULL, 0);
+ // Each UTF-16 code unit may need up to 3 bytes in UTF-8
+ char vcConverted[shortSize * 3 * 3];
+ ::WideCharToMultiByte(cpDoc, 0,
+ vwcConverted, charsConverted,
+ vcConverted, lengthConverted, NULL, 0);
+
+ return std::string(vcConverted, lengthConverted);
+ }
+}
+
void ScintillaWin::Copy() {
//Platform::DebugPrintf("Copy\n");
if (!sel.Empty()) {
@@ -1409,8 +1492,7 @@ void ScintillaWin::Paste() {
} else {
// CF_UNICODETEXT available, but not in Unicode mode
// Convert from Unicode to current Scintilla code page
- UINT cpDest = CodePageFromCharSet(
- vs.styles[STYLE_DEFAULT].characterSet, pdoc->dbcsCodePage);
+ UINT cpDest = CodePageOfDocument();
len = ::WideCharToMultiByte(cpDest, 0, uptr, memUSelection.Size() / 2,
NULL, 0, NULL, NULL) - 1; // subtract 0 terminator
putf = new char[len + 1];
@@ -2237,8 +2319,7 @@ STDMETHODIMP ScintillaWin::Drop(LPDATAOBJECT pIDataSource, DWORD grfKeyState,
// Default Scintilla behavior in Unicode mode
// CF_UNICODETEXT available, but not in Unicode mode
// Convert from Unicode to current Scintilla code page
- UINT cpDest = CodePageFromCharSet(
- vs.styles[STYLE_DEFAULT].characterSet, pdoc->dbcsCodePage);
+ UINT cpDest = CodePageOfDocument();
int tlen = ::WideCharToMultiByte(cpDest, 0, udata, -1,
NULL, 0, NULL, NULL) - 1; // subtract 0 terminator
data = new char[tlen + 1];