lean4-htt/src/Lean/Meta/TryThis.lean
Markus Himmel fa67f300f6
chore: rename String.ValidPos to String.Pos (#11240)
This PR renames `String.ValidPos` to `String.Pos`, `String.endValidPos`
to `String.endPos` and `String.startValidPos` to `String.startPos`.

Accordingly, the deprecations of `String.Pos` to `String.Pos.Raw` and
`String.endPos` to `String.rawEndPos` are removed early, after an
abbreviated deprecation cycle of two releases.
2025-11-24 16:40:21 +00:00

244 lines
11 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 Gabriel Ebner. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Gabriel Ebner, Mario Carneiro, Thomas Murrills
-/
module
prelude
public import Lean.Data.Lsp.Basic
public import Lean.PrettyPrinter
public section
/-!
# "Try this" data types
This defines the data types used in constructing "try this" widgets for suggestion-providing tactics
and inline error-message hints, as well as basic infrastructure for generating info trees and
widget content there from.
-/
namespace Lean.Meta.Tactic.TryThis
open PrettyPrinter
/-! # Code action information -/
/-! # `Suggestion` data -/
-- TODO: we could also support `Syntax` and `Format`
/-- Text to be used as a suggested replacement in the infoview. This can be either a `TSyntax kind`
for a single `kind : SyntaxNodeKind` or a raw `String`.
Instead of using constructors directly, there are coercions available from these types to
`SuggestionText`. -/
inductive SuggestionText where
/-- `TSyntax kind` used as suggested replacement text in the infoview. Note that while `TSyntax`
is in general parameterized by a list of `SyntaxNodeKind`s, we only allow one here; this
unambiguously guides pretty-printing. -/
| tsyntax {kind : SyntaxNodeKind} : TSyntax kind → SuggestionText
/-- A raw string to be used as suggested replacement text in the infoview. -/
| string : String → SuggestionText
deriving Inhabited
instance : ToMessageData SuggestionText where
toMessageData
| .tsyntax stx => stx
| .string s => s
instance {kind : SyntaxNodeKind} : CoeHead (TSyntax kind) SuggestionText where
coe := .tsyntax
instance : Coe String SuggestionText where
coe := .string
/--
Style hooks for `Suggestion`s. See `SuggestionStyle.error`, `.warning`, `.success`, `.value`,
and other definitions here for style presets. This is an arbitrary `Json` object, with the following
interesting fields:
* `title`: the hover text in the suggestion link
* `className`: the CSS classes applied to the link
* `style`: A `Json` object with additional inline CSS styles such as `color` or `textDecoration`.
-/
@[expose] def SuggestionStyle := Json deriving Inhabited, ToJson
/-- Style as an error. By default, decorates the text with an undersquiggle; providing the argument
`decorated := false` turns this off. -/
@[deprecated "`SuggestionStyle` is not used anymore." (since := "2025-08-14")]
def SuggestionStyle.error (decorated := true) : SuggestionStyle :=
let style := if decorated then
json% {
-- The VS code error foreground theme color (`--vscode-errorForeground`).
color: "var(--vscode-errorForeground)",
textDecoration: "underline wavy var(--vscode-editorError-foreground) 1pt"
}
else json% { color: "var(--vscode-errorForeground)" }
json% { className: "pointer dim", style: $style }
/-- Style as a warning. By default, decorates the text with an undersquiggle; providing the
argument `decorated := false` turns this off. -/
@[deprecated "`SuggestionStyle` is not used anymore." (since := "2025-08-14")]
def SuggestionStyle.warning (decorated := true) : SuggestionStyle :=
if decorated then
json% {
-- The `.gold` CSS class, which the infoview uses when e.g. building a file.
className: "gold pointer dim",
style: { textDecoration: "underline wavy var(--vscode-editorWarning-foreground) 1pt" }
}
else json% { className: "gold pointer dim" }
/-- Style as a success. -/
@[deprecated "`SuggestionStyle` is not used anymore." (since := "2025-08-14")]
def SuggestionStyle.success : SuggestionStyle :=
-- The `.information` CSS class, which the infoview uses on successes.
json% { className: "information pointer dim" }
/-- Style the same way as a hypothesis appearing in the infoview. -/
@[deprecated "`SuggestionStyle` is not used anymore." (since := "2025-08-14")]
def SuggestionStyle.asHypothesis : SuggestionStyle :=
json% { className: "goal-hyp pointer dim" }
/-- Style the same way as an inaccessible hypothesis appearing in the infoview. -/
@[deprecated "`SuggestionStyle` is not used anymore." (since := "2025-08-14")]
def SuggestionStyle.asInaccessible : SuggestionStyle :=
json% { className: "goal-inaccessible pointer dim" }
/-- Draws the color from a red-yellow-green color gradient with red at `0.0`, yellow at `0.5`, and
green at `1.0`. Values outside the range `[0.0, 1.0]` are clipped to lie within this range.
With `showValueInHoverText := true` (the default), the value `t` will be included in the `title` of
the HTML element (which appears on hover). -/
@[deprecated "`SuggestionStyle` is not used anymore." (since := "2025-08-14")]
def SuggestionStyle.value (t : Float) (showValueInHoverText := true) : SuggestionStyle :=
let t := min (max t 0) 1
json% {
className: "pointer dim",
-- interpolates linearly from 0º to 120º with 95% saturation and lightness
-- varying around 50% in HSL space
style: { color: $(s!"hsl({(t * 120).round} 95% {60 * ((t - 0.5)^2 + 0.75)}%)") },
title: $(if showValueInHoverText then s!"Apply suggestion ({t})" else "Apply suggestion")
}
/-- Holds a `suggestion` for replacement, along with `preInfo` and `postInfo` strings to be printed
immediately before and after that suggestion, respectively. It also includes an optional
`MessageData` to represent the suggestion in logs; by default, this is `none`, and `suggestion` is
used. -/
structure Suggestion where
/-- Text to be used as a replacement via a code action. -/
suggestion : SuggestionText
/-- Optional info to be printed immediately before replacement text in a widget. -/
preInfo? : Option String := none
/-- Optional info to be printed immediately after replacement text in a widget. -/
postInfo? : Option String := none
/-- Optional style specification for the suggestion. If `none` (the default), the suggestion is
styled as a text link. Otherwise, the suggestion can be styled as:
* a status: `.error`, `.warning`, `.success`
* a hypothesis name: `.asHypothesis`, `.asInaccessible`
* a variable color: `.value (t : Float)`, which draws from a red-yellow-green gradient, with red
at `0.0` and green at `1.0`.
See `SuggestionStyle` for details.
Note that this property is used only by the "try this" widget; it is ignored by the inline hint
widget. -/
style? : Option SuggestionStyle := none
/-- How to represent the suggestion as `MessageData`. This is used only in the text of the
widget diagnostic.
If `none`, we use `suggestion`. Use `toMessageData` to render a `Suggestion` in this manner. -/
messageData? : Option MessageData := none
/-- How to construct the text that appears in the lightbulb menu from the suggestion text. If
`none`, we use `fun ppSuggestionText => "Try this: " ++ ppSuggestionText`. Only the pretty-printed
`suggestion : SuggestionText` is used here. -/
toCodeActionTitle? : Option (String → String) := none
deriving Inhabited
attribute [deprecated "The `style?` property is not used anymore." (since := "2025-08-14")] Suggestion.style?
/- Use `toMessageData` of the suggestion text. -/
instance : ToMessageData Suggestion where
toMessageData s := s.messageData?.getD (toMessageData s.suggestion)
instance : Coe SuggestionText Suggestion where
coe t := { suggestion := t }
/--
A packet of information about a "Try this" suggestion
that we store in the infotree for the associated code action to retrieve.
-/
structure TryThisInfo : Type where
/-- Suggestion edit as it will be applied to the editor document. -/
edit : Lsp.TextEdit
/-- Title of the code action that is displayed in the code action selection dialog. -/
codeActionTitle : String
/--
Original suggestion that this `TryThisInfo` was derived from.
Stored so that meta-code downstream can extract the suggestions
produced by a tactic from the `InfoTree`.
-/
suggestion : Suggestion
deriving TypeName
/-! # Formatting -/
/-- Yields `(indent, column)` given a `FileMap` and a `Lean.Syntax.Range`, where `indent` is the number
of spaces by which the line that first includes `range` is initially indented, and `column` is the
column `range` starts at in that line. -/
def getIndentAndColumn (map : FileMap) (range : Lean.Syntax.Range) : Nat × Nat :=
let rangeStart := map.source.pos! range.start
let start := findLineStart rangeStart
let body := (map.source.slice! start rangeStart).find (fun c => c != ' ') |>.str.offset
(start.offset.byteDistance body, start.offset.byteDistance range.start)
where
findLineStart {s : String} (p : s.Pos) : s.Pos :=
p.revFind? '\n' |>.map (·.next!) |>.getD s.startPos
/--
An option allowing the user to customize the ideal input width. Defaults to 100.
This option controls output format when
the output is intended to be copied back into a lean file -/
register_builtin_option format.inputWidth : Nat := {
/- The default maximum width of an ideal line in source code. -/
defValue := 100
descr := "ideal input width"
}
/-- Get the input width specified in the options -/
def getInputWidth (o : Options) : Nat := format.inputWidth.get o
namespace SuggestionText
/-- Pretty-prints a `SuggestionText` as a `Format`. If the `SuggestionText` is some `TSyntax kind`,
we use the appropriate pretty-printer; strings are coerced to `Format`s as-is. -/
def pretty : SuggestionText → CoreM Format
| .tsyntax (kind := kind) stx => ppCategory kind stx
| .string text => return text
/- Note that this is essentially `return (← s.pretty).prettyExtra w indent column`, but we
special-case strings to avoid converting them to `Format`s and back, which adds indentation after each newline. -/
/-- Pretty-prints a `SuggestionText` as a `String` and wraps with respect to the pane width,
indentation, and column, via `Format.prettyExtra`. If `w := none`, then
`w := getInputWidth (← getOptions)` is used. Raw `String`s are returned as-is. -/
def prettyExtra (s : SuggestionText) (w : Option Nat := none)
(indent column : Nat := 0) : CoreM String :=
match s with
| .tsyntax (kind := kind) stx => do
let w ← match w with | none => do pure <| getInputWidth (← getOptions) | some n => pure n
return (← ppCategory kind stx).pretty w indent column
| .string text => return text
end SuggestionText
def Suggestion.pretty (s : Suggestion) (w : Option Nat := none) (indent column : Nat := 0) :
CoreM String := do
s.suggestion.prettyExtra w indent column
def Suggestion.processEdit (s : Suggestion) (range : Lean.Syntax.Range) : CoreM Lsp.TextEdit := do
let map ← getFileMap
-- FIXME: this produces incorrect results when `by` is at the beginning of the line, i.e.
-- replacing `tac` in `by tac`, because the next line will only be 2 space indented
-- (less than `tac` which starts at column 3)
let (indent, column) := getIndentAndColumn map range
let newText ← s.pretty (indent := indent) (column := column)
let range := map.utf8RangeToLspRange range
return { range, newText }