diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/hls-selection-range-plugin.cabal b/hls-selection-range-plugin.cabal
new file mode 100644
--- /dev/null
+++ b/hls-selection-range-plugin.cabal
@@ -0,0 +1,60 @@
+cabal-version:      2.4
+name:               hls-selection-range-plugin
+version:            1.0.0.0
+synopsis:
+  HLS Plugin to support smart selection range
+
+description:
+  Please see the README on GitHub at <https://github.com/haskell/haskell-language-server#readme>
+
+license:            Apache-2.0
+license-file:       LICENSE
+author: kokobd
+maintainer: kokobd
+
+category:           Development
+build-type:         Simple
+extra-source-files:
+  LICENSE
+  test/testdata/*.hs
+  test/testdata/*.yaml
+  test/testdata/*.txt
+
+library
+  exposed-modules:
+    Ide.Plugin.SelectionRange
+  other-modules:
+    Ide.Plugin.SelectionRange.ASTPreProcess
+  ghc-options: -Wall
+  hs-source-dirs: src
+  default-language:   Haskell2010
+  build-depends:
+    , aeson
+    , base             >=4.12 && <5
+    , containers
+    , ghcide           ^>=1.6
+    , hls-plugin-api   ^>=1.3
+    , lsp
+    , transformers
+    , mtl
+    , text
+    , extra
+    , semigroupoids
+
+test-suite tests
+  type:             exitcode-stdio-1.0
+  default-language: Haskell2010
+  hs-source-dirs:   test
+  main-is:          Main.hs
+  ghc-options:      -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+    , base
+    , containers
+    , filepath
+    , hls-selection-range-plugin
+    , hls-test-utils             ^>=1.2
+    , lsp
+    , lsp-test
+    , text
+    , bytestring
+    , lens
diff --git a/src/Ide/Plugin/SelectionRange.hs b/src/Ide/Plugin/SelectionRange.hs
new file mode 100644
--- /dev/null
+++ b/src/Ide/Plugin/SelectionRange.hs
@@ -0,0 +1,145 @@
+{-# LANGUAGE NamedFieldPuns    #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards   #-}
+
+module Ide.Plugin.SelectionRange (descriptor) where
+
+import           Control.Monad.Except                    (ExceptT (ExceptT),
+                                                          runExceptT)
+import           Control.Monad.IO.Class                  (liftIO)
+import           Control.Monad.Reader                    (runReader)
+import           Control.Monad.Trans.Maybe               (MaybeT (MaybeT),
+                                                          maybeToExceptT)
+import           Data.Coerce                             (coerce)
+import           Data.Containers.ListUtils               (nubOrd)
+import           Data.Either.Extra                       (maybeToEither)
+import           Data.Foldable                           (find)
+import qualified Data.Map.Strict                         as Map
+import           Data.Maybe                              (fromMaybe, mapMaybe)
+import qualified Data.Text                               as T
+import           Development.IDE                         (GetHieAst (GetHieAst),
+                                                          HieAstResult (HAR, hieAst, refMap),
+                                                          IdeAction,
+                                                          IdeState (shakeExtras),
+                                                          Range (Range),
+                                                          fromNormalizedFilePath,
+                                                          ideLogger, logDebug,
+                                                          realSrcSpanToRange,
+                                                          runIdeAction,
+                                                          toNormalizedFilePath',
+                                                          uriToFilePath')
+import           Development.IDE.Core.Actions            (useE)
+import           Development.IDE.Core.PositionMapping    (PositionMapping,
+                                                          fromCurrentPosition,
+                                                          toCurrentRange)
+import           Development.IDE.GHC.Compat              (HieAST (Node), Span,
+                                                          getAsts)
+import           Development.IDE.GHC.Compat.Util
+import           Ide.Plugin.SelectionRange.ASTPreProcess (PreProcessEnv (PreProcessEnv),
+                                                          preProcessAST)
+import           Ide.PluginUtils                         (response)
+import           Ide.Types                               (PluginDescriptor (pluginHandlers),
+                                                          PluginId,
+                                                          defaultPluginDescriptor,
+                                                          mkPluginHandler)
+import           Language.LSP.Server                     (LspM)
+import           Language.LSP.Types                      (List (List),
+                                                          NormalizedFilePath,
+                                                          Position,
+                                                          ResponseError,
+                                                          SMethod (STextDocumentSelectionRange),
+                                                          SelectionRange (..),
+                                                          SelectionRangeParams (..),
+                                                          TextDocumentIdentifier (TextDocumentIdentifier),
+                                                          Uri)
+import           Prelude                                 hiding (span)
+
+descriptor :: PluginId -> PluginDescriptor IdeState
+descriptor plId = (defaultPluginDescriptor plId)
+    { pluginHandlers = mkPluginHandler STextDocumentSelectionRange selectionRangeHandler
+    }
+
+selectionRangeHandler :: IdeState -> PluginId -> SelectionRangeParams -> LspM c (Either ResponseError (List SelectionRange))
+selectionRangeHandler ide _ SelectionRangeParams{..} = do
+    liftIO $ logDebug logger $ "requesting selection range for file: " <> T.pack (show uri)
+    response $ 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
+
+    logger = ideLogger ide
+
+getSelectionRanges :: NormalizedFilePath -> [Position] -> ExceptT String IdeAction [SelectionRange]
+getSelectionRanges file positions = do
+    (HAR{hieAst, refMap}, positionMapping) <- maybeToExceptT "fail to get hie ast" $ useE GetHieAst file
+    -- 'positionMapping' should be applied to the input positions before using them
+    positions' <- maybeToExceptT "fail to apply position mapping to input positions" . MaybeT . pure $
+        traverse (fromCurrentPosition positionMapping) positions
+
+    ast <- maybeToExceptT "fail to get ast for current file" . MaybeT . pure $
+        -- in GHC 9, the 'FastString' in 'HieASTs' is replaced by a newtype wrapper around 'LexicalFastString'
+        -- so we use 'coerce' to make it work in both GHC 8 and 9
+        getAsts hieAst Map.!? (coerce . mkFastString . fromNormalizedFilePath) file
+
+    let ast' = runReader (preProcessAST ast) (PreProcessEnv refMap)
+    let selectionRanges = findSelectionRangesByPositions (astPathsLeafToRoot ast') positions'
+
+    -- '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
+
+-- | 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
+    }
+
+-- | Build all paths from ast leaf to root
+astPathsLeafToRoot :: HieAST a -> [SelectionRange]
+astPathsLeafToRoot = mapMaybe (spansToSelectionRange . nubOrd) . go [[]]
+  where
+    go :: [[Span]] -> HieAST a -> [[Span]]
+    go acc (Node _ span [])       = fmap (span:) acc
+    go acc (Node _ span children) = concatMap (go (fmap (span:) acc)) children
+
+spansToSelectionRange :: [Span] -> Maybe SelectionRange
+spansToSelectionRange [] = Nothing
+spansToSelectionRange (span:spans) = Just $
+    SelectionRange {_range = realSrcSpanToRange span, _parent = spansToSelectionRange spans}
+
+{-|
+For each position, find the selection range that contains it, without taking each selection range's
+parent into account. These selection ranges are un-divisible, representing the leaf nodes in original AST, so they
+won't overlap.
+-}
+findSelectionRangesByPositions :: [SelectionRange] -- ^ all possible selection ranges
+                               -> [Position] -- ^ requested positions
+                               -> [SelectionRange]
+findSelectionRangesByPositions selectionRanges = fmap findByPosition
+    {-
+        Performance Tips:
+        Doing a linear search from the first selection range for each position is not optimal.
+        If it becomes too slow for a large file and many positions, you may optimize the implementation.
+        Assume the number of selection range is n, then the following techniques may be applied:
+            1. For each position, we may treat HieAST as a position indexed tree to search it in O(log(n)).
+            2. For all positions, a searched position will narrow the search range for other positions.
+    -}
+  where
+    findByPosition :: Position -> SelectionRange
+    findByPosition p = fromMaybe SelectionRange{_range = Range p p, _parent = Nothing} $
+        find (isPositionInSelectionRange p) selectionRanges
+
+    isPositionInSelectionRange :: Position -> SelectionRange -> Bool
+    isPositionInSelectionRange p SelectionRange{_range} =
+        let Range sp ep = _range in sp <= p && p <= ep
diff --git a/src/Ide/Plugin/SelectionRange/ASTPreProcess.hs b/src/Ide/Plugin/SelectionRange/ASTPreProcess.hs
new file mode 100644
--- /dev/null
+++ b/src/Ide/Plugin/SelectionRange/ASTPreProcess.hs
@@ -0,0 +1,159 @@
+{-# LANGUAGE OverloadedStrings   #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Ide.Plugin.SelectionRange.ASTPreProcess
+    ( preProcessAST
+    , PreProcessEnv(..)
+    ) where
+
+import           Control.Monad.Reader            (Reader, asks)
+import           Data.Foldable                   (find, foldl')
+import           Data.Functor.Identity           (Identity (Identity, runIdentity))
+import           Data.List                       (groupBy)
+import           Data.List.NonEmpty              (NonEmpty)
+import qualified Data.List.NonEmpty              as NonEmpty
+import qualified Data.Map.Strict                 as Map
+import           Data.Maybe                      (mapMaybe)
+import           Data.Semigroup.Foldable         (foldlM1)
+import qualified Data.Set                        as Set
+import           Development.IDE.GHC.Compat      (ContextInfo (MatchBind, TyDecl, ValBind),
+                                                  HieAST (..), Identifier,
+                                                  IdentifierDetails (identInfo),
+                                                  NodeInfo (NodeInfo, nodeIdentifiers),
+                                                  RealSrcSpan, RefMap, Span,
+                                                  combineRealSrcSpans,
+                                                  flattenAst,
+                                                  isAnnotationInNodeInfo,
+                                                  mkAstNode, nodeInfoFromSource,
+                                                  realSrcSpanEnd,
+                                                  realSrcSpanStart)
+import           Development.IDE.GHC.Compat.Util (FastString)
+import           Prelude                         hiding (span)
+
+{-|
+Extra arguments for 'preaProcessAST', meant to be used in a 'Reader' context. We use 'Reader' to combine
+-}
+newtype PreProcessEnv a = PreProcessEnv
+    { preProcessEnvRefMap :: RefMap a
+    }
+
+{-|
+Before converting the HieAST to selection range, we need to run some passes on it. Each pass potentially modifies
+the AST to handle some special cases.
+
+'preProcessAST' combines the passes. Refer to 'mergeImports' or 'mergeSignatureWithDefinition' as
+a concrete example example.
+
+Adding another manipulation to the AST is simple, just implement a function of type
+`HieAST a -> Reader (PreProcessEnv a) (HieAST a)`, and append it to 'preProcessAST' with `>>=`.
+
+If it goes more complex, it may be more appropriate to split different manipulations to different modules.
+-}
+preProcessAST :: HieAST a -> Reader (PreProcessEnv a) (HieAST a)
+preProcessAST node = mergeImports node >>= mergeSignatureWithDefinition
+
+{-|
+Combines adjacent import declarations under a new parent node, so that the user will have an extra step selecting
+the whole import area while expanding/shrinking the selection range.
+-}
+mergeImports :: forall a. HieAST a -> Reader (PreProcessEnv a) (HieAST a)
+mergeImports node = pure $ node { nodeChildren = children }
+  where
+    children :: [HieAST a]
+    children = mapMaybe merge
+        . groupBy (\x y -> nodeIsImport x && nodeIsImport y)
+        . nodeChildren $ node
+
+    merge :: [HieAST a] -> Maybe (HieAST a)
+    merge []     = Nothing
+    merge [x]    = Just x
+    merge (x:xs) = Just $ createVirtualNode (x NonEmpty.:| xs)
+
+nodeIsImport :: HieAST a -> Bool
+nodeIsImport = isAnnotationInAstNode ("ImportDecl", "ImportDecl")
+
+createVirtualNode :: NonEmpty (HieAST a) -> HieAST a
+createVirtualNode children = mkAstNode (NodeInfo mempty mempty mempty) span' (NonEmpty.toList children)
+  where
+    span' :: RealSrcSpan
+    span' = runIdentity . foldlM1 (\x y -> Identity (combineRealSrcSpans x y)) . fmap nodeSpan $ children
+
+{-|
+Combine type signature with variable definition under a new parent node, if the signature is placed right before the
+definition. This allows the user to have a step selecting both type signature and its accompanying definition.
+-}
+mergeSignatureWithDefinition :: HieAST a -> Reader (PreProcessEnv a) (HieAST a)
+mergeSignatureWithDefinition node = do
+    refMap <- asks preProcessEnvRefMap
+    -- Do this recursively for children, so that non top level functions can be handled.
+    children' <- traverse mergeSignatureWithDefinition (nodeChildren node)
+    pure $ node { nodeChildren = reverse $ foldl' (go refMap) [] children' }
+  where
+    -- For every two adjacent nodes, we try to combine them into one.
+    go :: RefMap a -> [HieAST a] -> HieAST a -> [HieAST a]
+    go _ [] node' = [node']
+    go refMap (prev:others) node' =
+        case mergeAdjacentSigDef refMap (prev, node') of
+            Nothing   -> node':prev:others
+            Just comb -> comb:others
+
+-- | Merge adjacent type signature and variable/function definition, if the type signature belongs to that variable or
+-- function.
+--
+-- The implementation potentially has some corner cases not handled properly.
+mergeAdjacentSigDef :: RefMap a -> (HieAST a, HieAST a) -> Maybe (HieAST a)
+mergeAdjacentSigDef refMap (n1, n2) = do
+    -- Let's check the node's annotation. There should be a function binding following its type signature.
+    checkAnnotation
+    -- Find the identifier of the type signature.
+    typeSigId <- identifierForTypeSig n1
+    -- Does that identifier appear in the second AST node as a definition? If so, we combines the two nodes.
+    refs <- Map.lookup typeSigId refMap
+    if any (isIdentADef (nodeSpan n2)) refs
+    then pure . createVirtualNode $ n1 NonEmpty.:| [n2]
+    else Nothing
+  where
+    checkAnnotation :: Maybe ()
+    checkAnnotation =
+      if ("TypeSig", "Sig") `isAnnotationInAstNode` n1 &&
+         (("FunBind", "HsBindLR") `isAnnotationInAstNode` n2 || ("VarBind", "HsBindLR") `isAnnotationInAstNode` n2)
+      then Just ()
+      else Nothing
+
+{-|
+Given the AST node of a type signature, tries to find the identifier of it.
+-}
+identifierForTypeSig :: forall a. HieAST a -> Maybe Identifier
+identifierForTypeSig node =
+    {-
+        It seems that the identifier lives in one of the children, so we search for the first 'TyDecl' node in
+        its children recursively.
+    -}
+    case mapMaybe extractIdentifier nodes of
+      []        -> Nothing
+      (ident:_) -> Just ident
+  where
+    nodes = flattenAst node
+
+    extractIdentifier :: HieAST a -> Maybe Identifier
+    extractIdentifier node' = nodeInfoFromSource node' >>=
+        (fmap fst . find (\(_, detail) -> TyDecl `Set.member` identInfo detail)
+        . Map.toList . nodeIdentifiers)
+
+-- | Determines if the given occurence of an identifier is a function/variable definition in the outer span
+isIdentADef :: Span -> (Span, IdentifierDetails a) -> Bool
+isIdentADef outerSpan (span, detail) =
+    realSrcSpanStart span >= realSrcSpanStart outerSpan && realSrcSpanEnd span <= realSrcSpanEnd outerSpan
+    && isDef
+  where
+    isDef :: Bool
+    isDef = any isContextInfoDef . Set.toList . identInfo $ detail
+
+    -- Does the 'ContextInfo' represents a variable/function definition?
+    isContextInfoDef :: ContextInfo -> Bool
+    isContextInfoDef ValBind{} = True
+    isContextInfoDef MatchBind = True
+    isContextInfoDef _         = False
+
+isAnnotationInAstNode :: (FastString, FastString) -> HieAST a -> Bool
+isAnnotationInAstNode p = maybe False (isAnnotationInNodeInfo p) . nodeInfoFromSource
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,52 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main (main) where
+
+import           Control.Lens               hiding (List, (<.>))
+import           Data.ByteString.Lazy       (ByteString)
+import qualified Data.ByteString.Lazy.Char8 as LBSChar8
+import           Data.String                (fromString)
+import           Ide.Plugin.SelectionRange  (descriptor)
+import           Language.LSP.Types.Lens
+import           System.FilePath            ((<.>), (</>))
+import           Test.Hls
+
+plugin :: PluginDescriptor IdeState
+plugin = descriptor "selectionRange"
+
+main :: IO ()
+main = defaultTestRunner $
+    testGroup "Selection Range"
+        [ goldenTest "Import" [(4, 36), (1, 8)]
+        , goldenTest "Function" [(5, 19), (5, 12), (4, 4), (3, 5)]
+        ]
+
+-- | build a golden test for
+goldenTest :: TestName -> [(UInt, UInt)] -> TestTree
+goldenTest testName positions = goldenGitDiff testName (testDataDir </> testName <.> "golden" <.> "txt") $ do
+    res <- runSessionWithServer plugin testDataDir $ do
+        doc <- openDoc (testName <.> "hs") "haskell"
+        resp <- request STextDocumentSelectionRange $ SelectionRangeParams Nothing Nothing doc
+            (List $ fmap (uncurry Position . (\(x, y) -> (x-1, y-1))) positions)
+        let res = resp ^. result
+        pure $ fmap showSelectionRangesForTest res
+    case res of
+        Left err     -> assertFailure (show err)
+        Right golden -> pure golden
+
+testDataDir :: FilePath
+testDataDir = "test" </> "testdata"
+
+showSelectionRangesForTest :: List SelectionRange -> ByteString
+showSelectionRangesForTest (List selectionRanges) = LBSChar8.intercalate "\n" $ fmap showSelectionRangeForTest selectionRanges
+
+showSelectionRangeForTest :: SelectionRange -> ByteString
+showSelectionRangeForTest selectionRange = go True (Just selectionRange)
+  where
+    go :: Bool -> Maybe SelectionRange -> ByteString
+    go _ Nothing = ""
+    go isFirst (Just (SelectionRange (Range sp ep) parent)) =
+        (if isFirst then "" else " => ") <> showPosition sp <> " " <> showPosition ep <> go False parent
+    showPosition :: Position -> ByteString
+    showPosition (Position line col) = "(" <> showLBS (line + 1) <> "," <> showLBS (col + 1) <> ")"
+    showLBS = fromString . show
diff --git a/test/testdata/Function.golden.txt b/test/testdata/Function.golden.txt
new file mode 100644
--- /dev/null
+++ b/test/testdata/Function.golden.txt
@@ -0,0 +1,4 @@
+(5,16) (5,20) => (5,16) (5,40) => (5,14) (11,20) => (5,1) (11,20) => (4,1) (11,20) => (3,1) (11,20) => (3,1) (14,15)
+(5,12) (5,13) => (5,1) (11,20) => (4,1) (11,20) => (3,1) (11,20) => (3,1) (14,15)
+(4,1) (4,9) => (4,1) (4,29) => (4,1) (11,20) => (3,1) (11,20) => (3,1) (14,15)
+(3,1) (3,9) => (3,1) (3,61) => (3,1) (11,20) => (3,1) (14,15)
diff --git a/test/testdata/Function.hs b/test/testdata/Function.hs
new file mode 100644
--- /dev/null
+++ b/test/testdata/Function.hs
@@ -0,0 +1,14 @@
+module FuncMultiMatch where
+
+someFunc :: Integral a => a -> String -> Maybe (Int, String)
+someFunc _ "magic" = Nothing
+someFunc x y = Just (fromIntegral x, y)
+  where
+    go :: Int -> Int
+    go 0 = -1
+    go x = x + 1
+
+    hi = "greeting"
+
+otherFunc :: String -> String
+otherFunc = id
diff --git a/test/testdata/Import.golden.txt b/test/testdata/Import.golden.txt
new file mode 100644
--- /dev/null
+++ b/test/testdata/Import.golden.txt
@@ -0,0 +1,2 @@
+(4,33) (4,38) => (4,32) (4,47) => (4,1) (4,47) => (3,1) (4,47)
+(1,8) (1,8)
diff --git a/test/testdata/Import.hs b/test/testdata/Import.hs
new file mode 100644
--- /dev/null
+++ b/test/testdata/Import.hs
@@ -0,0 +1,4 @@
+module MultiPositions where
+
+import Data.List (find)
+import qualified Data.Foldable (foldl, foldl')
diff --git a/test/testdata/hie.yaml b/test/testdata/hie.yaml
new file mode 100644
--- /dev/null
+++ b/test/testdata/hie.yaml
@@ -0,0 +1,5 @@
+cradle:
+  direct:
+    arguments:
+      - "Import"
+      - "Function"
