Skip to content

Commit

Permalink
ulsp: abstract jsonrpc
Browse files Browse the repository at this point in the history
Signed-off-by: Jafar Al-Gharaibeh <[email protected]>
  • Loading branch information
Jafaral committed Nov 10, 2024
1 parent a543033 commit 6a067e1
Show file tree
Hide file tree
Showing 7 changed files with 364 additions and 148 deletions.
7 changes: 4 additions & 3 deletions uni/ulsp/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ UFLAGS=-s -u
prog=ulsp

SRC=launch-lsp.icn workspace.icn database.icn server.icn completion.icn signature.icn hover.icn \
definition.icn logger.icn
OBJ=launch-lsp.u workspace.u database.u server.u completion.u signature.u hover.u definition.u logger.u
definition.icn jsonrpc.icn logger.icn
OBJ=launch-lsp.u workspace.u database.u server.u completion.u signature.u hover.u definition.u \
jsonrpc.u logger.u

export IPATH=$(UNI)/unidoc

Expand All @@ -21,7 +22,7 @@ $(prog): $(OBJ)

launch-lsp.u:launch-lsp.icn workspace.u database.u server.u completion.u signature.u hover.u definition.u

server.u:server.icn database.u completion.u workspace.u signature.u hover.u definition.u logger.u
server.u:server.icn database.u completion.u workspace.u signature.u hover.u definition.u jsonrpc.u logger.u
hover.u:hover.icn signature.u
definition.u: definition.icn hover.u

Expand Down
3 changes: 1 addition & 2 deletions uni/ulsp/completion.icn
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ class CompletionHandler(
}
}

results := tojson(results_table)
return results
return results_table
end

