Zulip Chat Archive
Stream: general
Topic: reading the "visible" file, rather than the saved one
Damiano Testa (Jul 24 2024 at 07:13):
While talking to Johan about writing the long-line linter as a syntax linter, we came up with the code below.
It works well, but uses information about the saved file. How do I access the actual file as it is being edited?
E.g., if you copy the long line below and paste it somewhere else, you will not get a warning unless you save the file and make sure that lean re-parses the command.
import Lean.Elab.Command
open Lean in
run_cmd
let fil ← IO.FS.lines (← getFileName)
let longLines := (List.range fil.size).filterMap fun l =>
if 70 < fil[l]!.length then some (l + 1) else none
let fm ← getFileMap
for l_idx in longLines do
let longLineStart := fm.ofPosition ⟨l_idx, 0⟩
let longLineEnd := fm.ofPosition ⟨l_idx, fil[l_idx-1]!.length⟩
logWarningAt (.ofRange ⟨longLineStart, longLineEnd⟩) "too long"
/-
0 1 2 3 4 5 6 7
^ flagged as too long
-/
Damiano Testa (Jul 24 2024 at 07:14):
(Also note that the code above does not work in the playground, since it tries to access the current file being used, something that does not seem to exist in the playground.)
Sebastian Ullrich (Jul 24 2024 at 09:03):
If you're writing a syntax linter, why would you not take that information from the syntax tree given to you?
Sebastian Ullrich (Jul 24 2024 at 09:06):
I.e. use Syntax.getSubstring?
Damiano Testa (Jul 24 2024 at 10:18):
Thanks! I did not know about Syntax.getSubstring?
! This works as expected!
import Lean.Elab.Command
open Lean in
/-- flags lines longer than 70 characters in the following command -/
elab "longLines " cmd:command : command => do
Elab.Command.elabCommand cmd
let sstr := cmd.raw.getSubstring?
let longLines := ((sstr.getD default).splitOn "\n").filter (70 < ·.toString.length)
for l_idx in longLines do
logWarningAt (.ofRange ⟨l_idx.startPos, l_idx.stopPos⟩) "too long"
longLines
example : True := trivial
/-
0 1 2 3 4 5 6 7
^ flagged as too long
-/
Damiano Testa (Jul 24 2024 at 10:19):
It does not flag long lines after #exit
, but #exit
already flags itself, so that's probably not a great deal!
Adam Topaz (Jul 24 2024 at 21:39):
In case you didn't know, this is also possible:
import Lean
open Lean
run_cmd do
let ctx ← read
println! ctx.fileMap.source
Damiano Testa (Jul 25 2024 at 03:50):
Adam, I did not know about this, thanks!
Sebastian Ullrich (Jul 26 2024 at 16:54):
@Damiano Testa The remaining readFile
in the linter seems to break live.lean-lang.org
Damiano Testa (Jul 26 2024 at 22:09):
Oh, would putting that code behind an if pathExists then
solve the issue?
Damiano Testa (Jul 26 2024 at 22:20):
#15179 hopefully this fixes it, but I do not know how to test it.
Mac Malone (Jul 27 2024 at 01:00):
@Damiano Testa I am curious, why are you re-reading the file in the linter rather than just using fileMap.source
like Adam suggested?
Damiano Testa (Jul 27 2024 at 03:19):
Mostly, the answer is that I do not really understand the difference between the two, so I just kept using what I already had, instead of switching over.
Damiano Testa (Jul 27 2024 at 03:20):
So, I guess from your comment that using Adam's suggestion would still avoid the issue on the lean server and be more efficient?
Damiano Testa (Jul 27 2024 at 03:20):
If so, then I'll adapt the linter!
Kyle Miller (Jul 27 2024 at 03:34):
Another benefit to using the filemap is that it gives you a version whose line ending have all been normalized to \n (vs on Windows, where they might otherwise be \r\n, depending on how you've got everything configured!)
Kyle Miller (Jul 27 2024 at 03:35):
Oh, never mind, I just read the linter code, and I see it goes to mkInputContext anyway. That function normalizes line endings by default.
Damiano Testa (Jul 27 2024 at 03:50):
As far as I can see, #15180 should therefore be a better solution, right? It does look better to me!
Mac Malone (Jul 27 2024 at 03:56):
Damiano Testa said:
Mostly, the answer is that I do not really understand the difference between the two, so I just kept using what I already had, instead of switching over.
The key difference is that the fileMap.source
uses the text of the current file that is already available to Lean processes, whereas the readFile
solution attempts to find the source file on disk and read it again. The later can fail if the file cannot be accessed (e.g., on the server or when there is no file, such as with lean --stdin
), and, even when successful, will not contain unsaved edits to the file. There are cases were these properties can be useful, but it does not seem like this linter is one of them.
Mac Malone (Jul 27 2024 at 03:58):
Also, as a minor style note, there is a helper called getFileMap
(like getFileName
) that can be used instead of (<- read).fileMap
.
Damiano Testa (Jul 27 2024 at 04:01):
Thanks for the explanation: that clarified some haziness that I had not even realized I had!
Also, I definitely want to access the not-necessarily-saved latest version of the file.
I'll edit the PR with you getFileMap
suggestion.
Damiano Testa (Jul 27 2024 at 04:02):
(It looks like Kyle also mentioned it in the PR! :smile: )
Last updated: May 02 2025 at 03:31 UTC