haskell / haskell-language-server

Official haskell ide support via language server (LSP). Successor of ghcide & haskell-ide-engine.
Apache License 2.0
2.72k stars 368 forks source link

Incorrect symbol range from documentSymbol #2898

Open stevearc opened 2 years ago

stevearc commented 2 years ago

Your environment

Which OS do you use: Ubuntu, though this has also been seen on MacOS

Which LSP client (editor/plugin) do you use: Neovim builtin

Describe your project (alternative: link to the project): Repros in minimal project, detailed below

Steps to reproduce

Main.hs:

module Main where

main :: IO ()
main = putStrLn "Hello, Haskell!"

Expected behaviour

Data returned should match the file

Actual behaviour

{ {
    children = { {
        kind = 12,
        name = "main",
        range = {
          end = {
            character = 33,
            line = 3
          },
          start = {
            character = 0,
            line = 3
          }
        },
        selectionRange = {
          end = {
            character = 33,
            line = 3
          },
          start = {
            character = 0,
            line = 3
          }
        }
      } },
    kind = 1,
    name = "Main",
    range = {
      end = {
        character = 0,
        line = 2147483647
      },
      start = {
        character = 0,
        line = 0
      }
    },
    selectionRange = {
      end = {
        character = 11,
        line = 0
      },
      start = {
        character = 7,
        line = 0
      }
    }
  } }

The line = 2147483647 is the problem. It was causing issues with a plugin I wrote because I wasn't being defensive when processing the line numbers. https://github.com/stevearc/aerial.nvim/issues/101

Include debug information

michaelpj commented 2 years ago

I don't really know why we do this instead of giving the right end position, but here's the culprit: https://github.com/haskell/haskell-language-server/blob/master/ghcide/src/Development/IDE/LSP/Outline.hs#L52

michaelpj commented 2 years ago

A priori it doesn't seem like it should be too hard to just give an accurate end to the module. Good first issue, perhaps. At least if it's hard we could find out why and document it.

coltenwebb commented 2 years ago

I'll try taking a look at this- it seems like range should be equal to selectionRange, at least for this example. I'll see if something breaks when doing this

coltenwebb commented 2 years ago

So the reason selectionRange != range is that all the children of a given DocumentSymbol need to be sub-spans of the parent's range for editor tools like document outline to work. So range really does need to span the whole file.

Getting an accurate end to the module isn't straightforward. GHC doesn't give a span for the whole module with ParsedModule. It's returning an empty source string from ModSummary for some reason too, so I'm unable to count the lines that way.

Loading the file directly and counting the lines seems unnecessarily slow/hacky. Maybe someone can chime in on whether they know of ways this is done elsewhere in the codebase?

michaelpj commented 2 years ago

Can we make an upstream GHC issue about the module source span? That seems like something which we might reasonably expect GHC to do.

Possible dirty hack: the end position of the module is the maximum over the end positions of all the module's child elements.