lean4-htt/src/Lean/Server/GoTo.lean
2025-07-25 12:02:51 +00:00

333 lines
10 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
Copyright (c) 2022 Sebastian Ullrich. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sebastian Ullrich, Lars König, Wojciech Nawrocki
-/
module
prelude
public import Lean.Data.Json.FromToJson.Basic
public import Lean.Util.Path
public import Lean.Server.Utils
public import Lean.Data.Lsp.Internal
public import Lean.Util.CollectFVars
public import Lean.Util.ForEachExpr
meta import Lean.Parser.Module
public section
namespace Lean.Server
open Lsp
open Elab
inductive GoToKind
| declaration | definition | type
deriving BEq, ToJson, FromJson
def GoToKind.determineTargetExprs (kind : GoToKind) (ti : TermInfo) : MetaM (Array Expr) := do
match kind with
| .type =>
let type ← instantiateMVars <| ← Meta.inferType ti.expr
let (_, targetExprs) ← StateT.run (s := #[]) <| type.forEach fun e =>
match e with
| .fvar ..
| .const .. =>
modify (·.push e)
| _ =>
pure ()
return targetExprs
| _ =>
return #[← instantiateMVars ti.expr]
def getInstanceProjectionArg? (e : Expr) : MetaM (Option Expr) := do
let env ← getEnv
let .const n _ := e.getAppFn'
| return none
let some projInfo := env.getProjectionFnInfo? n
| return none
let instIdx := projInfo.numParams
let appArgs := e.getAppArgs
return appArgs[instIdx]?
def isInstanceProjection (e : Expr) : MetaM Bool := do
return (← getInstanceProjectionArg? e).isSome
/-- Checks whether `ti1` is an instance projection info that subsumes `ti2`. -/
def isInstanceProjectionInfoFor (kind : GoToKind) (ti1 ti2 : TermInfo) : MetaM Bool := do
if kind == .type then
return false
let some startPos1 := ti1.stx.getPos? (canonicalOnly := true)
| return false
let some startPos2 := ti2.stx.getPos? (canonicalOnly := true)
| return false
if startPos1 != startPos2 then
return false
let e1 ← instantiateMVars ti1.expr
let e2 ← instantiateMVars ti2.expr
if ! (← isInstanceProjection e1) || (← isInstanceProjection e2) then
return false
return e1.getAppFn' == e2.getAppFn'
structure GoToContext where
doc : DocumentMeta
kind : GoToKind
infoTree? : Option InfoTree
originInfo? : Option Info
children : PersistentArray InfoTree
abbrev GoToM α := ReaderT GoToContext MetaM α
def GoToM.run (ctx : GoToContext) (ci : ContextInfo) (lctx : LocalContext) (act : GoToM α) :
IO α :=
ci.runMetaM lctx <| ReaderT.run act ctx
def locationLinksFromDecl (declName : Name) : GoToM (Array LeanLocationLink) := do
let ctx ← read
-- Potentially this name is a builtin that has not been imported yet:
if ! (← getEnv).contains declName then
return #[]
let some (declModName, declModUri) ← declMod?
| return #[]
let some ranges ← findDeclarationRanges? declName
| return #[]
let originSelectionRange? := do
let i ← ctx.originInfo?
let r ← i.range?
return r.toLspRange ctx.doc.text
let ll : LeanLocationLink := {
originSelectionRange?
targetUri := declModUri
targetRange := ranges.range.toLspRange
targetSelectionRange := ranges.selectionRange.toLspRange
ident? := some ⟨declModName, declName.eraseMacroScopes⟩
isDefault := false
}
return #[ll]
where
declMod? : GoToM (Option (Name × DocumentUri)) := do
let ctx ← read
let declMod? ← findModuleOf? declName
match declMod? with
| some declModName =>
let some declModUri ← documentUriFromModule? declModName
| return none
return some (declModName, declModUri)
| none =>
return some (ctx.doc.mod, ctx.doc.uri)
def locationLinksFromBinder (id : FVarId) : GoToM (Array LeanLocationLink) := do
let ctx ← read
let doc := ctx.doc
let some binderInfo ← binderInfo?
| return #[]
let some binderInfoRange := binderInfo.range?
| return #[]
let binderInfoRange := binderInfoRange.toLspRange doc.text
let originSelectionRange? := do
let i ← ctx.originInfo?
let r ← i.range?
return r.toLspRange doc.text
let ll : LeanLocationLink := {
originSelectionRange?
targetUri := doc.uri
targetRange := binderInfoRange
targetSelectionRange := binderInfoRange
ident? := none
isDefault := false
}
return #[ll]
where
binderInfo? : GoToM (Option Info) := do
let ctx ← read
let some infoTree := ctx.infoTree?
| return none
return infoTree.findInfo? fun
| .ofTermInfo { isBinder := true, expr := .fvar id' .., .. } => id' == id
| _ => false
def locationLinksFromImport (i : CommandInfo) : GoToM (Array LeanLocationLink) := do
let ctx ← read
let `(Parser.Module.import| $[public]? $[meta]? import $[all]? $mod) := i.stx
| return #[]
let some modUri ← documentUriFromModule? mod.getId
| return #[]
let range := { start := ⟨0, 0⟩, «end» := ⟨0, 0⟩ : Range }
let originSelectionRange? := do
let r ← mod.raw.getRange? (canonicalOnly := true)
return r.toLspRange ctx.doc.text
let ll : LeanLocationLink := {
originSelectionRange?
targetUri := modUri
targetRange := range
targetSelectionRange := range
ident? := none
isDefault := false
}
return #[ll]
def locationLinksDefault : GoToM (Array LeanLocationLink) := do
-- If other location link resolutions fail, we try to show the elaborator or parser
let names ← defaultDeclNames
let mut ll := #[]
for name in names do
ll := ll ++ (← locationLinksFromDecl name)
return ll.map ({· with isDefault := true})
where
defaultDeclNames : GoToM (Array Name) := do
let ctx ← read
let env ← getEnv
let some originInfo := ctx.originInfo?
| return #[]
let some ei := originInfo.toElabInfo?
| return #[]
let e := ei.elaborator
if e == `Delab then
return #[]
if e == `Lean.Elab.Term.elabApp || e == `Lean.Elab.Term.elabIdent then
return #[]
let mut names := #[]
if env.contains e then
names := names.push e
if ctx.kind == .declaration && env.contains ei.stx.getKind then
names := names.push ei.stx.getKind
return names
def locationLinksFromErrorNameInfo (eni : ErrorNameInfo) : GoToM (Array LeanLocationLink) := do
let ctx ← read
let some explan := getErrorExplanationRaw? (← getEnv) eni.errorName
| return #[]
let some loc := explan.declLoc?
| return #[]
let some modUri ← documentUriFromModule? loc.module
| return #[]
let range := loc.range.toLspRange
let originSelectionRange? := do
let r ← eni.stx.getRange? (canonicalOnly := true)
return r.toLspRange ctx.doc.text
let link : LeanLocationLink := {
originSelectionRange?
targetUri := modUri
targetRange := range
targetSelectionRange := range
ident? := none
isDefault := false
}
return #[link]
def locationLinksFromInstanceProjection (e : Expr) : GoToM (Array LeanLocationLink) := do
-- Go-to-definition on a projection application of a typeclass
-- should return all instances generated by TC.
let .const n _ := e.getAppFn.consumeMData
| return #[]
let some instArg ← getInstanceProjectionArg? e
| return #[]
let mut results := #[]
for inst in ← extractInstances instArg do
results := results.append <| ← locationLinksFromDecl inst
results := results.append <| ← locationLinksFromDecl n
return results
where
extractInstances (e : Expr) : GoToM (Array Name) := do
match e with
| .const declName _ =>
if ! (← Lean.Meta.isInstance declName) then
return #[]
return #[declName]
| .app fn arg =>
let fnInstances ← extractInstances fn
let argInstances ← extractInstances arg
return argInstances ++ fnInstances
| .mdata _ e =>
extractInstances e
| _ =>
return #[]
def locationLinksFromTermInfo (ti : TermInfo) : GoToM (Array LeanLocationLink) := do
let ctx ← read
let mut ll := #[]
let exprs ← ctx.kind.determineTargetExprs ti
for expr in exprs do
let newLL ←
match expr.consumeMData with
| Expr.const n .. =>
locationLinksFromDecl n
| Expr.fvar id .. =>
locationLinksFromBinder id
| _ =>
locationLinksFromInstanceProjection expr
ll := ll ++ newLL
return ll
def locationLinksFromDelabTermInfo (dti : DelabTermInfo) : GoToM (Array LeanLocationLink) := do
let ctx ← read
let { toTermInfo := ti, location?, .. } := dti
let some location := location?
| return ← locationLinksFromTermInfo ti
let some targetUri ← documentUriFromModule? location.module
-- If we fail to find a DocumentUri, use the term info so that we have something to jump to.
| return ← locationLinksFromTermInfo ti
let range := location.range.toLspRange
let originSelectionRange? := do
let r ← Info.ofDelabTermInfo dti |>.range?
return r.toLspRange ctx.doc.text
let result : LeanLocationLink := {
originSelectionRange?
targetUri
targetRange := range
targetSelectionRange := range
ident? := none
isDefault := false
}
return #[result]
def locationLinksFromFieldInfo (fi : FieldInfo) : GoToM (Array LeanLocationLink) := do
let ctx ← read
if ctx.kind != .type then
return ← locationLinksFromDecl fi.projName
let expr ← instantiateMVars (← Meta.inferType fi.val)
let some n := expr.getAppFn.constName?
| return #[]
locationLinksFromDecl n
def locationLinksFromOptionInfo (i : OptionInfo) : GoToM (Array LeanLocationLink) :=
locationLinksFromDecl i.declName
def locationLinksFromCommandInfo (i : CommandInfo) : GoToM (Array LeanLocationLink) := do
let ctx ← read
if ! (i matches ⟨`import, _⟩) || ctx.kind == .type then
return #[]
locationLinksFromImport i
def locationLinksOfInfo (doc : DocumentMeta) (kind : GoToKind) (ictx : InfoWithCtx)
(infoTree? : Option InfoTree := none) : IO (Array LeanLocationLink) := do
let ctx : GoToContext := {
doc
kind
infoTree?
originInfo? := some ictx.info
children := ictx.children
}
GoToM.run ctx ictx.ctx ictx.info.lctx do
let ll ←
match ictx.info with
| .ofTermInfo ti =>
locationLinksFromTermInfo ti
| .ofDelabTermInfo dti =>
locationLinksFromDelabTermInfo dti
| .ofFieldInfo fi =>
locationLinksFromFieldInfo fi
| .ofOptionInfo oi =>
locationLinksFromOptionInfo oi
| .ofCommandInfo cci =>
locationLinksFromCommandInfo cci
| .ofErrorNameInfo eni =>
locationLinksFromErrorNameInfo eni
| _ =>
pure #[]
if kind == .declaration || ll.isEmpty then
return ll ++ (← locationLinksDefault)
return ll
end Lean.Server