Skip to content

Commit ed6206e

Browse files
authored
Merge branch 'master' into wip/2.9.0.0
2 parents 40891bc + 62892ae commit ed6206e

File tree

10 files changed

+259
-90
lines changed

10 files changed

+259
-90
lines changed

docs/support/ghc-version-support.md

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -76,26 +76,62 @@ Major versions of GHC which are not supported by HLS on master are extremely unl
7676

7777
## GHC version deprecation policy
7878

79-
### Major versions
79+
### Base policy
8080

81-
A major GHC version is a "legacy" version if it is 3 or more major versions behind the latest GHC version that is
81+
This is the static part of the policy that can be checked by a machine.
8282

83-
1. Fully supported by HLS
84-
2. Used in the a Stackage LTS
83+
#### Major versions
8584

86-
For example, if 9.2 is the latest major version fully supported by HLS and used in a Stackage LTS, then the 8.8 major version and older will be legacy.
85+
HLS will support major versions of GHC until they are older than _both_
8786

88-
HLS will support all non-legacy major versions of GHC.
87+
1. The major version of GHC used in the current Stackage LTS; and
88+
2. The major version of GHC recommended by GHCup
8989

90-
### Minor versions
90+
For example, if
91+
92+
1. Stackage LTS uses GHC 9.2; and
93+
2. GHCUp recommends GHC 9.4
94+
95+
then HLS will support back to GHC 9.2.
96+
97+
#### Minor versions
9198

9299
For the latest supported major GHC version we will support at least 2 minor versions.
93100

94101
For the rest of the supported major GHC versions, we will support at least the latest minor version in Stackage LTS (so 1 minor version).
95102

