Skip to content

DRAFT: Completions for cabal.project files, hls-cabal-project-plugin #4680

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 27 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e677c9b
Initial cabal-project plugin setup
rm41339 Jun 5, 2025
61e7d95
Upgrade to latest Haskell version
rm41339 Jun 6, 2025
595efc1
successful parsing of cabal.project file
rm41339 Jun 9, 2025
fc7ac76
implement basic parsing with parseProject
rm41339 Jun 13, 2025
594bba1
preliminary, very basic working diagnostics
rm41339 Jun 14, 2025
ba5216d
add parsing and diagnostics tests
rm41339 Jun 16, 2025
2164729
remove some redundancies between cabal and cabal-project plugin
rm41339 Jun 17, 2025
7ff8731
removed submodule cabal (will replace in correct directory)
rm41339 Jun 25, 2025
2f9f826
remove vendor from .gitignore
rm41339 Jun 25, 2025
87ecedf
add cabal submodule
rm41339 Jun 25, 2025
8adcdd5
update cabal.project to reflect new submodule location
rm41339 Jun 25, 2025
b74bced
fix bytes and cache error in parsing
rm41339 Jun 27, 2025
72fc5ab
add NFData instances to cabal
rm41339 Jun 30, 2025
c05125e
fix parseCabalProjectFileContents arguments, add test
rm41339 Jul 3, 2025
8dc4a54
finish cleaning up diagnostics code
rm41339 Jul 3, 2025
09af6a4
very basic constant completions with formatting
rm41339 Jul 4, 2025
c69973b
add basic field constant completions
rm41339 Jul 6, 2025
a812311
add noopCompleters for all cabal.project fields in fieldGrammar
rm41339 Jul 9, 2025
3340ec4
update wrong constantCompleter
rm41339 Jul 9, 2025
d330009
fill in boolean/enum completion values
rm41339 Jul 13, 2025
0558c84
add completions tests
rm41339 Jul 17, 2025
a479a31
edit Completions/Data documentation
rm41339 Jul 29, 2025
4da8eb9
edit documentation and code to reflect cabal.project
rm41339 Jul 30, 2025
0a6ed29
fix variable name
rm41339 Jul 30, 2025
b1bced2
hls-cabal-project-plugin: Add completions suggestions for cabal.proje…
rm41339 Jul 31, 2025
0293d79
generalize Completer for .cabal and cabal.project files
rm41339 Aug 7, 2025
061943f
minor completions edits
rm41339 Aug 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@
# Commit git commit -m "Removed submodule <name>"
# Delete the now untracked submodule files
# rm -rf path_to_submodule

[submodule "vendor/cabal"]
path = vendor/cabal
url = https://github.com/rm41339/cabal.git
9 changes: 9 additions & 0 deletions cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@ packages:
./ghcide
./hls-plugin-api
./hls-test-utils
./vendor/cabal/Cabal
./vendor/cabal/Cabal-syntax
./vendor/cabal/cabal-install
./vendor/cabal/cabal-install-solver
./vendor/cabal/Cabal-described
./vendor/cabal/Cabal-tree-diff

package cabal-install
tests: False
benchmarks: False

index-state: 2025-05-12T13:26:29Z

Expand Down
86 changes: 86 additions & 0 deletions haskell-language-server.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,91 @@ test-suite hls-cabal-plugin-tests
, text
, hls-plugin-api

-----------------------------
-- cabal project plugin
-----------------------------

flag cabalProject
description: Enable cabalProject plugin
default: True
manual: True

common cabalProject
if flag(cabalProject)
build-depends: haskell-language-server:hls-cabal-project-plugin
cpp-options: -Dhls_cabal_project

library hls-cabal-project-plugin
import: defaults, pedantic, warnings
if !flag(cabalProject)
buildable: False
exposed-modules:
Ide.Plugin.CabalProject
Ide.Plugin.CabalProject.Parse
Ide.Plugin.CabalProject.Diagnostics
Ide.Plugin.CabalProject.Types
Ide.Plugin.CabalProject.Completion.Completions
Ide.Plugin.CabalProject.Completion.Data
Ide.Plugin.CabalProject.Completion.Completer.Types

build-depends:
, bytestring
, Cabal-syntax >= 3.7
, containers
, deepseq
, directory
, filepath
, extra >=1.7.4
, ghcide == 2.11.0.0
, hashable
, hls-plugin-api == 2.11.0.0
, hls-graph == 2.11.0.0
, lens
, lsp ^>=2.7
, lsp-types ^>=2.3
, regex-tdfa ^>=1.3.1
, text
, text-rope
, transformers
, unordered-containers >=0.2.10.0
, containers
, process
, aeson
, Cabal
, pretty
, cabal-install
, cabal-install-solver
, haskell-language-server:hls-cabal-plugin
, base16-bytestring
, cryptohash-sha1

