ghci-quickfix: GHC plugin that writes errors to a file for use with quickfix

[ bsd3, development, library ] [ Propose Tags ] [ Report a vulnerability ]

GHC plugin that writes errors to a file for use with vim/nvim's quickfix system


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

Versions [RSS] 0.1.0.0
Change log CHANGELOG.md
Dependencies async (>=2.2 && <3), base (<4.23), deferred-folds (>=0.9 && <1), directory, foldl (>=1 && <2), ghc (>=9.6 && <9.13), stm, stm-containers (>=1.2 && <1.3), text [details]
Tested with ghc ==9.12.1, ghc ==9.10.1, ghc ==9.8.1, ghc ==9.6.1
License BSD-3-Clause
Author Aaron Allen
Maintainer aaronallen8455@gmail.com
Uploaded by aaronallen8455 at 2026-01-12T16:19:47Z
Category Development
Source repo head: git clone https://github.com/aaronallen8455/ghci-quickfix
Distributions
Reverse Dependencies 1 direct, 0 indirect [details]
Downloads 1 total (1 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2026-01-12 [all 1 reports]

Readme for ghci-quickfix-0.1.0.0

[back to package description]

ghci-quickfix 📝

This is a GHC plugin that will write diagnostics to a file during compilation, which can then be used with vim/nvim's quickfix feature. By default, the file is errors.err in the project root directory but this can be customized (see plugin options).

NOTE: If you're using this plugin via repl-alliance, you need to explicitly enable it by passing --fplugin-opt ReplAlliance:--quickfix to GHC or by setting the environment variable GHCI_QUICKFIX_ENABLED=true.

Usage

This plugin is intended to be used with GHCi or adjacent utilities such as ghcid and ghciwatch as a development tool, not as a package dependency.

Stack Projects

To use with a stack project (you may need to add ghci-quickfix to your extra-deps first):

stack repl my-project --package ghci-quickfix --ghci-options='-fplugin GhciQuickfix'

Cabal Projects

To use with a cabal project (you may need to run cabal update first):

cabal repl my-project --build-depends ghci-quickfix --repl-options='-fplugin GhciQuickfix'

Vim/Neovim Integration

After starting your REPL with the plugin enabled, you can load errors in Vim:

:cf errors.err

The errors.err argument can be omitted since it is the default file.

Or to update without jumping to the first error:

:cg errors.err

Navigate between error locations using :cn (next) and :cp (previous).

To get highlighting for errors and warnings in the editor, you can turn the quickfix entries into diagnostics. The following snippet can be added to your init.lua file to accomplish this.

-- [[ Quickfix to diagnostics ]]
-- Configure errorformat to distinguish between warning and error type.
vim.o.errorformat = '%f:%l:%c: %tarning: %m,%f:%l:%c: %trror: %m,' .. vim.o.errorformat

-- Create namespace for quickfix diagnostics
local qf_ns = vim.api.nvim_create_namespace('quickfix_diagnostics')

-- Configure diagnostic display for quickfix namespace
vim.diagnostic.config({
  underline = true,
  virtual_text = false,
  signs = true,
  update_in_insert = false,
}, qf_ns)

-- Helper function to create diagnostic entry from quickfix item
local function create_diagnostic_from_qf_item(item)
  local severity = vim.diagnostic.severity.ERROR

  -- Determine severity based on type field
  if item.type == 'W' or item.type == 'w' then
    severity = vim.diagnostic.severity.WARN
  end

  -- Handle column range - if no end_col, highlight to end of line
  local col_start = (item.col or 1) - 1
  local col_end = nil
  local end_lnum = nil

  if item.end_col and item.end_col > 0 and item.end_lnum then
    col_end = item.end_col
    end_lnum = item.end_lnum - 1
  end

  return {
    lnum = item.lnum - 1,
    col = col_start,
    end_lnum = end_lnum,
    end_col = col_end,
    severity = severity,
    message = item.text or '',
    source = 'quickfix',
  }
end

-- Function to convert quickfix entries to diagnostics and apply to all buffers
local function quickfix_to_diagnostics()
  -- Clear all quickfix diagnostics from all buffers
  vim.diagnostic.reset(qf_ns)

  local qf_list = vim.fn.getqflist()
  local diagnostics_by_buf = {}

  for _, item in ipairs(qf_list) do
    if item.bufnr > 0 and item.lnum > 0 then
      if not diagnostics_by_buf[item.bufnr] then
        diagnostics_by_buf[item.bufnr] = {}
      end
      table.insert(diagnostics_by_buf[item.bufnr], create_diagnostic_from_qf_item(item))
    end
  end

  -- Set diagnostics for each buffer
  for bufnr, diagnostics in pairs(diagnostics_by_buf) do
    vim.diagnostic.set(qf_ns, bufnr, diagnostics, {})
  end
end

-- Function to apply diagnostics for a specific buffer
local function apply_quickfix_diagnostics_for_buffer(bufnr)
  local qf_list = vim.fn.getqflist()
  local diagnostics = {}

  for _, item in ipairs(qf_list) do
    if item.bufnr == bufnr and item.lnum > 0 then
      table.insert(diagnostics, create_diagnostic_from_qf_item(item))
    end
  end

  vim.diagnostic.set(qf_ns, bufnr, diagnostics, {})
end

-- Auto-convert quickfix entries to diagnostics when loading from file
vim.api.nvim_create_autocmd('QuickFixCmdPost', {
  pattern = '[cg]file,[cg]getfile',
  callback = function()
    quickfix_to_diagnostics()
  end,
})

-- Reapply diagnostics when a buffer is read (handles newly opened buffers)
vim.api.nvim_create_autocmd('BufReadPost', {
  pattern = '*',
  callback = function(args)
    local qf_list = vim.fn.getqflist()
    if #qf_list == 0 then
      return
    end

    -- Only apply diagnostics for this specific buffer if it has quickfix entries
    local bufnr = args.buf
    for _, item in ipairs(qf_list) do
      if item.bufnr == bufnr then
        apply_quickfix_diagnostics_for_buffer(bufnr)
        return
      end
    end
  end,
})

Plugin Options

Plugin options are passed using the --fplugin-opt flag. For example:

-fplugin GhciQuickfix -fplugin-opt GhciQuickfix:--quickfix-file=my-errors.err

Available Options

  • --quickfix-file=<path> Specify the output file path for diagnostics. Default: errors.err Alternative: Set environment variable GHCI_QUICKFIX_FILE=<path>

  • --quickfix-include-parser-errors Include parser errors in the quickfix file. Default: Parser errors are excluded (HLint typically reports them) Alternative: Set environment variable GHCI_QUICKFIX_INCLUDE_PARSER_ERRORS=true

  • --quickfix-path-replace=<needle>:<replace> Replace text in file paths in the quickfix output. Example: --quickfix-path-replace=/home/user:/Users/user Can be specified multiple times for multiple replacements. Useful for containerized or remote development environments. Alternative: Set environment variable GHCI_QUICKFIX_PATH_REPLACE=<needle>:<replace>

  • --quickfix Explicitly enable the plugin when using pluginOffByDefault (e.g., with repl-alliance). Alternative: Set environment variable GHCI_QUICKFIX_ENABLED=true

Output Format

The plugin generates quickfix entries in GCC-style format:

filename.hs:line:col: severity: message

This format is automatically recognized by Vim's quickfix system.

Compatibility

This plugin aims to support the 4 latest GHC major releases (i.e. 9.6.* through 9.12.*). Check the cabal file for the currently supported versions.