Fixes #1170. This PR adds the module name to `RefIdent` in order to distinguish conflicting names from different files. This also fixes related issues in find-references or the call hierarchy feature. It also adds some docstrings and stylistically refactors a bunch of code.
523 lines
20 KiB
Text
523 lines
20 KiB
Text
/-
|
||
Copyright (c) 2021 Joscha Mennicken. All rights reserved.
|
||
Released under Apache 2.0 license as described in the file LICENSE.
|
||
|
||
Authors: Joscha Mennicken
|
||
-/
|
||
prelude
|
||
import Lean.Data.Lsp.Internal
|
||
import Lean.Server.Utils
|
||
|
||
/-! # Representing collected and deduplicated definitions and usages -/
|
||
|
||
set_option linter.missingDocs true
|
||
|
||
namespace Lean.Server
|
||
open Lsp Lean.Elab Std
|
||
|
||
/--
|
||
Global reference. Used by the language server to figure out which identifiers refer to which
|
||
other identifiers across the whole project.
|
||
-/
|
||
structure Reference where
|
||
/-- Identifier of this reference. -/
|
||
ident : RefIdent
|
||
/-- Identifiers that are logically identical to this reference. -/
|
||
aliases : Array RefIdent := #[]
|
||
/-- Range where this reference occurs. -/
|
||
range : Lsp.Range
|
||
/-- Syntax of this reference. -/
|
||
stx : Syntax
|
||
/-- `ContextInfo` at the point of elaboration of this reference. -/
|
||
ci : ContextInfo
|
||
/-- Additional `InfoTree` information for this reference. -/
|
||
info : Info
|
||
/-- Whether this reference declares `ident`. -/
|
||
isBinder : Bool
|
||
|
||
/-- Definition and usages of an identifier within a single module. -/
|
||
structure RefInfo where
|
||
/--
|
||
Definition `Reference` of the identifier.
|
||
Is equal to `none` if e.g. the definition is outside of the module where this `RefInfo` is used.
|
||
-/
|
||
definition : Option Reference
|
||
/-- All usage `Reference`s of the identifier in a single module. -/
|
||
usages : Array Reference
|
||
|
||
namespace RefInfo
|
||
|
||
/-- No definition, no usages. -/
|
||
def empty : RefInfo := ⟨none, #[]⟩
|
||
|
||
/--
|
||
Adds `ref` to `i`.
|
||
If `i` has no `definition` and `ref` is a declaration, it becomes the `definition`.
|
||
If `i` already has a `definition` and `ref` is also a declaration, it is not added to `i`.
|
||
Otherwise, `ref` is added to `i.usages`.
|
||
-/
|
||
def addRef (i : RefInfo) (ref : Reference) : RefInfo :=
|
||
match i, ref with
|
||
| { definition := none, .. }, { isBinder := true, .. } =>
|
||
{ i with definition := ref }
|
||
| { definition := some .., .. }, { isBinder := true, .. } =>
|
||
i
|
||
| { usages, .. }, { isBinder := false, .. } =>
|
||
{ i with usages := usages.push ref }
|
||
|
||
/-- Converts `i` to a JSON-serializable `Lsp.RefInfo`. -/
|
||
def toLspRefInfo (i : RefInfo) : BaseIO Lsp.RefInfo := do
|
||
let refToRefInfoLocation (ref : Reference) : BaseIO RefInfo.Location := do
|
||
let parentDeclName? := ref.ci.parentDecl?
|
||
let .ok parentDeclRanges? ← EIO.toBaseIO <| ref.ci.runCoreM do
|
||
let some parentDeclName := parentDeclName?
|
||
| return none
|
||
findDeclarationRanges? parentDeclName
|
||
-- we only use `CoreM` to get access to a `MonadEnv`, but these are currently all `IO`
|
||
| unreachable!
|
||
return {
|
||
range := ref.range
|
||
parentDecl? := do
|
||
let parentDeclName ← parentDeclName?
|
||
let parentDeclRange := (← parentDeclRanges?).range.toLspRange
|
||
let parentDeclSelectionRange := (← parentDeclRanges?).selectionRange.toLspRange
|
||
return ⟨parentDeclName, parentDeclRange, parentDeclSelectionRange⟩
|
||
}
|
||
let definition? ← i.definition.mapM refToRefInfoLocation
|
||
let usages ← i.usages.mapM refToRefInfoLocation
|
||
return {
|
||
definition? := definition?
|
||
usages := usages
|
||
}
|
||
|
||
end RefInfo
|
||
|
||
/-- All references from within a module for all identifiers used in a single module. -/
|
||
def ModuleRefs := HashMap RefIdent RefInfo
|
||
|
||
namespace ModuleRefs
|
||
|
||
/-- Adds `ref` to the `RefInfo` corresponding to `ref.ident` in `self`. See `RefInfo.addRef`. -/
|
||
def addRef (self : ModuleRefs) (ref : Reference) : ModuleRefs :=
|
||
let refInfo := self.findD ref.ident RefInfo.empty
|
||
self.insert ref.ident (refInfo.addRef ref)
|
||
|
||
/-- Converts `refs` to a JSON-serializable `Lsp.ModuleRefs`. -/
|
||
def toLspModuleRefs (refs : ModuleRefs) : BaseIO Lsp.ModuleRefs := do
|
||
let refs ← refs.toList.mapM fun (k, v) => do
|
||
return (k, ← v.toLspRefInfo)
|
||
return HashMap.ofList refs
|
||
|
||
end ModuleRefs
|
||
|
||
end Lean.Server
|
||
|
||
namespace Lean.Lsp.RefInfo
|
||
open Server
|
||
|
||
/-- No definition, no usages -/
|
||
def empty : RefInfo := ⟨ none, #[] ⟩
|
||
|
||
/-- Combines the `usages` of `a` and `b` and prefers the `definition?` of `b` over that of `a`. -/
|
||
def merge (a : RefInfo) (b : RefInfo) : RefInfo where
|
||
definition? := b.definition?.orElse fun _ => a.definition?
|
||
usages := a.usages.append b.usages
|
||
|
||
/--
|
||
Finds the first definition or usage in `self` where the `RefInfo.Location.range`
|
||
contains the given `pos`. The `includeStop` parameter can be used to toggle between closed-interval
|
||
and half-open-interval behavior for the range. Closed-interval behavior matches the expectation of
|
||
VSCode when selecting an identifier at a cursor position (see #767).
|
||
-/
|
||
def findReferenceLocation?
|
||
(self : RefInfo)
|
||
(pos : Lsp.Position)
|
||
(includeStop : Bool := false)
|
||
: Option Location := do
|
||
if let some loc := self.definition? then
|
||
if contains loc.range pos then
|
||
return loc
|
||
for loc in self.usages do
|
||
if contains loc.range pos then
|
||
return loc
|
||
none
|
||
where
|
||
contains (range : Lsp.Range) (pos : Lsp.Position) : Bool :=
|
||
range.start <= pos && (if includeStop then pos <= range.end else pos < range.end)
|
||
|
||
/-- Checks whether any of the ranges in `self.definition?` or `self.usages` contains `pos`. -/
|
||
def contains (self : RefInfo) (pos : Lsp.Position) (includeStop := false) : Bool := Id.run do
|
||
(self.findReferenceLocation? pos includeStop).isSome
|
||
|
||
end Lean.Lsp.RefInfo
|
||
|
||
namespace Lean.Lsp.ModuleRefs
|
||
open Server
|
||
|
||
/--
|
||
Find all identifiers in `self` with a reference in this module that contains `pos` in its range.
|
||
-/
|
||
def findAt
|
||
(self : ModuleRefs)
|
||
(pos : Lsp.Position)
|
||
(includeStop := false)
|
||
: Array RefIdent := Id.run do
|
||
let mut result := #[]
|
||
for (ident, info) in self.toArray do
|
||
if info.contains pos includeStop then
|
||
result := result.push ident
|
||
result
|
||
|
||
/-- Finds the first range in `self` that contains `pos`. -/
|
||
def findRange? (self : ModuleRefs) (pos : Lsp.Position) (includeStop := false) : Option Range := do
|
||
for (_, info) in self.toList do
|
||
if let some loc := info.findReferenceLocation? pos includeStop then
|
||
return loc.range
|
||
none
|
||
|
||
end Lean.Lsp.ModuleRefs
|
||
|
||
namespace Lean.Server
|
||
open IO
|
||
open Lsp
|
||
open Elab
|
||
|
||
/-- Content of individual `.ilean` files -/
|
||
structure Ilean where
|
||
/-- Version number of the ilean format. -/
|
||
version : Nat := 3
|
||
/-- Name of the module that this ilean data has been collected for. -/
|
||
module : Name
|
||
/-- All references of this module. -/
|
||
references : Lsp.ModuleRefs
|
||
deriving FromJson, ToJson
|
||
|
||
namespace Ilean
|
||
|
||
/-- Reads and parses the .ilean file at `path`. -/
|
||
def load (path : System.FilePath) : IO Ilean := do
|
||
let content ← FS.readFile path
|
||
match Json.parse content >>= fromJson? with
|
||
| Except.ok ilean => pure ilean
|
||
| Except.error msg => throwServerError s!"Failed to load ilean at {path}: {msg}"
|
||
|
||
end Ilean
|
||
/-! # Collecting and deduplicating definitions and usages -/
|
||
|
||
/--
|
||
Determines the `RefIdent` for the `Info` `i` of an identifier in `module` and
|
||
whether it is a declaration.
|
||
-/
|
||
def identOf (module : Name) (i : Info) : Option (RefIdent × Bool) :=
|
||
match i with
|
||
| Info.ofTermInfo ti => match ti.expr with
|
||
| Expr.const n .. => some (RefIdent.const module n, ti.isBinder)
|
||
| Expr.fvar id => some (RefIdent.fvar module id, ti.isBinder)
|
||
| _ => none
|
||
| Info.ofFieldInfo fi => some (RefIdent.const module fi.projName, false)
|
||
| Info.ofOptionInfo oi => some (RefIdent.const module oi.declName, false)
|
||
| _ => none
|
||
|
||
/-- Finds all references in `trees`. -/
|
||
def findReferences (text : FileMap) (trees : Array InfoTree) : Array Reference :=
|
||
Id.run <| StateT.run' (s := #[]) do
|
||
for tree in trees do
|
||
tree.visitM' (postNode := fun ci info _ => do
|
||
let mod := ci.env.header.mainModule
|
||
let some (ident, isBinder) := identOf mod info
|
||
| return
|
||
let some range := info.range?
|
||
| return
|
||
if info.stx.getHeadInfo matches .original .. then -- we are not interested in canonical syntax here
|
||
modify (·.push { ident, range := range.toLspRange text, stx := info.stx, ci, info, isBinder }))
|
||
get
|
||
|
||
/--
|
||
There are several different identifiers that should be considered equal for the purpose of finding
|
||
all references of an identifier:
|
||
- `FVarId`s of a function parameter in the function's signature and body
|
||
- Chains of helper definitions like those created for do-reassignment `x := e`
|
||
- Overlapping definitions like those defined by `where` declarations that define both an FVar
|
||
(for local usage) and a constant (for non-local usage)
|
||
- Identifiers connected by `FVarAliasInfo` such as variables before and after `match` generalization
|
||
|
||
In the first three cases that are not explicitly denoted as aliases with an `FVarAliasInfo`, the
|
||
corresponding `Reference`s have the exact same range.
|
||
This function finds all definitions that have the exact same range as another definition or usage
|
||
and collapses them into a single identifier. It also collapses identifiers connected by
|
||
an `FVarAliasInfo`.
|
||
When collapsing identifiers, it prefers using a `RefIdent.const name` over a `RefIdent.fvar id` for
|
||
all identifiers that are being collapsed into one.
|
||
-/
|
||
partial def combineIdents (trees : Array InfoTree) (refs : Array Reference) : Array Reference := Id.run do
|
||
-- Deduplicate definitions based on their exact range
|
||
let mut posMap : HashMap Lsp.Range RefIdent := HashMap.empty
|
||
for ref in refs do
|
||
if let { ident, range, isBinder := true, .. } := ref then
|
||
posMap := posMap.insert range ident
|
||
|
||
let idMap := useConstRepresentatives <| buildIdMap posMap
|
||
|
||
let mut refs' := #[]
|
||
for ref in refs do
|
||
let id := ref.ident
|
||
if idMap.contains id then
|
||
refs' := refs'.push { ref with ident := findCanonicalRepresentative idMap id, aliases := #[id] }
|
||
else if !idMap.contains id then
|
||
refs' := refs'.push ref
|
||
refs'
|
||
where
|
||
useConstRepresentatives (idMap : HashMap RefIdent RefIdent)
|
||
: HashMap RefIdent RefIdent := Id.run do
|
||
let insertIntoClass classesById id :=
|
||
let representative := findCanonicalRepresentative idMap id
|
||
let «class» := classesById.findD representative ∅
|
||
let classesById := classesById.erase representative -- make `«class»` referentially unique
|
||
let «class» := «class».insert id
|
||
classesById.insert representative «class»
|
||
|
||
-- collect equivalence classes
|
||
let mut classesById : HashMap RefIdent (HashSet RefIdent) := ∅
|
||
for ⟨id, baseId⟩ in idMap.toArray do
|
||
classesById := insertIntoClass classesById id
|
||
classesById := insertIntoClass classesById baseId
|
||
|
||
let mut r := ∅
|
||
for ⟨currentRepresentative, «class»⟩ in classesById.toArray do
|
||
-- find best representative (ideally a const if available)
|
||
let mut bestRepresentative := currentRepresentative
|
||
for id in «class» do
|
||
bestRepresentative :=
|
||
match bestRepresentative, id with
|
||
| .fvar ma a, .fvar .. => .fvar ma a
|
||
| .fvar .., .const mb b => .const mb b
|
||
| .const ma a, .fvar .. => .const ma a
|
||
| .const ma a, .const .. => .const ma a
|
||
|
||
-- compress `idMap` so that all identifiers in a class point to the best representative
|
||
for id in «class» do
|
||
if id != bestRepresentative then
|
||
r := r.insert id bestRepresentative
|
||
return r
|
||
|
||
findCanonicalRepresentative (idMap : HashMap RefIdent RefIdent) (id : RefIdent) : RefIdent := Id.run do
|
||
let mut canonicalRepresentative := id
|
||
while idMap.contains canonicalRepresentative do
|
||
canonicalRepresentative := idMap.find! canonicalRepresentative
|
||
return canonicalRepresentative
|
||
|
||
buildIdMap posMap := Id.run <| StateT.run' (s := HashMap.empty) do
|
||
-- map fvar defs to overlapping fvar defs/uses
|
||
for ref in refs do
|
||
let baseId := ref.ident
|
||
if let some id := posMap.find? ref.range then
|
||
insertIdMap id baseId
|
||
|
||
-- apply `FVarAliasInfo`
|
||
trees.forM (·.visitM' (postNode := fun ci info _ => do
|
||
if let .ofFVarAliasInfo ai := info then
|
||
-- FVars can only be aliases of FVars of the same file / module
|
||
let mod := ci.env.header.mainModule
|
||
insertIdMap (.fvar mod ai.id) (.fvar mod ai.baseId)))
|
||
|
||
get
|
||
|
||
-- poor man's union-find; see also `findCanonicalBinder`
|
||
insertIdMap id baseId := do
|
||
let idMap ← get
|
||
let id := findCanonicalRepresentative idMap id
|
||
let baseId := findCanonicalRepresentative idMap baseId
|
||
if baseId != id then
|
||
modify (·.insert id baseId)
|
||
|
||
/--
|
||
Groups `refs` by identifier and range s.t. references with the same identifier and range
|
||
are added to the `aliases` of the representative of the group.
|
||
Yields to separate groups for declaration and usages if `allowSimultaneousBinderUse` is set.
|
||
-/
|
||
def dedupReferences (refs : Array Reference) (allowSimultaneousBinderUse := false) : Array Reference := Id.run do
|
||
let mut refsByIdAndRange : HashMap (RefIdent × Option Bool × Lsp.Range) Reference := HashMap.empty
|
||
for ref in refs do
|
||
let isBinder := if allowSimultaneousBinderUse then some ref.isBinder else none
|
||
let key := (ref.ident, isBinder, ref.range)
|
||
refsByIdAndRange := match refsByIdAndRange[key] with
|
||
| some ref' => refsByIdAndRange.insert key { ref' with aliases := ref'.aliases ++ ref.aliases }
|
||
| none => refsByIdAndRange.insert key ref
|
||
|
||
let dedupedRefs := refsByIdAndRange.fold (init := #[]) fun refs _ ref => refs.push ref
|
||
return dedupedRefs.qsort (·.range < ·.range)
|
||
|
||
/--
|
||
Finds all references in `trees` and deduplicates the result.
|
||
See `dedupReferences` and `combineIdents`.
|
||
-/
|
||
def findModuleRefs (text : FileMap) (trees : Array InfoTree) (localVars : Bool := true)
|
||
(allowSimultaneousBinderUse := false) : ModuleRefs := Id.run do
|
||
let mut refs :=
|
||
dedupReferences (allowSimultaneousBinderUse := allowSimultaneousBinderUse) <|
|
||
combineIdents trees <|
|
||
findReferences text trees
|
||
if !localVars then
|
||
refs := refs.filter fun
|
||
| { ident := RefIdent.fvar .., .. } => false
|
||
| _ => true
|
||
refs.foldl (init := HashMap.empty) fun m ref => m.addRef ref
|
||
|
||
/-! # Collecting and maintaining reference info from different sources -/
|
||
|
||
/-- References from ilean files and current ilean information from file workers. -/
|
||
structure References where
|
||
/-- References loaded from ilean files -/
|
||
ileans : HashMap Name (System.FilePath × Lsp.ModuleRefs)
|
||
/-- References from workers, overriding the corresponding ilean files -/
|
||
workers : HashMap Name (Nat × Lsp.ModuleRefs)
|
||
|
||
namespace References
|
||
|
||
/-- No ilean files, no information from workers. -/
|
||
def empty : References := { ileans := HashMap.empty, workers := HashMap.empty }
|
||
|
||
/-- Adds the contents of an ilean file `ilean` at `path` to `self`. -/
|
||
def addIlean (self : References) (path : System.FilePath) (ilean : Ilean) : References :=
|
||
{ self with ileans := self.ileans.insert ilean.module (path, ilean.references) }
|
||
|
||
/-- Removes the ilean file data at `path` from `self`. -/
|
||
def removeIlean (self : References) (path : System.FilePath) : References :=
|
||
let namesToRemove := self.ileans.toList.filter (fun (_, p, _) => p == path)
|
||
|>.map (fun (n, _, _) => n)
|
||
namesToRemove.foldl (init := self) fun self name =>
|
||
{ self with ileans := self.ileans.erase name }
|
||
|
||
/--
|
||
Updates the worker references in `self` with the `refs` of the worker managing the module `name`.
|
||
Replaces the current references with `refs` if `version` is newer than the current version managed
|
||
in `refs` and otherwise merges the reference data if `version` is equal to the current version.
|
||
-/
|
||
def updateWorkerRefs (self : References) (name : Name) (version : Nat) (refs : Lsp.ModuleRefs) : References := Id.run do
|
||
if let some (currVersion, _) := self.workers.find? name then
|
||
if version > currVersion then
|
||
return { self with workers := self.workers.insert name (version, refs) }
|
||
if version == currVersion then
|
||
let current := self.workers.findD name (version, HashMap.empty)
|
||
let merged := refs.fold (init := current.snd) fun m ident info =>
|
||
m.findD ident Lsp.RefInfo.empty |>.merge info |> m.insert ident
|
||
return { self with workers := self.workers.insert name (version, merged) }
|
||
return self
|
||
|
||
/--
|
||
Replaces the worker references in `self` with the `refs` of the worker managing the module `name`
|
||
if `version` is newer than the current version managed in `refs`.
|
||
-/
|
||
def finalizeWorkerRefs (self : References) (name : Name) (version : Nat) (refs : Lsp.ModuleRefs) : References := Id.run do
|
||
if let some (currVersion, _) := self.workers.find? name then
|
||
if version < currVersion then
|
||
return self
|
||
return { self with workers := self.workers.insert name (version, refs) }
|
||
|
||
/-- Erases all worker references in `self` for the worker managing `name`. -/
|
||
def removeWorkerRefs (self : References) (name : Name) : References :=
|
||
{ self with workers := self.workers.erase name }
|
||
|
||
/-- Yields a map from all modules to all of their references. -/
|
||
def allRefs (self : References) : HashMap Name Lsp.ModuleRefs :=
|
||
let ileanRefs := self.ileans.toArray.foldl (init := HashMap.empty) fun m (name, _, refs) => m.insert name refs
|
||
self.workers.toArray.foldl (init := ileanRefs) fun m (name, _, refs) => m.insert name refs
|
||
|
||
/--
|
||
Yields all references in `self` for `ident`, as well as the `DocumentUri` that each
|
||
reference occurs in.
|
||
-/
|
||
def allRefsFor
|
||
(self : References)
|
||
(srcSearchPath : SearchPath)
|
||
(ident : RefIdent)
|
||
: IO (Array (DocumentUri × Lsp.RefInfo)) := do
|
||
let refsToCheck := match ident with
|
||
| RefIdent.const .. => self.allRefs.toArray
|
||
| RefIdent.fvar identModule .. =>
|
||
match self.allRefs.find? identModule with
|
||
| none => #[]
|
||
| some refs => #[(identModule, refs)]
|
||
let mut result := #[]
|
||
for (module, refs) in refsToCheck do
|
||
let some info := refs.find? ident
|
||
| continue
|
||
let some path ← srcSearchPath.findModuleWithExt "lean" module
|
||
| continue
|
||
-- Resolve symlinks (such as `src` in the build dir) so that files are
|
||
-- opened in the right folder
|
||
let uri := System.Uri.pathToUri <| ← IO.FS.realPath path
|
||
result := result.push (uri, info)
|
||
return result
|
||
|
||
/-- Yields all references in `module` at `pos`. -/
|
||
def findAt (self : References) (module : Name) (pos : Lsp.Position) (includeStop := false) : Array RefIdent := Id.run do
|
||
if let some refs := self.allRefs.find? module then
|
||
return refs.findAt pos includeStop
|
||
#[]
|
||
|
||
/-- Yields the first reference in `module` at `pos`. -/
|
||
def findRange? (self : References) (module : Name) (pos : Lsp.Position) (includeStop := false) : Option Range := do
|
||
let refs ← self.allRefs.find? module
|
||
refs.findRange? pos includeStop
|
||
|
||
/-- Location and parent declaration of a reference. -/
|
||
structure DocumentRefInfo where
|
||
/-- Location of the reference. -/
|
||
location : Location
|
||
/-- Parent declaration of the reference. -/
|
||
parentInfo? : Option RefInfo.ParentDecl
|
||
|
||
/-- Yields locations and parent declaration for all references referring to `ident`. -/
|
||
def referringTo
|
||
(self : References)
|
||
(srcSearchPath : SearchPath)
|
||
(ident : RefIdent)
|
||
(includeDefinition : Bool := true)
|
||
: IO (Array DocumentRefInfo) := do
|
||
let mut result := #[]
|
||
for (moduleUri, info) in ← self.allRefsFor srcSearchPath ident do
|
||
if includeDefinition then
|
||
if let some ⟨range, parentDeclInfo?⟩ := info.definition? then
|
||
result := result.push ⟨⟨moduleUri, range⟩, parentDeclInfo?⟩
|
||
for ⟨range, parentDeclInfo?⟩ in info.usages do
|
||
result := result.push ⟨⟨moduleUri, range⟩, parentDeclInfo?⟩
|
||
return result
|
||
|
||
/-- Yields the definition location of `ident`. -/
|
||
def definitionOf?
|
||
(self : References)
|
||
(ident : RefIdent)
|
||
(srcSearchPath : SearchPath)
|
||
: IO (Option DocumentRefInfo) := do
|
||
for (moduleUri, info) in ← self.allRefsFor srcSearchPath ident do
|
||
let some ⟨definitionRange, definitionParentDeclInfo?⟩ := info.definition?
|
||
| continue
|
||
return some ⟨⟨moduleUri, definitionRange⟩, definitionParentDeclInfo?⟩
|
||
return none
|
||
|
||
/-- Yields all definitions matching the given `filter`. -/
|
||
def definitionsMatching
|
||
(self : References)
|
||
(srcSearchPath : SearchPath)
|
||
(filter : Name → Option α)
|
||
(maxAmount? : Option Nat := none) : IO $ Array (α × Location) := do
|
||
let mut result := #[]
|
||
for (module, refs) in self.allRefs.toList do
|
||
let some path ← srcSearchPath.findModuleWithExt "lean" module
|
||
| continue
|
||
let uri := System.Uri.pathToUri <| ← IO.FS.realPath path
|
||
for (ident, info) in refs.toList do
|
||
let (RefIdent.const _ name, some ⟨definitionRange, _⟩) := (ident, info.definition?)
|
||
| continue
|
||
let some a := filter name
|
||
| continue
|
||
result := result.push (a, ⟨uri, definitionRange⟩)
|
||
if let some maxAmount := maxAmount? then
|
||
if result.size >= maxAmount then
|
||
return result
|
||
return result
|
||
|
||
end References
|
||
|
||
end Lean.Server
|