hs-source-dirs: plugins/hls-cabal-project-plugin/src

test-suite hls-cabal-project-plugin-tests
import: defaults, pedantic, test-defaults, warnings
if !flag(cabalProject)
buildable: False
type: exitcode-stdio-1.0
hs-source-dirs: plugins/hls-cabal-project-plugin/test
main-is: Main.hs
other-modules:
Completer
Utils
build-depends:
, bytestring
, Cabal-syntax >= 3.7
, extra
, filepath
, ghcide
, haskell-language-server:hls-cabal-project-plugin
, hls-test-utils == 2.11.0.0
, lens
, lsp-types
, text
, hls-plugin-api
, cabal-install
, haskell-language-server:hls-cabal-plugin

-----------------------------
-- class plugin
-----------------------------
Expand Down Expand Up @@ -1830,6 +1915,7 @@ library
, pedantic
-- plugins
, cabal
, cabalProject
, callHierarchy
, cabalfmt
, cabalgild
Expand Down
17 changes: 16 additions & 1 deletion hls-plugin-api/src/Ide/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}
module Ide.Types
( PluginDescriptor(..), defaultPluginDescriptor, defaultCabalPluginDescriptor
( PluginDescriptor(..), defaultPluginDescriptor, defaultCabalPluginDescriptor, defaultCabalProjectPluginDescriptor
, defaultPluginPriority
, describePlugin
, IdeCommand(..)
Expand Down Expand Up @@ -1077,6 +1077,21 @@ defaultCabalPluginDescriptor plId desc =
Nothing
[".cabal"]

defaultCabalProjectPluginDescriptor :: PluginId -> T.Text -> PluginDescriptor ideState
defaultCabalProjectPluginDescriptor plId desc =
PluginDescriptor
plId
desc
defaultPluginPriority
mempty
mempty
mempty
defaultConfigDescriptor
mempty
mempty
Nothing
[".project"]

newtype CommandId = CommandId T.Text
deriving (Show, Read, Eq, Ord)
instance IsString CommandId where
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import qualified Text.Fuzzy.Parallel as Fuzzy

-- | Completer to be used when a file path can be completed for a field.
-- Completes file paths as well as directories.
filePathCompleter :: Completer
filePathCompleter :: HasPrefixInfo d => Completer d
filePathCompleter recorder cData = do
let prefInfo = cabalPrefixInfo cData
let prefInfo = getPrefixInfo cData
complInfo = pathCompletionInfoFromCabalPrefixInfo "" prefInfo
filePathCompletions <- listFileCompletions recorder complInfo
let scored =
Expand All @@ -40,7 +40,7 @@ filePathCompleter recorder cData = do
pure $ mkCompletionItem (completionRange prefInfo) fullFilePath fullFilePath
)

mainIsCompleter :: (Maybe StanzaName -> GenericPackageDescription -> [FilePath]) -> Completer
mainIsCompleter :: (Maybe StanzaName -> GenericPackageDescription -> [FilePath]) -> CabalCompleter
mainIsCompleter extractionFunction recorder cData = do
mGPD <- getLatestGPD cData
case mGPD of
Expand Down Expand Up @@ -74,9 +74,9 @@ mainIsCompleter extractionFunction recorder cData = do

-- | Completer to be used when a directory can be completed for the field.
-- Only completes directories.
directoryCompleter :: Completer
directoryCompleter :: HasPrefixInfo d => Completer d
directoryCompleter recorder cData = do
let prefInfo = cabalPrefixInfo cData
let prefInfo = getPrefixInfo cData
complInfo = pathCompletionInfoFromCabalPrefixInfo "" prefInfo
directoryCompletions <- listDirectoryCompletions recorder complInfo
let scored =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import qualified Text.Fuzzy.Parallel as Fuzzy
--
-- Takes an extraction function which extracts the source directories
-- to be used by the completer.
modulesCompleter :: (Maybe StanzaName -> GenericPackageDescription -> [FilePath]) -> Completer
modulesCompleter :: (Maybe StanzaName -> GenericPackageDescription -> [FilePath]) -> CabalCompleter
modulesCompleter extractionFunction recorder cData = do
mGPD <- getLatestGPD cData
case mGPD of
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,21 @@ import qualified Text.Fuzzy.Parallel as Fuzzy

-- | Completer to be used when no completion suggestions
-- are implemented for the field
noopCompleter :: Completer
noopCompleter :: Completer d
noopCompleter _ _ = pure []

-- | Completer to be used when no completion suggestions
-- are implemented for the field and a log message should be emitted.
errorNoopCompleter :: Log -> Completer
errorNoopCompleter :: Log -> Completer d
errorNoopCompleter l recorder _ = do
logWith recorder Warning l
pure []

-- | Completer to be used when a simple set of values
-- can be completed for a field.
constantCompleter :: [T.Text] -> Completer
constantCompleter :: [T.Text] -> HasPrefixInfo d => Completer d
constantCompleter completions _ cData = do
let prefInfo = cabalPrefixInfo cData
let prefInfo = getPrefixInfo cData
scored = Fuzzy.simpleFilter Fuzzy.defChunkSize Fuzzy.defMaxResults (completionPrefix prefInfo) completions
range = completionRange prefInfo
pure $ map (mkSimpleCompletionItem range . Fuzzy.original) scored
Expand All @@ -49,7 +49,7 @@ constantCompleter completions _ cData = do
--
-- TODO: Does not exclude imports, defined after the current cursor position
-- which are not allowed according to the cabal specification
importCompleter :: Completer
importCompleter :: CabalCompleter
importCompleter l cData = do
cabalCommonsM <- getCabalCommonSections cData
case cabalCommonsM of
Expand All @@ -66,7 +66,7 @@ importCompleter l cData = do
-- This is almost always the name of the cabal file. However,
-- it is not forbidden by the specification to have a different name,
-- it is just forbidden on hackage.
nameCompleter :: Completer
nameCompleter :: CabalCompleter
nameCompleter _ cData = do
let scored = Fuzzy.simpleFilter Fuzzy.defChunkSize Fuzzy.defMaxResults (completionPrefix prefInfo) [completionFileName prefInfo]
prefInfo = cabalPrefixInfo cData
Expand All @@ -80,7 +80,7 @@ nameCompleter _ cData = do
-- the value in the completion suggestion.
--
-- If the value does not occur in the weighted map its weight is defaulted to zero.
weightedConstantCompleter :: [T.Text] -> Map T.Text Double -> Completer
weightedConstantCompleter :: [T.Text] -> Map T.Text Double -> CabalCompleter
weightedConstantCompleter completions weights _ cData = do
let scored =
if perfectScore > 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import qualified Language.LSP.Protocol.Types as LSP
import qualified Text.Fuzzy.Parallel as Fuzzy

-- | Maps snippet triggerwords with their completers
snippetCompleter :: Completer
snippetCompleter :: CabalCompleter
snippetCompleter recorder cData = do
let scored = Fuzzy.simpleFilter Fuzzy.defChunkSize Fuzzy.defMaxResults (completionPrefix prefInfo) $ Map.keys snippets
mapMaybeM
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ import qualified Distribution.Parsec.Position as Syntax
import Ide.Plugin.Cabal.Completion.Types
import Language.LSP.Protocol.Types (CompletionItem)

-- | Takes information needed to build possible completion items
-- | Takes information and completer type needed to build possible completion items
-- and returns the list of possible completion items
type Completer = Recorder (WithPriority Log) -> CompleterData -> IO [CompletionItem]
type Completer d = Recorder (WithPriority Log) -> d -> IO [CompletionItem]

-- Cabal specific completer
type CabalCompleter = Completer CompleterData

-- | Contains information to be used by completers.
data CompleterData = CompleterData
Expand All @@ -26,3 +29,10 @@ data CompleterData = CompleterData
-- | The name of the stanza in which the completer is applied
stanzaName :: Maybe StanzaName
}

