{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators #-}

module Language.LSP.Protocol.Types.Edit where

import Data.Text (Text)
import Data.Text qualified as T

import Control.Lens hiding (index)
import Language.LSP.Protocol.Internal.Types
import Language.LSP.Protocol.Types.Common

-- | Convenience alias for the type in the 'WorkspaceEdit._documentChanges' field.
type DocumentChange = TextDocumentEdit |? CreateFile |? RenameFile |? DeleteFile

-- TODO: get rid of this in favour of the more correct things in VFS

{- | Applies a 'TextEdit' to some 'Text'.

 >>> applyTextEdit (TextEdit (Range (Position 0 1) (Position 0 2)) "i") "foo"
 "fio"
-}
applyTextEdit :: TextEdit -> Text -> Text
applyTextEdit :: TextEdit -> Text -> Text
applyTextEdit (TextEdit (Range Position
sp Position
ep) Text
newText) Text
oldText =
  let (Text
_, Text
afterEnd) = Position -> Text -> (Text, Text)
splitAtPos Position
ep Text
oldText
      (Text
beforeStart, Text
_) = Position -> Text -> (Text, Text)
splitAtPos Position
sp Text
oldText
   in forall a. Monoid a => [a] -> a
mconcat [Text
beforeStart, Text
newText, Text
afterEnd]
 where
  splitAtPos :: Position -> Text -> (Text, Text)
  splitAtPos :: Position -> Text -> (Text, Text)
splitAtPos (Position UInt
sl UInt
sc) Text
t =
    -- If we are looking for a line beyond the end of the text, this will give us an index
    -- past the end. Fortunately, T.splitAt is fine with this, and just gives us the whole
    -- string and an empty string, which is what we want.
    let index :: UInt
index = UInt
sc forall a. Num a => a -> a -> a
+ UInt -> Text -> UInt
startLineIndex UInt
sl Text
t
     in Int -> Text -> (Text, Text)
T.splitAt (forall a b. (Integral a, Num b) => a -> b
fromIntegral UInt
index) Text
t

  -- The index of the first character of line 'line'
  startLineIndex :: UInt -> Text -> UInt
  startLineIndex :: UInt -> Text -> UInt
startLineIndex UInt
0 Text
_ = UInt
0
  startLineIndex UInt
line Text
t' =
    case (Char -> Bool) -> Text -> Maybe Int
T.findIndex (forall a. Eq a => a -> a -> Bool
== Char
'\n') Text
t' of
      Just Int
i -> forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
i forall a. Num a => a -> a -> a
+ UInt
1 forall a. Num a => a -> a -> a
+ UInt -> Text -> UInt
startLineIndex (UInt
line forall a. Num a => a -> a -> a
- UInt
1) (Int -> Text -> Text
T.drop (Int
i forall a. Num a => a -> a -> a
+ Int
1) Text
t')
      -- i != 0, and there are no newlines, so this is a line beyond the end of the text.
      -- In this case give the "start index" as the end, so we will at least append the text.
      Maybe Int
Nothing -> forall a b. (Integral a, Num b) => a -> b
fromIntegral forall a b. (a -> b) -> a -> b
$ Text -> Int
T.length Text
t'

-- | 'editTextEdit' @outer@ @inner@ applies @inner@ to the text inside @outer@.
editTextEdit :: TextEdit -> TextEdit -> TextEdit
editTextEdit :: TextEdit -> TextEdit -> TextEdit
editTextEdit (TextEdit Range
origRange Text
origText) TextEdit
innerEdit =
  let newText :: Text
newText = TextEdit -> Text -> Text
applyTextEdit TextEdit
innerEdit Text
origText
   in Range -> Text -> TextEdit
TextEdit Range
origRange Text
newText

-- | Conversion between 'OptionalVersionedTextDocumentIdentifier' and 'VersionedTextDocumentIdentifier'.
_versionedTextDocumentIdentifier :: Prism' OptionalVersionedTextDocumentIdentifier VersionedTextDocumentIdentifier
_versionedTextDocumentIdentifier :: Prism'
  OptionalVersionedTextDocumentIdentifier
  VersionedTextDocumentIdentifier
_versionedTextDocumentIdentifier = forall b t s a. (b -> t) -> (s -> Either t a) -> Prism s t a b
prism VersionedTextDocumentIdentifier
-> OptionalVersionedTextDocumentIdentifier
down OptionalVersionedTextDocumentIdentifier
-> Either
     OptionalVersionedTextDocumentIdentifier
     VersionedTextDocumentIdentifier
up
 where
  down :: VersionedTextDocumentIdentifier
-> OptionalVersionedTextDocumentIdentifier
down (VersionedTextDocumentIdentifier Uri
uri Int32
v) = Uri -> (Int32 |? Null) -> OptionalVersionedTextDocumentIdentifier
OptionalVersionedTextDocumentIdentifier Uri
uri (forall a b. a -> a |? b
InL Int32
v)
  up :: OptionalVersionedTextDocumentIdentifier
-> Either
     OptionalVersionedTextDocumentIdentifier
     VersionedTextDocumentIdentifier
up (OptionalVersionedTextDocumentIdentifier Uri
uri (InL Int32
v)) = forall a b. b -> Either a b
Right forall a b. (a -> b) -> a -> b
$ Uri -> Int32 -> VersionedTextDocumentIdentifier
VersionedTextDocumentIdentifier Uri
uri Int32
v
  up i :: OptionalVersionedTextDocumentIdentifier
i@(OptionalVersionedTextDocumentIdentifier Uri
_ (InR Null
_)) = forall a b. a -> Either a b
Left OptionalVersionedTextDocumentIdentifier
i