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 }