Skip to content

Commit 98c079e

Browse files
committed
Switch to rope-utf16-splay for ropes
* This makes the licensing story simpler because rope-utf16-splay is BSD3 licensed. (#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).
1 parent bce0694 commit 98c079e

File tree

5 files changed

+53
-106
lines changed

5 files changed

+53
-106
lines changed

example/Main.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import qualified Language.Haskell.LSP.Utility as U
2828
import Language.Haskell.LSP.VFS
2929
import System.Exit
3030
import qualified System.Log.Logger as L
31-
import qualified Yi.Rope as Yi
31+
import qualified Data.Rope.UTF16 as Rope
3232
import Control.Lens
3333

3434

@@ -192,7 +192,7 @@ reactor lf inp = do
192192
mdoc <- liftIO $ Core.getVirtualFileFunc lf doc
193193
case mdoc of
194194
Just (VirtualFile _version str) -> do
195-
liftIO $ U.logs $ "reactor:processing NotDidChangeTextDocument: vf got:" ++ (show $ Yi.toString str)
195+
liftIO $ U.logs $ "reactor:processing NotDidChangeTextDocument: vf got:" ++ (show $ Rope.toString str)
196196
Nothing -> do
197197
liftIO $ U.logs $ "reactor:processing NotDidChangeTextDocument: vf returned Nothing"
198198

haskell-lsp.cabal

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@ library
4747
, lens >= 4.15.2
4848
, mtl
4949
, parsec
50+
, rope-utf16-splay >= 0.1
5051
, sorted-list == 0.2.0.*
5152
, stm
5253
, text
5354
, time
5455
, unordered-containers
55-
, yi-rope
5656
hs-source-dirs: src
5757
default-language: Haskell2010
5858

@@ -74,13 +74,13 @@ executable lsp-hello
7474
, lens >= 4.15.2
7575
, mtl
7676
, parsec
77+
, rope-utf16-splay >= 0.1
7778
, stm
7879
, text
7980
, time
8081
, transformers
8182
, unordered-containers
8283
, vector
83-
, yi-rope
8484
-- the package library. Comment this out if you want repl changes to propagate
8585
, haskell-lsp
8686

@@ -102,11 +102,11 @@ test-suite haskell-lsp-test
102102
-- , hspec-jenkins
103103
, lens >= 4.15.2
104104
, sorted-list == 0.2.0.*
105-
, yi-rope
106105
, haskell-lsp
107106
-- , data-default
108107
-- , bytestring
109108
-- , hslogger
109+
, rope-utf16-splay >= 0.1
110110
, text
111111
-- , unordered-containers
112112
ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall

src/Language/Haskell/LSP/VFS.hs

Lines changed: 21 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,16 @@ module Language.Haskell.LSP.VFS
1919
-- * for tests
2020
, applyChange
2121
, sortChanges
22-
, deleteChars , addChars
2322
, changeChars
24-
, yiSplitAt
2523
) where
2624

2725
import Data.Text ( Text )
2826
import Data.List
29-
import Data.Monoid
3027
import qualified Data.Map as Map
28+
import Data.Rope.UTF16 ( Rope )
29+
import qualified Data.Rope.UTF16 as Rope
3130
import qualified Language.Haskell.LSP.TH.DataTypesJSON as J
3231
import Language.Haskell.LSP.Utility
33-
import qualified Yi.Rope as Yi
3432

3533
-- ---------------------------------------------------------------------
3634
{-# ANN module ("hlint: ignore Eta reduce" :: String) #-}
@@ -40,7 +38,7 @@ import qualified Yi.Rope as Yi
4038
data VirtualFile =
4139
VirtualFile {
4240
_version :: Int
43-
, _text :: Yi.YiString
41+
, _text :: Rope
4442
} deriving (Show)
4543

4644
type VFS = Map.Map J.Uri VirtualFile
@@ -51,7 +49,7 @@ openVFS :: VFS -> J.DidOpenTextDocumentNotification -> IO VFS
5149
openVFS vfs (J.NotificationMessage _ _ params) = do
5250
let J.DidOpenTextDocumentParams
5351
(J.TextDocumentItem uri _ version text) = params
54-
return $ Map.insert uri (VirtualFile version (Yi.fromText text)) vfs
52+
return $ Map.insert uri (VirtualFile version (Rope.fromText text)) vfs
5553

5654
-- ---------------------------------------------------------------------
5755

@@ -87,92 +85,37 @@ data TextDocumentContentChangeEvent =
8785
-}
8886

