-
Notifications
You must be signed in to change notification settings - Fork 93
Support text-2.0 #392
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support text-2.0 #392
Conversation
Thanks for the pr. For things like this i miss some sort of benchmark here. We could use the hls benchmark making ghcide use this pr though. |
Nice! Will |
Could you point me to a specific benchmark please?
Of course, it is compatible with both already. |
Sure, it is the benchmark we use for ghcide: https://github.com/haskell/haskell-language-server/blob/9c2bc32875ae0bb6871d7cc06547da129cf4ff5f/ghcide/ghcide.cabal#L449 in our ci |
(before, after) = Rope.splitAt start str | ||
after' = Rope.drop len after | ||
(before, after) = fromJust $ Rope.splitAtPosition finish str | ||
(before', _) = fromJust $ Rope.splitAtPosition start before |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the best way to handle malformed input here? Since LSP counts by UTF-16 code units (not code points), it is possible that split happens in the middle of a code point, in which case text-lines
returns Nothing
.
(rope-utf16-splay
in such situation silently extends split region until the end of a code point)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@alanz ? do you remember what's up here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope. To be honest I have never really paid attention to issues of splits in the middle of code points, assuming the client will send meaningful input, since they are managing a cursor over the text and sending its positions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could throw. Maybe blowing up with an informative error message is the most sensible thing to do here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would be happy with either:
- Throwing, based on judging this to be very unlikely.
- Returning
Nothing
, and propagating that up to the exported functions. InchangeFromClientVFS
we can drop the change and log, as is done in the existingNothing
branch.
Thoughts @alanz ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, we can merge this with the fromJust
and experiment with removing it with @Bodigrim out of the loop :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking more at the code, I think we could push these failures right the way up to the LSP message response, which is probably the right thing to do, but I think we should do it separately. So my vote goes for "leave fromJust
for now and remove subsequently", which I volunteer to look into.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is unlikely to happen in practice, so leaving as is would likely be safe, as is pushing it up to an LSP response. For the latter it at least makes us aware it is happening, and hopefully that will show up in initial developer dogfooding, or not at all.
$ fst $ Rope.splitAtLine 1 $ snd $ Rope.splitAtLine (fromIntegral l) ropetext | ||
let beforePos = T.take (fromIntegral c) curLine | ||
let curRope = fst $ Rope.splitAtLine 1 $ snd $ Rope.splitAtLine (fromIntegral l) ropetext | ||
beforePos <- Rope.toText . fst <$> Rope.splitAt (fromIntegral c) curRope |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW this fixes an existing bug: T.take
counts code points, while LSP requires counting UTF-16 code units.
Benchmarks adapted from https://github.com/ollef/rope-bench/blob/main/bench/Bench.hs show that
|
@Bodigrim do you feel motivated to maintain |
I think I'm beyond the point of no return :) and will release and maintain it anyway. More benchmarks, now with a realistic payload mimicking
|
Hard to argue with those performance numbers. 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, so long as we resolve the question of what to do with malformed input.
lsp-types/src/Language/LSP/VFS.hs
Outdated
@@ -357,6 +348,7 @@ getCompletionPrefix pos@(J.Position l c) (VirtualFile _ _ ropetext) = | |||
let modParts = dropWhile (not . isUpper . T.head) | |||
$ reverse $ filter (not .T.null) xs | |||
modName = T.intercalate "." modParts | |||
curLine <- headMaybe $ T.lines $ Rope.toText curRope |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use Rope.lines
? Won't that be faster?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
curRope
is already a single line, but it likely includes an enclosing \n
. The only purpose of headMaybe . T.lines . Rope.toText
is to strip this \n
. I've changed it to a more explicit version.
lsp/test/VspSpec.hs
Outdated
@@ -65,7 +64,7 @@ vspSpec = do | |||
] | |||
new = applyChange (fromString orig) | |||
$ J.TextDocumentContentChangeEvent (Just $ J.mkRange 2 1 2 5) (Just 4) "" | |||
lines (Rope.toString new) `shouldBe` | |||
lines (T.unpack $ Rope.toText new) `shouldBe` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these could all be
lines (T.unpack $ Rope.toText new) `shouldBe` | |
Rope.lines new `shouldBe` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent idea!
(before, after) = Rope.splitAt start str | ||
after' = Rope.drop len after | ||
(before, after) = fromJust $ Rope.splitAtPosition finish str | ||
(before', _) = fromJust $ Rope.splitAtPosition start before |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would be happy with either:
- Throwing, based on judging this to be very unlikely.
- Returning
Nothing
, and propagating that up to the exported functions. InchangeFromClientVFS
we can drop the change and log, as is done in the existingNothing
branch.
Thoughts @alanz ?
Closes #391.
This is a draft, need to handle
fromJust
more gracefully (and publishtext-lines
on Hackage).I tried adapting
rope-utf16-splay
totext-2.0
, but did not succeed: the changes required are too radical to its API. Hence a switch totext-lines
.