96-
### Announcements
103+
### Extended policy
104+
105+
This is the part of the policy that needs evaluation by a human and possibly followed
106+
by a discussion.
107+
108+
#### Ecosystem factors
109+
110+
To establish and apply the policy we take the following ecosystem factors into account:
111+
112+
- Support status of HLS
113+
- The most recent [stackage](https://www.stackage.org/) LTS snapshot
114+
- The GHC version recommended by GHCup
115+
- The GHC versions used in the most popular [linux distributions](https://repology.org/project/ghc/versions)
116+
- The reliability of different ghc versions on the major operating systems (Linux, Windows, MacOS)
117+
- The [Haskell Survey results](https://taylor.fausak.me/2022/11/18/haskell-survey-results/#s2q4)
97118

98-
We will warn users about the upcoming deprecation of a GHC version in the notes of the release *prior* to the deprecation itself.
119+
### Supporting a GHC version beyond normal deprecation time
120+
121+
In cases where the base policy demands a deprecation, but ecosystem factors
122+
suggest that it's still widely used (e.g. last [Haskell Survey results](https://taylor.fausak.me/2022/11/18/haskell-survey-results/#s2q4)),
123+
the deprecation should be suspended for the next release and the situation be re-evaluated for the release after that.
124+
125+
When we decide to keep on an old version, we should track it as follows:
126+
127+
1. open a ticket on HLS issue tracker wrt discussing to deprecate said GHC version
128+
- explain the reason the GHC version wasn't deprecated (context)
129+
- explain the maintenance burden it causes (reason)
130+
- evaluate whether it impacts the next HLS release (impact)
131+
2. discuss whether ecosystem factors changed
132+
- e.g. if Haskell Survey results show that 25% or more of users are still on the GHC version in question, then dropping should be avoided
133+
3. if dropping is still undesired, but maintenance burden is also high, then set out a call-for-help and contact HF for additional funding to support this GHC version
134+
4. if no help or funding was received within 2 releases (say, e.g. 3-6 months), then drop the version regardless
99135

100136
### Why deprecate older versions of GHC?
101137

@@ -108,12 +144,3 @@ We will warn users about the upcoming deprecation of a GHC version in the notes
108144
So we need to limit the GHC support to save maintainers and contributors time and reduce CI resources.
109145

110146
At same time we aim to support the right balance of GHC versions to minimize the impact on users.
111-
112-
### What factors do we take into account when deprecating a version?
113-
114-
To establish and apply the policy we take into account:
115-
116-
- Completeness: support includes all plugins and features
117-
- The most recent [stackage](https://www.stackage.org/) LTS snapshot
118-
- The GHC versions used in the most popular [linux distributions](https://repology.org/project/ghc/versions)
119-
- The reliability of different ghc versions on the major operating systems (Linux, Windows, MacOS)

haskell-language-server.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ library hls-cabal-plugin
231231
exposed-modules:
232232
Ide.Plugin.Cabal
233233
Ide.Plugin.Cabal.Diagnostics
234+
Ide.Plugin.Cabal.Completion.CabalFields
234235
Ide.Plugin.Cabal.Completion.Completer.FilePath
235236
Ide.Plugin.Cabal.Completion.Completer.Module
236237
Ide.Plugin.Cabal.Completion.Completer.Paths

plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import Data.Hashable
1818
import Data.HashMap.Strict (HashMap)
1919
import qualified Data.HashMap.Strict as HashMap
2020
import qualified Data.List.NonEmpty as NE
21+
import qualified Data.Maybe as Maybe
2122
import qualified Data.Text.Encoding as Encoding
2223
import Data.Typeable
2324
import Development.IDE as D
@@ -32,7 +33,8 @@ import qualified Distribution.Parsec.Position as Syntax
3233
import GHC.Generics
3334
import qualified Ide.Plugin.Cabal.Completion.Completer.Types as CompleterTypes
3435
import qualified Ide.Plugin.Cabal.Completion.Completions as Completions
35-
import Ide.Plugin.Cabal.Completion.Types (ParseCabalFields (..),
36+
import Ide.Plugin.Cabal.Completion.Types (ParseCabalCommonSections (ParseCabalCommonSections),
37+
ParseCabalFields (..),
3638
ParseCabalFile (..))
3739
import qualified Ide.Plugin.Cabal.Completion.Types as Types
3840
import qualified Ide.Plugin.Cabal.Diagnostics as Diagnostics
@@ -170,6 +172,14 @@ cabalRules recorder plId = do
170172
Right fields ->
171173
pure ([], Just fields)
172174

175+
define (cmapWithPrio LogShake recorder) $ \ParseCabalCommonSections file -> do
176+
fields <- use_ ParseCabalFields file
177+
let commonSections = Maybe.mapMaybe (\case
178+
commonSection@(Syntax.Section (Syntax.Name _ "common") _ _) -> Just commonSection
179+
_ -> Nothing)
180+
fields
181+
pure ([], Just commonSections)
182+
173183
define (cmapWithPrio LogShake recorder) $ \ParseCabalFile file -> do
174184
config <- getPluginConfigAction plId
175185
if not (plcGlobalOn config && plcDiagnosticsOn config)
@@ -342,6 +352,9 @@ completion recorder ide _ complParams = do
342352
-- The `withStale` option is very important here, since we often call this rule with invalid cabal files.
343353
mGPD <- runIdeAction "cabal-plugin.modulesCompleter.gpd" (shakeExtras ide) $ useWithStaleFast ParseCabalFile $ toNormalizedFilePath fp
344354
pure $ fmap fst mGPD
355+
, getCabalCommonSections = do
356+
mSections <- runIdeAction "cabal-plugin.modulesCompleter.commonsections" (shakeExtras ide) $ useWithStaleFast ParseCabalCommonSections $ toNormalizedFilePath fp
357+
pure $ fmap fst mSections
345358
, cabalPrefixInfo = prefInfo
346359
, stanzaName =
347360
case fst ctx of
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
module Ide.Plugin.Cabal.Completion.CabalFields (findStanzaForColumn, findFieldSection, getOptionalSectionName, getAnnotation, getFieldName) where
2+
3+
import Data.List.NonEmpty (NonEmpty)
4+
import qualified Data.List.NonEmpty as NE
5+
import qualified Data.Text as T
6+
import qualified Data.Text.Encoding as T
7+
import qualified Distribution.Fields as Syntax
8+
import qualified Distribution.Parsec.Position as Syntax
9+
import Ide.Plugin.Cabal.Completion.Types
10+
11+
-- ----------------------------------------------------------------
12+
-- Cabal-syntax utilities I don't really want to write myself
13+
-- ----------------------------------------------------------------
14+
15+
-- | Determine the context of a cursor position within a stack of stanza contexts
16+
--
17+
-- If the cursor is indented more than one of the stanzas in the stack
18+
-- the respective stanza is returned if this is never the case, the toplevel stanza
19+
-- in the stack is returned.
20+
findStanzaForColumn :: Int -> NonEmpty (Int, StanzaContext) -> (StanzaContext, FieldContext)
21+
findStanzaForColumn col ctx = case NE.uncons ctx of
22+
((_, stanza), Nothing) -> (stanza, None)
23+
((indentation, stanza), Just res)
24+
| col < indentation -> findStanzaForColumn col res
25+
| otherwise -> (stanza, None)
26+
27+
-- | Determine the field the cursor is currently a part of.
28+
--
29+
-- The result is said field and its starting position
30+
-- or Nothing if the passed list of fields is empty.
31+
32+
-- This only looks at the row of the cursor and not at the cursor's
33+
-- position within the row.
34+
--
35+
-- TODO: we do not handle braces correctly. Add more tests!
36+
findFieldSection :: Syntax.Position -> [Syntax.Field Syntax.Position] -> Maybe (Syntax.Field Syntax.Position)
37+
findFieldSection _cursor [] = Nothing
38+
findFieldSection _cursor [x] =
39+
-- Last field. We decide later, whether we are starting
40+
-- a new section.
41+
Just x
42+
findFieldSection cursor (x:y:ys)
43+
| Syntax.positionRow (getAnnotation x) <= cursorLine && cursorLine < Syntax.positionRow (getAnnotation y)
44+
= Just x
45+
| otherwise = findFieldSection cursor (y:ys)
46+
where
47+
cursorLine = Syntax.positionRow cursor
48+
49+
type FieldName = T.Text
50+
51+
getAnnotation :: Syntax.Field ann -> ann
52+
getAnnotation (Syntax.Field (Syntax.Name ann _) _) = ann
53+
getAnnotation (Syntax.Section (Syntax.Name ann _) _ _) = ann
54+
55+
getFieldName :: Syntax.Field ann -> FieldName
56+
getFieldName (Syntax.Field (Syntax.Name _ fn) _) = T.decodeUtf8 fn
57+
getFieldName (Syntax.Section (Syntax.Name _ fn) _ _) = T.decodeUtf8 fn
58+
59+
-- | Returns the name of a section if it has a name.
60+
--
61+
-- This assumes that the given section args belong to named stanza
62+
-- in which case the stanza name is returned.
63+
getOptionalSectionName :: [Syntax.SectionArg ann] -> Maybe T.Text
64+
getOptionalSectionName [] = Nothing
65+
getOptionalSectionName (x:xs) = case x of
66+
Syntax.SecArgName _ name -> Just (T.decodeUtf8 name)
67+
_ -> getOptionalSectionName xs
68+

plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Completer/Simple.hs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE LambdaCase #-}
12
{-# LANGUAGE OverloadedStrings #-}
23

34
module Ide.Plugin.Cabal.Completion.Completer.Simple where
@@ -7,11 +8,14 @@ import Data.Function ((&))
78
import qualified Data.List as List
89
import Data.Map (Map)
910
import qualified Data.Map as Map
10-
import Data.Maybe (fromMaybe)
11+
import Data.Maybe (fromMaybe,
12+
mapMaybe)
1113
import Data.Ord (Down (Down))
1214
import qualified Data.Text as T
15+
import qualified Distribution.Fields as Syntax
1316
import Ide.Logger (Priority (..),
1417
logWith)
18+
import Ide.Plugin.Cabal.Completion.CabalFields
1519
import Ide.Plugin.Cabal.Completion.Completer.Types
1620
import Ide.Plugin.Cabal.Completion.Types (CabalPrefixInfo (..),
1721
Log)
@@ -41,6 +45,22 @@ constantCompleter completions _ cData = do
4145
range = completionRange prefInfo
4246
pure $ map (mkSimpleCompletionItem range . Fuzzy.original) scored
4347

48+
-- | Completer to be used for import fields.
49+
--
50+
-- TODO: Does not exclude imports, defined after the current cursor position
51+
-- which are not allowed according to the cabal specification
52+
importCompleter :: Completer
53+
importCompleter l cData = do
54+
cabalCommonsM <- getCabalCommonSections cData
55+
case cabalCommonsM of
56+
Just cabalCommons -> do
57+
let commonNames = mapMaybe (\case
58+
Syntax.Section (Syntax.Name _ "common") commonNames _ -> getOptionalSectionName commonNames
59+
_ -> Nothing)
60+
cabalCommons
61+
constantCompleter commonNames l cData
62+
Nothing -> noopCompleter l cData
63+
4464
-- | Completer to be used for the field @name:@ value.
4565
--
4666
-- This is almost always the name of the cabal file. However,

plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Completer/Types.hs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
module Ide.Plugin.Cabal.Completion.Completer.Types where
44

55
import Development.IDE as D
6+
import qualified Distribution.Fields as Syntax
67
import Distribution.PackageDescription (GenericPackageDescription)
8+
import qualified Distribution.Parsec.Position as Syntax
79
import Ide.Plugin.Cabal.Completion.Types
810
import Language.LSP.Protocol.Types (CompletionItem)
911

@@ -16,9 +18,11 @@ data CompleterData = CompleterData
1618
{ -- | Access to the latest available generic package description for the handled cabal file,
1719
-- relevant for some completion actions which require the file's meta information
1820
-- such as the module completers which require access to source directories
19-
getLatestGPD :: IO (Maybe GenericPackageDescription),
21+
getLatestGPD :: IO (Maybe GenericPackageDescription),
22+
-- | Access to the entries of the handled cabal file as parsed by ParseCabalFields
23+
getCabalCommonSections :: IO (Maybe [Syntax.Field Syntax.Position]),
2024
-- | Prefix info to be used for constructing completion items
21-
cabalPrefixInfo :: CabalPrefixInfo,
25+
cabalPrefixInfo :: CabalPrefixInfo,
2226
-- | The name of the stanza in which the completer is applied
23-
stanzaName :: Maybe StanzaName
27+
stanzaName :: Maybe StanzaName
2428
}

plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Completions.hs

Lines changed: 1 addition & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import Data.List.NonEmpty (NonEmpty)
88
import qualified Data.List.NonEmpty as NE
99
import qualified Data.Map as Map
1010
import qualified Data.Text as T
11-
import qualified Data.Text.Encoding as T
1211
import Development.IDE as D
1312
import qualified Development.IDE.Plugin.Completions.Types as Ghcide
1413
import qualified Distribution.Fields as Syntax
1514
import qualified Distribution.Parsec.Position as Syntax
15+
import Ide.Plugin.Cabal.Completion.CabalFields
1616
import Ide.Plugin.Cabal.Completion.Completer.Simple
1717
import Ide.Plugin.Cabal.Completion.Completer.Snippet
1818
import Ide.Plugin.Cabal.Completion.Completer.Types (Completer)
@@ -177,57 +177,3 @@ classifyFieldContext ctx cursor field
177177

178178
cursorColumn = Syntax.positionCol cursor
179179
fieldColumn = Syntax.positionCol (getAnnotation field)
180-
181-
-- ----------------------------------------------------------------
182-
-- Cabal-syntax utilities I don't really want to write myself
183-
-- ----------------------------------------------------------------
184-
185-
-- | Determine the context of a cursor position within a stack of stanza contexts
186-
--
187-
-- If the cursor is indented more than one of the stanzas in the stack
188-
-- the respective stanza is returned if this is never the case, the toplevel stanza
189-
-- in the stack is returned.
190-
findStanzaForColumn :: Int -> NonEmpty (Int, StanzaContext) -> (StanzaContext, FieldContext)
191-
findStanzaForColumn col ctx = case NE.uncons ctx of
192-
((_, stanza), Nothing) -> (stanza, None)
193-
((indentation, stanza), Just res)
194-
| col < indentation -> findStanzaForColumn col res
195-
| otherwise -> (stanza, None)
196-
197-
-- | Determine the field the cursor is currently a part of.
198-
--
199-
-- The result is said field and its starting position
200-
-- or Nothing if the passed list of fields is empty.
201-
202-
-- This only looks at the row of the cursor and not at the cursor's
203-
-- position within the row.
204-
--
205-
-- TODO: we do not handle braces correctly. Add more tests!
206-
findFieldSection :: Syntax.Position -> [Syntax.Field Syntax.Position] -> Maybe (Syntax.Field Syntax.Position)
207-
findFieldSection _cursor [] = Nothing
208-
findFieldSection _cursor [x] =
209-
-- Last field. We decide later, whether we are starting
210-
-- a new section.
211-
Just x
212-
findFieldSection cursor (x:y:ys)
213-
| Syntax.positionRow (getAnnotation x) <= cursorLine && cursorLine < Syntax.positionRow (getAnnotation y)
214-
= Just x
215-
| otherwise = findFieldSection cursor (y:ys)
216-
where
217-
cursorLine = Syntax.positionRow cursor
218-
219-
type FieldName = T.Text
220-
221-
getAnnotation :: Syntax.Field ann -> ann
222-
getAnnotation (Syntax.Field (Syntax.Name ann _) _) = ann
223-
getAnnotation (Syntax.Section (Syntax.Name ann _) _ _) = ann
224-
225-
getFieldName :: Syntax.Field ann -> FieldName
226-
getFieldName (Syntax.Field (Syntax.Name _ fn) _) = T.decodeUtf8 fn
227-
getFieldName (Syntax.Section (Syntax.Name _ fn) _ _) = T.decodeUtf8 fn
228-
229-
getOptionalSectionName :: [Syntax.SectionArg ann] -> Maybe T.Text
230-
getOptionalSectionName [] = Nothing
231-
getOptionalSectionName (x:xs) = case x of
232-
Syntax.SecArgName _ name -> Just (T.decodeUtf8 name)
233-
_ -> getOptionalSectionName xs

plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Data.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ flagFields =
162162
libExecTestBenchCommons :: Map KeyWordName Completer
163163
libExecTestBenchCommons =
164164
Map.fromList
165-
[ ("build-depends:", noopCompleter),
165+
[ ("import:", importCompleter),
166+
("build-depends:", noopCompleter),
166167
("hs-source-dirs:", directoryCompleter),
167168
("default-extensions:", noopCompleter),
168169
("other-extensions:", noopCompleter),

plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completion/Types.hs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ instance Hashable ParseCabalFields
5959

6060
instance NFData ParseCabalFields
6161

62+
type instance RuleResult ParseCabalCommonSections = [Syntax.Field Syntax.Position]
63+
64+
data ParseCabalCommonSections = ParseCabalCommonSections
65+
deriving (Eq, Show, Typeable, Generic)
66+
67+
instance Hashable ParseCabalCommonSections
68+
69+
instance NFData ParseCabalCommonSections
70+
6271
-- | The context a cursor can be in within a cabal file.
6372
--
6473
-- We can be in stanzas or the top level,

0 commit comments

Comments
 (0)