774 lines
22 KiB
Go
774 lines
22 KiB
Go
package websocket
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
const (
|
|
lspControlKind = "lsp"
|
|
lspControlField = "kandev"
|
|
lspControlAttachSync = "attachmentReady"
|
|
lspControlRelease = "release"
|
|
lspControlAck = "released"
|
|
lspLeaseStatusReady = "ready"
|
|
lspMethodInitialize = "initialize"
|
|
lspMethodDidOpen = "textDocument/didOpen"
|
|
lspMethodDidChange = "textDocument/didChange"
|
|
lspMethodDidClose = "textDocument/didClose"
|
|
)
|
|
|
|
type jsonRPCMessage struct {
|
|
JSONRPC string `json:"jsonrpc"`
|
|
ID json.RawMessage `json:"id"`
|
|
Method string `json:"method"`
|
|
Params json.RawMessage `json:"params"`
|
|
Result json.RawMessage `json:"result"`
|
|
Error json.RawMessage `json:"error"`
|
|
}
|
|
|
|
type jsonRPCResponse struct {
|
|
ID json.RawMessage `json:"id"`
|
|
Result json.RawMessage `json:"result"`
|
|
Error json.RawMessage `json:"error"`
|
|
}
|
|
|
|
type lspControlMessage struct {
|
|
Kind string `json:"kandev"`
|
|
Action string `json:"action"`
|
|
Reason string `json:"reason"`
|
|
RequestID string `json:"requestId"`
|
|
}
|
|
|
|
func (l *lspLease) handleUpstreamMessage(message []byte) error {
|
|
var status struct {
|
|
Status string `json:"status"`
|
|
WorkspacePath string `json:"workspacePath"`
|
|
WorkspaceURI string `json:"workspaceUri"`
|
|
RepositorySubpaths []string `json:"repoSubpaths"`
|
|
}
|
|
if json.Unmarshal(message, &status) == nil && status.Status != "" {
|
|
return l.handleTaskHostStatus(message, status.Status, status.WorkspacePath, status.WorkspaceURI, status.RepositorySubpaths)
|
|
}
|
|
var rpc jsonRPCMessage
|
|
if err := json.Unmarshal(message, &rpc); err != nil || rpc.JSONRPC != "2.0" {
|
|
return fmt.Errorf("invalid upstream JSON-RPC frame")
|
|
}
|
|
if rpc.Method != "" && len(rpc.ID) != 0 {
|
|
return l.handleServerRequest(rpc, message)
|
|
}
|
|
if rpc.Method == "" && len(rpc.ID) != 0 {
|
|
return l.handleServerResponse(rpc, message)
|
|
}
|
|
if rpc.Method != "" {
|
|
return l.handleServerNotification(rpc, message)
|
|
}
|
|
return fmt.Errorf("unrecognized JSON-RPC frame")
|
|
}
|
|
|
|
func (l *lspLease) handleTaskHostStatus(raw []byte, status, workspacePath, workspaceURI string, subpaths []string) error {
|
|
var value map[string]any
|
|
if err := json.Unmarshal(raw, &value); err != nil {
|
|
return err
|
|
}
|
|
l.mu.Lock()
|
|
l.readyStatus = cloneAnyMap(value)
|
|
if status == lspLeaseStatusReady {
|
|
l.ready = true
|
|
l.workspacePath = workspacePath
|
|
l.workspaceURI = workspaceURI
|
|
l.repoSubpaths = append([]string(nil), subpaths...)
|
|
}
|
|
if err := l.checkSnapshotLimitLocked(); err != nil {
|
|
l.mu.Unlock()
|
|
return err
|
|
}
|
|
generation, attached := l.generation, l.browser != nil
|
|
resumed := len(l.initializeResult) > 0
|
|
l.mu.Unlock()
|
|
if !attached {
|
|
return nil
|
|
}
|
|
if status == lspLeaseStatusReady {
|
|
return l.writeBrowserOrDetach(generation, l.readyHandshake(resumed))
|
|
}
|
|
return l.writeBrowserOrDetach(generation, value)
|
|
}
|
|
|
|
func (l *lspLease) readyHandshake(resumed bool) map[string]any {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
ready := cloneAnyMap(l.readyStatus)
|
|
if ready == nil {
|
|
ready = make(map[string]any)
|
|
}
|
|
ready["status"] = lspLeaseStatusReady
|
|
ready["leaseId"] = l.id
|
|
ready["resumed"] = resumed
|
|
ready["initialized"] = l.initializedReceived
|
|
ready["workspacePath"] = l.workspacePath
|
|
ready["workspaceUri"] = l.workspaceURI
|
|
ready["repoSubpaths"] = append([]string(nil), l.repoSubpaths...)
|
|
ready["registrations"] = l.rawMapValues(l.registrations)
|
|
ready["progressTokens"] = l.rawMapValues(l.progressTokens)
|
|
ready["progress"] = l.rawMapValues(l.progress)
|
|
if len(l.serverCapabilities) > 0 {
|
|
var capabilities any
|
|
if json.Unmarshal(l.serverCapabilities, &capabilities) == nil {
|
|
ready["capabilities"] = capabilities
|
|
}
|
|
}
|
|
return ready
|
|
}
|
|
|
|
func (l *lspLease) rawMapValues(values map[string][]byte) []any {
|
|
result := make([]any, 0, len(values))
|
|
for _, raw := range values {
|
|
var value any
|
|
if json.Unmarshal(raw, &value) == nil {
|
|
result = append(result, value)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (l *lspLease) handleServerRequest(rpc jsonRPCMessage, original []byte) error {
|
|
brokered := false
|
|
switch rpc.Method {
|
|
case "workspace/configuration":
|
|
return l.answerWorkspaceConfiguration(rpc.ID, rpc.Params)
|
|
case "client/registerCapability":
|
|
brokered = true
|
|
if err := l.updateRegistrations(rpc.Method, rpc.Params); err != nil {
|
|
return err
|
|
}
|
|
case "client/unregisterCapability":
|
|
brokered = true
|
|
if err := l.updateRegistrations(rpc.Method, rpc.Params); err != nil {
|
|
return err
|
|
}
|
|
case "window/workDoneProgress/create":
|
|
brokered = true
|
|
if err := l.addProgressToken(rpc.Params); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
l.mu.Lock()
|
|
if err := l.checkSnapshotLimitLocked(); err != nil {
|
|
l.mu.Unlock()
|
|
return err
|
|
}
|
|
generation, browser := l.generation, l.browser
|
|
if browser == nil {
|
|
l.mu.Unlock()
|
|
if brokered {
|
|
return l.writeUpstream(jsonRPCResultResponse(rpc.ID, nil))
|
|
}
|
|
return l.writeUpstream(jsonRPCErrorResponseRaw(rpc.ID, -32601, "method unavailable while the editor is detached"))
|
|
}
|
|
l.requestCounter++
|
|
forwardedID := jsonRPCStringID(fmt.Sprintf("kandev:server:%d", l.requestCounter))
|
|
key := jsonRPCIDKey(forwardedID)
|
|
l.serverRequests[key] = lspLeaseServerRequest{serverID: append([]byte(nil), rpc.ID...), generation: generation, method: rpc.Method}
|
|
l.mu.Unlock()
|
|
forwarded, err := replaceJSONRPCID(original, forwardedID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = l.writeBrowser(generation, json.RawMessage(forwarded)); err != nil {
|
|
l.detach(generation)
|
|
return nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *lspLease) answerWorkspaceConfiguration(id, params json.RawMessage) error {
|
|
var request struct {
|
|
Items []struct {
|
|
Section string `json:"section"`
|
|
} `json:"items"`
|
|
}
|
|
if len(params) > 0 {
|
|
if err := json.Unmarshal(params, &request); err != nil {
|
|
return l.writeUpstream(jsonRPCErrorResponseRaw(id, -32602, "invalid workspace/configuration parameters"))
|
|
}
|
|
}
|
|
l.mu.Lock()
|
|
configuration := cloneStringAnyMap(l.configuration)
|
|
l.mu.Unlock()
|
|
result := make([]any, len(request.Items))
|
|
for index := range result {
|
|
result[index] = configurationSection(configuration, request.Items[index].Section, l.language)
|
|
}
|
|
return l.writeUpstream(jsonRPCResultResponse(id, result))
|
|
}
|
|
|
|
func (l *lspLease) handleServerResponse(rpc jsonRPCMessage, original []byte) error {
|
|
key := jsonRPCIDKey(rpc.ID)
|
|
if response := l.takeBrokerResponse(key); response != nil {
|
|
response <- jsonRPCResponse{ID: append([]byte(nil), rpc.ID...), Result: append([]byte(nil), rpc.Result...), Error: append([]byte(nil), rpc.Error...)}
|
|
return nil
|
|
}
|
|
if handled, err := l.handleInitializeResponse(key, rpc, original); handled {
|
|
return err
|
|
}
|
|
return l.handleClientResponse(key, original)
|
|
}
|
|
|
|
func (l *lspLease) takeBrokerResponse(key string) chan jsonRPCResponse {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
if responseCh, ok := l.brokerRequests[key]; ok {
|
|
delete(l.brokerRequests, key)
|
|
return responseCh
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *lspLease) handleInitializeResponse(
|
|
key string,
|
|
rpc jsonRPCMessage,
|
|
original []byte,
|
|
) (bool, error) {
|
|
l.mu.Lock()
|
|
if len(l.initializeServerID) == 0 || jsonRPCIDKey(l.initializeServerID) != key {
|
|
l.mu.Unlock()
|
|
return false, nil
|
|
}
|
|
initializeFailed := len(rpc.Error) > 0 && string(rpc.Error) != "null"
|
|
if initializeFailed {
|
|
l.initializeResponse = nil
|
|
l.initializeResult = nil
|
|
l.serverCapabilities = nil
|
|
} else {
|
|
l.initializeResponse = append(l.initializeResponse[:0], original...)
|
|
}
|
|
l.initializeServerID = nil
|
|
waiter := l.initializeWaiter
|
|
l.initializeWaiter = nil
|
|
if !initializeFailed && len(rpc.Result) > 0 {
|
|
l.initializeResult = append(l.initializeResult[:0], rpc.Result...)
|
|
var result struct {
|
|
Capabilities json.RawMessage `json:"capabilities"`
|
|
}
|
|
if json.Unmarshal(rpc.Result, &result) == nil {
|
|
l.serverCapabilities = append(l.serverCapabilities[:0], result.Capabilities...)
|
|
}
|
|
}
|
|
if err := l.checkSnapshotLimitLocked(); err != nil {
|
|
l.mu.Unlock()
|
|
return true, err
|
|
}
|
|
l.mu.Unlock()
|
|
if waiter == nil || !l.isActiveGeneration(waiter.generation) {
|
|
return true, nil
|
|
}
|
|
forwarded, err := replaceJSONRPCID(original, waiter.clientID)
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
return true, l.writeBrowserOrDetach(waiter.generation, json.RawMessage(forwarded))
|
|
}
|
|
|
|
func (l *lspLease) handleClientResponse(key string, original []byte) error {
|
|
l.mu.Lock()
|
|
var pendingID string
|
|
var pending lspLeaseClientRequest
|
|
for clientID, request := range l.clientRequests {
|
|
if jsonRPCIDKey(request.serverID) == key {
|
|
pendingID, pending = clientID, request
|
|
l.removeClientRequestLocked(clientID)
|
|
break
|
|
}
|
|
}
|
|
if pendingID == "" {
|
|
l.mu.Unlock()
|
|
return nil
|
|
}
|
|
attached := l.browser != nil && l.generation == pending.generation
|
|
generation := l.generation
|
|
l.mu.Unlock()
|
|
if !attached {
|
|
return nil
|
|
}
|
|
forwarded, err := replaceJSONRPCID(original, pending.clientID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return l.writeBrowserOrDetach(generation, json.RawMessage(forwarded))
|
|
}
|
|
|
|
func (l *lspLease) handleServerNotification(rpc jsonRPCMessage, original []byte) error {
|
|
switch rpc.Method {
|
|
case "$/progress":
|
|
if err := l.updateProgress(rpc.Params); err != nil {
|
|
return err
|
|
}
|
|
case "textDocument/publishDiagnostics":
|
|
allowed, err := l.acceptDiagnostics(rpc.Params)
|
|
if err != nil || !allowed {
|
|
return err
|
|
}
|
|
}
|
|
l.mu.Lock()
|
|
generation, attached := l.generation, l.browser != nil
|
|
l.mu.Unlock()
|
|
if !attached {
|
|
return nil
|
|
}
|
|
if err := l.writeBrowser(generation, json.RawMessage(original)); err != nil {
|
|
l.detach(generation)
|
|
return nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *lspLease) handleBrowserMessage(generation uint64, message []byte) error {
|
|
var control lspControlMessage
|
|
if json.Unmarshal(message, &control) == nil && control.Kind == lspControlKind {
|
|
return l.handleControl(generation, control)
|
|
}
|
|
if !l.isActiveGeneration(generation) {
|
|
return nil
|
|
}
|
|
var rpc jsonRPCMessage
|
|
if err := json.Unmarshal(message, &rpc); err != nil || rpc.JSONRPC != "2.0" {
|
|
return errors.New("invalid browser JSON-RPC frame")
|
|
}
|
|
if rpc.Method == "" && len(rpc.ID) > 0 {
|
|
return l.handleBrowserResponse(generation, rpc, message)
|
|
}
|
|
if rpc.Method == "" {
|
|
return nil
|
|
}
|
|
if handled, err := l.handleBrowserMethod(generation, rpc, message); handled {
|
|
return err
|
|
}
|
|
forwarded, active, err := l.rewriteBrowserDocument(generation, rpc, message)
|
|
if err != nil || !active {
|
|
return err
|
|
}
|
|
if len(rpc.ID) == 0 {
|
|
return l.writeUpstreamForGeneration(generation, forwarded)
|
|
}
|
|
return l.forwardBrowserRequest(generation, rpc, forwarded)
|
|
}
|
|
|
|
func (l *lspLease) handleBrowserMethod(
|
|
generation uint64,
|
|
rpc jsonRPCMessage,
|
|
original []byte,
|
|
) (bool, error) {
|
|
switch rpc.Method {
|
|
case lspMethodInitialize:
|
|
return true, l.handleInitializeRequest(generation, rpc, original)
|
|
case "initialized":
|
|
return true, l.handleInitializedNotification(generation, original)
|
|
case "workspace/didChangeConfiguration":
|
|
var params struct {
|
|
Settings map[string]any `json:"settings"`
|
|
}
|
|
if json.Unmarshal(rpc.Params, ¶ms) == nil && params.Settings != nil {
|
|
return true, l.updateConfiguration(params.Settings, true)
|
|
}
|
|
case "$/cancelRequest":
|
|
return true, l.forwardCancellation(generation, rpc, original)
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func (l *lspLease) rewriteBrowserDocument(
|
|
generation uint64,
|
|
rpc jsonRPCMessage,
|
|
original []byte,
|
|
) ([]byte, bool, error) {
|
|
switch rpc.Method {
|
|
case lspMethodDidOpen, lspMethodDidChange, lspMethodDidClose:
|
|
return l.rewriteDocumentVersionForGeneration(generation, rpc.Method, original)
|
|
default:
|
|
return append([]byte(nil), original...), true, nil
|
|
}
|
|
}
|
|
|
|
func (l *lspLease) forwardBrowserRequest(
|
|
generation uint64,
|
|
rpc jsonRPCMessage,
|
|
message []byte,
|
|
) error {
|
|
l.mu.Lock()
|
|
if l.closed || l.generation != generation || l.browser == nil {
|
|
l.mu.Unlock()
|
|
return nil
|
|
}
|
|
l.requestCounter++
|
|
serverID := jsonRPCStringID(fmt.Sprintf("kandev:client:%d", l.requestCounter))
|
|
clientKey := jsonRPCIDKey(rpc.ID)
|
|
if err := l.addClientRequestLocked(generation, rpc.ID, serverID); err != nil {
|
|
l.mu.Unlock()
|
|
return err
|
|
}
|
|
if err := l.checkSnapshotLimitLocked(); err != nil {
|
|
l.removeClientRequestLocked(clientKey)
|
|
l.mu.Unlock()
|
|
return err
|
|
}
|
|
l.mu.Unlock()
|
|
forwarded, err := replaceJSONRPCID(message, serverID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = l.writeUpstreamForGeneration(generation, forwarded); err != nil {
|
|
l.mu.Lock()
|
|
l.removeClientRequestLocked(clientKey)
|
|
l.mu.Unlock()
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (l *lspLease) handleBrowserResponse(generation uint64, rpc jsonRPCMessage, original []byte) error {
|
|
key := jsonRPCIDKey(rpc.ID)
|
|
l.mu.Lock()
|
|
pending, ok := l.serverRequests[key]
|
|
if !ok || pending.generation != generation || l.generation != generation || l.browser == nil {
|
|
l.mu.Unlock()
|
|
return nil
|
|
}
|
|
delete(l.serverRequests, key)
|
|
l.mu.Unlock()
|
|
forwarded, err := replaceJSONRPCID(original, pending.serverID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return l.writeUpstreamForGeneration(generation, forwarded)
|
|
}
|
|
|
|
func (l *lspLease) handleInitializeRequest(generation uint64, rpc jsonRPCMessage, original []byte) error {
|
|
if len(rpc.ID) == 0 {
|
|
return errors.New("initialize request has no ID")
|
|
}
|
|
l.upstreamWriteMu.Lock()
|
|
l.mu.Lock()
|
|
if l.closed || l.browser == nil || l.generation != generation {
|
|
l.mu.Unlock()
|
|
l.upstreamWriteMu.Unlock()
|
|
return nil
|
|
}
|
|
if len(l.initializeResponse) > 0 {
|
|
response := append([]byte(nil), l.initializeResponse...)
|
|
l.mu.Unlock()
|
|
l.upstreamWriteMu.Unlock()
|
|
forwarded, err := replaceJSONRPCID(response, rpc.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return l.writeBrowserOrDetach(generation, json.RawMessage(forwarded))
|
|
}
|
|
if len(l.initializeServerID) > 0 {
|
|
l.initializeWaiter = &lspLeaseInitializeWaiter{generation: generation, clientID: append([]byte(nil), rpc.ID...)}
|
|
l.mu.Unlock()
|
|
l.upstreamWriteMu.Unlock()
|
|
return nil
|
|
}
|
|
l.requestCounter++
|
|
serverID := jsonRPCStringID(fmt.Sprintf("kandev:initialize:%d", l.requestCounter))
|
|
l.initializeServerID = append([]byte(nil), serverID...)
|
|
l.initializeWaiter = &lspLeaseInitializeWaiter{generation: generation, clientID: append([]byte(nil), rpc.ID...)}
|
|
var params struct {
|
|
WorkDoneToken json.RawMessage `json:"workDoneToken"`
|
|
}
|
|
if len(rpc.Params) > 0 && json.Unmarshal(rpc.Params, ¶ms) == nil && len(params.WorkDoneToken) > 0 {
|
|
storeSnapshotByteSlice(l.progressTokens, jsonRPCIDKey(params.WorkDoneToken), params.WorkDoneToken, &l.progressTokenSnapshotBytes)
|
|
}
|
|
if err := l.checkSnapshotLimitLocked(); err != nil {
|
|
l.initializeServerID = nil
|
|
l.initializeWaiter = nil
|
|
l.mu.Unlock()
|
|
l.upstreamWriteMu.Unlock()
|
|
return err
|
|
}
|
|
l.mu.Unlock()
|
|
forwarded, err := replaceJSONRPCID(original, serverID)
|
|
if err == nil {
|
|
err = l.writeUpstreamLocked(forwarded)
|
|
}
|
|
l.upstreamWriteMu.Unlock()
|
|
return err
|
|
}
|
|
|
|
func (l *lspLease) handleInitializedNotification(generation uint64, message []byte) error {
|
|
l.upstreamWriteMu.Lock()
|
|
defer l.upstreamWriteMu.Unlock()
|
|
l.mu.Lock()
|
|
if l.closed || l.browser == nil || l.generation != generation || l.initializedReceived {
|
|
l.mu.Unlock()
|
|
return nil
|
|
}
|
|
l.mu.Unlock()
|
|
if err := l.writeUpstreamLocked(message); err != nil {
|
|
return err
|
|
}
|
|
l.mu.Lock()
|
|
if !l.closed && l.browser != nil && l.generation == generation {
|
|
l.initializedReceived = true
|
|
}
|
|
l.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
func (l *lspLease) forwardCancellation(generation uint64, rpc jsonRPCMessage, original []byte) error {
|
|
var params struct {
|
|
ID json.RawMessage `json:"id"`
|
|
}
|
|
if err := json.Unmarshal(rpc.Params, ¶ms); err != nil || len(params.ID) == 0 {
|
|
return nil
|
|
}
|
|
key := jsonRPCIDKey(params.ID)
|
|
l.mu.Lock()
|
|
pending, ok := l.clientRequests[key]
|
|
if ok && pending.generation == generation {
|
|
pending, ok = l.removeClientRequestLocked(key)
|
|
}
|
|
l.mu.Unlock()
|
|
if !ok || pending.generation != generation {
|
|
return nil
|
|
}
|
|
var raw map[string]json.RawMessage
|
|
if err := json.Unmarshal(original, &raw); err != nil {
|
|
return err
|
|
}
|
|
var cancelParams map[string]json.RawMessage
|
|
if err := json.Unmarshal(raw["params"], &cancelParams); err != nil {
|
|
return err
|
|
}
|
|
cancelParams["id"] = append([]byte(nil), pending.serverID...)
|
|
encoded, err := json.Marshal(cancelParams)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
raw["params"] = encoded
|
|
forwarded, err := json.Marshal(raw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return l.writeUpstreamForGeneration(generation, forwarded)
|
|
}
|
|
|
|
func (l *lspLease) rewriteDocumentVersion(method string, original []byte) ([]byte, error) {
|
|
message, _, err := l.rewriteDocumentVersionInternal(0, method, original, false)
|
|
return message, err
|
|
}
|
|
|
|
func (l *lspLease) rewriteDocumentVersionForGeneration(generation uint64, method string, original []byte) ([]byte, bool, error) {
|
|
return l.rewriteDocumentVersionInternal(generation, method, original, true)
|
|
}
|
|
|
|
func (l *lspLease) rewriteDocumentVersionInternal(
|
|
generation uint64,
|
|
method string,
|
|
original []byte,
|
|
checkGeneration bool,
|
|
) ([]byte, bool, error) {
|
|
raw, params, document, uri, err := decodeDocumentSyncMessage(original)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
l.mu.Lock()
|
|
if checkGeneration && (l.closed || l.browser == nil || l.generation != generation) {
|
|
l.mu.Unlock()
|
|
return nil, false, nil
|
|
}
|
|
switch method {
|
|
case lspMethodDidOpen, lspMethodDidChange:
|
|
previous := l.documentVersions[uri]
|
|
version := previous + 1
|
|
if previous > 0 {
|
|
l.documentVersionsSnapshotBytes -= snapshotDocumentVersionEntrySize(uri, previous)
|
|
}
|
|
l.documentVersions[uri] = version
|
|
l.documentVersionsSnapshotBytes += snapshotDocumentVersionEntrySize(uri, version)
|
|
l.openDocuments[uri] = version
|
|
l.synchronizedDocs[uri] = true
|
|
encodedVersion, _ := json.Marshal(version)
|
|
document["version"] = encodedVersion
|
|
case lspMethodDidClose:
|
|
delete(l.openDocuments, uri)
|
|
delete(l.synchronizedDocs, uri)
|
|
}
|
|
if err := l.checkSnapshotLimitLocked(); err != nil {
|
|
l.mu.Unlock()
|
|
return nil, false, err
|
|
}
|
|
l.mu.Unlock()
|
|
encodedDocument, err := json.Marshal(document)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
params["textDocument"] = encodedDocument
|
|
encodedParams, err := json.Marshal(params)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
raw["params"] = encodedParams
|
|
message, err := json.Marshal(raw)
|
|
return message, true, err
|
|
}
|
|
|
|
func decodeDocumentSyncMessage(
|
|
original []byte,
|
|
) (map[string]json.RawMessage, map[string]json.RawMessage, map[string]json.RawMessage, string, error) {
|
|
var raw map[string]json.RawMessage
|
|
if err := json.Unmarshal(original, &raw); err != nil {
|
|
return nil, nil, nil, "", err
|
|
}
|
|
var params map[string]json.RawMessage
|
|
if err := json.Unmarshal(raw["params"], ¶ms); err != nil {
|
|
return nil, nil, nil, "", err
|
|
}
|
|
var document map[string]json.RawMessage
|
|
if err := json.Unmarshal(params["textDocument"], &document); err != nil {
|
|
return nil, nil, nil, "", err
|
|
}
|
|
var uri string
|
|
if err := json.Unmarshal(document["uri"], &uri); err != nil || uri == "" {
|
|
return nil, nil, nil, "", errors.New("document synchronization has no URI")
|
|
}
|
|
return raw, params, document, uri, nil
|
|
}
|
|
|
|
func (l *lspLease) acceptDiagnostics(params json.RawMessage) (bool, error) {
|
|
var diagnostic struct {
|
|
URI string `json:"uri"`
|
|
Version *int64 `json:"version"`
|
|
}
|
|
if err := json.Unmarshal(params, &diagnostic); err != nil {
|
|
return false, err
|
|
}
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
current, open := l.openDocuments[diagnostic.URI]
|
|
if !open || !l.synchronizedDocs[diagnostic.URI] {
|
|
return false, nil
|
|
}
|
|
if diagnostic.Version == nil {
|
|
return !l.resumedGeneration, nil
|
|
}
|
|
return *diagnostic.Version == current, nil
|
|
}
|
|
|
|
func (l *lspLease) updateRegistrations(method string, params json.RawMessage) error {
|
|
var payload map[string]json.RawMessage
|
|
if err := json.Unmarshal(params, &payload); err != nil {
|
|
return err
|
|
}
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
if method == "client/registerCapability" {
|
|
var registrations []struct {
|
|
ID string `json:"id"`
|
|
}
|
|
if err := json.Unmarshal(payload["registrations"], ®istrations); err != nil {
|
|
return err
|
|
}
|
|
var rawItems []json.RawMessage
|
|
if err := json.Unmarshal(payload["registrations"], &rawItems); err != nil {
|
|
return err
|
|
}
|
|
for index, registration := range registrations {
|
|
if registration.ID != "" && index < len(rawItems) {
|
|
storeSnapshotByteSlice(l.registrations, registration.ID, rawItems[index], &l.registrationSnapshotBytes)
|
|
}
|
|
}
|
|
return l.checkSnapshotLimitLocked()
|
|
}
|
|
items := payload["unregistrations"]
|
|
if len(items) == 0 {
|
|
items = payload["unregisterations"]
|
|
}
|
|
var unregistrations []struct {
|
|
ID string `json:"id"`
|
|
}
|
|
if err := json.Unmarshal(items, &unregistrations); err != nil {
|
|
return err
|
|
}
|
|
for _, registration := range unregistrations {
|
|
deleteSnapshotByteSlice(l.registrations, registration.ID, &l.registrationSnapshotBytes)
|
|
}
|
|
return l.checkSnapshotLimitLocked()
|
|
}
|
|
|
|
func (l *lspLease) addProgressToken(params json.RawMessage) error {
|
|
var payload struct {
|
|
Token json.RawMessage `json:"token"`
|
|
}
|
|
if err := json.Unmarshal(params, &payload); err != nil || len(payload.Token) == 0 {
|
|
return errors.New("progress creation request has no token")
|
|
}
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
storeSnapshotByteSlice(l.progressTokens, jsonRPCIDKey(payload.Token), payload.Token, &l.progressTokenSnapshotBytes)
|
|
return l.checkSnapshotLimitLocked()
|
|
}
|
|
|
|
func (l *lspLease) updateProgress(params json.RawMessage) error {
|
|
var payload struct {
|
|
Token json.RawMessage `json:"token"`
|
|
Value json.RawMessage `json:"value"`
|
|
}
|
|
if err := json.Unmarshal(params, &payload); err != nil || len(payload.Token) == 0 || len(payload.Value) == 0 {
|
|
return nil
|
|
}
|
|
key := jsonRPCIDKey(payload.Token)
|
|
var value struct {
|
|
Kind string `json:"kind"`
|
|
}
|
|
_ = json.Unmarshal(payload.Value, &value)
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
if _, registered := l.progressTokens[key]; !registered {
|
|
return nil
|
|
}
|
|
switch value.Kind {
|
|
case "end":
|
|
deleteSnapshotByteSlice(l.progress, key, &l.progressSnapshotBytes)
|
|
case "report":
|
|
storeSnapshotByteSlice(l.progress, key, mergeProgressReport(l.progress[key], params, payload.Value), &l.progressSnapshotBytes)
|
|
default:
|
|
storeSnapshotByteSlice(l.progress, key, params, &l.progressSnapshotBytes)
|
|
}
|
|
return l.checkSnapshotLimitLocked()
|
|
}
|
|
|
|
func (l *lspLease) handleControl(generation uint64, control lspControlMessage) error {
|
|
l.mu.Lock()
|
|
active := !l.closed && l.browser != nil && l.generation == generation
|
|
if active && control.Action == lspControlAttachSync {
|
|
for uri := range l.openDocuments {
|
|
l.synchronizedDocs[uri] = true
|
|
}
|
|
}
|
|
l.mu.Unlock()
|
|
if !active {
|
|
return nil
|
|
}
|
|
if control.Action == lspControlAttachSync {
|
|
return l.writeBrowser(generation, map[string]any{
|
|
lspControlField: lspControlKind,
|
|
"action": lspControlAttachSync,
|
|
"requestId": control.RequestID,
|
|
})
|
|
}
|
|
if control.Action != lspControlRelease {
|
|
return errors.New("unknown LSP broker control action")
|
|
}
|
|
reason := control.Reason
|
|
if reason != lspLeaseReleaseEditorIdle {
|
|
reason = lspLeaseReleaseStop
|
|
}
|
|
if err := l.gracefulRelease(generation, reason, control.RequestID); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|