Skip to content

Feat: Folding Ranges #3058

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 34 commits into from
Sep 21, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
098882d
save some progress: add basic starter code for folding ranges
sloorush Jul 23, 2022
2e44b7a
save some progress: add function to traverse through coderange and fo…
sloorush Aug 6, 2022
fec1824
save some progress: add parsing of folding ranges
sloorush Aug 9, 2022
f9ffcc1
fix: maybe issue with foldingRanges
sloorush Aug 9, 2022
1d28a2c
add: generate folding ranges from coderange
sloorush Aug 9, 2022
60b2136
add: plugin request method instance for folding ranges
sloorush Aug 9, 2022
02a1e9d
ref: alter function and var names
sloorush Aug 9, 2022
a032c78
post review: cleanup crk to frk & fix typo
sloorush Aug 10, 2022
892a129
fix: find folding ranges function
sloorush Aug 12, 2022
e6a2b5c
format: run formatter and add comments
sloorush Aug 12, 2022
e21f5cb
fix: return all response results of folding range request
sloorush Aug 12, 2022
799db9b
Revert "format: run formatter and add comments"
sloorush Aug 13, 2022
5d0d159
add: removed comments after revert
sloorush Aug 13, 2022
332e953
fix: formatting
sloorush Aug 16, 2022
c4f386d
docs: add folding range to features section and cabal file
sloorush Aug 16, 2022
8eb7a30
refactor: use destructuring for createFoldingRange function and use c…
sloorush Aug 16, 2022
60e3fb2
test: add basic unit test for findFoldingRanges function
sloorush Sep 7, 2022
e3f0007
test: add tests for children and code kind
sloorush Sep 7, 2022
474ffef
test: add more test cases
sloorush Sep 7, 2022
6975302
test: add test for createFoldingRange
sloorush Sep 8, 2022
a430a43
test: add integration test for folding ranges
sloorush Sep 10, 2022
baf419e
fix: duplicate start line foldingranges and remove single line
sloorush Sep 11, 2022
d6a8666
Merge branch 'master' of github.com:sloorush/haskell-language-server …
sloorush Sep 12, 2022
e9dc569
refactor: duplicate folding range functionality
sloorush Sep 12, 2022
c46a7f4
fix: formatting in code range plugin
sloorush Sep 12, 2022
959a53b
added more descriptive comments and encorporate code review suggestions
sloorush Sep 14, 2022
e8ee9f9
revert: automatic formatting for selection range test case file
sloorush Sep 14, 2022
44c5819
fix: ignoring children if root fails to provide folding ranges
sloorush Sep 18, 2022
9181b04
remove: redundant match on crkToFrk
sloorush Sep 19, 2022
86f1068
revert: filtering same line foldings and multiple foldings on the sam…
sloorush Sep 19, 2022
c3f1c4a
Merge branch 'haskell:master' into folding-ranges
sloorush Sep 19, 2022
57cb482
revert: formatting change to selection range test file
sloorush Sep 19, 2022
ccd9fa5
fix: entire file folding because of root node
sloorush Sep 21, 2022
c023548
Merge branch 'master' into folding-ranges
Sep 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion hls-plugin-api/src/Ide/Plugin/Config.hs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ data PluginConfig =
, plcCompletionOn :: !Bool
, plcRenameOn :: !Bool
, plcSelectionRangeOn :: !Bool
, plcFoldingRangeOn :: !Bool
, plcConfig :: !A.Object
} deriving (Show,Eq)

Expand All @@ -125,11 +126,12 @@ instance Default PluginConfig where
, plcCompletionOn = True
, plcRenameOn = True
, plcSelectionRangeOn = True
, plcFoldingRangeOn = True
, plcConfig = mempty
}

instance A.ToJSON PluginConfig where
toJSON (PluginConfig g ch ca cl d h s c rn sr cfg) = r
toJSON (PluginConfig g ch ca cl d h s c rn sr fr cfg) = r
where
r = object [ "globalOn" .= g
, "callHierarchyOn" .= ch
Expand All @@ -141,6 +143,7 @@ instance A.ToJSON PluginConfig where
, "completionOn" .= c
, "renameOn" .= rn
, "selectionRangeOn" .= sr
, "foldingRangeOn" .= fr
, "config" .= cfg
]

Expand All @@ -156,6 +159,7 @@ instance A.FromJSON PluginConfig where
<*> o .:? "completionOn" .!= plcCompletionOn def
<*> o .:? "renameOn" .!= plcRenameOn def
<*> o .:? "selectionRangeOn" .!= plcSelectionRangeOn def
<*> o .:? "foldingRangeOn" .!= plcFoldingRangeOn def
<*> o .:? "config" .!= plcConfig def

-- ---------------------------------------------------------------------
14 changes: 12 additions & 2 deletions hls-plugin-api/src/Ide/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
Expand All @@ -17,7 +18,6 @@
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE MultiParamTypeClasses #-}

module Ide.Types
where
Expand All @@ -31,10 +31,10 @@ import System.Posix.Signals
#endif
import Control.Lens ((^.))
import Data.Aeson hiding (defaultOptions)
import qualified Data.DList as DList
import qualified Data.Default
import Data.Dependent.Map (DMap)
import qualified Data.Dependent.Map as DMap
import qualified Data.DList as DList
import Data.GADT.Compare
import Data.List.NonEmpty (NonEmpty (..), toList)
import qualified Data.Map as Map
Expand Down Expand Up @@ -370,6 +370,13 @@ instance PluginMethod Request TextDocumentSelectionRange where
uri = msgParams ^. J.textDocument . J.uri
pid = pluginId pluginDesc

