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

Cabal plugin outline view #4323

Merged
merged 25 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
2 changes: 1 addition & 1 deletion ghcide/src/Development/IDE/LSP/Outline.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import Language.LSP.Protocol.Types (DocumentSymbol (..),
DocumentSymbolParams (DocumentSymbolParams, _textDocument),
SymbolKind (..),
TextDocumentIdentifier (TextDocumentIdentifier),
type (|?) (InL, InR), uriToFilePath)
type (|?) (InL, InR), uriToFilePath, mkRange, SymbolInformation (_deprecated))
import Language.LSP.Protocol.Message

-- See Note [Guidelines For Using CPP In GHCIDE Import Statements]
Expand Down
1 change: 1 addition & 0 deletions haskell-language-server.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ library hls-cabal-plugin
Ide.Plugin.Cabal.Completion.Types
Ide.Plugin.Cabal.LicenseSuggest
Ide.Plugin.Cabal.Orphans
Ide.Plugin.Cabal.Outline
Ide.Plugin.Cabal.Parse


Expand Down
2 changes: 2 additions & 0 deletions plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import qualified Ide.Plugin.Cabal.Diagnostics as Diagnostics
import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest
import Ide.Plugin.Cabal.Orphans ()
import qualified Ide.Plugin.Cabal.Parse as Parse
import Ide.Plugin.Cabal.Outline
import Ide.Types
import qualified Language.LSP.Protocol.Lens as JL
import qualified Language.LSP.Protocol.Message as LSP
Expand Down Expand Up @@ -88,6 +89,7 @@ descriptor recorder plId =
mconcat
[ mkPluginHandler LSP.SMethod_TextDocumentCodeAction licenseSuggestCodeAction
, mkPluginHandler LSP.SMethod_TextDocumentCompletion $ completion recorder
, mkPluginHandler LSP.SMethod_TextDocumentDocumentSymbol moduleOutline
]
, pluginNotificationHandlers =
mconcat
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,6 @@ lspPositionToCabalPosition :: Position -> Syntax.Position
lspPositionToCabalPosition pos = Syntax.Position
(fromIntegral (pos ^. JL.line) + 1)
(fromIntegral (pos ^. JL.character) + 1)

cabalPositionToLSPPosition :: Syntax.Position -> Position
fendor marked this conversation as resolved.
Show resolved Hide resolved
cabalPositionToLSPPosition (Syntax.Position start end) = Position (toEnum start -1) (toEnum end -1)
131 changes: 131 additions & 0 deletions plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Outline.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ViewPatterns #-}

module Ide.Plugin.Cabal.Outline
( moduleOutline,
)
where

