@@ -4,14 +4,15 @@ import Prelude
44
55import Spago.Search.Index
66import Spago.Search.TypeDecoder
7−import Spago.Search.TypeShape
87import Spago.Search.TypeQuery
8+import Spago.Search.TypeShape
99
1010import CSS (textWhitespace, whitespacePreWrap)
11+import Data.Array ((!!))
1112import Data.Array as Array
1213import Data.Either (Either(..))
1314import Data.List as List
14−import Data.Maybe (Maybe(..), isNothing, isJust)
15+import Data.Maybe (Maybe(..), isJust, isNothing)
1516import Data.Newtype (unwrap, wrap)
1617import Data.Search.Trie as Trie
1718import Data.String (length) as String
@@ -27,14 +28,16 @@ import Halogen.HTML.Events as HE
2728import Halogen.HTML.Properties as HP
2829import Spago.Search.App.SearchField (Message(..))
2930import Spago.Search.DocsJson (loadDeclarations)
30−import Spago.Search.Extra (whenJust)
31+import Spago.Search.Extra (whenJust, (>#>))
3132import Web.DOM.Element (Element)
3233import Web.DOM.Element as Element
3334import Web.HTML as HTML
3435import Web.HTML.Location as Location
3536import Web.HTML.Window as Window
3637
37−-- | Is it a search by type or by declaration?
38+data Mode = Off | Loading | Active
39+
40+-- | Is it a search by type or by name?
3841data ResultsType = TypeResults TypeQuery | DeclResults
3942
4043type State = { mbIndex :: Maybe SearchIndex
@@ -43,6 +46,7 @@ type State = { mbIndex :: Maybe SearchIndex
4346 , input :: String
4447 , contents :: Element
4548 , resultsCount :: Int
49+ , mode :: Mode
4650 }
4751
4852data Query a
@@ -50,9 +54,12 @@ data Query a
5054
5155data Action
5256 = SearchResultClicked String
53− | ShowMore
57+ | MoreResultsRequested
5458
55−mkComponent :: forall o i. Element -> H.Component HH.HTML Query i o Aff
59+mkComponent
60+ :: forall o i
61+ . Element
62+ -> H.Component HH.HTML Query i o Aff
5663mkComponent contents =
5764 H.mkComponent
5865 { initialState: const { mbIndex: Nothing
@@ -61,46 +68,66 @@ mkComponent contents =
6168 , input: ""
6269 , contents
6370 , resultsCount: 25
71+ , mode: Off
6472 }
6573 , render
6674 , eval: H.mkEval $ H.defaultEval { handleQuery = handleQuery
6775 , handleAction = handleAction }
6876 }
6977
70−handleQuery :: forall o a. Query a -> H.HalogenM State Action () o Aff (Maybe a)
78+handleQuery
79+ :: forall o a
80+ . Query a
81+ -> H.HalogenM State Action () o Aff (Maybe a)
7182handleQuery (SearchFieldMessage Focused next) = do
72− state <- H.get
73− when (isNothing state.mbIndex) do
74− eiDeclarations <- H.liftAff $ loadDeclarations "../spago-search-index.js"
83+ pure Nothing
84+handleQuery (SearchFieldMessage LostFocus next) = do
85+ pure Nothing
86+handleQuery (SearchFieldMessage InputCleared next) = do
87+ H.modify_ (_ { results = [], input = "", mode = Off })
88+ showPageContents
89+ pure Nothing
90+handleQuery (SearchFieldMessage (InputUpdated input) next) = do
91+ H.modify_ (_ { input = input })
92+
93+ H.get >>= \state -> when (isNothing state.mbIndex) do
94+ H.modify_ (_ { mode = Loading })
95+ eiDeclarations <-
96+ H.liftAff $ loadDeclarations "../spago-search-index.js"
7597 case eiDeclarations of
7698 Left err -> do
7799 H.liftEffect do
78100 error $ "spago-search: couldn't load search index: " <> err
79101 Right declarations -> do
80102 H.modify_ (_ { mbIndex = Just $ mkSearchIndex declarations })
81− pure Nothing
82−handleQuery (SearchFieldMessage LostFocus next) = do
83− pure Nothing
84−handleQuery (SearchFieldMessage (InputUpdated input) next) = do
85− H.modify_ (_ { input = input })
103+
104+ H.modify_ (_ { mode = Active })
105+
86106 if String.length input < 2
87107 then do
108+
88109 showPageContents
89110 H.modify_ (_ { results = [] })
111+
90112 else do
113+
91114 hidePageContents
92115 state <- H.get
116+
93117 whenJust (unwrap <$> state.mbIndex) \index -> do
94118 let path = List.fromFoldable $
95119 String.toCharArray $
96120 String.toLower input
121+
97122 eiTypeQuery = parseTypeQuery input
123+
98124 resultsType =
99125 case eiTypeQuery of
100126 Left _ -> DeclResults
101127 Right query
102− | isValuableQuery query -> TypeResults query
128+ | isValuableTypeQuery query -> TypeResults query
103129 | otherwise -> DeclResults
130+
104131 results =
105132 case resultsType of
106133 DeclResults ->
@@ -111,18 +138,24 @@ handleQuery (SearchFieldMessage (InputUpdated input) next) = do
111138 index.decls
112139 TypeResults query ->
113140 List.toUnfoldable $
114− Trie.queryValues shape index.types >>= identity
141+ join $
142+ Trie.queryValues shape index.types
115143 where
116144 shape = shapeOfTypeQuery query
145+
117146 H.modify_ (_ { results = results
118147 , resultsType = resultsType
119148 , resultsCount = 25 })
120149 pure Nothing
121150
122−handleAction :: forall o. Action -> H.HalogenM State Action () o Aff Unit
151+handleAction
152+ :: forall o
153+ . Action
154+ -> H.HalogenM State Action () o Aff Unit
123155handleAction = case _ of
124− ShowMore -> do
156+ MoreResultsRequested -> do
125157 H.modify_ (\state -> state { resultsCount = state.resultsCount + 25 })
158+
126159 SearchResultClicked moduleName -> do
127160 -- Decide if we are going to load a new page or to jump to a hash on the
128161 -- current page. In the latter case, hide search results and show the main
@@ -138,23 +171,32 @@ handleAction = case _ of
138171 showPageContents
139172 H.modify_ (_ { input = "" })
140173
141−showPageContents :: forall o. H.HalogenM State Action () o Aff Unit
174+showPageContents
175+ :: forall o
176+ . H.HalogenM State Action () o Aff Unit
142177showPageContents = do
143178 state <- H.get
144179 H.liftEffect do
145180 Element.removeAttribute "style" state.contents
146181
147−hidePageContents :: forall o. H.HalogenM State Action () o Aff Unit
182+hidePageContents
183+ :: forall o
184+ . H.HalogenM State Action () o Aff Unit
148185hidePageContents = do
149186 state <- H.get
150187 H.liftEffect do
151188 Element.setAttribute "style" "display: none" state.contents
152189
153−render :: forall m. State -> H.ComponentHTML Action () m
154−render { mbIndex: Nothing } =
155− HH.div_ []
156−render { input: "" } =
157− HH.div_ []
190+render
191+ :: forall m
192+ . State
193+ -> H.ComponentHTML Action () m
194+render { mbIndex: Nothing, mode: Loading } =
195+ HH.div [ HP.classes [ wrap "container", wrap "clearfix" ] ] $
196+ pure $
197+ HH.div [ HP.classes [ wrap "col", wrap "col--main" ] ] $
198+ [ HH.h1_ [ HH.text "Loading..." ] ]
199+
158200render state =
159201 HH.div [ HP.classes [ wrap "container", wrap "clearfix" ] ] $
160202 pure $
@@ -188,20 +230,25 @@ render state =
188230 , HH.div [ HP.class_ (wrap "load_more"), HP.id_ "load-more" ]
189231 [ if Array.length selectedResults < Array.length state.results
190232 then HH.a [ HP.id_ "load-more-link"
191− , HE.onClick $ const $ Just ShowMore ]
233+ , HE.onClick $ const $ Just MoreResultsRequested ]
192234 [ HH.text "Show more results" ]
193235 else HH.p_
194236 [ HH.text "No further results." ]
195237 ]
196238 ]
197239
198−renderSummary :: forall a b. String -> HH.HTML b a
240+renderSummary
241+ :: forall a b
242+ . String
243+ -> HH.HTML b a
199244renderSummary text =
200245 HH.div [ HP.id_ "spago-search-summary" ]
201− [ HH.text text
202− ]
246+ [ HH.text text ]
203247
204−renderResult :: forall a. SearchResult -> Array (HH.HTML a Action)
248+renderResult
249+ :: forall a
250+ . SearchResult
251+ -> Array (HH.HTML a Action)
205252renderResult = unwrap >>> \result ->
206253 [ HH.div [ HP.class_ (wrap "result") ]
207254 [ HH.h3 [ HP.class_ (wrap "result__title") ]
@@ -218,22 +265,21 @@ renderResult = unwrap >>> \result ->
218265 , HH.div [ HP.class_ (wrap "result__body") ] $
219266 renderResultType result <>
220267
221− case result.comments of
222− Just comments -> [ HH.pre [ HS.style do
223− textWhitespace whitespacePreWrap ]
224− [ HH.text comments ]
225− ]
226− Nothing -> [ ]
268+ result.comments >#>
269+ \comments -> [ HH.pre [ HS.style do
270+ textWhitespace whitespacePreWrap ]
271+ [ HH.text comments ]
272+ ]
227273
228274 , HH.div [ HP.class_ (wrap "result__actions") ]
275+
229276 [ HH.span [ HP.class_ (wrap "result__actions__item") ]
230277 [ HH.span [ HP.classes [ wrap "badge"
231278 , wrap "badge--package"
232279 ]
233280 , HP.title "Package"
234281 ]
235− [ HH.text "P"
236− ]
282+ [ HH.text "P" ]
237283 , HH.text result.packageName
238284 ]
239285
@@ -243,8 +289,7 @@ renderResult = unwrap >>> \result ->
243289 ]
244290 , HP.title "Module"
245291 ]
246− [ HH.text "M"
247− ]
292+ [ HH.text "M" ]
248293 , HH.text result.moduleName
249294 ]
250295 ]
@@ -254,42 +299,115 @@ renderResultType
254299 :: forall a rest
255300 . { info :: ResultInfo
256301 , name :: String
302+ , moduleName :: String
257303 | rest
258304 }
259305 -> Array (HH.HTML a Action)
260−renderResultType = \result ->
306+renderResultType result =
261307 case result.info of
262− ValueResult {type:ty} ->
263− [ HH.pre [ HP.class_ (wrap "result__signature") ]
264− [ HH.code_ [ HH.text (result.name <> " :: ")
265− , renderType ty ]
266− ]
267− ]
308+ ValueResult { type: ty } ->
309+ wrapSignature [ HH.a [ makeHref ValueLevel false result.moduleName result.name ]
310+ [ HH.text result.name ]
311+ , HH.text " :: "
312+ , renderType ty ]
313+
314+ TypeClassMemberResult info ->
315+ wrapSignature $ renderTypeClassMemberSignature info result
316+
317+ TypeClassResult info ->
318+ wrapSignature $ renderTypeClassResult info result
268319 _ -> []
320+ where
321+ wrapSignature signature =
322+ [ HH.pre [ HP.class_ (wrap "result__signature") ] [ HH.code_ signature ] ]
269323
270−renderType :: forall a. Type -> HH.HTML a Action
324+renderTypeClassResult
325+ :: forall a rest
326+ . { fundeps :: FunDeps
327+ , arguments :: Array TypeArgument
328+ , superclasses :: Array Constraint
329+ }
330+ -> { name :: String, moduleName :: String | rest }
331+ -> Array (HH.HTML a Action)
332+renderTypeClassResult { fundeps, arguments, superclasses } { name, moduleName } =
333+ [ keyword "class"
334+ , if Array.null superclasses
335+ then
336+ HH.text ""
337+ else
338+ HH.span_ $
339+ [ syntax " ("
340+ , HH.span_ $ Array.intercalate [ HH.text ", " ] (
341+ superclasses <#> renderConstraint >>> Array.singleton
342+ )
343+ , syntax ")"
344+ , HH.text " "
345+ , syntax "<="
346+ ]
347+ , HH.text " "
348+ , HH.a [ makeHref TypeLevel false moduleName name ]
349+ [ HH.text name ]
350+ , HH.text " "
351+ ] <> (
352+ Array.intercalate [ HH.text " " ] (
353+ arguments <#> (
354+ unwrap >>>
355+ \argument ->
356+ case argument.mbKind of
357+ Nothing ->
358+ [ HH.text argument.name ]
359+ Just kind ->
360+ [ HH.text "("
361+ , HH.text argument.name
362+ , HH.text " :: "
363+ , renderKind kind
364+ , HH.text ")"
365+ ]))
366+ )
367+
368+-- | Insert type class name and arguments
369+renderTypeClassMemberSignature
370+ :: forall a rest
371+ . { type :: Type
372+ , typeClass :: String
373+ , typeClassArguments :: Array String
374+ }
375+ -> { name :: String | rest }
376+ -> Array (HH.HTML a Action)
377+renderTypeClassMemberSignature { type: ty, typeClass, typeClassArguments } result =
378+ [ HH.text result.name
379+ , HH.text " :: "
380+ , HH.text $ typeClass <> " "
381+ -- We don't want to insert `forall` here to avoid visual noise,
382+ -- and to make type class members more easily distinguishable from ordinary values.
383+ -- TODO: consider doing what pursuit does.
384+ , HH.text $ Array.intercalate " " typeClassArguments <> " "
385+ , HH.text "=> "
386+ , renderType ty ]
387+
388+renderType
389+ :: forall a
390+ . Type
391+ -> HH.HTML a Action
271392renderType = case _ of
272393 TypeVar str -> HH.text str
273− TypeLevelString str -> HH.text str
394+ TypeLevelString str -> HH.text $ "(Text \"" <> str <> "\")" -- TODO: add escaping
274395 TypeWildcard -> HH.text "_"
275− TypeConstructor qname -> renderQualifiedName qname
276− TypeOp qname -> renderQualifiedName qname
396+ TypeConstructor qname -> renderQualifiedName false TypeLevel qname
397+ TypeOp qname -> renderQualifiedName true TypeLevel qname
277398
278399 TypeApp (TypeApp (TypeConstructor
279− (QualifiedName { moduleName: ["Prim"]
400+ (QualifiedName { moduleName: [ "Prim" ]
280401 , name: "Function" })) t1) t2 ->
281402 HH.span_ [ renderType t1
282403 , HH.text " -> "
283404 , renderType t2
284405 ]
285406
286− TypeApp (TypeConstructor (QualifiedName { moduleName: ["Prim"]
407+ TypeApp (TypeConstructor (QualifiedName { moduleName: [ "Prim" ]
287408 , name: "Record" }))
288− record ->
289− HH.span_ [ HH.text "{ | "
290− , renderType record
291− , HH.text " }" ]
292−
409+ row ->
410+ renderRow false row
293411
294412 TypeApp t1 t2 ->
295413 HH.span_ [ renderType t1
@@ -298,20 +416,7 @@ renderType = case _ of
298416 ]
299417
300418 ty@(ForAll _ _ _) ->
301− let foralls = joinForAlls ty in
302− HH.span_ $
303− [ HH.text "forall" ] <>
304− ( Array.fromFoldable foralls.binders <#>
305− \ { var, mbKind } ->
306− case mbKind of
307− Nothing -> HH.text (" " <> var)
308− Just kind ->
309− HH.span_ [ HH.text ("(" <> var <> " :: ")
310− , renderKind kind
311− , HH.text ")"
312− ]
313− ) <>
314− [ HH.text ". ", renderType foralls.ty ]
419+ renderForAll ty
315420
316421 ConstrainedType cnstr ty ->
317422 HH.span_
@@ -320,15 +425,18 @@ renderType = case _ of
320425 , renderType ty
321426 ]
322427
323− REmpty -> HH.text "{}"
324− ty@(RCons _ _ _) -> renderRow ty
428+ ty@REmpty -> renderRow true ty
429+ ty@(RCons _ _ _) -> renderRow true ty
325430
326431 BinaryNoParensType op t1 t2 ->
327432 HH.span_
328433 [ renderType t1
434+ , HH.text " "
329435 , renderType op
436+ , HH.text " "
330437 , renderType t2
331438 ]
439+
332440 ParensInType ty ->
333441 HH.span_
334442 [ HH.text "("
@@ -336,39 +444,123 @@ renderType = case _ of
336444 , HH.text ")"
337445 ]
338446
339−renderRow :: forall a. Type -> HH.HTML a Action
340−renderRow ty =
341− let rows = joinRows ty in
447+renderForAll
448+ :: forall a
449+ . Type
450+ -> HH.HTML a Action
451+renderForAll ty =
342452 HH.span_ $
343− [ HH.text "{ " ] <>
344− ( Array.intercalate [ HH.text ", " ] $ Array.fromFoldable rows <#>
345− \entry ->
346− [ HH.span_ [ HH.text $ entry.row <> " :: "
347− , renderType entry.ty ] ]
453+
454+ [ HH.text "forall" ] <>
455+
456+ ( Array.fromFoldable foralls.binders <#>
457+ \ { var, mbKind } ->
458+ case mbKind of
459+ Nothing -> HH.text (" " <> var)
460+ Just kind ->
461+ HH.span_ [ HH.text $ " (" <> var <> " :: "
462+ , renderKind kind
463+ , HH.text ")" ]
348464 ) <>
349− [ HH.text " }" ]
350465
351−htmlSingleton :: forall t406 t407. HH.HTML t407 t406 -> HH.HTML t407 t406
352−htmlSingleton x = HH.span_ [ x ]
466+ [ HH.text ". ", renderType foralls.ty ]
467+
468+ where
469+ foralls = joinForAlls ty
470+
471+renderRow
472+ :: forall a
473+ . Boolean
474+ -> Type
475+ -> HH.HTML a Action
476+renderRow asRow =
477+ joinRows >>> \ { rows, ty } ->
353478
354−renderConstraint :: forall a. Constraint -> HH.HTML a Action
355−renderConstraint (Constraint { constraintClass, constraintArgs }) =
356479 HH.span_ $
357− [ renderQualifiedName constraintClass, HH.text " " ] <>
358− Array.intercalate [ HH.text " " ] (constraintArgs <#> \ty -> [ renderType ty ])
359480
481+ if List.null rows
482+ then
483+ [ HH.text $ if asRow then "()" else "{}" ]
484+ else
485+ [ HH.text opening ] <>
486+
487+ ( Array.intercalate [ HH.text ", " ] $ Array.fromFoldable $ rows <#>
488+ \entry ->
489+ [ HH.span_ [ HH.text $ entry.row <> " :: "
490+ , renderType entry.ty ] ]
491+ ) <>
492+
493+ case ty of
494+ Just ty' -> [ HH.text " | ", renderType ty', HH.text closing ]
495+ Nothing -> [ HH.text closing ]
496+
497+ where
498+ opening = if asRow then "( " else "{ "
499+ closing = if asRow then " )" else " }"
360500
361−renderQualifiedName :: forall a. QualifiedName -> HH.HTML a Action
362−renderQualifiedName (QualifiedName { moduleName, name })
363− = HH.text name
501+renderConstraint
502+ :: forall a
503+ . Constraint
504+ -> HH.HTML a Action
505+renderConstraint (Constraint { constraintClass, constraintArgs }) =
506+ HH.span_ $
507+ [ renderQualifiedName false TypeLevel constraintClass, HH.text " " ] <>
508+ Array.intercalate [ HH.text " " ] (constraintArgs <#> \ty -> [ renderType ty ])
364509
365−renderKind :: forall a. Kind -> HH.HTML a Action
510+renderQualifiedName
511+ :: forall a
512+ . Boolean
513+ -> DeclLevel
514+ -> QualifiedName
515+ -> HH.HTML a Action
516+renderQualifiedName isInfix level (QualifiedName { moduleName, name })
517+ = if isBuiltIn then
518+ HH.text name
519+ else
520+ HH.a [ HE.onClick $ const $ Just $
521+ SearchResultClicked $ moduleNameString
522+ , makeHref level isInfix moduleNameString name
523+ ]
524+ [ HH.text name ]
525+ where
526+ moduleNameString = Array.intercalate "." moduleName
527+ isBuiltIn = moduleName !! 0 == Just "Prim"
528+
529+renderKind
530+ :: forall a
531+ . Kind ->
532+ HH.HTML a Action
366533renderKind = case _ of
367− Row k1 -> HH.span_ [ HH.text "#", renderKind k1 ]
368− FunKind k1 k2 -> HH.span_ [ renderKind k1, renderKind k2 ]
369− NamedKind qname -> renderQualifiedName qname
370−
371−isValuableQuery :: TypeQuery -> Boolean
372−isValuableQuery (QVar _) = false
373−isValuableQuery (QConst _) = false
374−isValuableQuery _ = true
534+ Row k1 -> HH.span_ [ HH.text "# ", renderKind k1 ]
535+ FunKind k1 k2 -> HH.span_ [ renderKind k1, HH.text " -> ", renderKind k2 ]
536+ NamedKind qname -> renderQualifiedName false KindLevel qname
537+
538+makeHref
539+ :: forall t rest
540+ . DeclLevel
541+ -> Boolean
542+ -> String
543+ -> String
544+ -> HH.IProp ( href :: String | rest ) t
545+makeHref level isInfix moduleName name =
546+ HP.href $
547+ moduleName <> ".html#" <>
548+ declLevelToHashAnchor level <> ":" <>
549+ if isInfix then "type (" <> name <> ")" else name
550+
551+keyword
552+ :: forall a
553+ . String
554+ -> HH.HTML a Action
555+keyword str = HH.span [ HP.class_ (wrap "keyword") ] [ HH.text str ]
556+
557+syntax
558+ :: forall a
559+ . String
560+ -> HH.HTML a Action
561+syntax str = HH.span [ HP.class_ (wrap "syntax") ] [ HH.text str ]
562+
563+isValuableTypeQuery :: TypeQuery -> Boolean
564+isValuableTypeQuery (QVar _) = false
565+isValuableTypeQuery (QConst _) = false
566+isValuableTypeQuery _ = true