diff --git a/LanguageClient b/LanguageClient index beb24b8..ddfe933 160000 --- a/LanguageClient +++ b/LanguageClient @@ -1 +1 @@ -Subproject commit beb24b80639f09988429d8a85de4d56ba59832f7 +Subproject commit ddfe933f6723ce3b243e30278be408460bd41b79 diff --git a/LanguageServerProtocol b/LanguageServerProtocol index 32bfb9d..b12dad0 160000 --- a/LanguageServerProtocol +++ b/LanguageServerProtocol @@ -1 +1 @@ -Subproject commit 32bfb9d873c2b5eb1827e88e400413129dab7674 +Subproject commit b12dad0a70423c7d60ade2264f85f6cbcb61b55c diff --git a/Sources/hylo-lsp-client/HyloClientTests.swift b/Sources/hylo-lsp-client/HyloClientTests.swift index 2a934b4..b0c4700 100644 --- a/Sources/hylo-lsp-client/HyloClientTests.swift +++ b/Sources/hylo-lsp-client/HyloClientTests.swift @@ -1,6 +1,7 @@ import Foundation import LanguageClient // import ProcessEnv +import LanguageClient import LanguageServerProtocol import LanguageServerProtocol_Client import JSONRPC @@ -12,11 +13,11 @@ import FrontEnd import IR import hylo_lsp -public func createServer(channel: DataChannel, docURL: URL) async throws -> RestartingServer { +public func createServer(channel: DataChannel, workspace: URL, documents: [URL]) async throws -> RestartingServer { let jsonServer = JSONRPCServer(dataChannel: channel) - let workspaceDirectory = docURL.deletingLastPathComponent() + // let workspaceDirectory = docURL.deletingLastPathComponent() - let provider: InitializingServer.InitializeParamsProvider = { + let initializationProvider: InitializingServer.InitializeParamsProvider = { // you may need to fill in more of the textDocument field for completions // to work, depending on your server let capabilities = ClientCapabilities(workspace: nil, @@ -27,7 +28,7 @@ public func createServer(channel: DataChannel, docURL: URL) async throws -> Rest // pay careful attention to rootPath/rootURI/workspaceFolders, as different servers will // have different expectations/requirements here - let ws = WorkspaceFolder(uri: workspaceDirectory.absoluteString, name: "workspace") + let ws = WorkspaceFolder(uri: workspace.absoluteString, name: "workspace") return InitializeParams(processId: Int(ProcessInfo.processInfo.processIdentifier), locale: nil, @@ -41,108 +42,126 @@ public func createServer(channel: DataChannel, docURL: URL) async throws -> Rest } - let docContent = try String(contentsOf: docURL) + let docs = try documents.map { url in + let docContent = try String(contentsOf: url) + return TextDocumentItem( + uri: url.absoluteString, + // uri: url.path, + languageId: .swift, + version: 1, + text: docContent) + }.reduce(into: [DocumentUri:TextDocumentItem]()) { (dict, item) in + dict[item.uri] = item + } + + let documentProvider = { @Sendable (uri: DocumentUri) in + + guard let doc = docs[uri] else { + throw RestartingServerError.noTextDocumentForURI(uri) + } + + return doc + } - let doc = TextDocumentItem(uri: docURL.absoluteString, - languageId: .swift, - version: 1, - text: docContent) // let server = InitializingServer(server: jsonServer, initializeParamsProvider: provider) let rsConf = RestartingServer.Configuration( serverProvider: { jsonServer }, - textDocumentItemProvider: { _ in doc }, - initializeParamsProvider: provider) + textDocumentItemProvider: documentProvider, + initializeParamsProvider: initializationProvider + ) let server = RestartingServer(configuration: rsConf) - let docParams = TextDocumentDidOpenParams(textDocument: doc) - - try await server.textDocumentDidOpen(params: docParams) + for doc in docs.values { + print("Open: \(doc.uri)") + let docParams = TextDocumentDidOpenParams(textDocument: doc) + try await server.textDocumentDidOpen(params: docParams) + } return server } -func RunHyloClientTests(channel: DataChannel, docURL: URL) async { - do { - let server = try await createServer(channel: channel, docURL: docURL) - if let c = await server.capabilities { - if let semanticTokensProvider = c.semanticTokensProvider { - print("Server legend: \(semanticTokensProvider.effectiveOptions.legend)") - } - } - - // make sure to pick a reasonable position within your test document - // NOTE: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#position - // Position in a text document expressed as zero-based line and zero-based character offset. A position is between two characters like an ‘insert’ cursor in an editor. Special values like for example -1 to denote the end of a line are not supported. - // let completionParams = CompletionParams(uri: docURL.absoluteString, - // position: pos, - // triggerKind: .invoked, - // triggerCharacter: nil) - // let completions = try await server.completion(params: completionParams) - // print("completions: ", completions!) - - do { - print("get symbols") - let params = DocumentSymbolParams(textDocument: TextDocumentIdentifier(uri: docURL.absoluteString)) - if let symbols = try await server.documentSymbol(params: params) { - if case let .optionA(symbols) = symbols { - print("symbols: ", symbols.map { $0.name }) - } - } - } - - do { - print("get semantic tokens") - let params = SemanticTokensParams(textDocument: TextDocumentIdentifier(uri: docURL.absoluteString)) - if let tokens = try await server.semanticTokensFull(params: params) { - var currentLine: UInt32 = 0 - var currentCol: UInt32 = 0 - let d = tokens.data - for n in 0.. 0 { - currentLine += d[i] - currentCol += 1 - } - print("line: \(line), col: \(col), len: \(len), type: \(type), modifiers: \(modifiers)") - } - } - } - - do { - // let pos = Position(line: 10, character: 14) - // let pos = Position(line: 10, character: 26) - // let pos = Position(line: 10, character: 35) - // let pos = Position(line: 3, character: 10) - // let pos = Position(line: 3, character: 29) - // let pos = Position(line: 20, character: 32) - // let pos = Position(line: 18, character: 37) - let pos = Position(line: 20, character: 8) - let params = TextDocumentPositionParams(uri: docURL.absoluteString, position: pos) - if let definition = try await server.definition(params: params) { - print("definition: ", definition) - } - } - - - print("exit") - // try client.close() - // try socket.close() - - } catch UniSocketError.error(let detail) { - print("fail: \(detail)") - } catch let e as AnyJSONRPCResponseError { - print("rpc error: [\(e.code)] \(e.message)") - } catch { - print("error: \(error), type: \(type(of: error))") - print(Thread.callStackSymbols) - } -} +// func RunHyloClientTests(channel: DataChannel, docURL: URL) async { +// do { +// let server = try await createServer(channel: channel, docURL: docURL) +// if let c = await server.capabilities { +// if let semanticTokensProvider = c.semanticTokensProvider { +// print("Server legend: \(semanticTokensProvider.effectiveOptions.legend)") +// } +// } + +// // make sure to pick a reasonable position within your test document +// // NOTE: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#position +// // Position in a text document expressed as zero-based line and zero-based character offset. A position is between two characters like an ‘insert’ cursor in an editor. Special values like for example -1 to denote the end of a line are not supported. +// // let completionParams = CompletionParams(uri: docURL.absoluteString, +// // position: pos, +// // triggerKind: .invoked, +// // triggerCharacter: nil) +// // let completions = try await server.completion(params: completionParams) +// // print("completions: ", completions!) + +// do { +// print("get symbols") +// let params = DocumentSymbolParams(textDocument: TextDocumentIdentifier(uri: docURL.absoluteString)) +// if let symbols = try await server.documentSymbol(params: params) { +// if case let .optionA(symbols) = symbols { +// print("symbols: ", symbols.map { $0.name }) +// } +// } +// } + +// do { +// print("get semantic tokens") +// let params = SemanticTokensParams(textDocument: TextDocumentIdentifier(uri: docURL.absoluteString)) +// if let tokens = try await server.semanticTokensFull(params: params) { +// var currentLine: UInt32 = 0 +// var currentCol: UInt32 = 0 +// let d = tokens.data +// for n in 0.. 0 { +// currentLine += d[i] +// currentCol += 1 +// } +// print("line: \(line), col: \(col), len: \(len), type: \(type), modifiers: \(modifiers)") +// } +// } +// } + +// do { +// // let pos = Position(line: 10, character: 14) +// // let pos = Position(line: 10, character: 26) +// // let pos = Position(line: 10, character: 35) +// // let pos = Position(line: 3, character: 10) +// // let pos = Position(line: 3, character: 29) +// // let pos = Position(line: 20, character: 32) +// // let pos = Position(line: 18, character: 37) +// let pos = Position(line: 20, character: 8) +// let params = TextDocumentPositionParams(uri: docURL.absoluteString, position: pos) +// if let definition = try await server.definition(params: params) { +// print("definition: ", definition) +// } +// } + + +// print("exit") +// // try client.close() +// // try socket.close() + +// } catch UniSocketError.error(let detail) { +// print("fail: \(detail)") +// } catch let e as AnyJSONRPCResponseError { +// print("rpc error: [\(e.code)] \(e.message)") +// } catch { +// print("error: \(error), type: \(type(of: error))") +// print(Thread.callStackSymbols) +// } +// } diff --git a/Sources/hylo-lsp-client/hylo-lsp-client.swift b/Sources/hylo-lsp-client/hylo-lsp-client.swift index dc540e1..c7c6d48 100644 --- a/Sources/hylo-lsp-client/hylo-lsp-client.swift +++ b/Sources/hylo-lsp-client/hylo-lsp-client.swift @@ -2,6 +2,7 @@ import Foundation import LanguageClient // import ProcessEnv import LanguageServerProtocol +import LanguageServerProtocol_Client import JSONRPC import UniSocket import JSONRPC_DataChannel_UniSocket @@ -34,12 +35,13 @@ struct Options: ParsableArguments { @Argument(help: "Hylo document filepath") var document: String - public func parseDocument() throws -> (path: String, line: UInt?) { + public func parseDocument() throws -> (path: String, line: UInt?, char: UInt?) { - let search1 = #/(.+)(?::(\d+))/# + let search1 = #/(.+)(?::(\d+)(?:\.(\d+))?)/# var path = document var line: UInt? + var char: UInt? if let result = try? search1.wholeMatch(in: document) { path = String(result.1) @@ -48,13 +50,21 @@ struct Options: ParsableArguments { } line = l + + if let c = result.3 { + guard let c = UInt(c) else { + throw ValidationError("Invalid document char number: \(result.2)") + } + + char = c + } } - return (String(path), line) + return (String(path), line, char) } func validate() throws { - let (path, _) = try parseDocument() + let (path, _, _) = try parseDocument() let fm = FileManager.default var isDirectory: ObjCBool = false @@ -80,7 +90,11 @@ struct HyloLspCommand: AsyncParsableCommand { static var configuration = CommandConfiguration( abstract: "HyloLSP command line client", - subcommands: [SemanticToken.self, Diagnostics.self], + subcommands: [ + SemanticToken.self, + Diagnostics.self, + Definition.self, + ], defaultSubcommand: nil) } @@ -91,23 +105,85 @@ public func cliLink(uri: String, range: LSPRange) -> String { "\(uri):\(range.start.line+1):\(range.start.character+1)" } +func initServer(workspace: String? = nil, documents: [String]) async throws -> RestartingServer< + JSONRPCServer +> { + let fm = FileManager.default + let workspace = URL.init(fileURLWithPath: workspace ?? fm.currentDirectoryPath) + + let docUrls = documents.map { URL.init(fileURLWithPath: $0) } + + let (clientChannel, serverChannel) = DataChannel.withDataActor() + + // Run the LSP Server in a background task + Task { + let server = HyloServer(serverChannel, logger: logger) + await server.run() + } + + // Return the RPC server (client side) + return try await createServer(channel: clientChannel, workspace: workspace, documents: docUrls) +} + extension HyloLspCommand { - struct Diagnostics : AsyncParsableCommand { + + struct Definition : AsyncParsableCommand { @OptionGroup var options: Options func run() async throws { - let (doc, _) = try options.parseDocument() - let docURL = URL.init(fileURLWithPath: doc) + logger.logLevel = options.log + let (doc, line, char) = try options.parseDocument() - let (clientChannel, serverChannel) = DataChannel.withDataActor() + guard let line = line else { + throw ValidationError("Invalid position") + } - Task { - let server = HyloServer(serverChannel, logger: logger) - await server.run() + guard let char = char else { + throw ValidationError("Invalid position") } - // await RunHyloClientTests(channel: clientChannel, docURL: docURL) - let server = try await createServer(channel: clientChannel, docURL: docURL) + let pos = Position(line: Int(line-1), character: Int(char-1)) + + let server = try await initServer(documents: [doc]) + let params = TextDocumentPositionParams(uri: doc, position: pos) + let definition = try await server.definition(params: params) + + switch definition { + case nil: + print("No definition") + case let .optionA(l): + printLocation(l) + case let .optionB(l): + for l in l { + printLocation(l) + } + case let .optionC(l): + for l in l { + printLocation(l) + } + } + } + + func locationLink(_ l: Location) -> LocationLink { + LocationLink(targetUri: l.uri, targetRange: l.range, targetSelectionRange: LSPRange(start: Position.zero, end: Position.zero)) + } + + func printLocation(_ l: Location) { + printLocation(locationLink(l)) + } + + func printLocation(_ l: LocationLink) { + print("\(cliLink(uri: l.targetUri, range: l.targetRange))") + } + } + + struct Diagnostics : AsyncParsableCommand { + @OptionGroup var options: Options + func run() async throws { + logger.logLevel = options.log + let (doc, line, _) = try options.parseDocument() + let docURL = URL.init(fileURLWithPath: doc) + let server = try await initServer(documents: [doc]) let params = DocumentDiagnosticParams(textDocument: TextDocumentIdentifier(uri: docURL.absoluteString)) let report = try await server.diagnostics(params: params) @@ -135,46 +211,39 @@ extension HyloLspCommand { // let docURL = URL.init(fileURLWithPath:"hylo/Examples/factorial.hylo") // let docURL = URL.init(fileURLWithPath:"hylo/Library/Hylo/Array.hylo") // let docURL = URL.init(fileURLWithPath: options.document) - let (doc, row) = try options.parseDocument() + let (doc, line, _) = try options.parseDocument() let docURL = URL.init(fileURLWithPath: doc) + let workspace = docURL.deletingLastPathComponent() if let pipe = options.pipe { print("starting client witn named pipe: \(pipe)") - let fileManager = FileManager.default - - // Check if file exists - if fileManager.fileExists(atPath: pipe) { - // Delete file - print("delete existing socket: \(pipe)") - try fileManager.removeItem(atPath: pipe) - } - - let socket = try UniSocket(type: .local, peer: pipe) - try socket.bind() - try socket.listen() - let client = try socket.accept() - print("lsp attached") - client.timeout = (connect: 5, read: nil, write: 5) - let clientChannel = DataChannel(socket: client) - await RunHyloClientTests(channel: clientChannel, docURL: docURL) + // let fileManager = FileManager.default + + // // Check if file exists + // if fileManager.fileExists(atPath: pipe) { + // // Delete file + // print("delete existing socket: \(pipe)") + // try fileManager.removeItem(atPath: pipe) + // } + + // let socket = try UniSocket(type: .local, peer: pipe) + // try socket.bind() + // try socket.listen() + // let client = try socket.accept() + // print("lsp attached") + // client.timeout = (connect: 5, read: nil, write: 5) + // let clientChannel = DataChannel(socket: client) + // await RunHyloClientTests(channel: clientChannel, docURL: docURL) } else { - let (clientChannel, serverChannel) = DataChannel.withDataActor() - - Task { - let server = HyloServer(serverChannel, logger: logger) - await server.run() - } - - // await RunHyloClientTests(channel: clientChannel, docURL: docURL) - let server = try await createServer(channel: clientChannel, docURL: docURL) + let server = try await initServer(documents: [doc]) let params = SemanticTokensParams(textDocument: TextDocumentIdentifier(uri: docURL.absoluteString)) if let tokensData = try await server.semanticTokensFull(params: params) { var tokens = tokensData.decode() - if let row = row { - tokens = tokens.filter { $0.line+1 == row } + if let line = line { + tokens = tokens.filter { $0.line+1 == line } } for t in tokens { diff --git a/Sources/hylo-lsp/AST+FindNode.swift b/Sources/hylo-lsp/AST+FindNode.swift index 1e55188..4df9d5d 100644 --- a/Sources/hylo-lsp/AST+FindNode.swift +++ b/Sources/hylo-lsp/AST+FindNode.swift @@ -24,12 +24,15 @@ extension AST { } } + // NOTE: We should cache root node per file if site.file != query.file { return false } + // logger.debug("Enter: \(site), id: \(n)") + if site.start > query.index { return false } diff --git a/Sources/hylo-lsp/HyloServer.swift b/Sources/hylo-lsp/HyloServer.swift index 05222a5..c76c584 100644 --- a/Sources/hylo-lsp/HyloServer.swift +++ b/Sources/hylo-lsp/HyloServer.swift @@ -35,6 +35,8 @@ public class LspState { let inputs = [URL.init(string: uri)!] return try _buildProgram(inputs) } + + logger.debug("Register opened document: \(uri)") documents[uri] = Document(uri: uri, task: task) } @@ -98,7 +100,7 @@ public struct HyloNotificationHandler : NotificationHandler { func buildDocument(_ uri: DocumentUri) async { state.uri = uri - logger.debug("textDocumentDidOpen: \(uri)") + // logger.debug("buildDocument: \(uri)") // logger.debug("lib uri: \(HyloModule.standardLibrary!.absoluteString)") // if uri.commonPrefix(with: HyloModule.standardLibrary!.absoluteString) == HyloModule.standardLibrary!.absoluteString { if uri.contains("hylo/Library/Hylo") { @@ -280,10 +282,29 @@ public struct HyloRequestHandler : RequestHandler { } + func locationLink(_ d: T) -> LocationLink where T: NodeIDProtocol { + let range = ast[d].site + let targetUri = range.file.url + var selectionRange = LSPRange(range) + + if let d = AnyDeclID(d) { + selectionRange = LSPRange(nameRange(of: d) ?? range) + } + + return LocationLink(targetUri: targetUri.absoluteString, targetRange: LSPRange(range), targetSelectionRange: selectionRange) + } + + func locationResponse(_ d: T) -> DefinitionResponse where T: NodeIDProtocol{ + let location = locationLink(d) + return .optionC([location]) + } + public func definition(_ params: TextDocumentPositionParams, _ program: TypedProgram) async -> Result { - let url = URL.init(string: params.textDocument.uri)! + + + let url = getDocumentUrl(params.textDocument) guard let f = try? SourceFile(contentsOf: url) else { - return .failure(JSONRPCResponseError(code: ErrorCodes.InternalError, message: "Invalid document uri")) + return .failure(JSONRPCResponseError(code: ErrorCodes.InternalError, message: "Invalid document uri: \(url)")) } let pos = params.position @@ -291,103 +312,105 @@ public struct HyloRequestHandler : RequestHandler { let p = SourcePosition(line: pos.line+1, column: pos.character+1, in: f) logger.debug("Look for symbol definition at position: \(p)") - if let id = ast.findNode(p) { - // logger.debug("found: \(id), in num nodes: \(ast.numNodes)") - // let s = program.nodeToScope[id] - let node = ast[id] - logger.debug("Found node: \(node), id: \(id)") + guard let id = ast.findNode(p) else { + logger.warning("Did not find node @ \(p)") + return .success(nil) + } + + // logger.debug("found: \(id), in num nodes: \(ast.numNodes)") + // let s = program.nodeToScope[id] + let node = ast[id] + logger.debug("Found node: \(node), id: \(id)") - let locationFromDecl = { (d: AnyDeclID) in - let range = ast[d].site - let selectionRange = LSPRange(nameRange(of: d) ?? range) - return LocationLink(targetUri: url.absoluteString, targetRange: LSPRange(range), targetSelectionRange: selectionRange) - } - let locationResponseFromDecl = { (d: AnyDeclID) -> DefinitionResponse in - let location = locationFromDecl(d) - return .optionC([location]) - } + if let d = AnyDeclID(id) { + return .success(locationResponse(d)) + } - if let d = AnyDeclID(id) { - return .success(locationResponseFromDecl(d)) - } + if let n = NameExpr.ID(id) { + // let d = program[n].referredDecl + let d = program.referredDecl[n] - if let n = NameExpr.ID(id) { - // let d = program[n].referredDecl - let d = program.referredDecl[n] - - if d == nil { - if let t = program.exprType[n] { - - switch t.base { - case let u as ProductType: - return .success(locationResponseFromDecl(AnyDeclID(u.decl))) - case let u as TypeAliasType: - return .success(locationResponseFromDecl(AnyDeclID(u.decl))) - case let u as AssociatedTypeType: - return .success(locationResponseFromDecl(AnyDeclID(u.decl))) - case let u as GenericTypeParameterType: - return .success(locationResponseFromDecl(AnyDeclID(u.decl))) - case let u as NamespaceType: - return .success(locationResponseFromDecl(AnyDeclID(u.decl))) - case let u as TraitType: - return .success(locationResponseFromDecl(AnyDeclID(u.decl))) - default: - fatalError("not implemented") - } - } + if d == nil { + if let t = program.exprType[n] { - if let x = AnyPatternID(id) { - logger.debug("pattern: \(x)") + switch t.base { + case let u as ProductType: + return .success(locationResponse(u.decl)) + case let u as TypeAliasType: + return .success(locationResponse(u.decl)) + case let u as AssociatedTypeType: + return .success(locationResponse(u.decl)) + case let u as GenericTypeParameterType: + return .success(locationResponse(u.decl)) + case let u as NamespaceType: + return .success(locationResponse(u.decl)) + case let u as TraitType: + return .success(locationResponse(u.decl)) + default: + fatalError("not implemented") } + } - if let s = program.nodeToScope[id] { - logger.debug("scope: \(s)") - if let decls = program.scopeToDecls[s] { - for d in decls { - if let t = program.declType[d] { - logger.debug("decl: \(d), type: \(t)") - } - } - } - + if let x = AnyPatternID(id) { + logger.debug("pattern: \(x)") + } - if let fn = ast[s] as? FunctionDecl { - logger.debug("TODO: Need to figure out how to get resolved return type of function signature: \(fn.site)") - return .success(nil) - // return .failure(JSONRPCResponseError(code: ErrorCodes.InternalError, message: "TODO: Need to figure out how to get resolved return type of function signature: \(fn.site)")) + if let s = program.nodeToScope[id] { + logger.debug("scope: \(s)") + if let decls = program.scopeToDecls[s] { + for d in decls { + if let t = program.declType[d] { + logger.debug("decl: \(d), type: \(t)") + } } } - // return .failure(JSONRPCResponseError(code: ErrorCodes.InternalError, message: "Internal error, must be able to resolve declaration")) - await logInternalError("Internal error, must be able to resolve declaration") - return .success(nil) - } - switch d { - case let .constructor(d, _): - let initializer = ast[d] - let range = ast[d].site - let selectionRange = LSPRange(initializer.introducer.site) - let response = LocationLink(targetUri: url.absoluteString, targetRange: LSPRange(range), targetSelectionRange: selectionRange) - return .success(.optionC([response])) - case let .direct(d, args): - logger.debug("d: \(d), generic args: \(args), name: \(program.name(of: d) ?? "__noname__")") - // let fnNode = ast[d] - // let range = LSPRange(hylocRange: fnNode.site) - return .success(locationResponseFromDecl(d)) - // if let fid = FunctionDecl.ID(d) { - // let f = sourceModule.functions[Function.ID(fid)]! - // logger.debug("Function: \(f)") - // } - default: - await logInternalError("Unknown declaration kind: \(d!)") + if let fn = ast[s] as? FunctionDecl { + logger.debug("TODO: Need to figure out how to get resolved return type of function signature: \(fn.site)") + return .success(nil) + // return .failure(JSONRPCResponseError(code: ErrorCodes.InternalError, message: "TODO: Need to figure out how to get resolved return type of function signature: \(fn.site)")) + } } + // return .failure(JSONRPCResponseError(code: ErrorCodes.InternalError, message: "Internal error, must be able to resolve declaration")) + await logInternalError("Internal error, must be able to resolve declaration") + return .success(nil) + } + + switch d { + case let .constructor(d, _): + let initializer = ast[d] + let range = ast[d].site + let selectionRange = LSPRange(initializer.introducer.site) + let response = LocationLink(targetUri: url.absoluteString, targetRange: LSPRange(range), targetSelectionRange: selectionRange) + return .success(.optionC([response])) + case let .builtinFunction(f): + logger.warning("builtinFunction: \(node)") + return .success(nil) + case .compilerKnownType: + logger.warning("compilerKnownType: \(node)") + return .success(nil) + case let .member(m, _, _): + return .success(locationResponse(m)) + case let .direct(d, args): + logger.debug("direct declaration: \(d), generic args: \(args), name: \(program.name(of: d) ?? "__noname__")") + // let fnNode = ast[d] + // let range = LSPRange(hylocRange: fnNode.site) + return .success(locationResponse(d)) + // if let fid = FunctionDecl.ID(d) { + // let f = sourceModule.functions[Function.ID(fid)]! + // logger.debug("Function: \(f)") + // } + default: + logger.error("Unknown declaration kind: \(d!)") + break } } + logger.warning("Unknown node: \(node)") return .success(nil) } @@ -466,9 +489,40 @@ public struct HyloRequestHandler : RequestHandler { } } + func getDocumentUrl(_ textDocument: TextDocumentIdentifier) -> URL { + + // Check if fully qualified url + if let url = URL(string: textDocument.uri) { + if url.scheme != nil { + return url + } + } + + let s = textDocument.uri as NSString + + // Check if absoult path + if s.isAbsolutePath { + return URL(fileURLWithPath: textDocument.uri) + } + else { + // TODO: Relative path is not generally supported, potenitially using rootUri, or single workspace entry + let fm = FileManager.default + let p = NSString.path(withComponents: [fm.currentDirectoryPath, textDocument.uri]) + return URL(fileURLWithPath: p) + } + } + + func getDocumentUri(_ textDocument: TextDocumentIdentifier) -> DocumentUri { + return getDocumentUrl(textDocument).absoluteString + } + + func getDocument(_ textDocument: TextDocumentIdentifier) -> Document? { + return state.documents[getDocumentUri(textDocument)] + } + public func diagnostics(_ params: DocumentDiagnosticParams) async -> Result { - guard let d = state.documents[params.textDocument.uri] else { - return .failure(JSONRPCResponseError(code: ErrorCodes.InternalError, message: "Expected an opened document")) + guard let d = getDocument(params.textDocument) else { + return .failure(JSONRPCResponseError(code: ErrorCodes.InternalError, message: "Expected an opened document: \(params.textDocument.uri)")) } do { @@ -511,8 +565,8 @@ public struct HyloRequestHandler : RequestHandler { } func withProgram(_ textDocument: TextDocumentIdentifier, fn: (TypedProgram) async -> Result) async -> Result { - guard let d = state.documents[textDocument.uri] else { - return .failure(JSONRPCResponseError(code: ErrorCodes.InternalError, message: "Expected an opened document")) + guard let d = getDocument(textDocument) else { + return .failure(JSONRPCResponseError(code: ErrorCodes.InternalError, message: "Expected an opened document: \(textDocument.uri)")) } do {