Skip to content

Commit 363e001

Browse files
authored
Sort search results by popularity (#21)
Sort search results by popularity (based on number of package reverse dependencies).
1 parent 1681ac7 commit 363e001

22 files changed

+703
-267
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
New features:
1111
- Render docs as markdown (#15)
12-
- Show help for each CLI command.
12+
- Show help for each CLI command (#17)
13+
- Add packages to the search index (#16)
14+
- Sort search results by popularity (based on number of package reverse dependencies).
15+
16+
Bugfixes:
17+
- Fix decoding of kind annotations in `forall`s (#17)
18+
- Fix rendering for variable-parametrized records (e.g. `Record a`) and type-level strings.
1319

1420
## [0.0.4] - 2019-07-25
1521

src/Docs/Search/App.purs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Prelude
66
import Docs.Search.App.SearchField as SearchField
77
import Docs.Search.App.SearchResults as SearchResults
88
import Docs.Search.Extra (whenJust)
9+
import Docs.Search.PackageIndex as PackageIndex
910

1011
import Control.Coroutine as Coroutine
1112
import Data.Maybe (Maybe(..))
@@ -38,8 +39,12 @@ main = do
3839

3940
whenJust mbContainers \ { searchField, searchResults, pageContents } -> do
4041
HA.runHalogenAff do
42+
packageIndex <- PackageIndex.loadPackageIndex
43+
44+
let initialSearchEngineState = { packageIndex: packageIndex, index: mempty, typeIndex: mempty }
45+
4146
sfio <- runUI SearchField.component unit searchField
42-
srio <- runUI (SearchResults.mkComponent pageContents markdownIt) unit searchResults
47+
srio <- runUI (SearchResults.mkComponent initialSearchEngineState pageContents markdownIt) unit searchResults
4348
sfio.subscribe $
4449
Coroutine.consumer (srio.query <<< H.tell <<< SearchResults.MessageFromSearchField)
4550

src/Docs/Search/App/SearchField.purs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import Web.DOM.ParentNode as ParentNode
2020
import Web.HTML (window) as Web
2121
import Web.HTML as HTML
2222
import Web.HTML.HTMLDocument as HTMLDocument
23-
import Web.HTML.HTMLElement (blur) as Web
23+
import Web.HTML.HTMLElement (blur, focus) as Web
2424
import Web.HTML.HTMLInputElement as HTMLInputElement
2525
import Web.HTML.Window (document) as Web
2626
import Web.HTML.Window as Window
@@ -74,6 +74,7 @@ handleAction = case _ of
7474
when (not state.focused) do
7575
H.liftEffect do
7676
withSearchField HTMLInputElement.select
77+
withSearchField (HTMLInputElement.toHTMLElement >>> Web.focus)
7778

7879
when (KE.code ev == "Escape") do
7980
state <- H.get

src/Docs/Search/App/SearchResults.purs

Lines changed: 83 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
-- | This module contains a Halogen component for search results.
22
module Docs.Search.App.SearchResults where
33

4-
import Prelude
5-
64
import Docs.Search.App.SearchField (SearchFieldMessage(..))
5+
import Docs.Search.BrowserEngine (PartialIndex, browserSearchEngine)
76
import Docs.Search.Config (config)
87
import Docs.Search.Declarations (DeclLevel(..), declLevelToHashAnchor)
98
import Docs.Search.DocsJson (DataDeclType(..))
10-
import Docs.Search.Extra ((>#>), homePageFromRepository)
9+
import Docs.Search.Extra (homePageFromRepository, (>#>))
10+
import Docs.Search.PackageIndex (PackageResult)
11+
import Docs.Search.Engine (Result(..))
12+
import Docs.Search.Engine as Engine
1113
import Docs.Search.SearchResult (ResultInfo(..), SearchResult(..))
1214
import Docs.Search.TypeDecoder (Constraint(..), FunDep(..), FunDeps(..), Kind(..), QualifiedName(..), Type(..), TypeArgument(..), joinForAlls, joinRows)
13-
import Docs.Search.Engine as SearchEngine
14-
import Docs.Search.Engine (ResultsType(..))
15+
import Docs.Search.TypeIndex (TypeIndex)
16+
17+
import Prelude
1518

1619
import Data.Array ((!!))
1720
import Data.Array as Array
1821
import Data.List as List
19-
import Data.Maybe (Maybe(..), isJust)
22+
import Data.Maybe (Maybe(..), isJust, fromMaybe)
2023
import Data.Newtype (wrap)
2124
import Data.String.CodeUnits (stripSuffix) as String
2225
import Data.String.Common (null, trim) as String
@@ -40,9 +43,12 @@ data Mode = Off | Loading | Active
4043
derive instance eqMode :: Eq Mode
4144

4245

43-
type State = { searchEngineState :: SearchEngine.State
44-
, results :: Array SearchResult
45-
, resultsType :: ResultsType
46+
type EngineState =
47+
Engine.EngineState PartialIndex TypeIndex
48+
49+
50+
type State = { engineState :: EngineState
51+
, results :: Array Result
4652
, input :: String
4753
, contents :: Element
4854
, resultsCount :: Int
@@ -62,14 +68,14 @@ data Action
6268

6369
mkComponent
6470
:: forall o i
65-
. Element
71+
. EngineState
72+
-> Element
6673
-> MD.MarkdownIt
6774
-> H.Component HH.HTML Query i o Aff
68-
mkComponent contents markdownIt =
75+
mkComponent initialEngineState contents markdownIt =
6976
H.mkComponent
70-
{ initialState: const { searchEngineState: mempty
77+
{ initialState: const { engineState: initialEngineState
7178
, results: []
72-
, resultsType: DeclResults
7379
, input: ""
7480
, contents
7581
, resultsCount: config.resultsCount
@@ -107,12 +113,14 @@ handleQuery (MessageFromSearchField (InputUpdated input_) next) = do
107113
H.modify_ (_ { mode = Loading, resultsCount = config.resultsCount })
108114

109115
void $ H.fork do
110-
{ searchEngineState, results, resultsType } <- H.liftAff $
111-
SearchEngine.query state.searchEngineState state.input
116+
{ index, results } <- H.liftAff $
117+
Engine.query browserSearchEngine state.engineState state.input
118+
112119
H.modify_ (_ { results = results
113120
, mode = Active
114-
, searchEngineState = searchEngineState
115-
, resultsType = resultsType })
121+
, engineState = index
122+
}
123+
)
116124

117125
hidePageContents
118126

@@ -186,15 +194,6 @@ render state@{ mode: Active } =
186194
renderContainer $
187195
[ HH.h1_ [ HH.text "Search results" ]
188196

189-
, HH.div [ HP.classes [ wrap "result" ] ] $
190-
[ HH.text "Found "
191-
, HH.strong_ [ HH.text $ show $ Array.length state.results ]
192-
, HH.text $
193-
case state.resultsType of
194-
DeclResults -> " definitions."
195-
TypeResults _ -> " definitions with similar types."
196-
]
197-
198197
, HH.div_ $
199198
Array.concat $ shownResults <#> renderResult state.markdownIt
200199

@@ -227,11 +226,49 @@ renderSummary text =
227226

228227

229228
renderResult
229+
:: forall a
230+
. MD.MarkdownIt
231+
-> Result
232+
-> Array (HH.HTML a Action)
233+
renderResult markdownIt (DeclResult sr) = renderSearchResult markdownIt sr
234+
renderResult markdownIt (TypeResult sr) = renderSearchResult markdownIt sr
235+
renderResult markdownIt (PackResult sr) = renderPackageResult sr
236+
237+
238+
renderPackageResult
239+
:: forall a
240+
. PackageResult
241+
-> Array (HH.HTML a Action)
242+
renderPackageResult { name, description, repository } =
243+
[ HH.div [ HP.class_ (wrap "result") ]
244+
[ HH.h3 [ HP.class_ (wrap "result__title") ]
245+
[ HH.span [ HP.classes [ wrap "result__badge"
246+
, wrap "badge"
247+
, wrap "badge--package" ]
248+
, HP.title "Package"
249+
]
250+
[ HH.text "P" ]
251+
252+
, HH.a [ HP.class_ (wrap "result__link")
253+
, HP.href $ fromMaybe "" repository # homePageFromRepository
254+
]
255+
[ HH.text name ]
256+
]
257+
]
258+
] <>
259+
260+
description >#> \descriptionText ->
261+
[ HH.div [ HP.class_ (wrap "result__body") ]
262+
[ HH.text descriptionText ]
263+
]
264+
265+
266+
renderSearchResult
230267
:: forall a
231268
. MD.MarkdownIt
232269
-> SearchResult
233270
-> Array (HH.HTML a Action)
234-
renderResult markdownIt (SearchResult result) =
271+
renderSearchResult markdownIt (SearchResult result) =
235272
-- class names here and below are from Pursuit.
236273
[ HH.div [ HP.class_ (wrap "result") ]
237274
[ HH.h3 [ HP.class_ (wrap "result__title") ]
@@ -274,30 +311,6 @@ renderResult markdownIt (SearchResult result) =
274311
]
275312
]
276313

277-
renderResult _markdownIt (PackageResult { name, description, repository }) =
278-
[ HH.div [ HP.class_ (wrap "result") ]
279-
[ HH.h3 [ HP.class_ (wrap "result__title") ]
280-
[ HH.span [ HP.classes [ wrap "result__badge"
281-
, wrap "badge"
282-
, wrap "badge--package" ]
283-
, HP.title "Package"
284-
]
285-
[ HH.text "P" ]
286-
287-
, HH.a [ HP.class_ (wrap "result__link")
288-
, HP.href $ homePageFromRepository repository
289-
]
290-
[ HH.text name ]
291-
]
292-
]
293-
] <> (
294-
description >#>
295-
\descriptionText ->
296-
[ HH.div [ HP.class_ (wrap "result__body") ]
297-
[ HH.text descriptionText ]
298-
]
299-
)
300-
301314

302315
renderResultType
303316
:: forall a rest
@@ -476,7 +489,7 @@ renderType
476489
-> HH.HTML a Action
477490
renderType = case _ of
478491
TypeVar str -> HH.text str
479-
TypeLevelString str -> HH.text $ "(Text \"" <> str <> "\")" -- TODO: add escaping
492+
TypeLevelString str -> HH.text $ "\"" <> str <> "\"" -- TODO: add escaping
480493
TypeWildcard -> HH.text "_"
481494
TypeConstructor qname -> renderQualifiedName false TypeLevel qname
482495
TypeOp qname -> renderQualifiedName true TypeLevel qname
@@ -569,7 +582,17 @@ renderRow asRow =
569582

570583
if List.null rows
571584
then
572-
[ HH.text $ if asRow then "()" else "{}" ]
585+
[ if asRow
586+
then HH.text "()"
587+
else
588+
fromMaybe (HH.text "{}") $
589+
ty <#> \ty' ->
590+
HH.span_
591+
[ renderQualifiedName false TypeLevel primRecord
592+
, HH.text " "
593+
, renderType ty'
594+
]
595+
]
573596
else
574597
[ HH.text opening ] <>
575598

@@ -579,9 +602,9 @@ renderRow asRow =
579602
, renderType entry.ty ] ]
580603
) <>
581604

582-
case ty of
583-
Just ty' -> [ HH.text " | ", renderType ty', HH.text closing ]
584-
Nothing -> [ HH.text closing ]
605+
(ty >#> \ty' -> [ HH.text " | ", renderType ty' ]) <>
606+
607+
[ HH.text closing ]
585608

586609
where
587610
opening = if asRow then "( " else "{ "
@@ -643,6 +666,10 @@ makeHref level isInfix moduleName name =
643666
if isInfix then "type (" <> name <> ")" else name
644667

645668

669+
primRecord :: QualifiedName
670+
primRecord = QualifiedName { moduleName: [ "Prim" ], name: "Record" }
671+
672+
646673
keyword
647674
:: forall a
648675
. String
Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1-
-- | Contains `Index` that can be loaded on demand, transparently
2-
-- | to the user.
3-
module Docs.Search.Index where
4-
5-
import Prelude
1+
-- | A search engine that is used in the browser.
2+
module Docs.Search.BrowserEngine where
63

74
import Docs.Search.Config (config)
5+
import Docs.Search.PackageIndex (queryPackageIndex)
6+
import Docs.Search.Engine (Engine, EngineState, Index)
87
import Docs.Search.SearchResult (SearchResult)
8+
import Docs.Search.TypeIndex (TypeIndex)
9+
import Docs.Search.TypeIndex as TypeIndex
10+
11+
import Prelude
912

13+
import Data.Char as Char
1014
import Control.Promise (Promise, toAffE)
1115
import Data.Argonaut.Core (Json)
1216
import Data.Argonaut.Decode (decodeJson)
1317
import Data.Array as Array
14-
import Data.Char as Char
1518
import Data.Either (hush)
1619
import Data.List (List, (:))
1720
import Data.List as List
@@ -26,22 +29,27 @@ import Data.Tuple (Tuple(..))
2629
import Effect (Effect)
2730
import Effect.Aff (Aff, try)
2831

29-
newtype Index
30-
= Index (Map Int (Trie Char (List SearchResult)))
3132

32-
derive instance newtypeIndex :: Newtype Index _
33-
derive newtype instance semigroupIndex :: Semigroup Index
34-
derive newtype instance monoidIndex :: Monoid Index
33+
newtype PartialIndex
34+
= PartialIndex (Map Int Index)
35+
36+
derive instance newtypePartialIndex :: Newtype PartialIndex _
37+
derive newtype instance semigroupPartialIndex :: Semigroup PartialIndex
38+
derive newtype instance monoidPartialIndex :: Monoid PartialIndex
39+
40+
41+
type BrowserEngineState = EngineState PartialIndex TypeIndex
42+
3543

3644
-- | This function dynamically injects a script with the required index part and returns
37-
-- | a new `Index` that contains newly loaded definitions.
45+
-- | a new `PartialIndex` that contains newly loaded definitions.
3846
-- |
3947
-- | We split the index because of its size, and also to speed up queries.
4048
query
41-
:: Index
49+
:: PartialIndex
4250
-> String
43-
-> Aff { index :: Index, results :: Array SearchResult }
44-
query index@(Index indexMap) input = do
51+
-> Aff { index :: PartialIndex, results :: Array SearchResult }
52+
query index@(PartialIndex indexMap) input = do
4553
let
4654
path :: List Char
4755
path =
@@ -70,7 +78,7 @@ query index@(Index indexMap) input = do
7078

7179
case mbNewTrie of
7280
Just newTrie -> do
73-
pure { index: Index $ Map.insert partId newTrie indexMap
81+
pure { index: PartialIndex $ Map.insert partId newTrie indexMap
7482
, results: flatten $ Trie.queryValues path newTrie
7583
}
7684
Nothing -> do
@@ -79,6 +87,7 @@ query index@(Index indexMap) input = do
7987
where
8088
flatten = Array.concat <<< Array.fromFoldable <<< map Array.fromFoldable
8189

90+
8291
insertResults
8392
:: Tuple String (Array SearchResult)
8493
-> Trie Char (List SearchResult)
@@ -96,6 +105,17 @@ insertResults (Tuple path newResults) =
96105
Nothing -> Just $ List.fromFoldable newResults
97106
Just old -> Just $ List.fromFoldable newResults <> old
98107

108+
109+
browserSearchEngine
110+
:: Engine Aff PartialIndex TypeIndex
111+
browserSearchEngine =
112+
{ queryIndex: query
113+
, queryTypeIndex: TypeIndex.query
114+
, queryPackageIndex
115+
}
116+
117+
118+
99119
-- | Find in which part of the index this path can be found.
100120
getPartId :: List Char -> Int
101121
getPartId (a : b : _) =
@@ -104,6 +124,7 @@ getPartId (a : _) =
104124
Char.toCharCode a `mod` config.numberOfIndexParts
105125
getPartId _ = 0
106126

127+
107128
-- | Load a part of the index by injecting a <script> tag into the DOM.
108129
foreign import loadIndex_
109130
:: Int

0 commit comments

Comments
 (0)