Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
/.purs*
/.psa*
/.spago/
/dist/
17 changes: 12 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,31 @@ install:
- tar -xvf $HOME/purescript.tar.gz -C $HOME/
- chmod a+x $HOME/purescript
- npm install -g spago
- npm install
- spago install

script:
- spago build
- spago test
- spago docs
- spago bundle-app -m Docs.Search.App --to docs-search-app.js
- spago bundle-app -m Docs.Search.IndexBuilder --to index-builder.js
- node index-builder.js
- npm run build
- node dist/main.js build-index

deploy:
- provider: releases
api_key: $API_KEY
file:
- docs-search-app.js
- index-builder.js
- dist/docs-search-app.js
- dist/main.js
skip_cleanup: true
on:
tags: true
script:
- echo 'done'
- provider: npm
api_key: $NPM_API_KEY
email: klntsky@gmail.com
skip_cleanup: true
on:
tags: true
branch: master
33 changes: 23 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,34 @@

An app that adds search capabilities to generated documentation for purescript code.

The goal is to replicate all functionality of pursuit, including querying by type.
It supports nearly-all functionality of [Pursuit](https://github.com/purescript/pursuit), including querying by type.

See [#89](https://github.com/spacchetti/spago/issues/89).
## Installing

To see it in action, run the following:
Run `npm install purescript-docs-search`.

```
spago build
spago docs
spago bundle-app -m Docs.Search.App --to generated-docs/docs-search-app.js
spago run -m Docs.Search.IndexBuilder
```
## Usage

## UI
There are two usage scenarios:

### Patching static documentation

Use `purescript-docs-search build-index` command to patch HTML files located in `generated-docs/html`. You then will be able to search for declarations or types:

![Preview](preview.png)

The user interface of the app is optimised for keyboard-only use.

**S** hotkey can be used to focus on the search field, **Escape** can be used to leave it. Pressing **Escape** twice will close the search results listing.

### Using the CLI

Running `purescript-docs-search` within a project directory will open an interactive command-line session.

Note that unlike in Pursuit, most relevant results will appear last.

A quick demo:

[![asciicast](https://asciinema.org/a/Hexie5JoWjlAqLqv2IgafIdb9.svg)](https://asciinema.org/a/Hexie5JoWjlAqLqv2IgafIdb9)

You may notice that the CLI offers slightly better results than the web interface. This is a performance tradeoff.
43 changes: 43 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "purescript-docs-search",
"version": "0.0.1",
"description": "Search frontend for the documentation generated by the PureScript compiler.",
"main": "dist/main.js",
"directories": {
"test": "test"
},
"bin": {
"purescript-docs-search": "dist/main.js"
},
"files": [
"dist/main.js",
"dist/docs-search-app.js",
"README.md"
],
"scripts": {
"test": "spago test",
"bundle-app": "spago bundle-app -m Docs.Search.App --to dist/docs-search-app.js",
"bundle-main": "spago bundle-app -m Docs.Search.Main --to dist/main.js && browserify --no-builtins --no-commondir --no-detect-globals --node dist/main.js --outfile dist/main-bundled.js && echo \"#!/usr/bin/env node\" > dist/main.js && cat dist/main-bundled.js >> dist/main.js && rm dist/main-bundled.js",
"build": "npm run bundle-app && npm run bundle-main",
"clean": "rm -rf dist"
},
"repository": {
"type": "git",
"url": "git+https://github.com/spacchetti/purescript-docs-search.git"
},
"keywords": [
"purescript"
],
"author": "Kalnitsky Vladimir <klntsky@gmail.com>",
"license": "BSD-3-Clause",
"bugs": {
"url": "https://github.com/spacchetti/purescript-docs-search/issues"
},
"homepage": "https://github.com/spacchetti/purescript-docs-search#readme",
"dependencies": {},
"devDependencies": {
"browserify": "^16.3.0",
"glob": "^7.1.4",
"spago": "^0.8.5"
}
}
22 changes: 22 additions & 0 deletions packages.dhall
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,28 @@ let additions =
[ "css", "halogen" ]
"https://github.com/slamdata/purescript-halogen-css.git"
"v8.0.0"
, optparse =
mkPackage
[ "prelude"
, "effect"
, "exitcodes"
, "strings"
, "ordered-collections"
, "arrays"
, "console"
, "memoize"
, "transformers"
, "exists"
, "node-process"
, "free"
]
"https://github.com/f-o-a-m/purescript-optparse.git"
"v3.0.1"
, exitcodes =
mkPackage
[ "enums" ]
"https://github.com/Risto-Stevcev/purescript-exitcodes.git"
"v4.0.0"
}

in upstream ⫽ overrides ⫽ additions
Binary file added preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions spago.dhall
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
, "node-fs"
, "node-fs-aff"
, "node-process"
, "node-readline"
, "optparse"
, "profunctor"
, "search-trie"
, "string-parsers"
Expand Down
101 changes: 33 additions & 68 deletions src/Docs/Search/App/SearchResults.purs
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,19 @@ import Docs.Search.Config (config)
import Docs.Search.Declarations (DeclLevel(..), declLevelToHashAnchor)
import Docs.Search.DocsJson (DataDeclType(..))
import Docs.Search.Extra ((>#>))
import Docs.Search.Index (Index)
import Docs.Search.Index as Index
import Docs.Search.SearchResult (ResultInfo(..), SearchResult, typeOf)
import Docs.Search.SearchResult (ResultInfo(..), SearchResult)
import Docs.Search.TypeDecoder (Constraint(..), FunDep(..), FunDeps(..), Kind(..), QualifiedName(..), Type(..), TypeArgument(..), joinForAlls, joinRows)
import Docs.Search.TypeIndex (TypeIndex)
import Docs.Search.TypeIndex as TypeIndex
import Docs.Search.TypeQuery (TypeQuery(..), parseTypeQuery, penalty)
import Docs.Search.Engine as SearchEngine
import Docs.Search.Engine (ResultsType(..))

import CSS (textWhitespace, whitespacePreWrap)
import Data.Array ((!!))
import Data.Array as Array
import Data.Either (hush)
import Data.List as List
import Data.Maybe (Maybe(..), isJust, maybe)
import Data.Maybe (Maybe(..), isJust)
import Data.Newtype (unwrap, wrap)
import Data.String (length) as String
import Data.String.CodeUnits (stripSuffix) as String
import Data.String.Common (toLower, trim) as String
import Data.String.Common (null, trim) as String
import Data.String.Pattern (Pattern(..)) as String
import Effect.Aff (Aff)
import Halogen as H
Expand All @@ -38,15 +33,11 @@ import Web.HTML as HTML
import Web.HTML.Location as Location
import Web.HTML.Window as Window

data Mode = Off | Loading | Active | InputTooShort
data Mode = Off | Loading | Active

derive instance eqMode :: Eq Mode

-- | Is it a search by type or by name?
data ResultsType = TypeResults TypeQuery | DeclResults

type State = { index :: Index
, typeIndex :: TypeIndex
type State = { searchEngineState :: SearchEngine.State
, results :: Array SearchResult
, resultsType :: ResultsType
, input :: String
Expand All @@ -68,8 +59,7 @@ mkComponent
-> H.Component HH.HTML Query i o Aff
mkComponent contents =
H.mkComponent
{ initialState: const { index: mempty
, typeIndex: mempty
{ initialState: const { searchEngineState: mempty
, results: []
, resultsType: DeclResults
, input: ""
Expand Down Expand Up @@ -99,36 +89,20 @@ handleQuery (MessageFromSearchField (InputUpdated input_) next) = do

state <- H.modify (_ { input = input })

if String.length input < 2
if String.null input
then do
if input == ""
then do
H.modify_ (_ { mode = Off })
showPageContents
else do
H.modify_ (_ { mode = InputTooShort })
hidePageContents
else do
H.modify_ (_ { mode = Loading, resultsCount = config.resultsCount })

void $ H.fork do
let resultsType =
maybe DeclResults TypeResults (hush (parseTypeQuery state.input)
>>= isValuableTypeQuery)

case resultsType of

DeclResults -> do
{ index, results } <- H.liftAff $ Index.query state.index (String.toLower state.input)
H.modify_ (_ { results = results
, mode = Active
, index = index })

TypeResults query -> do
{ index, results } <- H.liftAff $ TypeIndex.query state.typeIndex query
H.modify_ (_ { results = sortByDistance query results
, mode = Active
, typeIndex = index })
{ searchEngineState, results, resultsType } <- H.liftAff $
SearchEngine.query state.searchEngineState state.input
H.modify_ (_ { results = results
, mode = Active
, searchEngineState = searchEngineState
, resultsType = resultsType })

hidePageContents

Expand Down Expand Up @@ -181,12 +155,6 @@ render { mode: Off } = HH.div_ []
render { mode: Loading } =
renderContainer $
[ HH.h1_ [ HH.text "Loading..." ] ]
render { mode: InputTooShort } =
renderContainer $
[ HH.h1_ [ HH.text "Error" ] ] <>
[ HH.div [ HP.classes [ wrap "result", wrap "result--empty" ] ]
[ HH.text "Search query is too short." ]
]
render state@{ mode: Active, results: [] } =
renderContainer $

Expand Down Expand Up @@ -299,11 +267,7 @@ renderResultType
renderResultType result =
case result.info of
ValueResult { type: ty } ->
wrapSignature [ HH.a [ makeHref ValueLevel false result.moduleName result.name
, HE.onClick $ const $ Just $ SearchResultClicked result.moduleName ]
[ HH.text result.name ]
, HH.text " :: "
, renderType ty ]
wrapSignature $ renderValueSignature result ty

TypeClassResult info ->
wrapSignature $ renderTypeClassSignature info result
Expand All @@ -321,6 +285,21 @@ renderResultType result =
wrapSignature signature =
[ HH.pre [ HP.class_ (wrap "result__signature") ] [ HH.code_ signature ] ]

renderValueSignature
:: forall a rest
. { moduleName :: String
, name :: String
| rest
}
-> Type
-> Array (HH.HTML a Action)
renderValueSignature result ty =
[ HH.a [ makeHref ValueLevel false result.moduleName result.name
, HE.onClick $ const $ Just $ SearchResultClicked result.moduleName ]
[ HH.text result.name ]
, HH.text " :: "
, renderType ty ]

renderTypeClassSignature
:: forall a rest
. { fundeps :: FunDeps
Expand Down Expand Up @@ -582,8 +561,8 @@ renderQualifiedName isInfix level (QualifiedName { moduleName, name })

renderKind
:: forall a
. Kind ->
HH.HTML a Action
. Kind
-> HH.HTML a Action
renderKind = case _ of
Row k1 -> HH.span_ [ HH.text "# ", renderKind k1 ]
FunKind k1 k2 -> HH.span_ [ renderKind k1, syntax " -> ", renderKind k2 ]
Expand Down Expand Up @@ -616,17 +595,3 @@ syntax str = HH.span [ HP.class_ (wrap "syntax") ] [ HH.text str ]

space :: forall a b. HH.HTML a b
space = HH.text " "

isValuableTypeQuery :: TypeQuery -> Maybe TypeQuery
isValuableTypeQuery (QVar _) = Nothing
isValuableTypeQuery (QConst _) = Nothing
isValuableTypeQuery query = Just query

sortByDistance :: TypeQuery -> Array SearchResult -> Array SearchResult
sortByDistance typeQuery results =
_.result <$> Array.sortBy comparePenalties resultsWithPenalties
where
comparePenalties r1 r2 = compare r1.penalty r2.penalty
resultsWithPenalties = results <#>
\result -> { penalty: typeOf (unwrap result).info <#> penalty typeQuery
, result }
13 changes: 7 additions & 6 deletions src/Docs/Search/Config.purs
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,20 @@ config =
, numberOfIndexParts: 50
-- ^ In how many parts the index should be splitted?
, mkIndexPartPath:
\(partId :: Int) -> "generated-docs/index/declarations/" <> show partId <> ".js"
\(partId :: Int) -> "/index/declarations/" <> show partId <> ".js"
, mkIndexPartLoadPath:
\(partId :: Int) -> "../index/declarations/" <> show partId <> ".js"
, resultsCount: 25
-- ^ How many results to show by default?
, penalties: { typeVars: 6
, penalties: { typeVars: 2
, match: 2
, matchConstraint: 1
, instantiate: 1
, generalize: 4
, rowsMismatch: 6
, mismatch: 10
, instantiate: 2
, generalize: 2
, rowsMismatch: 3
, missingConstraint: 1
, excessiveConstraint: 1
}
-- ^ Penalties used to determine how "far" a type query is from a given type.
-- See Docs.Search.TypeQuery
}
Loading