-
-
Notifications
You must be signed in to change notification settings - Fork 389
/
Copy pathCodeRange.hs
136 lines (122 loc) · 7.21 KB
/
CodeRange.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Ide.Plugin.CodeRange (
descriptor
, Log
-- * Internal
, findPosition
) where
import Control.Monad.Except (ExceptT (ExceptT),
runExceptT)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Maybe (MaybeT (MaybeT),
maybeToExceptT)
import Data.Either.Extra (maybeToEither)
import Data.Maybe (fromMaybe)
import Data.Vector (Vector)
import qualified Data.Vector as V
import Development.IDE (IdeAction,
IdeState (shakeExtras),
Range (Range), Recorder,
WithPriority,
cmapWithPrio,
runIdeAction,
toNormalizedFilePath',
uriToFilePath')
import Development.IDE.Core.Actions (useE)
import Development.IDE.Core.PositionMapping (PositionMapping,
fromCurrentPosition,
toCurrentRange)
import Development.IDE.Types.Logger (Pretty (..))
import Ide.Plugin.CodeRange.Rules (CodeRange (..),
GetCodeRange (..),
codeRangeRule)
import qualified Ide.Plugin.CodeRange.Rules as Rules (Log)
import Ide.PluginUtils (pluginResponse,
positionInRange)
import Ide.Types (PluginDescriptor (pluginHandlers, pluginRules),
PluginId,
defaultPluginDescriptor,
mkPluginHandler)
import Language.LSP.Server (LspM)
import Language.LSP.Types (List (List),
NormalizedFilePath,
Position (..),
Range (_start),
ResponseError,
SMethod (STextDocumentSelectionRange),
SelectionRange (..),
SelectionRangeParams (..),
TextDocumentIdentifier (TextDocumentIdentifier),
Uri)
import Prelude hiding (log, span)
descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState
descriptor recorder plId = (defaultPluginDescriptor plId)
{ pluginHandlers = mkPluginHandler STextDocumentSelectionRange selectionRangeHandler
-- TODO @sloorush add folding range
-- <> mkPluginHandler STextDocumentFoldingRange foldingRangeHandler
, pluginRules = codeRangeRule (cmapWithPrio LogRules recorder)
}
data Log = LogRules Rules.Log
instance Pretty Log where
pretty log = case log of
LogRules codeRangeLog -> pretty codeRangeLog
selectionRangeHandler :: IdeState -> PluginId -> SelectionRangeParams -> LspM c (Either ResponseError (List SelectionRange))
selectionRangeHandler ide _ SelectionRangeParams{..} = do
pluginResponse $ do
filePath <- ExceptT . pure . maybeToEither "fail to convert uri to file path" $
toNormalizedFilePath' <$> uriToFilePath' uri
selectionRanges <- ExceptT . liftIO . runIdeAction "SelectionRange" (shakeExtras ide) . runExceptT $
getSelectionRanges filePath positions
pure . List $ selectionRanges
where
uri :: Uri
TextDocumentIdentifier uri = _textDocument
positions :: [Position]
List positions = _positions
getSelectionRanges :: NormalizedFilePath -> [Position] -> ExceptT String IdeAction [SelectionRange]
getSelectionRanges file positions = do
(codeRange, positionMapping) <- maybeToExceptT "fail to get code range" $ useE GetCodeRange file
-- 'positionMapping' should be appied to the input before using them
positions' <- maybeToExceptT "fail to apply position mapping to input positions" . MaybeT . pure $
traverse (fromCurrentPosition positionMapping) positions
let selectionRanges = flip fmap positions' $ \pos ->
-- We need a default selection range if the lookup fails, so that other positions can still have valid results.
let defaultSelectionRange = SelectionRange (Range pos pos) Nothing
in fromMaybe defaultSelectionRange . findPosition pos $ codeRange
-- 'positionMapping' should be applied to the output ranges before returning them
maybeToExceptT "fail to apply position mapping to output positions" . MaybeT . pure $
traverse (toCurrentSelectionRange positionMapping) selectionRanges
-- | Find 'Position' in 'CodeRange'. This can fail, if the given position is not covered by the 'CodeRange'.
findPosition :: Position -> CodeRange -> Maybe SelectionRange
findPosition pos root = go Nothing root
where
-- Helper function for recursion. The range list is built top-down
go :: Maybe SelectionRange -> CodeRange -> Maybe SelectionRange
go acc node =
if positionInRange pos range
then maybe acc' (go acc') (binarySearchPos children)
-- If all children doesn't contain pos, acc' will be returned.
-- acc' will be Nothing only if we are in the root level.
else Nothing
where
range = _codeRange_range node
children = _codeRange_children node
acc' = Just $ maybe (SelectionRange range Nothing) (SelectionRange range . Just) acc
binarySearchPos :: Vector CodeRange -> Maybe CodeRange
binarySearchPos v
| V.null v = Nothing
| V.length v == 1,
Just r <- V.headM v = if positionInRange pos (_codeRange_range r) then Just r else Nothing
| otherwise = do
let (left, right) = V.splitAt (V.length v `div` 2) v
startOfRight <- _start . _codeRange_range <$> V.headM right
if pos < startOfRight then binarySearchPos left else binarySearchPos right
-- | Likes 'toCurrentPosition', but works on 'SelectionRange'
toCurrentSelectionRange :: PositionMapping -> SelectionRange -> Maybe SelectionRange
toCurrentSelectionRange positionMapping SelectionRange{..} = do
newRange <- toCurrentRange positionMapping _range
pure $ SelectionRange {
_range = newRange,
_parent = _parent >>= toCurrentSelectionRange positionMapping
}