Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions hls-plugin-api/hls-plugin-api.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ library

hs-source-dirs: src
build-depends:
, split ^>=0.2.5
, aeson
, base >=4.12 && <5
, co-log-core
Expand Down
23 changes: 15 additions & 8 deletions hls-plugin-api/src/Ide/PluginUtils.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module Ide.PluginUtils
makeDiffTextEditAdditive,
diffText,
diffText',
diffTextEdit,
pluginDescToIdePlugins,
idePluginsToPluginDesc,
getClientConfig,
Expand Down Expand Up @@ -45,8 +46,10 @@ import Data.Algorithm.Diff
import Data.Algorithm.DiffOutput
import Data.Char (isPrint, showLitChar)
import Data.Functor (void)
import Data.List.Split
import qualified Data.Map as M
import qualified Data.Text as T
import Data.Tuple.Extra (both)
import Data.Void (Void)
import Ide.Plugin.Config
import Ide.Plugin.Properties
Expand Down Expand Up @@ -119,24 +122,28 @@ makeDiffTextEditAdditive :: T.Text -> T.Text -> [TextEdit]
makeDiffTextEditAdditive f1 f2 = diffTextEdit f1 f2 SkipDeletions

diffTextEdit :: T.Text -> T.Text -> WithDeletions -> [TextEdit]
diffTextEdit fText f2Text withDeletions = r
diffTextEdit fText f2Text withDeletions
= map diffOperationToTextEdit diffOps
where
r = map diffOperationToTextEdit diffOps
d = getGroupedDiff (lines $ T.unpack fText) (lines $ T.unpack f2Text)
-- Lossless versions of lines/unlines (they keep the line breaks)
lines = split (dropFinalBlank $ keepDelimsR $ whenElt (== '\n'))
unlines = concat

(linesL, linesR) = both (lines . T.unpack) (fText, f2Text)

diffOps =
filter
(\x -> (withDeletions == IncludeDeletions) || not (isDeletion x))
(diffToLineRanges d)

isDeletion (Deletion _ _) = True
isDeletion _ = False
(diffToLineRanges $ getGroupedDiff linesL linesR)
where
isDeletion (Deletion _ _) = True
isDeletion _ = False

diffOperationToTextEdit :: DiffOperation LineRange -> TextEdit
diffOperationToTextEdit (Change fm to) = TextEdit range nt
where
range = calcRange fm
nt = T.pack $ init $ unlines $ lrContents to
nt = T.pack $ unlines $ lrContents to

{-
In order to replace everything including newline characters,
Expand Down
58 changes: 56 additions & 2 deletions hls-plugin-api/test/Ide/PluginUtilsTest.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ import Ide.Plugin.Properties (KeyNamePath (..),
usePropertyByPath,
usePropertyByPathEither)
import qualified Ide.Plugin.RangeMap as RangeMap
import Ide.PluginUtils (extractTextInRange, unescape)
import Ide.PluginUtils (WithDeletions (IncludeDeletions),
diffTextEdit, extractTextInRange,
unescape)
import Language.LSP.Protocol.Types (Position (..), Range (Range),
UInt, isSubrangeOf)
TextEdit (TextEdit), UInt,
isSubrangeOf, mkRange)
import Test.Tasty
import Test.Tasty.Golden (goldenVsStringDiff)
import Test.Tasty.HUnit
Expand All @@ -31,6 +34,7 @@ import Test.Tasty.QuickCheck
tests :: TestTree
tests = testGroup "PluginUtils"
[ unescapeTest
, diffTextEditTest
, extractTextInRangeTest
, localOption (QuickCheckMaxSize 10000) $
testProperty "RangeMap-List filtering identical" $
Expand All @@ -56,6 +60,56 @@ unescapeTest = testGroup "unescape"
unescape "\"\\n\\t\"" @?= "\"\\n\\t\""
]

diffTextEditTest :: TestTree
diffTextEditTest = testGroup "diffTextEdit"
[ testGroup "inserting line at EOF"
[ testCase "both newline-terminated (linux-style vs linux-style)"
$ diffTextEditComplete "foo\n"
"foo\nbar\n"
@?= [textEdit "bar\n"
(mkRange 1 0 1 0)]
, testCase "neither newline-terminated (win-style vs win-style)"
$ diffTextEditComplete "foo"
"foo\nbar"
@?= [textEdit "foo\nbar"
(mkRange 0 0 0 3)]
, testCase "only left newline-terminated"
$ diffTextEditComplete "foo\n"
"foo\nbar"
@?= [textEdit "bar"
(mkRange 1 0 1 0)]
, testCase "only right newline-terminated"
$ diffTextEditComplete "foo"
"foo\nbar\n"
@?= [textEdit "foo\nbar\n"
(mkRange 0 0 0 3)]
]
, testGroup "deleting line at EOF"
[ testCase "both newline-terminated (linux-style vs linux-style)"
$ diffTextEditComplete "foo\nbar\n"
"foo\n"
@?= [textEdit ""
(mkRange 1 0 2 0)]
, testCase "neither newline-terminated (win-style vs win-style)"
$ diffTextEditComplete "foo\nbar"
"foo"
@?= [textEdit "foo"
(mkRange 0 0 1 3)]
, testCase "only left newline-terminated"
$ diffTextEditComplete "foo\nbar"
"foo\n"
@?= [textEdit ""
(mkRange 1 0 2 0)]
, testCase "only right newline-terminated"
$ diffTextEditComplete "foo\nbar\n"
"foo"
@?= [textEdit "foo"
(mkRange 0 0 1 4)]
]
]
where diffTextEditComplete from to = diffTextEdit from to IncludeDeletions
textEdit = flip TextEdit

