Code.Fragment (Elixir v1.13.0-dev) View Source

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 the cursor context.

Receives a string and returns the surround context.

Link to this section Types

Specs

position() :: {line :: pos_integer(), column :: pos_integer()}

Link to this section Functions

Link to this function

cursor_context(fragment, opts \\ [])

View Source (since 1.13.0)

Specs

cursor_context(
  List.Chars.t(),
  keyword()
) ::
  {: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, charlist()}
  | {:unquoted_atom, charlist()}
when inside_dot:
       {:alias, charlist()}
       | {:dot, inside_dot, charlist()}
       | {:module_attribute, charlist()}
       | {:unquoted_atom, charlist()}
       | {:var, 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 as Hello.Wor or HelloWor

  • {:dot, inside_dot, charlist} - the context is a dot where inside_dot is either a {:var, charlist}, {:alias, charlist}, {:module_attribute, charlist}, {:unquoted_atom, charlist} or a dot itself. If a var is given, this may either be a remote call or a map field access. Examples are Hello.wor, :hello.wor, hello.wor, Hello.nested.wor, hello.nested.wor, and @hello.world

  • {:dot_arity, inside_dot, charlist} - the context is a dot arity where inside_dot is either a {:var, charlist}, {:alias, charlist}, {:module_attribute, charlist}, {:unquoted_atom, charlist} or a dot itself. If a var is given, it must be a remote arity. Examples are Hello.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. where inside_dot is either a {:var, charlist}, {:alias, charlist}, {:module_attribute, charlist}, {:unquoted_atom, charlist} or a dot itself. If a var is given, it must be a remote call. Examples are Hello.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 as hello_wor

  • {:local_arity, charlist} - the context is a local (import or local) arity, such as hello_world/

  • {:local_call, charlist} - the context is a local (import or local) call, such as hello_world( and hello_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 as when 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/ or when/

  • {:operator_call, charlist} - the context is an operator call, which is an operator followed by space, such as left +, not or x 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, charlist} - the context is a struct, such as %, %UR or %URI

  • {: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. Other functions may be added in the future that consider the tree-structure of the code.

Link to this function

surround_context(fragment, position, options \\ [])

View Source (since 1.13.0)

Specs

surround_context(List.Chars.t(), position(), keyword()) ::
  %{begin: position(), end: position(), context: context} | :none
when context:
       {:alias, charlist()}
       | {:dot, inside_dot, charlist()}
       | {:local_or_var, charlist()}
       | {:local_arity, charlist()}
       | {:local_call, charlist()}
       | {:module_attribute, charlist()}
       | {:operator, charlist()}
       | {:unquoted_atom, charlist()},
     inside_dot:
       {:alias, charlist()}
       | {:dot, inside_dot, charlist()}
       | {:module_attribute, charlist()}
       | {:unquoted_atom, charlist()}
       | {:var, 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 expression 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 preceed 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 and operator_call/operator_arity are collapsed into dot and operator contexts respectively as they are not meaningful distinction between them

  • On the other hand, this function still makes a distinction between local_call/local_arity and local_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, ''} in cursor_context/2

  • This function never returns empty sigils {:sigil, ''} or empty structs {:struct, ''} as context