Skip to content
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

Feat: Folding Ranges #3058

Merged
merged 34 commits into from
Sep 21, 2022
Merged
Show file tree
Hide file tree
Changes from 27 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
kokobd Sep 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 24 additions & 14 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This table gives a summary of the features that HLS supports.
Many of these are standard LSP features, but a lot of special features are provided as [code actions](#code-actions) and [code lenses](#code-lenses).

| Feature | [LSP method](./what-is-hls.md#lsp-terminology) |
|-----------------------------------------------------|---------------------------------------------------------------------------------------------------|
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| [Diagnostics](#diagnostics) | `textDocument/publishDiagnostics` |
| [Hovers](#hovers) | `textDocument/hover` |
| [Jump to definition](#jump-to-definition) | `textDocument/definition` |
Expand Down Expand Up @@ -100,7 +100,7 @@ Completions for language pragmas.
Format your code with various Haskell code formatters.

| Formatter | Provided by |
|-----------------|------------------------------|
| --------------- | ---------------------------- |
| Brittany | `hls-brittany-plugin` |
| Floskell | `hls-floskell-plugin` |
| Fourmolu | `hls-fourmolu-plugin` |
Expand Down Expand Up @@ -261,6 +261,7 @@ Change/Update a type signature to match implementation.
Status: Until GHC 9.4, the implementation is ad-hoc and relies on GHC error messages to create a new signature. Not all GHC error messages are supported.

Known Limitations:

- Not all GHC error messages are supported
- Top-level and Function-local bindings with the same names can cause issues, such as incorrect signature changes or no code actions available.

Expand Down Expand Up @@ -337,6 +338,16 @@ support.

![Selection range demo](https://user-images.githubusercontent.com/16440269/177240833-7dc8fe39-b446-477e-b5b1-7fc303608d4f.gif)

## Folding range

Provided by: `hls-code-range-plugin`

Provides haskell specific
[Folding](https://code.visualstudio.com/docs/editor/codebasics#_folding)
support.

![Folding range demo](https://user-images.githubusercontent.com/54478821/184468510-7c0d5182-c684-48ef-9b39-3866dc2309df.gif)

## Rename

Provided by: `hls-rename-plugin`
Expand All @@ -354,15 +365,14 @@ Known limitations:
The following features are supported by the LSP specification but not implemented in HLS.
Contributions welcome!

| Feature | Status | [LSP method](./what-is-hls.md#lsp-terminology) |
|------------------------|------------------------------------------------------------------------------------------|-----------------------------------------------------|
| Signature help | Unimplemented | `textDocument/signatureHelp` |
| Jump to declaration | Unclear if useful | `textDocument/declaration` |
| Jump to implementation | Unclear if useful | `textDocument/implementation` |
| Folding | Unimplemented | `textDocument/foldingRange` |
| Semantic tokens | Unimplemented | `textDocument/semanticTokens` |
| Linked editing | Unimplemented | `textDocument/linkedEditingRange` |
| Document links | Unimplemented | `textDocument/documentLink` |
| Document color | Unclear if useful | `textDocument/documentColor` |
| Color presentation | Unclear if useful | `textDocument/colorPresentation` |
| Monikers | Unclear if useful | `textDocument/moniker` |
| Feature | Status | [LSP method](./what-is-hls.md#lsp-terminology) |
| ---------------------- | ----------------- | ---------------------------------------------- |
| Signature help | Unimplemented | `textDocument/signatureHelp` |
| Jump to declaration | Unclear if useful | `textDocument/declaration` |
| Jump to implementation | Unclear if useful | `textDocument/implementation` |
| Semantic tokens | Unimplemented | `textDocument/semanticTokens` |
| Linked editing | Unimplemented | `textDocument/linkedEditingRange` |
| Document links | Unimplemented | `textDocument/documentLink` |
| Document color | Unclear if useful | `textDocument/documentColor` |
| Color presentation | Unclear if useful | `textDocument/colorPresentation` |
| Monikers | Unclear if useful | `textDocument/moniker` |
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

-- ---------------------------------------------------------------------
10 changes: 10 additions & 0 deletions hls-plugin-api/src/Ide/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,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 @@ -529,6 +536,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
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ cabal-version: 2.4
name: hls-code-range-plugin
version: 1.0.0.0
synopsis:
HLS Plugin to support smart selection range
HLS Plugin to support smart selection range and Folding range

description:
Please see the README on GitHub at <https://github.com/haskell/haskell-language-server#readme>
Expand Down
91 changes: 84 additions & 7 deletions plugins/hls-code-range-plugin/src/Ide/Plugin/CodeRange.hs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Ide.Plugin.CodeRange (
descriptor
sloorush marked this conversation as resolved.
Show resolved Hide resolved
, Log

-- * Internal
, findPosition
, findFoldingRanges
, createFoldingRange
) where

import Control.Monad.Except (ExceptT (ExceptT),
Expand All @@ -33,7 +36,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,12 +45,14 @@ 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),
Expand All @@ -57,8 +62,7 @@ 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
<> mkPluginHandler STextDocumentFoldingRange foldingRangeHandler
, pluginRules = codeRangeRule (cmapWithPrio LogRules recorder)
}

Expand All @@ -68,6 +72,24 @@ instance Pretty Log where
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 $ removeDupStartLineFoldings 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

pure $ findFoldingRanges codeRange

selectionRangeHandler :: IdeState -> PluginId -> SelectionRangeParams -> LspM c (Either ResponseError (List SelectionRange))
selectionRangeHandler ide _ SelectionRangeParams{..} = do
pluginResponse $ do
Expand Down Expand Up @@ -126,6 +148,61 @@ findPosition pos root = go Nothing root
startOfRight <- _start . _codeRange_range <$> V.headM right
if pos < startOfRight then binarySearchPos left else binarySearchPos right

-- | Traverses through the code range and it children to a folding ranges.
--
-- It starts with the root node, converts that into a folding range then moves towards the children.
-- It converts each child of each root node and parses it to folding range and moves to its children.
findFoldingRanges :: CodeRange -> [FoldingRange]
findFoldingRanges r@(CodeRange _ children _) =
let frChildren :: [FoldingRange] = concat $ V.toList $ fmap findFoldingRanges children
in case createFoldingRange r of
Just x -> x:frChildren
Nothing -> []
sloorush marked this conversation as resolved.
Show resolved Hide resolved

-- | Parses code range to folding range
createFoldingRange :: CodeRange -> Maybe FoldingRange
createFoldingRange (CodeRange (Range (Position lineStart charStart) (Position lineEnd charEnd)) _ ck) = do
-- Type conversion of codeRangeKind to FoldingRangeKind
let frk = crkToFrk ck

-- Filtering code ranges that start and end on the same line as need/can not be folded.
--
-- Eg. A single line function will also generate a Folding Range but it cannot be folded
-- because it is already single line, so omiting it.
if lineStart == lineEnd
sloorush marked this conversation as resolved.
Show resolved Hide resolved
then Nothing
else case frk of
Just _ -> Just (FoldingRange lineStart (Just charStart) lineEnd (Just charEnd) frk)
Nothing -> Nothing

-- | Removes all small foldings that start from the same line.
--
-- As we are converting nodes of the ast into folding ranges, there are multiple nodes starting from a single line.
-- A single line of code doesn't mean a single node in AST, so this function removes all the nodes that have a duplicate
-- start line, ie. they start from the same line.
--
-- This function preserves the largest folding range from the ranges that start from the same line.
sloorush marked this conversation as resolved.
Show resolved Hide resolved
--
-- Eg. A multi-line function that also has a multi-line if statement starting from the same line should have the folding
-- according to the function.
--
-- This is done by breaking the [FoldingRange] into parts -->
-- frx: Head
-- xs: rest of the array
-- fry(not shown as it is not used): head of xs
-- xs2: rest of the array other than the first two elements
-- slx and sly: start line of frx and fry
--
-- We compare the start line of the first two elements in the array and if the start line is the same we remove the
-- second one as it is the smaller one amoung the two.
-- otherwise frx is returned and the function runs recursively on xs.
removeDupStartLineFoldings :: [FoldingRange] -> [FoldingRange]
sloorush marked this conversation as resolved.
Show resolved Hide resolved
removeDupStartLineFoldings [] = []
removeDupStartLineFoldings [x] = [x]
removeDupStartLineFoldings (frx@(FoldingRange slx _ _ _ _):xs@((FoldingRange sly _ _ _ _):xs2))
| slx == sly = removeDupStartLineFoldings (frx:xs2)
| otherwise = frx : removeDupStartLineFoldings xs

-- | Likes 'toCurrentPosition', but works on 'SelectionRange'
toCurrentSelectionRange :: PositionMapping -> SelectionRange -> Maybe SelectionRange
toCurrentSelectionRange positionMapping SelectionRange{..} = do
Expand Down
15 changes: 12 additions & 3 deletions plugins/hls-code-range-plugin/src/Ide/Plugin/CodeRange/Rules.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module Ide.Plugin.CodeRange.Rules
-- * Internal
, removeInterleaving
, simplify
, crkToFrk
) where

import Control.DeepSeq (NFData)
Expand All @@ -45,14 +46,14 @@ import Development.IDE
import Development.IDE.Core.Rules (toIdeResult)
import qualified Development.IDE.Core.Shake as Shake
import Development.IDE.GHC.Compat (HieAST (..),
HieASTs (getAsts),
RefMap)
HieASTs (getAsts), RefMap)
import Development.IDE.GHC.Compat.Util
import GHC.Generics (Generic)
import Ide.Plugin.CodeRange.ASTPreProcess (CustomNodeType (..),
PreProcessEnv (..),
isCustomNode,
preProcessAST)
import Language.LSP.Types (FoldingRangeKind (FoldingRangeComment, FoldingRangeImports, FoldingRangeRegion))
import Language.LSP.Types.Lens (HasEnd (end),
HasStart (start))
import Prelude hiding (log)
Expand Down Expand Up @@ -90,7 +91,7 @@ data CodeRangeKind =
| CodeKindImports
-- | a comment
| CodeKindComment
deriving (Show, Generic, NFData)
deriving (Show, Eq, Generic, NFData)

Lens.makeLenses ''CodeRange

Expand Down Expand Up @@ -190,3 +191,11 @@ handleError recorder action' = do
logWith recorder Error msg
pure $ toIdeResult (Left [])
Right value -> pure $ toIdeResult (Right value)

-- | Maps type CodeRangeKind to FoldingRangeKind
crkToFrk :: CodeRangeKind -> Maybe FoldingRangeKind
crkToFrk crk = case crk of
CodeKindComment -> Just FoldingRangeComment
CodeKindImports -> Just FoldingRangeImports
CodeKindRegion -> Just FoldingRangeRegion
_ -> Nothing