# HG changeset patch # User nyamatongwe # Date 1338077748 -36000 # Node ID e8e94310f16561ff62b74231f38cba806eddd54b # Parent 941018c1767a6f80bdc31e6c5ba8a17dd0b3197d Initial implementation of Unicode line ends. In UTF-8 mode the line ends NEL, LS, and PS are recognised. In all modes, FF is recognised as a line end. See the Unicode standard section 5.8 Newline Guidelines. diff -r 941018c1767a -r e8e94310f165 src/CellBuffer.cxx --- a/src/CellBuffer.cxx Sat May 26 15:32:33 2012 +1000 +++ b/src/CellBuffer.cxx Sun May 27 10:15:48 2012 +1000 @@ -16,6 +16,7 @@ #include "SplitVector.h" #include "Partitioning.h" #include "CellBuffer.h" +#include "UniConversion.h" #ifdef SCI_NAMESPACE using namespace Scintilla; @@ -331,6 +332,7 @@ CellBuffer::CellBuffer() { readOnly = false; + isUTF8 = false; collectingUndo = true; } @@ -458,6 +460,13 @@ style.ReAllocate(newSize); } +void CellBuffer::SetUTF8(bool isUTF8_) { + if (isUTF8 != isUTF8_) { + isUTF8 = isUTF8_; + ResetLineEnds(); + } +} + void CellBuffer::SetPerLine(PerLine *pl) { lv.SetPerLine(pl); } @@ -501,29 +510,31 @@ lv.RemoveLine(line); } -void CellBuffer::BasicInsertString(int position, const char *s, int insertLength) { - if (insertLength == 0) - return; - PLATFORM_ASSERT(insertLength > 0); +bool CellBuffer::UTF8LineEndOverlaps(int position) const { + unsigned char bytes[] = { + static_cast(substance.ValueAt(position-2)), + static_cast(substance.ValueAt(position-1)), + static_cast(substance.ValueAt(position)), + static_cast(substance.ValueAt(position+1)), + }; + return UTF8IsSeparator(bytes) || UTF8IsSeparator(bytes+1) || UTF8IsNEL(bytes+1); +} - substance.InsertFromArray(position, s, 0, insertLength); - style.InsertValue(position, insertLength, 0); +void CellBuffer::ResetLineEnds() { + // Reinitialize line data -- too much work to preserve + lv.Init(); - int lineInsert = lv.LineFromPosition(position) + 1; - bool atLineStart = lv.LineStart(lineInsert-1) == position; - // Point all the lines after the insertion point further along in the buffer - lv.InsertText(lineInsert-1, insertLength); - char chPrev = substance.ValueAt(position - 1); - char chAfter = substance.ValueAt(position + insertLength); - if (chPrev == '\r' && chAfter == '\n') { - // Splitting up a crlf pair at position - InsertLine(lineInsert, position, false); - lineInsert++; - } - char ch = ' '; - for (int i = 0; i < insertLength; i++) { - ch = s[i]; - if (ch == '\r') { + int position = 0; + int length = Length(); + int lineInsert = 1; + bool atLineStart = true; + lv.InsertText(lineInsert-1, length); + unsigned char chBeforePrev = 0; + unsigned char chPrev = 0; + unsigned char ch = ' '; + for (int i = 0; i < length; i++) { + ch = substance.ValueAt(position + i); + if ((ch == '\f') || (ch == '\r')) { InsertLine(lineInsert, (position + i) + 1, atLineStart); lineInsert++; } else if (ch == '\n') { @@ -534,7 +545,68 @@ InsertLine(lineInsert, (position + i) + 1, atLineStart); lineInsert++; } + } else if (isUTF8) { + unsigned char back3[3] = {chBeforePrev, chPrev, ch}; + if (UTF8IsSeparator(back3) || UTF8IsNEL(back3+1)) { + InsertLine(lineInsert, (position + i) + 1, atLineStart); + lineInsert++; + } } + chBeforePrev = chPrev; + chPrev = ch; + } +} + +void CellBuffer::BasicInsertString(int position, const char *s, int insertLength) { + if (insertLength == 0) + return; + PLATFORM_ASSERT(insertLength > 0); + + unsigned char chAfter = substance.ValueAt(position); + bool breakingUTF8LineEnd = false; + if (isUTF8 && UTF8IsTrailByte(chAfter)) { + breakingUTF8LineEnd = UTF8LineEndOverlaps(position); + } + + substance.InsertFromArray(position, s, 0, insertLength); + style.InsertValue(position, insertLength, 0); + + int lineInsert = lv.LineFromPosition(position) + 1; + bool atLineStart = lv.LineStart(lineInsert-1) == position; + // Point all the lines after the insertion point further along in the buffer + lv.InsertText(lineInsert-1, insertLength); + unsigned char chBeforePrev = substance.ValueAt(position - 2); + unsigned char chPrev = substance.ValueAt(position - 1); + if (chPrev == '\r' && chAfter == '\n') { + // Splitting up a crlf pair at position + InsertLine(lineInsert, position, false); + lineInsert++; + } + if (breakingUTF8LineEnd) { + RemoveLine(lineInsert); + } + unsigned char ch = ' '; + for (int i = 0; i < insertLength; i++) { + ch = s[i]; + if ((ch == '\f') || (ch == '\r')) { + InsertLine(lineInsert, (position + i) + 1, atLineStart); + lineInsert++; + } else if (ch == '\n') { + if (chPrev == '\r') { + // Patch up what was end of line + lv.SetLineStart(lineInsert - 1, (position + i) + 1); + } else { + InsertLine(lineInsert, (position + i) + 1, atLineStart); + lineInsert++; + } + } else if (isUTF8) { + unsigned char back3[3] = {chBeforePrev, chPrev, ch}; + if (UTF8IsSeparator(back3) || UTF8IsNEL(back3+1)) { + InsertLine(lineInsert, (position + i) + 1, atLineStart); + lineInsert++; + } + } + chBeforePrev = chPrev; chPrev = ch; } // Joining two lines where last insertion is cr and following substance starts with lf @@ -543,6 +615,22 @@ // End of line already in buffer so drop the newly created one RemoveLine(lineInsert - 1); } + } else if (isUTF8 && !UTF8IsAscii(chAfter)) { + // May have end of UTF-8 line end in buffer and start in insertion + for (int j = 0; j < UTF8SeparatorLength-1; j++) { + unsigned char chAt = substance.ValueAt(position + insertLength + j); + unsigned char back3[3] = {chBeforePrev, chPrev, chAt}; + if (UTF8IsSeparator(back3)) { + InsertLine(lineInsert, (position + insertLength + j) + 1, atLineStart); + lineInsert++; + } + if ((j == 0) && UTF8IsNEL(back3+1)) { + InsertLine(lineInsert, (position + insertLength + j) + 1, atLineStart); + lineInsert++; + } + chBeforePrev = chPrev; + chPrev = chAt; + } } } @@ -560,9 +648,9 @@ int lineRemove = lv.LineFromPosition(position) + 1; lv.InsertText(lineRemove-1, - (deleteLength)); - char chPrev = substance.ValueAt(position - 1); - char chBefore = chPrev; - char chNext = substance.ValueAt(position); + unsigned char chPrev = substance.ValueAt(position - 1); + unsigned char chBefore = chPrev; + unsigned char chNext = substance.ValueAt(position); bool ignoreNL = false; if (chPrev == '\r' && chNext == '\n') { // Move back one @@ -570,11 +658,18 @@ lineRemove++; ignoreNL = true; // First \n is not real deletion } + if (isUTF8 && UTF8IsTrailByte(chNext)) { + if (UTF8LineEndOverlaps(position)) { + RemoveLine(lineRemove); + } + } - char ch = chNext; + unsigned char ch = chNext; for (int i = 0; i < deleteLength; i++) { chNext = substance.ValueAt(position + i + 1); - if (ch == '\r') { + if (ch == '\f') { + RemoveLine(lineRemove); + } else if (ch == '\r') { if (chNext != '\n') { RemoveLine(lineRemove); } @@ -584,6 +679,14 @@ } else { RemoveLine(lineRemove); } + } else if (isUTF8) { + if (!UTF8IsAscii(ch)) { + unsigned char next3[3] = {ch, chNext, + static_cast(substance.ValueAt(position + i + 2))}; + if (UTF8IsSeparator(next3) || UTF8IsNEL(next3)) { + RemoveLine(lineRemove); + } + } } ch = chNext; diff -r 941018c1767a -r e8e94310f165 src/CellBuffer.h --- a/src/CellBuffer.h Sat May 26 15:32:33 2012 +1000 +++ b/src/CellBuffer.h Sun May 27 10:15:48 2012 +1000 @@ -136,12 +136,15 @@ SplitVector substance; SplitVector style; bool readOnly; + bool isUTF8; bool collectingUndo; UndoHistory uh; LineVector lv; + bool UTF8LineEndOverlaps(int position) const; + void ResetLineEnds(); /// Actions without undo void BasicInsertString(int position, const char *s, int insertLength); void BasicDeleteChars(int position, int deleteLength); @@ -162,6 +165,7 @@ int Length() const; void Allocate(int newSize); + void SetUTF8(bool isUTF8_); void SetPerLine(PerLine *pl); int Lines() const; int LineStart(int line) const; diff -r 941018c1767a -r e8e94310f165 src/Document.cxx --- a/src/Document.cxx Sat May 26 15:32:33 2012 +1000 +++ b/src/Document.cxx Sun May 27 10:15:48 2012 +1000 @@ -149,6 +149,16 @@ } } +bool Document::SetDBCSCodePage(int dbcsCodePage_) { + if (dbcsCodePage != dbcsCodePage_) { + dbcsCodePage = dbcsCodePage_; + cb.SetUTF8(SC_CP_UTF8 == dbcsCodePage); + return true; + } else { + return false; + } +} + void Document::InsertLine(int line) { for (int j=0; j(cb.CharAt(position-3)), + static_cast(cb.CharAt(position-2)), + static_cast(cb.CharAt(position-1)), + }; + if (UTF8IsSeparator(bytes)) { + return position - UTF8SeparatorLength; + } + if (UTF8IsNEL(bytes+1)) { + return position - UTF8NELLength; + } + } + position--; // Back over CR or LF // When line terminator is CR+LF, may need to go back one more if ((position > LineStart(line)) && (cb.CharAt(position - 1) == '\r')) { position--; @@ -281,6 +305,10 @@ return LineEnd(LineFromPosition(position)) == position; } +bool Document::IsPositionInLineEnd(int position) const { + return position >= LineEnd(LineFromPosition(position)); +} + int Document::VCHomePosition(int position) const { int line = LineFromPosition(position); int startPosition = LineStart(line); diff -r 941018c1767a -r e8e94310f165 src/Document.h --- a/src/Document.h Sat May 26 15:32:33 2012 +1000 +++ b/src/Document.h Sun May 27 10:15:48 2012 +1000 @@ -255,6 +255,7 @@ int SCI_METHOD Release(); virtual void Init(); + bool SetDBCSCodePage(int dbcsCodePage_); virtual void InsertLine(int line); virtual void RemoveLine(int line); @@ -339,6 +340,7 @@ int LineEnd(int line) const; int LineEndPosition(int position) const; bool IsLineEndPosition(int position) const; + bool IsPositionInLineEnd(int position) const; int VCHomePosition(int position) const; int SCI_METHOD SetLevel(int line, int level); diff -r 941018c1767a -r e8e94310f165 src/Editor.cxx --- a/src/Editor.cxx Sat May 26 15:32:33 2012 +1000 +++ b/src/Editor.cxx Sun May 27 10:15:48 2012 +1000 @@ -1600,7 +1600,7 @@ UndoGroup ug(pdoc); bool prevNonWS = true; for (int pos = targetStart; pos < targetEnd; pos++) { - if (IsEOLChar(pdoc->CharAt(pos))) { + if (pdoc->IsPositionInLineEnd(pos)) { targetEnd -= pdoc->LenChar(pos); pdoc->DelChar(pos); if (prevNonWS) { @@ -2187,7 +2187,7 @@ bool isBadUTF = isBadUTFNext; isBadUTFNext = IsUnicodeMode() && BadUTF(ll->chars + charInLine + 1, numCharsInLine - charInLine - 1, trailBytes); if ((ll->styles[charInLine] != ll->styles[charInLine + 1]) || - isControl || isControlNext || isBadUTF || isBadUTFNext) { + isControl || isControlNext || isBadUTF || isBadUTFNext || ((charInLine+1) >= numCharsBeforeEOL)) { ll->positions[startseg] = 0; if (vstyle.styles[ll->styles[charInLine]].visible) { if (isControl) { @@ -2208,7 +2208,7 @@ ll->positions + startseg + 1); } lastSegItalics = false; - } else if (isBadUTF) { + } else if ((isBadUTF) || (charInLine >= numCharsBeforeEOL)) { char hexits[4]; sprintf(hexits, "x%2X", ll->chars[charInLine] & 0xff); ll->positions[charInLine + 1] = @@ -2499,7 +2499,15 @@ rcSegment.left = xStart + ll->positions[eolPos] - subLineStart + virtualSpace; rcSegment.right = xStart + ll->positions[eolPos+1] - subLineStart + virtualSpace; blobsWidth += rcSegment.Width(); - const char *ctrlChar = ControlCharacterString(ll->chars[eolPos]); + char hexits[4]; + const char *ctrlChar; + unsigned char chEOL = ll->chars[eolPos]; + if (UTF8IsAscii(chEOL)) { + ctrlChar = ControlCharacterString(chEOL); + } else { + sprintf(hexits, "x%2X", chEOL); + ctrlChar = hexits; + } int styleMain = ll->styles[eolPos]; ColourDesired textBack = TextBackground(vsDraw, overrideBackground, background, eolInSelection, false, styleMain, eolPos, ll); ColourDesired textFore = vsDraw.styles[styleMain].fore; @@ -3972,7 +3980,7 @@ } } else if (inOverstrike) { if (positionInsert < pdoc->Length()) { - if (!IsEOLChar(pdoc->CharAt(positionInsert))) { + if (!pdoc->IsPositionInLineEnd(positionInsert)) { pdoc->DelChar(positionInsert); currentSel->ClearVirtualSpace(); } @@ -4217,7 +4225,7 @@ else sel.Range(r) = SelectionPosition(InsertSpace(sel.Range(r).caret.Position(), sel.Range(r).caret.VirtualSpace())); } - if ((sel.Count() == 1) || !IsEOLChar(pdoc->CharAt(sel.Range(r).caret.Position()))) { + if ((sel.Count() == 1) || !pdoc->IsPositionInLineEnd(sel.Range(r).caret.Position())) { pdoc->DelChar(sel.Range(r).caret.Position()); sel.Range(r).ClearVirtualSpace(); } // else multiple selection so don't eat line ends @@ -8005,8 +8013,12 @@ case SCI_SETCODEPAGE: if (ValidCodePage(wParam)) { - pdoc->dbcsCodePage = wParam; - InvalidateStyleRedraw(); + if (pdoc->SetDBCSCodePage(wParam)) { + cs.Clear(); + cs.InsertLines(0, pdoc->LinesTotal() - 1); + SetAnnotationHeights(0, pdoc->LinesTotal()); + InvalidateStyleRedraw(); + } } break; diff -r 941018c1767a -r e8e94310f165 src/PositionCache.h --- a/src/PositionCache.h Sat May 26 15:32:33 2012 +1000 +++ b/src/PositionCache.h Sun May 27 10:15:48 2012 +1000 @@ -13,7 +13,7 @@ #endif static inline bool IsEOLChar(char ch) { - return (ch == '\r') || (ch == '\n'); + return (ch == '\r') || (ch == '\n') || (ch == '\f'); } /** diff -r 941018c1767a -r e8e94310f165 src/UniConversion.h --- a/src/UniConversion.h Sat May 26 15:32:33 2012 +1000 +++ b/src/UniConversion.h Sun May 27 10:15:48 2012 +1000 @@ -26,3 +26,16 @@ enum { UTF8MaskWidth=0x7, UTF8MaskInvalid=0x8 }; int UTF8Classify(const unsigned char *us, int len); + +// Line separator is U+2028 \xe2\x80\xa8 +// Paragraph separator is U+2029 \xe2\x80\xa9 +const int UTF8SeparatorLength = 3; +inline bool UTF8IsSeparator(const unsigned char *us) { + return (us[0] == 0xe2) && (us[1] == 0x80) && ((us[2] == 0xa8) || (us[2] == 0xa9)); +} + +// NEL is U+0085 \xc2\x85 +const int UTF8NELLength = 2; +inline bool UTF8IsNEL(const unsigned char *us) { + return (us[0] == 0xc2) && (us[1] == 0x85); +} diff -r 941018c1767a -r e8e94310f165 test/simpleTests.py --- a/test/simpleTests.py Sat May 26 15:32:33 2012 +1000 +++ b/test/simpleTests.py Sun May 27 10:15:48 2012 +1000 @@ -282,6 +282,185 @@ self.assertEquals(self.ed.Contents(), b"x" + lineEnds[lineEndType] + b"y") self.assertEquals(self.ed.LineLength(0), 1 + len(lineEnds[lineEndType])) + # Several tests for unicode line ends U+2028 and U+2029 + + def testUnicodeLineEnds(self): + # Add two lines separated with U+2028 and ensure it is seen as two lines + # Then remove U+2028 and should be just 1 lines + self.ed.SetCodePage(65001) + self.ed.AddText(5, b"x\xe2\x80\xa8y") + self.assertEquals(self.ed.LineCount, 2) + self.assertEquals(self.ed.GetLineEndPosition(0), 1) + self.assertEquals(self.ed.GetLineEndPosition(1), 5) + self.assertEquals(self.ed.LineLength(0), 4) + self.assertEquals(self.ed.LineLength(1), 1) + self.ed.TargetStart = 1 + self.ed.TargetEnd = 4 + self.ed.ReplaceTarget(0, b"") + self.assertEquals(self.ed.LineCount, 1) + self.assertEquals(self.ed.LineLength(0), 2) + self.assertEquals(self.ed.GetLineEndPosition(0), 2) + + def testUnicodeLineEndsWithCodePage0(self): + # Try the Unicode line ends when not in Unicode mode -> should remain 1 line + self.ed.SetCodePage(0) + self.ed.AddText(5, b"x\xe2\x80\xa8y") + self.assertEquals(self.ed.LineCount, 1) + self.ed.AddText(4, b"x\xc2\x85y") + self.assertEquals(self.ed.LineCount, 1) + + def testUnicodeLineEndsSwitchToUnicodeAndBack(self): + # Add the Unicode line ends when not in Unicode mode + self.ed.SetCodePage(0) + self.ed.AddText(5, b"x\xe2\x80\xa8y") + self.assertEquals(self.ed.LineCount, 1) + # Into UTF-8 mode - should now be interpreting as two lines + self.ed.SetCodePage(65001) + self.assertEquals(self.ed.LineCount, 2) + # Back to code page 0 and 1 line + self.ed.SetCodePage(0) + self.assertEquals(self.ed.LineCount, 1) + + def testUFragmentedEOLCompletion(self): + # Add 2 starting bytes of UTF-8 line end then complete it + self.ed.ClearAll() + self.ed.AddText(4, b"x\xe2\x80y") + self.assertEquals(self.ed.LineCount, 1) + self.assertEquals(self.ed.GetLineEndPosition(0), 4) + self.ed.SetSel(3,3) + self.ed.AddText(1, b"\xa8") + self.assertEquals(self.ed.Contents(), b"x\xe2\x80\xa8y") + self.assertEquals(self.ed.LineCount, 2) + + # Add 1 starting bytes of UTF-8 line end then complete it + self.ed.ClearAll() + self.ed.AddText(3, b"x\xe2y") + self.assertEquals(self.ed.LineCount, 1) + self.assertEquals(self.ed.GetLineEndPosition(0), 3) + self.ed.SetSel(2,2) + self.ed.AddText(2, b"\x80\xa8") + self.assertEquals(self.ed.Contents(), b"x\xe2\x80\xa8y") + self.assertEquals(self.ed.LineCount, 2) + + def testUFragmentedEOLStart(self): + # Add end of UTF-8 line end then insert start + self.ed.SetCodePage(65001) + self.assertEquals(self.ed.LineCount, 1) + self.ed.AddText(4, b"x\x80\xa8y") + self.assertEquals(self.ed.LineCount, 1) + self.ed.SetSel(1,1) + self.ed.AddText(1, b"\xe2") + self.assertEquals(self.ed.LineCount, 2) + + def testUBreakApartEOL(self): + # Add two lines separated by U+2029 then remove and add back each byte ensuring + # only one line after each removal of any byte in line end and 2 lines after reinsertion + self.ed.SetCodePage(65001) + text = b"x\xe2\x80\xa9y"; + self.ed.AddText(5, text) + self.assertEquals(self.ed.LineCount, 2) + + for i in range(len(text)): + self.ed.TargetStart = i + self.ed.TargetEnd = i + 1 + self.ed.ReplaceTarget(0, b"") + if i in [0, 4]: + # Removing text characters does not change number of lines + self.assertEquals(self.ed.LineCount, 2) + else: + # Removing byte from line end, removes 1 line + self.assertEquals(self.ed.LineCount, 1) + + self.ed.TargetEnd = i + self.ed.ReplaceTarget(1, text[i:i+1]) + self.assertEquals(self.ed.LineCount, 2) + + def testURemoveEOLFragment(self): + # Add UTF-8 line end then delete each byte causing line end to disappear + self.ed.SetCodePage(65001) + for i in range(3): + self.ed.ClearAll() + self.ed.AddText(5, b"x\xe2\x80\xa8y") + self.assertEquals(self.ed.LineCount, 2) + self.ed.TargetStart = i+1 + self.ed.TargetEnd = i+2 + self.ed.ReplaceTarget(0, b"") + self.assertEquals(self.ed.LineCount, 1) + + # Several tests for unicode NEL line ends U+0085 + + def testNELLineEnds(self): + # Add two lines separated with U+0085 and ensure it is seen as two lines + # Then remove U+0085 and should be just 1 lines + self.ed.SetCodePage(65001) + self.ed.AddText(4, b"x\xc2\x85y") + self.assertEquals(self.ed.LineCount, 2) + self.assertEquals(self.ed.GetLineEndPosition(0), 1) + self.assertEquals(self.ed.GetLineEndPosition(1), 4) + self.assertEquals(self.ed.LineLength(0), 3) + self.assertEquals(self.ed.LineLength(1), 1) + self.ed.TargetStart = 1 + self.ed.TargetEnd = 3 + self.ed.ReplaceTarget(0, b"") + self.assertEquals(self.ed.LineCount, 1) + self.assertEquals(self.ed.LineLength(0), 2) + self.assertEquals(self.ed.GetLineEndPosition(0), 2) + + def testNELFragmentedEOLCompletion(self): + # Add starting byte of UTF-8 NEL then complete it + self.ed.AddText(3, b"x\xc2y") + self.assertEquals(self.ed.LineCount, 1) + self.assertEquals(self.ed.GetLineEndPosition(0), 3) + self.ed.SetSel(2,2) + self.ed.AddText(1, b"\x85") + self.assertEquals(self.ed.Contents(), b"x\xc2\x85y") + self.assertEquals(self.ed.LineCount, 2) + + def testNELFragmentedEOLStart(self): + # Add end of UTF-8 NEL then insert start + self.ed.SetCodePage(65001) + self.assertEquals(self.ed.LineCount, 1) + self.ed.AddText(4, b"x\x85y") + self.assertEquals(self.ed.LineCount, 1) + self.ed.SetSel(1,1) + self.ed.AddText(1, b"\xc2") + self.assertEquals(self.ed.LineCount, 2) + + def testNELBreakApartEOL(self): + # Add two lines separated by U+0085 then remove and add back each byte ensuring + # only one line after each removal of any byte in line end and 2 lines after reinsertion + self.ed.SetCodePage(65001) + text = b"x\xc2\x85y"; + self.ed.AddText(4, text) + self.assertEquals(self.ed.LineCount, 2) + + for i in range(len(text)): + self.ed.TargetStart = i + self.ed.TargetEnd = i + 1 + self.ed.ReplaceTarget(0, b"") + if i in [0, 3]: + # Removing text characters does not change number of lines + self.assertEquals(self.ed.LineCount, 2) + else: + # Removing byte from line end, removes 1 line + self.assertEquals(self.ed.LineCount, 1) + + self.ed.TargetEnd = i + self.ed.ReplaceTarget(1, text[i:i+1]) + self.assertEquals(self.ed.LineCount, 2) + + def testNELRemoveEOLFragment(self): + # Add UTF-8 NEL then delete each byte causing line end to disappear + self.ed.SetCodePage(65001) + for i in range(2): + self.ed.ClearAll() + self.ed.AddText(4, b"x\xc2\x85y") + self.assertEquals(self.ed.LineCount, 2) + self.ed.TargetStart = i+1 + self.ed.TargetEnd = i+2 + self.ed.ReplaceTarget(0, b"") + self.assertEquals(self.ed.LineCount, 1) + def testGoto(self): self.ed.AddText(5, b"a\nb\nc") self.assertEquals(self.ed.CurrentPos, 5)