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 }