From ec0f7ea8f4c9b16412a02f7a11c46800fe74160e Mon Sep 17 00:00:00 2001 From: Vadim Lopatin Date: Mon, 19 Jan 2015 14:32:09 +0300 Subject: [PATCH] support writing of editor contents to file --- src/dlangui/core/linestream.d | 59 +++++++++++++++++++---- src/dlangui/widgets/editors.d | 90 +++++++++++++++++++++++++++++++++-- 2 files changed, 134 insertions(+), 15 deletions(-) diff --git a/src/dlangui/core/linestream.d b/src/dlangui/core/linestream.d index 93a19162..d892062b 100644 --- a/src/dlangui/core/linestream.d +++ b/src/dlangui/core/linestream.d @@ -208,6 +208,7 @@ class OutputLineStream { } /// close stream void close() { + flush(); _stream.close(); _buf = null; } @@ -243,6 +244,10 @@ class LineStream { private uint _textLen; // position of last filled char in text buffer + 1 private dchar[] _textBuf; // text buffer private bool _eof; // end of file, no more lines + protected bool _bomDetected; + protected int _crCount; + protected int _lfCount; + protected int _crlfCount; /// Returns file name @property string filename() { return _filename; } @@ -250,6 +255,25 @@ class LineStream { @property uint line() { return _line; } /// Returns file encoding EncodingType @property EncodingType encoding() { return _encoding; } + + @property TextFileFormat textFormat() { + LineEnding le = LineEnding.CRLF; + if (_crlfCount) { + if (_crCount == _lfCount) + le = LineEnding.CRLF; + else + le = LineEnding.MIXED; + } else if (_crCount > _lfCount) { + le = LineEnding.CR; + } else if (_lfCount > _crCount) { + le = LineEnding.CR; + } else { + le = LineEnding.MIXED; + } + return TextFileFormat(_encoding, le, _bomDetected); + } + + /// Returns error code @property int errorCode() { return _errorCode; } /// Returns error message @@ -397,15 +421,21 @@ class LineStream { charsLeft = _textLen - _textPos; } dchar ch2 = (p < charsLeft - 1) ? _textBuf[_textPos + p + 1] : 0; - if (ch2 == 0x0A) + if (ch2 == 0x0A) { eol = p + 2; - else + _lfCount++; + _crCount++; + _crlfCount++; + } else { eol = p + 1; + _lfCount++; + } break; } else if (ch == 0x0A || ch == 0x2028 || ch == 0x2029) { // single char eoln lastchar = p; eol = p + 1; + _crCount++; break; } else if (ch == 0 || ch == 0x001A) { // eof @@ -450,25 +480,34 @@ class LineStream { } /// Factory for InputStream parser - public static LineStream create(InputStream stream, string filename) { + public static LineStream create(InputStream stream, string filename, bool autodetectUTFIfNoBOM = true) { ubyte[] buf = new ubyte[BYTE_BUFFER_SIZE]; buf[0] = buf[1] = buf[2] = buf[3] = 0; if (!stream.isOpen) return null; uint len = cast(uint)stream.read(buf); + LineStream res = null; if (buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF) { - return new Utf8LineStream(stream, filename, buf, len); + res = new Utf8LineStream(stream, filename, buf, len); } else if (buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0xFE && buf[3] == 0xFF) { - return new Utf32beLineStream(stream, filename, buf, len); + res = new Utf32beLineStream(stream, filename, buf, len); } else if (buf[0] == 0xFF && buf[1] == 0xFE && buf[2] == 0x00 && buf[3] == 0x00) { - return new Utf32leLineStream(stream, filename, buf, len); + res = new Utf32leLineStream(stream, filename, buf, len); } else if (buf[0] == 0xFE && buf[1] == 0xFF) { - return new Utf16beLineStream(stream, filename, buf, len); + res = new Utf16beLineStream(stream, filename, buf, len); } else if (buf[0] == 0xFF && buf[1] == 0xFE) { - return new Utf16leLineStream(stream, filename, buf, len); - } else { - return new AsciiLineStream(stream, filename, buf, len); + res = new Utf16leLineStream(stream, filename, buf, len); } + if (res) { + res._bomDetected = true; + } else { + if (autodetectUTFIfNoBOM) { + res = new Utf8LineStream(stream, filename, buf, len); + } else { + res = new AsciiLineStream(stream, filename, buf, len); + } + } + return res; } protected bool invalidCharFlag; diff --git a/src/dlangui/widgets/editors.d b/src/dlangui/widgets/editors.d index 415e92e1..84c52efa 100644 --- a/src/dlangui/widgets/editors.d +++ b/src/dlangui/widgets/editors.d @@ -27,6 +27,7 @@ import dlangui.widgets.controls; import dlangui.widgets.scroll; import dlangui.core.signals; import dlangui.core.collections; +import dlangui.core.linestream; import dlangui.platforms.common.platform; import dlangui.widgets.menu; import dlangui.widgets.popup; @@ -256,7 +257,10 @@ enum EditAction { /// delete content in range //Delete, /// replace range content with new content - Replace + Replace, + + /// replace whole content + ReplaceContent, } /// edit operation details for EditableContent @@ -435,6 +439,14 @@ class EditableContent { } return cast(dstring)buf; } + + /// call listener to say that whole content is replaced e.g. by loading from file + void notifyContentReplaced() { + TextRange rangeBefore; + TextRange rangeAfter; + handleContentChange(new EditOperation(EditAction.ReplaceContent), rangeBefore, rangeAfter, this); + } + /// replace whole text with another content @property EditableContent text(dstring newContent) { clearUndo(); @@ -445,6 +457,7 @@ class EditableContent { _lines.length = 1; _lines[0] = replaceEolsWithSpaces(newContent); } + notifyContentReplaced(); return this; } @@ -817,10 +830,22 @@ class EditableContent { void clearUndo() { _undoBuffer.clear(); } + + protected string _filename; + protected TextFileFormat _format; + + /// file used to load editor content + @property string filename() { + return _filename; + } + + /// load content form input stream bool load(InputStream f, string fname = null) { import dlangui.core.linestream; clear(); + _filename = fname; + _format = TextFileFormat.init; try { LineStream lines = LineStream.create(f, fname); for (;;) { @@ -832,13 +857,17 @@ class EditableContent { if (lines.errorCode != 0) { clear(); Log.e("Error ", lines.errorCode, " ", lines.errorMessage, " -- at line ", lines.errorLine, " position ", lines.errorPos); + notifyContentReplaced(); return false; } // EOF + _format = lines.textFormat; + notifyContentReplaced(); return true; } catch (Exception e) { Log.e("Exception while trying to read file ", fname, " ", e.toString); clear(); + notifyContentReplaced(); return false; } } @@ -855,6 +884,47 @@ class EditableContent { return false; } } + /// save to output stream in specified format + bool save(OutputStream stream, string filename, TextFileFormat format) { + if (!filename) + filename = _filename; + _format = format; + import dlangui.core.linestream; + try { + OutputLineStream writer = new OutputLineStream(stream, filename, format); + scope(exit) { writer.close(); } + for (int i = 0; i < _lines.length; i++) { + writer.writeLine(_lines[i]); + } + // EOF + return true; + } catch (Exception e) { + Log.e("Exception while trying to write file ", filename, " ", e.toString); + return false; + } + return false; + } + /// save to output stream in current format + bool save(OutputStream stream, string filename) { + return save(stream, filename, _format); + } + /// save to file in specified format + bool save(string filename, TextFileFormat format) { + if (!filename) + filename = _filename; + try { + std.stream.File f = new std.stream.File(filename, FileMode.Out); + scope(exit) { f.close(); } + return save(f, filename, format); + } catch (Exception e) { + Log.e("Exception while trying to save file ", filename, " ", e.toString); + return false; + } + } + /// save to file in current format + bool save(string filename) { + return save(filename, _format); + } } /// base for all editor widgets @@ -1133,10 +1203,20 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction updateMaxLineWidth(); measureVisibleText(); if (source is this) { - _caretPos = rangeAfter.end; - _selectionRange.start = _caretPos; - _selectionRange.end = _caretPos; - ensureCaretVisible(); + if (operation.action == EditAction.ReplaceContent) { + // loaded from file + _caretPos = rangeAfter.end; + _selectionRange.start = _caretPos; + _selectionRange.end = _caretPos; + ensureCaretVisible(); + correctCaretPos(); + requestLayout(); + } else { + _caretPos = rangeAfter.end; + _selectionRange.start = _caretPos; + _selectionRange.end = _caretPos; + ensureCaretVisible(); + } } else { correctCaretPos(); // TODO: do something better (e.g. take into account ranges when correcting)