packages feed

ghci-quickfix-0.1.0.0: README.md

# 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`](https://github.com/aaronallen8455/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):

```bash
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):

```bash
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:

```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:

```vim
: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.

```lua
-- [[ 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:

```bash
-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.