Skip to content

Commit afac9b1

Browse files
BurningLutzmichaelpjmergify[bot]
authored
Fix #3847 (#3854)
* Fix #3847 * Add unit test cases for `Ide.PluginUtils.extractTextInRange`. * More detailed comment about the issue. --------- Co-authored-by: Michael Peyton Jones <[email protected]> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 1c884ea commit afac9b1

File tree

2 files changed

+68
-2
lines changed

2 files changed

+68
-2
lines changed

hls-plugin-api/src/Ide/PluginUtils.hs

+14-1
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,20 @@ usePropertyLsp kn pId p = do
236236
extractTextInRange :: Range -> T.Text -> T.Text
237237
extractTextInRange (Range (Position sl sc) (Position el ec)) s = newS
238238
where
239-
focusLines = take (fromIntegral $ el - sl + 1) $ drop (fromIntegral sl) $ T.lines s
239+
focusLines =
240+
T.lines s
241+
-- NOTE: Always append an empty line to the end to ensure there are
242+
-- sufficient lines to take from.
243+
--
244+
-- There is a situation that when the end position is placed at the line
245+
-- below the last line, if we simply do `drop` and then `take`, there
246+
-- will be `el - sl` lines left, not `el - sl + 1` lines. And then
247+
-- the last line of code will be emptied unexpectedly.
248+
--
249+
-- For details, see https://github.com/haskell/haskell-language-server/issues/3847
250+
& (++ [""])
251+
& drop (fromIntegral sl)
252+
& take (fromIntegral $ el - sl + 1)
240253
-- NOTE: We have to trim the last line first to handle the single-line case
241254
newS =
242255
focusLines

hls-plugin-api/test/Ide/PluginUtilsTest.hs

+54-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import Data.Char (isPrint)
99
import qualified Data.Set as Set
1010
import qualified Data.Text as T
1111
import qualified Ide.Plugin.RangeMap as RangeMap
12-
import Ide.PluginUtils (positionInRange, unescape)
12+
import Ide.PluginUtils (extractTextInRange,
13+
positionInRange, unescape)
1314
import Language.LSP.Protocol.Types (Position (..), Range (Range),
1415
UInt, isSubrangeOf)
1516
import Test.Tasty
@@ -19,6 +20,7 @@ import Test.Tasty.QuickCheck
1920
tests :: TestTree
2021
tests = testGroup "PluginUtils"
2122
[ unescapeTest
23+
, extractTextInRangeTest
2224
, localOption (QuickCheckMaxSize 10000) $
2325
testProperty "RangeMap-List filtering identical" $
2426
prop_rangemapListEq @Int
@@ -42,6 +44,57 @@ unescapeTest = testGroup "unescape"
4244
unescape "\"\\n\\t\"" @?= "\"\\n\\t\""
4345
]
4446

47+
extractTextInRangeTest :: TestTree
48+
extractTextInRangeTest = testGroup "extractTextInRange"
49+
[ testCase "inline range" $
50+
extractTextInRange
51+
( Range (Position 0 3) (Position 3 5) )
52+
src
53+
@?= T.intercalate "\n"
54+
[ "ule Main where"
55+
, ""
56+
, "main :: IO ()"
57+
, "main "
58+
]
59+
, testCase "inline range with empty content" $
60+
extractTextInRange
61+
( Range (Position 0 0) (Position 0 1) )
62+
emptySrc
63+
@?= ""
64+
, testCase "multiline range with empty content" $
65+
extractTextInRange
66+
( Range (Position 0 0) (Position 1 0) )
67+
emptySrc
68+
@?= "\n"
69+
, testCase "multiline range" $
70+
extractTextInRange
71+
( Range (Position 1 0) (Position 4 0) )
72+
src
73+
@?= T.unlines
74+
[ ""
75+
, "main :: IO ()"
76+
, "main = do"
77+
]
78+
, testCase "multiline range with end pos at the line below the last line" $
79+
extractTextInRange
80+
( Range (Position 2 0) (Position 5 0) )
81+
src
82+
@?= T.unlines
83+
[ "main :: IO ()"
84+
, "main = do"
85+
, " putStrLn \"hello, world\""
86+
]
87+
]
88+
where
89+
src = T.unlines
90+
[ "module Main where"
91+
, ""
92+
, "main :: IO ()"
93+
, "main = do"
94+
, " putStrLn \"hello, world\""
95+
]
96+
emptySrc = "\n"
97+
4598
genRange :: Gen Range
4699
genRange = oneof [ genRangeInline, genRangeMultiline ]
47100

0 commit comments

Comments
 (0)