lean4-htt/src/Lean/Server/FileWorker/RequestHandling.lean
Marc Huisinga 95aee36fab
feat: inlay hints for auto-implicits (#6768)
This PR adds preliminary support for inlay hints, as well as support for
inlay hints that denote the auto-implicits of a function. Hovering over
an auto-implicit displays its type and double-clicking the auto-implicit
inserts it into the text document.

![Inlay hints for
auto-implicits](https://github.com/user-attachments/assets/fb204c42-5997-4f10-9617-c65f1042d732)

This PR is an extension of #3910.

### Known issues

- In VS Code, when inserting an inlay hint, the inlay hint may linger
for a couple of seconds before it disappears. This is a defect of the VS
Code implementation of inlay hints and cannot adequately be resolved by
us.
- When making a change to the document, it may take a couple of seconds
until the inlay hints respond to the change. This is deliberate and
intended to reduce the amount of inlay hint flickering while typing. VS
Code has a mechanism of its own for this, but in my experience it is
still far too sensitive without additional latency.
- Inserting an auto-implicit inlay hint that depends on an auto-implicit
meta-variable causes a "failed to infer binder type" error. We can't
display these meta-variables in the inlay hint because they don't have a
user-displayable name, so it is not clear how to resolve this problem.
- Inlay hints are currently always resolved eagerly, i.e. we do not
support the `textDocument/inlayHint/resolve` request yet. Implementing
support for this request is future work.

### Other changes
- Axioms did not support auto-implicits due to an oversight in the
implementation. This PR ensures they do.
- In order to reduce the amount of inlay hint flickering when making a
change to the document, the language server serves old inlay hints for
parts of the file that have not been processed yet. This requires LSP
request handler state (that sometimes must be invalidated on
`textDocument/didChange`), so this PR introduces the notion of a
stateful LSP request handler.
- The partial response mechanism that we use for semantic tokens, where
we simulate incremental LSP responses by periodically emitting refresh
requests to the client, is generalized to accommodate both inlay hints
and semantic tokens. Additionally, it is made more robust to ensure that
we never emit refresh requests while a corresponding request is in
flight, which causes VS Code to discard the respond of the request, as
well as to ensure that we keep prompting VS Code to send another request
if it spuriously decides not to respond to one of our refresh requests.
- The synthetic identifier of an `example` had the full declaration as
its (non-canonical synthetic) range. Since we need a reasonable position
for the identifier to insert an inlay hint for the auto-implicits of an
`example`, we change the (canonical synthetic) range of the synthetic
identifier to that of the `example` keyword.
- The semantic highlighting request handling is moved to a separate
file.

### Breaking changes
- The semantic highlighting request handler is not a pure request
handler anymore, but a stateful one. Notably, this means that clients
that extend the semantic highlighting of the Lean language server with
the `chainLspRequestHandler` function must now use the
`chainStatefulLspRequestHandler` function instead.
2025-02-04 17:36:49 +00:00

594 lines
25 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) 2021 Wojciech Nawrocki. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Wojciech Nawrocki, Marc Huisinga
-/
prelude
import Lean.Server.FileWorker.InlayHints
import Lean.Server.FileWorker.SemanticHighlighting
import Lean.Server.Completion
import Lean.Server.References
import Lean.Widget.Diff
namespace Lean.Server.FileWorker
open Lsp
open RequestM
open Snapshots
open Lean.Parser.Tactic.Doc (alternativeOfTactic getTacticExtensionString)
def findCompletionCmdDataAtPos
(doc : EditableDocument)
(pos : String.Pos)
: Task (Option (Syntax × Elab.InfoTree)) :=
findCmdDataAtPos doc (pos := pos) fun s => Id.run do
let some tailPos := s.stx.getTailPos?
| return false
return pos.byteIdx <= tailPos.byteIdx + s.stx.getTrailingSize
def handleCompletion (p : CompletionParams)
: RequestM (RequestTask CompletionList) := do
let doc ← readDoc
let text := doc.meta.text
let pos := text.lspPosToUtf8Pos p.position
let caps := (← read).initParams.capabilities
mapTask (findCompletionCmdDataAtPos doc pos) fun cmdData? => do
let some (cmdStx, infoTree) := cmdData?
-- work around https://github.com/microsoft/vscode/issues/155738
| return {
items := #[{label := "-", data? := toJson { params := p : Lean.Lsp.CompletionItemData }}],
isIncomplete := true
}
Completion.find? p doc.meta.text pos cmdStx infoTree caps
/--
Handles `completionItem/resolve` requests that are sent by the client after the user selects
a completion item that was provided by `textDocument/completion`. Resolving the item fills the
`detail?` field of the item with the pretty-printed type.
This control flow is necessary because pretty-printing the type for every single completion item
(even those never selected by the user) is inefficient.
-/
def handleCompletionItemResolve (item : CompletionItem)
: RequestM (RequestTask CompletionItem) := do
let doc ← readDoc
let text := doc.meta.text
let some (data : ResolvableCompletionItemData) := item.data?.bind fun data => (fromJson? data).toOption
| return .pure item
let some id := data.id?
| return .pure item
let pos := text.lspPosToUtf8Pos data.params.position
mapTask (findCompletionCmdDataAtPos doc pos) fun cmdData? => do
let some (cmdStx, infoTree) := cmdData?
| return item
Completion.resolveCompletionItem? text pos cmdStx infoTree item id data.cPos
open Elab in
def handleHover (p : HoverParams)
: RequestM (RequestTask (Option Hover)) := do
let doc ← readDoc
let text := doc.meta.text
let mkHover (s : String) (r : String.Range) : Hover := {
contents := {
kind := MarkupKind.markdown
value := s
}
range? := r.toLspRange text
}
let hoverPos := text.lspPosToUtf8Pos p.position
withWaitFindSnap doc (fun s => s.endPos > hoverPos)
(notFoundX := pure none) fun snap => do
-- try to find parser docstring from syntax tree
let stack? := snap.stx.findStack? (·.getRange?.any (·.contains hoverPos))
let stxDoc? ← match stack? with
| some stack => stack.findSomeM? fun (stx, _) => do
let .node _ kind _ := stx | pure none
let docStr ← findDocString? snap.env kind
return docStr.map (·, stx.getRange?.get!)
| none => pure none
-- now try info tree
if let some ictx := snap.infoTree.hoverableInfoAt? hoverPos then
if let some range := ictx.info.range? then
-- prefer info tree if at least as specific as parser docstring
if stxDoc?.all fun (_, stxRange) => stxRange.includes range then
if let some hoverFmt ← ictx.info.fmtHover? ictx.ctx then
return mkHover (toString hoverFmt.fmt) range
if let some (doc, range) := stxDoc? then
return mkHover doc range
return none
open Elab GoToKind in
def locationLinksOfInfo (kind : GoToKind) (ictx : InfoWithCtx)
(infoTree? : Option InfoTree := none) : RequestM (Array LocationLink) := do
let rc ← read
let doc ← readDoc
let text := doc.meta.text
let locationLinksFromDecl (i : Elab.Info) (n : Name) :=
locationLinksFromDecl rc.srcSearchPath doc.meta.uri n <| (·.toLspRange text) <$> i.range?
let locationLinksFromBinder (i : Elab.Info) (id : FVarId) := do
if let some i' := infoTree? >>= InfoTree.findInfo? fun
| Info.ofTermInfo { isBinder := true, expr := Expr.fvar id' .., .. } => id' == id
| _ => false then
if let some r := i'.range? then
let r := r.toLspRange text
let ll : LocationLink := {
originSelectionRange? := (·.toLspRange text) <$> i.range?
targetUri := doc.meta.uri
targetRange := r
targetSelectionRange := r
}
return #[ll]
return #[]
let locationLinksFromImport (i : Elab.Info) := do
let name := i.stx[2].getId
if let some modUri ← documentUriFromModule rc.srcSearchPath name then
let range := { start := ⟨0, 0⟩, «end» := ⟨0, 0⟩ : Range }
let ll : LocationLink := {
originSelectionRange? := (·.toLspRange text) <$> i.stx[2].getRange? (canonicalOnly := true)
targetUri := modUri
targetRange := range
targetSelectionRange := range
}
return #[ll]
return #[]
let i := ictx.info
let ci := ictx.ctx
let children := ictx.children
let locationLinksDefault : RequestM (Array LocationLink) := do
-- If other go-tos fail, we try to show the elaborator or parser
if let some ei := i.toElabInfo? then
if kind == declaration && ci.env.contains ei.stx.getKind then
return ← ci.runMetaM i.lctx <| locationLinksFromDecl i ei.stx.getKind
if kind == definition && ci.env.contains ei.elaborator then
return ← ci.runMetaM i.lctx <| locationLinksFromDecl i ei.elaborator
return #[]
let locationLinksFromTermInfo (ti : TermInfo) : RequestM (Array LocationLink) := do
let mut expr := ti.expr
if kind == type then
expr ← ci.runMetaM i.lctx do
return Expr.getAppFn (← instantiateMVars (← Meta.inferType expr))
match expr.consumeMData with
| Expr.const n .. => return ← ci.runMetaM i.lctx <| locationLinksFromDecl i n
| Expr.fvar id .. => return ← ci.runMetaM i.lctx <| locationLinksFromBinder i id
| _ => pure ()
-- Check whether this `TermInfo` node is directly responsible for its `.expr`.
-- This is the case iff all of its children represent strictly smaller subexpressions;
-- it is sufficient to check this of all direct children of this node (and that its elaborator didn't expand it as a macro)
let isExprGenerator := children.all fun
| .node (Info.ofTermInfo info) _ => info.expr != expr
| .node (Info.ofMacroExpansionInfo _) _ => false
| _ => true
-- don't go-to-instance if this `TermInfo` didn't directly generate its `.expr`
if kind != declaration && isExprGenerator then
-- go-to-definition on a projection application of a typeclass
-- should return all instances generated by TC
expr ← ci.runMetaM i.lctx do instantiateMVars expr
if let .const n _ := expr.getAppFn.consumeMData then
-- also include constant along with instance results
let mut results ← ci.runMetaM i.lctx <| locationLinksFromDecl i n
if let some info := ci.env.getProjectionFnInfo? n then
let mut elaborators := default
if let some ei := i.toElabInfo? then do
-- also include elaborator along with instance results, as this wouldn't be accessible otherwise
if ei.elaborator != `Delab -- prevent an error if this `TermInfo` came from the infoview
&& ei.elaborator != `Lean.Elab.Term.elabApp && ei.elaborator != `Lean.Elab.Term.elabIdent -- don't include trivial elaborators
then do
elaborators ← ci.runMetaM i.lctx <| locationLinksFromDecl i ei.elaborator
let instIdx := info.numParams
let appArgs := expr.getAppArgs
let rec extractInstances : Expr → RequestM (Array Name)
| .const declName _ => do
if ← ci.runMetaM i.lctx do Lean.Meta.isInstance declName then pure #[declName] else pure #[]
| .app fn arg => do pure $ (← extractInstances fn).append (← extractInstances arg)
| .mdata _ e => extractInstances e
| _ => pure #[]
if let some instArg := appArgs.get? instIdx then
for inst in (← extractInstances instArg) do
results := results.append (← ci.runMetaM i.lctx <| locationLinksFromDecl i inst)
results := results.append elaborators -- put elaborators at the end of the results
return results
locationLinksDefault
match i with
| .ofTermInfo ti =>
return ← locationLinksFromTermInfo ti
| .ofDelabTermInfo { toTermInfo := ti, location?, .. } =>
if let some location := location? then
if let some targetUri ← documentUriFromModule rc.srcSearchPath location.module then
let range := location.range.toLspRange
let result : LocationLink := {
targetUri, targetRange := range, targetSelectionRange := range,
originSelectionRange? := (·.toLspRange text) <$> i.range?
}
return #[result]
-- If we fail to find a DocumentUri, fall through and use the default method to at least try to have something to jump to.
return ← locationLinksFromTermInfo ti
| .ofFieldInfo fi =>
if kind == type then
let expr ← ci.runMetaM i.lctx do
instantiateMVars (← Meta.inferType fi.val)
if let some n := expr.getAppFn.constName? then
return ← ci.runMetaM i.lctx <| locationLinksFromDecl i n
else
return ← ci.runMetaM i.lctx <| locationLinksFromDecl i fi.projName
| .ofOptionInfo oi =>
return ← ci.runMetaM i.lctx <| locationLinksFromDecl i oi.declName
| .ofCommandInfo ⟨`import, _⟩ =>
if kind == definition || kind == declaration then
return ← ci.runMetaM i.lctx <| locationLinksFromImport i
| _ => pure ()
locationLinksDefault
open Elab GoToKind in
def handleDefinition (kind : GoToKind) (p : TextDocumentPositionParams)
: RequestM (RequestTask (Array LocationLink)) := do
let doc ← readDoc
let text := doc.meta.text
let hoverPos := text.lspPosToUtf8Pos p.position
withWaitFindSnap doc (fun s => s.endPos >= hoverPos)
(notFoundX := pure #[]) fun snap => do
if let some infoWithCtx := snap.infoTree.hoverableInfoAt? (omitIdentApps := true) (includeStop := true /- #767 -/) hoverPos then
locationLinksOfInfo kind infoWithCtx snap.infoTree
else return #[]
open RequestM in
def getInteractiveGoals (p : Lsp.PlainGoalParams) : RequestM (RequestTask (Option Widget.InteractiveGoals)) := do
let doc ← readDoc
let text := doc.meta.text
let hoverPos := text.lspPosToUtf8Pos p.position
mapTask (findInfoTreeAtPosWithTrailingWhitespace doc hoverPos) <| Option.bindM fun infoTree => do
let rs@(_ :: _) := infoTree.goalsAt? doc.meta.text hoverPos
| return none
let goals : List Widget.InteractiveGoals ← rs.mapM fun { ctxInfo := ci, tacticInfo := ti, useAfter := useAfter, .. } => do
let ciAfter := { ci with mctx := ti.mctxAfter }
let ci := if useAfter then ciAfter else { ci with mctx := ti.mctxBefore }
-- compute the interactive goals
let goals ← ci.runMetaM {} (do
let goals := List.toArray <| if useAfter then ti.goalsAfter else ti.goalsBefore
let goals ← goals.mapM Widget.goalToInteractive
return ⟨goals⟩
)
-- compute the goal diff
ciAfter.runMetaM {} (do
try
Widget.diffInteractiveGoals useAfter ti goals
catch _ =>
-- fail silently, since this is just a bonus feature
return goals
)
return some <| goals.foldl (· ++ ·) ∅
open Elab in
def handlePlainGoal (p : PlainGoalParams)
: RequestM (RequestTask (Option PlainGoal)) := do
let t ← getInteractiveGoals p
return t.map <| Except.map <| Option.map <| fun {goals, ..} =>
if goals.isEmpty then
{ goals := #[], rendered := "no goals" }
else
let goalStrs := goals.map (toString ·.pretty)
let goalBlocks := goalStrs.map fun goal => s!"```lean
{goal}
```"
let md := String.intercalate "\n---\n" goalBlocks.toList
{ goals := goalStrs, rendered := md }
def getInteractiveTermGoal (p : Lsp.PlainTermGoalParams)
: RequestM (RequestTask (Option Widget.InteractiveTermGoal)) := do
let doc ← readDoc
let text := doc.meta.text
let hoverPos := text.lspPosToUtf8Pos p.position
mapTask (findInfoTreeAtPosWithTrailingWhitespace doc hoverPos) <| Option.bindM fun infoTree => do
let some {ctx := ci, info := i@(Elab.Info.ofTermInfo ti), ..} := infoTree.termGoalAt? hoverPos
| return none
let ty ← ci.runMetaM i.lctx do
instantiateMVars <| ti.expectedType?.getD (← Meta.inferType ti.expr)
-- for binders, hide the last hypothesis (the binder itself)
let lctx' := if ti.isBinder then i.lctx.pop else i.lctx
let goal ← ci.runMetaM lctx' do
Widget.goalToInteractive (← Meta.mkFreshExprMVar ty).mvarId!
let range := if let some r := i.range? then r.toLspRange text else ⟨p.position, p.position⟩
return some { goal with range, term := ⟨ti⟩ }
def handlePlainTermGoal (p : PlainTermGoalParams)
: RequestM (RequestTask (Option PlainTermGoal)) := do
let t ← getInteractiveTermGoal p
return t.map <| Except.map <| Option.map fun goal =>
{ goal := toString goal.pretty
range := goal.range
}
partial def handleDocumentHighlight (p : DocumentHighlightParams)
: RequestM (RequestTask (Array DocumentHighlight)) := do
let doc ← readDoc
let text := doc.meta.text
let pos := text.lspPosToUtf8Pos p.position
let rec highlightReturn? (doRange? : Option Range) : Syntax → Option DocumentHighlight
| `(doElem|return%$i $e) => Id.run do
if let some range := i.getRange? then
if range.contains pos then
return some { range := doRange?.getD (range.toLspRange text), kind? := DocumentHighlightKind.text }
highlightReturn? doRange? e
| `(do%$i $elems) => highlightReturn? (i.getRange?.get!.toLspRange text) elems
| stx => stx.getArgs.findSome? (highlightReturn? doRange?)
let highlightRefs? (snaps : Array Snapshot) : IO (Option (Array DocumentHighlight)) := do
let trees := snaps.map (·.infoTree)
let refs : Lsp.ModuleRefs ← findModuleRefs text trees |>.toLspModuleRefs
let mut ranges := #[]
for ident in refs.findAt p.position (includeStop := true) do
if let some info := refs.get? ident then
if let some ⟨definitionRange, _⟩ := info.definition? then
ranges := ranges.push definitionRange
ranges := ranges.append <| info.usages.map (·.range)
if ranges.isEmpty then
return none
return some <| ranges.map ({ range := ·, kind? := DocumentHighlightKind.text })
withWaitFindSnap doc (fun s => s.endPos >= pos)
(notFoundX := pure #[]) fun snap => do
let (snaps, _) ← doc.cmdSnaps.getFinishedPrefix
if let some his ← highlightRefs? snaps.toArray then
return his
if let some hi := highlightReturn? none snap.stx then
return #[hi]
return #[]
structure NamespaceEntry where
/-- The list of the name components introduced by this namespace command,
in reverse order so that `end` will peel them off from the front. -/
name : List Name
stx : Syntax
selection : Syntax
prevSiblings : Array DocumentSymbol
def NamespaceEntry.finish (text : FileMap) (syms : Array DocumentSymbol) (endStx : Option Syntax) :
NamespaceEntry → Array DocumentSymbol
| { name, stx, selection, prevSiblings, .. } =>
-- we can assume that commands always have at least one position (see `parseCommand`)
let range := match endStx with
| some endStx => (mkNullNode #[stx, endStx]).getRange?.get!
| none => { stx.getRange?.get! with stop := text.source.endPos }
let name := name.foldr (fun x y => y ++ x) Name.anonymous
prevSiblings.push <| DocumentSymbol.mk {
-- anonymous sections are represented by `«»` name components
name := if name == `«» then "<section>" else name.toString
kind := .namespace
range := range.toLspRange text
selectionRange := selection.getRange?.getD range |>.toLspRange text
children? := syms
}
open Parser.Command in
partial def handleDocumentSymbol (_ : DocumentSymbolParams)
: RequestM (RequestTask DocumentSymbolResult) := do
let doc ← readDoc
-- bad: we have to wait on elaboration of the entire file before we can report document symbols
let t := doc.cmdSnaps.waitAll
mapTask t fun (snaps, _) => do
let mut stxs := snaps.map (·.stx)
return { syms := toDocumentSymbols doc.meta.text stxs #[] [] }
where
toDocumentSymbols (text : FileMap) (stxs : List Syntax)
(syms : Array DocumentSymbol) (stack : List NamespaceEntry) :
Array DocumentSymbol :=
match stxs with
| [] => stack.foldl (fun syms entry => entry.finish text syms none) syms
| stx::stxs => match stx with
| `(namespace $id) =>
let entry := { name := id.getId.componentsRev, stx, selection := id, prevSiblings := syms }
toDocumentSymbols text stxs #[] (entry :: stack)
| `(section $(id)?) =>
let name := id.map (·.getId.componentsRev) |>.getD [`«»]
let entry := { name, stx, selection := id.map (·.raw) |>.getD stx, prevSiblings := syms }
toDocumentSymbols text stxs #[] (entry :: stack)
| `(end $(id)?) =>
let rec popStack n syms
| [] => toDocumentSymbols text stxs syms []
| entry :: stack =>
if entry.name.length == n then
let syms := entry.finish text syms stx
toDocumentSymbols text stxs syms stack
else if entry.name.length > n then
let syms := { entry with name := entry.name.take n, prevSiblings := #[] }.finish text syms stx
toDocumentSymbols text stxs syms ({ entry with name := entry.name.drop n } :: stack)
else
let syms := entry.finish text syms stx
popStack (n - entry.name.length) syms stack
popStack (id.map (·.getId.getNumParts) |>.getD 1) syms stack
| _ => Id.run do
unless stx.isOfKind ``Lean.Parser.Command.declaration do
return toDocumentSymbols text stxs syms stack
if let some stxRange := stx.getRange? then
let (name, selection) := match stx with
| `($_:declModifiers $_:attrKind instance $[$np:namedPrio]? $[$id$[.{$ls,*}]?]? $sig:declSig $_) =>
((·.getId.toString) <$> id |>.getD s!"instance {sig.raw.reprint.getD ""}", id.map (·.raw) |>.getD sig)
| _ =>
match stx.getArg 1 |>.getArg 1 with
| `(declId|$id$[.{$ls,*}]?) => (id.raw.getId.toString, id)
| _ =>
let stx10 : Syntax := (stx.getArg 1).getArg 0 -- TODO: stx[1][0] times out
(stx10.isIdOrAtom?.getD "<unknown>", stx10)
if let some selRange := selection.getRange? then
let sym := DocumentSymbol.mk {
name := name
kind := SymbolKind.method
range := stxRange.toLspRange text
selectionRange := selRange.toLspRange text
}
return toDocumentSymbols text stxs (syms.push sym) stack
toDocumentSymbols text stxs syms stack
partial def handleFoldingRange (_ : FoldingRangeParams)
: RequestM (RequestTask (Array FoldingRange)) := do
let doc ← readDoc
let t := doc.cmdSnaps.waitAll
mapTask t fun (snaps, _) => do
let stxs := snaps.map (·.stx)
let (_, ranges) ← StateT.run (addRanges doc.meta.text [] stxs) #[]
return ranges
where
isImport stx := stx.isOfKind ``Lean.Parser.Module.header || stx.isOfKind ``Lean.Parser.Command.open
addRanges (text : FileMap) sections
| [] => do
if let (_, start)::rest := sections then
addRange text FoldingRangeKind.region start text.source.endPos
addRanges text rest []
| stx::stxs => match stx with
| `(namespace $id) =>
addRanges text ((id.getId.getNumParts, stx.getPos?)::sections) stxs
| `(section $(id)?) =>
addRanges text ((id.map (·.getId.getNumParts) |>.getD 1, stx.getPos?)::sections) stxs
| `(end $(id)?) => do
let rec popRanges n sections := do
if let (size, start)::rest := sections then
if size == n then
addRange text FoldingRangeKind.region start stx.getTailPos?
addRanges text rest stxs
else if size > n then
-- we don't add a range here because vscode doesn't like
-- multiple folding regions with the same start line
addRanges text ((size - n, start)::rest) stxs
else
addRange text FoldingRangeKind.region start stx.getTailPos?
popRanges (n - size) rest
else
addRanges text sections stxs
popRanges (id.map (·.getId.getNumParts) |>.getD 1) sections
| `(mutual $body* end) => do
addRangeFromSyntax text FoldingRangeKind.region stx
addRanges text [] body.raw.toList
addRanges text sections stxs
| _ => do
if isImport stx then
let (imports, stxs) := stxs.span isImport
let last := imports.getLastD stx
addRange text FoldingRangeKind.imports stx.getPos? last.getTailPos?
addRanges text sections stxs
else
addCommandRange text stx
addRanges text sections stxs
addCommandRange text stx :=
match stx.getKind with
| `Lean.Parser.Command.moduleDoc =>
addRangeFromSyntax text FoldingRangeKind.comment stx
| ``Lean.Parser.Command.declaration => do
-- When visiting a declaration, attempt to fold the doc comment
-- separately to the main definition.
-- We never fold other modifiers, such as annotations.
if let `($dm:declModifiers $decl) := stx then
if let some comment := dm.raw[0].getOptional? then
addRangeFromSyntax text FoldingRangeKind.comment comment
addRangeFromSyntax text FoldingRangeKind.region decl
else
addRangeFromSyntax text FoldingRangeKind.region stx
| _ =>
addRangeFromSyntax text FoldingRangeKind.region stx
addRangeFromSyntax (text : FileMap) kind stx := addRange text kind stx.getPos? stx.getTailPos?
addRange (text : FileMap) kind start? stop? := do
if let (some startP, some endP) := (start?, stop?) then
let startP := text.utf8PosToLspPos startP
let endP := text.utf8PosToLspPos endP
if startP.line != endP.line then
modify fun st => st.push
{ startLine := startP.line
endLine := endP.line
kind? := some kind }
partial def handleWaitForDiagnostics (p : WaitForDiagnosticsParams)
: RequestM (RequestTask WaitForDiagnostics) := do
let rec waitLoop : RequestM EditableDocument := do
let doc ← readDoc
if p.version ≤ doc.meta.version then
return doc
else
IO.sleep 50
waitLoop
let t ← RequestM.asTask waitLoop
RequestM.bindTask t fun doc? => do
let doc ← liftExcept doc?
return doc.reporter.map fun _ => pure WaitForDiagnostics.mk
builtin_initialize
registerLspRequestHandler
"textDocument/waitForDiagnostics"
WaitForDiagnosticsParams
WaitForDiagnostics
handleWaitForDiagnostics
registerLspRequestHandler
"textDocument/completion"
CompletionParams
CompletionList
handleCompletion
registerLspRequestHandler
"completionItem/resolve"
CompletionItem
CompletionItem
handleCompletionItemResolve
registerLspRequestHandler
"textDocument/hover"
HoverParams
(Option Hover)
handleHover
registerLspRequestHandler
"textDocument/declaration"
TextDocumentPositionParams
(Array LocationLink)
(handleDefinition GoToKind.declaration)
registerLspRequestHandler
"textDocument/definition"
TextDocumentPositionParams
(Array LocationLink)
(handleDefinition GoToKind.definition)
registerLspRequestHandler
"textDocument/typeDefinition"
TextDocumentPositionParams
(Array LocationLink)
(handleDefinition GoToKind.type)
registerLspRequestHandler
"textDocument/documentHighlight"
DocumentHighlightParams
DocumentHighlightResult
handleDocumentHighlight
registerLspRequestHandler
"textDocument/documentSymbol"
DocumentSymbolParams
DocumentSymbolResult
handleDocumentSymbol
registerLspRequestHandler
"textDocument/foldingRange"
FoldingRangeParams
(Array FoldingRange)
handleFoldingRange
registerLspRequestHandler
"$/lean/plainGoal"
PlainGoalParams
(Option PlainGoal)
handlePlainGoal
registerLspRequestHandler
"$/lean/plainTermGoal"
PlainTermGoalParams
(Option PlainTermGoal)
handlePlainTermGoal
end Lean.Server.FileWorker