Skip to content

Commit

Permalink
Implement completions for cabal files
Browse files Browse the repository at this point in the history
Includes:
* completions for keywords, sensitive to stanzas
* value completions for constant values, files, directories and exposed modules
* completion of snippets for different stanzas and the required basic fields
  • Loading branch information
VeryMilkyJoe authored and fendor committed Aug 3, 2023
1 parent 6111a10 commit e1fa729
Show file tree
Hide file tree
Showing 30 changed files with 2,314 additions and 295 deletions.
1 change: 0 additions & 1 deletion ghcide/src/Development/IDE/Plugin/Completions.hs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ import qualified Language.LSP.Protocol.Lens as L
import Language.LSP.Protocol.Message
import Language.LSP.Protocol.Types
import qualified Language.LSP.Server as LSP
import qualified Language.LSP.VFS as VFS
import Numeric.Natural
import Text.Fuzzy.Parallel (Scored (..))

Expand Down
76 changes: 56 additions & 20 deletions ghcide/src/Text/Fuzzy/Parallel.hs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
-- | Parallel versions of 'filter' and 'simpleFilter'

module Text.Fuzzy.Parallel
( filter,
simpleFilter,
match,
( filter, filter',
simpleFilter, simpleFilter',
match, defChunkSize, defMaxResults,
Scored(..)
) where

Expand All @@ -29,7 +29,6 @@ data Scored a = Scored {score :: !Int, original:: !a}
-- Just 5
--
{-# INLINABLE match #-}

match :: T.Text -- ^ Pattern in lowercase except for first character
-> T.Text -- ^ The text to search in.
-> Maybe Int -- ^ The score
Expand Down Expand Up @@ -70,22 +69,13 @@ match (T.Text pArr pOff pLen) (T.Text sArr sOff sLen) = go 0 1 pOff sOff

toLowerAscii w = if (w - 65) < 26 then w .|. 0x20 else w

-- | The function to filter a list of values by fuzzy search on the text extracted from them.
filter :: Int -- ^ Chunk size. 1000 works well.
-> Int -- ^ Max. number of results wanted
-> T.Text -- ^ Pattern.
-> [t] -- ^ The list of values containing the text to search in.
-> (t -> T.Text) -- ^ The function to extract the text from the container.
-> [Scored t] -- ^ The list of results, sorted, highest score first.
filter chunkSize maxRes pattern ts extract = partialSortByAscScore maxRes perfectScore (concat vss)
where
-- Preserve case for the first character, make all others lowercase
pattern' = case T.uncons pattern of
Just (c, rest) -> T.cons c (T.toLower rest)
_ -> pattern
vss = map (mapMaybe (\t -> flip Scored t <$> match pattern' (extract t))) (chunkList chunkSize ts)
`using` parList (evalList rseq)
perfectScore = fromMaybe (error $ T.unpack pattern) $ match pattern' pattern'
-- | Sensible default value for chunk size to use when calling simple filter.
defChunkSize :: Int
defChunkSize = 1000

-- | Sensible default value for the number of max results to use when calling simple filter.
defMaxResults :: Int
defMaxResults = 10

-- | Return all elements of the list that have a fuzzy
-- match against the pattern. Runs with default settings where
Expand All @@ -102,6 +92,52 @@ simpleFilter :: Int -- ^ Chunk size. 1000 works well.
simpleFilter chunk maxRes pattern xs =
filter chunk maxRes pattern xs id


-- | The function to filter a list of values by fuzzy search on the text extracted from them,
-- using a custom matching function which determines how close words are.
filter' :: Int -- ^ Chunk size. 1000 works well.
-> Int -- ^ Max. number of results wanted
-> T.Text -- ^ Pattern.
-> [t] -- ^ The list of values containing the text to search in.
-> (t -> T.Text) -- ^ The function to extract the text from the container.
-> (T.Text -> T.Text -> Maybe Int)
-- ^ Custom scoring function to use for calculating how close words are
-- When the function returns Nothing, this means the values are incomparable.
-> [Scored t] -- ^ The list of results, sorted, highest score first.
filter' chunkSize maxRes pattern ts extract match' = partialSortByAscScore maxRes perfectScore (concat vss)
where
-- Preserve case for the first character, make all others lowercase
pattern' = case T.uncons pattern of
Just (c, rest) -> T.cons c (T.toLower rest)
_ -> pattern
vss = map (mapMaybe (\t -> flip Scored t <$> match' pattern' (extract t))) (chunkList chunkSize ts)
`using` parList (evalList rseq)
perfectScore = fromMaybe (error $ T.unpack pattern) $ match' pattern' pattern'

-- | The function to filter a list of values by fuzzy search on the text extracted from them,
-- using a custom matching function which determines how close words are.
filter :: Int -- ^ Chunk size. 1000 works well.
-> Int -- ^ Max. number of results wanted
-> T.Text -- ^ Pattern.
-> [t] -- ^ The list of values containing the text to search in.
-> (t -> T.Text) -- ^ The function to extract the text from the container.
-> [Scored t] -- ^ The list of results, sorted, highest score first.
filter chunkSize maxRes pattern ts extract =
filter' chunkSize maxRes pattern ts extract match

-- | Return all elements of the list that have a fuzzy match against the pattern,
-- the closeness of the match is determined using the custom scoring match function that is passed.
-- Runs with default settings where nothing is added around the matches, as case insensitive.
{-# INLINABLE simpleFilter' #-}
simpleFilter' :: Int -- ^ Chunk size. 1000 works well.
-> Int -- ^ Max. number of results wanted
-> T.Text -- ^ Pattern to look for.
-> [T.Text] -- ^ List of texts to check.
-> (T.Text -> T.Text -> Maybe Int)
-- ^ Custom scoring function to use for calculating how close words are
-> [Scored T.Text] -- ^ The ones that match.
simpleFilter' chunk maxRes pattern xs match' =
filter' chunk maxRes pattern xs id match'
--------------------------------------------------------------------------------

chunkList :: Int -> [a] -> [[a]]
Expand Down
36 changes: 24 additions & 12 deletions plugins/hls-cabal-plugin/hls-cabal-plugin.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -27,36 +27,39 @@ library
exposed-modules:
Ide.Plugin.Cabal
Ide.Plugin.Cabal.Diagnostics
Ide.Plugin.Cabal.Completion.Completer.FilePath
Ide.Plugin.Cabal.Completion.Completer.Module
Ide.Plugin.Cabal.Completion.Completer.Simple
Ide.Plugin.Cabal.Completion.Completer.Snippet
Ide.Plugin.Cabal.Completion.Completer.Types
Ide.Plugin.Cabal.Completion.Completions
Ide.Plugin.Cabal.Completion.Data
Ide.Plugin.Cabal.Completion.Types
Ide.Plugin.Cabal.LicenseSuggest
Ide.Plugin.Cabal.Parse


build-depends:
, base >=4.12 && <5
, bytestring
-- Ideally, we only want to support a single Cabal version, supporting
-- older versions is completely pointless since Cabal is backwards compatible,
-- the latest Cabal version can parse all versions of the Cabal file format.
--
-- However, stack is making this difficult, if we change the version of Cabal,
-- we essentially need to make sure all other packages in the snapshot have their
-- Cabal dependency version relaxed.
-- Most packages have a Hackage revision, but stack won't pick these up (for sensible reasons)
-- automatically, forcing us to manually update the packages revision id.
-- This is a lot of work for almost zero benefit, so we just allow more versions here
-- and we eventually completely drop support for building HLS with stack.
, Cabal ^>=3.2 || ^>=3.4 || ^>=3.6 || ^>= 3.8 || ^>= 3.10
, Cabal-syntax >= 3.7
, containers
, deepseq
, directory
, filepath
, extra >=1.7.4
, ghcide == 2.1.0.0
, hashable
, hls-plugin-api == 2.1.0.0
, hls-graph == 2.1.0.0
, lens
, lsp ^>=2.0.0.0
, lsp-types ^>=2.0.0.1
, regex-tdfa ^>=1.3.1
, stm
, text
, text-rope
, transformers
, unordered-containers >=0.2.10.0
, containers
hs-source-dirs: src
Expand All @@ -68,15 +71,24 @@ test-suite tests
type: exitcode-stdio-1.0
hs-source-dirs: test
main-is: Main.hs
other-modules:
Completer
Context
Utils
build-depends:
, base
, bytestring
, Cabal-syntax >= 3.7
, directory
, filepath
, ghcide
, hls-cabal-plugin
, hls-test-utils == 2.1.0.0
, lens
, lsp
, lsp-types
, tasty-hunit
, text
, text-rope
, transformers
, row-types
Loading

0 comments on commit e1fa729

Please sign in to comment.