TextDocumentManager

Helper struct which should have one unique instance in the application which processes document events sent by a LSP client to an LSP server and creates an in-memory representation of all the files managed by the client.

This data structure is not thread safe.

Members

Functions

getOrFromFilesystem
Document getOrFromFilesystem(string uri, bool inserted)
Document getOrFromFilesystem(string uri)

Returns the managed document for the given URI or if it doesn't exist it tries to read the file from the filesystem and open it from that.

insertOrUpdate
Document insertOrUpdate(Document d)

Inserts a document manually or updates an existing one, acting like textDocument/didOpen if it didn't exist or fully replacing the document if it did exist.

loadFromFilesystem
deprecated Document loadFromFilesystem(string uri)
Undocumented in source. Be warned that the author may not have intended to support it.
opIndex
Document opIndex(string uri)

Same as tryGet but throws an exception if the URI doesn't exist.

process
bool process(RequestMessageRaw msg)

Processes an LSP packet and performs the document update in-memory that is requested.

tryGet
Document tryGet(string uri)

Tries to get a document from a URI, returns Document.init if it is not in the in-memory cache / not sent by the client.

unloadDocument
bool unloadDocument(string uri)

Unloads the given URI so it's no longer accessible. Note that this should only be done for documents loaded manually and never for LSP documents as it will break all features in that file until reopened.

Static functions

syncKind
TextDocumentSyncKind syncKind()

Returns the currently preferred syncKind to use with the client. Additionally always supports the full sync kind.

Variables

documentStore
Document[] documentStore;

Internal document storage. Only iterate over this using foreach, other operations are not considered officially supported.

Examples

1 import std.exception;
2 
3 TextDocumentManager documents;
4 // most common usage, forward LSP events to this helper struct.
5 RequestMessageRaw incomingPacket = {
6 	// dummy data
7 	method: "textDocument/didOpen",
8 	paramsJson: `{
9 		"textDocument": {
10 			"uri": "file:///home/projects/app.d",
11 			"languageId": "d",
12 			"version": 123,
13 			"text": "import std.stdio;\n\nvoid main()\n{\n\twriteln(\"hello world\");\n}\n"
14 		}
15 	}`
16 };
17 documents.process(incomingPacket);
18 // documents.process returns false if it's not a method meant for text
19 // document management. serve-d:serverbase abstracts this away automatically.
20 
21 // normally used from LSP methods where you have params like this
22 TextDocumentPositionParams params = {
23 	textDocument: TextDocumentIdentifier("file:///home/projects/app.d"),
24 	position: Position(4, 2)
25 };
26 
27 // if it's sent by the LSP, the document being loaded should be almost guaranteed.
28 auto doc = documents[params.textDocument.uri];
29 // trying to index files that haven't been sent by the client will throw an Exception
30 assertThrown(documents["file:///path/to/non-registered.d"]);
31 
32 // you can use tryGet to see if a Document has been opened yet and use it if so.
33 assert(documents.tryGet("file:///path/to/non-registered.d") is Document.init);
34 assert(documents.tryGet(params.textDocument.uri) !is Document.init);
35 
36 // Document defines a variety of utility functions that have been optimized
37 // for speed and convenience.
38 assert(doc.lineAtScope(params.position) == "\twriteln(\"hello world\");\n");
39 
40 auto range = doc.wordRangeAt(params.position);
41 assert(doc.positionToBytes(range.start) == 34);
42 assert(doc.positionToBytes(range.end) == 41);
43 
44 // when yielding (Fiber context switch) documents may be modified or deleted though:
45 
46 RequestMessageRaw incomingPacket2 = {
47 	// dummy data
48 	method: "textDocument/didChange",
49 	paramsJson: `{
50 		"textDocument": {
51 			"uri": "file:///home/projects/app.d",
52 			"version": 124
53 		},
54 		"contentChanges": [
55 			{
56 				"range": {
57 					"start": { "line": 4, "character": 6 },
58 					"end": { "line": 4, "character": 8 }
59 				},
60 				"text": ""
61 			}
62 		]
63 	}`
64 };
65 documents.process(incomingPacket2);
66 
67 assert(doc.lineAtScope(params.position) == "\twrite(\"hello world\");\n");
68 
69 RequestMessageRaw incomingPacket3 = {
70 	// dummy data
71 	method: "textDocument/didChange",
72 	paramsJson: `{
73 		"textDocument": {
74 			"uri": "file:///home/projects/app.d",
75 			"version": 125
76 		},
77 		"contentChanges": [
78 			{
79 				"text": "replace everything"
80 			}
81 		]
82 	}`
83 };
84 documents.process(incomingPacket3);
85 
86 // doc.rawText is now half overwritten, you need to refetch a document when yielding or updating:
87 assert(doc.rawText != "replace everything");
88 doc = documents[params.textDocument.uri];
89 assert(doc.rawText == "replace everything");
90 
91 RequestMessageRaw incomingPacket4 = {
92 	// dummy data
93 	method: "textDocument/didClose",
94 	paramsJson: `{
95 		"textDocument": {
96 			"uri": "file:///home/projects/app.d"
97 		}
98 	}`
99 };
100 documents.process(incomingPacket4);
101 
102 assertThrown(documents[params.textDocument.uri]);
103 // so make sure that you don't keep references to documents when leaving scope or switching context.

Meta