From c7d904d08c06bcf866fc33a529af53d8470ceec7 Mon Sep 17 00:00:00 2001 From: Olle Fredriksson Date: Sat, 10 Feb 2018 23:47:48 +0100 Subject: [PATCH] Switch to rope-utf16-splay for ropes * This makes the licensing story simpler because rope-utf16-splay is BSD3 licensed. (https://github.com/alanz/haskell-lsp/issues/16) * LSP uses UTF-16 code point based indexing, while the Yi rope library indexes by characters. This means that haskell-lsp would previously not correctly count characters that are encoded as two code points in UTF-16. The rope-utf16-splay library uses code point indexing, which fixes this issue. * From my benchmarks, this new library, which uses splay trees internally, can be about twice as fast as finger tree based ones (like the Yi one) for use cases that are similar to what haskell-lsp might be doing (many consecutive modifications). --- example/Main.hs | 4 +- haskell-lsp.cabal | 6 +- src/Language/Haskell/LSP/VFS.hs | 98 +++++++-------------------------- stack.yaml | 1 + test/VspSpec.hs | 53 +++++++++--------- 5 files changed, 55 insertions(+), 107 deletions(-) diff --git a/example/Main.hs b/example/Main.hs index 7f0b1539b..97932e284 100644 --- a/example/Main.hs +++ b/example/Main.hs @@ -28,7 +28,7 @@ import qualified Language.Haskell.LSP.Utility as U import Language.Haskell.LSP.VFS import System.Exit import qualified System.Log.Logger as L -import qualified Yi.Rope as Yi +import qualified Data.Rope.UTF16 as Rope import Control.Lens @@ -192,7 +192,7 @@ reactor lf inp = do mdoc <- liftIO $ Core.getVirtualFileFunc lf doc case mdoc of Just (VirtualFile _version str) -> do - liftIO $ U.logs $ "reactor:processing NotDidChangeTextDocument: vf got:" ++ (show $ Yi.toString str) + liftIO $ U.logs $ "reactor:processing NotDidChangeTextDocument: vf got:" ++ (show $ Rope.toString str) Nothing -> do liftIO $ U.logs $ "reactor:processing NotDidChangeTextDocument: vf returned Nothing" diff --git a/haskell-lsp.cabal b/haskell-lsp.cabal index fca76283d..69a5d5172 100644 --- a/haskell-lsp.cabal +++ b/haskell-lsp.cabal @@ -47,12 +47,12 @@ library , lens >= 4.15.2 , mtl , parsec + , rope-utf16-splay >= 0.1 , sorted-list == 0.2.0.* , stm , text , time , unordered-containers - , yi-rope hs-source-dirs: src default-language: Haskell2010 @@ -74,13 +74,13 @@ executable lsp-hello , lens >= 4.15.2 , mtl , parsec + , rope-utf16-splay >= 0.1 , stm , text , time , transformers , unordered-containers , vector - , yi-rope -- the package library. Comment this out if you want repl changes to propagate , haskell-lsp @@ -102,11 +102,11 @@ test-suite haskell-lsp-test -- , hspec-jenkins , lens >= 4.15.2 , sorted-list == 0.2.0.* - , yi-rope , haskell-lsp -- , data-default -- , bytestring -- , hslogger + , rope-utf16-splay >= 0.1 , text -- , unordered-containers ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall diff --git a/src/Language/Haskell/LSP/VFS.hs b/src/Language/Haskell/LSP/VFS.hs index e3dad09b1..8f4c51a5c 100644 --- a/src/Language/Haskell/LSP/VFS.hs +++ b/src/Language/Haskell/LSP/VFS.hs @@ -19,18 +19,17 @@ module Language.Haskell.LSP.VFS -- * for tests , applyChange , sortChanges - , deleteChars , addChars , changeChars - , yiSplitAt ) where import Data.Text ( Text ) import Data.List import Data.Monoid import qualified Data.Map as Map +import Data.Rope.UTF16 ( Rope ) +import qualified Data.Rope.UTF16 as Rope import qualified Language.Haskell.LSP.TH.DataTypesJSON as J import Language.Haskell.LSP.Utility -import qualified Yi.Rope as Yi -- --------------------------------------------------------------------- {-# ANN module ("hlint: ignore Eta reduce" :: String) #-} @@ -40,7 +39,7 @@ import qualified Yi.Rope as Yi data VirtualFile = VirtualFile { _version :: Int - , _text :: Yi.YiString + , _text :: Rope } deriving (Show) type VFS = Map.Map J.Uri VirtualFile @@ -51,7 +50,7 @@ openVFS :: VFS -> J.DidOpenTextDocumentNotification -> IO VFS openVFS vfs (J.NotificationMessage _ _ params) = do let J.DidOpenTextDocumentParams (J.TextDocumentItem uri _ version text) = params - return $ Map.insert uri (VirtualFile version (Yi.fromText text)) vfs + return $ Map.insert uri (VirtualFile version (Rope.fromText text)) vfs -- --------------------------------------------------------------------- @@ -87,7 +86,7 @@ data TextDocumentContentChangeEvent = -} -- | Apply the list of changes, in descending order of range. Assuming no overlaps. -applyChanges :: Yi.YiString -> [J.TextDocumentContentChangeEvent] -> Yi.YiString +applyChanges :: Rope -> [J.TextDocumentContentChangeEvent] -> Rope applyChanges str changes' = r where changes = sortChanges changes' @@ -95,84 +94,29 @@ applyChanges str changes' = r -- --------------------------------------------------------------------- -applyChange :: Yi.YiString -> J.TextDocumentContentChangeEvent -> Yi.YiString +applyChange :: Rope -> J.TextDocumentContentChangeEvent -> Rope applyChange _ (J.TextDocumentContentChangeEvent Nothing Nothing str) - = Yi.fromText str -applyChange str (J.TextDocumentContentChangeEvent (Just (J.Range fm _to)) (Just len) txt) = - if txt == "" - then -- delete len chars from fm - deleteChars str fm len - else -- add or change, based on length - if len == 0 - then addChars str fm txt - -- Note: changeChars comes from applyEdit, emacs will split it into a - -- delete and an add - else changeChars str fm len txt -applyChange str (J.TextDocumentContentChangeEvent (Just r@(J.Range (J.Position sl sc) (J.Position el ec))) Nothing txt) - = applyChange str (J.TextDocumentContentChangeEvent (Just r) (Just len) txt) - where len = Yi.length region - (beforeEnd, afterEnd) = Yi.splitAtLine el str - lastLine = Yi.take ec afterEnd - lastLine' | sl == el = Yi.drop sc lastLine - | otherwise = lastLine - (_beforeStart, afterStartBeforeEnd) = Yi.splitAtLine sl beforeEnd - region = Yi.drop sc afterStartBeforeEnd <> lastLine' -applyChange str (J.TextDocumentContentChangeEvent Nothing (Just _) _txt) - = str - --- --------------------------------------------------------------------- - -deleteChars :: Yi.YiString -> J.Position -> Int -> Yi.YiString -deleteChars str (J.Position l c) len = str' - where - (before,after) = Yi.splitAtLine l str - -- after contains the area we care about, starting with the selected line. - -- Due to LSP zero-based coordinates - beforeOnLine = Yi.take c after - after' = Yi.drop (c + len) after - str' = Yi.append before (Yi.append beforeOnLine after') - --- --------------------------------------------------------------------- - -addChars :: Yi.YiString -> J.Position -> Text -> Yi.YiString -addChars str (J.Position l c) new = str' + = Rope.fromText str +applyChange str (J.TextDocumentContentChangeEvent (Just (J.Range (J.Position sl sc) _to)) (Just len) txt) + = changeChars str start len txt where - (before,after) = Yi.splitAtLine l str - -- after contains the area we care about, starting with the selected line. - -- Due to LSP zero-based coordinates - beforeOnLine = Yi.take c after - after' = Yi.drop c after - str' = Yi.concat [before, beforeOnLine, (Yi.fromText new), after'] - --- --------------------------------------------------------------------- - -changeChars :: Yi.YiString -> J.Position -> Int -> Text -> Yi.YiString -changeChars str (J.Position ls cs) len new = str' + start = Rope.rowColumnCodePoints (Rope.RowColumn sl sc) str +applyChange str (J.TextDocumentContentChangeEvent (Just (J.Range (J.Position sl sc) (J.Position el ec))) Nothing txt) + = changeChars str start len txt where - (before,after) = yiSplitAt ls cs str - after' = Yi.drop len after - - str' = Yi.concat [before, (Yi.fromText new), after'] - --- changeChars :: Yi.YiString -> J.Position -> J.Position -> String -> Yi.YiString --- changeChars str (J.Position ls cs) (J.Position le ce) new = str' --- where --- (before,_after) = yiSplitAt ls cs str --- (_before,after) = yiSplitAt le ce str - --- str' = Yi.concat [before, (Yi.fromString new), after] --- -- str' = Yi.concat [before] --- -- str' = Yi.concat [_before] + start = Rope.rowColumnCodePoints (Rope.RowColumn sl sc) str + end = Rope.rowColumnCodePoints (Rope.RowColumn el ec) str + len = end - start +applyChange str (J.TextDocumentContentChangeEvent Nothing (Just _) _txt) + = str -- --------------------------------------------------------------------- -yiSplitAt :: Int -> Int -> Yi.YiString -> (Yi.YiString, Yi.YiString) -yiSplitAt l c str = (before,after) +changeChars :: Rope -> Int -> Int -> Text -> Rope +changeChars str start len new = mconcat [before, Rope.fromText new, after'] where - (b,a) = Yi.splitAtLine l str - before = Yi.concat [b,Yi.take c a] - after = Yi.drop c a - + (before, after) = Rope.splitAt start str + after' = Rope.drop len after -- --------------------------------------------------------------------- diff --git a/stack.yaml b/stack.yaml index 485dcb869..6490d56e4 100644 --- a/stack.yaml +++ b/stack.yaml @@ -3,6 +3,7 @@ resolver: lts-10.1 # GHC 8.2.2 version packages: - '.' +extra-deps: [rope-utf16-splay-0.1.0.0] flags: {} extra-package-dbs: [] nix: diff --git a/test/VspSpec.hs b/test/VspSpec.hs index 78481eb34..e977eaf4c 100644 --- a/test/VspSpec.hs +++ b/test/VspSpec.hs @@ -2,9 +2,10 @@ module VspSpec where +import Data.String +import qualified Data.Rope.UTF16 as Rope import Language.Haskell.LSP.VFS import qualified Language.Haskell.LSP.TH.DataTypesJSON as J -import qualified Yi.Rope as Yi import Test.Hspec @@ -56,9 +57,9 @@ vspSpec = do , "-- fooo" , "foo :: Int" ] - new = applyChange (Yi.fromString orig) + new = applyChange (fromString orig) $ J.TextDocumentContentChangeEvent (mkRange 2 1 2 5) (Just 4) "" - lines (Yi.toString new) `shouldBe` + lines (Rope.toString new) `shouldBe` [ "abcdg" , "module Foo where" , "-oo" @@ -73,9 +74,9 @@ vspSpec = do , "-- fooo" , "foo :: Int" ] - new = applyChange (Yi.fromString orig) + new = applyChange (fromString orig) $ J.TextDocumentContentChangeEvent (mkRange 2 1 2 5) Nothing "" - lines (Yi.toString new) `shouldBe` + lines (Rope.toString new) `shouldBe` [ "abcdg" , "module Foo where" , "-oo" @@ -93,9 +94,9 @@ vspSpec = do , "-- fooo" , "foo :: Int" ] - new = applyChange (Yi.fromString orig) + new = applyChange (fromString orig) $ J.TextDocumentContentChangeEvent (mkRange 2 0 3 0) (Just 8) "" - lines (Yi.toString new) `shouldBe` + lines (Rope.toString new) `shouldBe` [ "abcdg" , "module Foo where" , "foo :: Int" @@ -110,9 +111,9 @@ vspSpec = do , "-- fooo" , "foo :: Int" ] - new = applyChange (Yi.fromString orig) + new = applyChange (fromString orig) $ J.TextDocumentContentChangeEvent (mkRange 2 0 3 0) Nothing "" - lines (Yi.toString new) `shouldBe` + lines (Rope.toString new) `shouldBe` [ "abcdg" , "module Foo where" , "foo :: Int" @@ -128,9 +129,9 @@ vspSpec = do , "foo :: Int" , "foo = bb" ] - new = applyChange (Yi.fromString orig) + new = applyChange (fromString orig) $ J.TextDocumentContentChangeEvent (mkRange 1 0 3 0) (Just 19) "" - lines (Yi.toString new) `shouldBe` + lines (Rope.toString new) `shouldBe` [ "module Foo where" , "foo = bb" ] @@ -144,9 +145,9 @@ vspSpec = do , "foo :: Int" , "foo = bb" ] - new = applyChange (Yi.fromString orig) + new = applyChange (fromString orig) $ J.TextDocumentContentChangeEvent (mkRange 1 0 3 0) Nothing "" - lines (Yi.toString new) `shouldBe` + lines (Rope.toString new) `shouldBe` [ "module Foo where" , "foo = bb" ] @@ -161,8 +162,9 @@ vspSpec = do , "module Foo where" , "foo :: Int" ] - new = addChars (Yi.fromString orig) (J.Position 1 16) "\n-- fooo" - lines (Yi.toString new) `shouldBe` + new = applyChange (fromString orig) + $ J.TextDocumentContentChangeEvent (mkRange 1 16 1 16) (Just 0) "\n-- fooo" + lines (Rope.toString new) `shouldBe` [ "abcdg" , "module Foo where" , "-- fooo" @@ -178,8 +180,9 @@ vspSpec = do [ "module Foo where" , "foo = bb" ] - new = addChars (Yi.fromString orig) (J.Position 1 8) "\n-- fooo\nfoo :: Int" - lines (Yi.toString new) `shouldBe` + new = applyChange (fromString orig) + $ J.TextDocumentContentChangeEvent (mkRange 1 8 1 8) Nothing "\n-- fooo\nfoo :: Int" + lines (Rope.toString new) `shouldBe` [ "module Foo where" , "foo = bb" , "-- fooo" @@ -203,10 +206,10 @@ vspSpec = do , "baz = do" , " putStrLn \"hello world\"" ] - -- new = changeChars (Yi.fromString orig) (J.Position 7 0) (J.Position 7 8) "baz =" - new = applyChange (Yi.fromString orig) + -- new = changeChars (fromString orig) (J.Position 7 0) (J.Position 7 8) "baz =" + new = applyChange (fromString orig) $ J.TextDocumentContentChangeEvent (mkRange 7 0 7 8) (Just 8) "baz =" - lines (Yi.toString new) `shouldBe` + lines (Rope.toString new) `shouldBe` [ "module Foo where" , "-- fooo" , "foo :: Int" @@ -231,10 +234,10 @@ vspSpec = do , "baz = do" , " putStrLn \"hello world\"" ] - -- new = changeChars (Yi.fromString orig) (J.Position 7 0) (J.Position 7 8) "baz =" - new = applyChange (Yi.fromString orig) + -- new = changeChars (fromString orig) (J.Position 7 0) (J.Position 7 8) "baz =" + new = applyChange (fromString orig) $ J.TextDocumentContentChangeEvent (mkRange 7 0 7 8) Nothing "baz =" - lines (Yi.toString new) `shouldBe` + lines (Rope.toString new) `shouldBe` [ "module Foo where" , "-- fooo" , "foo :: Int" @@ -251,9 +254,9 @@ vspSpec = do [ "a𐐀b" , "a𐐀b" ] - new = applyChange (Yi.fromString orig) + new = applyChange (fromString orig) $ J.TextDocumentContentChangeEvent (mkRange 1 0 1 3) (Just 3) "𐐀𐐀" - lines (Yi.toString new) `shouldBe` + lines (Rope.toString new) `shouldBe` [ "a𐐀b" , "𐐀𐐀b" ]