Skip to content

Commit fb12352

Browse files
committed
Server and introspections improvements (internal)
1 parent 7859a9b commit fb12352

File tree

3 files changed

+146
-45
lines changed

3 files changed

+146
-45
lines changed

src/loreline/Interpreter.hx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,17 @@ typedef InterpreterOptions = {
10671067

10681068
}
10691069

1070+
/**
1071+
* Returns the file path of the file containing the current node.
1072+
* Uses the Lens to walk up through import statements and resolve
1073+
* the path relative to the given root file path.
1074+
*/
1075+
public function currentNodeFilePath(rootPath:String):String {
1076+
final node = currentNode();
1077+
if (node == null || rootPath == null) return rootPath;
1078+
return lens.getNodeFilePath(cast node, rootPath);
1079+
}
1080+
10701081
/**
10711082
* Serializes a scope to save data.
10721083
*

src/loreline/Lens.hx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,55 @@ class Lens {
277277
return null;
278278
}
279279

280+
/**
281+
* Returns the file path of the file containing the given node.
282+
* Walks up the parent chain through NImportStatement nodes to
283+
* resolve the full path. Returns rootPath if the node is in the root file.
284+
*/
285+
public function getNodeFilePath(node:Node, rootPath:String):String {
286+
// Collect NImportStatements from innermost to outermost
287+
var importChain:Array<NImportStatement> = [];
288+
var current:Node = node;
289+
while (current != null) {
290+
final importStmt = getFirstParentOfType(current, NImportStatement);
291+
if (importStmt != null) {
292+
importChain.push(importStmt);
293+
current = cast importStmt;
294+
} else {
295+
break;
296+
}
297+
}
298+
299+
if (importChain.length == 0) return rootPath;
300+
301+
// Resolve paths from outermost to innermost
302+
var currentDir = Path.directory(rootPath);
303+
var ext = rootPath.endsWith('.lor.txt') ? '.lor.txt' : '.lor';
304+
var resolvedPath = rootPath;
305+
306+
// Reverse: outermost first
307+
var i = importChain.length - 1;
308+
while (i >= 0) {
309+
final importStmt = importChain[i];
310+
var importPath = switch importStmt.path.parts[0].partType {
311+
case Raw(text): text;
312+
case _: "";
313+
};
314+
if (!Path.isAbsolute(importPath)) {
315+
importPath = Path.join([currentDir, importPath]);
316+
}
317+
importPath = Path.normalize(importPath);
318+
if (!importPath.toLowerCase().endsWith(ext)) {
319+
importPath += ext;
320+
}
321+
resolvedPath = importPath;
322+
currentDir = Path.directory(resolvedPath);
323+
i--;
324+
}
325+
326+
return resolvedPath;
327+
}
328+
280329
public function getImportedPaths(rootPath:String):Array<String> {
281330

282331
final result:Array<String> = [];

src/loreline/lsp/Server.hx

Lines changed: 86 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -112,24 +112,35 @@ class Server {
112112
/**
113113
* Handle incoming JSON-RPC message
114114
*/
115-
public function handleMessage(msg:Message):Null<ResponseMessage> {
115+
/**
116+
* Synchronous wrapper for handleMessage. Only safe when handleFile is synchronous.
117+
* Used by the VS Code extension (Node.js) where file access is sync.
118+
*/
119+
public function handleMessageSync(msg:Message):Null<ResponseMessage> {
120+
var result:Null<ResponseMessage> = null;
121+
handleMessage(msg, (response) -> { result = response; });
122+
return result;
123+
}
124+
125+
public function handleMessage(msg:Message, ?onResponse:(response:Null<ResponseMessage>)->Void):Void {
116126
try {
117127
// Check message type and dispatch accordingly
118128
if (Reflect.hasField(msg, "method")) {
119129
if (Reflect.hasField(msg, "id")) {
120-
// Request message
121-
return handleRequest(cast msg);
130+
// Request message (may be async)
131+
handleRequest(cast msg, onResponse);
122132
} else {
123-
// Notification message
133+
// Notification message (no response)
124134
handleNotification(cast msg);
125-
return null;
135+
if (onResponse != null) onResponse(null);
126136
}
137+
return;
127138
}
128139

129-
return createErrorResponse(null, ErrorCodes.InvalidRequest, "Invalid message");
140+
if (onResponse != null) onResponse(createErrorResponse(null, ErrorCodes.InvalidRequest, "Invalid message"));
130141

131142
} catch (e:Dynamic) {
132-
return createErrorResponse(null, ErrorCodes.ParseError, Std.string(e));
143+
if (onResponse != null) onResponse(createErrorResponse(null, ErrorCodes.ParseError, Std.string(e)));
133144
}
134145
}
135146

@@ -164,60 +175,88 @@ class Server {
164175
/**
165176
* Handle a request message
166177
*/
167-
function handleRequest(request:RequestMessage):ResponseMessage {
178+
function handleRequest(request:RequestMessage, ?onResponse:(response:ResponseMessage)->Void):Void {
168179
try {
169180
if (!initialized && request.method != "initialize") {
170181
throw { code: ErrorCodes.ServerNotInitialized, message: "Server not initialized" };
171182
}
172183

173-
final result = resolveRequest(request);
174-
return createResponse(request.id, result);
184+
resolveRequest(request, (result) -> {
185+
if (onResponse != null) onResponse(createResponse(request.id, result));
186+
});
175187

176188
} catch (e:Any) {
177-
if (Reflect.hasField(e, 'code') && Reflect.hasField(e, 'message')) {
178-
final err:ResponseError = e;
179-
return createErrorResponse(request.id, err.code, err.message);
180-
}
181-
else {
182-
return createErrorResponse(request.id, ErrorCodes.InternalError, Std.string(e));
189+
if (onResponse != null) {
190+
if (Reflect.hasField(e, 'code') && Reflect.hasField(e, 'message')) {
191+
final err:ResponseError = e;
192+
onResponse(createErrorResponse(request.id, err.code, err.message));
193+
}
194+
else {
195+
onResponse(createErrorResponse(request.id, ErrorCodes.InternalError, Std.string(e)));
196+
}
183197
}
184198
}
185-
return null;
186199
}
187200

188-
function resolveRequest(request:RequestMessage):Any {
201+
function resolveRequest(request:RequestMessage, callback:(result:Any)->Void):Void {
189202

190-
return switch (request.method) {
191-
case "initialize":
192-
handleInitialize(cast request.params);
203+
// Determine which URI needs to be up-to-date before handling
204+
final uri = getRequestDocumentUri(request);
193205

194-
case "shutdown":
195-
handleShutdown();
206+
// Ensure the document is up-to-date (async-safe), then dispatch
207+
updateDocumentIfNeeded(uri, () -> {
208+
try {
209+
final result:Any = switch (request.method) {
210+
case "initialize":
211+
handleInitialize(cast request.params);
196212

197-
case "textDocument/completion":
198-
handleCompletion(cast request.params);
213+
case "shutdown":
214+
handleShutdown();
199215

200-
case "textDocument/definition":
201-
handleDefinition(cast request.params);
216+
case "textDocument/completion":
217+
handleCompletion(cast request.params);
202218

203-
case "textDocument/hover":
204-
handleHover(cast request.params);
219+
case "textDocument/definition":
220+
handleDefinition(cast request.params);
205221

206-
case "textDocument/documentSymbol":
207-
handleDocumentSymbol(cast request.params);
222+
case "textDocument/hover":
223+
handleHover(cast request.params);
208224

209-
case "textDocument/references":
210-
null; // TODO
225+
case "textDocument/documentSymbol":
226+
handleDocumentSymbol(cast request.params);
211227

212-
case "textDocument/formatting":
213-
handleDocumentFormatting(cast request.params);
228+
case "textDocument/references":
229+
null; // TODO
214230

215-
case _:
216-
throw { code: ErrorCodes.MethodNotFound, message: 'Method not found: ${request.method}'};
217-
}
231+
case "textDocument/formatting":
232+
handleDocumentFormatting(cast request.params);
233+
234+
case _:
235+
throw { code: ErrorCodes.MethodNotFound, message: 'Method not found: ${request.method}'};
236+
}
237+
callback(result);
238+
} catch (e:Any) {
239+
if (Reflect.hasField(e, 'code') && Reflect.hasField(e, 'message')) {
240+
throw e;
241+
} else {
242+
throw { code: ErrorCodes.InternalError, message: Std.string(e) };
243+
}
244+
}
245+
});
218246

219247
}
220248

249+
/** Extracts the textDocument.uri from a request's params, or null if not applicable. */
250+
function getRequestDocumentUri(request:RequestMessage):Null<String> {
251+
try {
252+
final params:Dynamic = request.params;
253+
if (params != null && params.textDocument != null && params.textDocument.uri != null) {
254+
return params.textDocument.uri;
255+
}
256+
} catch (_:Dynamic) {}
257+
return null;
258+
}
259+
221260
/**
222261
* Handle a notification message
223262
*/
@@ -545,14 +584,17 @@ class Server {
545584
return components.join("/");
546585
}
547586

548-
function updateDocumentIfNeeded(uri:String) {
587+
function updateDocumentIfNeeded(uri:String, ?callback:()->Void) {
549588

550589
if (dirtyDocuments.exists(uri)) {
551590
handleFile(pathFromUri(uri), content -> {
552591
if (content != null) {
553592
updateDocument(uri, content, true);
554593
}
594+
if (callback != null) callback();
555595
});
596+
} else {
597+
if (callback != null) callback();
556598
}
557599

558600
}
@@ -715,7 +757,6 @@ class Server {
715757
}):Array<CompletionItem> {
716758

717759
final uri = params.textDocument.uri;
718-
updateDocumentIfNeeded(uri);
719760

720761
final ast = documents.get(uri);
721762
if (ast == null) return [];
@@ -1122,7 +1163,6 @@ class Server {
11221163
final result:Array<LocationLink> = [];
11231164

11241165
final uri = params.textDocument.uri;
1125-
updateDocumentIfNeeded(uri);
11261166

11271167
final ast = documents.get(uri);
11281168
if (ast == null) return result;
@@ -1405,7 +1445,6 @@ class Server {
14051445
position:Position
14061446
}):Null<Hover> {
14071447
final uri = params.textDocument.uri;
1408-
updateDocumentIfNeeded(uri);
14091448

14101449
final ast = documents.get(uri);
14111450
if (ast == null) return null;
@@ -1858,7 +1897,11 @@ class Server {
18581897
for (ref in incomingBeats) {
18591898
final incoming = lens.getFirstParentOfType(ref.origin, NBeatDecl);
18601899
if (incoming != null) {
1861-
targets.push(makePositionLink(incoming.name, uri, incoming));
1900+
if (incoming.name == "_") {
1901+
targets.push("*top level*");
1902+
} else {
1903+
targets.push(makePositionLink(incoming.name, uri, incoming));
1904+
}
18621905
}
18631906
}
18641907
description.push("- From: " + targets.join(', '));
@@ -2107,8 +2150,6 @@ class Server {
21072150
textDocument:TextDocumentIdentifier
21082151
}):Array<DocumentSymbol> {
21092152

2110-
updateDocumentIfNeeded(params.textDocument.uri);
2111-
21122153
final ast = documents.get(params.textDocument.uri);
21132154
if (ast == null) return [];
21142155

0 commit comments

Comments
 (0)