mirror of
https://github.com/roc-lang/roc.git
synced 2025-11-01 21:40:58 +00:00
Merge pull request #6569 from faldor20/docs
Add docs to completions and hover
This commit is contained in:
commit
14ba398b5d
24 changed files with 954 additions and 597 deletions
|
|
@ -51,7 +51,7 @@ false = @Bool False
|
|||
## gate. The infix operator `&&` can also be used as shorthand for
|
||||
## `Bool.and`.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## expect (Bool.and Bool.true Bool.true) == Bool.true
|
||||
## expect (Bool.true && Bool.true) == Bool.true
|
||||
## expect (Bool.false && Bool.true) == Bool.false
|
||||
|
|
@ -66,7 +66,7 @@ false = @Bool False
|
|||
## In these languages the compiler will skip evaluating the expression after the
|
||||
## first operator under certain circumstances. For example an expression like
|
||||
## `enablePets && likesDogs user` would compile to.
|
||||
## ```
|
||||
## ```roc
|
||||
## if enablePets then
|
||||
## likesDogs user
|
||||
## else
|
||||
|
|
@ -80,7 +80,7 @@ and : Bool, Bool -> Bool
|
|||
## Returns `Bool.true` when either input is a `Bool.true`. This is equivalent to
|
||||
## the logic [OR](https://en.wikipedia.org/wiki/Logical_disjunction) gate.
|
||||
## The infix operator `||` can also be used as shorthand for `Bool.or`.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect (Bool.or Bool.false Bool.true) == Bool.true
|
||||
## expect (Bool.true || Bool.true) == Bool.true
|
||||
## expect (Bool.false || Bool.true) == Bool.true
|
||||
|
|
@ -98,7 +98,7 @@ or : Bool, Bool -> Bool
|
|||
## Returns `Bool.false` when given `Bool.true`, and vice versa. This is
|
||||
## equivalent to the logic [NOT](https://en.wikipedia.org/wiki/Negation)
|
||||
## gate. The operator `!` can also be used as shorthand for `Bool.not`.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect (Bool.not Bool.false) == Bool.true
|
||||
## expect (!Bool.false) == Bool.true
|
||||
## ```
|
||||
|
|
@ -111,7 +111,7 @@ not : Bool -> Bool
|
|||
##
|
||||
## **Note** that `isNotEq` does not accept arguments whose types contain
|
||||
## functions.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect (Bool.isNotEq Bool.false Bool.true) == Bool.true
|
||||
## expect (Bool.false != Bool.false) == Bool.false
|
||||
## expect "Apples" != "Oranges"
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ interface Box
|
|||
## the value from the stack to the heap. This may provide a performance
|
||||
## optimization for advanced use cases with large values. A platform may require
|
||||
## that some values are boxed.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster"
|
||||
## ```
|
||||
box : a -> Box a
|
||||
|
||||
## Returns a boxed value.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster"
|
||||
## ```
|
||||
unbox : Box a -> a
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ DecodeError : [TooShort]
|
|||
## This can be useful when creating a [custom](#custom) decoder or when
|
||||
## using [fromBytesPartial](#fromBytesPartial). For example writing unit tests,
|
||||
## such as;
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## input = "\"hello\", " |> Str.toUtf8
|
||||
## actual = Decode.fromBytesPartial input Json.json
|
||||
|
|
@ -117,7 +117,7 @@ DecoderFormatting implements
|
|||
## Build a custom [Decoder] function. For example the implementation of
|
||||
## `decodeBool` could be defined as follows;
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## decodeBool = Decode.custom \bytes, @Json {} ->
|
||||
## when bytes is
|
||||
## ['f', 'a', 'l', 's', 'e', ..] -> { result: Ok Bool.false, rest: List.dropFirst bytes 5 }
|
||||
|
|
@ -132,7 +132,7 @@ decodeWith : List U8, Decoder val fmt, fmt -> DecodeResult val where fmt impleme
|
|||
decodeWith = \bytes, @Decoder decode, fmt -> decode bytes fmt
|
||||
|
||||
## Decode a `List U8` utf-8 bytes and return a [DecodeResult](#DecodeResult)
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## input = "\"hello\", " |> Str.toUtf8
|
||||
## actual = Decode.fromBytesPartial input Json.json
|
||||
|
|
@ -146,7 +146,7 @@ fromBytesPartial = \bytes, fmt -> decodeWith bytes decoder fmt
|
|||
## Decode a `List U8` utf-8 bytes and return a [Result] with no leftover bytes
|
||||
## expected. If successful returns `Ok val`, however, if there are bytes
|
||||
## remaining returns `Err Leftover (List U8)`.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## input = "\"hello\", " |> Str.toUtf8
|
||||
## actual = Decode.fromBytes input Json.json
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ interface Dict
|
|||
##
|
||||
## Here's an example of a dictionary which uses a city's name as the key, and
|
||||
## its population as the associated value.
|
||||
## ```
|
||||
## ```roc
|
||||
## populationByCity =
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "London" 8_961_989
|
||||
|
|
@ -74,7 +74,7 @@ interface Dict
|
|||
## ## Removing
|
||||
##
|
||||
## We can remove an element from the dictionary, like so:
|
||||
## ```
|
||||
## ```roc
|
||||
## populationByCity
|
||||
## |> Dict.remove "Philadelphia"
|
||||
## |> Dict.keys
|
||||
|
|
@ -135,7 +135,7 @@ toInspectorDict = \dict ->
|
|||
Inspect.apply (Inspect.dict dict walk Inspect.toInspector Inspect.toInspector) fmt
|
||||
|
||||
## Return an empty dictionary.
|
||||
## ```
|
||||
## ```roc
|
||||
## emptyDict = Dict.empty {}
|
||||
## ```
|
||||
empty : {} -> Dict * *
|
||||
|
|
@ -200,7 +200,7 @@ releaseExcessCapacity = \@Dict { buckets, data, maxBucketCapacity: originalMaxBu
|
|||
@Dict { buckets, data, maxBucketCapacity: originalMaxBucketCapacity, maxLoadFactor, shifts }
|
||||
|
||||
## Returns the max number of elements the dictionary can hold before requiring a rehash.
|
||||
## ```
|
||||
## ```roc
|
||||
## foodDict =
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "apple" "fruit"
|
||||
|
|
@ -212,7 +212,7 @@ capacity = \@Dict { maxBucketCapacity } ->
|
|||
maxBucketCapacity
|
||||
|
||||
## Returns a dictionary containing the key and value provided as input.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## Dict.single "A" "B"
|
||||
## |> Bool.isEq (Dict.insert (Dict.empty {}) "A" "B")
|
||||
|
|
@ -222,7 +222,7 @@ single = \k, v ->
|
|||
insert (empty {}) k v
|
||||
|
||||
## Returns dictionary with the keys and values specified by the input [List].
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## Dict.single 1 "One"
|
||||
## |> Dict.insert 2 "Two"
|
||||
|
|
@ -241,7 +241,7 @@ fromList = \data ->
|
|||
List.walk data (empty {}) (\dict, (k, v) -> insert dict k v)
|
||||
|
||||
## Returns the number of values in the dictionary.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "One" "A Song"
|
||||
|
|
@ -255,7 +255,7 @@ len = \@Dict { data } ->
|
|||
List.len data
|
||||
|
||||
## Check if the dictionary is empty.
|
||||
## ```
|
||||
## ```roc
|
||||
## Dict.isEmpty (Dict.empty {} |> Dict.insert "key" 42)
|
||||
##
|
||||
## Dict.isEmpty (Dict.empty {})
|
||||
|
|
@ -265,7 +265,7 @@ isEmpty = \@Dict { data } ->
|
|||
List.isEmpty data
|
||||
|
||||
## Clears all elements from a dictionary keeping around the allocation if it isn't huge.
|
||||
## ```
|
||||
## ```roc
|
||||
## songs =
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "One" "A Song"
|
||||
|
|
@ -312,7 +312,7 @@ joinMap = \dict, transform ->
|
|||
## Iterate through the keys and values in the dictionary and call the provided
|
||||
## function with signature `state, k, v -> state` for each value, with an
|
||||
## initial `state` value provided for the first call.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "Apples" 12
|
||||
|
|
@ -335,7 +335,7 @@ walk = \@Dict { data }, initialState, transform ->
|
|||
##
|
||||
## As such, it is typically better for performance to use this over [Dict.walk]
|
||||
## if returning `Break` earlier than the last element is expected to be common.
|
||||
## ```
|
||||
## ```roc
|
||||
## people =
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "Alice" 17
|
||||
|
|
@ -358,7 +358,7 @@ walkUntil = \@Dict { data }, initialState, transform ->
|
|||
|
||||
## Run the given function on each key-value pair of a dictionary, and return
|
||||
## a dictionary with just the pairs for which the function returned `Bool.true`.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Dict.empty {}
|
||||
## |> Dict.insert "Alice" 17
|
||||
## |> Dict.insert "Bob" 18
|
||||
|
|
@ -384,7 +384,7 @@ keepIfHelp = \@Dict dict, predicate, index, length ->
|
|||
|
||||
## Run the given function on each key-value pair of a dictionary, and return
|
||||
## a dictionary with just the pairs for which the function returned `Bool.false`.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Dict.empty {}
|
||||
## |> Dict.insert "Alice" 17
|
||||
## |> Dict.insert "Bob" 18
|
||||
|
|
@ -399,7 +399,7 @@ dropIf = \dict, predicate ->
|
|||
|
||||
## Get the value for a given key. If there is a value for the specified key it
|
||||
## will return [Ok value], otherwise return [Err KeyNotFound].
|
||||
## ```
|
||||
## ```roc
|
||||
## dictionary =
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert 1 "Apple"
|
||||
|
|
@ -414,7 +414,7 @@ get = \dict, key ->
|
|||
|> .result
|
||||
|
||||
## Check if the dictionary has a value for a specified key.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert 1234 "5678"
|
||||
|
|
@ -428,7 +428,7 @@ contains = \dict, key ->
|
|||
|> Result.isOk
|
||||
|
||||
## Insert a value into the dictionary at a specified key.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "Apples" 12
|
||||
|
|
@ -472,7 +472,7 @@ insertHelper = \buckets0, data0, bucketIndex0, distAndFingerprint0, key, value,
|
|||
insertHelper buckets0 data0 bucketIndex1 distAndFingerprint1 key value maxBucketCapacity maxLoadFactor shifts
|
||||
|
||||
## Remove a value from the dictionary for a specified key.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## Dict.empty {}
|
||||
## |> Dict.insert "Some" "Value"
|
||||
|
|
@ -510,7 +510,7 @@ removeHelper = \buckets, bucketIndex, distAndFingerprint, data, key ->
|
|||
## performance optimization for the use case of providing a default when a value
|
||||
## is missing. This is more efficient than doing both a `Dict.get` and then a
|
||||
## `Dict.insert` call, and supports being piped.
|
||||
## ```
|
||||
## ```roc
|
||||
## alterValue : [Present Bool, Missing] -> [Present Bool, Missing]
|
||||
## alterValue = \possibleValue ->
|
||||
## when possibleValue is
|
||||
|
|
@ -573,7 +573,7 @@ circularDist = \start, end, size ->
|
|||
|
||||
## Returns the keys and values of a dictionary as a [List].
|
||||
## This requires allocating a temporary list, prefer using [Dict.toList] or [Dict.walk] instead.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## Dict.single 1 "One"
|
||||
## |> Dict.insert 2 "Two"
|
||||
|
|
@ -588,7 +588,7 @@ toList = \@Dict { data } ->
|
|||
|
||||
## Returns the keys of a dictionary as a [List].
|
||||
## This requires allocating a temporary [List], prefer using [Dict.toList] or [Dict.walk] instead.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## Dict.single 1 "One"
|
||||
## |> Dict.insert 2 "Two"
|
||||
|
|
@ -603,7 +603,7 @@ keys = \@Dict { data } ->
|
|||
|
||||
## Returns the values of a dictionary as a [List].
|
||||
## This requires allocating a temporary [List], prefer using [Dict.toList] or [Dict.walk] instead.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## Dict.single 1 "One"
|
||||
## |> Dict.insert 2 "Two"
|
||||
|
|
@ -621,7 +621,7 @@ values = \@Dict { data } ->
|
|||
## both dictionaries will be combined. Note that where there are pairs
|
||||
## with the same key, the value contained in the second input will be
|
||||
## retained, and the value in the first input will be removed.
|
||||
## ```
|
||||
## ```roc
|
||||
## first =
|
||||
## Dict.single 1 "Not Me"
|
||||
## |> Dict.insert 2 "And Me"
|
||||
|
|
@ -650,7 +650,7 @@ insertAll = \xs, ys ->
|
|||
## Combine two dictionaries by keeping the [intersection](https://en.wikipedia.org/wiki/Intersection_(set_theory))
|
||||
## of all the key-value pairs. This means that we keep only those pairs
|
||||
## that are in both dictionaries. Both the key and value must match to be kept.
|
||||
## ```
|
||||
## ```roc
|
||||
## first =
|
||||
## Dict.single 1 "Keep Me"
|
||||
## |> Dict.insert 2 "And Me"
|
||||
|
|
@ -691,7 +691,7 @@ keepShared = \xs0, ys0 ->
|
|||
## using the [set difference](https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement)
|
||||
## of the values. This means that we will be left with only those pairs that
|
||||
## are in the first dictionary and whose keys are not in the second.
|
||||
## ```
|
||||
## ```roc
|
||||
## first =
|
||||
## Dict.single 1 "Keep Me"
|
||||
## |> Dict.insert 2 "And Me"
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ EncoderFormatting implements
|
|||
|
||||
## Creates a custom encoder from a given function.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## # Appends the byte 42
|
||||
## customEncoder = Encode.custom (\bytes, _fmt -> List.append bytes 42)
|
||||
|
|
@ -93,7 +93,7 @@ appendWith = \lst, @Encoder doEncoding, fmt -> doEncoding lst fmt
|
|||
|
||||
## Appends the encoded representation of a value to an existing list of bytes.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## actual = Encode.append [] { foo: 43 } Core.json
|
||||
## expected = Str.toUtf8 """{"foo":43}"""
|
||||
|
|
@ -105,7 +105,7 @@ append = \lst, val, fmt -> appendWith lst (toEncoder val) fmt
|
|||
|
||||
## Encodes a value to a list of bytes (`List U8`) according to the specified format.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## expect
|
||||
## fooRec = { foo: 42 }
|
||||
##
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ interface List
|
|||
## ## Types
|
||||
##
|
||||
## A sequential list of values.
|
||||
## ```
|
||||
## ```roc
|
||||
## [1, 2, 3] # a list of numbers
|
||||
## ["a", "b", "c"] # a list of strings
|
||||
## [[1.1], [], [2.2, 3.3]] # a list of lists of numbers
|
||||
|
|
@ -112,7 +112,7 @@ interface List
|
|||
## will be immediately freed.
|
||||
##
|
||||
## Let's look at an example.
|
||||
## ```
|
||||
## ```roc
|
||||
## ratings = [5, 4, 3]
|
||||
##
|
||||
## { foo: ratings, bar: ratings }
|
||||
|
|
@ -125,7 +125,7 @@ interface List
|
|||
## refcount getting incremented from 1 to 3.
|
||||
##
|
||||
## Let's turn this example into a function.
|
||||
## ```
|
||||
## ```roc
|
||||
## getRatings = \first ->
|
||||
## ratings = [first, 4, 3]
|
||||
##
|
||||
|
|
@ -147,7 +147,7 @@ interface List
|
|||
## list, and that list has a refcount of 2.
|
||||
##
|
||||
## Let's change the last line to be `(getRatings 5).bar` instead of `getRatings 5`:
|
||||
## ```
|
||||
## ```roc
|
||||
## getRatings = \first ->
|
||||
## ratings = [first, 4, 3]
|
||||
##
|
||||
|
|
@ -161,7 +161,7 @@ interface List
|
|||
## where it started: there is only 1 reference to it.
|
||||
##
|
||||
## Finally let's suppose the final line were changed to this:
|
||||
## ```
|
||||
## ```roc
|
||||
## List.first (getRatings 5).bar
|
||||
## ```
|
||||
## This call to [List.first] means that even the list in the `bar` field has become
|
||||
|
|
@ -174,7 +174,7 @@ interface List
|
|||
## and then with a list of lists, to see how they differ.
|
||||
##
|
||||
## Here's the example using a list of numbers.
|
||||
## ```
|
||||
## ```roc
|
||||
## nums = [1, 2, 3, 4, 5, 6, 7]
|
||||
##
|
||||
## first = List.first nums
|
||||
|
|
@ -185,7 +185,7 @@ interface List
|
|||
## It makes a list, calls [List.first] and [List.last] on it, and then returns `first`.
|
||||
##
|
||||
## Here's the equivalent code with a list of lists:
|
||||
## ```
|
||||
## ```roc
|
||||
## lists = [[1], [2, 3], [], [4, 5, 6, 7]]
|
||||
##
|
||||
## first = List.first lists
|
||||
|
|
@ -216,7 +216,7 @@ interface List
|
|||
# separator so List.isEmpty doesn't absorb the above into its doc comment
|
||||
|
||||
## Check if the list is empty.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.isEmpty [1, 2, 3]
|
||||
##
|
||||
## List.isEmpty []
|
||||
|
|
@ -232,7 +232,7 @@ getUnsafe : List a, U64 -> a
|
|||
## Returns an element from a list at the given index.
|
||||
##
|
||||
## Returns `Err OutOfBounds` if the given index exceeds the List's length
|
||||
## ```
|
||||
## ```roc
|
||||
## expect List.get [100, 200, 300] 1 == Ok 200
|
||||
## expect List.get [100, 200, 300] 5 == Err OutOfBounds
|
||||
## ```
|
||||
|
|
@ -255,7 +255,7 @@ replace = \list, index, newValue ->
|
|||
{ list, value: newValue }
|
||||
|
||||
## Replaces the element at the given index with a replacement.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.set ["a", "b", "c"] 1 "B"
|
||||
## ```
|
||||
## If the given index is outside the bounds of the list, returns the original
|
||||
|
|
@ -267,7 +267,7 @@ set = \list, index, value ->
|
|||
(List.replace list index value).list
|
||||
|
||||
## Updates the element at the given index with the given function.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.update [1, 2, 3] 1 (\x -> x + 1)
|
||||
## ```
|
||||
## If the given index is outside the bounds of the list, returns the original
|
||||
|
|
@ -311,7 +311,7 @@ expect
|
|||
got == want
|
||||
|
||||
## Add a single element to the end of a list.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.append [1, 2, 3] 4
|
||||
##
|
||||
## [0, 1, 2]
|
||||
|
|
@ -326,7 +326,7 @@ append = \list, element ->
|
|||
## If the given [Result] is `Ok`, add it to the end of a list.
|
||||
## Otherwise, return the list unmodified.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## List.appendIfOk [1, 2, 3] (Ok 4)
|
||||
##
|
||||
## [0, 1, 2]
|
||||
|
|
@ -346,7 +346,7 @@ appendIfOk = \list, result ->
|
|||
appendUnsafe : List a, a -> List a
|
||||
|
||||
## Add a single element to the beginning of a list.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.prepend [1, 2, 3] 0
|
||||
##
|
||||
## [2, 3, 4]
|
||||
|
|
@ -357,7 +357,7 @@ prepend : List a, a -> List a
|
|||
## If the given [Result] is `Ok`, add it to the beginning of a list.
|
||||
## Otherwise, return the list unmodified.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## List.prepend [1, 2, 3] (Ok 0)
|
||||
##
|
||||
## [2, 3, 4]
|
||||
|
|
@ -387,7 +387,7 @@ reserve : List a, U64 -> List a
|
|||
releaseExcessCapacity : List a -> List a
|
||||
|
||||
## Put two lists together.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.concat [1, 2, 3] [4, 5]
|
||||
##
|
||||
## [0, 1, 2]
|
||||
|
|
@ -396,7 +396,7 @@ releaseExcessCapacity : List a -> List a
|
|||
concat : List a, List a -> List a
|
||||
|
||||
## Returns the last element in the list, or `ListWasEmpty` if it was empty.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect List.last [1, 2, 3] == Ok 3
|
||||
## expect List.last [] == Err ListWasEmpty
|
||||
## ```
|
||||
|
|
@ -409,7 +409,7 @@ last = \list ->
|
|||
## A list with a single element in it.
|
||||
##
|
||||
## This is useful in pipelines, like so:
|
||||
## ```
|
||||
## ```roc
|
||||
## websites =
|
||||
## Str.concat domain ".com"
|
||||
## |> List.single
|
||||
|
|
@ -430,7 +430,7 @@ repeatHelp = \value, count, accum ->
|
|||
accum
|
||||
|
||||
## Returns the list with its elements reversed.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect List.reverse [1, 2, 3] == [3, 2, 1]
|
||||
## ```
|
||||
reverse : List a -> List a
|
||||
|
|
@ -448,7 +448,7 @@ reverseHelp = \list, left, right ->
|
|||
clone : List a -> List a
|
||||
|
||||
## Join the given lists together into one list.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect List.join [[1], [2, 3], [], [4, 5]] == [1, 2, 3, 4, 5]
|
||||
## expect List.join [[], []] == []
|
||||
## expect List.join [] == []
|
||||
|
|
@ -471,7 +471,7 @@ contains = \list, needle ->
|
|||
## which updates the `state`. It returns the final `state` at the end.
|
||||
##
|
||||
## You can use it in a pipeline:
|
||||
## ```
|
||||
## ```roc
|
||||
## [2, 4, 8]
|
||||
## |> List.walk 0 Num.add
|
||||
## ```
|
||||
|
|
@ -490,7 +490,7 @@ contains = \list, needle ->
|
|||
## 6 | 8 | 14
|
||||
##
|
||||
## The following returns -6:
|
||||
## ```
|
||||
## ```roc
|
||||
## [1, 2, 3]
|
||||
## |> List.walk 0 Num.sub
|
||||
## ```
|
||||
|
|
@ -639,7 +639,7 @@ all = \list, predicate ->
|
|||
|
||||
## Run the given function on each element of a list, and return all the
|
||||
## elements for which the function returned `Bool.true`.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.keepIf [1, 2, 3, 4] (\num -> num > 2)
|
||||
## ```
|
||||
## ## Performance Details
|
||||
|
|
@ -676,7 +676,7 @@ keepIfHelp = \list, predicate, kept, index, length ->
|
|||
|
||||
## Run the given function on each element of a list, and return all the
|
||||
## elements for which the function returned `Bool.false`.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.dropIf [1, 2, 3, 4] (\num -> num > 2)
|
||||
## ```
|
||||
## ## Performance Details
|
||||
|
|
@ -689,7 +689,7 @@ dropIf = \list, predicate ->
|
|||
|
||||
## Run the given function on each element of a list, and return the
|
||||
## number of elements for which the function returned `Bool.true`.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect List.countIf [1, -2, -3] Num.isNegative == 2
|
||||
## expect List.countIf [1, 2, 3] (\num -> num > 1 ) == 2
|
||||
## ```
|
||||
|
|
@ -705,7 +705,7 @@ countIf = \list, predicate ->
|
|||
|
||||
## This works like [List.map], except only the transformed values that are
|
||||
## wrapped in `Ok` are kept. Any that are wrapped in `Err` are dropped.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect List.keepOks ["1", "Two", "23", "Bird"] Str.toI32 == [1, 23]
|
||||
##
|
||||
## expect List.keepOks [["a", "b"], [], ["c", "d", "e"], [] ] List.first == ["a", "c"]
|
||||
|
|
@ -724,7 +724,7 @@ keepOks = \list, toResult ->
|
|||
|
||||
## This works like [List.map], except only the transformed values that are
|
||||
## wrapped in `Err` are kept. Any that are wrapped in `Ok` are dropped.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.keepErrs [["a", "b"], [], [], ["c", "d", "e"]] List.last
|
||||
##
|
||||
## fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
|
||||
|
|
@ -742,7 +742,7 @@ keepErrs = \list, toResult ->
|
|||
|
||||
## Convert each element in the list to something new, by calling a conversion
|
||||
## function on each of them. Then return a new list of the converted values.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect List.map [1, 2, 3] (\num -> num + 1) == [2, 3, 4]
|
||||
##
|
||||
## expect List.map ["", "a", "bc"] Str.isEmpty == [Bool.true, Bool.false, Bool.false]
|
||||
|
|
@ -755,7 +755,7 @@ map : List a, (a -> b) -> List b
|
|||
##
|
||||
## Some languages have a function named `zip`, which does something similar to
|
||||
## calling [List.map2] passing two lists and `Pair`:
|
||||
## ```
|
||||
## ```roc
|
||||
## zipped = List.map2 ["a", "b", "c"] [1, 2, 3] Pair
|
||||
## ```
|
||||
map2 : List a, List b, (a, b -> c) -> List c
|
||||
|
|
@ -772,7 +772,7 @@ map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
|
|||
|
||||
## This works like [List.map], except it also passes the index
|
||||
## of the element to the conversion function.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect List.mapWithIndex [10, 20, 30] (\num, index -> num + index) == [10, 21, 32]
|
||||
## ```
|
||||
mapWithIndex : List a, (a, U64 -> b) -> List b
|
||||
|
|
@ -797,23 +797,23 @@ mapWithIndexHelp = \src, dest, func, index, length ->
|
|||
## Returns a list of all the integers between `start` and `end`.
|
||||
##
|
||||
## To include the `start` and `end` integers themselves, use `At` like so:
|
||||
## ```
|
||||
## ```roc
|
||||
## List.range { start: At 2, end: At 5 } # returns [2, 3, 4, 5]
|
||||
## ```
|
||||
## To exclude them, use `After` and `Before`, like so:
|
||||
## ```
|
||||
## ```roc
|
||||
## List.range { start: After 2, end: Before 5 } # returns [3, 4]
|
||||
## ```
|
||||
## You can have the list end at a certain length rather than a certain integer:
|
||||
## ```
|
||||
## ```roc
|
||||
## List.range { start: At 6, end: Length 4 } # returns [6, 7, 8, 9]
|
||||
## ```
|
||||
## If `step` is specified, each integer increases by that much. (`step: 1` is the default.)
|
||||
## ```
|
||||
## ```roc
|
||||
## List.range { start: After 0, end: Before 9, step: 3 } # returns [3, 6]
|
||||
## ```
|
||||
## List.range will also generate a reversed list if step is negative or end comes before start:
|
||||
## ```
|
||||
## ```roc
|
||||
## List.range { start: At 5, end: At 2 } # returns [5, 4, 3, 2]
|
||||
## ```
|
||||
## All of these options are compatible with the others. For example, you can use `At` or `After`
|
||||
|
|
@ -966,12 +966,12 @@ first = \list ->
|
|||
Err _ -> Err ListWasEmpty
|
||||
|
||||
## Returns the given number of elements from the beginning of the list.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.takeFirst [1, 2, 3, 4, 5, 6, 7, 8] 4
|
||||
## ```
|
||||
## If there are fewer elements in the list than the requested number,
|
||||
## returns the entire list.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.takeFirst [1, 2] 5
|
||||
## ```
|
||||
## To *remove* elements from the beginning of the list, use `List.takeLast`.
|
||||
|
|
@ -986,12 +986,12 @@ takeFirst = \list, outputLength ->
|
|||
List.sublist list { start: 0, len: outputLength }
|
||||
|
||||
## Returns the given number of elements from the end of the list.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.takeLast [1, 2, 3, 4, 5, 6, 7, 8] 4
|
||||
## ```
|
||||
## If there are fewer elements in the list than the requested number,
|
||||
## returns the entire list.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.takeLast [1, 2] 5
|
||||
## ```
|
||||
## To *remove* elements from the end of the list, use `List.takeFirst`.
|
||||
|
|
@ -1132,11 +1132,11 @@ findLastIndex = \list, matches ->
|
|||
## including a total of `len` elements.
|
||||
##
|
||||
## If `start` is outside the bounds of the given list, returns the empty list.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.sublist [1, 2, 3] { start: 4, len: 0 }
|
||||
## ```
|
||||
## If more elements are requested than exist in the list, returns as many as it can.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.sublist [1, 2, 3, 4, 5] { start: 2, len: 10 }
|
||||
## ```
|
||||
## > If you want a sublist which goes all the way to the end of the list, no
|
||||
|
|
@ -1151,7 +1151,7 @@ sublist = \list, config ->
|
|||
sublistLowlevel : List elem, U64, U64 -> List elem
|
||||
|
||||
## Intersperses `sep` between the elements of `list`
|
||||
## ```
|
||||
## ```roc
|
||||
## List.intersperse [1, 2, 3] 9 # [1, 9, 2, 9, 3]
|
||||
## ```
|
||||
intersperse : List elem, elem -> List elem
|
||||
|
|
@ -1211,7 +1211,7 @@ split = \elements, userSplitIndex ->
|
|||
|
||||
## Returns the elements before the first occurrence of a delimiter, as well as the
|
||||
## remaining elements after that occurrence. If the delimiter is not found, returns `Err`.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.splitFirst [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo], after: [Bar, Z, Baz] }
|
||||
## ```
|
||||
splitFirst : List elem, elem -> Result { before : List elem, after : List elem } [NotFound] where elem implements Eq
|
||||
|
|
@ -1227,7 +1227,7 @@ splitFirst = \list, delimiter ->
|
|||
|
||||
## Returns the elements before the last occurrence of a delimiter, as well as the
|
||||
## remaining elements after that occurrence. If the delimiter is not found, returns `Err`.
|
||||
## ```
|
||||
## ```roc
|
||||
## List.splitLast [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo, Z, Bar], after: [Baz] }
|
||||
## ```
|
||||
splitLast : List elem, elem -> Result { before : List elem, after : List elem } [NotFound] where elem implements Eq
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ interface Num
|
|||
## Represents a number that could be either an [Int] or a [Frac].
|
||||
##
|
||||
## This is useful for functions that can work on either, for example [Num.add], whose type is:
|
||||
## ```
|
||||
## ```roc
|
||||
## add : Num a, Num a -> Num a
|
||||
## ```
|
||||
## The number 1.5 technically has the type `Num (Fraction *)`, so when you pass
|
||||
|
|
@ -199,7 +199,7 @@ interface Num
|
|||
##
|
||||
## If this default of [I64] is not big enough for your purposes,
|
||||
## you can add an `i128` to the end of the number literal, like so:
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.toStr 5_000_000_000i128
|
||||
## ```
|
||||
## This `i128` suffix specifies that you want this number literal to be
|
||||
|
|
@ -268,7 +268,7 @@ Num range := range
|
|||
##
|
||||
## You can optionally put underscores in your [Int] literals.
|
||||
## They have no effect on the number's value, but can make large numbers easier to read.
|
||||
## ```
|
||||
## ```roc
|
||||
## 1_000_000
|
||||
## ```
|
||||
## Integers come in two flavors: *signed* and *unsigned*.
|
||||
|
|
@ -335,14 +335,14 @@ Int range : Num (Integer range)
|
|||
##
|
||||
## If you don't specify a type, Roc will default to using [Dec] because it's
|
||||
## the least error-prone overall. For example, suppose you write this:
|
||||
## ```
|
||||
## ```roc
|
||||
## wasItPrecise = 0.1 + 0.2 == 0.3
|
||||
## ```
|
||||
## The value of `wasItPrecise` here will be `Bool.true`, because Roc uses [Dec]
|
||||
## by default when there are no types specified.
|
||||
##
|
||||
## In contrast, suppose we use `f32` or `f64` for one of these numbers:
|
||||
## ```
|
||||
## ```roc
|
||||
## wasItPrecise = 0.1f64 + 0.2 == 0.3
|
||||
## ```
|
||||
## Here, `wasItPrecise` will be `Bool.false` because the entire calculation will have
|
||||
|
|
@ -528,11 +528,11 @@ tau = 2 * pi
|
|||
# ------- Functions
|
||||
## Convert a number to a [Str].
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.toStr 42
|
||||
## ```
|
||||
## Only [Frac] values will include a decimal point, and they will always include one.
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.toStr 4.2
|
||||
## Num.toStr 4.0
|
||||
## ```
|
||||
|
|
@ -550,7 +550,7 @@ compare : Num a, Num a -> [LT, EQ, GT]
|
|||
##
|
||||
## If either argument is [*NaN*](Num.isNaN), returns `Bool.false` no matter what. (*NaN*
|
||||
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
|
||||
## ```
|
||||
## ```roc
|
||||
## 5
|
||||
## |> Num.isLt 6
|
||||
## ```
|
||||
|
|
@ -562,7 +562,7 @@ isLt : Num a, Num a -> Bool
|
|||
##
|
||||
## If either argument is [*NaN*](Num.isNaN), returns `Bool.false` no matter what. (*NaN*
|
||||
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
|
||||
## ```
|
||||
## ```roc
|
||||
## 6
|
||||
## |> Num.isGt 5
|
||||
## ```
|
||||
|
|
@ -625,14 +625,14 @@ toFrac : Num * -> Frac *
|
|||
|
||||
## Returns `Bool.true` if the [Frac] is not a number as defined by [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754)
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.isNaN (0 / 0)
|
||||
## ```
|
||||
isNaN : Frac * -> Bool
|
||||
|
||||
## Returns `Bool.true` if the [Frac] is positive or negative infinity as defined by [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754)
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.isInfinite (1 / 0)
|
||||
##
|
||||
## Num.isInfinite (-1 / 0)
|
||||
|
|
@ -641,7 +641,7 @@ isInfinite : Frac * -> Bool
|
|||
|
||||
## Returns `Bool.true` if the [Frac] is not an infinity as defined by [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754)
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.isFinite 42
|
||||
## ```
|
||||
isFinite : Frac * -> Bool
|
||||
|
|
@ -651,7 +651,7 @@ isFinite : Frac * -> Bool
|
|||
## * For a positive number, returns the same number.
|
||||
## * For a negative number, returns the same number except positive.
|
||||
## * For zero, returns zero.
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.abs 4
|
||||
##
|
||||
## Num.abs -2.5
|
||||
|
|
@ -671,7 +671,7 @@ abs : Num a -> Num a
|
|||
|
||||
## Returns the absolute difference between two numbers.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.absDiff 5 3
|
||||
##
|
||||
## Num.absDiff -3 5
|
||||
|
|
@ -691,7 +691,7 @@ absDiff = \a, b ->
|
|||
b - a
|
||||
|
||||
## Returns a negative number when given a positive one, and vice versa.
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.neg 5
|
||||
##
|
||||
## Num.neg -2.5
|
||||
|
|
@ -716,13 +716,13 @@ neg : Num a -> Num a
|
|||
## (To add an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.)
|
||||
##
|
||||
## `a + b` is shorthand for `Num.add a b`.
|
||||
## ```
|
||||
## ```roc
|
||||
## 5 + 7
|
||||
##
|
||||
## Num.add 5 7
|
||||
## ```
|
||||
## `Num.add` can be convenient in pipelines.
|
||||
## ```
|
||||
## ```roc
|
||||
## Frac.pi
|
||||
## |> Num.add 1.0
|
||||
## ```
|
||||
|
|
@ -737,13 +737,13 @@ add : Num a, Num a -> Num a
|
|||
## (To subtract an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.)
|
||||
##
|
||||
## `a - b` is shorthand for `Num.sub a b`.
|
||||
## ```
|
||||
## ```roc
|
||||
## 7 - 5
|
||||
##
|
||||
## Num.sub 7 5
|
||||
## ```
|
||||
## `Num.sub` can be convenient in pipelines.
|
||||
## ```
|
||||
## ```roc
|
||||
## Frac.pi
|
||||
## |> Num.sub 2.0
|
||||
## ```
|
||||
|
|
@ -758,7 +758,7 @@ sub : Num a, Num a -> Num a
|
|||
## (To multiply an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.)
|
||||
##
|
||||
## `a * b` is shorthand for `Num.mul a b`.
|
||||
## ```
|
||||
## ```roc
|
||||
## 5 * 7
|
||||
##
|
||||
## Num.mul 5 7
|
||||
|
|
@ -766,7 +766,7 @@ sub : Num a, Num a -> Num a
|
|||
##
|
||||
## `Num.mul` can be convenient in pipelines.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Frac.pi
|
||||
## |> Num.mul 2.0
|
||||
## ```
|
||||
|
|
@ -778,7 +778,7 @@ mul : Num a, Num a -> Num a
|
|||
|
||||
## Obtains the smaller between two numbers of the same type.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.min 100 0
|
||||
##
|
||||
## Num.min 3.0 -3.0
|
||||
|
|
@ -792,7 +792,7 @@ min = \a, b ->
|
|||
|
||||
## Obtains the greater between two numbers of the same type.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.max 100 0
|
||||
##
|
||||
## Num.max 3.0 -3.0
|
||||
|
|
@ -828,7 +828,7 @@ atan : Frac a -> Frac a
|
|||
## > this standard, deviating from these rules has a significant performance
|
||||
## > cost! Since the most common reason to choose [F64] or [F32] over [Dec] is
|
||||
## > access to hardware-accelerated performance, Roc follows these rules exactly.
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.sqrt 4.0
|
||||
##
|
||||
## Num.sqrt 1.5
|
||||
|
|
@ -877,13 +877,13 @@ logChecked = \x ->
|
|||
##
|
||||
## To divide an [Int] and a [Frac], first convert the [Int] to a [Frac] using
|
||||
## one of the functions in this module like #toDec.
|
||||
## ```
|
||||
## ```roc
|
||||
## 5.0 / 7.0
|
||||
##
|
||||
## Num.div 5 7
|
||||
## ```
|
||||
## `Num.div` can be convenient in pipelines.
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.pi
|
||||
## |> Num.div 2.0
|
||||
## ```
|
||||
|
|
@ -912,7 +912,7 @@ divCeilChecked = \a, b ->
|
|||
## Division by zero is undefined in mathematics. As such, you should make
|
||||
## sure never to pass zero as the denominator to this function! If you do,
|
||||
## it will crash.
|
||||
## ```
|
||||
## ```roc
|
||||
## 5 // 7
|
||||
##
|
||||
## Num.divTrunc 5 7
|
||||
|
|
@ -941,7 +941,7 @@ divTruncUnchecked : Int a, Int a -> Int a
|
|||
## Obtains the remainder (truncating modulo) from the division of two integers.
|
||||
##
|
||||
## `a % b` is shorthand for `Num.rem a b`.
|
||||
## ```
|
||||
## ```roc
|
||||
## 5 % 7
|
||||
##
|
||||
## Num.rem 5 7
|
||||
|
|
@ -992,7 +992,7 @@ bitwiseNot = \n ->
|
|||
##
|
||||
## The least significant bits always become 0. This means that shifting left is
|
||||
## like multiplying by factors of two for unsigned integers.
|
||||
## ```
|
||||
## ```roc
|
||||
## shiftLeftBy 0b0000_0011 2 == 0b0000_1100
|
||||
##
|
||||
## 0b0000_0101 |> shiftLeftBy 2 == 0b0000_1100
|
||||
|
|
@ -1003,7 +1003,7 @@ shiftLeftBy : Int a, U8 -> Int a
|
|||
## Bitwise arithmetic shift of a number by another
|
||||
##
|
||||
## The most significant bits are copied from the current.
|
||||
## ```
|
||||
## ```roc
|
||||
## shiftRightBy 0b0000_1100 2 == 0b0000_0011
|
||||
##
|
||||
## 0b0001_0100 |> shiftRightBy 2 == 0b0000_0101
|
||||
|
|
@ -1017,7 +1017,7 @@ shiftRightBy : Int a, U8 -> Int a
|
|||
##
|
||||
## The most significant bits always become 0. This means that shifting right is
|
||||
## like dividing by factors of two for unsigned integers.
|
||||
## ```
|
||||
## ```roc
|
||||
## shiftRightZfBy 0b0010_1000 2 == 0b0000_1010
|
||||
##
|
||||
## 0b0010_1000 |> shiftRightZfBy 2 == 0b0000_1010
|
||||
|
|
@ -1053,7 +1053,7 @@ powInt : Int a, Int a -> Int a
|
|||
|
||||
## Counts the number of most-significant (leading in a big-Endian sense) zeroes in an integer.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.countLeadingZeroBits 0b0001_1100u8
|
||||
##
|
||||
## 3
|
||||
|
|
@ -1066,7 +1066,7 @@ countLeadingZeroBits : Int a -> U8
|
|||
|
||||
## Counts the number of least-significant (trailing in a big-Endian sense) zeroes in an integer.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.countTrailingZeroBits 0b0001_1100u8
|
||||
##
|
||||
## 2
|
||||
|
|
@ -1079,7 +1079,7 @@ countTrailingZeroBits : Int a -> U8
|
|||
|
||||
## Counts the number of set bits in an integer.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## Num.countOneBits 0b0001_1100u8
|
||||
##
|
||||
## 3
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ interface Result
|
|||
Result ok err : [Ok ok, Err err]
|
||||
|
||||
## Returns `Bool.true` if the result indicates a success, else returns `Bool.false`
|
||||
## ```
|
||||
## ```roc
|
||||
## Result.isOk (Ok 5)
|
||||
## ```
|
||||
isOk : Result ok err -> Bool
|
||||
|
|
@ -17,7 +17,7 @@ isOk = \result ->
|
|||
Err _ -> Bool.false
|
||||
|
||||
## Returns `Bool.true` if the result indicates a failure, else returns `Bool.false`
|
||||
## ```
|
||||
## ```roc
|
||||
## Result.isErr (Err "uh oh")
|
||||
## ```
|
||||
isErr : Result ok err -> Bool
|
||||
|
|
@ -28,7 +28,7 @@ isErr = \result ->
|
|||
|
||||
## If the result is `Ok`, returns the value it holds. Otherwise, returns
|
||||
## the given default value.
|
||||
## ```
|
||||
## ```roc
|
||||
## Result.withDefault (Ok 7) 42
|
||||
## Result.withDefault (Err "uh oh") 42
|
||||
## ```
|
||||
|
|
@ -41,7 +41,7 @@ withDefault = \result, default ->
|
|||
## If the result is `Ok`, transforms the value it holds by running a conversion
|
||||
## function on it. Then returns a new `Ok` holding the transformed value. If the
|
||||
## result is `Err`, this has no effect. Use [mapErr] to transform an `Err`.
|
||||
## ```
|
||||
## ```roc
|
||||
## Result.map (Ok 12) Num.neg
|
||||
## Result.map (Err "yipes!") Num.neg
|
||||
## ```
|
||||
|
|
@ -57,7 +57,7 @@ map = \result, transform ->
|
|||
## If the result is `Err`, transforms the value it holds by running a conversion
|
||||
## function on it. Then returns a new `Err` holding the transformed value. If
|
||||
## the result is `Ok`, this has no effect. Use [map] to transform an `Ok`.
|
||||
## ```
|
||||
## ```roc
|
||||
## Result.mapErr (Err "yipes!") Str.isEmpty
|
||||
## Result.mapErr (Ok 12) Str.isEmpty
|
||||
## ```
|
||||
|
|
@ -70,7 +70,7 @@ mapErr = \result, transform ->
|
|||
## If the result is `Ok`, transforms the entire result by running a conversion
|
||||
## function on the value the `Ok` holds. Then returns that new result. If the
|
||||
## result is `Err`, this has no effect. Use `onErr` to transform an `Err`.
|
||||
## ```
|
||||
## ```roc
|
||||
## Result.try (Ok -1) \num -> if num < 0 then Err "negative!" else Ok -num
|
||||
## Result.try (Err "yipes!") \num -> if num < 0 then Err "negative!" else Ok -num
|
||||
## ```
|
||||
|
|
@ -83,7 +83,7 @@ try = \result, transform ->
|
|||
## If the result is `Err`, transforms the entire result by running a conversion
|
||||
## function on the value the `Err` holds. Then returns that new result. If the
|
||||
## result is `Ok`, this has no effect. Use `try` to transform an `Ok`.
|
||||
## ```
|
||||
## ```roc
|
||||
## Result.onErr (Ok 10) \errorNum -> Str.toU64 errorNum
|
||||
## Result.onErr (Err "42") \errorNum -> Str.toU64 errorNum
|
||||
## ```
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ toInspectorSet = \set ->
|
|||
Inspect.apply (Inspect.set set walk Inspect.toInspector) fmt
|
||||
|
||||
## Creates a new empty `Set`.
|
||||
## ```
|
||||
## ```roc
|
||||
## emptySet = Set.empty {}
|
||||
## countValues = Set.len emptySet
|
||||
##
|
||||
|
|
@ -97,7 +97,7 @@ releaseExcessCapacity = \@Set dict ->
|
|||
@Set (Dict.releaseExcessCapacity dict)
|
||||
|
||||
## Creates a new `Set` with a single value.
|
||||
## ```
|
||||
## ```roc
|
||||
## singleItemSet = Set.single "Apple"
|
||||
## countValues = Set.len singleItemSet
|
||||
##
|
||||
|
|
@ -108,7 +108,7 @@ single = \key ->
|
|||
Dict.single key {} |> @Set
|
||||
|
||||
## Insert a value into a `Set`.
|
||||
## ```
|
||||
## ```roc
|
||||
## fewItemSet =
|
||||
## Set.empty {}
|
||||
## |> Set.insert "Apple"
|
||||
|
|
@ -141,7 +141,7 @@ expect
|
|||
expected == actual
|
||||
|
||||
## Counts the number of values in a given `Set`.
|
||||
## ```
|
||||
## ```roc
|
||||
## fewItemSet =
|
||||
## Set.empty {}
|
||||
## |> Set.insert "Apple"
|
||||
|
|
@ -157,7 +157,7 @@ len = \@Set dict ->
|
|||
Dict.len dict
|
||||
|
||||
## Returns the max number of elements the set can hold before requiring a rehash.
|
||||
## ```
|
||||
## ```roc
|
||||
## foodSet =
|
||||
## Set.empty {}
|
||||
## |> Set.insert "apple"
|
||||
|
|
@ -169,7 +169,7 @@ capacity = \@Set dict ->
|
|||
Dict.capacity dict
|
||||
|
||||
## Check if the set is empty.
|
||||
## ```
|
||||
## ```roc
|
||||
## Set.isEmpty (Set.empty {} |> Set.insert 42)
|
||||
##
|
||||
## Set.isEmpty (Set.empty {})
|
||||
|
|
@ -191,7 +191,7 @@ expect
|
|||
actual == 3
|
||||
|
||||
## Removes the value from the given `Set`.
|
||||
## ```
|
||||
## ```roc
|
||||
## numbers =
|
||||
## Set.empty {}
|
||||
## |> Set.insert 10
|
||||
|
|
@ -209,7 +209,7 @@ remove = \@Set dict, key ->
|
|||
Dict.remove dict key |> @Set
|
||||
|
||||
## Test if a value is in the `Set`.
|
||||
## ```
|
||||
## ```roc
|
||||
## Fruit : [Apple, Pear, Banana]
|
||||
##
|
||||
## fruit : Set Fruit
|
||||
|
|
@ -228,7 +228,7 @@ contains = \@Set dict, key ->
|
|||
Dict.contains dict key
|
||||
|
||||
## Retrieve the values in a `Set` as a `List`.
|
||||
## ```
|
||||
## ```roc
|
||||
## numbers : Set U64
|
||||
## numbers = Set.fromList [1,2,3,4,5]
|
||||
##
|
||||
|
|
@ -241,7 +241,7 @@ toList = \@Set dict ->
|
|||
Dict.keys dict
|
||||
|
||||
## Create a `Set` from a `List` of values.
|
||||
## ```
|
||||
## ```roc
|
||||
## values =
|
||||
## Set.empty {}
|
||||
## |> Set.insert Banana
|
||||
|
|
@ -261,7 +261,7 @@ fromList = \list ->
|
|||
## [union](https://en.wikipedia.org/wiki/Union_(set_theory))
|
||||
## of all the values pairs. This means that all of the values in both `Set`s
|
||||
## will be combined.
|
||||
## ```
|
||||
## ```roc
|
||||
## set1 = Set.single Left
|
||||
## set2 = Set.single Right
|
||||
##
|
||||
|
|
@ -274,7 +274,7 @@ union = \@Set dict1, @Set dict2 ->
|
|||
## Combine two `Set`s by keeping the [intersection](https://en.wikipedia.org/wiki/Intersection_(set_theory))
|
||||
## of all the values pairs. This means that we keep only those values that are
|
||||
## in both `Set`s.
|
||||
## ```
|
||||
## ```roc
|
||||
## set1 = Set.fromList [Left, Other]
|
||||
## set2 = Set.fromList [Left, Right]
|
||||
##
|
||||
|
|
@ -288,7 +288,7 @@ intersection = \@Set dict1, @Set dict2 ->
|
|||
## using the [set difference](https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement)
|
||||
## of the values. This means that we will be left with only those values that
|
||||
## are in the first and not in the second.
|
||||
## ```
|
||||
## ```roc
|
||||
## first = Set.fromList [Left, Right, Up, Down]
|
||||
## second = Set.fromList [Left, Right]
|
||||
##
|
||||
|
|
@ -299,7 +299,7 @@ difference = \@Set dict1, @Set dict2 ->
|
|||
Dict.removeAll dict1 dict2 |> @Set
|
||||
|
||||
## Iterate through the values of a given `Set` and build a value.
|
||||
## ```
|
||||
## ```roc
|
||||
## values = Set.fromList ["March", "April", "May"]
|
||||
##
|
||||
## startsWithLetterM = \month ->
|
||||
|
|
@ -345,7 +345,7 @@ joinMap = \set, transform ->
|
|||
|
||||
## Iterate through the values of a given `Set` and build a value, can stop
|
||||
## iterating part way through the collection.
|
||||
## ```
|
||||
## ```roc
|
||||
## numbers = Set.fromList [1,2,3,4,5,6,42,7,8,9,10]
|
||||
##
|
||||
## find42 = \state, k ->
|
||||
|
|
@ -364,7 +364,7 @@ walkUntil = \@Set dict, state, step ->
|
|||
|
||||
## Run the given function on each element in the `Set`, and return
|
||||
## a `Set` with just the elements for which the function returned `Bool.true`.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Set.fromList [1,2,3,4,5]
|
||||
## |> Set.keepIf \k -> k >= 3
|
||||
## |> Bool.isEq (Set.fromList [3,4,5])
|
||||
|
|
@ -375,7 +375,7 @@ keepIf = \@Set dict, predicate ->
|
|||
|
||||
## Run the given function on each element in the `Set`, and return
|
||||
## a `Set` with just the elements for which the function returned `Bool.false`.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Set.fromList [1,2,3,4,5]
|
||||
## |> Set.dropIf \k -> k >= 3
|
||||
## |> Bool.isEq (Set.fromList [1,2])
|
||||
|
|
|
|||
|
|
@ -388,14 +388,14 @@ Utf8ByteProblem : [
|
|||
Utf8Problem : { byteIndex : U64, problem : Utf8ByteProblem }
|
||||
|
||||
## Returns [Bool.true] if the string is empty, and [Bool.false] otherwise.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.isEmpty "hi!" == Bool.false
|
||||
## expect Str.isEmpty "" == Bool.true
|
||||
## ```
|
||||
isEmpty : Str -> Bool
|
||||
|
||||
## Concatenates two strings together.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.concat "ab" "cd" == "abcd"
|
||||
## expect Str.concat "hello" "" == "hello"
|
||||
## expect Str.concat "" "" == ""
|
||||
|
|
@ -407,7 +407,7 @@ concat : Str, Str -> Str
|
|||
## This is a performance optimization tool that's like calling [Str.reserve] on an empty string.
|
||||
## It's useful when you plan to build up a string incrementally, for example by calling [Str.concat] on it:
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## greeting = "Hello and welcome to Roc"
|
||||
## subject = "Awesome Programmer"
|
||||
##
|
||||
|
|
@ -434,7 +434,7 @@ withCapacity : U64 -> Str
|
|||
## allocating extra capacity up front, which can prevent the need for reallocations and copies.
|
||||
## Consider the following example which does not use [Str.reserve]:
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## greeting = "Hello and welcome to Roc"
|
||||
## subject = "Awesome Programmer"
|
||||
##
|
||||
|
|
@ -456,7 +456,7 @@ withCapacity : U64 -> Str
|
|||
##
|
||||
## Here's a modified example which uses [Str.reserve] to eliminate the need for all that reallocation, copying, and deallocation.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## helloWorld =
|
||||
## greeting
|
||||
## |> Str.reserve 21
|
||||
|
|
@ -488,7 +488,7 @@ reserve : Str, U64 -> Str
|
|||
|
||||
## Combines a [List] of strings into a single string, with a separator
|
||||
## string in between each.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.joinWith ["one", "two", "three"] ", " == "one, two, three"
|
||||
## expect Str.joinWith ["1", "2", "3", "4"] "." == "1.2.3.4"
|
||||
## ```
|
||||
|
|
@ -498,19 +498,19 @@ joinWith : List Str, Str -> Str
|
|||
##
|
||||
## Passing `""` for the separator is not useful;
|
||||
## it returns the original string wrapped in a [List].
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.split "1,2,3" "," == ["1","2","3"]
|
||||
## expect Str.split "1,2,3" "" == ["1,2,3"]
|
||||
## ```
|
||||
split : Str, Str -> List Str
|
||||
|
||||
## Repeats a string the given number of times.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.repeat "z" 3 == "zzz"
|
||||
## expect Str.repeat "na" 8 == "nananananananana"
|
||||
## ```
|
||||
## Returns `""` when given `""` for the string or `0` for the count.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.repeat "" 10 == ""
|
||||
## expect Str.repeat "anything" 0 == ""
|
||||
## ```
|
||||
|
|
@ -519,7 +519,7 @@ repeat : Str, U64 -> Str
|
|||
## Returns a [List] of the string's [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit).
|
||||
## (To split the string into a [List] of smaller [Str] values instead of [U8] values,
|
||||
## see [Str.split].)
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toUtf8 "Roc" == [82, 111, 99]
|
||||
## expect Str.toUtf8 "鹏" == [233, 185, 143]
|
||||
## expect Str.toUtf8 "சி" == [224, 174, 154, 224, 174, 191]
|
||||
|
|
@ -530,7 +530,7 @@ toUtf8 : Str -> List U8
|
|||
## Converts a [List] of [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit) to a string.
|
||||
##
|
||||
## Returns `Err` if the given bytes are invalid UTF-8, and returns `Ok ""` when given `[]`.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.fromUtf8 [82, 111, 99] == Ok "Roc"
|
||||
## expect Str.fromUtf8 [233, 185, 143] == Ok "鹏"
|
||||
## expect Str.fromUtf8 [224, 174, 154, 224, 174, 191] == Ok "சி"
|
||||
|
|
@ -563,14 +563,14 @@ FromUtf8Result : {
|
|||
fromUtf8Lowlevel : List U8 -> FromUtf8Result
|
||||
|
||||
## Check if the given [Str] starts with a value.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.startsWith "ABC" "A" == Bool.true
|
||||
## expect Str.startsWith "ABC" "X" == Bool.false
|
||||
## ```
|
||||
startsWith : Str, Str -> Bool
|
||||
|
||||
## Check if the given [Str] ends with a value.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.endsWith "ABC" "C" == Bool.true
|
||||
## expect Str.endsWith "ABC" "X" == Bool.false
|
||||
## ```
|
||||
|
|
@ -578,26 +578,26 @@ endsWith : Str, Str -> Bool
|
|||
|
||||
## Return the [Str] with all whitespace removed from both the beginning
|
||||
## as well as the end.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.trim " Hello \n\n" == "Hello"
|
||||
## ```
|
||||
trim : Str -> Str
|
||||
|
||||
## Return the [Str] with all whitespace removed from the beginning.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.trimStart " Hello \n\n" == "Hello \n\n"
|
||||
## ```
|
||||
trimStart : Str -> Str
|
||||
|
||||
## Return the [Str] with all whitespace removed from the end.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.trimEnd " Hello \n\n" == " Hello"
|
||||
## ```
|
||||
trimEnd : Str -> Str
|
||||
|
||||
## Encode a [Str] to a [Dec]. A [Dec] value is a 128-bit decimal
|
||||
## [fixed-point number](https://en.wikipedia.org/wiki/Fixed-point_arithmetic).
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toDec "10" == Ok 10dec
|
||||
## expect Str.toDec "-0.25" == Ok -0.25dec
|
||||
## expect Str.toDec "not a number" == Err InvalidNumStr
|
||||
|
|
@ -608,7 +608,7 @@ toDec = \string -> strToNumHelp string
|
|||
## Encode a [Str] to a [F64]. A [F64] value is a 64-bit
|
||||
## [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) and can be
|
||||
## specified with a `f64` suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toF64 "0.10" == Ok 0.10f64
|
||||
## expect Str.toF64 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
|
|
@ -618,7 +618,7 @@ toF64 = \string -> strToNumHelp string
|
|||
## Encode a [Str] to a [F32].A [F32] value is a 32-bit
|
||||
## [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) and can be
|
||||
## specified with a `f32` suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toF32 "0.10" == Ok 0.10f32
|
||||
## expect Str.toF32 "not a number" == Err InvalidNumStr
|
||||
## ```
|
||||
|
|
@ -628,7 +628,7 @@ toF32 = \string -> strToNumHelp string
|
|||
## Encode a [Str] to an unsigned [U128] integer. A [U128] value can hold numbers
|
||||
## from `0` to `340_282_366_920_938_463_463_374_607_431_768_211_455` (over
|
||||
## 340 undecillion). It can be specified with a u128 suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toU128 "1500" == Ok 1500u128
|
||||
## expect Str.toU128 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toU128 "-1" == Err InvalidNumStr
|
||||
|
|
@ -641,7 +641,7 @@ toU128 = \string -> strToNumHelp string
|
|||
## from `-170_141_183_460_469_231_731_687_303_715_884_105_728` to
|
||||
## `170_141_183_460_469_231_731_687_303_715_884_105_727`. It can be specified
|
||||
## with a i128 suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toI128 "1500" == Ok 1500i128
|
||||
## expect Str.toI128 "-1" == Ok -1i128
|
||||
## expect Str.toI128 "0.1" == Err InvalidNumStr
|
||||
|
|
@ -653,7 +653,7 @@ toI128 = \string -> strToNumHelp string
|
|||
## Encode a [Str] to an unsigned [U64] integer. A [U64] value can hold numbers
|
||||
## from `0` to `18_446_744_073_709_551_615` (over 18 quintillion). It
|
||||
## can be specified with a u64 suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toU64 "1500" == Ok 1500u64
|
||||
## expect Str.toU64 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toU64 "-1" == Err InvalidNumStr
|
||||
|
|
@ -665,7 +665,7 @@ toU64 = \string -> strToNumHelp string
|
|||
## Encode a [Str] to a signed [I64] integer. A [I64] value can hold numbers
|
||||
## from `-9_223_372_036_854_775_808` to `9_223_372_036_854_775_807`. It can be
|
||||
## specified with a i64 suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toI64 "1500" == Ok 1500i64
|
||||
## expect Str.toI64 "-1" == Ok -1i64
|
||||
## expect Str.toI64 "0.1" == Err InvalidNumStr
|
||||
|
|
@ -677,7 +677,7 @@ toI64 = \string -> strToNumHelp string
|
|||
## Encode a [Str] to an unsigned [U32] integer. A [U32] value can hold numbers
|
||||
## from `0` to `4_294_967_295` (over 4 billion). It can be specified with
|
||||
## a u32 suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toU32 "1500" == Ok 1500u32
|
||||
## expect Str.toU32 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toU32 "-1" == Err InvalidNumStr
|
||||
|
|
@ -689,7 +689,7 @@ toU32 = \string -> strToNumHelp string
|
|||
## Encode a [Str] to a signed [I32] integer. A [I32] value can hold numbers
|
||||
## from `-2_147_483_648` to `2_147_483_647`. It can be
|
||||
## specified with a i32 suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toI32 "1500" == Ok 1500i32
|
||||
## expect Str.toI32 "-1" == Ok -1i32
|
||||
## expect Str.toI32 "0.1" == Err InvalidNumStr
|
||||
|
|
@ -700,7 +700,7 @@ toI32 = \string -> strToNumHelp string
|
|||
|
||||
## Encode a [Str] to an unsigned [U16] integer. A [U16] value can hold numbers
|
||||
## from `0` to `65_535`. It can be specified with a u16 suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toU16 "1500" == Ok 1500u16
|
||||
## expect Str.toU16 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toU16 "-1" == Err InvalidNumStr
|
||||
|
|
@ -712,7 +712,7 @@ toU16 = \string -> strToNumHelp string
|
|||
## Encode a [Str] to a signed [I16] integer. A [I16] value can hold numbers
|
||||
## from `-32_768` to `32_767`. It can be
|
||||
## specified with a i16 suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toI16 "1500" == Ok 1500i16
|
||||
## expect Str.toI16 "-1" == Ok -1i16
|
||||
## expect Str.toI16 "0.1" == Err InvalidNumStr
|
||||
|
|
@ -723,7 +723,7 @@ toI16 = \string -> strToNumHelp string
|
|||
|
||||
## Encode a [Str] to an unsigned [U8] integer. A [U8] value can hold numbers
|
||||
## from `0` to `255`. It can be specified with a u8 suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toU8 "250" == Ok 250u8
|
||||
## expect Str.toU8 "-0.1" == Err InvalidNumStr
|
||||
## expect Str.toU8 "not a number" == Err InvalidNumStr
|
||||
|
|
@ -735,7 +735,7 @@ toU8 = \string -> strToNumHelp string
|
|||
## Encode a [Str] to a signed [I8] integer. A [I8] value can hold numbers
|
||||
## from `-128` to `127`. It can be
|
||||
## specified with a i8 suffix.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.toI8 "-15" == Ok -15i8
|
||||
## expect Str.toI8 "150.00" == Err InvalidNumStr
|
||||
## expect Str.toI8 "not a number" == Err InvalidNumStr
|
||||
|
|
@ -747,7 +747,7 @@ toI8 = \string -> strToNumHelp string
|
|||
getUnsafe : Str, U64 -> U8
|
||||
|
||||
## Gives the number of bytes in a [Str] value.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.countUtf8Bytes "Hello World" == 11
|
||||
## ```
|
||||
countUtf8Bytes : Str -> U64
|
||||
|
|
@ -758,7 +758,7 @@ substringUnsafe : Str, U64, U64 -> Str
|
|||
## Returns the given [Str] with each occurrence of a substring replaced.
|
||||
## If the substring is not found, returns the original string.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.replaceEach "foo/bar/baz" "/" "_" == "foo_bar_baz"
|
||||
## expect Str.replaceEach "not here" "/" "_" == "not here"
|
||||
## ```
|
||||
|
|
@ -792,7 +792,7 @@ expect Str.replaceEach "abcdefg" "nothing" "_" == "abcdefg"
|
|||
## Returns the given [Str] with the first occurrence of a substring replaced.
|
||||
## If the substring is not found, returns the original string.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.replaceFirst "foo/bar/baz" "/" "_" == "foo_bar/baz"
|
||||
## expect Str.replaceFirst "no slashes here" "/" "_" == "no slashes here"
|
||||
## ```
|
||||
|
|
@ -810,7 +810,7 @@ expect Str.replaceFirst "abcdefg" "nothing" "_" == "abcdefg"
|
|||
## Returns the given [Str] with the last occurrence of a substring replaced.
|
||||
## If the substring is not found, returns the original string.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.replaceLast "foo/bar/baz" "/" "_" == "foo/bar_baz"
|
||||
## expect Str.replaceLast "no slashes here" "/" "_" == "no slashes here"
|
||||
## ```
|
||||
|
|
@ -828,7 +828,7 @@ expect Str.replaceLast "abcdefg" "nothing" "_" == "abcdefg"
|
|||
## Returns the given [Str] before the first occurrence of a [delimiter](https://www.computerhope.com/jargon/d/delimite.htm), as well
|
||||
## as the rest of the string after that occurrence.
|
||||
## Returns [Err NotFound] if the delimiter is not found.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.splitFirst "foo/bar/baz" "/" == Ok { before: "foo", after: "bar/baz" }
|
||||
## expect Str.splitFirst "no slashes here" "/" == Err NotFound
|
||||
## ```
|
||||
|
|
@ -882,7 +882,7 @@ firstMatchHelp = \haystack, needle, index, lastPossible ->
|
|||
## Returns the given [Str] before the last occurrence of a delimiter, as well as
|
||||
## the rest of the string after that occurrence.
|
||||
## Returns [Err NotFound] if the delimiter is not found.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.splitLast "foo/bar/baz" "/" == Ok { before: "foo/bar", after: "baz" }
|
||||
## expect Str.splitLast "no slashes here" "/" == Err NotFound
|
||||
## ```
|
||||
|
|
@ -974,7 +974,7 @@ matchesAtHelp = \state ->
|
|||
## Walks over the `UTF-8` bytes of the given [Str] and calls a function to update
|
||||
## state for each byte. The index for that byte in the string is provided
|
||||
## to the update function.
|
||||
## ```
|
||||
## ```roc
|
||||
## f : List U8, U8, U64 -> List U8
|
||||
## f = \state, byte, _ -> List.append state byte
|
||||
## expect Str.walkUtf8WithIndex "ABC" [] f == [65, 66, 67]
|
||||
|
|
@ -996,7 +996,7 @@ walkUtf8WithIndexHelp = \string, state, step, index, length ->
|
|||
## Walks over the `UTF-8` bytes of the given [Str] and calls a function to update
|
||||
## state for each byte.
|
||||
##
|
||||
## ```
|
||||
## ```roc
|
||||
## sumOfUtf8Bytes =
|
||||
## Str.walkUtf8 "Hello, World!" 0 \total, byte ->
|
||||
## total + byte
|
||||
|
|
@ -1037,14 +1037,14 @@ strToNumHelp = \string ->
|
|||
Err InvalidNumStr
|
||||
|
||||
## Adds a prefix to the given [Str].
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.withPrefix "Awesome" "Roc" == "RocAwesome"
|
||||
## ```
|
||||
withPrefix : Str, Str -> Str
|
||||
withPrefix = \str, prefix -> Str.concat prefix str
|
||||
|
||||
## Determines whether or not the first Str contains the second.
|
||||
## ```
|
||||
## ```roc
|
||||
## expect Str.contains "foobarbaz" "bar"
|
||||
## expect !(Str.contains "apple" "orange")
|
||||
## expect Str.contains "anything" ""
|
||||
|
|
|
|||
|
|
@ -18,10 +18,22 @@ pub struct ModuleDocumentation {
|
|||
pub exposed_symbols: VecSet<Symbol>,
|
||||
}
|
||||
|
||||
impl ModuleDocumentation {
|
||||
pub fn get_doc_for_symbol(&self, symbol_to_match: &Symbol) -> Option<String> {
|
||||
self.entries.iter().find_map(|doc| match doc {
|
||||
DocEntry::DocDef(DocDef { symbol, docs, .. }) if symbol == symbol_to_match => {
|
||||
docs.clone()
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DocEntry {
|
||||
DocDef(DocDef),
|
||||
DetachedDoc(String),
|
||||
ModuleDoc(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -174,10 +186,10 @@ fn generate_entry_docs(
|
|||
) -> Vec<DocEntry> {
|
||||
use roc_parse::ast::Pattern;
|
||||
|
||||
let mut acc = Vec::with_capacity(defs.tags.len() + 1);
|
||||
let mut doc_entries = Vec::with_capacity(defs.tags.len() + 1);
|
||||
|
||||
if let Some(docs) = comments_or_new_lines_to_docs(header_comments) {
|
||||
acc.push(DetachedDoc(docs));
|
||||
doc_entries.push(DocEntry::ModuleDoc(docs));
|
||||
}
|
||||
|
||||
let mut before_comments_or_new_lines: Option<&[CommentOrNewline]> = None;
|
||||
|
|
@ -211,7 +223,7 @@ fn generate_entry_docs(
|
|||
type_vars: Vec::new(),
|
||||
docs,
|
||||
};
|
||||
acc.push(DocEntry::DocDef(doc_def));
|
||||
doc_entries.push(DocEntry::DocDef(doc_def));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -231,13 +243,25 @@ fn generate_entry_docs(
|
|||
symbol: Symbol::new(home, ident_id),
|
||||
docs,
|
||||
};
|
||||
acc.push(DocEntry::DocDef(doc_def));
|
||||
doc_entries.push(DocEntry::DocDef(doc_def));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ValueDef::Body(_, _) => {
|
||||
// TODO generate docs for un-annotated bodies
|
||||
ValueDef::Body(pattern, _) => {
|
||||
if let Pattern::Identifier(identifier) = pattern.value {
|
||||
// Check if this module exposes the def
|
||||
if let Some(ident_id) = ident_ids.get_id(identifier) {
|
||||
let doc_def = DocDef {
|
||||
name: identifier.to_string(),
|
||||
type_annotation: TypeAnnotation::NoTypeAnn,
|
||||
type_vars: Vec::new(),
|
||||
symbol: Symbol::new(home, ident_id),
|
||||
docs,
|
||||
};
|
||||
doc_entries.push(DocEntry::DocDef(doc_def));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ValueDef::Dbg { .. } => {
|
||||
|
|
@ -252,6 +276,7 @@ fn generate_entry_docs(
|
|||
// Don't generate docs for `expect-fx`s
|
||||
}
|
||||
},
|
||||
|
||||
Ok(type_index) => match &defs.type_defs[type_index.index()] {
|
||||
TypeDef::Alias {
|
||||
header: TypeHeader { name, vars },
|
||||
|
|
@ -284,7 +309,7 @@ fn generate_entry_docs(
|
|||
docs,
|
||||
symbol: Symbol::new(home, ident_id),
|
||||
};
|
||||
acc.push(DocEntry::DocDef(doc_def));
|
||||
doc_entries.push(DocEntry::DocDef(doc_def));
|
||||
}
|
||||
|
||||
TypeDef::Opaque {
|
||||
|
|
@ -307,7 +332,7 @@ fn generate_entry_docs(
|
|||
docs,
|
||||
symbol: Symbol::new(home, ident_id),
|
||||
};
|
||||
acc.push(DocEntry::DocDef(doc_def));
|
||||
doc_entries.push(DocEntry::DocDef(doc_def));
|
||||
}
|
||||
|
||||
TypeDef::Ability {
|
||||
|
|
@ -347,7 +372,7 @@ fn generate_entry_docs(
|
|||
type_vars,
|
||||
docs,
|
||||
};
|
||||
acc.push(DocEntry::DocDef(doc_def));
|
||||
doc_entries.push(DocEntry::DocDef(doc_def));
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -359,40 +384,49 @@ fn generate_entry_docs(
|
|||
let it = before_comments_or_new_lines.iter().flat_map(|e| e.iter());
|
||||
|
||||
for detached_doc in detached_docs_from_comments_and_new_lines(it) {
|
||||
acc.push(DetachedDoc(detached_doc));
|
||||
doc_entries.push(DetachedDoc(detached_doc));
|
||||
}
|
||||
|
||||
acc
|
||||
doc_entries
|
||||
}
|
||||
|
||||
/// Does this type contain any types which are not exposed outside the package?
|
||||
/// (If so, we shouldn't try to render a type annotation for it.)
|
||||
fn contains_unexposed_type(
|
||||
ann: &ast::TypeAnnotation,
|
||||
type_ann: &ast::TypeAnnotation,
|
||||
exposed_module_ids: &[ModuleId],
|
||||
module_ids: &ModuleIds,
|
||||
) -> bool {
|
||||
use ast::TypeAnnotation::*;
|
||||
|
||||
match ann {
|
||||
match type_ann {
|
||||
// Apply is the one case that can directly return true.
|
||||
Apply(module_name, _ident, loc_args) => {
|
||||
let apply_module_id = module_ids.get_id(&(*module_name).into());
|
||||
let loc_args_contains_unexposed_type = loc_args.iter().any(|loc_arg| {
|
||||
contains_unexposed_type(&loc_arg.value, exposed_module_ids, module_ids)
|
||||
});
|
||||
|
||||
// If the *ident* was unexposed, we would have gotten a naming error
|
||||
// during canonicalization, so all we need to check is the module.
|
||||
let module_id = module_ids.get_id(&(*module_name).into()).unwrap();
|
||||
if let Some(module_id) = apply_module_id {
|
||||
!exposed_module_ids.contains(&module_id) || loc_args_contains_unexposed_type
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
!exposed_module_ids.contains(&module_id)
|
||||
|| loc_args.iter().any(|loc_arg| {
|
||||
contains_unexposed_type(&loc_arg.value, exposed_module_ids, module_ids)
|
||||
})
|
||||
}
|
||||
Malformed(_) | Inferred | Wildcard | BoundVariable(_) => false,
|
||||
|
||||
Function(loc_args, loc_ret) => {
|
||||
let loc_args_contains_unexposed_type = loc_args.iter().any(|loc_arg| {
|
||||
contains_unexposed_type(&loc_arg.value, exposed_module_ids, module_ids)
|
||||
});
|
||||
|
||||
contains_unexposed_type(&loc_ret.value, exposed_module_ids, module_ids)
|
||||
|| loc_args.iter().any(|loc_arg| {
|
||||
contains_unexposed_type(&loc_arg.value, exposed_module_ids, module_ids)
|
||||
})
|
||||
|| loc_args_contains_unexposed_type
|
||||
}
|
||||
|
||||
Record { fields, ext } => {
|
||||
if let Some(loc_ext) = ext {
|
||||
if contains_unexposed_type(&loc_ext.value, exposed_module_ids, module_ids) {
|
||||
|
|
@ -422,6 +456,7 @@ fn contains_unexposed_type(
|
|||
|
||||
false
|
||||
}
|
||||
|
||||
Tuple { elems: fields, ext } => {
|
||||
if let Some(loc_ext) = ext {
|
||||
if contains_unexposed_type(&loc_ext.value, exposed_module_ids, module_ids) {
|
||||
|
|
@ -433,6 +468,7 @@ fn contains_unexposed_type(
|
|||
contains_unexposed_type(&loc_field.value, exposed_module_ids, module_ids)
|
||||
})
|
||||
}
|
||||
|
||||
TagUnion { ext, tags } => {
|
||||
use ast::Tag;
|
||||
|
||||
|
|
@ -468,14 +504,17 @@ fn contains_unexposed_type(
|
|||
|
||||
false
|
||||
}
|
||||
|
||||
Where(loc_ann, _loc_has_clauses) => {
|
||||
// We assume all the abilities in the `implements` clause are from exported modules.
|
||||
// TODO don't assume this! Instead, look them up and verify.
|
||||
contains_unexposed_type(&loc_ann.value, exposed_module_ids, module_ids)
|
||||
}
|
||||
|
||||
As(loc_ann, _spaces, _type_header) => {
|
||||
contains_unexposed_type(&loc_ann.value, exposed_module_ids, module_ids)
|
||||
}
|
||||
|
||||
SpaceBefore(ann, _) | ast::TypeAnnotation::SpaceAfter(ann, _) => {
|
||||
contains_unexposed_type(ann, exposed_module_ids, module_ids)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ fn start_phase<'a>(
|
|||
//
|
||||
// At the end of this loop, dep_idents contains all the information to
|
||||
// resolve a symbol from another module: if it's in here, that means
|
||||
// we have both imported the module and the ident was exported by that mdoule.
|
||||
// we have both imported the module and the ident was exported by that module.
|
||||
for dep_id in deps_by_name.values() {
|
||||
// We already verified that these are all present,
|
||||
// so unwrapping should always succeed here.
|
||||
|
|
@ -3340,7 +3340,7 @@ fn finish(
|
|||
exposed_types_storage: ExposedTypesStorageSubs,
|
||||
resolved_implementations: ResolvedImplementations,
|
||||
dep_idents: IdentIdsByModule,
|
||||
mut documentation: VecMap<ModuleId, ModuleDocumentation>,
|
||||
documentation: VecMap<ModuleId, ModuleDocumentation>,
|
||||
abilities_store: AbilitiesStore,
|
||||
//
|
||||
#[cfg(debug_assertions)] checkmate: Option<roc_checkmate::Collector>,
|
||||
|
|
@ -3379,18 +3379,6 @@ fn finish(
|
|||
|
||||
roc_checkmate::dump_checkmate!(checkmate);
|
||||
|
||||
let mut docs_by_module = Vec::with_capacity(state.exposed_modules.len());
|
||||
|
||||
for module_id in state.exposed_modules.iter() {
|
||||
let docs = documentation.remove(module_id).unwrap_or_else(|| {
|
||||
panic!("A module was exposed but didn't have an entry in `documentation` somehow: {module_id:?}");
|
||||
});
|
||||
|
||||
docs_by_module.push(docs);
|
||||
}
|
||||
|
||||
debug_assert_eq!(documentation.len(), 0);
|
||||
|
||||
LoadedModule {
|
||||
module_id: state.root_id,
|
||||
interns,
|
||||
|
|
@ -3404,10 +3392,11 @@ fn finish(
|
|||
exposed_values,
|
||||
exposed_to_host: exposed_vars_by_symbol.into_iter().collect(),
|
||||
exposed_types_storage,
|
||||
exposed_modules: state.exposed_modules.into(),
|
||||
resolved_implementations,
|
||||
sources,
|
||||
timings: state.timings,
|
||||
docs_by_module,
|
||||
docs_by_module: documentation,
|
||||
abilities_store,
|
||||
exposed_imports: state.module_cache.exposed_imports,
|
||||
imports: state.module_cache.imports,
|
||||
|
|
@ -5404,36 +5393,22 @@ fn canonicalize_and_constrain<'a>(
|
|||
|
||||
// Generate documentation information
|
||||
// TODO: store timing information?
|
||||
let module_docs = match header_type {
|
||||
HeaderType::App { .. } => None,
|
||||
HeaderType::Platform { .. } | HeaderType::Package { .. } => {
|
||||
// TODO: actually generate docs for platform and package modules.
|
||||
None
|
||||
}
|
||||
HeaderType::Interface { name, .. }
|
||||
| HeaderType::Builtin { name, .. }
|
||||
| HeaderType::Hosted { name, .. }
|
||||
if exposed_module_ids.contains(&parsed.module_id) =>
|
||||
{
|
||||
let module_docs = {
|
||||
let module_name = header_type.get_name();
|
||||
module_name.map(|module_name| {
|
||||
let mut scope = module_output.scope.clone();
|
||||
scope.add_docs_imports();
|
||||
let docs = crate::docs::generate_module_docs(
|
||||
crate::docs::generate_module_docs(
|
||||
scope,
|
||||
module_id,
|
||||
module_ids,
|
||||
name.as_str().into(),
|
||||
module_name.into(),
|
||||
&parsed_defs_for_docs,
|
||||
exposed_module_ids,
|
||||
module_output.exposed_symbols.clone(),
|
||||
parsed.header_comments,
|
||||
);
|
||||
|
||||
Some(docs)
|
||||
}
|
||||
HeaderType::Interface { .. } | HeaderType::Builtin { .. } | HeaderType::Hosted { .. } => {
|
||||
// This module isn't exposed by the platform, so don't generate docs for it!
|
||||
None
|
||||
}
|
||||
)
|
||||
})
|
||||
};
|
||||
|
||||
// _before has an underscore because it's unused in --release builds
|
||||
|
|
|
|||
|
|
@ -38,12 +38,13 @@ pub struct LoadedModule {
|
|||
pub exposed_to_host: MutMap<Symbol, Variable>,
|
||||
pub dep_idents: IdentIdsByModule,
|
||||
pub exposed_aliases: MutMap<Symbol, Alias>,
|
||||
pub exposed_modules: Vec<ModuleId>,
|
||||
pub exposed_values: Vec<Symbol>,
|
||||
pub exposed_types_storage: ExposedTypesStorageSubs,
|
||||
pub resolved_implementations: ResolvedImplementations,
|
||||
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
|
||||
pub timings: MutMap<ModuleId, ModuleTiming>,
|
||||
pub docs_by_module: Vec<(ModuleId, ModuleDocumentation)>,
|
||||
pub docs_by_module: VecMap<ModuleId, ModuleDocumentation>,
|
||||
pub abilities_store: AbilitiesStore,
|
||||
pub typechecked: MutMap<ModuleId, CheckedModule>,
|
||||
|
||||
|
|
|
|||
18
crates/compiler/load_internal/tests/fixtures/build/no_deps/Docs.roc
vendored
Normal file
18
crates/compiler/load_internal/tests/fixtures/build/no_deps/Docs.roc
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
## An interface for docs tests
|
||||
interface Docs
|
||||
exposes [makeUser, getNameExposed]
|
||||
imports []
|
||||
|
||||
## This is a user
|
||||
User : { name : Str }
|
||||
|
||||
## Makes a user
|
||||
makeUser : Str -> User
|
||||
makeUser = \name ->
|
||||
{ name }
|
||||
|
||||
## Gets the user's name
|
||||
getName = \a -> a.name
|
||||
|
||||
getNameExposed = getName
|
||||
|
||||
|
|
@ -17,6 +17,7 @@ mod helpers;
|
|||
use crate::helpers::fixtures_dir;
|
||||
use bumpalo::Bump;
|
||||
use roc_can::module::ExposedByModule;
|
||||
use roc_load_internal::docs::DocDef;
|
||||
use roc_load_internal::file::{
|
||||
ExecutionMode, LoadConfig, LoadResult, LoadStart, LoadingProblem, Threading,
|
||||
};
|
||||
|
|
@ -423,6 +424,50 @@ fn load_unit() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_docs() {
|
||||
let subs_by_module = Default::default();
|
||||
let loaded_module = load_fixture("no_deps", "Docs", subs_by_module);
|
||||
|
||||
let module_docs = loaded_module
|
||||
.docs_by_module
|
||||
.get(&loaded_module.module_id)
|
||||
.expect("module should have docs");
|
||||
|
||||
let all_docs = module_docs
|
||||
.entries
|
||||
.iter()
|
||||
.map(|a| match a {
|
||||
roc_load_internal::docs::DocEntry::DocDef(DocDef { name, docs, .. }) => {
|
||||
(Some(name.clone()), docs.clone().map(|a| a.to_string()))
|
||||
}
|
||||
|
||||
roc_load_internal::docs::DocEntry::ModuleDoc(docs)
|
||||
| roc_load_internal::docs::DocEntry::DetachedDoc(docs) => (None, Some(docs.clone())),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let expected = vec![
|
||||
(None, Some("An interface for docs tests\n")),
|
||||
(Some("User"), Some("This is a user\n")),
|
||||
(Some("makeUser"), Some("Makes a user\n")),
|
||||
(Some("getName"), Some("Gets the user's name\n")),
|
||||
(Some("getNameExposed"), None),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(ident_str_opt, doc_str_opt)| {
|
||||
(
|
||||
ident_str_opt.map(|a| a.to_string()),
|
||||
doc_str_opt.map(|b| b.to_string()),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// let has_all_docs = expected.map(|a| docs.contains(&a)).all(|a| a);
|
||||
// assert!(has_all_docs, "Some of the expected docs were not created")
|
||||
assert_eq!(expected, all_docs);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_alias() {
|
||||
let subs_by_module = Default::default();
|
||||
|
|
|
|||
|
|
@ -79,6 +79,32 @@ pub enum HeaderType<'a> {
|
|||
},
|
||||
}
|
||||
|
||||
impl<'a> HeaderType<'a> {
|
||||
pub fn get_name(self) -> Option<&'a str> {
|
||||
match self {
|
||||
Self::Interface { name, .. }
|
||||
| Self::Builtin { name, .. }
|
||||
| Self::Hosted { name, .. } => Some(name.into()),
|
||||
Self::App {
|
||||
output_name: StrLiteral::PlainLine(name),
|
||||
..
|
||||
}
|
||||
| Self::Platform {
|
||||
config_shorthand: name,
|
||||
..
|
||||
}
|
||||
| Self::Package {
|
||||
config_shorthand: name,
|
||||
..
|
||||
} => Some(name),
|
||||
Self::App { .. } => {
|
||||
//TODO:Eli This can be removed once module params is implemented and app names are no longer strings
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum Version<'a> {
|
||||
Exact(&'a str),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use roc_collections::VecSet;
|
|||
use roc_load::docs::{DocEntry, TypeAnnotation};
|
||||
use roc_load::docs::{ModuleDocumentation, RecordField};
|
||||
use roc_load::{ExecutionMode, LoadConfig, LoadedModule, LoadingProblem, Threading};
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||
use roc_packaging::cache::{self, RocCacheDir};
|
||||
use roc_parse::ident::{parse_ident, Accessor, Ident};
|
||||
use roc_parse::keyword;
|
||||
|
|
@ -20,7 +20,8 @@ use std::path::{Path, PathBuf};
|
|||
const LINK_SVG: &str = include_str!("./static/link.svg");
|
||||
|
||||
pub fn generate_docs_html(root_file: PathBuf, build_dir: &Path) {
|
||||
let loaded_module = load_module_for_docs(root_file);
|
||||
let mut loaded_module = load_module_for_docs(root_file);
|
||||
let exposed_module_docs = get_exposed_module_docs(&mut loaded_module);
|
||||
|
||||
// TODO get these from the platform's source file rather than hardcoding them!
|
||||
// github.com/roc-lang/roc/issues/5712
|
||||
|
|
@ -95,8 +96,7 @@ pub fn generate_docs_html(root_file: PathBuf, build_dir: &Path) {
|
|||
.raw_template_html
|
||||
.replace(
|
||||
"<!-- Prefetch links -->",
|
||||
loaded_module
|
||||
.docs_by_module
|
||||
exposed_module_docs
|
||||
.iter()
|
||||
.map(|(_, module)| {
|
||||
let href = module.name.as_str();
|
||||
|
|
@ -110,13 +110,13 @@ pub fn generate_docs_html(root_file: PathBuf, build_dir: &Path) {
|
|||
.replace("<!-- base -->", &base_url())
|
||||
.replace(
|
||||
"<!-- Module links -->",
|
||||
render_sidebar(loaded_module.docs_by_module.iter().map(|(_, docs)| docs)).as_str(),
|
||||
render_sidebar(exposed_module_docs.iter().map(|(_, docs)| docs)).as_str(),
|
||||
);
|
||||
|
||||
let all_exposed_symbols = {
|
||||
let mut set = VecSet::default();
|
||||
|
||||
for (_, docs) in loaded_module.docs_by_module.iter() {
|
||||
for (_, docs) in exposed_module_docs.iter() {
|
||||
set.insert_all(docs.exposed_symbols.iter().copied());
|
||||
}
|
||||
|
||||
|
|
@ -137,7 +137,7 @@ pub fn generate_docs_html(root_file: PathBuf, build_dir: &Path) {
|
|||
)
|
||||
.replace(
|
||||
"<!-- Module Docs -->",
|
||||
render_package_index(&loaded_module).as_str(),
|
||||
render_package_index(&exposed_module_docs).as_str(),
|
||||
);
|
||||
|
||||
fs::write(build_dir.join("index.html"), rendered_package).unwrap_or_else(|error| {
|
||||
|
|
@ -146,7 +146,7 @@ pub fn generate_docs_html(root_file: PathBuf, build_dir: &Path) {
|
|||
}
|
||||
|
||||
// Write each package module's index.html file
|
||||
for (_, module_docs) in loaded_module.docs_by_module.iter() {
|
||||
for (_, module_docs) in exposed_module_docs.iter() {
|
||||
let module_name = module_docs.name.as_str();
|
||||
let module_dir = build_dir.join(module_name.replace('.', "/").as_str());
|
||||
|
||||
|
|
@ -175,15 +175,33 @@ pub fn generate_docs_html(root_file: PathBuf, build_dir: &Path) {
|
|||
println!("🎉 Docs generated in {}", build_dir.display());
|
||||
}
|
||||
|
||||
/// Gives only the module docs for modules that are exposed by the platform or package.
|
||||
fn get_exposed_module_docs(
|
||||
loaded_module: &mut LoadedModule,
|
||||
) -> Vec<(ModuleId, ModuleDocumentation)> {
|
||||
let mut exposed_docs = Vec::with_capacity(loaded_module.exposed_modules.len());
|
||||
// let mut docs_by_module = Vec::with_capacity(state.exposed_modules.len());
|
||||
|
||||
for module_id in loaded_module.exposed_modules.iter() {
|
||||
let docs =
|
||||
loaded_module.docs_by_module.remove(module_id).unwrap_or_else(|| {
|
||||
panic!("A module was exposed but didn't have an entry in `documentation` somehow: {module_id:?}");
|
||||
});
|
||||
|
||||
exposed_docs.push(docs);
|
||||
}
|
||||
exposed_docs
|
||||
}
|
||||
|
||||
fn page_title(package_name: &str, module_name: &str) -> String {
|
||||
format!("<title>{module_name} - {package_name}</title>")
|
||||
}
|
||||
|
||||
fn render_package_index(root_module: &LoadedModule) -> String {
|
||||
fn render_package_index(docs_by_module: &[(ModuleId, ModuleDocumentation)]) -> String {
|
||||
// The list items containing module links
|
||||
let mut module_list_buf = String::new();
|
||||
|
||||
for (_, module) in root_module.docs_by_module.iter() {
|
||||
for (_, module) in docs_by_module.iter() {
|
||||
// The anchor tag containing the module link
|
||||
let mut link_buf = String::new();
|
||||
|
||||
|
|
@ -284,6 +302,15 @@ fn render_module_documentation(
|
|||
buf.push_str("</section>");
|
||||
}
|
||||
}
|
||||
DocEntry::ModuleDoc(docs) => {
|
||||
markdown_to_html(
|
||||
&mut buf,
|
||||
all_exposed_symbols,
|
||||
&module.scope,
|
||||
docs,
|
||||
root_module,
|
||||
);
|
||||
}
|
||||
DocEntry::DetachedDoc(docs) => {
|
||||
markdown_to_html(
|
||||
&mut buf,
|
||||
|
|
|
|||
39
crates/lang_srv/TODO.md
Normal file
39
crates/lang_srv/TODO.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# Improvements to the language server.
|
||||
|
||||
## Performance
|
||||
- [ ] Implement some performance logging for actions like completion goto def hover etc
|
||||
|
||||
### Completion
|
||||
Currently the way we handle documentation and type info for completion requires us to prform all the computation up front and has no caching. Documentation is also quite inneficient and likely requires a lot of repeated computation which could be slow in files with lots of doc comments. The language server allows us to defer getting the info for a completion until the item is actually selected in the editor, this could speed up completion requests.
|
||||
We would need to profile this to see how performant it really is.
|
||||
|
||||
## Features
|
||||
- [ ] Rename refactoring #HighPriority
|
||||
- [ ] Show references #HighPriority
|
||||
Initially this could just be within the current file and it could be expanded to multi file
|
||||
Should have a lot in commmon with rename refactoring
|
||||
- [ ] Completion within the import section
|
||||
|
||||
### Code Actions
|
||||
- [ ] Create cases of when is block
|
||||
- [ ] Destructure record
|
||||
- [ ] Extract selection into it's own function (This one seems hard)
|
||||
- [ ] Add function to exposed list
|
||||
|
||||
### Completion
|
||||
- [ ] Completion of Tags #HighPriority
|
||||
- [ ] Completion of Types inside signatures
|
||||
|
||||
- [ ] Completion of when is cases
|
||||
- [ ] Completion of record fields
|
||||
- [ ] During destructuring
|
||||
- [ ] When creating records
|
||||
- [ ] When describing records inside function params
|
||||
|
||||
- [ ] Completion of unimported vars that are exposed by modules within the project (will need to have appropriate indicator and ranking so as not to be annoying)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -8,8 +8,8 @@ use bumpalo::Bump;
|
|||
|
||||
use parking_lot::Mutex;
|
||||
use roc_can::{abilities::AbilitiesStore, expr::Declarations};
|
||||
use roc_collections::{MutMap, MutSet};
|
||||
use roc_load::{CheckedModule, LoadedModule};
|
||||
use roc_collections::{MutMap, MutSet, VecMap};
|
||||
use roc_load::{docs::ModuleDocumentation, CheckedModule, LoadedModule};
|
||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||
use roc_packaging::cache::{self, RocCacheDir};
|
||||
use roc_region::all::LineInfo;
|
||||
|
|
@ -38,22 +38,22 @@ pub const HIGHLIGHT_TOKENS_LEGEND: &[SemanticTokenType] = Token::LEGEND;
|
|||
pub(super) struct ModulesInfo {
|
||||
subs: Mutex<HashMap<ModuleId, Subs>>,
|
||||
exposed: HashMap<ModuleId, Arc<Vec<(Symbol, Variable)>>>,
|
||||
docs: VecMap<ModuleId, ModuleDocumentation>,
|
||||
}
|
||||
|
||||
impl ModulesInfo {
|
||||
/// Apply function to subs
|
||||
fn with_subs<F, A>(&self, mod_id: &ModuleId, f: F) -> Option<A>
|
||||
where
|
||||
F: FnOnce(&mut Subs) -> A,
|
||||
{
|
||||
self.subs.lock().get_mut(mod_id).map(f)
|
||||
}
|
||||
|
||||
/// Transforms some of the raw data from the analysis into a state that is
|
||||
/// more useful during processes like completion.
|
||||
fn from_analysis(
|
||||
exposes: MutMap<ModuleId, Vec<(Symbol, Variable)>>,
|
||||
typechecked: &MutMap<ModuleId, CheckedModule>,
|
||||
docs_by_module: VecMap<ModuleId, ModuleDocumentation>,
|
||||
) -> ModulesInfo {
|
||||
// We wrap this in Arc because later we will go through each module's imports and
|
||||
// store the full list of symbols that each imported module exposes.
|
||||
|
|
@ -76,6 +76,7 @@ impl ModulesInfo {
|
|||
ModulesInfo {
|
||||
subs: all_subs,
|
||||
exposed,
|
||||
docs: docs_by_module,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -149,9 +150,10 @@ pub(crate) fn global_analysis(doc_info: DocInfo) -> Vec<AnalyzedDocument> {
|
|||
mut typechecked,
|
||||
solved,
|
||||
abilities_store,
|
||||
mut imports,
|
||||
exposed_imports,
|
||||
mut imports,
|
||||
exposes,
|
||||
docs_by_module,
|
||||
..
|
||||
} = module;
|
||||
|
||||
|
|
@ -162,7 +164,11 @@ pub(crate) fn global_analysis(doc_info: DocInfo) -> Vec<AnalyzedDocument> {
|
|||
|
||||
let exposed_imports = resolve_exposed_imports(exposed_imports, &exposes);
|
||||
|
||||
let modules_info = Arc::new(ModulesInfo::from_analysis(exposes, &typechecked));
|
||||
let modules_info = Arc::new(ModulesInfo::from_analysis(
|
||||
exposes,
|
||||
&typechecked,
|
||||
docs_by_module,
|
||||
));
|
||||
|
||||
let mut builder = AnalyzedDocumentBuilder {
|
||||
interns: &interns,
|
||||
|
|
|
|||
|
|
@ -169,19 +169,33 @@ impl AnalyzedDocument {
|
|||
declarations,
|
||||
module_id,
|
||||
interns,
|
||||
modules_info,
|
||||
..
|
||||
} = self.module()?;
|
||||
|
||||
let (region, var) = roc_can::traverse::find_closest_type_at(pos, declarations)?;
|
||||
|
||||
//TODO:Can this be integrated into find closest type? is it even worth it?
|
||||
let docs_opt = self
|
||||
.symbol_at(position)
|
||||
.and_then(|symb| modules_info.docs.get(module_id)?.get_doc_for_symbol(&symb));
|
||||
|
||||
let type_str = format_var_type(var, &mut subs.clone(), module_id, interns);
|
||||
|
||||
let range = region.to_range(self.line_info());
|
||||
|
||||
let type_content = MarkedString::LanguageString(LanguageString {
|
||||
language: "roc".to_string(),
|
||||
value: type_str,
|
||||
});
|
||||
|
||||
let content = vec![Some(type_content), docs_opt.map(MarkedString::String)]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Some(Hover {
|
||||
contents: HoverContents::Scalar(MarkedString::LanguageString(LanguageString {
|
||||
language: "roc".to_string(),
|
||||
value: type_str,
|
||||
})),
|
||||
contents: HoverContents::Array(content),
|
||||
range: Some(range),
|
||||
})
|
||||
}
|
||||
|
|
@ -285,6 +299,7 @@ impl AnalyzedDocument {
|
|||
&mut subs.clone(),
|
||||
module_id,
|
||||
interns,
|
||||
modules_info.docs.get(module_id),
|
||||
exposed_imports,
|
||||
);
|
||||
Some(completions)
|
||||
|
|
|
|||
|
|
@ -1,254 +1,23 @@
|
|||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use log::{debug, trace, warn};
|
||||
use log::{debug, warn};
|
||||
|
||||
use roc_can::{
|
||||
def::Def,
|
||||
expr::{ClosureData, Declarations, Expr, WhenBranch},
|
||||
pattern::{ListPatterns, Pattern, RecordDestruct, TupleDestruct},
|
||||
traverse::{walk_decl, walk_def, walk_expr, DeclarationInfo, Visitor},
|
||||
};
|
||||
use roc_can::{expr::Declarations, traverse::Visitor};
|
||||
use roc_collections::MutMap;
|
||||
use roc_load::docs::{DocDef, ModuleDocumentation};
|
||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||
use roc_region::all::{Loc, Position, Region};
|
||||
use roc_region::all::Position;
|
||||
use roc_types::{
|
||||
subs::{Subs, Variable},
|
||||
types::Alias,
|
||||
};
|
||||
use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind};
|
||||
use tower_lsp::lsp_types::{self, CompletionItem, CompletionItemKind};
|
||||
|
||||
use self::visitor::CompletionVisitor;
|
||||
|
||||
use super::{utils::format_var_type, ModulesInfo};
|
||||
mod formatting;
|
||||
|
||||
pub struct CompletionVisitor<'a> {
|
||||
position: Position,
|
||||
found_decls: Vec<(Symbol, Variable)>,
|
||||
pub interns: &'a Interns,
|
||||
pub prefix: String,
|
||||
}
|
||||
|
||||
impl Visitor for CompletionVisitor<'_> {
|
||||
fn should_visit(&mut self, region: Region) -> bool {
|
||||
region.contains_pos(self.position)
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &Expr, region: Region, var: Variable) {
|
||||
if region.contains_pos(self.position) {
|
||||
let mut res = self.expression_defs(expr);
|
||||
self.found_decls.append(&mut res);
|
||||
|
||||
walk_expr(self, expr, var);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_decl(&mut self, decl: DeclarationInfo<'_>) {
|
||||
match decl {
|
||||
DeclarationInfo::Value { loc_expr, .. }
|
||||
| DeclarationInfo::Function {
|
||||
loc_body: loc_expr, ..
|
||||
}
|
||||
| DeclarationInfo::Destructure { loc_expr, .. } => {
|
||||
let res = self.decl_to_completion_item(&decl);
|
||||
self.found_decls.extend(res);
|
||||
if loc_expr.region.contains_pos(self.position) {
|
||||
walk_decl(self, decl);
|
||||
};
|
||||
}
|
||||
_ => {
|
||||
walk_decl(self, decl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_def(&mut self, def: &Def) {
|
||||
let res = self.extract_defs(def);
|
||||
self.found_decls.extend(res);
|
||||
walk_def(self, def);
|
||||
}
|
||||
}
|
||||
impl CompletionVisitor<'_> {
|
||||
fn extract_defs(&mut self, def: &Def) -> Vec<(Symbol, Variable)> {
|
||||
trace!("Completion begin");
|
||||
def.pattern_vars
|
||||
.iter()
|
||||
.map(|(symbol, var)| (*symbol, *var))
|
||||
.collect()
|
||||
}
|
||||
fn expression_defs(&self, expr: &Expr) -> Vec<(Symbol, Variable)> {
|
||||
match expr {
|
||||
Expr::When {
|
||||
expr_var, branches, ..
|
||||
} => self.when_is_expr(branches, expr_var),
|
||||
Expr::Closure(ClosureData {
|
||||
arguments,
|
||||
loc_body,
|
||||
..
|
||||
}) => {
|
||||
//if we are inside the closure complete it's vars
|
||||
if loc_body.region.contains_pos(self.position) {
|
||||
arguments
|
||||
.iter()
|
||||
.flat_map(|(var, _, pat)| self.patterns(&pat.value, var))
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
///Extract any variables made available by the branch of a when_is expression that contains `self.position`
|
||||
fn when_is_expr(
|
||||
&self,
|
||||
branches: &[WhenBranch],
|
||||
expr_var: &Variable,
|
||||
) -> Vec<(Symbol, Variable)> {
|
||||
branches
|
||||
.iter()
|
||||
.flat_map(
|
||||
|WhenBranch {
|
||||
patterns, value, ..
|
||||
}| {
|
||||
if value.region.contains_pos(self.position) {
|
||||
patterns
|
||||
.iter()
|
||||
.flat_map(|pattern| self.patterns(&pattern.pattern.value, expr_var))
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn record_destructure(&self, destructs: &[Loc<RecordDestruct>]) -> Vec<(Symbol, Variable)> {
|
||||
destructs
|
||||
.iter()
|
||||
.flat_map(|a| match &a.value.typ {
|
||||
roc_can::pattern::DestructType::Required
|
||||
| roc_can::pattern::DestructType::Optional(_, _) => {
|
||||
vec![(a.value.symbol, a.value.var)]
|
||||
}
|
||||
roc_can::pattern::DestructType::Guard(var, pat) => self.patterns(&pat.value, var),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn tuple_destructure(&self, destructs: &[Loc<TupleDestruct>]) -> Vec<(Symbol, Variable)> {
|
||||
destructs
|
||||
.iter()
|
||||
.flat_map(|a| {
|
||||
let (var, pattern) = &a.value.typ;
|
||||
self.patterns(&pattern.value, var)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn list_pattern(&self, list_elems: &ListPatterns, var: &Variable) -> Vec<(Symbol, Variable)> {
|
||||
list_elems
|
||||
.patterns
|
||||
.iter()
|
||||
.flat_map(|a| self.patterns(&a.value, var))
|
||||
.collect()
|
||||
}
|
||||
fn tag_pattern(&self, arguments: &[(Variable, Loc<Pattern>)]) -> Vec<(Symbol, Variable)> {
|
||||
arguments
|
||||
.iter()
|
||||
.flat_map(|(var, pat)| self.patterns(&pat.value, var))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn as_pattern(
|
||||
&self,
|
||||
as_pat: &Pattern,
|
||||
as_symbol: Symbol,
|
||||
var: &Variable,
|
||||
) -> Vec<(Symbol, Variable)> {
|
||||
//Get the variables introduced within the pattern
|
||||
let mut patterns = self.patterns(as_pat, var);
|
||||
//Add the "as" that wraps the whole pattern
|
||||
patterns.push((as_symbol, *var));
|
||||
patterns
|
||||
}
|
||||
///Returns a list of symbols defined by this pattern.
|
||||
///`pattern_var`: Variable type of the entire pattern. This will be returned if the pattern turns out to be an identifier
|
||||
fn patterns(
|
||||
&self,
|
||||
pattern: &roc_can::pattern::Pattern,
|
||||
pattern_var: &Variable,
|
||||
) -> Vec<(Symbol, Variable)> {
|
||||
match pattern {
|
||||
roc_can::pattern::Pattern::Identifier(symbol) => {
|
||||
if self.is_match(symbol) {
|
||||
vec![(*symbol, *pattern_var)]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
Pattern::AppliedTag { arguments, .. } => self.tag_pattern(arguments),
|
||||
Pattern::UnwrappedOpaque { argument, .. } => {
|
||||
self.patterns(&argument.1.value, &argument.0)
|
||||
}
|
||||
Pattern::List {
|
||||
elem_var, patterns, ..
|
||||
} => self.list_pattern(patterns, elem_var),
|
||||
roc_can::pattern::Pattern::As(pat, symbol) => {
|
||||
self.as_pattern(&pat.value, *symbol, pattern_var)
|
||||
}
|
||||
roc_can::pattern::Pattern::RecordDestructure { destructs, .. } => {
|
||||
self.record_destructure(destructs)
|
||||
}
|
||||
roc_can::pattern::Pattern::TupleDestructure { destructs, .. } => {
|
||||
self.tuple_destructure(destructs)
|
||||
}
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn is_match(&self, symbol: &Symbol) -> bool {
|
||||
symbol.as_str(self.interns).starts_with(&self.prefix)
|
||||
}
|
||||
|
||||
fn decl_to_completion_item(&self, decl: &DeclarationInfo) -> Vec<(Symbol, Variable)> {
|
||||
match decl {
|
||||
DeclarationInfo::Value {
|
||||
expr_var, pattern, ..
|
||||
} => self.patterns(pattern, expr_var),
|
||||
DeclarationInfo::Function {
|
||||
expr_var,
|
||||
pattern,
|
||||
function,
|
||||
loc_body,
|
||||
..
|
||||
} => {
|
||||
let mut out = vec![];
|
||||
//Append the function declaration itself for recursive calls
|
||||
out.extend(self.patterns(pattern, expr_var));
|
||||
|
||||
if loc_body.region.contains_pos(self.position) {
|
||||
//also add the arguments if we are inside the function
|
||||
let args = function
|
||||
.value
|
||||
.arguments
|
||||
.iter()
|
||||
.flat_map(|(var, _, pat)| self.patterns(&pat.value, var));
|
||||
//We add in the pattern for the function declaration
|
||||
out.extend(args);
|
||||
trace!("Added function args to completion output =:{:#?}", out);
|
||||
}
|
||||
out
|
||||
}
|
||||
DeclarationInfo::Destructure {
|
||||
loc_pattern,
|
||||
expr_var,
|
||||
..
|
||||
} => self.patterns(&loc_pattern.value, expr_var),
|
||||
DeclarationInfo::Expectation { .. } => vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
mod visitor;
|
||||
|
||||
fn get_completions(
|
||||
position: Position,
|
||||
|
|
@ -258,49 +27,17 @@ fn get_completions(
|
|||
) -> Vec<(Symbol, Variable)> {
|
||||
let mut visitor = CompletionVisitor {
|
||||
position,
|
||||
found_decls: Vec::new(),
|
||||
found_declarations: Vec::new(),
|
||||
interns,
|
||||
prefix,
|
||||
};
|
||||
visitor.visit_decls(decls);
|
||||
visitor.found_decls
|
||||
}
|
||||
|
||||
fn make_completion_item(
|
||||
subs: &mut Subs,
|
||||
module_id: &ModuleId,
|
||||
interns: &Interns,
|
||||
str: String,
|
||||
var: Variable,
|
||||
) -> CompletionItem {
|
||||
let type_str = format_var_type(var, subs, module_id, interns);
|
||||
let typ = match subs.get(var).content {
|
||||
roc_types::subs::Content::Structure(var) => match var {
|
||||
roc_types::subs::FlatType::Apply(_, _) => CompletionItemKind::FUNCTION,
|
||||
roc_types::subs::FlatType::Func(_, _, _) => CompletionItemKind::FUNCTION,
|
||||
roc_types::subs::FlatType::EmptyTagUnion
|
||||
| roc_types::subs::FlatType::TagUnion(_, _) => CompletionItemKind::ENUM,
|
||||
_ => CompletionItemKind::VARIABLE,
|
||||
},
|
||||
a => {
|
||||
debug!(
|
||||
"No specific completionKind for variable type: {:?} defaulting to 'Variable'",
|
||||
a
|
||||
);
|
||||
CompletionItemKind::VARIABLE
|
||||
}
|
||||
};
|
||||
|
||||
CompletionItem {
|
||||
label: str,
|
||||
detail: Some(type_str),
|
||||
kind: Some(typ),
|
||||
..Default::default()
|
||||
}
|
||||
visitor.found_declarations
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
/// Walks through declarations that would be accessible from the provided
|
||||
/// position, adding them to a list of completion items until all accessible
|
||||
/// position adding them to a list of completion items until all accessible
|
||||
/// declarations have been fully explored.
|
||||
pub fn get_completion_items(
|
||||
position: Position,
|
||||
|
|
@ -309,22 +46,13 @@ pub fn get_completion_items(
|
|||
subs: &mut Subs,
|
||||
module_id: &ModuleId,
|
||||
interns: &Interns,
|
||||
docs: Option<&ModuleDocumentation>,
|
||||
exposed_imports: &[(Symbol, Variable)],
|
||||
) -> Vec<CompletionItem> {
|
||||
let mut completions = get_completions(position, decls, prefix, interns);
|
||||
completions.extend(exposed_imports);
|
||||
|
||||
debug!("extended with:{:#?}", exposed_imports);
|
||||
|
||||
make_completion_items(
|
||||
subs,
|
||||
module_id,
|
||||
interns,
|
||||
completions
|
||||
.into_iter()
|
||||
.map(|(symb, var)| (symb.as_str(interns).to_string(), var))
|
||||
.collect(),
|
||||
)
|
||||
make_completion_items(subs, module_id, interns, docs, completions)
|
||||
}
|
||||
|
||||
pub(super) fn get_module_completion_items(
|
||||
|
|
@ -339,6 +67,7 @@ pub(super) fn get_module_completion_items(
|
|||
.flat_map(|(mod_id, exposed_symbols)| {
|
||||
let mod_name = mod_id.to_ident_str(interns).to_string();
|
||||
|
||||
// Completion for modules themselves
|
||||
if mod_name.starts_with(&prefix) {
|
||||
let item = CompletionItem {
|
||||
label: mod_name.clone(),
|
||||
|
|
@ -348,15 +77,22 @@ pub(super) fn get_module_completion_items(
|
|||
mod_id,
|
||||
interns,
|
||||
exposed_symbols,
|
||||
modules_info.docs.get(mod_id),
|
||||
modules_info,
|
||||
)),
|
||||
..Default::default()
|
||||
};
|
||||
vec![item]
|
||||
|
||||
// Complete dot completions
|
||||
vec![item]
|
||||
// Complete dot completions for module exports
|
||||
} else if prefix.starts_with(&(mod_name + ".")) {
|
||||
get_module_exposed_completion(exposed_symbols, modules_info, mod_id, interns)
|
||||
get_module_exposed_completion(
|
||||
exposed_symbols,
|
||||
modules_info,
|
||||
mod_id,
|
||||
modules_info.docs.get(mod_id),
|
||||
interns,
|
||||
)
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
|
|
@ -373,20 +109,26 @@ fn get_module_exposed_completion(
|
|||
exposed_symbols: &[(Symbol, Variable)],
|
||||
modules_info: &ModulesInfo,
|
||||
mod_id: &ModuleId,
|
||||
docs: Option<&ModuleDocumentation>,
|
||||
interns: &Interns,
|
||||
) -> Vec<CompletionItem> {
|
||||
let mut completion_docs = docs.map_or(Default::default(), |docs| {
|
||||
get_completion_docs(exposed_symbols, docs)
|
||||
});
|
||||
|
||||
exposed_symbols
|
||||
.iter()
|
||||
.map(|(sym, var)| {
|
||||
.map(|(symbol, var)| {
|
||||
// We need to fetch the subs for the module that is exposing what we
|
||||
// are trying to complete, because that will have the type info we need.
|
||||
// are trying to complete because that will have the type info we need.
|
||||
modules_info
|
||||
.with_subs(mod_id, |subs| {
|
||||
make_completion_item(
|
||||
subs,
|
||||
mod_id,
|
||||
interns,
|
||||
sym.as_str(interns).to_string(),
|
||||
completion_docs.remove(symbol),
|
||||
symbol.as_str(interns).to_string(),
|
||||
*var,
|
||||
)
|
||||
})
|
||||
|
|
@ -395,8 +137,38 @@ fn get_module_exposed_completion(
|
|||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
/// Efficiently walks the list of docs collecting the docs for completions as we go.
|
||||
/// Should be faster than re-walking for every completion.
|
||||
fn get_completion_docs(
|
||||
completions: &[(Symbol, Variable)],
|
||||
docs: &ModuleDocumentation,
|
||||
) -> HashMap<Symbol, String> {
|
||||
let mut symbols = completions
|
||||
.iter()
|
||||
.map(|(symbol, _)| symbol)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
docs.entries
|
||||
.iter()
|
||||
.filter_map(|doc| match doc {
|
||||
roc_load::docs::DocEntry::DocDef(DocDef { docs, symbol, .. }) => {
|
||||
let docs_str = docs.as_ref().map(|str| str.trim().to_string())?;
|
||||
let (index, _symbol) = symbols
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_index, symb)| symb == &&symbol)?;
|
||||
|
||||
symbols.swap_remove(index);
|
||||
|
||||
Some((*symbol, docs_str))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Provides a list of completions for Type aliases within the scope.
|
||||
/// TODO: Use this when we know we are within a type definition.
|
||||
///TODO: Use this when we know we are within a type definition
|
||||
fn _alias_completions(
|
||||
aliases: &MutMap<Symbol, (bool, Alias)>,
|
||||
module_id: &ModuleId,
|
||||
|
|
@ -407,17 +179,43 @@ fn _alias_completions(
|
|||
.filter(|(symbol, (_exposed, _alias))| &symbol.module_id() == module_id)
|
||||
.map(|(symbol, (_exposed, _alias))| {
|
||||
let name = symbol.as_str(interns).to_string();
|
||||
|
||||
CompletionItem {
|
||||
label: name.clone(),
|
||||
detail: Some(name + " we don't know how to print types."),
|
||||
detail: Some(name + "we don't know how to print types "),
|
||||
kind: Some(CompletionItemKind::CLASS),
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn make_completion_items(
|
||||
subs: &mut Subs,
|
||||
module_id: &ModuleId,
|
||||
interns: &Interns,
|
||||
docs: Option<&ModuleDocumentation>,
|
||||
completions: Vec<(Symbol, Variable)>,
|
||||
) -> Vec<CompletionItem> {
|
||||
let mut completion_docs = docs.map_or(Default::default(), |mod_docs| {
|
||||
get_completion_docs(&completions, mod_docs)
|
||||
});
|
||||
|
||||
completions
|
||||
.into_iter()
|
||||
.map(|(symbol, var)| {
|
||||
make_completion_item(
|
||||
subs,
|
||||
module_id,
|
||||
interns,
|
||||
completion_docs.remove(&symbol),
|
||||
symbol.as_str(interns).to_string(),
|
||||
var,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn make_completion_items_string(
|
||||
subs: &mut Subs,
|
||||
module_id: &ModuleId,
|
||||
interns: &Interns,
|
||||
|
|
@ -425,12 +223,60 @@ fn make_completion_items(
|
|||
) -> Vec<CompletionItem> {
|
||||
completions
|
||||
.into_iter()
|
||||
.map(|(symbol, var)| make_completion_item(subs, module_id, interns, symbol, var))
|
||||
.map(|(symbol, var)| make_completion_item(subs, module_id, interns, None, symbol, var))
|
||||
.collect()
|
||||
}
|
||||
|
||||
///Finds the types of and names of all the fields of a record
|
||||
///`var` should be a `Variable` that you know is a record's type or else it will return an empty list
|
||||
fn make_completion_item(
|
||||
subs: &mut Subs,
|
||||
module_id: &ModuleId,
|
||||
interns: &Interns,
|
||||
docs_opt: Option<String>,
|
||||
symbol_str: String,
|
||||
var: Variable,
|
||||
) -> CompletionItem {
|
||||
let type_str = format_var_type(var, subs, module_id, interns);
|
||||
let typ = match subs.get(var).content {
|
||||
roc_types::subs::Content::Structure(var) => match var {
|
||||
roc_types::subs::FlatType::Apply(_, _) => CompletionItemKind::FUNCTION,
|
||||
roc_types::subs::FlatType::Func(_, _, _) => CompletionItemKind::FUNCTION,
|
||||
roc_types::subs::FlatType::EmptyTagUnion
|
||||
| roc_types::subs::FlatType::TagUnion(_, _) => CompletionItemKind::ENUM,
|
||||
_ => CompletionItemKind::VARIABLE,
|
||||
},
|
||||
other => {
|
||||
debug!(
|
||||
"No specific completionKind for variable type: {:?} defaulting to 'Variable'",
|
||||
other
|
||||
);
|
||||
CompletionItemKind::VARIABLE
|
||||
}
|
||||
};
|
||||
|
||||
CompletionItem {
|
||||
label: symbol_str,
|
||||
detail: Some(type_str),
|
||||
kind: Some(typ),
|
||||
documentation: docs_opt.map(|docs| {
|
||||
lsp_types::Documentation::MarkupContent(lsp_types::MarkupContent {
|
||||
kind: lsp_types::MarkupKind::Markdown,
|
||||
value: docs,
|
||||
})
|
||||
}),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// E.g. a.b.c.d->{variable_name:"a", field:"d", middle_fields:["b","c"]}
|
||||
struct RecFieldCompletion {
|
||||
/// name of variable that is a record
|
||||
variable_name: String,
|
||||
field: String,
|
||||
middle_fields: Vec<String>,
|
||||
}
|
||||
|
||||
/// Finds the types of and names of all the fields of a record.
|
||||
/// `var` should be a `Variable` that you know is of type record or else it will return an empty list.
|
||||
fn find_record_fields(var: Variable, subs: &mut Subs) -> Vec<(String, Variable)> {
|
||||
let content = subs.get(var);
|
||||
match content.content {
|
||||
|
|
@ -484,22 +330,17 @@ fn find_record_fields(var: Variable, subs: &mut Subs) -> Vec<(String, Variable)>
|
|||
}
|
||||
}
|
||||
|
||||
struct FieldCompletion {
|
||||
var: String,
|
||||
field: String,
|
||||
middle_fields: Vec<String>,
|
||||
}
|
||||
///Splits a completion prefix for a field into its components
|
||||
///E.g. a.b.c.d->{var:"a",middle_fields:["b","c"],field:"d"}
|
||||
fn get_field_completion_parts(symbol_prefix: &str) -> Option<FieldCompletion> {
|
||||
/// Splits a completion prefix for a field into its components.
|
||||
/// E.g. a.b.c.d->{variable_name:"a",middle_fields:["b","c"],field:"d"}
|
||||
fn get_field_completion_parts(symbol_prefix: &str) -> Option<RecFieldCompletion> {
|
||||
let mut parts = symbol_prefix.split('.').collect::<Vec<_>>();
|
||||
let field = parts.pop().unwrap_or("").to_string();
|
||||
let var = parts.remove(0);
|
||||
//Now that we have the head and tail removed this is all the intermediate fields
|
||||
let variable_name = parts.remove(0).to_string();
|
||||
// Now that we have the head and tail removed this is all the intermediate fields.
|
||||
let middle_fields = parts.into_iter().map(ToString::to_string).collect();
|
||||
|
||||
Some(FieldCompletion {
|
||||
var: var.to_string(),
|
||||
Some(RecFieldCompletion {
|
||||
variable_name,
|
||||
field,
|
||||
middle_fields,
|
||||
})
|
||||
|
|
@ -512,25 +353,30 @@ pub fn field_completion(
|
|||
subs: &mut Subs,
|
||||
module_id: &ModuleId,
|
||||
) -> Option<Vec<CompletionItem>> {
|
||||
let FieldCompletion {
|
||||
var,
|
||||
let RecFieldCompletion {
|
||||
variable_name,
|
||||
field,
|
||||
middle_fields,
|
||||
} = get_field_completion_parts(&symbol_prefix)?;
|
||||
|
||||
debug!(
|
||||
"Getting record field completions: variable: {:?} field: {:?} middle: {:?} ",
|
||||
var, field, middle_fields
|
||||
variable_name, field, middle_fields
|
||||
);
|
||||
|
||||
let completion = get_completions(position, declarations, var.to_string(), interns)
|
||||
// We get completions here, but all we really want is the info about the variable that
|
||||
// is the first part of our record completion.
|
||||
// We are completing the full name of the variable so we should only have one match.
|
||||
let completion = get_completions(position, declarations, variable_name, interns)
|
||||
.into_iter()
|
||||
.map(|a| (a.0.as_str(interns).to_string(), a.1))
|
||||
.map(|(symbol, var)| (symbol.as_str(interns).to_string(), var))
|
||||
.next()?;
|
||||
|
||||
//If we have a type that has nested records we could have a completion prefix like: "var.field1.field2.fi"
|
||||
//If the document isn't fully typechecked we won't know what the type of field2 is for us to offer completions based on it's fields
|
||||
//Instead we get the type of "var" and then the type of "field1" within var's type and then "field2" within field1's type etc etc, until we have the type of the record we are actually looking for field completions for.
|
||||
// If we have a type that has nested records we could have a completion prefix like: "var.field1.field2.fi".
|
||||
// If the document isn't fully typechecked we won't know what the type of field2 is for us to offer
|
||||
// completions based on it's fields. Instead we get the type of "var" and then the type of "field1" within
|
||||
// var's type and then "field2" within field1's type etc etc, until we have the type of the record we are
|
||||
// actually looking for field completions for.
|
||||
let completion_record = middle_fields.iter().fold(completion, |state, chain_field| {
|
||||
let fields_vars = find_record_fields(state.1, subs);
|
||||
fields_vars
|
||||
|
|
@ -544,6 +390,8 @@ pub fn field_completion(
|
|||
.filter(|(str, _)| str.starts_with(&field.to_string()))
|
||||
.collect();
|
||||
|
||||
let field_completions = make_completion_items(subs, module_id, interns, field_completions);
|
||||
let field_completions =
|
||||
make_completion_items_string(subs, module_id, interns, field_completions);
|
||||
|
||||
Some(field_completions)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use roc_load::docs::ModuleDocumentation;
|
||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||
|
||||
use roc_types::subs::Variable;
|
||||
|
|
@ -40,12 +41,26 @@ pub(super) fn module_documentation(
|
|||
module_id: &ModuleId,
|
||||
interns: &Interns,
|
||||
exposed: &[(Symbol, Variable)],
|
||||
module_docs: Option<&ModuleDocumentation>,
|
||||
modules_info: &ModulesInfo,
|
||||
) -> Documentation {
|
||||
let exposed_string =
|
||||
get_module_exposed_list(module_id, interns, modules_info, exposed).unwrap_or_default();
|
||||
|
||||
let module_doc = module_docs
|
||||
.and_then(|docs| {
|
||||
docs.entries.first().and_then(|first_doc| match first_doc {
|
||||
roc_load::docs::DocEntry::ModuleDoc(str) => Some(str.clone().trim().to_string()),
|
||||
_ => None,
|
||||
})
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
match description_type {
|
||||
DescriptionsType::Exposes => md_doc(format!("```roc\n{0}\n```", exposed_string)),
|
||||
DescriptionsType::Exposes => md_doc(format!(
|
||||
"{0}```roc\n{1}\n```",
|
||||
module_doc + "\n",
|
||||
exposed_string
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
254
crates/lang_srv/src/analysis/completion/visitor.rs
Normal file
254
crates/lang_srv/src/analysis/completion/visitor.rs
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
use log::trace;
|
||||
|
||||
use roc_can::{
|
||||
def::Def,
|
||||
expr::{ClosureData, Expr, WhenBranch},
|
||||
pattern::{ListPatterns, Pattern, RecordDestruct, TupleDestruct},
|
||||
traverse::{walk_decl, walk_def, walk_expr, DeclarationInfo, Visitor},
|
||||
};
|
||||
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_region::all::{Loc, Position, Region};
|
||||
use roc_types::subs::Variable;
|
||||
|
||||
pub(crate) struct CompletionVisitor<'a> {
|
||||
pub(crate) position: Position,
|
||||
pub(crate) found_declarations: Vec<(Symbol, Variable)>,
|
||||
pub(crate) interns: &'a Interns,
|
||||
pub(crate) prefix: String,
|
||||
}
|
||||
|
||||
impl Visitor for CompletionVisitor<'_> {
|
||||
fn should_visit(&mut self, region: Region) -> bool {
|
||||
region.contains_pos(self.position)
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &Expr, region: Region, var: Variable) {
|
||||
if region.contains_pos(self.position) {
|
||||
let mut res = self.expression_defs(expr);
|
||||
self.found_declarations.append(&mut res);
|
||||
|
||||
walk_expr(self, expr, var);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_decl(&mut self, decl: DeclarationInfo<'_>) {
|
||||
match decl {
|
||||
DeclarationInfo::Value { loc_expr, .. }
|
||||
| DeclarationInfo::Function {
|
||||
loc_body: loc_expr, ..
|
||||
}
|
||||
| DeclarationInfo::Destructure { loc_expr, .. } => {
|
||||
let res = self.decl_to_completion_item(&decl);
|
||||
self.found_declarations.extend(res);
|
||||
|
||||
if loc_expr.region.contains_pos(self.position) {
|
||||
walk_decl(self, decl);
|
||||
};
|
||||
}
|
||||
_ => {
|
||||
walk_decl(self, decl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_def(&mut self, def: &Def) {
|
||||
let sym_var_vec = self.extract_defs(def);
|
||||
self.found_declarations.extend(sym_var_vec);
|
||||
|
||||
walk_def(self, def);
|
||||
}
|
||||
}
|
||||
|
||||
impl CompletionVisitor<'_> {
|
||||
fn extract_defs(&mut self, def: &Def) -> Vec<(Symbol, Variable)> {
|
||||
trace!("Completion begin");
|
||||
|
||||
def.pattern_vars
|
||||
.iter()
|
||||
.map(|(symbol, var)| (*symbol, *var))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn expression_defs(&self, expr: &Expr) -> Vec<(Symbol, Variable)> {
|
||||
match expr {
|
||||
Expr::When {
|
||||
expr_var, branches, ..
|
||||
} => self.when_is_expr(branches, expr_var),
|
||||
Expr::Closure(ClosureData {
|
||||
arguments,
|
||||
loc_body,
|
||||
..
|
||||
}) => {
|
||||
// if we are inside the closure complete it's vars
|
||||
if loc_body.region.contains_pos(self.position) {
|
||||
arguments
|
||||
.iter()
|
||||
.flat_map(|(var, _, pat)| self.patterns(&pat.value, var))
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract any variables made available by the branch of a when_is expression that contains `self.position`
|
||||
fn when_is_expr(
|
||||
&self,
|
||||
branches: &[WhenBranch],
|
||||
expr_var: &Variable,
|
||||
) -> Vec<(Symbol, Variable)> {
|
||||
branches
|
||||
.iter()
|
||||
.flat_map(
|
||||
|WhenBranch {
|
||||
patterns, value, ..
|
||||
}| {
|
||||
if value.region.contains_pos(self.position) {
|
||||
patterns
|
||||
.iter()
|
||||
.flat_map(|pattern| self.patterns(&pattern.pattern.value, expr_var))
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn record_destructure(&self, destructs: &[Loc<RecordDestruct>]) -> Vec<(Symbol, Variable)> {
|
||||
destructs
|
||||
.iter()
|
||||
.flat_map(|loc| match &loc.value.typ {
|
||||
roc_can::pattern::DestructType::Required
|
||||
| roc_can::pattern::DestructType::Optional(_, _) => {
|
||||
vec![(loc.value.symbol, loc.value.var)]
|
||||
}
|
||||
roc_can::pattern::DestructType::Guard(var, pat) => self.patterns(&pat.value, var),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn tuple_destructure(&self, destructs: &[Loc<TupleDestruct>]) -> Vec<(Symbol, Variable)> {
|
||||
destructs
|
||||
.iter()
|
||||
.flat_map(|loc| {
|
||||
let (var, pattern) = &loc.value.typ;
|
||||
self.patterns(&pattern.value, var)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn list_pattern(&self, list_elems: &ListPatterns, var: &Variable) -> Vec<(Symbol, Variable)> {
|
||||
list_elems
|
||||
.patterns
|
||||
.iter()
|
||||
.flat_map(|loc| self.patterns(&loc.value, var))
|
||||
.collect()
|
||||
}
|
||||
fn tag_pattern(&self, arguments: &[(Variable, Loc<Pattern>)]) -> Vec<(Symbol, Variable)> {
|
||||
arguments
|
||||
.iter()
|
||||
.flat_map(|(var, pat)| self.patterns(&pat.value, var))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn as_pattern(
|
||||
&self,
|
||||
as_pat: &Pattern,
|
||||
as_symbol: Symbol,
|
||||
var: &Variable,
|
||||
) -> Vec<(Symbol, Variable)> {
|
||||
// get the variables introduced within the pattern
|
||||
let mut patterns = self.patterns(as_pat, var);
|
||||
// add the "as" that wraps the whole pattern
|
||||
patterns.push((as_symbol, *var));
|
||||
patterns
|
||||
}
|
||||
|
||||
/// Returns a list of symbols defined by this pattern.
|
||||
/// `pattern_var`: Variable type of the entire pattern. This will be returned if
|
||||
/// the pattern turns out to be an identifier.
|
||||
fn patterns(
|
||||
&self,
|
||||
pattern: &roc_can::pattern::Pattern,
|
||||
pattern_var: &Variable,
|
||||
) -> Vec<(Symbol, Variable)> {
|
||||
match pattern {
|
||||
roc_can::pattern::Pattern::Identifier(symbol) => {
|
||||
if self.is_match(symbol) {
|
||||
vec![(*symbol, *pattern_var)]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
Pattern::AppliedTag { arguments, .. } => self.tag_pattern(arguments),
|
||||
Pattern::UnwrappedOpaque { argument, .. } => {
|
||||
self.patterns(&argument.1.value, &argument.0)
|
||||
}
|
||||
Pattern::List {
|
||||
elem_var, patterns, ..
|
||||
} => self.list_pattern(patterns, elem_var),
|
||||
roc_can::pattern::Pattern::As(pat, symbol) => {
|
||||
self.as_pattern(&pat.value, *symbol, pattern_var)
|
||||
}
|
||||
roc_can::pattern::Pattern::RecordDestructure { destructs, .. } => {
|
||||
self.record_destructure(destructs)
|
||||
}
|
||||
roc_can::pattern::Pattern::TupleDestructure { destructs, .. } => {
|
||||
self.tuple_destructure(destructs)
|
||||
}
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn is_match(&self, symbol: &Symbol) -> bool {
|
||||
symbol.as_str(self.interns).starts_with(&self.prefix)
|
||||
}
|
||||
|
||||
fn decl_to_completion_item(&self, decl: &DeclarationInfo) -> Vec<(Symbol, Variable)> {
|
||||
match decl {
|
||||
DeclarationInfo::Value {
|
||||
expr_var, pattern, ..
|
||||
} => self.patterns(pattern, expr_var),
|
||||
DeclarationInfo::Function {
|
||||
expr_var,
|
||||
pattern,
|
||||
function,
|
||||
loc_body,
|
||||
..
|
||||
} => {
|
||||
let mut sym_var_vec = vec![];
|
||||
// append the function declaration itself for recursive calls
|
||||
sym_var_vec.extend(self.patterns(pattern, expr_var));
|
||||
|
||||
if loc_body.region.contains_pos(self.position) {
|
||||
// also add the arguments if we are inside the function
|
||||
let args = function
|
||||
.value
|
||||
.arguments
|
||||
.iter()
|
||||
.flat_map(|(var, _, pat)| self.patterns(&pat.value, var));
|
||||
// we add in the pattern for the function declaration
|
||||
sym_var_vec.extend(args);
|
||||
|
||||
trace!(
|
||||
"Added function args to completion output =:{:#?}",
|
||||
sym_var_vec
|
||||
);
|
||||
}
|
||||
|
||||
sym_var_vec
|
||||
}
|
||||
DeclarationInfo::Destructure {
|
||||
loc_pattern,
|
||||
expr_var,
|
||||
..
|
||||
} => self.patterns(&loc_pattern.value, expr_var),
|
||||
DeclarationInfo::Expectation { .. } => vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -354,24 +354,33 @@ mod tests {
|
|||
|
||||
use super::*;
|
||||
|
||||
fn completion_resp_to_labels(resp: CompletionResponse) -> Vec<String> {
|
||||
fn completion_resp_to_strings(
|
||||
resp: CompletionResponse,
|
||||
) -> Vec<(String, Option<Documentation>)> {
|
||||
match resp {
|
||||
CompletionResponse::Array(list) => list.into_iter(),
|
||||
CompletionResponse::List(list) => list.items.into_iter(),
|
||||
}
|
||||
.map(|item| item.label)
|
||||
.map(|item| (item.label, item.documentation))
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
/// Gets completion and returns only the label for each completion
|
||||
async fn get_completion_labels(
|
||||
/// gets completion and returns only the label and docs for each completion
|
||||
async fn get_basic_completion_info(
|
||||
reg: &Registry,
|
||||
url: &Url,
|
||||
position: Position,
|
||||
) -> Option<Vec<String>> {
|
||||
) -> Option<Vec<(String, Option<Documentation>)>> {
|
||||
reg.completion_items(url, position)
|
||||
.await
|
||||
.map(completion_resp_to_labels)
|
||||
.map(completion_resp_to_strings)
|
||||
}
|
||||
|
||||
/// gets completion and returns only the label for each completion
|
||||
fn comp_labels(
|
||||
completions: Option<Vec<(String, Option<Documentation>)>>,
|
||||
) -> Option<Vec<String>> {
|
||||
completions.map(|list| list.into_iter().map(|(labels, _)| labels).collect())
|
||||
}
|
||||
|
||||
const DOC_LIT: &str = indoc! {r#"
|
||||
|
|
@ -403,7 +412,7 @@ mod tests {
|
|||
initial: &str,
|
||||
addition: &str,
|
||||
position: Position,
|
||||
) -> Option<std::vec::Vec<std::string::String>> {
|
||||
) -> Option<Vec<(String, Option<Documentation>)>> {
|
||||
let doc = DOC_LIT.to_string() + initial;
|
||||
let (inner, url) = test_setup(doc.clone()).await;
|
||||
let registry = &inner.registry;
|
||||
|
|
@ -413,10 +422,18 @@ mod tests {
|
|||
|
||||
inner.change(&url, change, 1).await.unwrap();
|
||||
|
||||
get_completion_labels(registry, &url, position).await
|
||||
get_basic_completion_info(registry, &url, position).await
|
||||
}
|
||||
|
||||
/// Tests that completion works properly when we apply an "as" pattern to an identifier.
|
||||
async fn completion_test_labels(
|
||||
initial: &str,
|
||||
addition: &str,
|
||||
position: Position,
|
||||
) -> Option<Vec<String>> {
|
||||
comp_labels(completion_test(initial, addition, position).await)
|
||||
}
|
||||
|
||||
/// Test that completion works properly when we apply an "as" pattern to an identifier
|
||||
#[tokio::test]
|
||||
async fn test_completion_as_identifier() {
|
||||
let suffix = DOC_LIT.to_string()
|
||||
|
|
@ -428,15 +445,15 @@ mod tests {
|
|||
|
||||
let (inner, url) = test_setup(suffix.clone()).await;
|
||||
let position = Position::new(6, 7);
|
||||
let reg = &inner.registry;
|
||||
let registry = &inner.registry;
|
||||
|
||||
let change = suffix.clone() + "o";
|
||||
inner.change(&url, change, 1).await.unwrap();
|
||||
let comp1 = get_completion_labels(reg, &url, position).await;
|
||||
let comp1 = comp_labels(get_basic_completion_info(registry, &url, position).await);
|
||||
|
||||
let c = suffix.clone() + "i";
|
||||
inner.change(&url, c, 2).await.unwrap();
|
||||
let comp2 = get_completion_labels(reg, &url, position).await;
|
||||
let comp2 = comp_labels(get_basic_completion_info(registry, &url, position).await);
|
||||
|
||||
let actual = [comp1, comp2];
|
||||
|
||||
|
|
@ -474,11 +491,11 @@ mod tests {
|
|||
|
||||
let change = doc.clone() + "o";
|
||||
inner.change(&url, change, 1).await.unwrap();
|
||||
let comp1 = get_completion_labels(reg, &url, position).await;
|
||||
let comp1 = comp_labels(get_basic_completion_info(reg, &url, position).await);
|
||||
|
||||
let c = doc.clone() + "t";
|
||||
inner.change(&url, c, 2).await.unwrap();
|
||||
let comp2 = get_completion_labels(reg, &url, position).await;
|
||||
let comp2 = comp_labels(get_basic_completion_info(reg, &url, position).await);
|
||||
let actual = [comp1, comp2];
|
||||
|
||||
expect![[r#"
|
||||
|
|
@ -502,10 +519,10 @@ mod tests {
|
|||
.assert_debug_eq(&actual);
|
||||
}
|
||||
|
||||
/// Tests that completion of function args in scope works properly.
|
||||
/// Test that completion works properly when we apply an "as" pattern to a record
|
||||
#[tokio::test]
|
||||
async fn test_completion_fun_params() {
|
||||
let actual = completion_test(
|
||||
let actual = completion_test_labels(
|
||||
indoc! {r"
|
||||
main = \param1, param2 ->
|
||||
"},
|
||||
|
|
@ -526,10 +543,10 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_completion_fun_params_map() {
|
||||
let actual = completion_test(
|
||||
async fn test_completion_closure() {
|
||||
let actual = completion_test_labels(
|
||||
indoc! {r"
|
||||
main = [] |> List.map \param1 , param2->
|
||||
main = [] |> List.map \ param1 , param2->
|
||||
"},
|
||||
"par",
|
||||
Position::new(4, 3),
|
||||
|
|
@ -545,4 +562,36 @@ mod tests {
|
|||
"#]]
|
||||
.assert_debug_eq(&actual);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_completion_with_docs() {
|
||||
let actual = completion_test(
|
||||
indoc! {r"
|
||||
## This is the main function
|
||||
main = mai
|
||||
"},
|
||||
"par",
|
||||
Position::new(4, 10),
|
||||
)
|
||||
.await;
|
||||
|
||||
expect![[r#"
|
||||
Some(
|
||||
[
|
||||
(
|
||||
"main",
|
||||
Some(
|
||||
MarkupContent(
|
||||
MarkupContent {
|
||||
kind: Markdown,
|
||||
value: "This is the main function",
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
"#]]
|
||||
.assert_debug_eq(&actual);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue