This didn't do anything except on Windows, where it would make the application differ from standard Windows applications, which we don't want.
148 lines
4.9 KiB
Text
148 lines
4.9 KiB
Text
/-
|
||
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.Lsp.Communication
|
||
import Lean.Data.Lsp.Diagnostics
|
||
import Lean.Data.Lsp.Extra
|
||
import Lean.Data.Lsp.TextSync
|
||
import Lean.Server.InfoUtils
|
||
|
||
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 DocumentMeta.mkInputContext (doc : DocumentMeta) : Parser.InputContext where
|
||
input := doc.text.source
|
||
fileName := (System.Uri.fileUriToPath? doc.uri).getD doc.uri |>.toString
|
||
fileMap := doc.text
|
||
|
||
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.endPos
|
||
(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
|
||
let hTee := FS.Stream.ofHandle hTee
|
||
pure $ if isOut then
|
||
hTee.chainLeft h true
|
||
else
|
||
h.chainRight hTee true
|
||
|
||
open Lsp
|
||
|
||
/-- Returns the document contents with the change applied. -/
|
||
def applyDocumentChange (oldText : FileMap) : (change : Lsp.TextDocumentContentChangeEvent) → FileMap
|
||
| TextDocumentContentChangeEvent.rangeChange (range : Range) (newText : String) =>
|
||
replaceLspRange oldText range newText
|
||
| TextDocumentContentChangeEvent.fullChange (newText : String) =>
|
||
newText.toFileMap
|
||
|
||
/-- Returns the document contents with all changes applied. -/
|
||
def foldDocumentChanges (changes : Array Lsp.TextDocumentContentChangeEvent) (oldText : FileMap) : FileMap :=
|
||
changes.foldl applyDocumentChange oldText
|
||
|
||
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) (kind : LeanFileProgressKind := LeanFileProgressKind.processing) : IO Unit :=
|
||
publishProgress m #[{ range := ⟨m.text.utf8PosToLspPos pos, m.text.utf8PosToLspPos m.text.source.endPos⟩, kind := kind }] hOut
|
||
|
||
def publishProgressDone (m : DocumentMeta) (hOut : FS.Stream) : IO Unit :=
|
||
publishProgress m #[] hOut
|
||
|
||
-- TODO: should return a request ID (or task?) when we add response handling
|
||
def applyWorkspaceEdit (params : ApplyWorkspaceEditParams) (hOut : FS.Stream) : IO Unit :=
|
||
hOut.writeLspRequest ⟨"workspace/applyEdit", "workspace/applyEdit", params⟩
|
||
|
||
end Lean.Server
|
||
|
||
def String.Range.toLspRange (text : Lean.FileMap) (r : String.Range) : Lean.Lsp.Range :=
|
||
⟨text.utf8PosToLspPos r.start, text.utf8PosToLspPos r.stop⟩
|