Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LSP server development #14

Merged
merged 17 commits into from
Nov 17, 2023

Conversation

koliyo
Copy link
Contributor

@koliyo koliyo commented Oct 27, 2023

Support for serverside LSP development:

Split library in three parts

  • LanguageServerProtocol
    • Common protocol code
  • LanguageServerProtocol-Client
    • Client side functionality to communicate with existing LSP servers
  • LanguageServerProtocol-Server
    • Functionality to build LSP server backends

Renaming of Server concept/protocol

Calling the clientside connection to the server makes sense in the client context, but makes things a bit confusing when working from the other side.

I have attempted to find a terminology that works well from both sides:

What was previously called Server, is now ServerConnection. Where a ServerConnection contains:

  • Event sequence from the server
  • Methods for sending notifications and requests to the server

And with matching ClientConnection protocol on the server side.

Serverside LSP developent

In addition to the ClientConnection protocol, the serverside library has additional support that can be reused for specific LSP implementations.

The same pattern should probably be implemented in the client side, but first lets make sure we can agree on a design that seems reasonable and support both sides of the LSP protocol.

  • ClientConnection
    • Event sequence from the client
    • Methods for sending notifications and requests to the client
  • JSONRPCClientConnection
    • JSONRPC implementation for the ClientConnection protocol
  • RequestHandler
    • Protocol for all LSP request methods
    • Has default implementation that return not-implemented errors to client
      • We can not check this statically since this depends on registered server capabilities, but this way the server does not need to implement the full protocol for parts it does not need to use
    • Pass request id to all request handlers
      • This is needed if the server want to support protocolCancelRequest
    • Protocol methods use return value instead of response callback
      • This allows static compilation checking that the request handlers actually return a result to the caller
    • Has a default request dispatcher to send request to proper method
  • NotificationHandler
    • Protocol for all LSP notification methods
    • Very similar to request handler, but notifications does not return results, and can not be cancelled
  • ErrorHandler
    • Where internal errors are sent
  • EventHandler
    • Convenience protocol which combines: RequestHandler, NotificationHandler, ErrorHandler
  • EventDispatcher
    • Consumes client event stream and dispatch events to registered handlers

@koliyo koliyo mentioned this pull request Oct 27, 2023
@koliyo
Copy link
Contributor Author

koliyo commented Oct 27, 2023

NOTE: This PR depends on #13 so that should be merged first. Or could potentially be merged as part of this if that makes it easier.

@koliyo
Copy link
Contributor Author

koliyo commented Oct 27, 2023

In the Hylo LSP I have separated NotificationHandler and RequestHandler, but since the protocols compose it is also possible to implementing them in the same actor, which is more similar to the current ServerConnection (previously Server).

I to however think it would be best if the client side had similar setup as the server side.

@mattmassicotte
Copy link
Contributor

Ok #13 is now merged! I'm going to start looking at this more closely.

import Logging

public protocol ProtocolHandler {
var connection: JSONRPCClientConnection { get }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This protocol gives me pause. A synchronous getter can be very problematic for an actor. I haven't yet looked closely enough to be sure, but it seems like it may be possible to make this { get async }.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been refactored and the protocol is not there anymore, so not sure if it is still something that needs fixing in the updated changeset. This is a bit beyond my swift knowledge atm!

import Foundation
import JSONRPC
import LanguageServerProtocol
import Logging
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is Logging still being used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging usage has been removed

@mattmassicotte
Copy link
Contributor

I explicitly do not squash to try to avoid conflict here, but it still happened...

Copy link
Contributor

@mattmassicotte mattmassicotte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is pretty stylistic, but it is uncommon (though not unheard of) to have module names with "-" in them. But, I think it's ok to just go with "LSPClient" and "LSPServer". I usually like to avoid acronyms, but in this case its already getting long and I don't think anyone will be confused.

@koliyo
Copy link
Contributor Author

koliyo commented Oct 28, 2023

Sounds good with rename. And I have removed Logger and ProtocolHandler.

Also, regarding client side, not sure it needs to follow the same pattern as the server protocols, client development will typically use LanguageClient and work on that abstraction level anyway.

But also, the splitting of this repo into server/client parts makes the separation between LSPClient and LanguageClient a bit more ambigious. Perhaps LanguageClient should just be moved into LSPClient, with preserved code? Not something that needs to be handled in this PR, just something to think about.

@mattmassicotte
Copy link
Contributor

At first, when I read your comment I thought "yes! why is there a LanguageClient package at all?" But, exploring that idea quickly led to a very good reason: the LanguageClient package has 6 dependencies. I agree that this is an awkward arrangement. I'm just not sure how to best balance these trade-offs. But I agree that whatever change, if any, we decide to do, can be done separately from this work.

I'm going to give this another closer look this week.

@koliyo
Copy link
Contributor Author

koliyo commented Nov 1, 2023

Ah, yes, additional dependencies makes sense as reason to keep it separate 👍

Copy link
Contributor

@mattmassicotte mattmassicotte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found some very minor stuff.

I was worried, at first, about the introduction of the handler protocols. But, I think that was largely overblown from having such a hard time doing similar (but not identical) work when adopting swift concurrency.

But after looking more closely, I think this is going to be just fine. Thanks so much for all your hard work here. I really appreciate it.

public init(ranges: [LSPRange], wordPattern: String? = nil) {
self.ranges = ranges
self.wordPattern = wordPattern
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed on purpose?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge issue, restored now

@@ -2,7 +2,7 @@ import Foundation

public typealias MonikerClientCapabilities = DynamicRegistrationClientCapabilities

public struct MonikerParams: Codable, Hashable, Sendable {
public struct MonkierParams: Codable, Hashable, Sendable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, fixed


public enum ClientNotification: Sendable, Hashable {
public enum Method: String, Hashable, Sendable {
case initialized
case exit
case windowWorkDoneProgressCancel = "window/workDoneProgress/cancel"
case workspaceDidChangeWatchedFiles = "workspace/didChangeWatchedFiles"
case workspaceDidChangeWatchedFiles = "workspace/workspaceDidChangeWatchedFiles"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copy-paste error I bet

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh, yes!

enum ServerError: Error {
case unrecognizedMethod(String)
case missingParams
case unhandledRegisterationMethod(String)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo "unhandledRegisterationMethod" -> unhandleRegistrationMethod"

@koliyo
Copy link
Contributor Author

koliyo commented Nov 17, 2023

I've addressed the comments, let me know if I missed anything, or if you have additional comments!

@mattmassicotte
Copy link
Contributor

I suspect we may still end up making some changes here and there. But, I think we're ready enough to make progress. Let's do it!

Thank you so much for all of your hard work and patience.

@mattmassicotte mattmassicotte merged commit cddb5bd into ChimeHQ:main Nov 17, 2023
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants