-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Description
Hi everyone,
This is as second take on "building environments for code fragments" for IDEs (follow up on #12566). The goal of this issue is to propose a rich data structure for tracking fragment/buffers. Let's start with some background.
Status quo
Currently, Code.Fragment
contains functions that analyze where we are in a code snippet:
-
cursor_context
tells us where we are in a token -
container_cursor_to_quoted
tells us where we are in the AST (improved in Elixir v1.15 to provide additional context)
So while Elixir can tell that you are inside a alias Enumera
call or that you are in the middle of typing Code.Fr
, it does not provide any mechanism for telling you that Code.Fragment
is a valid autocompletion for Code.Fr
.
In order for us to provide such completions, we need to consider three environments:
-
The fragment/buffer environment. This is a file in your editor, a cell in Livebook, or a prompt in IEx. This file may or may not be valid Elixir.
-
The eval environment. This comes from previous cells in Livebook and previously executed code in IEx. AFAIK, it does not exist in IDEs.
-
The global environment. This is all modules, dependencies, etc. Exactly how those modules are compiled and searchable is a separate problem.
The goal of this issue is to build infrastructure to solve the fragment/buffer environment.
Fragment/buffer environment
Take the following code (think of text in your editor):
defmodule Foo do
import Baz
def bat do
var = 123
{<CURSOR>
end
def local_function do
...
end
end
Our goal is to know what is the fragment/buffer environment so we can use it for completion and other features. For example: what has been imported from Baz that is available inside bat
? What are the local functions visible from bat
? And so on.
After talking with @michalmuskala, we want to approach this problem using the following techniques:
-
First the whole file is loaded as a fragment/buffer
-
We will process the fragment using a robust parser that will be capable of ignoring incomplete expressions, such as
{
in the example above -
Once the fragment is processed, we will build a tree of scopes. For example, both
bat
andlocal_function
, each delimited by theirdo/end
blocks, depend on the scope fromdefmodule Foo do
-
As the user changes those files, we will replicate those diffs on top of the fragment. For example, if a line is added, then we add said line to the buffer in a way we can reuse scopes and minimize additional computations
Therefore, the goal is to always be able to quickly answer which functions, variables, imports, aliases, are available at any point in a fragment, ultimately reducing latency in IDE features. I believe this will allow us to replace elixirsense
in a way that is robust and performant.
The tasks above are major efforts in themselves, so we will need further issues to break them down, but this issue introduces a general direction.
PS: it is yet unclear if the buffer will track function/macro calls - in order to provide go-to definition and similar - or only the environment. However, note that today it is not trivial to provide mouse-over suggestions with
surround_context
because it doesn't tell us the arity. Livebook addresses this by explicitly looking after|>
operators, but that does not cover all cases.