diff --git a/README.md b/README.md index 885e4e4d..2a160cb7 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ I'm sorry, the root makefile assumes it is executed from the `project` directory * Ncurses-based terminal support. * Mouse and key modifiers support on the linux console. * Overall better display performance than SET's or Sergio Sigala's ports. -* Reads UTF-8 input from the terminal and displays UTF-8 text, but still works with 8-bit ASCII characters internally. +* UTF-8 support both in terminal I/O and the API. * Implementation of some Borland C++ RTL functions: `findfirst`, `findnext`, `fnsplit`, `_dos_findfirst`, `_dos_findnext`, `getdisk`, `setdisk`, `getcurdir`, `filelenght`. * Accepts both Unix and Windows-style file paths in 'Open File' dialogs. * Simple segmentation fault handler that gives you the chance to 'continue running' the application if something goes wrong. @@ -341,11 +341,9 @@ The functions above depend on the following lower-level functions. You will need size_t TText::next(TStringView text); size_t TText::prev(TStringView text, size_t index); size_t TText::wseek(TStringView text, int count, Boolean incRemainder=True); -#ifndef __BORLANDC__ void TText::eat(TScreenCell *cell, size_t n, size_t &width, TStringView text, size_t &bytes); void TText::next(TStringView text, size_t &bytes, size_t &width); void TText::wseek(TStringView text, size_t &index, size_t &remainder, int count); -#endif ``` For drawing `TScreenCell` buffers directly, the following methods are available: @@ -433,4 +431,4 @@ Support for creating Unicode-aware views is in place, but some views that are pa * `TInputLine` can display and process Unicode text. * Word wrapping in `TStaticText` and `THelpViewer` is Unicode-aware. * Automatic shortcuts in `TMenuBox` won't work with Unicode text, as shortcuts are still compared against `event.keyDown.charScan.charCode`. -* `TEditor` assumes a single-byte encoding both when handling input events and when displaying text. So it won't display UTF-8 but at least it has a consistent behaviour. +* `TEditor` instances (as in the `tvedit` application) are in UTF-8 mode by default. Press `Ctrl+P` to switch back to single-byte mode. This only changes how the document is displayed and the encoding of user input; it does not alter the document. diff --git a/include/tvision/editors.h b/include/tvision/editors.h index 51027d92..34168ac3 100644 --- a/include/tvision/editors.h +++ b/include/tvision/editors.h @@ -77,7 +77,8 @@ const int cmIndentMode = 522, cmUpdateTitle = 523, cmSelectAll = 524, - cmDelWordLeft = 525; + cmDelWordLeft = 525, + cmEncoding = 526; const int edOutOfMemory = 0, @@ -216,7 +217,7 @@ class TEditor : public TView void doUpdate(); void doSearchReplace(); void drawLines( int, int, uint ); - void formatLine(ushort *, uint, int, ushort ); + void formatLine(TScreenCell *, uint, int, ushort ); void find(); uint getMousePtr( TPoint ); Boolean hasSelection(); @@ -238,6 +239,7 @@ class TEditor : public TView void setBufLen( uint ); void setCurPtr( uint, uchar ); void startSelect(); + void toggleEncoding(); void toggleInsMode(); void unlock(); void update( uchar ); @@ -272,6 +274,13 @@ class TEditor : public TView const char* eolBytes; uint eolSize; + Boolean encSingleByte; + char charsBuf[4]; + void nextChar( TStringView, uint &P, uint &width ); + void formatCell( TScreenCell*, uint, uint&, TStringView, uint& , TCellAttribs ); + TStringView bufChars( uint ); + TStringView bufPrevChars( uint ); + static TEditorDialog _NEAR editorDialog; static ushort _NEAR editorFlags; static char _NEAR findStr[maxFindStrLen]; diff --git a/include/tvision/scrncell.h b/include/tvision/scrncell.h index 4d15ef63..712f11e8 100644 --- a/include/tvision/scrncell.h +++ b/include/tvision/scrncell.h @@ -36,6 +36,11 @@ inline void setChar(TScreenCell &cell, TCellChar ch) ((uchar *) &cell)[0] = ch; } +inline void setCell(TScreenCell &cell, TCellChar ch, TCellAttribs attr) +{ + cell = (attr << 8) | ch; +} + #else #include @@ -238,6 +243,14 @@ inline void setChar(TScreenCell &cell, uchar ch) cell.extraWidth = 0; } +inline void setCell(TScreenCell &cell, TCellChar ch, TCellAttribs attr, uchar extraWidth=0) +{ + TScreenCell c = 0; + ::setChar(c, ch, extraWidth); + ::setAttr(c, attr); + cell = c; +} + #endif // __BORLANDC__ #endif diff --git a/include/tvision/ttext.h b/include/tvision/ttext.h index b5469657..76cc2e48 100644 --- a/include/tvision/ttext.h +++ b/include/tvision/ttext.h @@ -136,11 +136,9 @@ class TText { static size_t prev(TStringView text, size_t index); static size_t wseek(TStringView text, int count, Boolean incRemainder=True); -#ifndef __BORLANDC__ static void eat(TScreenCell *cell, size_t n, size_t &width, TStringView text, size_t &bytes); static void next(TStringView text, size_t &bytes, size_t &width); static void wseek(TStringView text, size_t &index, size_t &remainder, int count); -#endif }; @@ -161,6 +159,31 @@ inline size_t TText::wseek(TStringView text, int count, Boolean) return count > 0 ? min(count, text.size()) : 0; } +inline void TText::eat( TScreenCell *cell, size_t n, size_t &width, + TStringView text, size_t &bytes ) +{ + if (n) { + ::setChar(*cell, text[0]; + ++width; + ++bytes; + } +} + +inline void TText::next(TStringView text, size_t &bytes, size_t &width) +{ + if (text.size()) { + ++bytes; + ++width; + } +} + +inline void TText::wseek(TStringView text, size_t &index, size_t &remainder, int count) +{ + if (count > 0) + index += count; + remainder = 0; +} + #else inline size_t TText::next(TStringView text) diff --git a/source/tvision/edits.cpp b/source/tvision/edits.cpp index f1cf8999..062df0b5 100644 --- a/source/tvision/edits.cpp +++ b/source/tvision/edits.cpp @@ -27,7 +27,7 @@ uint TEditor::bufPtr( uint P ) return P < curPtr ? P : P + gapLen; } -void TEditor::formatLine( ushort *DrawBuf, +void TEditor::formatLine( TScreenCell *DrawBuf, uint P, int Width, ushort Colors @@ -42,29 +42,28 @@ void TEditor::formatLine( ushort *DrawBuf, { uchar(Colors), bufLen } }; - uchar Color = uchar(Colors); int X = 0; - for (int r = 0; r < 3; ++r) { - Color = ranges[r].color; + uchar Color = ranges[r].color; while (P < ranges[r].end && X < Width) { - uchar Char = bufChar(P++); + TStringView chars = bufChars(P); + uchar Char = chars[0]; if (Char == '\r' || Char == '\n') goto fill; if (Char == '\t') { do { - DrawBuf[X++] = (Color << 8) | ' '; + ::setCell(DrawBuf[X++], ' ', Color); } while (X%8 != 0 && X < Width); - } else { - DrawBuf[X++] = (Color << 8) | Char; - } + ++P; + } else + formatCell(&DrawBuf[X], Width - X, (uint&) X, chars, P, Color); } } fill: while (X < Width) - DrawBuf[X++] = (Color << 8) | ' '; + ::setCell(DrawBuf[X++], ' ', uchar(Colors)); } uint TEditor::lineEnd( uint P ) @@ -103,7 +102,10 @@ uint TEditor::nextChar( uint P ) { if (bufChar(P) == '\r' && bufChar(P + 1) == '\n') return P + 2; - return P + 1; + if (encSingleByte) + return P + 1; + else + return P + TText::next(bufChars(P)); } return bufLen; } @@ -114,7 +116,13 @@ uint TEditor::prevChar( uint P ) { if (bufChar(P - 2) == '\r' && bufChar(P - 1) == '\n') return P - 2; - return P - 1; + if (encSingleByte) + return P - 1; + else + { + TStringView t = bufPrevChars(P); + return P - TText::prev(t, t.size()); + } } return 0; } diff --git a/source/tvision/teditor1.cpp b/source/tvision/teditor1.cpp index 6095b667..491dd1b9 100644 --- a/source/tvision/teditor1.cpp +++ b/source/tvision/teditor1.cpp @@ -45,7 +45,7 @@ const ushort firstKeys[] = { - 39, + 40, kbCtrlA, cmSelectAll, kbCtrlC, cmPageDown, kbCtrlD, cmCharRight, @@ -57,6 +57,7 @@ const ushort firstKeys[] = kbCtrlL, cmSearchAgain, kbCtrlM, cmNewLine, kbCtrlO, cmIndentMode, + kbCtrlP, cmEncoding, kbCtrlQ, 0xFF01, kbCtrlR, cmPageUp, kbCtrlS, cmCharLeft, @@ -170,6 +171,12 @@ asm POP DS #define cpEditor "\x06\x07" +#ifdef __BORLANDC__ +#define _encSB True +#else +#define _encSB False +#endif + TEditor::TEditor( const TRect& bounds, TScrollBar *aHScrollBar, TScrollBar *aVScrollBar, @@ -184,6 +191,7 @@ TEditor::TEditor( const TRect& bounds, selecting( False ), overwrite( False ), autoIndent( True ) , + encSingleByte( _encSB ), lockCount( 0 ), updateFlags( 0 ), keyState( 0 ) @@ -222,33 +230,94 @@ void TEditor::changeBounds( const TRect& bounds ) update(ufView); } +TStringView TEditor::bufChars( uint P ) +{ + if (!encSingleByte) + { + int len = min(4, max(bufLen - P, 0)); + for (int i = 0; i < len; ++i) + charsBuf[i] = bufChar(P + i); + return TStringView(charsBuf, len); + } + else if (P < bufLen) + { + charsBuf[0] = bufChar(P); + return TStringView(charsBuf, 1); + } + return TStringView(); +} + +TStringView TEditor::bufPrevChars( uint P ) +{ + if (!encSingleByte) + { + int len = min(4, P); + for (int i = 0; i < len; ++i) + charsBuf[i] = bufChar(P - len + i); + return TStringView(charsBuf, len); + } + else if (P) + { + charsBuf[0] = bufChar(P - 1); + return TStringView(charsBuf, 1); + } + return TStringView(); +} + +void TEditor::nextChar( TStringView s, uint &p, uint &width ) +{ + if (encSingleByte) + { + ++p; + ++width; + } + else + { + size_t p_ = 0, w_ = 0; + TText::next(s, p_, w_); + p += p_; width += w_; + } +} + +void TEditor::formatCell( TScreenCell* cell, uint n, uint &width, + TStringView text, uint &p, TCellAttribs color ) +{ + ::setAttr(*cell, color); + size_t p_ = 0, w_ = 0; + TText::eat(cell, n, w_, text, p_); + p += p_; width += w_; +} + int TEditor::charPos( uint p, uint target ) { - int pos = 0; + uint pos = 0; while( p < target ) { - if( bufChar(p) == '\x9' ) + TStringView chars = bufChars(p); + if( chars[0] == '\x9' ) pos |= 7; - pos++; - p++; + nextChar(chars, p, pos); } return pos; } uint TEditor::charPtr( uint p, int target ) { - int pos = 0; - char c; - while( (pos < target) && (p < bufLen) && (c = bufChar(p)) != '\r' && c != '\n' ) - { - if( c == '\x09' ) - pos |= 7; - pos++; - p++; - } - if( pos > target ) - p--; - return p; + uint pos = 0; + uint lastP = p; + char c; + TStringView chars; + while( (int) pos < target && p < bufLen && + (c = (chars = bufChars(p))[0]) != '\r' && c != '\n' ) + { + lastP = p; + if( c == '\x09' ) + pos |= 7; + nextChar(chars, p, pos); + } + if( (int) pos > target) + p = lastP; + return p; } Boolean TEditor::clipCopy() @@ -409,9 +478,9 @@ void TEditor::drawLines( int y, int count, uint linePtr ) { ushort color = getColor(0x0201); #ifndef __FLAT__ - ushort b[maxLineLength]; + TScreenCell b[maxLineLength]; #else - ushort *b = (ushort*) alloca(sizeof(ushort)*(delta.x+size.x)); + TScreenCell *b = (TScreenCell*) alloca(sizeof(TScreenCell)*(delta.x+size.x)); #endif while( count-- > 0 ) { @@ -524,17 +593,22 @@ void TEditor::handleEvent( TEvent& event ) break; case evKeyDown: - if( event.keyDown.charScan.charCode == 9 || - ( event.keyDown.charScan.charCode >= 32 && event.keyDown.charScan.charCode < 255 ) ) - { - lock(); - if( overwrite == True && hasSelection() == False ) - if( curPtr != lineEnd(curPtr) ) - selEnd = nextChar(curPtr); + if( ( !encSingleByte && event.keyDown.textLength ) || + event.keyDown.charScan.charCode == 9 || + ( event.keyDown.charScan.charCode >= 32 && event.keyDown.charScan.charCode < 255 ) + ) + { + lock(); + if( overwrite == True && hasSelection() == False ) + if( curPtr != lineEnd(curPtr) ) + selEnd = nextChar(curPtr); + if ( !encSingleByte && event.keyDown.textLength ) + insertText( event.keyDown.text, event.keyDown.textLength, False); + else insertText( &event.keyDown.charScan.charCode, 1, False); - trackCursor(centerCursor); - unlock(); - } + trackCursor(centerCursor); + unlock(); + } else return; break; @@ -647,6 +721,9 @@ void TEditor::handleEvent( TEvent& event ) selectMode |= smExtend; setCurPtr(bufLen, selectMode); break; + case cmEncoding: + toggleEncoding(); + break; default: unlock(); return; diff --git a/source/tvision/teditor2.cpp b/source/tvision/teditor2.cpp index 1e170842..444421fc 100644 --- a/source/tvision/teditor2.cpp +++ b/source/tvision/teditor2.cpp @@ -444,13 +444,13 @@ void TEditor::setSelect( uint newStart, uint newEnd, Boolean curStart ) curPos.y -= countLines(&buffer[curPtr], l); memmove( &buffer[curPtr + gapLen], &buffer[curPtr], l); } - drawLine = curPos.y; - drawPtr = lineStart(p); - curPos.x = charPos(drawPtr, p); delCount = 0; insCount = 0; setBufSize(bufLen); - } + } + drawLine = curPos.y; + drawPtr = lineStart(p); + curPos.x = charPos(drawPtr, p); selStart = newStart; selEnd = newEnd; update(flags); @@ -483,6 +483,13 @@ void TEditor::startSelect() selecting = True; } +void TEditor::toggleEncoding() +{ + encSingleByte = !encSingleByte; + updateFlags |= ufView; + setSelect(selStart, selEnd, curPtr < selEnd); +} + void TEditor::toggleInsMode() { overwrite = Boolean(!overwrite);