1
1
{-# LANGUAGE DataKinds #-}
2
+ {-# LANGUAGE LambdaCase #-}
2
3
{-# LANGUAGE OverloadedStrings #-}
3
4
{-# LANGUAGE PatternSynonyms #-}
4
5
{-# LANGUAGE RecordWildCards #-}
5
6
{-# LANGUAGE ViewPatterns #-}
6
7
{-# OPTIONS_GHC -Wall -Wwarn -fno-warn-type-defaults #-}
8
+ {-# OPTIONS_GHC -Wno-name-shadowing #-}
7
9
8
10
{- | Keep the module name in sync with its file path.
9
11
@@ -15,65 +17,71 @@ module Ide.Plugin.ModuleName (
15
17
descriptor ,
16
18
) where
17
19
18
- import Control.Monad (forM_ , void )
19
- import Control.Monad.IO.Class (liftIO )
20
- import Control.Monad.Trans.Class (lift )
20
+ import Control.Monad (forM_ , void )
21
+ import Control.Monad.IO.Class (liftIO )
22
+ import Control.Monad.Trans.Class (lift )
21
23
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 )
24
+ import Data.Aeson (Value (Null ), toJSON )
25
+ import Data.Char (isLower )
26
+ import qualified Data.HashMap.Strict as HashMap
27
+ import Data.List (intercalate , isPrefixOf ,
28
+ minimumBy )
29
+ import qualified Data.List.NonEmpty as NE
30
+ import Data.Maybe (maybeToList )
31
+ import Data.Ord (comparing )
32
+ import Data.String (IsString )
33
+ import qualified Data.Text as T
34
+ import Development.IDE (GetParsedModule (GetParsedModule ),
35
+ GhcSession (GhcSession ),
36
+ IdeState , Pretty ,
37
+ Priority (Debug , Info ), Recorder ,
38
+ WithPriority , evalGhcEnv ,
39
+ hscEnvWithImportPaths , logWith ,
40
+ realSrcSpanToRange , runAction ,
41
+ uriToFilePath' , use , use_ )
42
+ import Development.IDE.GHC.Compat (GenLocated (L ),
43
+ getSessionDynFlags , hsmodName ,
44
+ importPaths , locA ,
45
+ moduleNameString ,
46
+ pattern RealSrcSpan ,
47
+ pm_parsed_source , unLoc )
48
+ import Development.IDE.Types.Logger (Pretty (.. ))
41
49
import Ide.Types
42
50
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 )
51
+ import Language.LSP.Types hiding
52
+ (SemanticTokenAbsolute (length , line ),
53
+ SemanticTokenRelative (length ),
54
+ SemanticTokensEdit (_start ))
55
+ import Language.LSP.VFS (virtualFileText )
56
+ import System.Directory ( canonicalizePath , makeAbsolute )
57
+ import System.FilePath (dropExtension , 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 (ModuleNameLog $ " ModuleName.correctNames: " <> T. unlines correctNames)
105
114
bestName <- minimumBy (comparing T. length ) <$> (MaybeT . pure $ NE. nonEmpty correctNames)
115
+ logWith recorder Info (ModuleNameLog $ " ModuleName.bestName: " <> bestName)
106
116
107
- statedNameMaybe <- liftIO $ traceAs " statedName" <$> codeModuleName state nfp
117
+ statedNameMaybe <- liftIO $ codeModuleName state nfp
118
+ logWith recorder Debug (ModuleNameLog $ " ModuleName.statedNameMaybe: " <> (T. pack $ show statedNameMaybe))
108
119
case statedNameMaybe of
109
120
Just (nameRange, statedName)
110
121
| statedName `notElem` correctNames ->
@@ -118,22 +129,28 @@ 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 (ModuleNameLog $ " ModuleName.srcpath: " <> T. pack (unlines srcPaths))
139
+
140
+ paths <- mapM canonicalizePath srcPaths
141
+ logWith recorder Debug (ModuleNameLog $ " ModuleName.paths: " <> T. pack (unlines paths))
142
+
128
143
mdlPath <- makeAbsolute filePath
144
+ logWith recorder Debug (ModuleNameLog $ " ModuleName.mdlPath: " <> T. pack mdlPath)
145
+
129
146
let prefixes = filter (`isPrefixOf` mdlPath) paths
130
147
pure (map (moduleNameFrom mdlPath) prefixes)
131
148
where
132
149
moduleNameFrom mdlPath prefix =
133
150
T. pack
134
151
. intercalate " ."
135
152
. splitDirectories
136
- . drop (length prefix + 1 )
153
+ . drop (length prefix + 1 ) -- plus one to remove `/`, for example `a/b/c` to `b/c`.
137
154
$ dropExtension mdlPath
138
155
139
156
-- | The module name, as stated in the module
@@ -143,8 +160,8 @@ codeModuleName state nfp = runMaybeT $ do
143
160
L (locA -> (RealSrcSpan l _)) m <- MaybeT . pure . hsmodName . unLoc $ pm_parsed_source pm
144
161
pure (realSrcSpanToRange l, T. pack $ moduleNameString m)
145
162
146
- -- traceAs :: Show a => String -> a -> a
147
- -- traceAs lbl a = trace (lbl ++ " = " ++ show a) a
163
+ newtype Log = ModuleNameLog T. Text deriving Show
148
164
149
- traceAs :: b -> a -> a
150
- traceAs _ a = a
165
+ instance Pretty Log where
166
+ pretty = \ case
167
+ ModuleNameLog log -> pretty log
0 commit comments