8987
-- | Apply the list of changes, in descending order of range. Assuming no overlaps.
90-
applyChanges :: Yi.YiString -> [J.TextDocumentContentChangeEvent] -> Yi.YiString
88+
applyChanges :: Rope -> [J.TextDocumentContentChangeEvent] -> Rope
9189
applyChanges str changes' = r
9290
where
9391
changes = sortChanges changes'
9492
r = foldl' applyChange str changes
9593

9694
-- ---------------------------------------------------------------------
9795

98-
applyChange :: Yi.YiString -> J.TextDocumentContentChangeEvent -> Yi.YiString
96+
applyChange :: Rope -> J.TextDocumentContentChangeEvent -> Rope
9997
applyChange _ (J.TextDocumentContentChangeEvent Nothing Nothing str)
100-
= Yi.fromText str
101-
applyChange str (J.TextDocumentContentChangeEvent (Just (J.Range fm _to)) (Just len) txt) =
102-
if txt == ""
103-
then -- delete len chars from fm
104-
deleteChars str fm len
105-
else -- add or change, based on length
106-
if len == 0
107-
then addChars str fm txt
108-
-- Note: changeChars comes from applyEdit, emacs will split it into a
109-
-- delete and an add
110-
else changeChars str fm len txt
111-
applyChange str (J.TextDocumentContentChangeEvent (Just r@(J.Range (J.Position sl sc) (J.Position el ec))) Nothing txt)
112-
= applyChange str (J.TextDocumentContentChangeEvent (Just r) (Just len) txt)
113-
where len = Yi.length region
114-
(beforeEnd, afterEnd) = Yi.splitAtLine el str
115-
lastLine = Yi.take ec afterEnd
116-
lastLine' | sl == el = Yi.drop sc lastLine
117-
| otherwise = lastLine
118-
(_beforeStart, afterStartBeforeEnd) = Yi.splitAtLine sl beforeEnd
119-
region = Yi.drop sc afterStartBeforeEnd <> lastLine'
120-
applyChange str (J.TextDocumentContentChangeEvent Nothing (Just _) _txt)
121-
= str
122-
123-
-- ---------------------------------------------------------------------
124-
125-
deleteChars :: Yi.YiString -> J.Position -> Int -> Yi.YiString
126-
deleteChars str (J.Position l c) len = str'
127-
where
128-
(before,after) = Yi.splitAtLine l str
129-
-- after contains the area we care about, starting with the selected line.
130-
-- Due to LSP zero-based coordinates
131-
beforeOnLine = Yi.take c after
132-
after' = Yi.drop (c + len) after
133-
str' = Yi.append before (Yi.append beforeOnLine after')
134-
135-
-- ---------------------------------------------------------------------
136-
137-
addChars :: Yi.YiString -> J.Position -> Text -> Yi.YiString
138-
addChars str (J.Position l c) new = str'
98+
= Rope.fromText str
99+
applyChange str (J.TextDocumentContentChangeEvent (Just (J.Range (J.Position sl sc) _to)) (Just len) txt)
100+
= changeChars str start len txt
139101
where
140-
(before,after) = Yi.splitAtLine l str
141-
-- after contains the area we care about, starting with the selected line.
142-
-- Due to LSP zero-based coordinates
143-
beforeOnLine = Yi.take c after
144-
after' = Yi.drop c after
145-
str' = Yi.concat [before, beforeOnLine, (Yi.fromText new), after']
146-
147-
-- ---------------------------------------------------------------------
148-
149-
changeChars :: Yi.YiString -> J.Position -> Int -> Text -> Yi.YiString
150-
changeChars str (J.Position ls cs) len new = str'
102+
start = Rope.rowColumnCodePoints (Rope.RowColumn sl sc) str
103+
applyChange str (J.TextDocumentContentChangeEvent (Just (J.Range (J.Position sl sc) (J.Position el ec))) Nothing txt)
104+
= changeChars str start len txt
151105
where
152-
(before,after) = yiSplitAt ls cs str
153-
after' = Yi.drop len after
154-
155-
str' = Yi.concat [before, (Yi.fromText new), after']
156-
157-
-- changeChars :: Yi.YiString -> J.Position -> J.Position -> String -> Yi.YiString
158-
-- changeChars str (J.Position ls cs) (J.Position le ce) new = str'
159-
-- where
160-
-- (before,_after) = yiSplitAt ls cs str
161-
-- (_before,after) = yiSplitAt le ce str
162-
163-
-- str' = Yi.concat [before, (Yi.fromString new), after]
164-
-- -- str' = Yi.concat [before]
165-
-- -- str' = Yi.concat [_before]
106+
start = Rope.rowColumnCodePoints (Rope.RowColumn sl sc) str
107+
end = Rope.rowColumnCodePoints (Rope.RowColumn el ec) str
108+
len = end - start
109+
applyChange str (J.TextDocumentContentChangeEvent Nothing (Just _) _txt)
110+
= str
166111

