👩🏻🚀 This project is still a tad experimental. Contributors and pioneers welcome!
SwiftLSP offers a quite dynamic Swift representation of the LSP (Language Server Protocol) and helps with many related use cases. It is foundational for LSPService and LSPServiceKit.
Since the LSP standard defines a complex amorphous multitude of valid JSON objects, it doesn't exactly lend itself to being represented as a strict type system that would mirror the standard down to every permutation and property. So SwiftLSP is strictly typed at the higher level of LSP messages but falls back onto a more dynamic and flexible JSON representation for the details. The strict typing can easily be expanded on client demand.
Some of these examples build upon preceding ones, so it's best to read them from the beginning.
let myRequest = LSP.Request(method: "myMethod", params: nil)
let myRequestMessage = LSP.Message.request(myRequest)
let myNotification = LSP.Notification(method: "myMethod", params: nil)
let myNotificationMessage = LSP.Message.notification(myNotification)
SwiftLSP encodes LSP messages with the LSP-conform JSON-RPC encoding.
let myRequestMessageEncoded = try myRequestMessage.encode() // Data
let myRequestMessageDecoded = try LSP.Message(myRequestMessageEncoded)
To send LSP messages via data channels, the standard defines how to wrap each message in what we call an LSP.Packet
, which holds the Data
of its header
- and content
part.
let myRequestMessagePacket = try LSP.Packet(myRequestMessage)
let packetHeader = myRequestMessagePacket.header // Data
let packetContent = myRequestMessagePacket.content // Data
let packetTotalData = myRequestMessagePacket.data // Data
let myRequestMessageUnpacked = try myRequestMessagePacket.message() // LSP.Message
A client talking to an LSP server might need to extract LSP.Packet
s from the server's output Data
stream.
SwiftLSP can parse an LSP.Packet
from the beginning of a Data
instance:
let dataStartingWithPacket = packetTotalData + "Some other data".data(using: .utf8)!
let detectedPacket = try LSP.Packet(parsingPrefixOf: dataStartingWithPacket)
// now detectedPacket == myRequestMessagePacket
SwiftLSP also offers the LSP.PacketDetector
for parsing a stream of Data
incrementally:
var streamedPacket: LSP.Packet? = nil
let detector = LSP.PacketDetector { packet in
streamedPacket = packet
}
for byte in dataStartingWithPacket {
detector.read(byte)
}
// now streamedPacket == myRequestMessagePacket
Beyond what the examples above have touched, SwiftLSP also helps with:
- Creating messages for specific use cases (initialize server, request symbols, request references ...)
- Launching an LSP server executable
- Matching response messages to request messages
- Making requests to an LSP Server through
async
functions - Using an LSP Server via WebSocket
Some context and essential types:
Internal architecture (composition and essential dependencies) of the top-level source folder:
The above image was generated with Codeface.
From version/tag 0.1.0 on, SwiftLSP adheres to semantic versioning. So until it has reached 1.0.0, its API may still break frequently, but this will be expressed in version bumps.
SwiftLSP is already being used in production, but Codeface is still its primary client. SwiftLSP will move to version 1.0.0 as soon as its basic practicality and conceptual soundness have been validated by serving multiple real-world clients.