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]; | 
