1 module served.textdocumentmanager; 2 3 import std.algorithm; 4 import std.json; 5 import std.utf : decode; 6 7 import served.jsonrpc; 8 import served.protocol; 9 10 import painlessjson; 11 12 struct Document 13 { 14 DocumentUri uri; 15 string languageId; 16 long version_; 17 string text; 18 19 this(DocumentUri uri) 20 { 21 this.uri = uri; 22 languageId = "d"; 23 version_ = 0; 24 text = ""; 25 } 26 27 this(TextDocumentItem doc) 28 { 29 uri = doc.uri; 30 languageId = doc.languageId; 31 version_ = doc.version_; 32 text = doc.text; 33 } 34 35 size_t offsetToBytes(size_t offset) 36 { 37 size_t bytes; 38 size_t index; 39 while (index < offset) 40 { 41 decode(text, bytes); 42 index++; 43 } 44 return bytes; 45 } 46 47 size_t bytesToOffset(size_t bytes) 48 { 49 size_t offset; 50 size_t index; 51 while (index < bytes) 52 { 53 decode(text, index); 54 offset++; 55 } 56 return offset; 57 } 58 59 size_t positionToOffset(Position position) 60 { 61 size_t index = 0; 62 size_t offset = 0; 63 Position cur; 64 while (index < text.length) 65 { 66 if (position == cur) 67 return offset; 68 auto c = decode(text, index); 69 offset++; 70 cur.character++; 71 if (c == '\n') 72 { 73 if (cur.line == position.line) 74 return offset - 1; // end of line 75 cur.character = 0; 76 cur.line++; 77 } 78 } 79 return offset; 80 } 81 82 size_t positionToBytes(Position position) 83 { 84 size_t index = 0; 85 Position cur; 86 while (index < text.length) 87 { 88 if (position == cur) 89 return index; 90 auto c = decode(text, index); 91 cur.character++; 92 if (c == '\n') 93 { 94 if (cur.line == position.line) 95 return index - 1; // end of line 96 cur.character = 0; 97 cur.line++; 98 } 99 } 100 return text.length; 101 } 102 103 Position offsetToPosition(size_t offset) 104 { 105 size_t index = 0; 106 size_t offs = 0; 107 Position cur; 108 while (index < text.length) 109 { 110 if (offs >= offset) 111 return cur; 112 auto c = decode(text, index); 113 offs++; 114 cur.character++; 115 if (c == '\n') 116 { 117 cur.character = 0; 118 cur.line++; 119 } 120 } 121 return cur; 122 } 123 124 Position bytesToPosition(size_t offset) 125 { 126 size_t index = 0; 127 Position cur; 128 while (index < text.length) 129 { 130 if (index >= offset) 131 return cur; 132 auto c = decode(text, index); 133 cur.character++; 134 if (c == '\n') 135 { 136 cur.character = 0; 137 cur.line++; 138 } 139 } 140 return cur; 141 } 142 143 string lineAt(Position position) 144 { 145 size_t index = 0; 146 size_t lineStart = 0; 147 bool wasStart = true; 148 bool found = false; 149 Position cur; 150 while (index < text.length) 151 { 152 if (wasStart) 153 { 154 if (cur.line == position.line) 155 { 156 lineStart = index; 157 found = true; 158 } 159 if (cur.line == position.line + 1) 160 break; 161 } 162 wasStart = false; 163 auto c = decode(text, index); 164 cur.character++; 165 if (c == '\n') 166 { 167 wasStart = true; 168 cur.character = 0; 169 cur.line++; 170 } 171 } 172 if (!found) 173 return ""; 174 return text[lineStart .. index]; 175 } 176 177 unittest 178 { 179 void assertEqual(A, B)(A a, B b) 180 { 181 import std.conv : to; 182 183 assert(a == b, a.to!string ~ " is not equal to " ~ b.to!string); 184 } 185 186 Document doc; 187 doc.text = `abc 188 hellö world 189 how åre 190 you?`; 191 assertEqual(doc.lineAt(Position(0, 0)), "abc\n"); 192 assertEqual(doc.lineAt(Position(0, 100)), "abc\n"); 193 assertEqual(doc.lineAt(Position(1, 3)), "hellö world\n"); 194 assertEqual(doc.lineAt(Position(2, 0)), "how åre\n"); 195 assertEqual(doc.lineAt(Position(3, 0)), "you?"); 196 assertEqual(doc.lineAt(Position(3, 8)), "you?"); 197 assertEqual(doc.lineAt(Position(4, 0)), ""); 198 } 199 200 EolType eolAt(int line) 201 { 202 size_t index = 0; 203 int curLine = 0; 204 bool prevWasCr = false; 205 while (index < text.length) 206 { 207 if (curLine > line) 208 return EolType.lf; 209 auto c = decode(text, index); 210 if (c == '\n') 211 { 212 if (curLine == line) 213 { 214 return prevWasCr ? EolType.crlf : EolType.lf; 215 } 216 curLine++; 217 } 218 prevWasCr = c == '\r'; 219 } 220 return EolType.lf; 221 } 222 } 223 224 struct TextDocumentManager 225 { 226 Document[] documentStore; 227 228 ref Document opIndex(string uri) 229 { 230 auto idx = documentStore.countUntil!(a => a.uri == uri); 231 if (idx == -1) 232 throw new Exception("Document '" ~ uri ~ "' not found"); 233 return documentStore[idx]; 234 } 235 236 Document tryGet(string uri) 237 { 238 auto idx = documentStore.countUntil!(a => a.uri == uri); 239 if (idx == -1) 240 return Document.init; 241 return documentStore[idx]; 242 } 243 244 static TextDocumentSyncKind syncKind() 245 { 246 return TextDocumentSyncKind.incremental; 247 } 248 249 bool process(RequestMessage msg) 250 { 251 import std.stdio; 252 253 if (msg.method == "textDocument/didOpen") 254 { 255 auto params = msg.params.fromJSON!DidOpenTextDocumentParams; 256 documentStore ~= Document(params.textDocument); 257 return true; 258 } 259 else if (msg.method == "textDocument/didClose") 260 { 261 auto idx = documentStore.countUntil!(a => a.uri == msg.params["textDocument"]["uri"].str); 262 if (idx >= 0) 263 { 264 documentStore[idx] = documentStore[$ - 1]; 265 documentStore.length--; 266 } 267 return true; 268 } 269 else if (msg.method == "textDocument/didChange") 270 { 271 auto idx = documentStore.countUntil!(a => a.uri == msg.params["textDocument"]["uri"].str); 272 if (idx >= 0) 273 { 274 documentStore[idx].version_ = msg.params["textDocument"]["version"].integer; 275 foreach (change; msg.params["contentChanges"].array) 276 { 277 auto rangePtr = "range" in change; 278 if (!rangePtr) 279 { 280 documentStore[idx].text = change["text"].str; 281 break; 282 } 283 auto range = *rangePtr; 284 TextRange textRange = [range["start"].fromJSON!Position, range["end"].fromJSON!Position]; 285 auto start = documentStore[idx].positionToBytes(textRange[0]); 286 auto end = documentStore[idx].positionToBytes(textRange[1]); 287 if (start > end) 288 { 289 auto tmp = start; 290 start = end; 291 end = tmp; 292 } 293 documentStore[idx].text = documentStore[idx].text[0 .. start] 294 ~ change["text"].str ~ documentStore[idx].text[end .. $]; 295 } 296 } 297 return true; 298 } 299 return false; 300 } 301 }