lean4-htt/src/Lean/Server/Utils.lean
2021-09-16 09:46:10 +02:00

156 lines
5.2 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) 2020 Wojciech Nawrocki. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Wojciech Nawrocki, Marc Huisinga
-/
import Lean.Data.Position
import Lean.Data.Lsp
import Lean.Server.InfoUtils
import Init.System.FilePath
namespace IO
def throwServerError (err : String) : IO α :=
throw (userError err)
namespace FS.Stream
/-- Chains two streams by creating a new stream s.t. writing to it
just writes to `a` but reading from it also duplicates the read output
into `b`, c.f. `a | tee b` on Unix.
NB: if `a` is written to but this stream is never read from,
the output will *not* be duplicated. Use this if you only care
about the data that was actually read. -/
def chainRight (a : Stream) (b : Stream) (flushEagerly : Bool := false) : Stream :=
{ a with
flush := a.flush *> b.flush
read := fun sz => do
let bs ← a.read sz
b.write bs
if flushEagerly then b.flush
pure bs
getLine := do
let ln ← a.getLine
b.putStr ln
if flushEagerly then b.flush
pure ln }
/-- Like `tee a | b` on Unix. See `chainOut`. -/
def chainLeft (a : Stream) (b : Stream) (flushEagerly : Bool := false) : Stream :=
{ b with
flush := a.flush *> b.flush
write := fun bs => do
a.write bs
if flushEagerly then a.flush
b.write bs
putStr := fun s => do
a.putStr s
if flushEagerly then a.flush
b.putStr s }
/-- Prefixes all written outputs with `pre`. -/
def withPrefix (a : Stream) (pre : String) : Stream :=
{ a with
write := fun bs => do
a.putStr pre
a.write bs
putStr := fun s =>
a.putStr (pre ++ s) }
end FS.Stream
end IO
namespace Lean.Server
structure DocumentMeta where
uri : Lsp.DocumentUri
version : Nat
text : FileMap
deriving Inhabited
def replaceLspRange (text : FileMap) (r : Lsp.Range) (newText : String) : FileMap :=
let start := text.lspPosToUtf8Pos r.start
let «end» := text.lspPosToUtf8Pos r.«end»
let pre := text.source.extract 0 start
let post := text.source.extract «end» text.source.bsize
(pre ++ newText ++ post).toFileMap
open IO
/-- Duplicates an I/O stream to a log file `fName` in LEAN_SERVER_LOG_DIR
if that envvar is set. -/
def maybeTee (fName : String) (isOut : Bool) (h : FS.Stream) : IO FS.Stream := do
match (← IO.getEnv "LEAN_SERVER_LOG_DIR") with
| none => pure h
| some logDir =>
IO.FS.createDirAll logDir
let hTee ← FS.Handle.mk (System.mkFilePath [logDir, fName]) FS.Mode.write true
let hTee := FS.Stream.ofHandle hTee
pure $ if isOut then
hTee.chainLeft h true
else
h.chainRight hTee true
/-- Transform the given path to a file:// URI. -/
def toFileUri (fname : System.FilePath) : Lsp.DocumentUri :=
let fname := fname.normalize.toString
let fname := if System.Platform.isWindows then
fname.map fun c => if c == '\\' then '/' else c
else
fname
-- TODO(WN): URL-encode special characters
-- Three slashes denote localhost.
"file:///" ++ fname.dropWhile (· == '/')
open Lsp
/-- Returns the document contents with all changes applied, together with the position of the change
which lands earliest in the file. Panics if there are no changes. -/
def foldDocumentChanges (changes : @& Array Lsp.TextDocumentContentChangeEvent) (oldText : FileMap)
: FileMap × String.Pos :=
if changes.isEmpty then panic! "Lean.Server.foldDocumentChanges: empty change array" else
let accumulateChanges : FileMap × String.Pos → TextDocumentContentChangeEvent → FileMap × String.Pos :=
fun ⟨newDocText, minStartOff⟩ change =>
match change with
| TextDocumentContentChangeEvent.rangeChange (range : Range) (newText : String) =>
let startOff := oldText.lspPosToUtf8Pos range.start
let newDocText := replaceLspRange newDocText range newText
let minStartOff := minStartOff.min startOff
⟨newDocText, minStartOff⟩
| TextDocumentContentChangeEvent.fullChange (newText : String) =>
⟨newText.toFileMap, 0⟩
-- NOTE: We assume Lean files are below 16 EiB.
changes.foldl accumulateChanges (oldText, 0xffffffff)
def publishDiagnostics (m : DocumentMeta) (diagnostics : Array Lsp.Diagnostic) (hOut : FS.Stream) : IO Unit :=
hOut.writeLspNotification {
method := "textDocument/publishDiagnostics"
param := {
uri := m.uri
version? := m.version
diagnostics := diagnostics
: PublishDiagnosticsParams
}
}
def publishProgress (m : DocumentMeta) (processing : Array LeanFileProgressProcessingInfo) (hOut : FS.Stream) : IO Unit :=
hOut.writeLspNotification {
method := "$/lean/fileProgress"
param := {
textDocument := { uri := m.uri, version? := m.version }
processing
: LeanFileProgressParams
}
}
def publishProgressAtPos (m : DocumentMeta) (pos : String.Pos) (hOut : FS.Stream) : IO Unit :=
publishProgress m #[{ range := ⟨m.text.utf8PosToLspPos pos, m.text.utf8PosToLspPos m.text.source.bsize⟩ }] hOut
def publishProgressDone (m : DocumentMeta) (hOut : FS.Stream) : IO Unit :=
publishProgress m #[] hOut
end Lean.Server
def String.Range.toLspRange (text : Lean.FileMap) (r : String.Range) : Lean.Lsp.Range :=
⟨text.utf8PosToLspPos r.start, text.utf8PosToLspPos r.stop⟩