-- Allows CabalCompleter and CabalProjectCompleter to be passed into the same completers
class HasPrefixInfo d where
getPrefixInfo :: d -> CabalPrefixInfo

instance HasPrefixInfo CompleterData where
getPrefixInfo = cabalPrefixInfo
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import qualified Distribution.Parsec.Position as Syntax
import Ide.Plugin.Cabal.Completion.CabalFields
import Ide.Plugin.Cabal.Completion.Completer.Simple
import Ide.Plugin.Cabal.Completion.Completer.Snippet
import Ide.Plugin.Cabal.Completion.Completer.Types (Completer)
import Ide.Plugin.Cabal.Completion.Completer.Types (CabalCompleter)
import Ide.Plugin.Cabal.Completion.Data
import Ide.Plugin.Cabal.Completion.Types
import qualified Language.LSP.Protocol.Lens as JL
Expand All @@ -28,7 +28,7 @@ import System.FilePath (takeBaseName)

-- | Takes information about the completion context within the file
-- and finds the correct completer to be applied.
contextToCompleter :: Context -> Completer
contextToCompleter :: Context -> CabalCompleter
-- if we are in the top level of the cabal file and not in a keyword context,
-- we can write any top level keywords or a stanza declaration
contextToCompleter (TopLevel, None) =
Expand Down
Loading
Loading