Skip to content

josephsumabat/static-ls

Repository files navigation

static-ls

static-ls ("static language server") is a hie files and hiedb based language server heavily inspired by halfsp, which reads static project information to provide IDE functionality through the language server protocol. static-ls will not generate this information on its own and instead will rely on the user to generate this information via separate programs

See Supported static sources to see what is supported

The goal of static-ls is to provide a high-speed, low-memory language server for large projects for which haskell-language-server tends to take up too much memory on recompilation. Haskell-language-server is recommended if you are not experiencing these issues. static-ls is meant to work on enterprise size projects where aforementioned constraints can be an issue. static-ls can work as a standalone code navigation tool if you generate static sources once or can support a performant fully integrated editor workflow with some setup and usage of a file watcher to recompile your project.

If you want to use static-ls in your IDE then ghcid or ghciwatch are strongly recommended to watch files for compilation and the -fdefer-type-errors flag.

Currently only ghc 9.4.4 and 9.6.1 are explicitly supported but I'm happy to add support for other versions of ghc if desired

Quick start

  1. Compile your project with ide info -fwrite-ide-info and -hiedir .hiefiles. You can also add -hidir .hifiles for haddock support (Only supported on 64 bit systems right now) though this may require some extra build configuration) Note if you don't want to change the output directories of these files you can symlink them instead or point static-ls to them with its arguments. (See static-ls --help for info)

    For a better UX, the following flags are strongly recommended.

    - -fdefer-type-errors
    - -Werror=deferred-type-errors
    - -Werror=deferred-out-of-scope-variables
    - -fno-defer-typed-holes
    

    These flags will allow hie files to be refreshed even if compilation fails to type check and will ensure that type check failures are still thrown as errors.

    • If you're using hpack you can add:
        ghc-options:
          - -fwrite-ide-info
          - -hiedir .hiefiles
          - -fdefer-type-errors
          - -Werror=deferred-type-errors
          - -Werror=deferred-out-of-scope-variables
          - -fno-defer-typed-holes
      
      to your package.yaml. See this project's package.yaml or static-ls.cabal for examples
    • You may instead add the following to your cabal.project.local file:
      ignore-project: False
      program-options:
        ghc-options:
          -fdefer-type-errors
          -Werror=deferred-type-errors
          -Werror=deferred-out-of-scope-variables
          -fno-defer-typed-holes
      
  2. Index your project in hiedb running:

      hiedb -D .hiedb index .hiefiles --src-base-dir .
    

    from your workspace root. If you're on an older version of hiedb where the --src-base-dir argument is not available use:

      hiedb -D .hiedb index .hiefiles
    

    entr is also recommended if you want file watching functionality:

    find -L .hiefiles | entr hiedb -D .hiedb index /_       
    
  3. Point your language client to the static-ls binary and begin editing! (See Editor Setup for instructions if you're not sure how)

ghciwatch or ghcid is recommended to refresh hie files but compiling with cabal build should work as well

Features

static-ls supports the following lsp methods:

  • textDocument/references
    • Note that find references only works on top level definitions and can be slow for functions which are used frequently

Find references

  • textDocument/hover
    • Provides type information and definition location on hover

Type on hover

  • textDocument/definition
    • Works on both local and top level definitions

Find definition

Supported static sources

Below are supported static sources for ide information. Static-ls will not handle generating this information on its own but will require the user to handle generating them.

Static Source Default Director(y/ies) static-ls argument Used For Description
hie files .hiefiles/ --hiefiles DIR Currently required for everything since AST is read from these.

Specifically used for:
- Type on hover
- go to definition/type definition
GHC generated annotated AST used to determine identifiers, type on hover, and go to definition at a given location

Using the -hiefiles flag in GHC will generate these
source code directories - src/
- lib/
- app/
- test/

Will fallback to checking hiedb
--src-base-dirs DIR1,DIR2.. - Go to definition Paths where source code is stored. Will assume that modules start at these directories
hiedb .hiedb --hiedb TARGET - Go to definition (when not available in hie files)
- Find references
- Document Symbol
Indexed database of hie files. Used primarily for find references, but you can index dependencies or things not found in source code directories. Generated by hiedb.
ghc interface files .hifiles/ --hifiles TARGET - Docs on hover GHC generated interface files. Must be compiled with -haddock for docs. GHC will generate these automatically, but you can specify the output directory with -hifiles.

Other potential sources of static information include haddock files as an alternative to interface files for docs, ghcid and hlint output for diagnostics, and ctags for a backup jump to definition. Future features using existing static sources include auto import resolution code actions, autocomplete based on hiedb, and call heiarchy

Limitations

  • Must be compiled on the same version of ghc as the project
  • You will need to re-index your hie files once you edit your project

Editor setup

Instructions for editor setup

neovim - coc.nvim

call :CocConfig and copy the following in:

{
  "languageserver": {
    "static-ls": {
      "command": "static-ls",
      "rootPatterns": ["*.cabal", "stack.yaml", "cabal.project", "package.yaml", "hie.yaml"],
      "filetypes": ["haskell"]
    },
  },
}

Visual Studio Code

  1. Install the Haskell language extension Haskell Extension

  2. Open the extension settings Go to tsettings

  3. Scroll down until you see "Server Executable Path" and set the path to the path of your static-ls binary Set the language server executable path

Alternatively you can also in your workspace's ./vscode/settings.json you can use the following:

{
  "haskell.manageHLS": "PATH",
  "haskell.serverExecutablePath": "static-ls",
  "haskell.serverExtraArgs": ""
}

(Note haskell.serverExecutablePath should be the path to your binary).