rzk-0.11.0: test/Rzk/PositionEncodingSpec.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Position conversion at the LSP boundary: code points vs UTF-16 units
-- (see issue #303). An astral-plane character such as @π@ counts as one code
-- point but two UTF-16 units, so every position to its right on the same
-- line differs between the two encodings.
module Rzk.PositionEncodingSpec (spec) where
import Test.Hspec
#ifdef LSP_ENABLED
import qualified Data.Text as T
import Language.LSP.Protocol.Types (SemanticTokenAbsolute (..))
import Language.Rzk.VSCode.PositionEncoding
import Language.Rzk.VSCode.Tokenize (tokenizeSyntaxSymbols)
-- | @abπcd@: code points a0 b1 π2 c3 d4; UTF-16 a0 b1 π2 c4 d5.
astralLine :: T.Text
astralLine = "ab\120128cd"
spec :: Spec
spec = do
describe "column conversion" $ do
let als = astralLines astralLine
it "is the identity before an astral character" $ do
colToUtf16 als 0 2 `shouldBe` 2
colFromUtf16 als 0 2 `shouldBe` 2
it "shifts by one per astral character to the left" $ do
colToUtf16 als 0 3 `shouldBe` 4
colToUtf16 als 0 5 `shouldBe` 6
colFromUtf16 als 0 4 `shouldBe` 3
colFromUtf16 als 0 6 `shouldBe` 5
it "maps a column inside a surrogate pair to the character start" $
colFromUtf16 als 0 3 `shouldBe` 2
it "keeps the distance past the end of the line" $
-- The diagnostics code uses column 99 for "to the end of the line".
colToUtf16 als 0 99 `shouldBe` 100
it "is the identity on lines without astral characters" $ do
let alsAscii = astralLines "abcd\nefgh"
colToUtf16 alsAscii 1 3 `shouldBe` 3
colFromUtf16 alsAscii 1 3 `shouldBe` 3
it "round-trips on character boundaries" $ do
let cols = [0 .. 6]
[ colFromUtf16 als 0 (colToUtf16 als 0 c) | c <- cols ] `shouldBe` cols
describe "utf16Length" $
it "counts an astral character as two units" $
utf16Length astralLine `shouldBe` 6
describe "semantic tokens" $ do
-- @#define f (x : π) : π := x@ β π at code points 15 and 20, @:=@ at 22.
let src = "#lang rzk-1\n#define f (x : \120128) : \120128 := x\n"
als = astralLines src
toks = tokensToUtf16 als (tokenizeSyntaxSymbols src)
spansAt line = [ (_startChar t, _length t) | t <- toks, _line t == line ]
it "reports the UTF-16 width of an astral token" $
-- The first π: same start (nothing astral before it), doubled width.
spansAt 1 `shouldSatisfy` elem (15, 2)
it "shifts tokens to the right of an astral character" $ do
-- The second π (code point 20) has one astral character before it,
-- and @:=@ (code point 22) has two.
spansAt 1 `shouldSatisfy` elem (21, 2)
spansAt 1 `shouldSatisfy` elem (24, 2)
it "does not move tokens on other lines" $
spansAt 0 `shouldSatisfy` elem (0, 5) -- #lang
#else
spec :: Spec
spec = describe "position encoding" (pure ())
#endif