@@ -13,16 +13,24 @@ module Ide.Plugin.OverloadedRecordDot
13
13
14
14
-- based off of Berk Okzuturk's hls-explicit-records-fields-plugin
15
15
16
- import Control.Lens ((^.) )
16
+ import Control.Lens (_Just , (^.) , (^?) )
17
+ import Control.Monad (replicateM )
17
18
import Control.Monad.IO.Class (MonadIO , liftIO )
19
+ import Control.Monad.Trans.Class (lift )
18
20
import Control.Monad.Trans.Except (ExceptT )
21
+ import Data.Aeson (FromJSON , ToJSON , decode ,
22
+ encode , fromJSON , toJSON )
19
23
import Data.Generics (GenericQ , everything ,
20
24
everythingBut , mkQ )
25
+ import qualified Data.IntMap.Strict as IntMap
21
26
import qualified Data.Map as Map
22
- import Data.Maybe (mapMaybe , maybeToList )
27
+ import Data.Maybe (fromJust , mapMaybe ,
28
+ maybeToList )
23
29
import Data.Text (Text )
30
+ import Data.Unique
24
31
import Development.IDE (IdeState ,
25
32
NormalizedFilePath ,
33
+ NormalizedUri ,
26
34
Pretty (.. ), Range ,
27
35
Recorder (.. ), Rules ,
28
36
WithPriority (.. ),
@@ -76,17 +84,20 @@ import Ide.Types (PluginDescriptor (..),
76
84
PluginMethodHandler ,
77
85
defaultPluginDescriptor ,
78
86
mkPluginHandler )
87
+ import Language.LSP.Protocol.Lens (HasChanges (changes ))
79
88
import qualified Language.LSP.Protocol.Lens as L
80
89
import Language.LSP.Protocol.Message (Method (.. ),
81
90
SMethod (.. ))
82
91
import Language.LSP.Protocol.Types (CodeAction (.. ),
83
92
CodeActionKind (CodeActionKind_RefactorRewrite ),
84
93
CodeActionParams (.. ),
85
94
Command , TextEdit (.. ),
95
+ Uri (.. ),
86
96
WorkspaceEdit (WorkspaceEdit ),
87
97
fromNormalizedUri ,
88
98
normalizedFilePathToUri ,
89
99
type (|? ) (.. ))
100
+ import Language.LSP.Server (getClientCapabilities )
90
101
data Log
91
102
= LogShake Shake. Log
92
103
| LogCollectedRecordSelectors [RecordSelectorExpr ]
@@ -105,7 +116,8 @@ instance Hashable CollectRecordSelectors
105
116
instance NFData CollectRecordSelectors
106
117
107
118
data CollectRecordSelectorsResult = CRSR
108
- { recordInfos :: RangeMap RecordSelectorExpr
119
+ { records :: RangeMap Int
120
+ , recordInfos :: IntMap. IntMap RecordSelectorExpr
109
121
, enabledExtensions :: [Extension ]
110
122
}
111
123
deriving (Generic )
@@ -135,56 +147,102 @@ instance Pretty RecordSelectorExpr where
135
147
instance NFData RecordSelectorExpr where
136
148
rnf = rwhnf
137
149
150
+ data ORDResolveData = ORDRD {
151
+ uri :: NormalizedFilePath
152
+ , uniqueID :: Int
153
+ } deriving (Generic , Show )
154
+ instance ToJSON ORDResolveData
155
+ instance FromJSON ORDResolveData
156
+
157
+ -- TODO: move the orphans to their packages
158
+ instance ToJSON NormalizedFilePath
159
+ instance FromJSON NormalizedFilePath
160
+ instance ToJSON NormalizedUri
161
+ instance FromJSON NormalizedUri
138
162
descriptor :: Recorder (WithPriority Log ) -> PluginId
139
163
-> PluginDescriptor IdeState
140
164
descriptor recorder plId = (defaultPluginDescriptor plId)
141
165
{ pluginHandlers =
142
- mkPluginHandler SMethod_TextDocumentCodeAction codeActionProvider
166
+ mkPluginHandler SMethod_TextDocumentCodeAction codeActionProvider <> mkPluginHandler SMethod_CodeActionResolve resolveProvider
143
167
, pluginRules = collectRecSelsRule recorder
144
168
}
145
169
170
+ resolveProvider :: PluginMethodHandler IdeState 'Method_CodeActionResolve
171
+ resolveProvider ideState pId ca@ (CodeAction _ _ _ _ _ _ _ (Just resData)) =
172
+ pluginResponse $ do
173
+ case decode . encode $ resData of
174
+ Just (ORDRD nfp int) -> do
175
+ CRSR _ crsDetails exts <- collectRecSelResult ideState nfp
176
+ pragma <- getFirstPragma pId ideState nfp
177
+ let pragmaEdit =
178
+ if OverloadedRecordDot `elem` exts
179
+ then Nothing
180
+ else Just $ insertNewPragma pragma OverloadedRecordDot
181
+ edits (Just crs) = convertRecordSelectors crs : maybeToList pragmaEdit
182
+ edits _ = []
183
+ changes = Just $ WorkspaceEdit
184
+ (Just (Map. singleton (fromNormalizedUri
185
+ (normalizedFilePathToUri nfp))
186
+ (edits (IntMap. lookup int crsDetails))))
187
+ Nothing Nothing
188
+ pure $ ca {_edit = changes}
189
+ _ -> pure ca
190
+
146
191
codeActionProvider :: PluginMethodHandler IdeState 'Method_TextDocumentCodeAction
147
192
codeActionProvider ideState pId (CodeActionParams _ _ caDocId caRange _) =
148
193
pluginResponse $ do
149
194
nfp <- getNormalizedFilePath (caDocId ^. L. uri)
150
195
pragma <- getFirstPragma pId ideState nfp
151
- CRSR crsMap exts <- collectRecSelResult ideState nfp
152
- let pragmaEdit =
196
+ caps <- lift getClientCapabilities
197
+ CRSR crsMap crsDetails exts <- collectRecSelResult ideState nfp
198
+ let supportsResolve :: Maybe Bool
199
+ supportsResolve = caps ^? L. textDocument . _Just . L. codeAction . _Just . L. dataSupport . _Just
200
+ pragmaEdit =
153
201
if OverloadedRecordDot `elem` exts
154
202
then Nothing
155
203
else Just $ insertNewPragma pragma OverloadedRecordDot
156
- edits crs = convertRecordSelectors crs : maybeToList pragmaEdit
157
- changes crs =
158
- Just $ Map. singleton (fromNormalizedUri
204
+ edits (Just crs) = convertRecordSelectors crs : maybeToList pragmaEdit
205
+ edits _ = []
206
+ changes crsM crsD =
207
+ case supportsResolve of
208
+ Just True -> Just $ WorkspaceEdit
209
+ (Just (Map. singleton (fromNormalizedUri
159
210
(normalizedFilePathToUri nfp))
160
- (edits crs)
161
- mkCodeAction crs = InR CodeAction
211
+ (edits (IntMap. lookup crsM crsD))))
212
+ Nothing Nothing
213
+ _ -> Nothing
214
+ resolveData crsM =
215
+ case supportsResolve of
216
+ Just True -> Just $ toJSON $ ORDRD nfp crsM
217
+ _ -> Nothing
218
+ mkCodeAction crsD crsM = InR CodeAction
162
219
{ -- We pass the record selector to the title function, so that
163
220
-- we can have the name of the record selector in the title of
164
221
-- the codeAction. This allows the user can easily distinguish
165
222
-- between the different codeActions when using nested record
166
223
-- selectors, the disadvantage is we need to print out the
167
224
-- name of the record selector which will decrease performance
168
- _title = mkCodeActionTitle exts crs
225
+ _title = mkCodeActionTitle exts crsM crsD
169
226
, _kind = Just CodeActionKind_RefactorRewrite
170
227
, _diagnostics = Nothing
171
228
, _isPreferred = Nothing
172
229
, _disabled = Nothing
173
- , _edit = Just $ WorkspaceEdit ( changes crs) Nothing Nothing
230
+ , _edit = changes crsM crsD
174
231
, _command = Nothing
175
- , _data_ = Nothing
232
+ , _data_ = resolveData crsM
176
233
}
177
- actions = map mkCodeAction (RangeMap. filterByRange caRange crsMap)
234
+ actions = map ( mkCodeAction crsDetails) (RangeMap. filterByRange caRange crsMap)
178
235
pure $ InL actions
179
236
where
180
- mkCodeActionTitle :: [Extension ] -> RecordSelectorExpr -> Text
181
- mkCodeActionTitle exts ( RecordSelectorExpr _ se _) =
237
+ mkCodeActionTitle :: [Extension ] -> Int -> IntMap. IntMap RecordSelectorExpr -> Text
238
+ mkCodeActionTitle exts crsM crsD =
182
239
if OverloadedRecordDot `elem` exts
183
240
then title
184
241
else title <> " (needs extension: OverloadedRecordDot)"
185
242
where
186
- title = " Convert `" <> name <> " ` to record dot syntax"
187
- name = printOutputable se
243
+ title = " Convert `" <> name (IntMap. lookup crsM crsD) <> " ` to record dot syntax"
244
+ name (Just (RecordSelectorExpr _ se _)) = printOutputable se
245
+ name _ = " "
188
246
189
247
collectRecSelsRule :: Recorder (WithPriority Log ) -> Rules ()
190
248
collectRecSelsRule recorder = define (cmapWithPrio LogShake recorder) $
@@ -201,11 +259,14 @@ collectRecSelsRule recorder = define (cmapWithPrio LogShake recorder) $
201
259
-- the OverloadedRecordDot pragma
202
260
exts = getEnabledExtensions tmr
203
261
recSels = mapMaybe (rewriteRange pm) (getRecordSelectors tmr)
262
+ uniques <- liftIO $ replicateM (length recSels) (hashUnique <$> newUnique)
204
263
logWith recorder Debug (LogCollectedRecordSelectors recSels)
205
- let -- We need the rangeMap to be able to filter by range later
206
- crsMap :: RangeMap RecordSelectorExpr
207
- crsMap = RangeMap. fromList location recSels
208
- pure ([] , CRSR <$> Just crsMap <*> Just exts)
264
+ let crsDetails = IntMap. fromList $ zip uniques recSels
265
+ -- We need the rangeMap to be able to filter by range later
266
+ crsMap :: RangeMap Int
267
+ crsMap = RangeMap. fromList (location . (\ x-> fromJust $ IntMap. lookup x crsDetails)) uniques
268
+ crsDetails :: IntMap. IntMap RecordSelectorExpr
269
+ pure ([] , CRSR <$> Just crsMap <*> Just crsDetails <*> Just exts)
209
270
where getEnabledExtensions :: TcModuleResult -> [Extension ]
210
271
getEnabledExtensions = getExtensions . tmrParsed
211
272
getRecordSelectors :: TcModuleResult -> [RecordSelectorExpr ]
0 commit comments