import Control.Monad.IO.Class
import Data.Maybe
import Data.Text qualified as T
import Data.Text.Encoding (decodeASCII, decodeLatin1)
import Development.IDE.Core.Rules
import Development.IDE.Core.Shake (IdeState (shakeExtras), runIdeAction, useWithStaleFast)
import Development.IDE.Types.Location (toNormalizedFilePath')
import Distribution.Fields.Field
( Field (Field, Section),
FieldLine (FieldLine),
Name (Name),
SectionArg (SecArgName, SecArgOther, SecArgStr),
)
import Distribution.Parsec.Position (Position)
import Ide.Plugin.Cabal.Completion.Types (ParseCabalFields (..), cabalPositionToLSPPosition)
import Ide.Plugin.Cabal.Orphans ()
import Ide.Types
import Language.LSP.Protocol.Message qualified as LSP
import Language.LSP.Protocol.Types qualified as LSP

moduleOutline :: PluginMethodHandler IdeState LSP.Method_TextDocumentDocumentSymbol
moduleOutline ideState _ LSP.DocumentSymbolParams {_textDocument = LSP.TextDocumentIdentifier uri} =
case LSP.uriToFilePath uri of
Just (toNormalizedFilePath' -> fp) -> do
mFields <- liftIO $ runIdeAction "cabal-plugin.fields" (shakeExtras ideState) (useWithStaleFast ParseCabalFields fp)
case fmap fst mFields of
Just fieldPositions -> pure $ LSP.InR (LSP.InL allSymbols)
where
allSymbols = mapMaybe documentSymbolForField fieldPositions
Nothing -> pure $ LSP.InL []
Nothing -> pure $ LSP.InL []

documentSymbolForField :: Field Position -> Maybe LSP.DocumentSymbol
documentSymbolForField (Field (Name pos fieldName) fieldLines) =
Just
(defDocumentSymbol range)
{ LSP._name = decodeASCII fieldName,
fendor marked this conversation as resolved.
Show resolved Hide resolved
LSP._kind = LSP.SymbolKind_Object,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
LSP._kind = LSP.SymbolKind_Object,
LSP._kind = LSP.SymbolKind_Field,

LSP._children = Just $ mapMaybe documentSymbolForFieldLine fieldLines
}
where
range = cabalPositionToLSPRange pos `addNameLengthToLSPRange` decodeASCII fieldName
documentSymbolForField (Section (Name pos fieldName) sectionArgs fields) =
Just
(defDocumentSymbol range)
{ LSP._name = decodeASCII fieldName,
LSP._kind = LSP.SymbolKind_Object,
LSP._children =
Just
( mapMaybe documentSymbolForField fields
++ mapMaybe documentSymbolForSectionArgs sectionArgs
)
}
where
range = cabalPositionToLSPRange pos `addNameLengthToLSPRange` decodeASCII fieldName

documentSymbolForSectionArgs :: SectionArg Position -> Maybe LSP.DocumentSymbol
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure we want to include these either, what do they correspond to? It looks like this is responsible for e.g. the weirdness with conditionals.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they can also include the names of stanzas so we definitely want to process them somehow but probably not into their own DocumentSymbol element

documentSymbolForSectionArgs (SecArgName pos identifier) =
Just
(defDocumentSymbol range)
{ LSP._name = decodeLatin1 identifier,
fendor marked this conversation as resolved.
Show resolved Hide resolved
LSP._kind = LSP.SymbolKind_Variable,
LSP._children = Nothing
}
where
range = cabalPositionToLSPRange pos `addNameLengthToLSPRange` decodeASCII identifier
documentSymbolForSectionArgs (SecArgStr pos quotedString) =
Just
(defDocumentSymbol range)
{ LSP._name = decodeLatin1 quotedString,
LSP._kind = LSP.SymbolKind_Constant,
LSP._children = Nothing
}
where
range = cabalPositionToLSPRange pos `addNameLengthToLSPRange` decodeASCII quotedString
documentSymbolForSectionArgs (SecArgOther pos string) =
Just
(defDocumentSymbol range)
{ LSP._name = decodeLatin1 string,
LSP._kind = LSP.SymbolKind_String,
LSP._children = Nothing
}
where
range = cabalPositionToLSPRange pos `addNameLengthToLSPRange` decodeASCII string

documentSymbolForFieldLine :: FieldLine Position -> Maybe LSP.DocumentSymbol
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what I'm suggesting is that we get rid of this.

documentSymbolForFieldLine (FieldLine pos line) =
Just
(defDocumentSymbol range)
{ LSP._name = decodeLatin1 line, -- since there is no ascii invariant (?)
LSP._kind = LSP.SymbolKind_Field,
LSP._children = Nothing -- can't delete even though the base case covers this (?)
}
where
range = cabalPositionToLSPRange pos `addNameLengthToLSPRange` decodeASCII line

cabalPositionToLSPRange :: Position -> LSP.Range
fendor marked this conversation as resolved.
Show resolved Hide resolved
cabalPositionToLSPRange pos = LSP.Range lspPos lspPos
where
lspPos = cabalPositionToLSPPosition pos

addNameLengthToLSPRange :: LSP.Range -> T.Text -> LSP.Range
addNameLengthToLSPRange (LSP.Range pos1 (LSP.Position line char)) name =
LSP.Range
pos1
(LSP.Position line (char + fromIntegral (T.length name)))

defDocumentSymbol :: LSP.Range -> LSP.DocumentSymbol
defDocumentSymbol range = LSP.DocumentSymbol {..}
where
_detail = Nothing
_deprecated = Nothing
_name = ""
_kind = LSP.SymbolKind_File
_range = range
_selectionRange = range
_children = Nothing
_tags = Nothing
Loading