Skip to content

Commit

Permalink
_
Browse files Browse the repository at this point in the history
Signed-off-by: Rudi Grinberg <me@rgrinberg.com>
  • Loading branch information
rgrinberg committed May 18, 2024
1 parent cfee035 commit 8dc8f5c
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 64 deletions.
1 change: 1 addition & 0 deletions dune-project
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ possible and does not make any assumptions about IO.
(description "An LSP server for OCaml.")
(depends
yojson
base
(re (>= 1.5.0))
(ppx_yojson_conv_lib (>= "v0.14"))
(dune-rpc (>= 3.4.0))
Expand Down
2 changes: 0 additions & 2 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,7 @@
yojson
ppx_yojson_conv_lib
merlin-lib
re2
base
core
];
propagatedBuildInputs = [ ];
doCheck = false;
Expand Down
1 change: 1 addition & 0 deletions ocaml-lsp-server.opam
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ bug-reports: "https://github.com/ocaml/ocaml-lsp/issues"
depends: [
"dune" {>= "3.0"}
"yojson"
"base"
"re" {>= "1.5.0"}
"ppx_yojson_conv_lib" {>= "v0.14"}
"dune-rpc" {>= "3.4.0"}
Expand Down
129 changes: 70 additions & 59 deletions ocaml-lsp-server/src/code_actions/action_destruct_line.ml
Original file line number Diff line number Diff line change
Expand Up @@ -66,27 +66,40 @@ let is_hole (case_line : string) (cursor_pos : int) =
then true
else false

let get_statement_kind (code_line : string) (range : Range.t) =
let logical_line = String.strip code_line in
(* Line starts with [match], ends with [with], and has at least one other word. *)
let match_with_regex = "^match[ \t]+[^ \t].*[ \t]with$" in
let get_statement_kind =
let space_without_nl = Re.set " \t" in
(* Line starts with [match] and has at least one other word. *)
let match_regex = "^match[ \t]+[^ \t]" in
let match_regex =
let open Re in
seq [ str "match"; rep1 space_without_nl; compl [ space_without_nl ] ]
in
let match_with_regex =
let open Re in
seq [ match_regex; rep any; space_without_nl; str "with"; eos ]
in
(* Line starts with a pipe and contains an arrow. *)
let case_regex = "^\\|.*->.*" in
let regex =
Re2.Multiple.create_exn
[ (match_with_regex, `MatchWithLine)
; (match_regex, `MatchLine)
; (case_regex, `CaseLine)
]
let case_regex =
let open Re in
seq [ str "|"; rep any; str "->"; rep any ]
in
match Re2.Multiple.matches regex logical_line with
| `MatchWithLine :: _ -> Some MatchWithLine
| `MatchLine :: _ -> Some MatchLine
| `CaseLine :: _ ->
if is_hole code_line range.start.character then Some Hole else Some CaseLine
| [] -> None
let regexes =
[ (match_with_regex, `MatchWithLine)
; (match_regex, `MatchLine)
; (case_regex, `CaseLine)
]
|> List.map ~f:(fun (re, kind) -> (Re.(seq [ bos; re ] |> compile), kind))
in
fun (code_line : string) (range : Range.t) ->
let logical_line = String.strip code_line in
(* Line starts with [match], ends with [with], and has at least one other word. *)
List.find_map regexes ~f:(fun (re, name) ->
Option.some_if (Re.execp re logical_line) name)
|> Option.bind ~f:(function
| `MatchWithLine -> Some MatchWithLine
| `MatchLine -> Some MatchLine
| `CaseLine ->
if is_hole code_line range.start.character then Some Hole
else Some CaseLine)

(** Given a line of the form [match x] or [match x with] or [| x -> y], create a
query range corresponding to [x]. *)
Expand Down Expand Up @@ -162,51 +175,49 @@ let extract_statement (doc : Document.t) (ca_range : Range.t) :
Some { code; kind; query_range; reply_range }

(* Merlin often surrounds [line] (or part of it) with parentheses that we don't want. *)
let strip_parentheses ~(kind : statement_kind) (line : string) =
(match kind with
| MatchLine | MatchWithLine | Hole -> line
| CaseLine -> (
let regex = Re2.create_exn "\\)\\s+->\\s+_" in
match Re2.replace ~f:(fun _ -> " -> _") regex line with
| Ok new_line -> new_line
| Error _ -> line))
|> String.chop_prefix_if_exists ~prefix:"("
|> String.chop_suffix_if_exists ~suffix:")"
let strip_parentheses =
let regex =
let open Re in
seq [ str ")"; rep1 space; str "->"; rep1 space; char '_' ] |> compile
in
fun ~(kind : statement_kind) (line : string) ->
(match kind with
| MatchLine | MatchWithLine | Hole -> line
| CaseLine -> Re.replace ~f:(fun _ -> " -> _") regex line)
|> String.chop_prefix_if_exists ~prefix:"("
|> String.chop_suffix_if_exists ~suffix:")"

let match_indent ~(statement : destructable_statement) (new_code : string) =
let full_line = statement.code in
let i = String.substr_index_exn full_line ~pattern:(String.strip full_line) in
let indent = String.sub full_line ~pos:0 ~len:i in
match
Re2.replace
~f:(fun _ -> "\n" ^ indent ^ "| ")
(Re2.create_exn "\n\\| ")
new_code
with
| Ok with_newlines -> with_newlines
| Error _ -> new_code
let match_indent =
let re = Re.str "\n| " |> Re.compile in
fun ~(statement : destructable_statement) (new_code : string) ->
let full_line = statement.code in
let i =
String.substr_index_exn full_line ~pattern:(String.strip full_line)
in
let indent = String.sub full_line ~pos:0 ~len:i in
Re.replace ~f:(fun _ -> "\n" ^ indent ^ "| ") re new_code

(* TODO: If [ocamlformat_rpc] ever gets implemented, it would probably be worth
re-thinking the post-processing that's happening here. *)
let format_merlin_reply ~(statement : destructable_statement)
(new_code : string) =
let start_of_case = Re2.create_exn " \\| " in
let lines = Re2.split start_of_case new_code in
let lines =
match lines with
| fst :: rst -> fst :: List.map ~f:String.strip rst
| [] -> lines
in
match statement.kind with
| MatchLine | MatchWithLine ->
String.concat ~sep:"\n| " lines
|> strip_parentheses ~kind:statement.kind
|> match_indent ~statement
| CaseLine ->
List.map ~f:(strip_parentheses ~kind:statement.kind) lines
|> String.concat ~sep:" -> _\n| "
|> match_indent ~statement
| Hole -> String.concat ~sep:" -> _\n| " lines |> match_indent ~statement
let format_merlin_reply =
let start_of_case = Re.str " | " |> Re.compile in
fun ~(statement : destructable_statement) (new_code : string) ->
let lines = Re.split start_of_case new_code in
let lines =
match lines with
| fst :: rst -> fst :: List.map ~f:String.strip rst
| [] -> lines
in
match statement.kind with
| MatchLine | MatchWithLine ->
String.concat ~sep:"\n| " lines
|> strip_parentheses ~kind:statement.kind
|> match_indent ~statement
| CaseLine ->
List.map ~f:(strip_parentheses ~kind:statement.kind) lines
|> String.concat ~sep:" -> _\n| "
|> match_indent ~statement
| Hole -> String.concat ~sep:" -> _\n| " lines |> match_indent ~statement

let code_action_of_case_analysis ~supportsJumpToNextHole doc uri (loc, newText)
=
Expand Down
4 changes: 1 addition & 3 deletions ocaml-lsp-server/src/dune
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@
dune-rpc
ocamlformat-rpc-lib
ocamlc-loc
core
base
re2)
base)
(lint
(pps ppx_yojson_conv))
(instrumentation
Expand Down

0 comments on commit 8dc8f5c

Please sign in to comment.