Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[lsp] Jump to definition for non-local definitions #762

Merged
merged 1 commit into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
#755, cc: #722, #725)
- [hover] Show input howto for unicode characters on hover
(@ejgallego, Léo Stefanesco, #756)
- [lsp] [definition] Support for jump to definition across workspace
files. The location information is obtained from `.glob` files, so
it is often not perfect. (@ejgallego, #762, fixes #317)
- [lsp] [hover] Show full name and provenance of identifiers
(@ejgallego, #762)

# coq-lsp 0.1.10: Hasta el 40 de Mayo _en effect_...
----------------------------------------------------
Expand Down
57 changes: 57 additions & 0 deletions controller/rq_common.ml
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,60 @@ let get_uchar_at_point ~prev ~contents ~point =
else None
in
get_char_at_point_gen ~prev ~get ~contents ~point

module CoqModule = struct
type t =
{ dp : Names.DirPath.t
; source : string
; vo : string
; uri : Lang.LUri.File.t
}

let uri { uri; _ } = uri
let source { source; _ } = source

let make dp =
match Loadpath.locate_absolute_library dp with
| Ok vo ->
Fleche.Io.Log.trace "rq_definition" "File Found";
let source = Filename.remove_extension vo ^ ".v" in
let source = Str.replace_first (Str.regexp "_build/default/") "" source in
let uri = Lang.LUri.of_string ("file://" ^ source) in
let uri = Lang.LUri.File.of_uri uri |> Result.get_ok in
Ok { dp; source; vo; uri }
| Error err ->
Fleche.Io.Log.trace "rq_definition" "File Not Found :(";
(* Debug? *)
Error err

let offset_to_range source (bp, ep) =
let text =
Coq.Compat.Ocaml_414.In_channel.(with_open_text source input_all)
in
let rec count (lines, char) cur goal =
if cur >= goal then (lines, char)
else
match text.[cur] with
| '\n' -> count (lines + 1, 0) (cur + 1) goal
| _ -> count (lines, char + 1) (cur + 1) goal
in
(* XXX UTF-8 / 16 adjust *)
let bline, bchar = count (0, 0) 0 bp in
let eline, echar = count (bline, bchar) bp ep in
let start = Lang.Point.{ line = bline; character = bchar; offset = bp } in
let end_ = Lang.Point.{ line = eline; character = echar; offset = ep } in
Lang.Range.{ start; end_ }

let find { vo; source; _ } name =
let glob = Filename.remove_extension vo ^ ".glob" in
match Coq.Glob.open_file glob with
| Error err ->
Fleche.Io.Log.trace "rq_definition:open_file" ("Error: " ^ err);
Error err
| Ok g -> (
match Coq.Glob.get_info g name with
| Some { offset; _ } -> Ok (Some (offset_to_range source offset))
| None ->
Fleche.Io.Log.trace "rq_definition:get_info" "Not found";
Ok None)
end
10 changes: 10 additions & 0 deletions controller/rq_common.mli
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,13 @@ val get_uchar_at_point :
-> contents:Fleche.Contents.t
-> point:int * int
-> (Uchar.t * string) option

module CoqModule : sig
type t

(* Lookup module as needed *)
val make : Names.DirPath.t -> (t, Loadpath.Error.t) Result.t
val uri : t -> Lang.LUri.File.t
val source : t -> string
val find : t -> string -> (Lang.Range.t option, string) Result.t
end
87 changes: 75 additions & 12 deletions controller/rq_definition.ml
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,80 @@
(* Written by: Emilio J. Gallego Arias *)
(************************************************************************)

let request ~token:_ ~(doc : Fleche.Doc.t) ~point =
let get_from_toc ~doc id_at_point =
let { Fleche.Doc.toc; _ } = doc in
Fleche.Io.Log.trace "rq_definition" "get_from_toc";
match CString.Map.find_opt id_at_point toc with
| Some node ->
let uri = doc.uri in
let range = node.range in
Some Lsp.Core.Location.{ uri; range }
| None -> None

let lp_to_string = function
| Loadpath.Error.LibNotFound -> "library not found"
| Loadpath.Error.LibUnmappedDir -> "unmapped library"

let err_code = -32803

let locate_extended qid =
try Some (Nametab.locate_extended qid) with Not_found -> None

let find_name_in dp name =
match Rq_common.CoqModule.make dp with
| Error err -> Error (err_code, lp_to_string err)
| Ok mod_ -> (
let uri = Rq_common.CoqModule.uri mod_ in
match Rq_common.CoqModule.find mod_ name with
| Error err -> Error (err_code, err)
| Ok range ->
Ok (Option.map (fun range -> Lsp.Core.Location.{ uri; range }) range))

let get_from_file id_at_point =
Fleche.Io.Log.trace "rq_definition" "get_from_file";
let qid = Libnames.qualid_of_string id_at_point in
match locate_extended qid with
| Some (TrueGlobal (ConstRef cr)) ->
Fleche.Io.Log.trace "rq_definition" "TrueGlobal Found";
let dp = Names.Constant.modpath cr |> Names.ModPath.dp in
let name = Names.Constant.to_string cr in
find_name_in dp name
| Some (TrueGlobal (IndRef (ind, _idx))) ->
let dp = Names.MutInd.modpath ind |> Names.ModPath.dp in
let name = Names.MutInd.to_string ind in
find_name_in dp name
| Some (Abbrev _abbrev) ->
(* Needs improved .glob parsing *)
Ok None
| _ ->
Fleche.Io.Log.trace "rq_definition" "No TrueGlobal Found";
Ok None

let get_from_file ~token ~st id_at_point =
let f = get_from_file in
Coq.State.in_state ~token ~st ~f id_at_point

let request ~token ~(doc : Fleche.Doc.t) ~point =
let { Fleche.Doc.contents; _ } = doc in
let ok s = Coq.Protect.E.ok (Result.Ok s) in
let idp = Rq_common.get_id_at_point ~contents ~point in
Option.cata
(fun id_at_point ->
let { Fleche.Doc.toc; _ } = doc in
match CString.Map.find_opt id_at_point toc with
| Some node ->
let uri = doc.uri in
let range = node.range in
Lsp.Core.Location.({ uri; range } |> to_yojson)
| None -> `Null)
`Null
(Rq_common.get_id_at_point ~contents ~point)
|> Result.ok
(fun idp ->
match get_from_toc ~doc idp with
| Some loc -> ok (Some loc)
| None ->
let approx = Fleche.Info.PrevIfEmpty in
Fleche.Info.LC.node ~doc ~point approx
|> Option.cata
(fun node ->
let st = Fleche.Doc.Node.state node in
get_from_file ~token ~st idp)
(ok None))
(ok None) idp
|> Coq.Protect.E.map
~f:(Result.map (Option.cata Lsp.Core.Location.to_yojson `Null))

let request ~token ~doc ~point =
let name = "textDocument/definition" in
let f () = request ~token ~doc ~point in
Request.R.of_execution ~name ~f ()
29 changes: 23 additions & 6 deletions controller/rq_hover.ml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ let build_ind_type mip = Inductive.type_of_inductive mip

type id_info =
| Notation of Pp.t
| Def of Pp.t
| Def of (Pp.t * Names.Constant.t option * string option)

let info_of_ind env sigma ((sp, i) : Names.Ind.t) =
let mib = Environ.lookup_mind sp env in
Expand All @@ -37,7 +37,7 @@ let info_of_ind env sigma ((sp, i) : Names.Ind.t) =
(Impargs.implicits_of_global (Names.GlobRef.IndRef (sp, i)))
in
let impargs = List.map Impargs.binding_kind_of_status impargs in
Def (Printer.pr_ltype_env ~impargs env_params sigma arity)
Def (Printer.pr_ltype_env ~impargs env_params sigma arity, None, None)

let type_of_constant cb = cb.Declarations.const_type

Expand All @@ -53,7 +53,12 @@ let info_of_const env sigma cr =
(Impargs.implicits_of_global (Names.GlobRef.ConstRef cr))
in
let impargs = List.map Impargs.binding_kind_of_status impargs in
Def (Printer.pr_ltype_env env sigma ~impargs typ)
let typ = Printer.pr_ltype_env env sigma ~impargs typ in
let dp = Names.Constant.modpath cr |> Names.ModPath.dp in
let source =
Rq_common.CoqModule.(make dp |> Result.to_option |> Option.map source)
in
Def (typ, Some cr, source)

let info_of_var env vr =
let vdef = Environ.lookup_named vr env in
Expand All @@ -70,7 +75,7 @@ let info_of_constructor env cr =
in
ctype

let print_type env sigma x = Def (Printer.pr_ltype_env env sigma x)
let print_type env sigma x = Def (Printer.pr_ltype_env env sigma x, None, None)

let info_of_id env sigma id =
let qid = Libnames.qualid_of_string id in
Expand Down Expand Up @@ -114,10 +119,22 @@ let info_of_id_at_point ~token ~node id =
let st = node.Fleche.Doc.Node.state in
Coq.State.in_state ~token ~st ~f:(info_of_id ~st) id

let pp_cr fmt = function
| None -> ()
| Some cr ->
Format.fprintf fmt " - **full path**: `%a`@\n" Pp.pp_with
(Names.Constant.print cr)

let pp_file fmt = function
| None -> ()
| Some file -> Format.fprintf fmt " - **in file**: `%s`" file

let pp_typ id = function
| Def typ ->
| Def (typ, cr, file) ->
let typ = Pp.string_of_ppcmds typ in
Format.(asprintf "```coq\n%s : %s\n```" id typ)
Format.(
asprintf "@[```coq\n%s : %s@\n```@\n@[%a@]@[%a@]@]" id typ pp_cr cr
pp_file file)
| Notation nt ->
let nt = Pp.string_of_ppcmds nt in
Format.(asprintf "```coq\n%s\n```" nt)
Expand Down
9 changes: 9 additions & 0 deletions coq/compat.ml
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,12 @@ let format_to_file ~file ~f x =
Out_channel.with_open_bin file (fun oc ->
let of_fmt = Format.formatter_of_out_channel oc in
Format.fprintf of_fmt "@[%a@]%!" f x)

module Option = struct
include Stdlib.Option

module O = struct
let ( let+ ) r f = map f r
let ( let* ) r f = bind r f
end
end
9 changes: 9 additions & 0 deletions coq/compat.mli
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,12 @@ module Result : sig
-> ('r, 'e) Result.t
-> unit
end

module Option : sig
include module type of Stdlib.Option

module O : sig
val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t
val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t
end
end
5 changes: 1 addition & 4 deletions coq/glob.ml
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,4 @@ let open_file file =
Compat.Ocaml_414.In_channel.with_open_text file (Coq.read_glob (Some vfile))
else Error (Format.asprintf "Cannot open file: %s" file)

let get_info map name =
match DefMap.find_opt name map with
| Some info -> Ok info
| None -> Error (Format.asprintf "definition %s not found in glob table" name)
let get_info map name = DefMap.find_opt name map
3 changes: 2 additions & 1 deletion coq/glob.mli
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
(* Glob file that was read and parsed successfully *)
type t

(* Input is a .glob file *)
val open_file : string -> (t, string) Result.t

module Info : sig
Expand All @@ -27,4 +28,4 @@ module Info : sig
}
end

val get_info : t -> string -> (Info.t, string) Result.t
val get_info : t -> string -> Info.t option
70 changes: 35 additions & 35 deletions etc/doc/PROTOCOL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,41 +19,41 @@ https://github.com/microsoft/language-server-protocol/issues/1414

If a feature doesn't appear here it usually means it is not planned in the short term:

| Method | Support | Notes |
|---------------------------------------|---------|------------------------------------------------------------|
| `initialize` | Partial | We don't obey the advertised client capabilities |
| `client/registerCapability` | No | Not planned ATM |
| `$/setTrace` | Yes | |
| `$/logTrace` | Yes | |
| `window/logMessage` | Yes | |
|---------------------------------------|---------|------------------------------------------------------------|
| `textDocument/didOpen` | Yes | We can't reuse Memo tables yet |
| `textDocument/didChange` | Yes | We only support `TextDocumentSyncKind.Full` for now |
| `textDocument/didClose` | Partial | We'd likely want to save a `.vo` file on close if possible |
| `textDocument/didSave` | Partial | Undergoing behavior refinement |
|---------------------------------------|---------|------------------------------------------------------------|
| `notebookDocument/didOpen` | No | Planned |
|---------------------------------------|---------|------------------------------------------------------------|
| `textDocument/declaration` | No | Planned, blocked on upstream issues |
| `textDocument/definition` | Partial | Working only locally on files for now |
| `textDocument/references` | No | Planned, blocked on upstream issues |
| `textDocument/hover` | Yes | Shows stats and type info of identifiers at point |
| `textDocument/codeLens` | No | |
| `textDocument/foldingRange` | No | |
| `textDocument/documentSymbol` | Yes | Sections and modules missing (#322) |
| `textDocument/semanticTokens` | No | Planned |
| `textDocument/inlineValue` | No | Planned |
| `textDocument/inlayHint` | No | Planned |
| `textDocument/completion` | Partial | Needs more work locally and upstream (#50) |
| `textDocument/publishDiagnostics` | Yes | |
| `textDocument/diagnostic` | No | Planned, issue #49 |
| `textDocument/codeAction` | No | Planned |
| `textDocument/selectionRange` | Partial | Selection for a point is its span; no parents |
|---------------------------------------|---------|------------------------------------------------------------|
| `workspace/workspaceFolders` | Yes | Each folder should have a `_CoqProject` file at the root. |
| `workspace/didChangeWorkspaceFolders` | Yes | |
| `workspace/didChangeConfiguration` | Yes (*) | We still do a client -> server push, instead of pull |
|---------------------------------------|---------|------------------------------------------------------------|
| Method | Support | Notes |
|---------------------------------------|---------|---------------------------------------------------------------|
| `initialize` | Partial | We don't obey the advertised client capabilities |
| `client/registerCapability` | No | Not planned ATM |
| `$/setTrace` | Yes | |
| `$/logTrace` | Yes | |
| `window/logMessage` | Yes | |
|---------------------------------------|---------|---------------------------------------------------------------|
| `textDocument/didOpen` | Yes | We can't reuse Memo tables yet |
| `textDocument/didChange` | Yes | We only support `TextDocumentSyncKind.Full` for now |
| `textDocument/didClose` | Partial | We'd likely want to save a `.vo` file on close if possible |
| `textDocument/didSave` | Partial | Undergoing behavior refinement |
|---------------------------------------|---------|---------------------------------------------------------------|
| `notebookDocument/didOpen` | No | Planned |
|---------------------------------------|---------|---------------------------------------------------------------|
| `textDocument/declaration` | No | Planned, blocked on upstream issues |
| `textDocument/definition` | Yes (*) | Uses .glob information which is often incomplete |
| `textDocument/references` | No | Planned, blocked on upstream issues |
| `textDocument/hover` | Yes | Shows stats and type info of identifiers at point, extensible |
| `textDocument/codeLens` | No | |
| `textDocument/foldingRange` | No | |
| `textDocument/documentSymbol` | Yes | Sections and modules missing (#322) |
| `textDocument/semanticTokens` | No | Planned |
| `textDocument/inlineValue` | No | Planned |
| `textDocument/inlayHint` | No | Planned |
| `textDocument/completion` | Partial | Needs more work locally and upstream (#50) |
| `textDocument/publishDiagnostics` | Yes | |
| `textDocument/diagnostic` | No | Planned, issue #49 |
| `textDocument/codeAction` | No | Planned |
| `textDocument/selectionRange` | Partial | Selection for a point is its span; no parents |
|---------------------------------------|---------|---------------------------------------------------------------|
| `workspace/workspaceFolders` | Yes | Each folder should have a `_CoqProject` file at the root. |
| `workspace/didChangeWorkspaceFolders` | Yes | |
| `workspace/didChangeConfiguration` | Yes (*) | We still do a client -> server push, instead of pull |
|---------------------------------------|---------|---------------------------------------------------------------|

### URIs accepted by coq-lsp

Expand Down
2 changes: 2 additions & 0 deletions examples/Pff.v
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Require Export List.
Require Export PeanoNat.
Require Import Psatz.

Set Warnings "-deprecated".

(* Compatibility workaround, remove once requiring Coq >= 8.16 *)
Module Import Compat.

Expand Down
Loading
Loading