-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* lsp: initial structure * lsp: error handling
- Loading branch information
1 parent
19495ea
commit 708513f
Showing
15 changed files
with
548 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
(executable | ||
(name teikalsp) | ||
(libraries lsp eio eio_main) | ||
(preprocess | ||
(pps ppx_deriving.show ppx_deriving.eq ppx_deriving.ord sedlex.ppx))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
module Io : sig | ||
type 'a t | ||
|
||
val return : 'a -> 'a t | ||
val raise : exn -> 'a t | ||
val await : 'a t -> 'a | ||
val async : (sw:Eio.Switch.t -> ('a, exn) result) -> 'a t | ||
|
||
module O : sig | ||
val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t | ||
val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t | ||
end | ||
end = struct | ||
type 'a t = sw:Eio.Switch.t -> ('a, exn) result Eio.Promise.t | ||
|
||
let await t = Eio.Switch.run @@ fun sw -> Eio.Promise.await_exn (t ~sw) | ||
let return value ~sw:_ = Eio.Promise.create_resolved (Ok value) | ||
let error desc ~sw:_ = Eio.Promise.create_resolved (Error desc) | ||
|
||
let async f ~sw = | ||
let promise, resolver = Eio.Promise.create () in | ||
( Eio.Fiber.fork ~sw @@ fun () -> | ||
try | ||
let result = f ~sw in | ||
Eio.Promise.resolve resolver result | ||
with exn -> Eio.Promise.resolve resolver @@ Error exn ); | ||
promise | ||
|
||
let bind t f = | ||
async @@ fun ~sw -> | ||
match Eio.Promise.await (t ~sw) with | ||
| Ok value -> Eio.Promise.await @@ f value ~sw | ||
| Error desc -> Error desc | ||
|
||
let raise = error | ||
|
||
module O = struct | ||
let ( let+ ) x f = bind x @@ fun value -> return @@ f value | ||
let ( let* ) = bind | ||
end | ||
end | ||
|
||
module Chan : sig | ||
type input | ||
type output | ||
|
||
(* eio *) | ||
val of_source : #Eio.Flow.source -> input | ||
val with_sink : #Eio.Flow.sink -> (output -> 'a) -> 'a | ||
|
||
(* lsp *) | ||
val read_line : input -> string option Io.t | ||
val read_exactly : input -> int -> string option Io.t | ||
val write : output -> string -> unit Io.t | ||
end = struct | ||
type input = Input of { mutex : Eio.Mutex.t; buf : Eio.Buf_read.t } | ||
type output = Output of { mutex : Eio.Mutex.t; buf : Eio.Buf_write.t } | ||
|
||
(* TODO: magic numbers *) | ||
let initial_size = 1024 | ||
let max_size = 1024 * 1024 | ||
|
||
let of_source source = | ||
let mutex = Eio.Mutex.create () in | ||
let buf = Eio.Buf_read.of_flow ~initial_size ~max_size source in | ||
Input { mutex; buf } | ||
|
||
let with_sink sink f = | ||
let mutex = Eio.Mutex.create () in | ||
Eio.Buf_write.with_flow ~initial_size sink @@ fun buf -> | ||
f @@ Output { mutex; buf } | ||
|
||
let read_line input = | ||
let (Input { mutex; buf }) = input in | ||
Io.async @@ fun ~sw:_ -> | ||
(* TODO: what this protect does? *) | ||
Eio.Mutex.use_rw ~protect:true mutex @@ fun () -> | ||
match Eio.Buf_read.eof_seen buf with | ||
| true -> Ok None | ||
| false -> Ok (Some (Eio.Buf_read.line buf)) | ||
|
||
let read_exactly input size = | ||
let (Input { mutex; buf }) = input in | ||
Io.async @@ fun ~sw:_ -> | ||
Eio.Mutex.use_rw ~protect:true mutex @@ fun () -> | ||
match Eio.Buf_read.eof_seen buf with | ||
| true -> Ok None | ||
| false -> Ok (Some (Eio.Buf_read.take size buf)) | ||
|
||
let write output str = | ||
let (Output { mutex; buf }) = output in | ||
Io.async @@ fun ~sw:_ -> | ||
Eio.Mutex.use_rw ~protect:true mutex @@ fun () -> | ||
Ok (Eio.Buf_write.string buf str) | ||
end | ||
|
||
module Lsp_io = Lsp.Io.Make (Io) (Chan) | ||
open Jsonrpc | ||
open Lsp_error | ||
|
||
(* TODO: is a mutex needed for write? *) | ||
type channel = Chan.output | ||
type t = channel | ||
|
||
let notify channel notification = | ||
(* TODO: fork here *) | ||
(* TODO: buffering and async? *) | ||
let notification = Lsp.Server_notification.to_jsonrpc notification in | ||
Io.await @@ Lsp_io.write channel @@ Notification notification | ||
|
||
let respond channel response = | ||
Io.await @@ Lsp_io.write channel @@ Response response | ||
|
||
let rec input_loop ~input ~output with_ = | ||
(* TODO: buffering and async handling *) | ||
match Io.await @@ Lsp_io.read input with | ||
| Some packet -> | ||
let () = with_ packet in | ||
input_loop ~input ~output with_ | ||
| exception exn -> (* TODO: handle this exception *) raise exn | ||
| None -> | ||
(* TODO: this means EOF right? *) | ||
() | ||
|
||
let request_of_jsonrpc request = | ||
match Lsp.Client_request.of_jsonrpc request with | ||
| Ok request -> request | ||
| Error error -> fail (Error_invalid_notification { error }) | ||
|
||
let notification_of_jsonrpc notification = | ||
match Lsp.Client_notification.of_jsonrpc notification with | ||
| Ok notification -> notification | ||
| Error error -> fail (Error_invalid_notification { error }) | ||
|
||
type on_request = { | ||
f : | ||
'response. | ||
channel -> | ||
'response Lsp.Client_request.t -> | ||
('response, Response.Error.t) result; | ||
} | ||
|
||
let listen ~input ~output ~on_request ~on_notification = | ||
let on_request channel request = | ||
(* TODO: error handling *) | ||
let result = | ||
let (E request) = request_of_jsonrpc request in | ||
match on_request.f channel request with | ||
| Ok result -> Ok (Lsp.Client_request.yojson_of_result request result) | ||
| Error _error as error -> error | ||
in | ||
let response = Jsonrpc.Response.{ id = request.id; result } in | ||
respond channel response | ||
in | ||
let on_notification channel notification = | ||
let notification = notification_of_jsonrpc notification in | ||
on_notification channel notification | ||
in | ||
|
||
let input = Chan.of_source input in | ||
Chan.with_sink output @@ fun channel -> | ||
input_loop ~input ~output @@ fun packet -> | ||
(* TODO: make this async? *) | ||
match packet with | ||
| Notification notification -> on_notification channel notification | ||
| Request request -> on_request channel request | ||
| Batch_call calls -> | ||
(* TODO: what if one fails? It should not prevents the others *) | ||
List.iter | ||
(fun call -> | ||
match call with | ||
| `Request request -> on_request channel request | ||
| `Notification notification -> on_notification channel notification) | ||
calls | ||
(* TODO: can the server receive a response? | ||
Yes but right now it will not be supported *) | ||
| Response _ -> fail Error_response_unsupported | ||
| Batch_response _ -> fail Error_response_unsupported |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
open Jsonrpc | ||
|
||
type channel | ||
type t = channel | ||
|
||
val notify : channel -> Lsp.Server_notification.t -> unit | ||
|
||
type on_request = { | ||
f : | ||
'response. | ||
channel -> | ||
'response Lsp.Client_request.t -> | ||
('response, Response.Error.t) result; | ||
} | ||
|
||
(* TODO: request*) | ||
val listen : | ||
input:#Eio.Flow.source -> | ||
output:#Eio.Flow.sink -> | ||
on_request:on_request -> | ||
on_notification:(channel -> Lsp.Client_notification.t -> unit) -> | ||
unit | ||
|
||
(* val input_loop : input:Chan.input -> | ||
output:Chan.output -> (Jsonrpc.Packet.t -> Jsonrpc.Packet.t list) -> unit) *) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
open Lsp.Types | ||
open Lsp_error | ||
module Document_uri_map = Map.Make (DocumentUri) | ||
|
||
(* TODO: capabilities *) | ||
(* TODO: initialized *) | ||
type status = Handshake | Running | ||
|
||
type context = { | ||
mutable status : status; | ||
mutable text_documents : Lsp_text_document.t Document_uri_map.t; | ||
} | ||
|
||
type t = context | ||
|
||
let create () = { status = Handshake; text_documents = Document_uri_map.empty } | ||
let status context = context.status | ||
|
||
let initialize context = | ||
match context.status with | ||
| Handshake -> context.status <- Running | ||
| Running -> fail Error_invalid_status_during_initialize | ||
|
||
let update_text_documents context f = | ||
let text_documents = context.text_documents in | ||
let text_documents = f text_documents in | ||
context.text_documents <- text_documents | ||
|
||
let open_text_document context uri text_document = | ||
update_text_documents context @@ fun text_documents -> | ||
(match Document_uri_map.mem uri text_documents with | ||
| true -> fail Error_text_document_already_in_context | ||
| false -> ()); | ||
Document_uri_map.add uri text_document text_documents | ||
|
||
let change_text_document context uri cb = | ||
update_text_documents context @@ fun text_documents -> | ||
let text_document = | ||
match Document_uri_map.find_opt uri text_documents with | ||
| Some text_document -> text_document | ||
| None -> fail Error_text_document_not_in_context | ||
in | ||
let text_document = cb text_document in | ||
(* TODO: only accept if version is newer or equal *) | ||
Document_uri_map.add uri text_document text_documents | ||
|
||
let close_text_document context uri = | ||
update_text_documents context @@ fun text_documents -> | ||
(match Document_uri_map.mem uri text_documents with | ||
| true -> () | ||
| false -> fail Error_text_document_not_in_context); | ||
Document_uri_map.remove uri text_documents |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
open Lsp.Types | ||
|
||
type status = private Handshake | Running | ||
type context | ||
type t = context | ||
|
||
(* TODO: rollback? Requests and notifications should probably be atomic *) | ||
val create : unit -> context | ||
val status : context -> status | ||
val initialize : context -> unit | ||
|
||
(* documents *) | ||
val open_text_document : context -> DocumentUri.t -> Lsp_text_document.t -> unit | ||
|
||
val change_text_document : | ||
context -> | ||
DocumentUri.t -> | ||
(Lsp_text_document.t -> Lsp_text_document.t) -> | ||
unit | ||
|
||
val close_text_document : context -> DocumentUri.t -> unit |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
open Lsp.Types | ||
|
||
type error = | ||
(* channel *) | ||
| Error_request_unsupported | ||
| Error_response_unsupported | ||
| Error_invalid_request of { error : string } | ||
| Error_invalid_notification of { error : string } | ||
(* server *) | ||
| Error_unsupported_request | ||
| Error_unsupported_notification | ||
(* context *) | ||
| Error_notification_before_initialize | ||
| Error_invalid_status_during_initialize | ||
| Error_text_document_already_in_context | ||
| Error_text_document_not_in_context | ||
(* notification *) | ||
| Error_multiple_content_changes of { | ||
content_changes : TextDocumentContentChangeEvent.t list; [@opaque] | ||
} | ||
| Error_partial_content_change of { | ||
content_change : TextDocumentContentChangeEvent.t; [@opaque] | ||
} | ||
| Error_invalid_content_change of { | ||
content_change : TextDocumentContentChangeEvent.t; [@opaque] | ||
} | ||
| Error_unknown_language_id of { language_id : string } | ||
|
||
and t = error [@@deriving show] | ||
|
||
(* TODO: what happen with errors? *) | ||
exception Lsp_error of { error : error } | ||
|
||
let fail error = raise (Lsp_error { error }) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
open Lsp.Types | ||
|
||
type error = | ||
(* channel *) | ||
| Error_request_unsupported | ||
| Error_response_unsupported | ||
| Error_invalid_request of { error : string } | ||
| Error_invalid_notification of { error : string } | ||
(* server *) | ||
| Error_unsupported_request | ||
| Error_unsupported_notification | ||
(* context *) | ||
| Error_notification_before_initialize | ||
| Error_invalid_status_during_initialize | ||
| Error_text_document_already_in_context | ||
| Error_text_document_not_in_context | ||
(* notification *) | ||
| Error_multiple_content_changes of { | ||
content_changes : TextDocumentContentChangeEvent.t list; | ||
} | ||
| Error_partial_content_change of { | ||
content_change : TextDocumentContentChangeEvent.t; | ||
} | ||
| Error_invalid_content_change of { | ||
content_change : TextDocumentContentChangeEvent.t; | ||
} | ||
| Error_unknown_language_id of { language_id : string } | ||
|
||
type t = error [@@deriving show] | ||
|
||
exception Lsp_error of { error : error } | ||
|
||
val fail : error -> 'a |
Oops, something went wrong.