|
| 1 | +{-# LANGUAGE ViewPatterns #-} |
| 2 | +{-# LANGUAGE DeriveGeneric #-} |
| 3 | +{-# LANGUAGE DuplicateRecordFields #-} |
| 4 | +{-# LANGUAGE FlexibleContexts #-} |
| 5 | +{-# LANGUAGE FlexibleInstances #-} |
| 6 | +{-# LANGUAGE OverloadedStrings #-} |
| 7 | +{-# LANGUAGE RecordWildCards #-} |
| 8 | +{-# LANGUAGE TupleSections #-} |
| 9 | +{-# LANGUAGE TypeFamilies #-} |
| 10 | + |
| 11 | +module Ide.Plugin.Example2 |
| 12 | + ( |
| 13 | + plugin |
| 14 | + ) where |
| 15 | + |
| 16 | +import Control.DeepSeq ( NFData ) |
| 17 | +import Control.Monad.Trans.Maybe |
| 18 | +import Data.Aeson.Types (toJSON, fromJSON, Value(..), Result(..)) |
| 19 | +import Data.Binary |
| 20 | +import Data.Functor |
| 21 | +import qualified Data.HashMap.Strict as Map |
| 22 | +import Data.Hashable |
| 23 | +import qualified Data.HashSet as HashSet |
| 24 | +import qualified Data.Text as T |
| 25 | +import Data.Typeable |
| 26 | +import Development.IDE.Core.OfInterest |
| 27 | +import Development.IDE.Core.Rules |
| 28 | +import Development.IDE.Core.RuleTypes |
| 29 | +import Development.IDE.Core.Service |
| 30 | +import Development.IDE.Core.Shake |
| 31 | +import Development.IDE.LSP.Server |
| 32 | +import Development.IDE.Plugin |
| 33 | +import Development.IDE.Types.Diagnostics as D |
| 34 | +import Development.IDE.Types.Location |
| 35 | +import Development.IDE.Types.Logger |
| 36 | +import Development.Shake hiding ( Diagnostic ) |
| 37 | +import GHC.Generics |
| 38 | +import qualified Language.Haskell.LSP.Core as LSP |
| 39 | +import Language.Haskell.LSP.Messages |
| 40 | +import Language.Haskell.LSP.Types |
| 41 | +import Text.Regex.TDFA.Text() |
| 42 | + |
| 43 | +-- --------------------------------------------------------------------- |
| 44 | + |
| 45 | +plugin :: Plugin c |
| 46 | +plugin = Plugin exampleRules handlersExample2 |
| 47 | + <> codeActionPlugin codeAction |
| 48 | + <> Plugin mempty handlersCodeLens |
| 49 | + |
| 50 | +hover :: IdeState -> TextDocumentPositionParams -> IO (Either ResponseError (Maybe Hover)) |
| 51 | +hover = request "Hover" blah (Right Nothing) foundHover |
| 52 | + |
| 53 | +blah :: NormalizedFilePath -> Position -> Action (Maybe (Maybe Range, [T.Text])) |
| 54 | +blah _ (Position line col) |
| 55 | + = return $ Just (Just (Range (Position line col) (Position (line+1) 0)), ["example hover"]) |
| 56 | + |
| 57 | +handlersExample2 :: PartialHandlers c |
| 58 | +handlersExample2 = PartialHandlers $ \WithMessage{..} x -> |
| 59 | + return x{LSP.hoverHandler = withResponse RspHover $ const hover} |
| 60 | + |
| 61 | + |
| 62 | +-- --------------------------------------------------------------------- |
| 63 | + |
| 64 | +data Example2 = Example2 |
| 65 | + deriving (Eq, Show, Typeable, Generic) |
| 66 | +instance Hashable Example2 |
| 67 | +instance NFData Example2 |
| 68 | +instance Binary Example2 |
| 69 | + |
| 70 | +type instance RuleResult Example2 = () |
| 71 | + |
| 72 | +exampleRules :: Rules () |
| 73 | +exampleRules = do |
| 74 | + define $ \Example2 file -> do |
| 75 | + _pm <- getParsedModule file |
| 76 | + let diag = mkDiag file "example2" DsError (Range (Position 0 0) (Position 1 0)) "example2 diagnostic, hello world" |
| 77 | + return ([diag], Just ()) |
| 78 | + |
| 79 | + action $ do |
| 80 | + files <- getFilesOfInterest |
| 81 | + void $ uses Example2 $ HashSet.toList files |
| 82 | + |
| 83 | +mkDiag :: NormalizedFilePath |
| 84 | + -> DiagnosticSource |
| 85 | + -> DiagnosticSeverity |
| 86 | + -> Range |
| 87 | + -> T.Text |
| 88 | + -> FileDiagnostic |
| 89 | +mkDiag file diagSource sev loc msg = (file, D.ShowDiag,) |
| 90 | + Diagnostic |
| 91 | + { _range = loc |
| 92 | + , _severity = Just sev |
| 93 | + , _source = Just diagSource |
| 94 | + , _message = msg |
| 95 | + , _code = Nothing |
| 96 | + , _relatedInformation = Nothing |
| 97 | + } |
| 98 | + |
| 99 | +-- --------------------------------------------------------------------- |
| 100 | + |
| 101 | +-- | Generate code actions. |
| 102 | +codeAction |
| 103 | + :: LSP.LspFuncs c |
| 104 | + -> IdeState |
| 105 | + -> TextDocumentIdentifier |
| 106 | + -> Range |
| 107 | + -> CodeActionContext |
| 108 | + -> IO (Either ResponseError [CAResult]) |
| 109 | +codeAction _lsp _state (TextDocumentIdentifier uri) _range CodeActionContext{_diagnostics=List _xs} = do |
| 110 | + let |
| 111 | + title = "Add TODO2 Item" |
| 112 | + tedit = [TextEdit (Range (Position 0 0) (Position 0 0)) |
| 113 | + "-- TODO2 added by Example2 Plugin directly\n"] |
| 114 | + edit = WorkspaceEdit (Just $ Map.singleton uri $ List tedit) Nothing |
| 115 | + pure $ Right |
| 116 | + [ CACodeAction $ CodeAction title (Just CodeActionQuickFix) (Just $ List []) (Just edit) Nothing ] |
| 117 | + |
| 118 | +-- --------------------------------------------------------------------- |
| 119 | + |
| 120 | +-- | Generate code lenses. |
| 121 | +handlersCodeLens :: PartialHandlers c |
| 122 | +handlersCodeLens = PartialHandlers $ \WithMessage{..} x -> return x{ |
| 123 | + LSP.codeLensHandler = withResponse RspCodeLens codeLens, |
| 124 | + LSP.executeCommandHandler = withResponseAndRequest RspExecuteCommand ReqApplyWorkspaceEdit executeAddSignatureCommand |
| 125 | + } |
| 126 | + |
| 127 | +codeLens |
| 128 | + :: LSP.LspFuncs c |
| 129 | + -> IdeState |
| 130 | + -> CodeLensParams |
| 131 | + -> IO (Either ResponseError (List CodeLens)) |
| 132 | +codeLens _lsp ideState CodeLensParams{_textDocument=TextDocumentIdentifier uri} = do |
| 133 | + case uriToFilePath' uri of |
| 134 | + Just (toNormalizedFilePath -> filePath) -> do |
| 135 | + _ <- runAction ideState $ runMaybeT $ useE TypeCheck filePath |
| 136 | + _diag <- getDiagnostics ideState |
| 137 | + _hDiag <- getHiddenDiagnostics ideState |
| 138 | + let |
| 139 | + title = "Add TODO2 Item via Code Lens" |
| 140 | + tedit = [TextEdit (Range (Position 3 0) (Position 3 0)) |
| 141 | + "-- TODO2 added by Example2 Plugin via code lens action\n"] |
| 142 | + edit = WorkspaceEdit (Just $ Map.singleton uri $ List tedit) Nothing |
| 143 | + range = (Range (Position 3 0) (Position 4 0)) |
| 144 | + pure $ Right $ List |
| 145 | + -- [ CodeLens range (Just (Command title "codelens.do" (Just $ List [toJSON edit]))) Nothing |
| 146 | + [ CodeLens range (Just (Command title "codelens.todo" (Just $ List [toJSON edit]))) Nothing |
| 147 | + ] |
| 148 | + Nothing -> pure $ Right $ List [] |
| 149 | + |
| 150 | +-- | Execute the "codelens.todo2" command. |
| 151 | +executeAddSignatureCommand |
| 152 | + :: LSP.LspFuncs c |
| 153 | + -> IdeState |
| 154 | + -> ExecuteCommandParams |
| 155 | + -> IO (Value, Maybe (ServerMethod, ApplyWorkspaceEditParams)) |
| 156 | +executeAddSignatureCommand _lsp _ideState ExecuteCommandParams{..} |
| 157 | + | _command == "codelens.todo2" |
| 158 | + , Just (List [edit]) <- _arguments |
| 159 | + , Success wedit <- fromJSON edit |
| 160 | + = return (Null, Just (WorkspaceApplyEdit, ApplyWorkspaceEditParams wedit)) |
| 161 | + | otherwise |
| 162 | + = return (Null, Nothing) |
| 163 | + |
| 164 | +-- --------------------------------------------------------------------- |
| 165 | + |
| 166 | +foundHover :: (Maybe Range, [T.Text]) -> Either ResponseError (Maybe Hover) |
| 167 | +foundHover (mbRange, contents) = |
| 168 | + Right $ Just $ Hover (HoverContents $ MarkupContent MkMarkdown |
| 169 | + $ T.intercalate sectionSeparator contents) mbRange |
| 170 | + |
| 171 | + |
| 172 | +-- | Respond to and log a hover or go-to-definition request |
| 173 | +request |
| 174 | + :: T.Text |
| 175 | + -> (NormalizedFilePath -> Position -> Action (Maybe a)) |
| 176 | + -> Either ResponseError b |
| 177 | + -> (a -> Either ResponseError b) |
| 178 | + -> IdeState |
| 179 | + -> TextDocumentPositionParams |
| 180 | + -> IO (Either ResponseError b) |
| 181 | +request label getResults notFound found ide (TextDocumentPositionParams (TextDocumentIdentifier uri) pos _) = do |
| 182 | + mbResult <- case uriToFilePath' uri of |
| 183 | + Just path -> logAndRunRequest label getResults ide pos path |
| 184 | + Nothing -> pure Nothing |
| 185 | + pure $ maybe notFound found mbResult |
| 186 | + |
| 187 | +logAndRunRequest :: T.Text -> (NormalizedFilePath -> Position -> Action b) -> IdeState -> Position -> String -> IO b |
| 188 | +logAndRunRequest label getResults ide pos path = do |
| 189 | + let filePath = toNormalizedFilePath path |
| 190 | + logInfo (ideLogger ide) $ |
| 191 | + label <> " request at position " <> T.pack (showPosition pos) <> |
| 192 | + " in file: " <> T.pack path |
| 193 | + runAction ide $ getResults filePath pos |
0 commit comments