1:- module(lsp_server, [main/0]).
9:- use_module(library(apply), [maplist/2]). 10:- use_module(library(debug), [debug/3, debug/1]). 11:- use_module(library(http/json), [atom_json_dict/3]). 12:- use_module(library(prolog_xref)). 13:- use_module(library(prolog_source), [directory_source_files/3]). 14:- use_module(library(utf8), [utf8_codes//1]). 15:- use_module(library(yall)). 16
17:- use_module(lsp_utils). 18:- use_module(lsp_checking, [check_errors/2]). 19:- use_module(lsp_parser, [lsp_request//1]). 20:- use_module(lsp_changes, [handle_doc_changes/2]). 21:- use_module(lsp_completion, [completions_at/3]). 22:- use_module(lsp_colours, [file_colours/2,
23 file_range_colours/4,
24 token_types/1,
25 token_modifiers/1]). 26
27main :-
28 set_prolog_flag(debug_on_error, false),
29 set_prolog_flag(report_error, true),
30 set_prolog_flag(toplevel_prompt, ''),
31 current_prolog_flag(argv, Args),
32 debug(server),
33 start(Args).
34
35start([stdio]) :- !,
36 debug(server, "Starting stdio client", []),
37 stdio_server.
38start(Args) :-
39 debug(server, "Unknown args ~w", [Args]).
40
41
43
44stdio_server :-
45 current_input(In),
46 set_stream(In, buffer(full)),
47 set_stream(In, newline(posix)),
48 set_stream(In, tty(false)),
49 set_stream(In, representation_errors(error)),
50 51 52 set_stream(In, encoding(octet)),
53 current_output(Out),
54 set_stream(Out, encoding(utf8)),
55 stdio_handler(A-A, In).
58stdio_handler(Extra-ExtraTail, In) :-
59 wait_for_input([In], _, infinite),
60 fill_buffer(In),
61 read_pending_codes(In, ReadCodes, Tail),
62 ( Tail == []
63 -> true
64 ; ( current_output(Out),
65 ExtraTail = ReadCodes,
66 handle_requests(Out, Extra, Remainder),
67 stdio_handler(Remainder-Tail, In) )
68 ).
69
70handle_requests(Out, In, Tail) :-
71 handle_request(Out, In, Rest), !,
72 ( var(Rest)
73 -> Tail = Rest
74 ; handle_requests(Out, Rest, Tail) ).
75handle_requests(_, T, T).
76
78
79send_message(Stream, Msg) :-
80 put_dict(jsonrpc, Msg, "2.0", VersionedMsg),
81 atom_json_dict(JsonCodes, VersionedMsg, [as(codes), width(0)]),
82 phrase(utf8_codes(JsonCodes), UTF8Codes),
83 length(UTF8Codes, ContentLength),
84 format(Stream, "Content-Length: ~w\r\n\r\n~s", [ContentLength, JsonCodes]),
85 flush_output(Stream).
86
87handle_request(OutStream, Input, Rest) :-
88 phrase(lsp_request(Req), Input, Rest),
89 debug(server(high), "Request ~w", [Req.body]),
90 catch(
91 ( handle_msg(Req.body.method, Req.body, Resp),
92 debug(server(high), "response ~w", [Resp]),
93 ( is_dict(Resp) -> send_message(OutStream, Resp) ; true ) ),
94 Err,
95 ( debug(server, "error handling msg ~w", [Err]),
96 get_dict(id, Req.body, Id),
97 send_message(OutStream, _{id: Id,
98 error: _{code: -32001,
99 message: "server error"}})
100 )).
101
103
104server_capabilities(
105 _{textDocumentSync: _{openClose: true,
106 change: 2, 107 save: _{includeText: false},
108 willSave: false,
109 willSaveWaitUntil: false 110 },
111 hoverProvider: true,
112 completionProvider: _{},
113 definitionProvider: true,
114 declarationProvider: true,
115 implementationProvider: true,
116 referencesProvider: true,
117 documentHighlightProvider: false,
118 documentSymbolProvider: true,
119 workspaceSymbolProvider: true,
120 codeActionProvider: false,
122 documentFormattingProvider:false,
124 renameProvider: false,
125 126 127 foldingRangeProvider: false,
128 executeCommandProvider: _{commands: ["query", "assert"]},
129 semanticTokensProvider: _{legend: _{tokenTypes: TokenTypes,
130 tokenModifiers: TokenModifiers},
131 range: true,
132 133 full: _{delta: false}},
134 workspace: _{workspaceFolders: _{supported: true,
135 changeNotifications: true}}
136 }
137)
137 :-
138 token_types(TokenTypes),
139 token_modifiers(TokenModifiers)
139.
140
141:- dynamic loaded_source/1. 142
144handle_msg("initialize", Msg,
145 _{id: Id, result: _{capabilities: ServerCapabilities} }) :-
146 _{id: Id, params: Params} :< Msg, !,
147 ( Params.rootUri \== null
148 -> ( atom_concat('file://', RootPath, Params.rootUri),
149 directory_source_files(RootPath, Files, [recursive(true)]),
150 maplist([F]>>assert(loaded_source(F)), Files) )
151 ; true ),
152 server_capabilities(ServerCapabilities).
153handle_msg("shutdown", Msg, _{id: Id, result: null}) :-
154 _{id: Id} :< Msg,
155 debug(server, "recieved shutdown message", []).
156handle_msg("textDocument/hover", Msg, _{id: Id, result: Response}) :-
157 _{params: _{position: _{character: Char0, line: Line0},
158 textDocument: _{uri: Doc}}, id: Id} :< Msg,
159 atom_concat('file://', Path, Doc),
160 Line1 is Line0 + 1,
161 ( help_at_position(Path, Line1, Char0, Help)
162 -> Response = _{contents: _{kind: plaintext, value: Help}}
163 ; Response = null).
164handle_msg("textDocument/documentSymbol", Msg, _{id: Id, result: Symbols}) :-
165 _{id: Id, params: _{textDocument: _{uri: Doc}}} :< Msg,
166 atom_concat('file://', Path, Doc), !,
167 xref_source(Path),
168 findall(
169 Symbol,
170 ( xref_defined(Path, Goal, local(Line)),
171 succ(Line, NextLine),
172 succ(Line0, Line),
173 functor(Goal, Name, Arity),
174 format(string(GoalName), "~w/~w", [Name, Arity]),
175 Symbol = _{name: GoalName,
176 kind: 12, 177 location:
178 _{uri: Doc,
179 range: _{start: _{line: Line0, character: 1},
180 end: _{line: NextLine, character: 0}}}}
181 ),
182 Symbols).
183handle_msg("textDocument/definition", Msg, _{id: Id, result: Location}) :-
184 _{id: Id, params: Params} :< Msg,
185 _{textDocument: _{uri: Doc},
186 position: _{line: Line0, character: Char0}} :< Params,
187 atom_concat('file://', Path, Doc),
188 succ(Line0, Line1),
189 clause_in_file_at_position(Name/Arity, Path, line_char(Line1, Char0)),
190 defined_at(Path, Name/Arity, Location).
191handle_msg("textDocument/definition", Msg, _{id: Msg.id, result: null}) :- !.
192handle_msg("textDocument/references", Msg, _{id: Id, result: Locations}) :-
193 _{id: Id, params: Params} :< Msg,
194 _{textDocument: _{uri: Uri},
195 position: _{line: Line0, character: Char0}} :< Params,
196 atom_concat('file://', Path, Uri),
197 succ(Line0, Line1),
198 clause_in_file_at_position(Clause, Path, line_char(Line1, Char0)),
199 findall(
200 Location,
201 ( loaded_source(Doc),
202 atom_concat('file://', Doc, DocUri),
203 called_at(Doc, Clause, Caller, Loc),
204 relative_ref_location(DocUri, Caller, Loc, Location)
205 ),
206 Locations), !.
207handle_msg("textDocument/references", Msg, _{id: Msg.id, result: null}) :- !.
208handle_msg("textDocument/completion", Msg, _{id: Id, result: Completions}) :-
209 _{id: Id, params: Params} :< Msg,
210 _{textDocument: _{uri: Uri},
211 position: _{line: Line0, character: Char0}} :< Params,
212 atom_concat('file://', Path, Uri),
213 succ(Line0, Line1),
214 completions_at(Path, line_char(Line1, Char0), Completions).
215handle_msg("textDocument/semanticTokens", Msg, Response) :-
216 handle_msg("textDocument/semanticTokens/full", Msg, Response).
217handle_msg("textDocument/semanticTokens/full", Msg,
218 _{id: Id, result: _{data: Highlights}}) :-
219 _{id: Id, params: Params} :< Msg,
220 _{textDocument: _{uri: Uri}} :< Params,
221 atom_concat('file://', Path, Uri), !,
222 xref_source(Path),
223 file_colours(Path, Highlights).
224handle_msg("textDocument/semanticTokens/range", Msg,
225 _{id: Id, result: _{data: Highlights}}) :-
226 _{id: Id, params: Params} :< Msg,
227 _{textDocument: _{uri: Uri}, range: Range} :< Params,
228 _{start: _{line: StartLine0, character: StartChar},
229 end: _{line: EndLine0, character: EndChar}} :< Range,
230 atom_concat('file://', Path, Uri), !,
231 succ(StartLine0, StartLine), succ(EndLine0, EndLine),
232 xref_source(Path),
233 file_range_colours(Path,
234 line_char(StartLine, StartChar),
235 line_char(EndLine, EndChar),
236 Highlights).
238handle_msg("textDocument/didOpen", Msg, Resp) :-
239 _{params: _{textDocument: TextDoc}} :< Msg,
240 _{uri: FileUri} :< TextDoc,
241 atom_concat('file://', Path, FileUri),
242 ( loaded_source(Path) ; assertz(loaded_source(Path)) ),
243 check_errors_resp(FileUri, Resp).
244handle_msg("textDocument/didChange", Msg, false) :-
245 _{params: _{textDocument: TextDoc,
246 contentChanges: Changes}} :< Msg,
247 _{uri: Uri} :< TextDoc,
248 atom_concat('file://', Path, Uri),
249 handle_doc_changes(Path, Changes).
250handle_msg("textDocument/didSave", Msg, Resp) :-
251 _{params: Params} :< Msg,
252 check_errors_resp(Params.textDocument.uri, Resp).
253handle_msg("textDocument/didClose", Msg, false) :-
254 _{params: _{textDocument: TextDoc}} :< Msg,
255 _{uri: FileUri} :< TextDoc,
256 atom_concat('file://', Path, FileUri),
257 retractall(loaded_source(Path)).
258handle_msg("initialized", Msg, false) :-
259 debug(server, "initialized ~w", [Msg]).
260handle_msg("$/cancelRequest", Msg, false) :-
261 debug(server, "Cancel request Msg ~w", [Msg]).
262handle_msg("exit", _Msg, false) :-
263 debug(server, "recieved exit, shutting down", []),
264 halt.
266handle_msg(_, Msg, _{id: Id, error: _{code: -32603, message: "Unimplemented"}}) :-
267 _{id: Id} :< Msg, !,
268 debug(server, "unknown message ~w", [Msg]).
269handle_msg(_, Msg, false) :-
270 debug(server, "unknown notification ~w", [Msg]).
271
272check_errors_resp(FileUri, _{method: "textDocument/publishDiagnostics",
273 params: _{uri: FileUri, diagnostics: Errors}}) :-
274 atom_concat('file://', Path, FileUri),
275 check_errors(Path, Errors).
276check_errors_resp(_, false) :-
277 debug(server, "Failed checking errors", [])
LSP Server
The main entry point for the Language Server implementation.