Appropriately deal with files that don't end with a line terminator - #5058
Appropriately deal with files that don't end with a line terminator#5058Aster89 wants to merge 5 commits into
Conversation
|
A small experiment that I should probably turn into a test: $ cabal repl /home/enrico/haskell-language-server/hls-plugin-api/src/Ide/PluginUtils.hs
λ> import Language.LSP.Protocol.Types
λ> import Ide.PluginUtils
λ> :set -XOverloadedStrings
λ> uri = Uri {getUri = "file:///home/enrico/haskell-language-server/plugins/hls-class-plugin/test/testdata/T1W.hs"}
λ> verTxtDocId = VersionedTextDocumentIdentifier uri 0
λ> old = "module T1 where\n\ndata X = X\n\ninstance Eq X where\n"
λ> new = "module T1 where\n\ndata X = X\n\ninstance Eq X where\n (==) = _\n"
λ> e1 = diffText' True (verTxtDocId, old) new IncludeDeletions
λ> e1
WorkspaceEdit {_changes = Nothing, _documentChanges = Just [InL (TextDocumentEdit {_textDocument = OptionalVersionedTextDocumentIdentifier {_uri = Uri {getUri = "file:///home/enrico/haskell-language-server/plugins/hls-class-plugin/test/tes
tdata/T1W.hs"}, _version = InL 0}, _edits = [InL (TextEdit {_range = Range {_start = Position {_line = 5, _character = 0}, _end = Position {_line = 5, _character = 0}}, _newText = " (==) = _\n"})]})], _changeAnnotations = Nothing}See that the This is correct. But look what happens if we remove the trailing λ> old = "module T1 where\n\ndata X = X\n\ninstance Eq X where"
λ> new = "module T1 where\n\ndata X = X\n\ninstance Eq X where\n (==) = _"
λ> e2 = diffText' True (verTxtDocId, old) new IncludeDeletions
λ> e1 == e2
Truewhich is wrong! The These are probably the shortest reproduction steps (but λ> d1 = diffTextEdit "foo" "foo\nbar" IncludeDeletions
λ> d2 = diffTextEdit "foo\n" "foo\nbar\n" IncludeDeletions
λ> d1 == d2
True
λ> d1
[TextEdit {_range = Range {_start = Position {_line = 1, _character = 0}, _end = Position {_line = 1, _character = 0}}, _newText = "bar\n"}] |
|
Well, the bug is clearly on this line: I mean, once you've done |
|
Building on my earlier experience with text diff tools, the solution I've attempted consisted of
The idea is that point 1 allows us not to throw away the line terminators, and point 2 preserves the equality used so far. The drawback is that the resulting [Both ["foo"] ["foo\n"],Second ["bar"]]from which the current code deduces that In the context of a full-fledged text diff tool, the direction I'd take is to perform a sub-comparison between the 2 sides of the [(Both ["foo"] ["foo\n"], Just (NonEmpty [Both "foo" "foo", Second "\n"])]),(Second ["bar"], Nothing)]where the If we were to diff [(Both ["foo\n"] ["foo\n"], Nothing]),(Second ["bar\n"], Nothing)]Anyway, I've taken note of the above to avoid forgetting, but it sounds too much of a complication considering that the only time that And maybe it would break several tests. Probably a simple hack is a better approach. Looking into it. But I also have to check what happens in case an action removes the last line of a file. |
I say this solution is partial because:
1. from the perspective of text comparison, it is wrong, as
demonstrated by the two tests that fail,
2. from the perspective of our usage of it, which is from call sites¹
that guarantee (or don't they?) that we'll never get those inputs
like in the files that cause those failures, it's good enough.
---
¹ The point is that we don't ever use this `diffTextEdit` function on
two independent `Text` inputs. Those two inputs are
- the content of the source file on which HLS wants to do the change,
- the content after the change as computed via GHC's API.
As long as GHC (well, and `ghc-exactprint` after it) guarantees to honor
the line-ending policy of a file, the scenario of the tests at point 1
above should never materialize.
|
This PR, in its current state, is
What do I mean?
The point is that we never use this
As long as GHC (well, and |
Both the given and expected files don't have a line terminator at EOF.
The new test does fail, demonstrating there is a bug, just like I observe in VSCode, see GIF attached to #5059.
However, Vim+YCM and Neovim seem to be immune to it. (As far as Vim+YCM goes, I know why it's immune because I fixed another bug, ycm-core/YouCompleteMe#4311.)
I think that the test failing shows that the bug is in HLS, not in VSCode. Vim+YCM and Neovim are probably just being smart and sidestepping the bug entirely.
Fixes #5059.