@@ -17,13 +17,13 @@ defmodule Code.Fragment do
17
17
18
18
This function receives a string with an Elixir code fragment,
19
19
representing a cursor position, and based on the string, it
20
- provides contextual information about said position. The
21
- return of this function can then be used to provide tips,
20
+ provides contextual information about the latest token.
21
+ The return of this function can then be used to provide tips,
22
22
suggestions, and autocompletion functionality.
23
23
24
- This function provides a best-effort detection and may not be
25
- accurate under all circumstances. See the "Limitations"
26
- section below.
24
+ This function performs its analyses on tokens. This means
25
+ it does not understand how constructs are nested within each
26
+ other. See the "Limitations" section below.
27
27
28
28
Consider adding a catch-all clause when handling the return
29
29
type of this function as new cursor information may be added
@@ -110,11 +110,28 @@ defmodule Code.Fragment do
110
110
* `{:unquoted_atom, charlist}` - the context is an unquoted atom. This
111
111
can be any atom or an atom representing a module
112
112
113
+ We recommend looking at the test suite of this function for a complete list
114
+ of examples and their return values.
115
+
113
116
## Limitations
114
117
115
- The current algorithm only considers the last line of the input. This means
116
- it will also show suggestions inside strings, heredocs, etc, which is
117
- intentional as it helps with doctests, references, and more.
118
+ The analysis is based on the current token, by analysing the last line of
119
+ the input. For example, this code:
120
+
121
+ iex> Code.Fragment.cursor_context("%URI{")
122
+ :expr
123
+
124
+ returns `:expr`, which suggests any variable, local function or alias
125
+ could be used. However, given we are inside a struct, the best suggestion
126
+ would be a struct field. In such cases, you can use
127
+ `container_cursor_to_quoted`, which will return the container of the AST
128
+ the cursor is currently within. You can then analyse this AST to provide
129
+ completion of field names.
130
+
131
+ As a consequence of its token-based implementation, this function considers
132
+ only the last line of the input. This means it will show suggestions inside
133
+ strings, heredocs, etc, which is intentional as it helps with doctests,
134
+ references, and more.
118
135
"""
119
136
@ doc since: "1.13.0"
120
137
@ spec cursor_context ( List.Chars . t ( ) , keyword ( ) ) ::
@@ -480,7 +497,8 @@ defmodule Code.Fragment do
480
497
This function receives a string with an Elixir code fragment
481
498
and a `position`. It returns a map containing the beginning
482
499
and ending of the identifier alongside its context, or `:none`
483
- if there is nothing with a known context.
500
+ if there is nothing with a known context. This is useful to
501
+ provide mouse-over and highlight functionality in editors.
484
502
485
503
The difference between `cursor_context/2` and `surround_context/3`
486
504
is that the former assumes the expression in the code fragment
@@ -509,10 +527,10 @@ defmodule Code.Fragment do
509
527
The returned map contains the column the expression starts and the
510
528
first column after the expression ends.
511
529
512
- Similar to `cursor_context/2`, this function also provides a best-effort
513
- detection and may not be accurate under all circumstances. See the
514
- "Return values" and "Limitations" section under `cursor_context/2` for
515
- more information.
530
+ Similar to `cursor_context/2`, this function is also token-based
531
+ and may not be accurate under all circumstances. See the
532
+ "Return values" and "Limitations" section under `cursor_context/2`
533
+ for more information.
516
534
517
535
## Examples
518
536
@@ -521,8 +539,8 @@ defmodule Code.Fragment do
521
539
522
540
## Differences to `cursor_context/2`
523
541
524
- Because `surround_context/3` deals with complete code, it has some
525
- difference to `cursor_context/2`:
542
+ Because `surround_context/3` attempts to capture complex expressions,
543
+ it has some differences to `cursor_context/2`:
526
544
527
545
* `dot_call`/`dot_arity` and `operator_call`/`operator_arity`
528
546
are collapsed into `dot` and `operator` contexts respectively
@@ -542,6 +560,8 @@ defmodule Code.Fragment do
542
560
543
561
* This function never returns `:expr`
544
562
563
+ We recommend looking at the test suite of this function for a complete list
564
+ of examples and their return values.
545
565
"""
546
566
@ doc since: "1.13.0"
547
567
@ spec surround_context ( List.Chars . t ( ) , position ( ) , keyword ( ) ) ::
@@ -937,17 +957,15 @@ defmodule Code.Fragment do
937
957
938
958
@ doc """
939
959
Receives a string and returns a quoted expression
940
- with a cursor at the nearest argument position .
960
+ with the cursor AST position within its parent expression .
941
961
942
962
This function receives a string with an Elixir code fragment,
943
963
representing a cursor position, and converts such string to
944
- AST with the inclusion of special `__cursor__()` node based
945
- on the position of the cursor with a container .
964
+ AST with the inclusion of special `__cursor__()` node representing
965
+ the cursor position within its parent .
946
966
947
- A container is any Elixir expression starting with `(`,
948
- `{`, and `[`. This includes function calls, tuples, lists,
949
- maps, and so on. For example, take this code, which would
950
- be given as input:
967
+ The parent node is any function call, tuple, list, map, and so on.
968
+ For example, take this code, which would be given as input:
951
969
952
970
max(some_value,
953
971
@@ -956,9 +974,9 @@ defmodule Code.Fragment do
956
974
max(some_value, __cursor__())
957
975
958
976
In other words, this function is capable of closing any open
959
- brackets and insert the cursor position. Any content at the
960
- cursor position that is after a comma or an opening bracket
961
- is discarded. For example, if this is given as input:
977
+ brackets and insert the cursor position. Other content at the
978
+ cursor position which is not a parent is discarded.
979
+ For example, if this is given as input:
962
980
963
981
max(some_value, another_val
964
982
@@ -1004,6 +1022,8 @@ defmodule Code.Fragment do
1004
1022
1005
1023
if(some_value, do: __cursor__())
1006
1024
1025
+ For multi-line blocks, all previous lines are preserved.
1026
+
1007
1027
The AST returned by this function is not safe to evaluate but
1008
1028
it can be analyzed and expanded.
1009
1029
@@ -1019,7 +1039,25 @@ defmodule Code.Fragment do
1019
1039
iex> Code.Fragment.container_cursor_to_quoted("[some, value")
1020
1040
{:ok, [{:some, [line: 1], nil}, {:__cursor__, [line: 1], []}]}
1021
1041
1022
- For binaries, the `::` is exclusively kept as an operator:
1042
+ If an expression is complete, then the whole expression is discarded
1043
+ and only the parent is returned:
1044
+
1045
+ iex> Code.Fragment.container_cursor_to_quoted("if(is_atom(var)")
1046
+ {:ok, {:if, [line: 1], [{:__cursor__, [line: 1], []}]}}
1047
+
1048
+ this means complete expressions themselves return only the cursor:
1049
+
1050
+ iex> Code.Fragment.container_cursor_to_quoted("if(is_atom(var))")
1051
+ {:ok, {:__cursor__, [line: 1], []}}
1052
+ iex> Code.Fragment.container_cursor_to_quoted("alias Foo.Bar")
1053
+ {:ok, {:__cursor__, [line: 1], []}}
1054
+
1055
+ Operators are never considered containers:
1056
+
1057
+ iex> Code.Fragment.container_cursor_to_quoted("if(foo +")
1058
+ {:ok, {:if, [line: 1], [{:__cursor__, [line: 1], []}]}}
1059
+
1060
+ with the exception of `::` inside binaries and `|` inside maps:
1023
1061
1024
1062
iex> Code.Fragment.container_cursor_to_quoted("<<some::integer")
1025
1063
{:ok, {:<<>>, [line: 1], [{:"::", [line: 1], [{:some, [line: 1], nil}, {:__cursor__, [line: 1], []}]}]}}
0 commit comments