167112
-- ---------------------------------------------------------------------
168113

169-
yiSplitAt :: Int -> Int -> Yi.YiString -> (Yi.YiString, Yi.YiString)
170-
yiSplitAt l c str = (before,after)
114+
changeChars :: Rope -> Int -> Int -> Text -> Rope
115+
changeChars str start len new = mconcat [before, Rope.fromText new, after']
171116
where
172-
(b,a) = Yi.splitAtLine l str
173-
before = Yi.concat [b,Yi.take c a]
174-
after = Yi.drop c a
175-
117+
(before, after) = Rope.splitAt start str
118+
after' = Rope.drop len after
176119

177120
-- ---------------------------------------------------------------------
178121

stack.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ resolver: lts-10.1 # GHC 8.2.2 version
33

44
packages:
55
- '.'
6+
extra-deps: [rope-utf16-splay-0.1.0.0]
67
flags: {}
78
extra-package-dbs: []
89
nix:

test/VspSpec.hs

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
module VspSpec where
33

44

5+
import Data.String
6+
import qualified Data.Rope.UTF16 as Rope
57
import Language.Haskell.LSP.VFS
68
import qualified Language.Haskell.LSP.TH.DataTypesJSON as J
7-
import qualified Yi.Rope as Yi
89

910
import Test.Hspec
1011

@@ -56,9 +57,9 @@ vspSpec = do
5657
, "-- fooo"
5758
, "foo :: Int"
5859
]
59-
new = applyChange (Yi.fromString orig)
60+
new = applyChange (fromString orig)
6061
$ J.TextDocumentContentChangeEvent (mkRange 2 1 2 5) (Just 4) ""
61-
lines (Yi.toString new) `shouldBe`
62+
lines (Rope.toString new) `shouldBe`
6263
[ "abcdg"
6364
, "module Foo where"
6465
, "-oo"
@@ -73,9 +74,9 @@ vspSpec = do
7374
, "-- fooo"
7475
, "foo :: Int"
7576
]
76-
new = applyChange (Yi.fromString orig)
77+
new = applyChange (fromString orig)
7778
$ J.TextDocumentContentChangeEvent (mkRange 2 1 2 5) Nothing ""
78-
lines (Yi.toString new) `shouldBe`
79+
lines (Rope.toString new) `shouldBe`
7980
[ "abcdg"
8081
, "module Foo where"
8182
, "-oo"
@@ -93,9 +94,9 @@ vspSpec = do
9394
, "-- fooo"
9495
, "foo :: Int"
9596
]
96-
new = applyChange (Yi.fromString orig)
97+
new = applyChange (fromString orig)
9798
$ J.TextDocumentContentChangeEvent (mkRange 2 0 3 0) (Just 8) ""
98-
lines (Yi.toString new) `shouldBe`
99+
lines (Rope.toString new) `shouldBe`
99100
[ "abcdg"
100101
, "module Foo where"
101102
, "foo :: Int"
@@ -110,9 +111,9 @@ vspSpec = do
110111
, "-- fooo"
111112
, "foo :: Int"
112113
]
113-
new = applyChange (Yi.fromString orig)
114+
new = applyChange (fromString orig)
114115
$ J.TextDocumentContentChangeEvent (mkRange 2 0 3 0) Nothing ""
115-
lines (Yi.toString new) `shouldBe`
116+
lines (Rope.toString new) `shouldBe`
116117
[ "abcdg"
117118
, "module Foo where"
118119
, "foo :: Int"
@@ -128,9 +129,9 @@ vspSpec = do
128129
, "foo :: Int"
129130
, "foo = bb"
130131
]
131-
new = applyChange (Yi.fromString orig)
132+
new = applyChange (fromString orig)
132133
$ J.TextDocumentContentChangeEvent (mkRange 1 0 3 0) (Just 19) ""
133-
lines (Yi.toString new) `shouldBe`
134+
lines (Rope.toString new) `shouldBe`
134135
[ "module Foo where"
135136
, "foo = bb"
136137
]
@@ -144,9 +145,9 @@ vspSpec = do
144145
, "foo :: Int"
145146
, "foo = bb"
146147
]
147-
new = applyChange (Yi.fromString orig)
148+
new = applyChange (fromString orig)
148149
$ J.TextDocumentContentChangeEvent (mkRange 1 0 3 0) Nothing ""
149-
lines (Yi.toString new) `shouldBe`
150+
lines (Rope.toString new) `shouldBe`
150151
[ "module Foo where"
151152
, "foo = bb"
152153
]
@@ -161,8 +162,9 @@ vspSpec = do
161162
, "module Foo where"
162163
, "foo :: Int"
163164
]
164-
new = addChars (Yi.fromString orig) (J.Position 1 16) "\n-- fooo"
165-
lines (Yi.toString new) `shouldBe`
165+
new = applyChange (fromString orig)
166+
$ J.TextDocumentContentChangeEvent (mkRange 1 16 1 16) (Just 0) "\n-- fooo"
167+
lines (Rope.toString new) `shouldBe`
166168
[ "abcdg"
167169
, "module Foo where"
168170
, "-- fooo"
@@ -178,8 +180,9 @@ vspSpec = do
178180
[ "module Foo where"
179181
, "foo = bb"
180182
]
181-
new = addChars (Yi.fromString orig) (J.Position 1 8) "\n-- fooo\nfoo :: Int"
182-
lines (Yi.toString new) `shouldBe`
183+
new = applyChange (fromString orig)
184+
$ J.TextDocumentContentChangeEvent (mkRange 1 8 1 8) Nothing "\n-- fooo\nfoo :: Int"
185+
lines (Rope.toString new) `shouldBe`
183186
[ "module Foo where"
184187
, "foo = bb"
185188
, "-- fooo"
@@ -203,10 +206,10 @@ vspSpec = do
203206
, "baz = do"
204207
, " putStrLn \"hello world\""
205208
]
206-
-- new = changeChars (Yi.fromString orig) (J.Position 7 0) (J.Position 7 8) "baz ="
207-
new = applyChange (Yi.fromString orig)
209+
-- new = changeChars (fromString orig) (J.Position 7 0) (J.Position 7 8) "baz ="
210+
new = applyChange (fromString orig)
208211
$ J.TextDocumentContentChangeEvent (mkRange 7 0 7 8) (Just 8) "baz ="
209-
lines (Yi.toString new) `shouldBe`
212+
lines (Rope.toString new) `shouldBe`
210213
[ "module Foo where"
211214
, "-- fooo"
212215
, "foo :: Int"
@@ -231,10 +234,10 @@ vspSpec = do
231234
, "baz = do"
232235
, " putStrLn \"hello world\""
233236
]
234-
-- new = changeChars (Yi.fromString orig) (J.Position 7 0) (J.Position 7 8) "baz ="
235-
new = applyChange (Yi.fromString orig)
237+
-- new = changeChars (fromString orig) (J.Position 7 0) (J.Position 7 8) "baz ="
238+
new = applyChange (fromString orig)
236239
$ J.TextDocumentContentChangeEvent (mkRange 7 0 7 8) Nothing "baz ="
237-
lines (Yi.toString new) `shouldBe`
240+
lines (Rope.toString new) `shouldBe`
238241
[ "module Foo where"
239242
, "-- fooo"
240243
, "foo :: Int"

0 commit comments

Comments
 (0)