extractTextInRangeTest :: TestTree
extractTextInRangeTest = testGroup "extractTextInRange"
[ testCase "inline range" $
Expand Down
3 changes: 1 addition & 2 deletions plugins/hls-case-split-plugin/test/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ codeActionTests = testGroup
Prelude.flip inspectCodeAction [title]

-- Windows support
, expectFailBecause "https://github.com/haskell/haskell-language-server/issues/5059"
$ goldenWithClass "Like TNoPatternsNoBraces, but lacks line terminator at EOF" "TNoPatternsNoBracesWindows" $
, goldenWithClass "Like TNoPatternsNoBraces, but lacks line terminator at EOF" "TNoPatternsNoBracesWindows" $
Prelude.flip inspectCodeAction [title]

-- Patterns with irregular indentation
Expand Down
2 changes: 2 additions & 0 deletions plugins/hls-class-plugin/test/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ codeActionTests = testGroup
]
, goldenWithClass "Creates a placeholder for '=='" "T1" "eq" $
getActionByTitle "Add placeholders for '=='"
, goldenWithClass "Like previous one, but this file has no line terminator" "T1W" "eq" $
getActionByTitle "Add placeholders for '=='"
, goldenWithClass "Creates a placeholder for '/='" "T1" "ne" $
getActionByTitle "Add placeholders for '/='"
, goldenWithClass "Creates a placeholder for both '==' and '/='" "T1" "all" $
Expand Down
6 changes: 6 additions & 0 deletions plugins/hls-class-plugin/test/testdata/T1W.eq.expected.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module T1 where

data X = X

instance Eq X where
(==) = _
5 changes: 5 additions & 0 deletions plugins/hls-class-plugin/test/testdata/T1W.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module T1 where

data X = X

instance Eq X where
30 changes: 18 additions & 12 deletions plugins/hls-splice-plugin/src/Ide/Plugin/Splice.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import Control.Arrow (Arrow (first))
import Control.Exception (SomeException)
import qualified Control.Foldl as L
import Control.Lens (Identity (..), ix, view,
(%~), (<&>), (^.))
(%~), (.~), (<&>), (^.))
import Control.Monad (forM, guard, unless)
import Control.Monad.Error.Class (MonadError (throwError))
import Control.Monad.Extra (eitherM)
Expand Down Expand Up @@ -61,7 +61,8 @@ import Data.Foldable (Foldable (foldl'))
import GHC.Data.Bag (Bag)

#if MIN_VERSION_ghc(9,13,0)
import GHC.Parser.Annotation (EpAnn (..), EpToken (..))
import GHC.Parser.Annotation (EpAnn (..),
EpToken (..))
#elif MIN_VERSION_ghc(9,9,0)
import GHC.Parser.Annotation (EpAnn (..))
#else
Expand Down Expand Up @@ -237,26 +238,29 @@ setupDynFlagsForGHCiLike env dflags = do
Loader.initializePlugins (hscSetFlags dflags4 env)

adjustToRange :: Uri -> Range -> WorkspaceEdit -> WorkspaceEdit
adjustToRange uri ran (WorkspaceEdit mhult mlt x) =
WorkspaceEdit (adjustWS <$> mhult) (fmap adjustDoc <$> mlt) x
adjustToRange uri ran wsEdit@WorkspaceEdit{..} =
wsEdit { _changes = adjustWS <$> _changes
, _documentChanges = fmap adjustDoc <$> _documentChanges }
where
adjustTextEdits :: Traversable f => f TextEdit -> f TextEdit
adjustTextEdits eds =
let minStart =
case L.fold (L.premap (view J.range) L.minimum) eds of
let minStart :: Range -- leftmost-starting range or, to break ties, leftmost-ending range
= case L.fold (L.premap (view J.range) L.minimum) eds of
Nothing -> error "impossible"
Just v -> v
in adjustLine minStart <$> eds

adjustATextEdits :: Traversable f => f (TextEdit |? AnnotatedTextEdit) -> f (TextEdit |? AnnotatedTextEdit)
adjustATextEdits = fmap $ \case
InL t -> InL $ runIdentity $ adjustTextEdits (Identity t)
InR AnnotatedTextEdit{_range, _newText, _annotationId} ->
let oldTE = TextEdit{_range,_newText}
in let TextEdit{_range,_newText} = runIdentity $ adjustTextEdits (Identity oldTE)
in InR $ AnnotatedTextEdit{_range,_newText,_annotationId}
InR ate@AnnotatedTextEdit{ _annotationId } ->
InR $ annotate (runIdentity $ adjustTextEdits $ Identity $ unannotate ate) _annotationId
where
unannotate AnnotatedTextEdit{..} = TextEdit _range _newText
annotate TextEdit{..} anno = AnnotatedTextEdit _range _newText anno

adjustWS = ix uri %~ adjustTextEdits

adjustDoc :: DocumentChange -> DocumentChange
adjustDoc (InR es) = InR es
adjustDoc (InL es)
Expand All @@ -266,8 +270,10 @@ adjustToRange uri ran (WorkspaceEdit mhult mlt x) =

adjustLine :: Range -> TextEdit -> TextEdit
adjustLine bad =
J.range %~ \r ->
if r == bad then ran else bad
J.range %~ \r ->
if r == bad
then bad & J.start .~ ran ^. J.start
else bad

-- Define a pattern to get hold of a `SrcSpan` from the location part of a
-- `GenLocated`. In GHC >= 9.2 this will be a SrcSpanAnn', with annotations;
Expand Down
Loading