4
4
{-# LANGUAGE RecordWildCards #-}
5
5
{-# LANGUAGE ViewPatterns #-}
6
6
{-# OPTIONS_GHC -Wall -Wwarn -fno-warn-type-defaults #-}
7
+ {-# OPTIONS_GHC -Wno-name-shadowing #-}
7
8
8
9
{- | Keep the module name in sync with its file path.
9
10
@@ -15,65 +16,72 @@ module Ide.Plugin.ModuleName (
15
16
descriptor ,
16
17
) where
17
18
18
- import Control.Monad (forM_ , void )
19
- import Control.Monad.IO.Class (liftIO )
20
- import Control.Monad.Trans.Class (lift )
19
+ import Control.Monad (forM_ , void )
20
+ import Control.Monad.IO.Class (liftIO )
21
+ import Control.Monad.Trans.Class (lift )
21
22
import Control.Monad.Trans.Maybe
22
- import Data.Aeson (Value (Null ), toJSON )
23
- import Data.Char (isLower )
24
- import qualified Data.HashMap.Strict as HashMap
25
- import Data.List (intercalate , isPrefixOf , minimumBy )
26
- import qualified Data.List.NonEmpty as NE
27
- import Data.Maybe (maybeToList )
28
- import Data.Ord (comparing )
29
- import Data.String (IsString )
30
- import qualified Data.Text as T
31
- import Development.IDE (GetParsedModule (GetParsedModule ),
32
- GhcSession (GhcSession ), IdeState ,
33
- evalGhcEnv , hscEnvWithImportPaths ,
34
- realSrcSpanToRange , runAction ,
35
- uriToFilePath' , use , use_ )
36
- import Development.IDE.GHC.Compat (GenLocated (L ), getSessionDynFlags ,
37
- hsmodName , importPaths , locA ,
38
- moduleNameString ,
39
- pattern RealSrcSpan ,
40
- pm_parsed_source , unLoc )
23
+ import Data.Aeson (Value (Null ), toJSON )
24
+ import Data.Char (isLower )
25
+ import qualified Data.HashMap.Strict as HashMap
26
+ import Data.List (intercalate , isPrefixOf ,
27
+ minimumBy )
28
+ import qualified Data.List.NonEmpty as NE
29
+ import Data.Maybe (maybeToList )
30
+ import Data.Ord (comparing )
31
+ import Data.String (IsString )
32
+ import qualified Data.Text as T
33
+ import Development.IDE (GetParsedModule (GetParsedModule ),
34
+ GhcSession (GhcSession ),
35
+ IdeState , Pretty ,
36
+ Priority (Debug , Info ), Recorder ,
37
+ WithPriority , colon , evalGhcEnv ,
38
+ hscEnvWithImportPaths , logWith ,
39
+ realSrcSpanToRange , runAction ,
40
+ uriToFilePath' , use , use_ , (<+>) )
41
+ import Development.IDE.GHC.Compat (GenLocated (L ),
42
+ getSessionDynFlags , hsmodName ,
43
+ importPaths , locA ,
44
+ moduleNameString ,
45
+ pattern RealSrcSpan ,
46
+ pm_parsed_source , unLoc )
47
+ import Development.IDE.Types.Logger (Pretty (.. ))
41
48
import Ide.Types
42
49
import Language.LSP.Server
43
- import Language.LSP.Types hiding
44
- (SemanticTokenAbsolute (length , line ),
45
- SemanticTokenRelative (length ),
46
- SemanticTokensEdit (_start ))
47
- import Language.LSP.VFS (virtualFileText )
48
- import System.Directory (makeAbsolute )
49
- import System.FilePath (dropExtension , splitDirectories ,
50
- takeFileName )
50
+ import Language.LSP.Types hiding
51
+ (SemanticTokenAbsolute (length , line ),
52
+ SemanticTokenRelative (length ),
53
+ SemanticTokensEdit (_start ))
54
+ import Language.LSP.VFS (virtualFileText )
55
+ import System.Directory (makeAbsolute )
56
+ import System.FilePath (dropExtension , normalise ,
57
+ pathSeparator , splitDirectories ,
58
+ takeFileName )
51
59
52
60
-- | Plugin descriptor
53
- descriptor :: PluginId -> PluginDescriptor IdeState
54
- descriptor plId =
61
+ descriptor :: Recorder ( WithPriority Log ) -> PluginId -> PluginDescriptor IdeState
62
+ descriptor recorder plId =
55
63
(defaultPluginDescriptor plId)
56
- { pluginHandlers = mkPluginHandler STextDocumentCodeLens codeLens
57
- , pluginCommands = [PluginCommand updateModuleNameCommand " set name of module to match with file path" command]
64
+ { pluginHandlers = mkPluginHandler STextDocumentCodeLens ( codeLens recorder)
65
+ , pluginCommands = [PluginCommand updateModuleNameCommand " set name of module to match with file path" ( command recorder) ]
58
66
}
59
67
60
68
updateModuleNameCommand :: IsString p => p
61
69
updateModuleNameCommand = " updateModuleName"
62
70
63
71
-- | Generate code lenses
64
- codeLens :: PluginMethodHandler IdeState 'TextDocumentCodeLens
65
- codeLens state pluginId CodeLensParams {_textDocument= TextDocumentIdentifier uri} =
66
- Right . List . maybeToList . (asCodeLens <$> ) <$> action state uri
72
+ codeLens :: Recorder ( WithPriority Log ) -> PluginMethodHandler IdeState 'TextDocumentCodeLens
73
+ codeLens recorder state pluginId CodeLensParams {_textDocument= TextDocumentIdentifier uri} =
74
+ Right . List . maybeToList . (asCodeLens <$> ) <$> action recorder state uri
67
75
where
68
76
asCodeLens :: Action -> CodeLens
69
77
asCodeLens Replace {.. } = CodeLens aRange (Just cmd) Nothing
70
78
where
71
79
cmd = mkLspCommand pluginId updateModuleNameCommand aTitle (Just [toJSON aUri])
72
80
73
81
-- | (Quasi) Idempotent command execution: recalculate action to execute on command request
74
- command :: CommandFunction IdeState Uri
75
- command state uri = do
76
- actMaybe <- action state uri
82
+ command :: Recorder ( WithPriority Log ) -> CommandFunction IdeState Uri
83
+ command recorder state uri = do
84
+ actMaybe <- action recorder state uri
77
85
forM_ actMaybe $ \ Replace {.. } ->
78
86
let
79
87
-- | Convert an Action to the corresponding edit operation
@@ -92,19 +100,22 @@ data Action = Replace
92
100
deriving (Show )
93
101
94
102
-- | Required action (that can be converted to either CodeLenses or CodeActions)
95
- action :: IdeState -> Uri -> LspM c (Maybe Action )
96
- action state uri =
97
- traceAs " action " <$> runMaybeT $ do
103
+ action :: Recorder ( WithPriority Log ) -> IdeState -> Uri -> LspM c (Maybe Action )
104
+ action recorder state uri =
105
+ runMaybeT $ do
98
106
nfp <- MaybeT . pure . uriToNormalizedFilePath $ toNormalizedUri uri
99
107
fp <- MaybeT . pure $ uriToFilePath' uri
100
108
101
109
contents <- lift . getVirtualFile $ toNormalizedUri uri
102
110
let emptyModule = maybe True (T. null . T. strip . virtualFileText) contents
103
111
104
- correctNames <- liftIO $ traceAs " correctNames" <$> pathModuleNames state nfp fp
112
+ correctNames <- liftIO $ pathModuleNames recorder state nfp fp
113
+ logWith recorder Debug (CorrectNames correctNames)
105
114
bestName <- minimumBy (comparing T. length ) <$> (MaybeT . pure $ NE. nonEmpty correctNames)
115
+ logWith recorder Info (BestName bestName)
106
116
107
- statedNameMaybe <- liftIO $ traceAs " statedName" <$> codeModuleName state nfp
117
+ statedNameMaybe <- liftIO $ codeModuleName state nfp
118
+ logWith recorder Debug (ModuleName $ snd <$> statedNameMaybe)
108
119
case statedNameMaybe of
109
120
Just (nameRange, statedName)
110
121
| statedName `notElem` correctNames ->
@@ -118,22 +129,31 @@ action state uri =
118
129
-- | Possible module names, as derived by the position of the module in the
119
130
-- source directories. There may be more than one possible name, if the source
120
131
-- directories are nested inside each other.
121
- pathModuleNames :: IdeState -> NormalizedFilePath -> String -> IO [T. Text ]
122
- pathModuleNames state normFilePath filePath
132
+ pathModuleNames :: Recorder ( WithPriority Log ) -> IdeState -> NormalizedFilePath -> FilePath -> IO [T. Text ]
133
+ pathModuleNames recorder state normFilePath filePath
123
134
| isLower . head $ takeFileName filePath = return [" Main" ]
124
135
| otherwise = do
125
136
session <- runAction " ModuleName.ghcSession" state $ use_ GhcSession normFilePath
126
137
srcPaths <- evalGhcEnv (hscEnvWithImportPaths session) $ importPaths <$> getSessionDynFlags
127
- paths <- mapM makeAbsolute srcPaths
138
+ logWith recorder Debug (SrcPaths srcPaths)
139
+
140
+ -- Append a `pathSeparator` to make the path looks like a directory,
141
+ -- and then we can drop it uniformly.
142
+ -- See https://github.com/haskell/haskell-language-server/pull/3092 for details.
143
+ let paths = map (normalise . (<> pure pathSeparator)) srcPaths
144
+ logWith recorder Debug (NormalisedPaths paths)
145
+
128
146
mdlPath <- makeAbsolute filePath
147
+ logWith recorder Debug (AbsoluteFilePath mdlPath)
148
+
129
149
let prefixes = filter (`isPrefixOf` mdlPath) paths
130
150
pure (map (moduleNameFrom mdlPath) prefixes)
131
151
where
132
152
moduleNameFrom mdlPath prefix =
133
153
T. pack
134
154
. intercalate " ."
135
155
. splitDirectories
136
- . drop (length prefix + 1 )
156
+ . drop (length prefix)
137
157
$ dropExtension mdlPath
138
158
139
159
-- | The module name, as stated in the module
@@ -143,8 +163,20 @@ codeModuleName state nfp = runMaybeT $ do
143
163
L (locA -> (RealSrcSpan l _)) m <- MaybeT . pure . hsmodName . unLoc $ pm_parsed_source pm
144
164
pure (realSrcSpanToRange l, T. pack $ moduleNameString m)
145
165
146
- -- traceAs :: Show a => String -> a -> a
147
- -- traceAs lbl a = trace (lbl ++ " = " ++ show a) a
148
-
149
- traceAs :: b -> a -> a
150
- traceAs _ a = a
166
+ data Log =
167
+ CorrectNames [T. Text ]
168
+ | BestName T. Text
169
+ | ModuleName (Maybe T. Text )
170
+ | SrcPaths [FilePath ]
171
+ | NormalisedPaths [FilePath ]
172
+ | AbsoluteFilePath FilePath
173
+ deriving Show
174
+
175
+ instance Pretty Log where
176
+ pretty log = " ModuleName." <> case log of
177
+ CorrectNames log -> " CorrectNames" <> colon <+> pretty log
178
+ BestName log -> " BestName" <> colon <+> pretty log
179
+ ModuleName log -> " StatedNameMaybe" <> colon <+> pretty log
180
+ SrcPaths log -> " SrcPaths" <> colon <+> pretty log
181
+ NormalisedPaths log -> " NormalisedPaths" <> colon <+> pretty log
182
+ AbsoluteFilePath log -> " AbsoluteFilePath" <> colon <+> pretty log
0 commit comments