diff options
-rw-r--r-- | gtk/ScintillaGTK.cxx | 52 | ||||
-rw-r--r-- | src/Editor.cxx | 53 | ||||
-rw-r--r-- | src/Editor.h | 4 | ||||
-rw-r--r-- | src/PositionCache.cxx | 1 | ||||
-rw-r--r-- | src/ScintillaBase.cxx | 1 | ||||
-rw-r--r-- | test/simpleTests.py | 62 | ||||
-rw-r--r-- | win32/ScintillaWin.cxx | 93 |
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]; |