haskell / haskell/haskell-language-server

notes: "Internal Error: Line not found in file" when hovering the empty line after a file's final newline

Open Beginner friendly
#5,063 3 comments 0 reactions 0 assignees View on GitHub
component: hls-notes-plugin level: easy type: bug
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.