1 module dls.util.document; 2 3 import dls.protocol.definitions; 4 import dls.protocol.interfaces; 5 6 class Document 7 { 8 private static Document[DocumentUri] _documents; 9 private wchar[][] _lines; 10 11 static auto opIndex(DocumentUri uri) 12 { 13 return uri in _documents ? _documents[uri] : null; 14 } 15 16 static void open(TextDocumentItem textDocument) 17 { 18 _documents[textDocument.uri] = new Document(textDocument); 19 } 20 21 static void close(TextDocumentIdentifier textDocument) 22 { 23 if (textDocument.uri in _documents) 24 { 25 _documents.remove(textDocument.uri); 26 } 27 } 28 29 static void change(VersionedTextDocumentIdentifier textDocument, 30 TextDocumentContentChangeEvent[] events) 31 { 32 if (textDocument.uri in _documents) 33 { 34 _documents[textDocument.uri].change(events); 35 } 36 } 37 38 @property auto lines() const 39 { 40 return this._lines; 41 } 42 43 this(TextDocumentItem textDocument) 44 { 45 this._lines = this.getText(textDocument.text); 46 } 47 48 override string toString() const 49 { 50 import std.range : join; 51 import std.utf : toUTF8; 52 53 return this._lines.join().toUTF8(); 54 } 55 56 auto bytePosition(Position position) 57 { 58 import std.algorithm : reduce; 59 import std.range : iota; 60 import std.utf : codeLength; 61 62 return reduce!((s, i) => s + codeLength!char(this._lines[i]))(cast(size_t) 0, 63 iota(position.line)) + codeLength!char( 64 this._lines[position.line][0 .. position.character]); 65 } 66 67 private void change(TextDocumentContentChangeEvent[] events) 68 { 69 foreach (event; events) 70 { 71 if (event.range.isNull) 72 { 73 this._lines = this.getText(event.text); 74 } 75 else 76 { 77 with (event.range) 78 { 79 auto linesBefore = this._lines[0 .. start.line]; 80 auto linesAfter = this._lines[end.line + 1 .. $]; 81 82 auto lineStart = this._lines[start.line][0 .. start.character]; 83 auto lineEnd = this._lines[end.line][end.character .. $]; 84 85 auto newLines = this.getText(event.text); 86 87 if (newLines.length) 88 { 89 newLines[0] = lineStart ~ newLines[0]; 90 newLines[$ - 1] = newLines[$ - 1] ~ lineEnd; 91 } 92 else 93 { 94 newLines = [lineStart ~ lineEnd]; 95 } 96 97 this._lines = linesBefore ~ newLines ~ linesAfter; 98 } 99 } 100 } 101 } 102 103 private auto getText(string text) const 104 { 105 import std.algorithm : endsWith; 106 import std.array : array; 107 import std.conv : to; 108 import std.string : lineSplitter; 109 import std.typecons : Yes; 110 import std.utf : toUTF16; 111 112 auto lines = lineSplitter!(Yes.keepTerminator)(text.toUTF16()).array; 113 114 if (!lines.length || lines[$ - 1].endsWith('\r', '\n')) 115 { 116 lines ~= ""; 117 } 118 119 return lines.to!(wchar[][]); 120 } 121 }