method addBuiltInFunctionsCompletionItems(results_table)
Expand Down
6 changes: 3 additions & 3 deletions uni/ulsp/definition.icn
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ class DefinitionHandler(
signatureHandler.handleNewSignature(context)

possibleItems := signatureHandler.getCurrentSigItems()
\possibleItems | return tojson(results_table)
member(item := possibleItems[1], "source") | return tojson(results_table)
\possibleItems | return results_table
member(item := possibleItems[1], "source") | return results_table

if source := open(item["source"]) then {
every line := !source do {
Expand All @@ -58,6 +58,6 @@ class DefinitionHandler(
results_table["uri"] := item["source"]
results_table["range"] := ["start":["line":i;"character":startPos];"end":["line":i;"character":endPos]]

return tojson(results_table)
return results_table
end
end
6 changes: 3 additions & 3 deletions uni/ulsp/hover.icn
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ class HoverHandler(
signatureHandler := SignatureHandler()
signatureHandler.setVariables(workspace)

hover_item := \getHoverItem(desired_line, character) | return tojson(results_table)
_context := \getContext(context) | return tojson(results_table)
hover_item := \getHoverItem(desired_line, character) | return results_table
_context := \getContext(context) | return results_table

if _context == "function" | _context == "built-in-function" | _context == "method" |
_context == "procedure" | _context == "constructor" then {
Expand All @@ -48,7 +48,7 @@ class HoverHandler(
results_table["contents"] := table("kind", "markdown", "value", "(_" || _context || "_) " || hover_item )
}

return tojson(results_table)
return results_table
end

method getContext(context)
Expand Down
262 changes: 262 additions & 0 deletions uni/ulsp/jsonrpc.icn
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
#
# https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/
#

# Request message:
#
# Content-Length: ...\r\n
# \r\n
# {
# "jsonrpc": "2.0",
# "id": 1,
# "method": "textDocument/completion",
# "params": {
# ...
# }
# }
#
# Response msg:
#
# Content-Length: ...\r\n
# \r\n
# {
# "jsonrpc": "2.0",
# "id": 1,
# "result": ...,
# "error": ...
# }
#
# The result of a request is REQUIRED on success.
# It MUST NOT exist if there was an error invoking the method.
# In case of an error, and error objet should bre returned.
#

package ulsp
import json
link ximage

$define CONTENT_LENGTH "Content-Length: "

class JRPC_Message(msg, kind, json_table)

method parse_json(s)
msg := s
if json_table := jtou(s) then {
guess_kind()
return self
} else
kind := "unknown"
end

method guess_kind()
#if we have a method, the msg is a request
if \json_table["method"] then
kind := if \json_table["id"] then "request" else "notification"
else if \json_table["error"] then
kind := "error"
else if \json_table["result"] then
kind := "response"
else
kind := "unknown"
end

method get_kind()
return kind
end
method get_id()
return json_table["id"]
end
method get_method()
return json_table["method"]
end
method get_params()
return json_table["params"]
end
# many requests operate on a file, stored in uri param
method get_param_uri()
return json_table["params"]["textDocument"]["uri"]
end

method get_result()
return json_table["result"]
end
method get_error()
return json_table["error"]
end
method get_content()
return msg
end

method init_msg()
/json_table := ["jsonrpc" : "2.0"]
end

method set_result(result)
json_table["result"] := result
end
method set_id(id)
return json_table["id"] := id
end
method set_method(meth)
return json_table["method"] := meth
end
method set_params(params)
return json_table["params"] := params
end

method make_result_response(result)
delete(json_table, "method", "params", "error")
json_table["result"] := \result | ""
if msg := tojson(json_table) then
return self
end

method make_request(id, meth, params)
if /json_table then
json_table := [
"id" : id;
"method" : meth;
"params" : params
]
else {
delete(json_table, "result", "error")
json_table["id"] := id
json_table["method"] := meth
json_table["params"] := params
}
if msg := tojson(json_table) then
return self
end

# ErrorCodes
# # Defined by JSON-RPC
# ParseError: -32700;
# InvalidRequest: -32600;
# MethodNotFound: -32601;
# InvalidParams: -32602;
# InternalError: -32603;

#
# Error code indicating that a server received a notification or
# request before the server has received the `initialize` request.
#
# ServerNotInitialized: -32002;
# UnknownErrorCode: -32001;

#
# A request failed but it was syntactically correct, e.g the
# method name was known and the parameters were valid. The error
# message should contain human readable information about why
# the request failed.
#
# RequestFailed: -32803;

#
# The server cancelled the request. This error code should
# only be used for requests that explicitly support being
# server cancellable.
#
# ServerCancelled: -32802;

#
# The server detected that the content of a document got
# modified outside normal conditions. A server should
# NOT send this error code if it detects a content change
# in its unprocessed messages. The result even computed
# on an older state might still be useful for the client.
#
# If a client decides that a result is not of any use anymore
# the client should cancel the request.
#
# ContentModified: -32801;

#
# The client has canceled a request and a server has detected
# the cancel.
#
# RequestCancelled: -32800;

method make_error_response(err_code, err_msg, err_data)
delete(json_table, "method", "params", "result")
json_table["error"] := [
"code" : err_code;
"message" : err_msg;
"data" : \err_data | ""
]
if msg := tojson(json_table) then
return self
end

initially
kind := "unknown"
end


class JRPC_HTTPSocket(sock)
#
# parse the http header in the form of
# ...
# Content-Length: <SIZE>
# ...
# \r\n\r\n
# and return the length of the rpc message
method get_http_header(timeout)
local len, data := "", size := 22
/timeout := -1
repeat {
# Wait for a message to arrive, read the header
# assume the min header size is 22, because the shortest jrpc
# msgs will be at least 2 digits, I.e header size >=22
*select(sock, timeout) > 0 | fail
data ||:= ready(sock, size-*data)
data ~== "" | fail
if *data < size then next

data ? {
if = CONTENT_LENGTH then {
len := integer(tab(many(&digits))) | fail
# Make sure we have the whole header.
# For long messages, 5 digits size, read one byte at a time until we
# find end. This should be very uncommon
="\r\n\r\n" & return len
size +:=1
}
else
data := tab(find(CONTENT_LENGTH)) | ""
}
}
end

method get_data(size, timeout)
local data := ""
/timeout := -1
while *data < size do {
*select(sock, timeout) > 0 | fail
data ||:= ready(sock, size-*data)
data ~== "" | fail
}
return data
end

method get_msg(timeout)
local msg_body, len
len := get_http_header(timeout) | fail
msg_body := get_data(len, timeout) | fail
if msg_body[1] == "{" & msg_body[-1] == "}" then {
return JRPC_Message().parse_json(msg_body)
}
end

method send_msg(msg)
local data
data := msg.get_content()
writes(sock, CONTENT_LENGTH, *data, "\r\n\r\n", data)
end

initially(addr, opt)
if string(addr) then
sock := open(addr,opt)
else
sock := addr

end
Loading

0 comments on commit 6a067e1

Please sign in to comment.