haskell / haskell/haskell-language-server
notes: "Internal Error: Line not found in file" when hovering the empty line after a file's final newline
- Dominant language
- Haskell
- Stars
- 3k
- Forks
- 455
- Avg merge
- 3d 21h
- Merged PRs (30d)
- 13
Description
**Version:** HLS 2.14.0.0, GHC 9.10.3, Linux. Code unchanged on `master`.
**Symptom:** hovering with the cursor at end of buffer pops up
`notes: Internal Error: Line not found in file`. With Emacs/eldoc this fires
constantly, since point rests there while typing.
**Repro:** any file ending in a newline, e.g.
```haskell
positions :: Ord a => a -> [a] -> [Int]
positions x xs = [i | (i, x') <- zip [0..] xs, x' == x]
```
textDocument/hover at {"line": 2, "character": 0} (the empty line after the
final newline) throws; hover at lines 0-1 is silent.
Cause: getNote ([Notes.hs#L113]) treats a lineAt miss as an internal
error, but lineAt is built on Rope.lines, which - "similar to
Data.Text.lines" - drops the empty line after a trailing newline. LSP counts
that line as a valid position, so clients request it legitimately.
haskell.plugin.notes.globalOn: false does not suppress it, so there is no
user-side workaround short of filtering the notification in the client.
Fix: the LSP-facing helper is the right layer; text-rope already offers the
LSP-shaped count (posLine . charLengthAsPosition).
```
--- a/ghcide/src/Development/IDE/Core/Text.hs
+++ b/ghcide/src/Development/IDE/Core/Text.hs
-import Data.Maybe (listToMaybe)
+import Data.Maybe (fromMaybe, listToMaybe)
--- | The 0-based line @n@ of @rope@, if it has one.
+-- | The 0-based line @n@ of @rope@, if the document has one. A document
+-- ending in a newline has a final empty line: LSP counts it, 'Rope.lines'
+-- (like 'Data.Text.lines') does not.
lineAt :: Word -> Rope -> Maybe Text
-lineAt n = listToMaybe . takeLineRange n n
+lineAt n rope
+ | n > Rope.posLine (Rope.charLengthAsPosition rope) = Nothing
+ | otherwise = Just $ fromMaybe "" $ listToMaybe $ takeLineRange n n rope
```
Out-of-range lines still give Nothing. takeLineRange has no
callers outside this module; lineAt has 4 — Notes.hs:293/:394 already
default Nothing to "", Completions/Logic.hs:880 has an equivalent
fromMaybe default, and Notes.hs:113 is this bug.
If you'd rather not touch ghcide, the local alternative is getNote returning
pure Nothing instead of erroring when the line is absent.
[Notes.hs#L113]: https://github.com/haskell/haskell-language-server/blob/master/plugins/hls-notes-plugin/src/Ide/Plugin/Notes.hs#L113
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in ghcide/src/Development/IDE/Core/Text.hs, then inspect lineAt and its caller at plugins/hls-notes-plugin/src/Ide/Plugin/Notes.hs:113. Review the four lineAt callers noted in the issue and run the relevant test suite. Done means hovering the empty line after a final newline no longer reports an internal error, while genuinely out-of-range lines still behave as before.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- haskell
- Domain
- devtools
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 84/100