instance PluginMethod Request TextDocumentFoldingRange where
pluginEnabled _ msgParams pluginDesc conf = pluginResponsible uri pluginDesc
&& pluginEnabledConfig plcFoldingRangeOn pid conf
where
uri = msgParams ^. J.textDocument . J.uri
pid = pluginId pluginDesc

instance PluginMethod Request CallHierarchyIncomingCalls where
-- This method has no URI parameter, thus no call to 'pluginResponsible'
pluginEnabled _ _ pluginDesc conf = pluginEnabledConfig plcCallHierarchyOn pid conf
Expand Down Expand Up @@ -470,6 +477,9 @@ instance PluginRequestMethod TextDocumentPrepareCallHierarchy where
instance PluginRequestMethod TextDocumentSelectionRange where
combineResponses _ _ _ _ (x :| _) = x

instance PluginRequestMethod TextDocumentFoldingRange where
combineResponses _ _ _ _ x = sconcat x

instance PluginRequestMethod CallHierarchyIncomingCalls where

instance PluginRequestMethod CallHierarchyOutgoingCalls where
Expand Down
157 changes: 107 additions & 50 deletions plugins/hls-code-range-plugin/src/Ide/Plugin/CodeRange.hs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Ide.Plugin.CodeRange (
descriptor
, Log
module Ide.Plugin.CodeRange
( descriptor,
Log,

-- * Internal
, findPosition
) where
findPosition,
)
where

import Control.Monad.Except (ExceptT (ExceptT),
runExceptT)
Expand All @@ -33,7 +35,7 @@ import Development.IDE.Core.PositionMapping (PositionMapping,
import Development.IDE.Types.Logger (Pretty (..))
import Ide.Plugin.CodeRange.Rules (CodeRange (..),
GetCodeRange (..),
codeRangeRule)
codeRangeRule, crkToFrk)
import qualified Ide.Plugin.CodeRange.Rules as Rules (Log)
import Ide.PluginUtils (pluginResponse,
positionInRange)
Expand All @@ -42,40 +44,67 @@ import Ide.Types (PluginDescriptor (pluginH
defaultPluginDescriptor,
mkPluginHandler)
import Language.LSP.Server (LspM)
import Language.LSP.Types (List (List),
import Language.LSP.Types (FoldingRange (..),
FoldingRangeParams (..),
List (List),
NormalizedFilePath,
Position (..),
Range (_start),
ResponseError,
SMethod (STextDocumentSelectionRange),
SMethod (STextDocumentFoldingRange, 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)
descriptor recorder plId =
(defaultPluginDescriptor plId)
{ pluginHandlers =
mkPluginHandler STextDocumentSelectionRange selectionRangeHandler
<> 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
pretty log = case log of
LogRules codeRangeLog -> pretty codeRangeLog

foldingRangeHandler :: IdeState -> PluginId -> FoldingRangeParams -> LspM c (Either ResponseError (List FoldingRange))
foldingRangeHandler ide _ FoldingRangeParams {..} = do
pluginResponse $ do
filePath <-
ExceptT . pure . maybeToEither "fail to convert uri to file path" $
toNormalizedFilePath' <$> uriToFilePath' uri
foldingRanges <-
ExceptT . liftIO . runIdeAction "FoldingRange" (shakeExtras ide) . runExceptT $
getFoldingRanges filePath
pure . List $ foldingRanges
where
uri :: Uri
TextDocumentIdentifier uri = _textDocument

getFoldingRanges :: NormalizedFilePath -> ExceptT String IdeAction [FoldingRange]
getFoldingRanges file = do
(codeRange, _) <- maybeToExceptT "fail to get code range" $ useE GetCodeRange file

let foldingRanges = findFoldingRanges codeRange

maybeToExceptT "Fail to generate folding range" (MaybeT . pure $ Just foldingRanges)

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
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
Expand All @@ -85,19 +114,20 @@ selectionRangeHandler ide _ SelectionRangeParams{..} = do

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
(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
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
-- '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
Expand All @@ -106,31 +136,58 @@ findPosition pos root = go Nothing root
-- Helper function for recursion. The range list is built top-down
go :: Maybe SelectionRange -> CodeRange -> Maybe SelectionRange
go acc node =
if positionInRange pos range
if positionInRange pos range
then maybe acc' (go acc') (binarySearchPos children)
-- If all children doesn't contain pos, acc' will be returned.
else -- 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
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
| 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

-- | Traverses through the code range and children to a folding ranges
findFoldingRanges :: CodeRange -> [FoldingRange]
findFoldingRanges r@(CodeRange _ children _) =
let frRoot :: [FoldingRange] = case createFoldingRange r of
Just x -> [x]
Nothing -> []

frChildren :: [FoldingRange] = concat $ V.toList $ fmap findFoldingRanges children
in frRoot ++ frChildren

-- | Parses code range to folding range
createFoldingRange :: CodeRange -> Maybe FoldingRange
createFoldingRange node1 = do
let range = _codeRange_range node1
let Range startPos endPos = range
let Position lineStart _ = startPos
let Position lineEnd _ = endPos
let codeRangeKind = _codeRange_kind node1

let frk = crkToFrk codeRangeKind

case frk of
Just _ -> Just (FoldingRange lineStart Nothing lineEnd Nothing frk)
Nothing -> Nothing

-- | Likes 'toCurrentPosition', but works on 'SelectionRange'
toCurrentSelectionRange :: PositionMapping -> SelectionRange -> Maybe SelectionRange
toCurrentSelectionRange positionMapping SelectionRange{..} = do
newRange <- toCurrentRange positionMapping _range
pure $ SelectionRange {
_range = newRange,
toCurrentSelectionRange positionMapping SelectionRange {..} = do
newRange <- toCurrentRange positionMapping _range
pure $
SelectionRange
{ _range = newRange,
_parent = _parent >>= toCurrentSelectionRange positionMapping
}
}
Loading