forked from haskell/haskell-language-server
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathContext.hs
198 lines (188 loc) · 10.5 KB
/
Context.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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
{-# LANGUAGE DisambiguateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
module Context where
import Control.Monad.Trans.Maybe (runMaybeT)
import qualified Data.Text as T
import qualified Data.Text.Utf16.Rope as Rope
import Ide.Plugin.Cabal
import Ide.Plugin.Cabal.Completion.Completer.Paths
import Ide.Plugin.Cabal.Completion.Completions
import Ide.Plugin.Cabal.Completion.Types (Context,
FieldContext (KeyWord, None),
StanzaContext (Stanza, TopLevel))
import Test.Hls
import Utils as T
cabalPlugin :: PluginTestDescriptor Ide.Plugin.Cabal.Log
cabalPlugin = mkPluginTestDescriptor descriptor "cabal context"
contextTests :: TestTree
contextTests =
testGroup
"Context Tests "
[ pathCompletionInfoFromCompletionContextTests
, getContextTests
]
pathCompletionInfoFromCompletionContextTests :: TestTree
pathCompletionInfoFromCompletionContextTests =
testGroup
"Completion Info to Completion Context Tests"
[ testCase "Current Directory" $ do
let complInfo = pathCompletionInfoFromCabalPrefixInfo "" $ simpleCabalPrefixInfoFromFp "" testDataDir
queryDirectory complInfo @?= "./"
, testCase "Current Directory - partly written next" $ do
let complInfo = pathCompletionInfoFromCabalPrefixInfo "" $ simpleCabalPrefixInfoFromFp "di" testDataDir
queryDirectory complInfo @?= "./"
pathSegment complInfo @?= "di"
, testCase "Current Directory - alternative writing" $ do
let complInfo = pathCompletionInfoFromCabalPrefixInfo "" $ simpleCabalPrefixInfoFromFp "./" testDataDir
queryDirectory complInfo @?= "./"
, testCase "Subdirectory" $ do
let complInfo = pathCompletionInfoFromCabalPrefixInfo "" $ simpleCabalPrefixInfoFromFp "dir1/" testDataDir
queryDirectory complInfo @?= "dir1/"
pathSegment complInfo @?= ""
, testCase "Subdirectory - partly written next" $ do
let complInfo = pathCompletionInfoFromCabalPrefixInfo "" $ simpleCabalPrefixInfoFromFp "dir1/d" testDataDir
queryDirectory complInfo @?= "dir1/"
pathSegment complInfo @?= "d"
, testCase "Subdirectory - partly written next" $ do
let complInfo = pathCompletionInfoFromCabalPrefixInfo "" $ simpleCabalPrefixInfoFromFp "dir1/dir2/d" testDataDir
queryDirectory complInfo @?= "dir1/dir2/"
pathSegment complInfo @?= "d"
]
getContextTests :: TestTree
getContextTests =
testGroup
"Context Tests"
[ testCase "Empty File - Start" $ do
-- for a completely empty file, the context needs to
-- be top level without a specified keyword
ctx <- callGetContext (Position 0 0) "" [""]
ctx @?= (TopLevel, None)
, testCase "Cabal version keyword - no value, no space after :" $ do
-- on a file, where the keyword is already written
-- the context should still be toplevel but the keyword should be recognized
ctx <- callGetContext (Position 0 14) "" ["cabal-version:"]
ctx @?= (TopLevel, KeyWord "cabal-version:")
, testCase "Cabal version keyword - cursor in keyword" $ do
-- on a file, where the keyword is already written
-- but the cursor is in the middle of the keyword,
-- we are not in a keyword context
ctx <- callGetContext (Position 0 5) "cabal" ["cabal-version:"]
ctx @?= (TopLevel, None)
, testCase "Cabal version keyword - no value, many spaces" $ do
-- on a file, where the "cabal-version:" keyword is already written
-- the context should still be top level but the keyword should be recognized
ctx <- callGetContext (Position 0 45) "" ["cabal-version:" <> T.replicate 50 " "]
ctx @?= (TopLevel, KeyWord "cabal-version:")
, testCase "Cabal version keyword - keyword partly written" $ do
-- in the first line of the file, if the keyword
-- has not been written completely, the keyword context
-- should still be None
ctx <- callGetContext (Position 0 5) "cabal" ["cabal"]
ctx @?= (TopLevel, None)
, testCase "Cabal version keyword - value partly written" $ do
-- in the first line of the file, if the keyword
-- has not been written completely, the keyword context
-- should still be None
ctx <- callGetContext (Position 0 17) "1." ["cabal-version: 1."]
ctx @?= (TopLevel, KeyWord "cabal-version:")
, testCase "Inside Stanza - no keyword" $ do
-- on a file, where the library stanza has been defined
-- but no keyword is defined afterwards, the stanza context should be recognized
ctx <- callGetContext (Position 3 2) "" libraryStanzaData
ctx @?= (Stanza "library" Nothing, None)
, testCase "Inside Stanza - keyword, no value" $ do
-- on a file, where the library stanza and a keyword
-- has been defined, the keyword and stanza should be recognized
ctx <- callGetContext (Position 4 21) "" libraryStanzaData
ctx @?= (Stanza "library" Nothing, KeyWord "build-depends:")
, expectFailBecause "While not valid, it is not that important to make the code more complicated for this" $
testCase "Cabal version keyword - no value, next line" $ do
-- if the cabal version keyword has been written but without a value,
-- in the next line we still should be in top level context with no keyword
-- since the cabal version keyword and value pair need to be in the same line
ctx <- callGetContext (Position 1 2) "" ["cabal-version:", ""]
ctx @?= (TopLevel, None)
, testCase "Non-cabal-version keyword - no value, next line indentented position" $ do
-- if a keyword, other than the cabal version keyword has been written
-- with no value, in the next line we still should be in top level keyword context
-- of the keyword with no value, since its value may be written in the next line
ctx <- callGetContext (Position 2 4) "" topLevelData
ctx @?= (TopLevel, KeyWord "name:")
, testCase "Non-cabal-version keyword - no value, next line at start" $ do
-- if a keyword, other than the cabal version keyword has been written
-- with no value, in the next line we still should be in top level context
-- but not the keyword's, since it is not viable to write a value for a
-- keyword a the start of the next line
ctx <- callGetContext (Position 2 0) "" topLevelData
ctx @?= (TopLevel, None)
, testCase "Toplevel after stanza partially written" $ do
ctx <- callGetContext (Position 6 2) "ma" libraryStanzaData
ctx @?= (TopLevel, None)
, testCase "Non-cabal-version keyword - no value, multiple lines between" $ do
-- if a keyword, other than the cabal version keyword has been written
-- with no value, even with multiple lines in between we can still write the
-- value corresponding to the keyword
ctx <- callGetContext (Position 5 4) "" topLevelData
ctx @?= (TopLevel, KeyWord "name:")
, testCase "Keyword inside stanza - cursor indented more than keyword in next line" $ do
-- if a keyword, other than the cabal version keyword has been written
-- in a stanza context with no value, then the value may be written in the next line,
-- when the cursor is indented more than the keyword
ctx <- callGetContext (Position 5 8) "" libraryStanzaData
ctx @?= (Stanza "library" Nothing, KeyWord "build-depends:")
, testCase "Keyword inside stanza - cursor indented less than keyword in next line" $ do
-- if a keyword, other than the cabal version keyword has been written
-- in a stanza context with no value, then the value may not be written in the next line,
-- when the cursor is indented less than the keyword
ctx <- callGetContext (Position 5 2) "" libraryStanzaData
ctx @?= (Stanza "library" Nothing, None)
, testCase "Keyword inside stanza - cursor at start of next line" $ do
-- in a stanza context with no value the value may not be written in the next line,
-- when the cursor is not indented and we are in the top level context
ctx <- callGetContext (Position 5 0) "" libraryStanzaData
ctx @?= (TopLevel, None)
, testCase "Top level - cursor in later line with partially written value" $ do
ctx <- callGetContext (Position 5 13) "eee" topLevelData
ctx @?= (TopLevel, KeyWord "name:")
, testCase "Named Stanza" $ do
ctx <- callGetContext (Position 2 18) "" executableStanzaData
ctx @?= (Stanza "executable" (Just "exeName"), None)
]
where
callGetContext :: Position -> T.Text -> [T.Text] -> IO Context
callGetContext pos pref ls = do
runMaybeT (getContext mempty (simpleCabalPrefixInfoFromPos pos pref) (Rope.fromText $ T.unlines ls))
>>= \case
Nothing -> assertFailure "Context must be found"
Just ctx -> pure ctx
-- ------------------------------------------------------------------------
-- Test Data
-- ------------------------------------------------------------------------
libraryStanzaData :: [T.Text]
libraryStanzaData =
[ "cabal-version: 3.0"
, "name: simple-cabal"
, "library "
, " default-language: Haskell98"
, " build-depends: "
, " "
, "ma "
]
executableStanzaData :: [T.Text]
executableStanzaData =
[ "cabal-version: 3.0"
, "name: simple-cabal"
, "executable exeName"
, " default-language: Haskell2010"
, " hs-source-dirs: test/preprocessor"
]
topLevelData :: [T.Text]
topLevelData =
[ "cabal-version: 3.0"
, "name:"
, ""
, ""
, ""
, " eee"
]