{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {- | Module : Neovim.Quickfix Description : API for interacting with the quickfix list Copyright : (c) Sebastian Witte License : Apache-2.0 Maintainer : woozletoff@gmail.com Stability : experimental Portability : GHC -} module Neovim.Quickfix where import Neovim.API.String ( vim_call_function ) import Neovim.Classes ( Generic, NFData, (<+>), Doc, AnsiStyle, NvimObject(toObject, fromObject), (+:) ) import Neovim.Context ( throwError, Neovim ) import Control.Monad (void) import Data.ByteString as BS (ByteString, all, elem) import qualified Data.Map as Map import Data.Maybe ( fromMaybe ) import Data.MessagePack ( Object(ObjectBinary, ObjectMap) ) import Prettyprinter (viaShow) import Prelude {- | This is a wrapper around neovim's @setqflist()@. @strType@ can be any string that you can append to (hence 'Monoid') that is also an instance of 'NvimObject'. You can e.g. use the plain old 'String'. -} setqflist :: (Monoid strType, NvimObject strType) => [QuickfixListItem strType] -> QuickfixAction -> Neovim env () setqflist qs a = void $ vim_call_function "setqflist" $ qs +: a +: [] data ColumnNumber = VisualColumn Int | ByteIndexColumn Int | NoColumn deriving (Eq, Ord, Show, Generic) instance NFData ColumnNumber data SignLocation strType = LineNumber Int | SearchPattern strType deriving (Eq, Ord, Show, Generic) instance (NFData strType) => NFData (SignLocation strType) {- | Quickfix list item. The parameter names should mostly conform to those in @:h setqflist()@. Some fields are merged to explicitly state mutually exclusive elements or some other behavior of the fields. see 'quickfixListItem' for creating a value of this type without typing too much. -} data QuickfixListItem strType = QFItem { -- | Since the filename is only used if no buffer can be specified, this -- field is a merge of @bufnr@ and @filename@. bufOrFile :: Either Int strType , -- | Line number or search pattern to locate the error. lnumOrPattern :: Either Int strType , -- | A tuple of a column number and a boolean indicating which kind of -- indexing should be used. 'True' means that the visual column should be -- used. 'False' means to use the byte index. col :: ColumnNumber , -- | Error number. nr :: Maybe Int , -- | Description of the error. text :: strType , -- | Type of error. errorType :: QuickfixErrorType } deriving (Eq, Show, Generic) instance (NFData strType) => NFData (QuickfixListItem strType) -- | Simple error type enum. data QuickfixErrorType = Warning | Error deriving (Eq, Ord, Show, Read, Enum, Bounded, Generic) instance NFData QuickfixErrorType instance NvimObject QuickfixErrorType where toObject = \case Warning -> ObjectBinary "W" Error -> ObjectBinary "E" fromObject o = case fromObject o :: Either (Doc AnsiStyle) String of Right "W" -> return Warning Right "E" -> return Error _ -> return Error {- | Create a 'QuickfixListItem' by providing the minimal amount of arguments needed. -} quickfixListItem :: (Monoid strType) => -- | buffer of file name Either Int strType -> -- | line number or pattern Either Int strType -> QuickfixListItem strType quickfixListItem bufferOrFile lineOrPattern = QFItem { bufOrFile = bufferOrFile , lnumOrPattern = lineOrPattern , col = NoColumn , nr = Nothing , text = mempty , errorType = Error } instance (Monoid strType, NvimObject strType) => NvimObject (QuickfixListItem strType) where toObject QFItem{..} = (toObject :: Map.Map ByteString Object -> Object) . Map.fromList $ [ either (\b -> ("bufnr", toObject b)) (\f -> ("filename", toObject f)) bufOrFile , either (\l -> ("lnum", toObject l)) (\p -> ("pattern", toObject p)) lnumOrPattern , ("type", toObject errorType) , ("text", toObject text) ] ++ case col of NoColumn -> [] ByteIndexColumn i -> [("col", toObject i), ("vcol", toObject False)] VisualColumn i -> [("col", toObject i), ("vcol", toObject True)] fromObject objectMap@(ObjectMap _) = do m <- fromObject objectMap let l :: NvimObject o => ByteString -> Either (Doc AnsiStyle) o l key = case Map.lookup key m of Just o -> fromObject o Nothing -> throwError $ "Key not found." bufOrFile <- case (l "bufnr", l "filename") of (Right b, _) -> return $ Left b (_, Right f) -> return $ Right f _ -> throwError $ "No buffer number or file name inside quickfix list item." lnumOrPattern <- case (l "lnum", l "pattern") of (Right lnum, _) -> return $ Left lnum (_, Right pat) -> return $ Right pat _ -> throwError $ "No line number or search pattern inside quickfix list item." let l' :: NvimObject o => ByteString -> Either (Doc AnsiStyle) (Maybe o) l' key = case Map.lookup key m of Just o -> Just <$> fromObject o Nothing -> return Nothing nr <- l' "nr" >>= \case Just 0 -> return Nothing nr' -> return nr' c <- l' "col" v <- l' "vcol" let col = fromMaybe NoColumn $ do c' <- c v' <- v case (c', v') of (0, _) -> return NoColumn (_, True) -> return $ VisualColumn c' (_, False) -> return $ ByteIndexColumn c' text <- fromMaybe mempty <$> l' "text" errorType <- fromMaybe Error <$> l' "type" return QFItem{..} fromObject o = throwError $ "Could not deserialize QuickfixListItem," <+> "expected a map but received:" <+> viaShow o data QuickfixAction = -- | Add items to the current list (or create a new one if none exists). Append | -- | Replace current list (or create a new one if none exists). Replace | -- | Create a new list. New deriving (Eq, Ord, Enum, Bounded, Show, Generic) instance NFData QuickfixAction instance NvimObject QuickfixAction where toObject = \case Append -> ObjectBinary "a" Replace -> ObjectBinary "r" New -> ObjectBinary "" fromObject o = case fromObject o of Right "a" -> return Append Right "r" -> return Replace Right s | BS.all (`BS.elem` " \t\n\r") s -> return New _ -> Left "Could not convert to QuickfixAction"