hoshi-lang dev
Yet another programming language
Loading...
Searching...
No Matches
server.cpp
Go to the documentation of this file.
1//
2// LSP Server implementation — JSON-RPC I/O and message dispatch
3//
4
5#include "server.h"
6#include "completion.h"
7#include "hover.h"
8#include "definition.h"
9#include <climits>
10#include <iostream>
11#include <filesystem>
12#include <sstream>
13#include <share/def.hpp>
14
15namespace lsp {
16
19 compilerCtx = std::make_shared<yoi::compilerContext>();
20 // Set a minimal build config immediately so the visitor doesn't crash
21 auto buildConfig = yoi::IRBuildConfig::Builder()
26 .setSearchPaths({L"", (std::filesystem::path(yoi::whereIsHoshiLang()) / ".." / "lib").wstring()})
27 .setMarco(L"platform", yoi::string2wstring(YOI_PLATFORM))
28 .setMarco(L"arch", yoi::string2wstring(YOI_ARCH))
29 .setMarco(L"hoshi_feature_version", yoi::string2wstring(HOSHI_LANG_VERSION))
30 .setMarco(L"hoshi_lang_commit", yoi::string2wstring(HOSHI_LANG_GIT_COMMIT_HASH))
31 .setImmediatelyClearupCache(true)
32 .yield();
33 compilerCtx->setBuildConfig(buildConfig);
34 // Defer initializeSharedObjects() — heavy, done in handleInitialized
36}
37
38LspServer::~LspServer() = default;
39
40// ============================================================
41// JSON-RPC I/O (stdio transport)
42// ============================================================
43
45 // Read Content-Length header
46 std::string line;
47 int contentLength = 0;
48
49 while (std::getline(std::cin, line)) {
50 // Trim trailing \r
51 if (!line.empty() && line.back() == '\r')
52 line.pop_back();
53
54 if (line.empty()) break; // End of headers
55
56 if (line.rfind("Content-Length:", 0) == 0) {
57 std::string lenStr = line.substr(15);
58 // Trim leading space
59 size_t pos = lenStr.find_first_not_of(" \t");
60 if (pos != std::string::npos) lenStr = lenStr.substr(pos);
61 contentLength = std::stoi(lenStr);
62 }
63 }
64
65 if (contentLength <= 0) return "";
66
67 // Read the JSON body
68 std::string content(contentLength, '\0');
69 std::cin.read(&content[0], contentLength);
70 return content;
71}
72
73void LspServer::writeMessage(const std::string &content) {
74 std::ostringstream header;
75 header << "Content-Length: " << content.size() << "\r\n\r\n";
76 std::cout << header.str() << content << std::flush;
77}
78
79void LspServer::sendResponse(const json &id, const json &result) {
80 json response = {
81 {"jsonrpc", "2.0"},
82 {"id", id},
83 {"result", result}
84 };
85 writeMessage(response.dump());
86}
87
88void LspServer::sendError(const json &id, int code, const std::string &message) {
89 json response = {
90 {"jsonrpc", "2.0"},
91 {"id", id},
92 {"error", {{"code", code}, {"message", message}}}
93 };
94 writeMessage(response.dump());
95}
96
97void LspServer::sendNotification(const std::string &method, const json &params) {
98 json notification = {
99 {"jsonrpc", "2.0"},
100 {"method", method},
101 {"params", params}
102 };
103 writeMessage(notification.dump());
104}
105
106// ============================================================
107// Message dispatch
108// ============================================================
109
111 std::string method = msg.value("method", "");
112 json id = msg.contains("id") ? msg["id"] : json();
113 json params = msg.contains("params") ? msg["params"] : json::object();
114
115 bool isRequest = msg.contains("id");
116
117 if (method == "initialize") {
118 handleInitialize(id, params);
119 } else if (method == "initialized") {
120 handleInitialized(params);
121 } else if (method == "shutdown") {
122 handleShutdown(id);
123 } else if (method == "exit") {
124 handleExit();
125 } else if (method == "textDocument/didOpen") {
126 handleDidOpen(params);
127 } else if (method == "textDocument/didChange") {
128 handleDidChange(params);
129 } else if (method == "textDocument/didClose") {
130 handleDidClose(params);
131 } else if (method == "textDocument/completion") {
132 handleCompletion(id, params);
133 } else if (method == "textDocument/hover") {
134 handleHover(id, params);
135 } else if (method == "textDocument/definition") {
136 handleDefinition(id, params);
137 } else if (method == "textDocument/references") {
138 handleReferences(id, params);
139 } else if (method == "textDocument/documentSymbol") {
140 handleDocumentSymbol(id, params);
141 } else if (isRequest) {
142 // Unhandled request — return method not found
143 sendError(id, -32601, "Method not found: " + method);
144 }
145 // Notifications with no handler are silently ignored
146}
147
148// ============================================================
149// LSP Lifecycle
150// ============================================================
151
152void LspServer::handleInitialize(const json &id, const json &params) {
153 // Extract workspace root for module search paths
154 if (params.contains("rootUri") && params["rootUri"].is_string()) {
155 workspaceRoot = params["rootUri"].get<std::string>();
156 // Strip file:// prefix
157 if (workspaceRoot.rfind("file://", 0) == 0) {
158 workspaceRoot = workspaceRoot.substr(7);
159 }
161 projectIndex.addSearchPath(workspaceRoot + "/.tsuki_modules");
163 }
164
165 // Also check rootPath (older LSP clients)
166 if (params.contains("rootPath") && params["rootPath"].is_string() && workspaceRoot.empty()) {
167 workspaceRoot = params["rootPath"].get<std::string>();
169 projectIndex.addSearchPath(workspaceRoot + "/.tsuki_modules");
171 }
172
173 // Mirror the compiler's default include paths (main.cpp line 77):
174 // includeDirs{L"", (whereIsHoshiLang() / ".." / "lib").wstring()}
175 // "" = current working directory
177
178 // Global hoshi library: ~/.hoshi/lib/ (whereIsHoshiLang() returns ~/.hoshi/bin)
179 std::filesystem::path hoshiBinPath(yoi::wstring2string(yoi::whereIsHoshiLang()));
180 std::filesystem::path hoshiLibPath = hoshiBinPath / ".." / "lib";
181 std::error_code ec;
182 // canonical() resolves .. and symlinks for a clean path
183 std::string globalLib = std::filesystem::canonical(hoshiLibPath, ec).string();
184 if (!ec) {
185 projectIndex.addSearchPath(globalLib);
186 projectIndex.addSearchPath(globalLib + "/.tsuki_modules");
187 }
188
189 InitializeResult result;
190 sendResponse(id, json(result));
191}
192
194 isInitialized = true;
196 try {
197 compilerCtx->initializeSharedObjects();
198 compilerInitialized = true;
199 } catch (const std::exception &e) {
200 std::cerr << "[lsp] compiler init failed: " << e.what() << std::endl;
201 }
202 }
203}
204
206 isShuttingDown = true;
207 sendResponse(id, json(nullptr));
208}
209
211 if (isShuttingDown)
212 std::exit(0);
213 else
214 std::exit(1);
215}
216
217// ============================================================
218// Document Sync
219// ============================================================
220
227
228void LspServer::handleDidChange(const json &params) {
230 if (!p.contentChanges.empty()) {
231 // Full document sync (change = 1): take the full text from the last change
232 std::string newText = p.contentChanges.back().text;
234 }
236}
237
238void LspServer::handleDidClose(const json &params) {
241
242 // Clear diagnostics for closed document
244 pub.uri = p.textDocument.uri;
245 sendNotification("textDocument/publishDiagnostics", json(pub));
246}
247
248// ============================================================
249// Diagnostics
250// ============================================================
251
252void LspServer::publishDiagnostics(const std::string &uri, int version) {
253 Document *doc = documents.getDocument(uri);
254 if (!doc) return;
255
257 pub.uri = uri;
258 pub.version = version;
259
260 for (auto &diag : doc->diagnostics) {
261 LspDiagnostic ld;
262 // Convert 1-based (internal) to 0-based (LSP)
263 ld.range.start.line = static_cast<int>(diag.line) - 1;
264 ld.range.start.character = static_cast<int>(diag.column) - 1;
265 ld.range.end.line = ld.range.start.line;
267
268 switch (diag.severity) {
271 break;
274 break;
277 break;
280 break;
281 }
282
283 ld.message = diag.message;
284 ld.source = "hoshi-lang";
285 pub.diagnostics.push_back(ld);
286 }
287
288 sendNotification("textDocument/publishDiagnostics", json(pub));
289}
290
291// ============================================================
292// Language Features
293// ============================================================
294
295void LspServer::handleCompletion(const json &id, const json &params) {
296 CompletionParams p = params.get<CompletionParams>();
298
299 CompletionProvider provider;
300 CompletionList result = provider.provide(doc, p.position);
301
302 sendResponse(id, json(result));
303}
304
305void LspServer::handleHover(const json &id, const json &params) {
306 HoverParams p = params.get<HoverParams>();
308
309 HoverProvider provider;
310 std::optional<Hover> result = provider.provide(doc, p.position);
311
312 if (result) {
313 sendResponse(id, json(*result));
314 } else {
315 sendResponse(id, json(nullptr));
316 }
317}
318
319void LspServer::handleDefinition(const json &id, const json &params) {
320 DefinitionParams p = params.get<DefinitionParams>();
322
323 DefinitionProvider provider;
324 DefinitionResult result = provider.provide(doc, p.position, p.textDocument.uri);
325
326 if (result.empty()) {
327 sendResponse(id, json(nullptr));
328 } else if (result.size() == 1) {
329 sendResponse(id, json(result[0]));
330 } else {
331 sendResponse(id, json(result));
332 }
333}
334
335void LspServer::handleReferences(const json &id, const json &) {
336 // References not fully implemented yet
338}
339
340// Safe cast: yoi::indexT (uint64_t) → int for LSP positions.
341// Clamps to [0, INT_MAX] to avoid negative values from overflow.
342static int safeInt(yoi::indexT v) {
343 return static_cast<int>(v > static_cast<yoi::indexT>(INT_MAX) ? INT_MAX : v);
344}
345
346// Map internal HoshiSymbolKind → LSP SymbolKind
368
370 DocumentSymbol result;
371 result.name = yoi::wstring2string(symbol.name);
372 result.detail = yoi::wstring2string(symbol.detail);
373 result.kind = mapSymbolKind(symbol.kind);
374
375 const int startLine = safeInt(symbol.line);
376 const int startColumn = safeInt(symbol.column);
377 const bool hasEnd = symbol.endLine != 0 || symbol.endColumn != 0;
378 int endLine = safeInt(hasEnd ? symbol.endLine : symbol.line);
379 int endColumn = safeInt(hasEnd ? symbol.endColumn :
380 symbol.column + symbol.name.size());
381 if (endLine < startLine) endLine = startLine;
382 if (endLine == startLine && endColumn <= startColumn) endColumn = startColumn + 1;
383
384 result.range.start.line = startLine;
385 result.range.start.character = startColumn;
386 result.range.end.line = endLine;
387 result.range.end.character = endColumn;
388 result.selectionRange.start.line = startLine;
389 result.selectionRange.start.character = startColumn;
390 result.selectionRange.end.line = startLine;
391 result.selectionRange.end.character = startColumn + safeInt(symbol.name.size());
392 if (result.selectionRange.end.character <= startColumn) {
393 result.selectionRange.end.character = startColumn + 1;
394 }
395 return result;
396}
397
398static void extendRangeToInclude(Range &range, const Range &child) {
399 if (child.end.line > range.end.line ||
400 (child.end.line == range.end.line && child.end.character > range.end.character)) {
401 range.end = child.end;
402 }
403}
404
405void LspServer::handleDocumentSymbol(const json &id, const json &params) {
406 auto uri = params["textDocument"]["uri"].get<std::string>();
407 Document *doc = documents.getDocument(uri);
408
409 std::vector<DocumentSymbol> result;
410
411 if (doc && doc->parseSucceeded) {
412 for (auto &sym : doc->symbols) {
413 if (sym.isLocal) continue;
414
416 const Range selectionRange = ds.selectionRange;
417
418 for (auto &child : sym.children) {
419 DocumentSymbol childSymbol = documentSymbolFor(child);
420 extendRangeToInclude(ds.range, childSymbol.range);
421 ds.children.push_back(std::move(childSymbol));
422 }
423
424 // Function parameters and body declarations live in the extractor's
425 // lookup index. Present them as children rather than top-level Outline entries.
426 for (auto &local : doc->symbols) {
427 if (!local.isLocal || local.parentName != sym.name ||
428 local.ownerLine != sym.line) {
429 continue;
430 }
431 DocumentSymbol localSymbol = documentSymbolFor(local);
432 extendRangeToInclude(ds.range, localSymbol.range);
433 ds.children.push_back(std::move(localSymbol));
434 }
435
436 ds.selectionRange = selectionRange;
437
438 result.push_back(ds);
439 }
440 }
441
442 sendResponse(id, json(result));
443}
444
445// ============================================================
446// Main loop
447// ============================================================
448
450 while (!std::cin.eof() && !isShuttingDown) {
451 try {
452 std::string content = readMessage();
453 if (content.empty()) {
454 if (std::cin.eof()) break;
455 continue;
456 }
457 json msg = json::parse(content);
458 handleMessage(msg);
459 } catch (const std::exception &e) {
460 std::cerr << "[lsp] error: " << e.what() << std::endl;
461 }
462 }
463}
464
465} // namespace lsp
static JSON_HEDLEY_WARN_UNUSED_RESULT basic_json object(initializer_list_t init={})
explicitly create an object from an initializer list
Definition json.hpp:21035
array_t * array
array (stored with pointer to save storage)
Definition json.hpp:20445
CompletionList provide(Document *doc, const Position &pos)
DefinitionResult provide(Document *doc, const Position &pos, const std::string &uri)
void closeDocument(const std::string &uri)
Definition document.cpp:47
void openDocument(const std::string &uri, const std::string &text, const std::string &languageId, int version)
Definition document.cpp:25
void setCompilerContext(std::shared_ptr< yoi::compilerContext > ctx)
Definition document.h:44
Document * getDocument(const std::string &uri)
Definition document.cpp:51
void setProjectIndex(ProjectIndex *index)
Definition document.h:42
void updateDocument(const std::string &uri, const std::string &text, int version)
Definition document.cpp:37
std::optional< Hover > provide(Document *doc, const Position &pos)
Definition hover.cpp:11
void handleDidOpen(const json &params)
Definition server.cpp:221
void writeMessage(const std::string &content)
Definition server.cpp:73
void handleShutdown(const json &id)
Definition server.cpp:205
DocumentStore documents
Definition server.h:55
bool isInitialized
Definition server.h:59
bool isShuttingDown
Definition server.h:61
bool compilerInitialized
Definition server.h:60
void handleDefinition(const json &id, const json &params)
Definition server.cpp:319
void handleExit()
Definition server.cpp:210
void handleDidChange(const json &params)
Definition server.cpp:228
void sendNotification(const std::string &method, const json &params)
Definition server.cpp:97
void sendResponse(const json &id, const json &result)
Definition server.cpp:79
void handleReferences(const json &id, const json &params)
Definition server.cpp:335
void handleDidClose(const json &params)
Definition server.cpp:238
std::string readMessage()
Definition server.cpp:44
std::string workspaceRoot
Definition server.h:58
void sendError(const json &id, int code, const std::string &message)
Definition server.cpp:88
std::shared_ptr< yoi::compilerContext > compilerCtx
Definition server.h:57
void handleHover(const json &id, const json &params)
Definition server.cpp:305
ProjectIndex projectIndex
Definition server.h:56
void handleMessage(const json &msg)
Definition server.cpp:110
void handleCompletion(const json &id, const json &params)
Definition server.cpp:295
void handleInitialize(const json &id, const json &params)
Definition server.cpp:152
void handleInitialized(const json &params)
Definition server.cpp:193
void handleDocumentSymbol(const json &id, const json &params)
Definition server.cpp:405
void publishDiagnostics(const std::string &uri, int version)
Definition server.cpp:252
void addSearchPath(const std::string &path)
#define YOI_PLATFORM
Definition def.hpp:31
#define YOI_ARCH
Definition def.hpp:44
#define HOSHI_LANG_VERSION
Definition defines.h:4
#define HOSHI_LANG_GIT_COMMIT_HASH
Definition defines.h:5
SymbolKind
Definition protocol.h:301
static void extendRangeToInclude(Range &range, const Range &child)
Definition server.cpp:398
static DocumentSymbol documentSymbolFor(const Symbol &symbol)
Definition server.cpp:369
static int safeInt(yoi::indexT v)
Definition server.cpp:342
static lsp::SymbolKind mapSymbolKind(HoshiSymbolKind k)
Definition server.cpp:347
nlohmann::json json
Definition protocol.h:15
std::vector< Location > DefinitionResult
Definition protocol.h:271
std::string wstring2string(const std::wstring &v)
Definition def.cpp:230
std::wstring string2wstring(const std::string &v)
Definition def.cpp:224
std::wstring whereIsHoshiLang()
Definition def.cpp:246
uint64_t indexT
Definition def.hpp:54
TextDocumentIdentifier textDocument
Definition protocol.h:160
TextDocumentIdentifier textDocument
Definition protocol.h:261
std::vector< TextDocumentContentChangeEvent > contentChanges
Definition protocol.h:139
VersionedTextDocumentIdentifier textDocument
Definition protocol.h:138
TextDocumentIdentifier textDocument
Definition protocol.h:148
TextDocumentItem textDocument
Definition protocol.h:130
std::string detail
Definition protocol.h:332
std::string name
Definition protocol.h:331
std::vector< DocumentSymbol > children
Definition protocol.h:336
SymbolKind kind
Definition protocol.h:333
yoi::vec< yoi::Diagnostic > diagnostics
Definition document.h:31
Position position
Definition protocol.h:229
TextDocumentIdentifier textDocument
Definition protocol.h:228
std::string message
Definition protocol.h:365
DiagnosticSeverity severity
Definition protocol.h:364
std::optional< std::string > source
Definition protocol.h:366
std::vector< LspDiagnostic > diagnostics
Definition protocol.h:381
Position start
Definition protocol.h:40
Position end
Definition protocol.h:41
yoi::wstr detail
yoi::indexT endLine
yoi::indexT column
yoi::indexT line
yoi::wstr name
HoshiSymbolKind kind
yoi::indexT endColumn
std::string uri
Definition protocol.h:98
std::string languageId
Definition protocol.h:99
Builder & setBuildType(BuildType buildType)
Definition IR.cpp:890
std::shared_ptr< IRBuildConfig > yield()
Definition IR.cpp:905
Builder & setBuildMode(BuildMode buildMode)
Definition IR.cpp:921
Builder & setBuildPlatform(const yoi::wstr &buildPlatform)
Definition IR.cpp:895
Builder & setBuildArch(const yoi::wstr &buildArch)
Definition IR.cpp:900
Builder & setSearchPaths(const yoi::vec< yoi::wstr > &searchPaths)
Definition IR.cpp:1062