diff --git a/dlangide.visualdproj b/dlangide.visualdproj
index 96b9e75..af61e9d 100644
--- a/dlangide.visualdproj
+++ b/dlangide.visualdproj
@@ -194,7 +194,6 @@
-
diff --git a/src/ddc/lexer/Lexer.d b/src/ddc/lexer/Lexer.d
index 808070c..1ea7108 100644
--- a/src/ddc/lexer/Lexer.d
+++ b/src/ddc/lexer/Lexer.d
@@ -1,7 +1,6 @@
// D grammar - according to http://dlang.org/grammar
module ddc.lexer.Lexer;
-import ddc.lexer.LineStream;
import ddc.lexer.Tokenizer;
/** Lexem type constants */
@@ -280,9 +279,4 @@ class AltDeclarator : Lexem {
class Lexer
{
- LineStream _lineStream;
- this(LineStream lineStream)
- {
- _lineStream = lineStream;
- }
}
diff --git a/src/ddc/lexer/LineStream.d b/src/ddc/lexer/LineStream.d
deleted file mode 100644
index 7f1f063..0000000
--- a/src/ddc/lexer/LineStream.d
+++ /dev/null
@@ -1,589 +0,0 @@
-module ddc.lexer.LineStream;
-
-import std.stream;
-import ddc.lexer.exceptions;
-import std.stdio;
-import std.conv;
-import ddc.lexer.textsource;
-
-class LineStream : SourceLines {
- public enum EncodingType {
- ASCII,
- UTF8,
- UTF16BE,
- UTF16LE,
- UTF32BE,
- UTF32LE
- };
-
- static immutable uint LINE_POSITION_UNDEFINED = uint.max;
- static immutable int TEXT_BUFFER_SIZE = 1024;
- static immutable int BYTE_BUFFER_SIZE = 512;
- static immutable int QUARTER_BYTE_BUFFER_SIZE = BYTE_BUFFER_SIZE / 4;
-
- InputStream _stream;
- string _filename;
- SourceFile _file;
- ubyte[] _buf; // stream reading buffer
- uint _pos; // reading position of stream buffer
- uint _len; // number of bytes in stream buffer
- bool _streamEof; // true if input stream is in EOF state
- uint _line; // current line number
-
- uint _textPos; // start of text line in text buffer
- uint _textLen; // position of last filled char in text buffer + 1
- dchar[] _textBuf; // text buffer
- bool _eof; // end of file, no more lines
-
- override @property SourceFile file() { return _file; }
- @property string filename() { return _file.filename; }
- override @property uint line() { return _line; }
- @property EncodingType encoding() { return _encoding; }
- override @property int errorCode() { return _errorCode; }
- override @property string errorMessage() { return _errorMessage; }
- override @property int errorLine() { return _errorLine; }
- override @property int errorPos() { return _errorPos; }
-
- immutable EncodingType _encoding;
-
- int _errorCode;
- string _errorMessage;
- uint _errorLine;
- uint _errorPos;
-
- protected this(InputStream stream, SourceFile file, EncodingType encoding, ubyte[] buf, uint offset, uint len) {
- _file = file;
- _stream = stream;
- _encoding = encoding;
- _buf = buf;
- _len = len;
- _pos = offset;
- _streamEof = _stream.eof;
- }
-
- // returns slice of bytes available in buffer
- uint readBytes() {
- uint bytesLeft = _len - _pos;
- if (_streamEof || bytesLeft > QUARTER_BYTE_BUFFER_SIZE)
- return bytesLeft;
- if (_pos > 0) {
- for (uint i = 0; i < bytesLeft; i++)
- _buf[i] = _buf[i + _pos];
- _len = bytesLeft;
- _pos = 0;
- }
- uint bytesRead = cast(uint)_stream.read(_buf[_len .. BYTE_BUFFER_SIZE]);
- _len += bytesRead;
- _streamEof = _stream.eof;
- return _len - _pos; //_buf[_pos .. _len];
- }
-
- // when bytes consumed from byte buffer, call this method to update position
- void consumedBytes(uint count) {
- _pos += count;
- }
-
- // reserve text buffer for specified number of characters, and return pointer to first free character in buffer
- dchar * reserveTextBuf(uint len) {
- // create new text buffer if necessary
- if (_textBuf == null) {
- if (len < TEXT_BUFFER_SIZE)
- len = TEXT_BUFFER_SIZE;
- _textBuf = new dchar[len];
- return _textBuf.ptr;
- }
- uint spaceLeft = cast(uint)_textBuf.length - _textLen;
- if (spaceLeft >= len)
- return _textBuf.ptr + _textLen;
- // move text to beginning of buffer, if necessary
- if (_textPos > _textBuf.length / 2) {
- uint charCount = _textLen - _textPos;
- dchar * p = _textBuf.ptr;
- for (uint i = 0; i < charCount; i++)
- p[i] = p[i + _textPos];
- _textLen = charCount;
- _textPos = 0;
- }
- // resize buffer if necessary
- if (_textLen + len > _textBuf.length) {
- // resize buffer
- uint newsize = cast(uint)_textBuf.length * 2;
- if (newsize < _textLen + len)
- newsize = _textLen + len;
- _textBuf.length = newsize;
- }
- return _textBuf.ptr + _textLen;
- }
-
- void appendedText(uint len) {
- //writeln("appended ", len, " chars of text"); //:", _textBuf[_textLen .. _textLen + len]);
- _textLen += len;
- }
-
- void setError(int code, string message, uint errorLine, uint errorPos) {
- _errorCode = code;
- _errorMessage = message;
- _errorLine = errorLine;
- _errorPos = errorPos;
- }
-
- // override to decode text
- abstract uint decodeText();
-
- override public dchar[] readLine() {
- if (_errorCode != 0) {
- //writeln("error ", _errorCode, ": ", _errorMessage, " in line ", _errorLine);
- return null; // error detected
- }
- if (_eof) {
- //writeln("EOF found");
- return null;
- }
- _line++;
- uint p = 0;
- uint eol = LINE_POSITION_UNDEFINED;
- uint eof = LINE_POSITION_UNDEFINED;
- uint lastchar = LINE_POSITION_UNDEFINED;
- do {
- if (_errorCode != 0) {
- //writeln("error ", _errorCode, ": ", _errorMessage, " in line ", _errorLine);
- return null; // error detected
- }
- uint charsLeft = _textLen - _textPos;
- if (p >= charsLeft) {
- uint decodedChars = decodeText();
- if (_errorCode != 0) {
- return null; // error detected
- }
- charsLeft = _textLen - _textPos;
- if (decodedChars == 0) {
- eol = charsLeft;
- eof = charsLeft;
- lastchar = charsLeft;
- break;
- }
- }
- for (; p < charsLeft; p++) {
- dchar ch = _textBuf[_textPos + p];
- if (ch == 0x0D) {
- lastchar = p;
- if (p == charsLeft - 1) {
- // need one more char to check if it's 0D0A or just 0D eol
- //writeln("read one more char for 0D0A detection");
- decodeText();
- if (_errorCode != 0) {
- return null; // error detected
- }
- charsLeft = _textLen - _textPos;
- }
- dchar ch2 = (p < charsLeft - 1) ? _textBuf[_textPos + p + 1] : 0;
- if (ch2 == 0x0A)
- eol = p + 2;
- else
- eol = p + 1;
- break;
- } else if (ch == 0x0A || ch == 0x2028 || ch == 0x2029) {
- // single char eoln
- lastchar = p;
- eol = p + 1;
- break;
- } else if (ch == 0 || ch == 0x001A) {
- // eof
- //writeln("EOF char found");
- lastchar = p;
- eol = eof = p + 1;
- break;
- }
- }
- } while (eol == LINE_POSITION_UNDEFINED);
- uint lineStart = _textPos;
- uint lineEnd = _textPos + lastchar;
- _textPos += eol; // consume text
- if (eof != LINE_POSITION_UNDEFINED) {
- _eof = true;
- //writeln("Setting eof flag. lastchar=", lastchar, ", p=", p, ", lineStart=", lineStart);
- if (lineStart >= lineEnd) {
- //writeln("lineStart >= lineEnd -- treat as eof");
- return null; // eof
- }
- }
- // return slice with decoded line
- return _textBuf[lineStart .. lineEnd];
- }
-
-
- // factory for string parser
- public static LineStream create(string code, string filename = "") {
- uint len = cast(uint)code.length;
- ubyte[] data = new ubyte[len + 3];
- for (uint i = 0; i < len; i++)
- data[i + 3] = code[i];
- // BOM for UTF8
- data[0] = 0xEF;
- data[1] = 0xBB;
- data[2] = 0xBF;
- MemoryStream stream = new MemoryStream(data);
- return create(stream, filename);
- }
-
- // factory
- public static LineStream create(InputStream stream, string filename) {
- 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);
- if (buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF) {
- return 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);
- } else if (buf[0] == 0xFF && buf[1] == 0xFE && buf[2] == 0x00 && buf[3] == 0x00) {
- return new Utf32leLineStream(stream, filename, buf, len);
- } else if (buf[0] == 0xFE && buf[1] == 0xFF) {
- return 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);
- }
- }
-
- protected bool invalidCharFlag;
- protected void invalidCharError() {
- uint pos = _textLen - _textPos + 1;
- setError(1, "Invalid character in line " ~ to!string(_line) ~ ":" ~ to!string(pos), _line, pos);
- }
-}
-
-
-
-class AsciiLineStream : LineStream {
- this(InputStream stream, string filename, ubyte[] buf, uint len) {
- super(stream, new SourceFile(filename), EncodingType.ASCII, buf, 0, len);
- }
- override uint decodeText() {
- if (invalidCharFlag) {
- invalidCharError();
- return 0;
- }
- uint bytesAvailable = readBytes();
- ubyte * bytes = _buf.ptr + _pos;
- if (bytesAvailable == 0)
- return 0; // nothing to decode
- uint len = bytesAvailable;
- ubyte* b = bytes;
- dchar* text = reserveTextBuf(len);
- uint i = 0;
- for (; i < len; i++) {
- ubyte ch = b[i];
- if (ch & 0x80) {
- // invalid character
- invalidCharFlag = true;
- break;
- }
- text[i] = ch;
- }
- consumedBytes(i);
- appendedText(i);
- return len;
- }
-
-}
-
-class Utf8LineStream : LineStream {
- this(InputStream stream, string filename, ubyte[] buf, uint len) {
- super(stream, new SourceFile(filename), EncodingType.UTF8, buf, 3, len);
- }
- override uint decodeText() {
- if (invalidCharFlag) {
- invalidCharError();
- return 0;
- }
- uint bytesAvailable = readBytes();
- ubyte * bytes = _buf.ptr + _pos;
- if (bytesAvailable == 0)
- return 0; // nothing to decode
- uint len = bytesAvailable;
- uint chars = 0;
- ubyte* b = bytes;
- dchar* text = reserveTextBuf(len);
- uint i = 0;
- for (; i < len; i++) {
- uint ch = 0;
- uint ch0 = b[i];
- uint bleft = len - i;
- uint bread = 0;
- if (!(ch0 & 0x80)) {
- // 0x00..0x7F single byte
- ch = ch0;
- bread = 1;
- } if ((ch0 & 0xE0) == 0xC0) {
- // two bytes 110xxxxx 10xxxxxx
- if (bleft < 2)
- break;
- uint ch1 = b[i + 1];
- if ((ch1 & 0xC0) != 0x80) {
- invalidCharFlag = true;
- break;
- }
- ch = ((ch0 & 0x1F) << 6) | ((ch1 & 0x3F));
- bread = 2;
- } if ((ch0 & 0xF0) == 0xE0) {
- // three bytes 1110xxxx 10xxxxxx 10xxxxxx
- if (bleft < 3)
- break;
- uint ch1 = b[i + 1];
- uint ch2 = b[i + 2];
- if ((ch1 & 0xC0) != 0x80 || (ch2 & 0xC0) != 0x80) {
- invalidCharFlag = true;
- break;
- }
- ch = ((ch0 & 0x0F) << 12) | ((ch1 & 0x1F) << 6) | ((ch2 & 0x3F));
- bread = 3;
- } if ((ch0 & 0xF8) == 0xF0) {
- // four bytes 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
- if (bleft < 4)
- break;
- uint ch1 = b[i + 1];
- uint ch2 = b[i + 2];
- uint ch3 = b[i + 3];
- if ((ch1 & 0xC0) != 0x80 || (ch2 & 0xC0) != 0x80 || (ch3 & 0xC0) != 0x80) {
- invalidCharFlag = true;
- break;
- }
- ch = ((ch0 & 0x07) << 18) | ((ch1 & 0x3F) << 12) | ((ch2 & 0x3F) << 6) | ((ch3 & 0x3F));
- bread = 4;
- } if ((ch0 & 0xFC) == 0xF8) {
- // five bytes 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
- if (bleft < 5)
- break;
- uint ch1 = b[i + 1];
- uint ch2 = b[i + 2];
- uint ch3 = b[i + 3];
- uint ch4 = b[i + 4];
- if ((ch1 & 0xC0) != 0x80 || (ch2 & 0xC0) != 0x80 || (ch3 & 0xC0) != 0x80 || (ch4 & 0xC0) != 0x80) {
- invalidCharFlag = true;
- break;
- }
- ch = ((ch0 & 0x03) << 24) | ((ch1 & 0x3F) << 18) | ((ch2 & 0x3F) << 12) | ((ch3 & 0x3F) << 6) | ((ch4 & 0x3F));
- bread = 5;
- } if ((ch0 & 0xFE) == 0xFC) {
- // six bytes 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
- if (bleft < 6)
- break;
- uint ch1 = b[i + 1];
- uint ch2 = b[i + 2];
- uint ch3 = b[i + 3];
- uint ch4 = b[i + 4];
- uint ch5 = b[i + 5];
- if ((ch1 & 0xC0) != 0x80 || (ch2 & 0xC0) != 0x80 || (ch3 & 0xC0) != 0x80 || (ch4 & 0xC0) != 0x80 || (ch5 & 0xC0) != 0x80) {
- invalidCharFlag = true;
- break;
- }
- ch = ((ch0 & 0x01) << 30) | ((ch1 & 0x3F) << 24) | ((ch2 & 0x3F) << 18) | ((ch3 & 0x3F) << 12) | ((ch4 & 0x3F) << 6) | ((ch5 & 0x3F));
- bread = 5;
- }
- if ((ch >= 0xd800 && ch < 0xe000) || (ch > 0x10FFFF)) {
- invalidCharFlag = true;
- break;
- }
- if (ch < 0x10000) {
- text[chars++] = ch;
- } else {
- uint lo = ch & 0x3FF;
- uint hi = ch >> 10;
- text[chars++] = (0xd800 | hi);
- text[chars++] = (0xdc00 | lo);
- }
- i += bread - 1;
- }
- consumedBytes(i);
- appendedText(chars);
- uint bleft = len - i;
- if (_streamEof && bleft > 0)
- invalidCharFlag = true; // incomplete character at end of stream
- return chars;
- }
-}
-
-class Utf16beLineStream : LineStream {
- this(InputStream stream, string filename, ubyte[] buf, uint len) {
- super(stream, new SourceFile(filename), EncodingType.UTF16BE, buf, 2, len);
- }
- override uint decodeText() {
- if (invalidCharFlag) {
- invalidCharError();
- return 0;
- }
- uint bytesAvailable = readBytes();
- ubyte * bytes = _buf.ptr + _pos;
- if (bytesAvailable == 0)
- return 0; // nothing to decode
- uint len = bytesAvailable;
- uint chars = 0;
- ubyte* b = bytes;
- dchar* text = reserveTextBuf(len / 2 + 1);
- uint i = 0;
- for (; i < len - 1; i += 2) {
- uint ch0 = b[i];
- uint ch1 = b[i + 1];
- uint ch = (ch0 << 8) | ch1;
- // TODO: check special cases
- text[chars++] = ch;
- }
- consumedBytes(i);
- appendedText(chars);
- uint bleft = len - i;
- if (_streamEof && bleft > 0)
- invalidCharFlag = true; // incomplete character at end of stream
- return chars;
- }
-}
-
-class Utf16leLineStream : LineStream {
- this(InputStream stream, string filename, ubyte[] buf, uint len) {
- super(stream, new SourceFile(filename), EncodingType.UTF16LE, buf, 2, len);
- }
- override uint decodeText() {
- if (invalidCharFlag) {
- invalidCharError();
- return 0;
- }
- uint bytesAvailable = readBytes();
- ubyte * bytes = _buf.ptr + _pos;
- if (bytesAvailable == 0)
- return 0; // nothing to decode
- uint len = bytesAvailable;
- uint chars = 0;
- ubyte* b = bytes;
- dchar* text = reserveTextBuf(len / 2 + 1);
- uint i = 0;
- for (; i < len - 1; i += 2) {
- uint ch0 = b[i];
- uint ch1 = b[i + 1];
- uint ch = (ch1 << 8) | ch0;
- // TODO: check special cases
- text[chars++] = ch;
- }
- consumedBytes(i);
- appendedText(chars);
- uint bleft = len - i;
- if (_streamEof && bleft > 0)
- invalidCharFlag = true; // incomplete character at end of stream
- return chars;
- }
-}
-
-class Utf32beLineStream : LineStream {
- this(InputStream stream, string filename, ubyte[] buf, uint len) {
- super(stream, new SourceFile(filename), EncodingType.UTF32BE, buf, 4, len);
- }
- override uint decodeText() {
- if (invalidCharFlag) {
- invalidCharError();
- return 0;
- }
- uint bytesAvailable = readBytes();
- ubyte * bytes = _buf.ptr + _pos;
- if (bytesAvailable == 0)
- return 0; // nothing to decode
- uint len = bytesAvailable;
- uint chars = 0;
- ubyte* b = bytes;
- dchar* text = reserveTextBuf(len / 2 + 1);
- uint i = 0;
- for (; i < len - 3; i += 4) {
- uint ch0 = b[i];
- uint ch1 = b[i + 1];
- uint ch2 = b[i + 2];
- uint ch3 = b[i + 3];
- uint ch = (ch0 << 24) | (ch1 << 16) | (ch2 << 8) | ch3;
- if ((ch >= 0xd800 && ch < 0xe000) || (ch > 0x10FFFF)) {
- invalidCharFlag = true;
- break;
- }
- text[chars++] = ch;
- }
- consumedBytes(i);
- appendedText(chars);
- uint bleft = len - i;
- if (_streamEof && bleft > 0)
- invalidCharFlag = true; // incomplete character at end of stream
- return chars;
- }
-}
-
-class Utf32leLineStream : LineStream {
- this(InputStream stream, string filename, ubyte[] buf, uint len) {
- super(stream, new SourceFile(filename), EncodingType.UTF32LE, buf, 4, len);
- }
- override uint decodeText() {
- if (invalidCharFlag) {
- invalidCharError();
- return 0;
- }
- uint bytesAvailable = readBytes();
- ubyte * bytes = _buf.ptr + _pos;
- if (bytesAvailable == 0)
- return 0; // nothing to decode
- uint len = bytesAvailable;
- uint chars = 0;
- ubyte* b = bytes;
- dchar* text = reserveTextBuf(len / 2 + 1);
- uint i = 0;
- for (; i < len - 3; i += 4) {
- uint ch3 = b[i];
- uint ch2 = b[i + 1];
- uint ch1 = b[i + 2];
- uint ch0 = b[i + 3];
- uint ch = (ch0 << 24) | (ch1 << 16) | (ch2 << 8) | ch3;
- if ((ch >= 0xd800 && ch < 0xe000) || (ch > 0x10FFFF)) {
- invalidCharFlag = true;
- break;
- }
- text[chars++] = ch;
- }
- consumedBytes(i);
- appendedText(chars);
- uint bleft = len - i;
- if (_streamEof && bleft > 0)
- invalidCharFlag = true; // incomplete character at end of stream
- return chars;
- }
-}
-
-
-unittest {
- static if (false) {
- import std.stdio;
- import std.conv;
- import std.utf;
- //string fname = "C:\\projects\\d\\ddc\\ddclexer\\src\\ddc\\lexer\\LineStream.d";
- //string fname = "/home/lve/src/d/ddc/ddclexer/" ~ __FILE__; //"/home/lve/src/d/ddc/ddclexer/src/ddc/lexer/Lexer.d";
- //string fname = "/home/lve/src/d/ddc/ddclexer/tests/LineStream_utf8.d";
- //string fname = "/home/lve/src/d/ddc/ddclexer/tests/LineStream_utf16be.d";
- //string fname = "/home/lve/src/d/ddc/ddclexer/tests/LineStream_utf16le.d";
- //string fname = "/home/lve/src/d/ddc/ddclexer/tests/LineStream_utf32be.d";
- string fname = "/home/lve/src/d/ddc/ddclexer/tests/LineStream_utf32le.d";
- writeln("opening file");
- std.stream.File f = new std.stream.File(fname);
- scope(exit) { f.close(); }
- try {
- LineStream lines = LineStream.create(f, fname);
- for (;;) {
- dchar[] s = lines.readLine();
- if (s is null)
- break;
- writeln("line " ~ to!string(lines.line()) ~ ":" ~ toUTF8(s));
- }
- if (lines.errorCode != 0) {
- writeln("Error ", lines.errorCode, " ", lines.errorMessage, " -- at line ", lines.errorLine, " position ", lines.errorPos);
- } else {
- writeln("EOF reached");
- }
- } catch (Exception e) {
- writeln("Exception " ~ e.toString);
- }
- }
-}
-// LAST LINE
diff --git a/src/dlangide/ui/frame.d b/src/dlangide/ui/frame.d
index b19d936..c3bd88b 100644
--- a/src/dlangide/ui/frame.d
+++ b/src/dlangide/ui/frame.d
@@ -58,24 +58,24 @@ class SimpleDSyntaxHighlighter : SyntaxHighlighter {
//writeln("EOF token");
break;
}
- uint newPos = token.pos;
- uint newLine = token.line;
+ uint newPos = token.pos - 1;
+ uint newLine = token.line - 1;
- if (category) {
- // fill with category
- for (uint i = tokenLine - 1; i <= newLine - 1; i++) {
- uint start = i > tokenLine - 1 ? 0 : tokenPos;
- uint end = i < newLine - 1 ? lines[i].length : tokenPos;
- for (uint j = start; j < end; j++) {
- assert(i < _props.length);
- if (j - 1 < _props[i].length)
- _props[i][j - 1] = category;
- }
+ //if (category) {
+ // fill with category
+ for (uint i = tokenLine; i <= newLine; i++) {
+ uint start = i > tokenLine ? 0 : tokenPos;
+ uint end = i < newLine ? lines[i].length : tokenPos;
+ for (uint j = start; j < end; j++) {
+ assert(i < _props.length);
+ if (j - 1 < _props[i].length)
+ _props[i][j - 1] = category;
}
}
+ //}
TokenType t = token.type;
- // handle token
+ // handle token - convert to category
if (t == TokenType.COMMENT) {
category = TokenCategory.Comment;
} else if (t == TokenType.KEYWORD) {
diff --git a/workspaces/sample1/sampleproject1/source/collections.d b/workspaces/sample1/sampleproject1/source/collections.d
new file mode 100644
index 0000000..da49f2b
--- /dev/null
+++ b/workspaces/sample1/sampleproject1/source/collections.d
@@ -0,0 +1,287 @@
+/**
+
+This module implements object collections.
+
+Copyright: Vadim Lopatin, 2014
+License: Boost License 1.0
+Authors: Vadim Lopatin, coolreader.org@gmail.com
+*/
+module collections;
+
+import std.algorithm;
+
+/**
+ Array based collection of items.
+
+ Retains item order when during add/remove operations.
+
+ Optionally destroys removed objects when instanciated with ownItems = true.
+*/
+struct Collection(T, bool ownItems = false) {
+ private T[] _items;
+ private size_t _len;
+ /// returns true if there are no items in collection
+ @property bool empty() { return _len == 0; }
+ /// returns number of items in collection
+ @property size_t length() { return _len; }
+ /// returns currently allocated capacity (may be more than length)
+ @property size_t size() { return _items.length; }
+ /// change capacity (e.g. to reserve big space to avoid multiple reallocations)
+ @property void size(size_t newSize) {
+ if (_len > newSize)
+ length = newSize; // shrink
+ _items.length = newSize;
+ }
+ /// returns number of items in collection
+ @property void length(size_t newSize) {
+ if (newSize < _len) {
+ // shrink
+ static if (is(T == class) || is(T == struct)) {
+ // clear items
+ for (size_t i = newSize; i < _len; i++) {
+ static if (ownItems)
+ destroy(_items[i]);
+ _items[i] = T.init;
+ }
+ }
+ } else if (newSize > _len) {
+ // expand
+ if (_items.length < newSize)
+ _items.length = newSize;
+ }
+ _len = newSize;
+ }
+ /// access item by index
+ ref T opIndex(size_t index) {
+ assert(index < _len);
+ return _items[index];
+ }
+ /// insert new item in specified position
+ void add(T item, size_t index = size_t.max) {
+ if (index > _len)
+ index = _len;
+ if (_items.length <= _len) {
+ if (_items.length < 4)
+ _items.length = 4;
+ else
+ _items.length = _items.length * 2;
+ }
+ if (index < _len) {
+ for (size_t i = _len; i > index; i--)
+ _items[i] = _items[i - 1];
+ }
+ _items[index] = item;
+ _len++;
+ }
+ /// add all items from other collection
+ void addAll(ref Collection!(T, ownItems) v) {
+ for (int i = 0; i < v.length; i++)
+ add(v[i]);
+ }
+ /// support for appending (~=, +=) and removing by value (-=)
+ ref Collection opOpAssign(string op)(T item) {
+ static if (op.equal("~") || op.equal("+")) {
+ // append item to end of collection
+ add(item);
+ return this;
+ } else if (op.equal("-")) {
+ // remove item from collection, if present
+ removeValue(item);
+ return this;
+ } else {
+ assert(false, "not supported opOpAssign " ~ op);
+ }
+ }
+ /// returns index of first occurence of item, size_t.max if not found
+ size_t indexOf(T item) {
+ for (size_t i = 0; i < _len; i++)
+ if (_items[i] == item)
+ return i;
+ return size_t.max;
+ }
+ /// remove single item, returning removed item
+ T remove(size_t index) {
+ assert(index < _len);
+ T result = _items[index];
+ for (size_t i = index; i + 1 < _len; i++)
+ _items[i] = _items[i + 1];
+ _items[_len] = T.init;
+ _len--;
+ return result;
+ }
+ /// remove single item by value - if present in collection, returning true if item was found and removed
+ bool removeValue(T value) {
+ size_t index = indexOf(value);
+ if (index == size_t.max)
+ return false;
+ T res = remove(index);
+ static if (ownItems)
+ destroy(res);
+ return true;
+ }
+ /// support of foreach with reference
+ int opApply(int delegate(ref T param) op) {
+ int result = 0;
+ for (size_t i = 0; i < _len; i++) {
+ result = op(_items[i]);
+ if (result)
+ break;
+ }
+ return result;
+ }
+ /// remove all items
+ void clear() {
+ static if (is(T == class) || is(T == struct)) {
+ /// clear references
+ for(size_t i = 0; i < _len; i++) {
+ static if (ownItems)
+ destroy(_items[i]);
+ _items[i] = T.init;
+ }
+ }
+ _len = 0;
+ _items = null;
+ }
+
+ //===================================
+ // stack/queue-like ops
+
+ /// remove first item
+ @property T popFront() {
+ if (empty)
+ return T.init; // no items
+ return remove(0);
+ }
+
+ /// insert item at beginning of collection
+ void pushFront(T item) {
+ add(item, 0);
+ }
+
+ /// remove last item
+ @property T popBack() {
+ if (empty)
+ return T.init; // no items
+ return remove(length - 1);
+ }
+
+ /// insert item at end of collection
+ void pushBack(T item) {
+ add(item);
+ }
+
+ /// peek first item
+ @property T front() {
+ if (empty)
+ return T.init; // no items
+ return _items[0];
+ }
+
+ /// peek last item
+ @property T back() {
+ if (empty)
+ return T.init; // no items
+ return _items[_len - 1];
+ }
+ /// removes all items on destroy
+ ~this() {
+ clear();
+ }
+}
+
+
+/** object list holder, owning its objects - on destroy of holder, all own objects will be destroyed */
+struct ObjectList(T) {
+ protected T[] _list;
+ protected int _count;
+ /** returns count of items */
+ @property int count() const { return _count; }
+ /** get item by index */
+ T get(int index) {
+ assert(index >= 0 && index < _count, "child index out of range");
+ return _list[index];
+ }
+ /// get item by index
+ T opIndex(int index) {
+ return get(index);
+ }
+ /** add item to list */
+ T add(T item) {
+ if (_list.length <= _count) // resize
+ _list.length = _list.length < 4 ? 4 : _list.length * 2;
+ _list[_count++] = item;
+ return item;
+ }
+ /** add item to list */
+ T insert(T item, int index = -1) {
+ if (index > _count || index < 0)
+ index = _count;
+ if (_list.length <= _count) // resize
+ _list.length = _list.length < 4 ? 4 : _list.length * 2;
+ for (int i = _count; i > index; i--)
+ _list[i] = _list[i - 1];
+ _list[index] = item;
+ _count++;
+ return item;
+ }
+ /** find child index for item, return -1 if not found */
+ int indexOf(T item) {
+ if (item is null)
+ return -1;
+ for (int i = 0; i < _count; i++)
+ if (_list[i] == item)
+ return i;
+ return -1;
+ }
+ /** find child index for item by id, return -1 if not found */
+ static if (__traits(hasMember, T, "compareId")) {
+ int indexOf(string id) {
+ for (int i = 0; i < _count; i++)
+ if (_list[i].compareId(id))
+ return i;
+ return -1;
+ }
+ }
+ /** remove item from list, return removed item */
+ T remove(int index) {
+ assert(index >= 0 && index < _count, "child index out of range");
+ T item = _list[index];
+ for (int i = index; i < _count - 1; i++)
+ _list[i] = _list[i + 1];
+ _count--;
+ return item;
+ }
+ /** Replace item with another value, destroy old value. */
+ void replace(T item, int index) {
+ T old = _list[index];
+ _list[index] = item;
+ destroy(old);
+ }
+ /** Replace item with another value, destroy old value. */
+ void replace(T newItem, T oldItem) {
+ int idx = indexOf(oldItem);
+ if (newItem is null) {
+ if (idx >= 0) {
+ T item = remove(idx);
+ destroy(item);
+ }
+ } else {
+ if (idx >= 0)
+ replace(newItem, idx);
+ else
+ add(newItem);
+ }
+ }
+ /** remove and destroy all items */
+ void clear() {
+ for (int i = 0; i < _count; i++) {
+ destroy(_list[i]);
+ _list[i] = null;
+ }
+ _count = 0;
+ }
+ ~this() {
+ clear();
+ }
+}
+
diff --git a/workspaces/sample1/sampleproject1/source/types.d b/workspaces/sample1/sampleproject1/source/types.d
new file mode 100644
index 0000000..45b4466
--- /dev/null
+++ b/workspaces/sample1/sampleproject1/source/types.d
@@ -0,0 +1,135 @@
+/**
+ This module contains some type definitions.
+ */
+module types;
+
+/** 2D point */
+struct Point {
+ /// x coordinate
+ int x;
+ /// y coordinate
+ int y;
+ this(int x0, int y0) {
+ x = x0;
+ y = y0;
+ }
+}
+
+/** 2D rectangle */
+struct Rect {
+ /// x coordinate of top left corner
+ int left;
+ /// y coordinate of top left corner
+ int top;
+ /// x coordinate of bottom right corner
+ int right;
+ /// y coordinate of bottom right corner
+ int bottom;
+ /// returns average of left, right
+ @property int middlex() { return (left + right) / 2; }
+ /// returns average of top, bottom
+ @property int middley() { return (top + bottom) / 2; }
+ /// returns middle point
+ @property Point middle() { return Point(middlex, middley); }
+ /// add offset to horizontal and vertical coordinates
+ void offset(int dx, int dy) {
+ left += dx;
+ right += dx;
+ top += dy;
+ bottom += dy;
+ }
+ /// expand rectangle dimensions
+ void expand(int dx, int dy) {
+ left -= dx;
+ right += dx;
+ top -= dy;
+ bottom += dy;
+ }
+ /// shrink rectangle dimensions
+ void shrink(int dx, int dy) {
+ left += dx;
+ right -= dx;
+ top += dy;
+ bottom -= dy;
+ }
+ /// for all fields, sets this.field to rc.field if rc.field > this.field
+ void setMax(Rect rc) {
+ if (left < rc.left)
+ left = rc.left;
+ if (right < rc.right)
+ right = rc.right;
+ if (top < rc.top)
+ top = rc.top;
+ if (bottom < rc.bottom)
+ bottom = rc.bottom;
+ }
+ /// returns width of rectangle (right - left)
+ @property int width() { return right - left; }
+ /// returns height of rectangle (bottom - top)
+ @property int height() { return bottom - top; }
+ /// constructs rectangle using left, top, right, bottom coordinates
+ this(int x0, int y0, int x1, int y1) {
+ left = x0;
+ top = y0;
+ right = x1;
+ bottom = y1;
+ }
+ /// returns true if rectangle is empty (right <= left || bottom <= top)
+ @property bool empty() {
+ return right <= left || bottom <= top;
+ }
+ /// translate rectangle coordinates by (x,y) - add deltax to x coordinates, and deltay to y coordinates
+ void moveBy(int deltax, int deltay) {
+ left += deltax;
+ right += deltax;
+ top += deltay;
+ bottom += deltay;
+ }
+ /// moves this rect to fit rc bounds, retaining the same size
+ void moveToFit(ref Rect rc) {
+ if (right > rc.right)
+ moveBy(rc.right - right, 0);
+ if (bottom > rc.bottom)
+ moveBy(0, rc.bottom - bottom);
+ if (left < rc.left)
+ moveBy(rc.left - left, 0);
+ if (top < rc.top)
+ moveBy(0, rc.top - top);
+
+ }
+ /// updates this rect to intersection with rc, returns true if result is non empty
+ bool intersect(Rect rc) {
+ if (left < rc.left)
+ left = rc.left;
+ if (top < rc.top)
+ top = rc.top;
+ if (right > rc.right)
+ right = rc.right;
+ if (bottom > rc.bottom)
+ bottom = rc.bottom;
+ return right > left && bottom > top;
+ }
+ /// returns true if this rect has nonempty intersection with rc
+ bool intersects(Rect rc) {
+ if (rc.left >= right || rc.top >= bottom || rc.right <= left || rc.bottom <= top)
+ return false;
+ return true;
+ }
+ /// returns true if point is inside of this rectangle
+ bool isPointInside(Point pt) {
+ return pt.x >= left && pt.x < right && pt.y >= top && pt.y < bottom;
+ }
+ /// returns true if point is inside of this rectangle
+ bool isPointInside(int x, int y) {
+ return x >= left && x < right && y >= top && y < bottom;
+ }
+ /// this rectangle is completely inside rc
+ bool isInsideOf(Rect rc) {
+ return left >= rc.left && right <= rc.right && top >= rc.top && bottom <= rc.bottom;
+ }
+
+ bool opEquals(Rect rc) {
+ return left == rc.left && right == rc.right && top == rc.top && bottom == rc.bottom;
+ }
+}
+
diff --git a/workspaces/sample1/sampleproject2/source/exlib/files.d b/workspaces/sample1/sampleproject2/source/exlib/files.d
new file mode 100644
index 0000000..88bd5b2
--- /dev/null
+++ b/workspaces/sample1/sampleproject2/source/exlib/files.d
@@ -0,0 +1,340 @@
+// Written in the D programming language.
+
+/**
+
+This module contains cross-platform file access utilities
+
+
+
+Synopsis:
+
+----
+import exlib.files;
+----
+
+Copyright: Vadim Lopatin, 2014
+License: Boost License 1.0
+Authors: Vadim Lopatin, coolreader.org@gmail.com
+*/
+module dlangui.core.files;
+
+import std.algorithm;
+
+private import dlangui.core.logger;
+private import std.process;
+private import std.path;
+private import std.file;
+private import std.utf;
+
+version (Windows) {
+ /// path delimiter (\ for windows, / for others)
+ immutable char PATH_DELIMITER = '\\';
+} else {
+ /// path delimiter (\ for windows, / for others)
+ immutable char PATH_DELIMITER = '/';
+}
+
+/// Filesystem root entry / bookmark types
+enum RootEntryType : uint {
+ /// filesystem root
+ ROOT,
+ /// current user home
+ HOME,
+ /// removable drive
+ REMOVABLE,
+ /// fixed drive
+ FIXED,
+ /// network
+ NETWORK,
+ /// cd rom
+ CDROM,
+ /// sd card
+ SDCARD,
+ /// custom bookmark
+ BOOKMARK,
+}
+
+/// Filesystem root entry item
+struct RootEntry {
+ private RootEntryType _type;
+ private string _path;
+ private dstring _display;
+ this(RootEntryType type, string path, dstring display = null) {
+ _type = type;
+ _path = path;
+ _display = display;
+ if (display is null) {
+ _display = toUTF32(baseName(path));
+ }
+ }
+ /// Returns type
+ @property RootEntryType type() { return _type; }
+ /// Returns path
+ @property string path() { return _path; }
+ /// Returns display label
+ @property dstring label() { return _display; }
+ /// Returns icon resource id
+ @property string icon() {
+ switch (type) {
+ case RootEntryType.NETWORK:
+ return "folder-network";
+ case RootEntryType.BOOKMARK:
+ return "folder-bookmark";
+ case RootEntryType.CDROM:
+ return "drive-optical";
+ case RootEntryType.FIXED:
+ return "drive-harddisk";
+ case RootEntryType.HOME:
+ return "user-home";
+ case RootEntryType.ROOT:
+ return "computer";
+ case RootEntryType.SDCARD:
+ return "media-flash-sd-mmc";
+ case RootEntryType.REMOVABLE:
+ return "device-removable-media";
+ default:
+ return "folder-blue";
+ }
+ }
+}
+
+/// Returns
+@property RootEntry homeEntry() {
+ return RootEntry(RootEntryType.HOME, homePath);
+}
+
+/// returns array of system root entries
+@property RootEntry[] getRootPaths() {
+ RootEntry[] res;
+ res ~= RootEntry(RootEntryType.HOME, homePath);
+ version (posix) {
+ res ~= RootEntry(RootEntryType.ROOT, "/", "File System"d);
+ }
+ version (Windows) {
+ import win32.windows;
+ uint mask = GetLogicalDrives();
+ for (int i = 0; i < 26; i++) {
+ if (mask & (1 << i)) {
+ char letter = cast(char)('A' + i);
+ string path = "" ~ letter ~ ":\\";
+ dstring display = ""d ~ letter ~ ":"d;
+ // detect drive type
+ RootEntryType type;
+ uint wtype = GetDriveTypeA(("" ~ path).ptr);
+ //Log.d("Drive ", path, " type ", wtype);
+ switch (wtype) {
+ case DRIVE_REMOVABLE:
+ type = RootEntryType.REMOVABLE;
+ break;
+ case DRIVE_REMOTE:
+ type = RootEntryType.NETWORK;
+ break;
+ case DRIVE_CDROM:
+ type = RootEntryType.CDROM;
+ break;
+ default:
+ type = RootEntryType.FIXED;
+ break;
+ }
+ res ~= RootEntry(type, path, display);
+ }
+ }
+ }
+ return res;
+}
+
+/// returns true if directory is root directory (e.g. / or C:\)
+bool isRoot(string path) {
+ string root = rootName(path);
+ if (path.equal(root))
+ return true;
+ return false;
+}
+
+/// returns parent directory for specified path
+string parentDir(string path) {
+ return buildNormalizedPath(path, "..");
+}
+
+/// check filename with pattern (currently only *.ext pattern is supported)
+bool filterFilename(string filename, string pattern) {
+ if (pattern.equal("*.*"))
+ return true; // matches any
+ if (pattern.length < 3)
+ return false;
+ if (pattern[0] != '*' || pattern[1] != '.')
+ return false;
+ return filename.endsWith(pattern[1..$]);
+}
+
+/// Filters file name by pattern list
+bool filterFilename(string filename, string[] filters) {
+ if (filters.length == 0)
+ return true; // no filters - show all
+ foreach(pattern; filters) {
+ if (filterFilename(filename, pattern))
+ return true;
+ }
+ return false;
+}
+
+/** List directory content
+
+ Optionally filters file names by filter.
+
+ Result will be placed into entries array.
+
+ Returns true if directory exists and listed successfully, false otherwise.
+*/
+bool listDirectory(string dir, bool includeDirs, bool includeFiles, bool showHiddenFiles, string[] filters, ref DirEntry[] entries) {
+
+ entries.length = 0;
+
+ if (!isDir(dir)) {
+ return false;
+ }
+
+ if (!isRoot(dir) && includeDirs) {
+ entries ~= DirEntry(appendPath(dir, ".."));
+ }
+
+ try {
+ DirEntry[] dirs;
+ DirEntry[] files;
+ foreach (DirEntry e; dirEntries(dir, SpanMode.shallow)) {
+ string fn = baseName(e.name);
+ if (!showHiddenFiles && fn.startsWith("."))
+ continue;
+ if (e.isDir) {
+ dirs ~= e;
+ } else if (e.isFile) {
+ files ~= e;
+ }
+ }
+ if (includeDirs)
+ foreach(DirEntry e; dirs)
+ entries ~= e;
+ if (includeFiles)
+ foreach(DirEntry e; files)
+ if (filterFilename(e.name, filters))
+ entries ~= e;
+ return true;
+ } catch (FileException e) {
+ return false;
+ }
+
+}
+
+/** Returns true if char ch is / or \ slash */
+bool isPathDelimiter(char ch) {
+ return ch == '/' || ch == '\\';
+}
+
+/// Returns current directory
+@property string currentDir() {
+ return getcwd();
+}
+
+/** Returns current executable path only, including last path delimiter - removes executable name from result of std.file.thisExePath() */
+@property string exePath() {
+ string path = thisExePath();
+ int lastSlash = 0;
+ for (int i = 0; i < path.length; i++)
+ if (path[i] == PATH_DELIMITER)
+ lastSlash = i;
+ return path[0 .. lastSlash + 1];
+}
+
+/// Returns user's home directory
+@property string homePath() {
+ string path;
+ version (Windows) {
+ path = environment.get("USERPROFILE");
+ if (path is null)
+ path = environment.get("HOME");
+ } else {
+ path = environment.get("HOME");
+ }
+ if (path is null)
+ path = "."; // fallback to current directory
+ return path;
+}
+
+/**
+
+ Returns application data directory
+
+ On unix, it will return path to subdirectory in home directory - e.g. /home/user/.subdir if ".subdir" is passed as a paramter.
+
+ On windows, it will return path to subdir in APPDATA directory - e.g. C:\Users\User\AppData\Roaming\.subdir.
+
+ */
+string appDataPath(string subdir = null) {
+ string path;
+ version (Windows) {
+ path = environment.get("APPDATA");
+ }
+ if (path is null)
+ path = homePath;
+ if (subdir !is null) {
+ path ~= PATH_DELIMITER;
+ path ~= subdir;
+ }
+ return path;
+}
+
+/// Converts path delimiters to standard for platform inplace in buffer(e.g. / to \ on windows, \ to / on posix), returns buf
+char[] convertPathDelimiters(char[] buf) {
+ foreach(ref ch; buf) {
+ version (Windows) {
+ if (ch == '/')
+ ch = '\\';
+ } else {
+ if (ch == '\\')
+ ch = '/';
+ }
+ }
+ return buf;
+}
+
+/** Converts path delimiters to standard for platform (e.g. / to \ on windows, \ to / on posix) */
+string convertPathDelimiters(string src) {
+ char[] buf = src.dup;
+ return cast(string)convertPathDelimiters(buf);
+}
+
+/** Appends file path parts with proper delimiters e.g. appendPath("/home/user", ".myapp", "config") => "/home/user/.myapp/config" */
+string appendPath(string[] pathItems ...) {
+ char[] buf;
+ foreach (s; pathItems) {
+ if (buf.length && !isPathDelimiter(buf[$-1]))
+ buf ~= PATH_DELIMITER;
+ buf ~= s;
+ }
+ return convertPathDelimiters(buf).dup;
+}
+
+/** Appends file path parts with proper delimiters (as well converts delimiters inside path to system) to buffer e.g. appendPath("/home/user", ".myapp", "config") => "/home/user/.myapp/config" */
+char[] appendPath(char[] buf, string[] pathItems ...) {
+ foreach (s; pathItems) {
+ if (buf.length && !isPathDelimiter(buf[$-1]))
+ buf ~= PATH_DELIMITER;
+ buf ~= s;
+ }
+ return convertPathDelimiters(buf);
+}
+
+/** Split path into elements, e.g. /home/user/dir1 -> ["home", "user", "dir1"], "c:\dir1\dir2" -> ["c:", "dir1", "dir2"] */
+string[] splitPath(string path) {
+ string[] res;
+ int start = 0;
+ for (int i = 0; i <= path.length; i++) {
+ char ch = i < path.length ? path[i] : 0;
+ if (ch == '\\' || ch == '/' || ch == 0) {
+ if (start < i)
+ res ~= path[start .. i].dup;
+ start = i + 1;
+ }
+ }
+ return res;
+}
diff --git a/workspaces/sample1/sampleproject2/source/exlib/i18n.d b/workspaces/sample1/sampleproject2/source/exlib/i18n.d
new file mode 100644
index 0000000..a487319
--- /dev/null
+++ b/workspaces/sample1/sampleproject2/source/exlib/i18n.d
@@ -0,0 +1,408 @@
+/**
+This module contains UI internationalization support implementation.
+
+UIString struct provides string container which can be either plain unicode string or id of string resource.
+
+Translation strings are being stored in translation files, consisting of simple key=value pair lines:
+---
+STRING_RESOURCE_ID=Translation text 1
+ANOTHER_STRING_RESOURCE_ID=Translation text 2
+---
+
+Supports fallback to another translation file (e.g. default language).
+
+If string resource is not found neither in main nor fallback translation files, UNTRANSLATED: RESOURCE_ID will be returned.
+
+String resources must be placed in i18n subdirectory inside one or more resource directories (set using Platform.instance.resourceDirs
+property on application initialization).
+
+File names must be language code with extension .ini (e.g. en.ini, fr.ini, es.ini)
+
+If several files for the same language are found in (different directories) their content will be merged. It's useful to merge string resources
+from DLangUI framework with resources of application.
+
+Set interface language using Platform.instance.uiLanguage in UIAppMain during initialization of application settings:
+---
+Platform.instance.uiLanguage = "en";
+---
+
+
+Synopsis:
+
+----
+import exlib.i18n;
+
+// use global i18n object to get translation for string ID
+dstring translated = i18n.get("STR_FILE_OPEN");
+// as well, you can specify fallback value - to return if translation is not found
+dstring translated = i18n.get("STR_FILE_OPEN", "Open..."d);
+
+// UIString type can hold either string resource id or dstring raw value.
+UIString text;
+
+// assign resource id as string (will remove dstring value if it was here)
+text = "ID_FILE_EXIT";
+// or assign raw value as dstring (will remove id if it was here)
+text = "some text"d;
+// assign both resource id and fallback value - to use if string resource is not found
+text = UIString("ID_FILE_EXIT", "Exit"d);
+
+// i18n.get() will automatically be invoked when getting UIString value (e.g. using alias this).
+dstring translated = text;
+
+----
+
+Copyright: Vadim Lopatin, 2014
+License: Boost License 1.0
+Authors: Vadim Lopatin, coolreader.org@gmail.com
+*/
+module exlib.i18n;
+
+import exlib.logger;
+import exlib.files;
+
+private import exlib.linestream;
+private import std.utf;
+private import std.algorithm;
+
+/**
+ Container for UI string - either raw value or string resource ID
+
+ Set resource id (string) or plain unicode text (dstring) to it, and get dstring.
+
+*/
+struct UIString {
+ /** if not null, use it, otherwise lookup by id */
+ private dstring _value;
+ /** id to find value in translator */
+ private string _id;
+
+ /** create string with i18n resource id */
+ this(string id) {
+ _id = id;
+ }
+ /** create string with raw value */
+ this(dstring value) {
+ _value = value;
+ }
+ /** create string with resource id and raw value as fallback for missing translations */
+ this(string id, dstring fallbackValue) {
+ _id = id;
+ _value = fallbackValue;
+ }
+
+
+ /// Returns string resource id
+ @property string id() const { return _id; }
+ /// Sets string resource id
+ @property void id(string ID) {
+ _id = ID;
+ _value = null;
+ }
+ /** Get value (either raw or translated by id) */
+ @property dstring value() const {
+ if (_id !is null) // translate ID to dstring
+ return i18n.get(_id, _value); // get from resource, use _value as fallback
+ return _value;
+ }
+ /** Set raw value using property */
+ @property void value(dstring newValue) {
+ _value = newValue;
+ }
+ /** Assign raw value */
+ ref UIString opAssign(dstring rawValue) {
+ _value = rawValue;
+ _id = null;
+ return this;
+ }
+ /** Assign string resource id */
+ ref UIString opAssign(string ID) {
+ _id = ID;
+ _value = null;
+ return this;
+ }
+ /** Default conversion to dstring */
+ alias value this;
+}
+
+/**
+ UIString item collection
+
+ Based on array.
+*/
+struct UIStringCollection {
+ private UIString[] _items;
+ private int _length;
+
+ /** Returns number of items */
+ @property int length() { return _length; }
+ /** Slice */
+ UIString[] opIndex() {
+ return _items[0 .. _length];
+ }
+ /** Slice */
+ UIString[] opSlice() {
+ return _items[0 .. _length];
+ }
+ /** Slice */
+ UIString[] opSlice(size_t start, size_t end) {
+ return _items[start .. end];
+ }
+ /** Read item by index */
+ UIString opIndex(size_t index) {
+ return _items[index];
+ }
+ /** Modify item by index */
+ UIString opIndexAssign(UIString value, size_t index) {
+ _items[index] = value;
+ return _items[index];
+ }
+ /** Return unicode string for item by index */
+ dstring get(size_t index) {
+ return _items[index].value;
+ }
+ /** Assign UIStringCollection */
+ void opAssign(ref UIStringCollection items) {
+ clear();
+ addAll(items);
+ }
+ /** Append UIStringCollection */
+ void addAll(ref UIStringCollection items) {
+ foreach (UIString item; items) {
+ add(item);
+ }
+ }
+ /** Assign array of string resource IDs */
+ void opAssign(string[] items) {
+ clear();
+ addAll(items);
+ }
+ /** Append array of string resource IDs */
+ void addAll(string[] items) {
+ foreach (string item; items) {
+ add(item);
+ }
+ }
+ /** Assign array of unicode strings */
+ void opAssign(dstring[] items) {
+ clear();
+ addAll(items);
+ }
+ /** Append array of unicode strings */
+ void addAll(dstring[] items) {
+ foreach (dstring item; items) {
+ add(item);
+ }
+ }
+ /** Remove all items */
+ void clear() {
+ _items.length = 0;
+ _length = 0;
+ }
+ /** Insert resource id item into specified position */
+ void add(string item, int index = -1) {
+ UIString s;
+ s = item;
+ add(s, index);
+ }
+ /** Insert unicode string item into specified position */
+ void add(dstring item, int index = -1) {
+ UIString s;
+ s = item;
+ add(s, index);
+ }
+ /** Insert UIString item into specified position */
+ void add(UIString item, int index = -1) {
+ if (index < 0 || index > _length)
+ index = _length;
+ if (_items.length < _length + 1) {
+ if (_items.length < 8)
+ _items.length = 8;
+ else
+ _items.length = _items.length * 2;
+ }
+ for (size_t i = _length; i > index; i--) {
+ _items[i] = _items[i + 1];
+ }
+ _items[index] = item;
+ _length++;
+ }
+ /** Remove item with specified index */
+ void remove(int index) {
+ if (index < 0 || index >= _length)
+ return;
+ for (size_t i = index; i < _length - 1; i++)
+ _items[i] = _items[i + 1];
+ _length--;
+ }
+ /** Return index of first item with specified text or -1 if not found. */
+ int indexOf(dstring str) {
+ for (int i = 0; i < _length; i++) {
+ if (_items[i].value.equal(str))
+ return i;
+ }
+ return -1;
+ }
+ /** Return index of first item with specified string resource id or -1 if not found. */
+ int indexOf(string strId) {
+ for (int i = 0; i < _length; i++) {
+ if (_items[i].id.equal(strId))
+ return i;
+ }
+ return -1;
+ }
+ /** Return index of first item with specified string or -1 if not found. */
+ int indexOf(UIString str) {
+ if (str.id !is null)
+ return indexOf(str.id);
+ return indexOf(str.value);
+ }
+}
+
+/** UI Strings internationalization translator */
+synchronized class UIStringTranslator {
+
+ private UIStringList _main;
+ private UIStringList _fallback;
+ private string[] _resourceDirs;
+
+ /** Looks for i18n directory inside one of passed dirs, and uses first found as directory to read i18n files from */
+ void findTranslationsDir(string[] dirs ...) {
+ _resourceDirs.length = 0;
+ import std.file;
+ foreach(dir; dirs) {
+ string path = appendPath(dir, "i18n/");
+ if (exists(path) && isDir(path)) {
+ Log.i("Adding i18n dir ", path);
+ _resourceDirs ~= path;
+ }
+ }
+ }
+
+ /** Convert resource path - append resource dir if necessary */
+ string[] convertResourcePaths(string filename) {
+ if (filename is null)
+ return null;
+ bool hasPathDelimiters = false;
+ foreach(char ch; filename)
+ if (ch == '/' || ch == '\\')
+ hasPathDelimiters = true;
+ string[] res;
+ if (!hasPathDelimiters && _resourceDirs.length) {
+ foreach (dir; _resourceDirs)
+ res ~= dir ~ filename;
+ } else {
+ res ~= filename;
+ }
+ return res;
+ }
+
+ /// create empty translator
+ this() {
+ _main = new shared UIStringList();
+ _fallback = new shared UIStringList();
+ }
+
+ /** Load translation file(s) */
+ bool load(string mainFilename, string fallbackFilename = null) {
+ _main.clear();
+ _fallback.clear();
+ bool res = _main.load(convertResourcePaths(mainFilename));
+ if (fallbackFilename !is null) {
+ res = _fallback.load(convertResourcePaths(fallbackFilename)) || res;
+ }
+ return res;
+ }
+
+ /** Translate string ID to string (returns "UNTRANSLATED: id" for missing values) */
+ dstring get(string id, dstring fallbackValue = null) {
+ if (id is null)
+ return null;
+ dstring s = _main.get(id);
+ if (s !is null)
+ return s;
+ s = _fallback.get(id);
+ if (s !is null)
+ return s;
+ if (fallbackValue.length > 0)
+ return fallbackValue;
+ return "UNTRANSLATED: "d ~ toUTF32(id);
+ }
+}
+
+/** UI string translator */
+private shared class UIStringList {
+ private dstring[string] _map;
+ /// remove all items
+ void clear() {
+ _map.destroy();
+ }
+ /// set item value
+ void set(string id, dstring value) {
+ _map[id] = value;
+ }
+ /// get item value, null if translation is not found for id
+ dstring get(string id) const {
+ if (id in _map)
+ return _map[id];
+ return null;
+ }
+ /// load strings from stream
+ bool load(std.stream.InputStream stream) {
+ dlangui.core.linestream.LineStream lines = dlangui.core.linestream.LineStream.create(stream, "");
+ int count = 0;
+ for (;;) {
+ dchar[] s = lines.readLine();
+ if (s is null)
+ break;
+ int eqpos = -1;
+ int firstNonspace = -1;
+ int lastNonspace = -1;
+ for (int i = 0; i < s.length; i++)
+ if (s[i] == '=') {
+ eqpos = i;
+ break;
+ } else if (s[i] != ' ' && s[i] != '\t') {
+ if (firstNonspace == -1)
+ firstNonspace = i;
+ lastNonspace = i;
+ }
+ if (eqpos > 0 && firstNonspace != -1) {
+ string id = toUTF8(s[firstNonspace .. lastNonspace + 1]);
+ dstring value = s[eqpos + 1 .. $].dup;
+ set(id, value);
+ count++;
+ }
+ }
+ return count > 0;
+ }
+
+ /// load strings from file (utf8, id=value lines)
+ bool load(string[] filenames) {
+ clear();
+ bool res = false;
+ foreach(filename; filenames) {
+ import std.stream;
+ import std.file;
+ try {
+ debug Log.d("Loading string resources from file ", filename);
+ if (!exists(filename) || !isFile(filename)) {
+ Log.e("File does not exist: ", filename);
+ continue;
+ }
+ std.stream.File f = new std.stream.File(filename);
+ scope(exit) { f.close(); }
+ res = load(f) || res;
+ } catch (StreamFileException e) {
+ Log.e("Cannot read string resources from file ", filename);
+ }
+ }
+ return res;
+ }
+}
+
+/** Global UI translator object */
+shared UIStringTranslator i18n;
+shared static this() {
+ i18n = new shared UIStringTranslator();
+}
diff --git a/workspaces/sample1/sampleproject2/source/exlib/logger.d b/workspaces/sample1/sampleproject2/source/exlib/logger.d
new file mode 100644
index 0000000..64555f7
--- /dev/null
+++ b/workspaces/sample1/sampleproject2/source/exlib/logger.d
@@ -0,0 +1,155 @@
+// Written in the D programming language.
+
+/**
+This module provides logging utilities.
+
+Use Log class static methods.
+
+Synopsis:
+
+----
+import exlib.logger;
+
+// setup:
+
+// use stderror for logging
+setStderrLogger();
+// set log level
+setLogLevel(LogLeve.Debug);
+
+// usage:
+
+// log debug message
+Log.d("mouse clicked at ", x, ",", y);
+// log error message
+Log.e("exception while reading file", e);
+----
+
+Copyright: Vadim Lopatin, 2014
+License: Boost License 1.0
+Authors: Vadim Lopatin, coolreader.org@gmail.com
+*/
+module exlib.logger;
+
+import std.stdio;
+import std.datetime;
+
+/// Log levels
+enum LogLevel : int {
+ /// Fatal error, cannot resume
+ Fatal,
+ /// Error
+ Error,
+ /// Warning
+ Warn,
+ /// Informational message
+ Info,
+ /// Debug message
+ Debug,
+ /// Tracing message
+ Trace
+}
+
+/// Returns timestamp in milliseconds since 1970 UTC similar to Java System.currentTimeMillis()
+long currentTimeMillis() {
+ return std.datetime.Clock.currStdTime / 10000;
+}
+
+/**
+
+ Logging utilities
+
+Setup example:
+----
+// use stderror for logging
+setStderrLogger();
+// set log level
+setLogLevel(LogLeve.Debug);
+----
+
+Logging example:
+----
+// log debug message
+Log.d("mouse clicked at ", x, ",", y);
+// log error message
+Log.e("exception while reading file", e);
+----
+
+*/
+synchronized class Log {
+ static:
+ private LogLevel logLevel = LogLevel.Info;
+ private std.stdio.File logFile;
+
+ /// Redirects output to stdout
+ void setStdoutLogger() {
+ logFile = stdout;
+ }
+
+ /// Redirects output to stderr
+ void setStderrLogger() {
+ logFile = stderr;
+ }
+
+ /// Redirects output to file
+ void setFileLogger(File file) {
+ logFile = file;
+ }
+
+ /// Sets log level (one of LogLevel)
+ void setLogLevel(LogLevel level) {
+ logLevel = level;
+ }
+
+ /// Log level to name helper function
+ string logLevelName(LogLevel level) {
+ switch (level) {
+ case LogLevel.Fatal: return "F";
+ case LogLevel.Error: return "E";
+ case LogLevel.Warn: return "W";
+ case LogLevel.Info: return "I";
+ case LogLevel.Debug: return "D";
+ case LogLevel.Trace: return "V";
+ default: return "?";
+ }
+ }
+ /// Log message with arbitrary log level
+ void log(S...)(LogLevel level, S args) {
+ if (logLevel >= level && logFile.isOpen) {
+ SysTime ts = Clock.currTime();
+ logFile.writef("%04d-%02d-%02d %02d:%02d:%02d.%03d %s ", ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second, ts.fracSec.msecs, logLevelName(level));
+ logFile.writeln(args);
+ logFile.flush();
+ }
+ }
+ /// Log verbose / trace message
+ void v(S...)(S args) {
+ if (logLevel >= LogLevel.Trace && logFile.isOpen)
+ log(LogLevel.Trace, args);
+ }
+ /// Log debug message
+ void d(S...)(S args) {
+ if (logLevel >= LogLevel.Debug && logFile.isOpen)
+ log(LogLevel.Debug, args);
+ }
+ /// Log info message
+ void i(S...)(S args) {
+ if (logLevel >= LogLevel.Info && logFile.isOpen)
+ log(LogLevel.Info, args);
+ }
+ /// Log warn message
+ void w(S...)(S args) {
+ if (logLevel >= LogLevel.Warn && logFile.isOpen)
+ log(LogLevel.Warn, args);
+ }
+ /// Log error message
+ void e(S...)(S args) {
+ if (logLevel >= LogLevel.Error && logFile.isOpen)
+ log(LogLevel.Error, args);
+ }
+ /// Log fatal error message
+ void f(S...)(S args) {
+ if (logLevel >= LogLevel.Fatal && logFile.isOpen)
+ log(LogLevel.Fatal, args);
+ }
+}
diff --git a/workspaces/sample1/sampleproject2/source/main.d b/workspaces/sample1/sampleproject2/source/main.d
index d5df88b..635face 100644
--- a/workspaces/sample1/sampleproject2/source/main.d
+++ b/workspaces/sample1/sampleproject2/source/main.d
@@ -1,6 +1,9 @@
#!/usr/bin/env rdmd
// Computes average line length for standard input.
import std.stdio;
+import exlib.logger;
+import exlib.files;
+import exlib.i18n;
void main()
{