View Source Code.Fragment (Elixir v1.14.0)
This module provides conveniences for analyzing fragments of textual code and extract available information whenever possible.
Most of the functions in this module provide a best-effort and may not be accurate under all circumstances. Read each documentation for more information.
This module should be considered experimental.
Link to this section Summary
Functions
Receives a string and returns a quoted expression with a cursor at the nearest argument position.
Receives a string and returns the cursor context.
Receives a string and returns the surround context.
Link to this section Types
@type position() :: {line :: pos_integer(), column :: pos_integer()}
Link to this section Functions
@spec container_cursor_to_quoted( List.Chars.t(), keyword() ) :: {:ok, Macro.t()} | {:error, {location :: keyword(), binary() | {binary(), binary()}, binary()}}
Receives a string and returns a quoted expression with a cursor at the nearest argument position.
This function receives a string with an Elixir code fragment,
representing a cursor position, and converts such string to
AST with the inclusion of special __cursor__()
node based
on the position of the cursor with a container.
A container is any Elixir expression starting with (
,
{
, and [
. This includes function calls, tuples, lists,
maps, and so on. For example, take this code, which would
be given as input:
max(some_value,
This function will return the AST equivalent to:
max(some_value, __cursor__())
In other words, this function is capable of closing any open brackets and insert the cursor position. Any content at the cursor position that is after a comma or an opening bracket is discarded. For example, if this is given as input:
max(some_value, another_val
It will return the same AST:
max(some_value, __cursor__())
Similarly, if only this is given:
max(some_va
Then it returns:
max(__cursor__())
Calls without parenthesis are also supported, as we assume the brackets are implicit.
Operators and anonymous functions are not containers, and therefore will be discarded. The following will all return the same AST:
max(some_value,
max(some_value, fn x -> x end
max(some_value, 1 + another_val
max(some_value, 1 |> some_fun() |> another_fun
On the other hand, tuples, lists, maps, and binaries all retain the cursor position:
max(some_value, [1, 2,
Returns the following AST:
max(some_value, [1, 2, __cursor__()])
Keyword lists (and do-end blocks) are also retained. The following:
if(some_value, do:
if(some_value, do: :token
if(some_value, do: 1 + val
all return:
if(some_value, do: __cursor__())
The AST returned by this function is not safe to evaluate but it can be analyzed and expanded.
Examples
Function call:
iex> Code.Fragment.container_cursor_to_quoted("max(some_value, ")
{:ok, {:max, [line: 1], [{:some_value, [line: 1], nil}, {:__cursor__, [line: 1], []}]}}
Containers (for example, a list):
iex> Code.Fragment.container_cursor_to_quoted("[some, value")
{:ok, [{:some, [line: 1], nil}, {:__cursor__, [line: 1], []}]}
For binaries, the ::
is exclusively kept as an operator:
iex> Code.Fragment.container_cursor_to_quoted("<<some::integer")
{:ok, {:<<>>, [line: 1], [{:"::", [line: 1], [{:some, [line: 1], nil}, {:__cursor__, [line: 1], []}]}]}}
Options
:file
- the filename to be reported in case of parsing errors. Defaults to"nofile"
.:line
- the starting line of the string being parsed. Defaults to 1.:column
- the starting column of the string being parsed. Defaults to 1.:columns
- whentrue
, attach a:column
key to the quoted metadata. Defaults tofalse
.:token_metadata
- whentrue
, includes token-related metadata in the expression AST, such as metadata fordo
andend
tokens, for closing tokens, end of expressions, as well as delimiters for sigils. SeeMacro.metadata/0
. Defaults tofalse
.:literal_encoder
- a function to encode literals in the AST. See the documentation forCode.string_to_quoted/2
for more information.
@spec cursor_context( List.Chars.t(), keyword() ) :: {:alias, charlist()} | {:alias, inside_alias, charlist()} | {:dot, inside_dot, charlist()} | {:dot_arity, inside_dot, charlist()} | {:dot_call, inside_dot, charlist()} | :expr | {:local_or_var, charlist()} | {:local_arity, charlist()} | {:local_call, charlist()} | {:module_attribute, charlist()} | {:operator, charlist()} | {:operator_arity, charlist()} | {:operator_call, charlist()} | :none | {:sigil, charlist()} | {:struct, inside_struct} | {:unquoted_atom, charlist()} when inside_dot: {:alias, charlist()} | {:alias, inside_alias, charlist()} | {:dot, inside_dot, charlist()} | {:module_attribute, charlist()} | {:unquoted_atom, charlist()} | {:var, charlist()}, inside_alias: {:local_or_var, charlist()} | {:module_attribute, charlist()}, inside_struct: charlist() | {:alias, inside_alias, charlist()} | {:local_or_var, charlist()} | {:module_attribute, charlist()} | {:dot, inside_dot, charlist()}
Receives a string and returns the cursor context.
This function receives a string with an Elixir code fragment, representing a cursor position, and based on the string, it provides contextual information about said position. The return of this function can then be used to provide tips, suggestions, and autocompletion functionality.
This function provides a best-effort detection and may not be accurate under all circumstances. See the "Limitations" section below.
Consider adding a catch-all clause when handling the return type of this function as new cursor information may be added in future releases.
Examples
iex> Code.Fragment.cursor_context("")
:expr
iex> Code.Fragment.cursor_context("hello_wor")
{:local_or_var, 'hello_wor'}
Return values
{:alias, charlist}
- the context is an alias, potentially a nested one, such asHello.Wor
orHelloWor
{:alias, inside_alias, charlist}
- the context is an alias, potentially a nested one, whereinside_alias
is an expression{:module_attribute, charlist}
or{:local_or_var, charlist}
andcharlist
is a static part Examples are__MODULE__.Submodule
or@hello.Submodule
{:dot, inside_dot, charlist}
- the context is a dot whereinside_dot
is either a{:var, charlist}
,{:alias, charlist}
,{:module_attribute, charlist}
,{:unquoted_atom, charlist}
or adot
itself. If a var is given, this may either be a remote call or a map field access. Examples areHello.wor
,:hello.wor
,hello.wor
,Hello.nested.wor
,hello.nested.wor
, and@hello.world
. Ifcharlist
is empty andinside_dot
is an alias, then the autocompletion may either be an alias or a remote call.{:dot_arity, inside_dot, charlist}
- the context is a dot arity whereinside_dot
is either a{:var, charlist}
,{:alias, charlist}
,{:module_attribute, charlist}
,{:unquoted_atom, charlist}
or adot
itself. If a var is given, it must be a remote arity. Examples areHello.world/
,:hello.world/
,hello.world/2
, and@hello.world/2
{:dot_call, inside_dot, charlist}
- the context is a dot call. This means parentheses or space have been added after the expression. whereinside_dot
is either a{:var, charlist}
,{:alias, charlist}
,{:module_attribute, charlist}
,{:unquoted_atom, charlist}
or adot
itself. If a var is given, it must be a remote call. Examples areHello.world(
,:hello.world(
,Hello.world
,hello.world(
,hello.world
, and@hello.world(
:expr
- may be any expression. Autocompletion may suggest an alias, local or var{:local_or_var, charlist}
- the context is a variable or a local (import or local) call, such ashello_wor
{:local_arity, charlist}
- the context is a local (import or local) arity, such ashello_world/
{:local_call, charlist}
- the context is a local (import or local) call, such ashello_world(
andhello_world
{:module_attribute, charlist}
- the context is a module attribute, such as@hello_wor
{:operator, charlist}
- the context is an operator, such as+
or==
. Note textual operators, such aswhen
do not appear as operators but rather as:local_or_var
.@
is never an:operator
and always a:module_attribute
{:operator_arity, charlist}
- the context is an operator arity, which is an operator followed by /, such as+/
,not/
orwhen/
{:operator_call, charlist}
- the context is an operator call, which is an operator followed by space, such asleft +
,not
orx when
:none
- no context possible{:sigil, charlist}
- the context is a sigil. It may be either the beginning of a sigil, such as~
or~s
, or an operator starting with~
, such as~>
and~>>
{:struct, inside_struct}
- the context is a struct, such as%
,%UR
or%URI
.inside_struct
can either be acharlist
in case of a static alias or an expression{:alias, inside_alias, charlist}
,{:module_attribute, charlist}
,{:local_or_var, charlist}
,{:dot, inside_dot, charlist}
{:unquoted_atom, charlist}
- the context is an unquoted atom. This can be any atom or an atom representing a module
Limitations
The current algorithm only considers the last line of the input. This means it will also show suggestions inside strings, heredocs, etc, which is intentional as it helps with doctests, references, and more.
surround_context(fragment, position, options \\ [])
View Source (since 1.13.0)@spec surround_context(List.Chars.t(), position(), keyword()) :: %{begin: position(), end: position(), context: context} | :none when context: {:alias, charlist()} | {:alias, inside_alias, charlist()} | {:dot, inside_dot, charlist()} | {:local_or_var, charlist()} | {:local_arity, charlist()} | {:local_call, charlist()} | {:module_attribute, charlist()} | {:operator, charlist()} | {:sigil, charlist()} | {:struct, inside_struct} | {:unquoted_atom, charlist()} | {:keyword, charlist()}, inside_dot: {:alias, charlist()} | {:alias, inside_alias, charlist()} | {:dot, inside_dot, charlist()} | {:module_attribute, charlist()} | {:unquoted_atom, charlist()} | {:var, charlist()}, inside_alias: {:local_or_var, charlist()} | {:module_attribute, charlist()}, inside_struct: charlist() | {:alias, inside_alias, charlist()} | {:local_or_var, charlist()} | {:module_attribute, charlist()} | {:dot, inside_dot, charlist()}
Receives a string and returns the surround context.
This function receives a string with an Elixir code fragment
and a position
. It returns a map containing the beginning
and ending of the identifier alongside its context, or :none
if there is nothing with a known context.
The difference between cursor_context/2
and surround_context/3
is that the former assumes the expression in the code fragment
is incomplete. For example, do
in cursor_context/2
may be
a keyword or a variable or a local call, while surround_context/3
assumes the expression in the code fragment is complete, therefore
do
would always be a keyword.
The position
contains both the line
and column
, both starting
with the index of 1. The column must precede the surrounding expression.
For example, the expression foo
, will return something for the columns
1, 2, and 3, but not 4:
foo
^ column 1
foo
^ column 2
foo
^ column 3
foo
^ column 4
The returned map contains the column the expression starts and the first column after the expression ends.
Similar to cursor_context/2
, this function also provides a best-effort
detection and may not be accurate under all circumstances. See the
"Return values" and "Limitations" section under cursor_context/2
for
more information.
Examples
iex> Code.Fragment.surround_context("foo", {1, 1})
%{begin: {1, 1}, context: {:local_or_var, 'foo'}, end: {1, 4}}
Differences to cursor_context/2
Because surround_context/3
deals with complete code, it has some
difference to cursor_context/2
:
dot_call
/dot_arity
andoperator_call
/operator_arity
are collapsed intodot
andoperator
contexts respectively as there aren't any meaningful distinctions between themOn the other hand, this function still makes a distinction between
local_call
/local_arity
andlocal_or_var
, since the latter can be a local or variable@
when not followed by any identifier is returned as{:operator, '@'}
(in contrast to{:module_attribute, ''}
incursor_context/2
This function never returns empty sigils
{:sigil, ''}
or empty structs{:struct, ''}
as contextThis function returns keywords as
{:keyword, 'do'}
This function never returns
:expr