Skip to content

Commit 775a60a

Browse files
committed
Big v0.2 code dump
1 parent 65c47a8 commit 775a60a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+2176
-2335
lines changed

CONTRIBUTING.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
While you can develop ElixirLS on its own, it's easiest to test out changes if you clone the [vscode-elixir-ls](https://github.com/JakeBecker/vscode-elixir-ls) repository instead. It includes ElixirLS as a Git submodule, so you can make your changes in the submodule directory and launch the extension from the parent directory via the included "Launch Extension" configuration in `launch.json`. See the README on that repo for more details.
2+
3+
### Building and running
4+
5+
To build for release, run the `release.sh` script. It compiles these two apps to escripts in the `release` folder. If doing a public release, compile it with Erlang OTP 19, since OTP 20 builds are not backwards-compatible with earlier versions of Erlang.
6+
7+
Elixir escripts typically embed Elixir, which is not what we want because users will want to run it against their own system Elixir installation. In order to support this, there are scripts in the `bin` folder that look for the system-wide Elixir installation and set the `ERL_LIBS` environment variable prior to running the escripts. These scripts are copied to the `release` folder by `release.sh`.

README.md

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,32 @@
11
# Elixir Language Server (ElixirLS)
22

3-
*This is a very early release. It only works with Elixir 1.4, and it's only been tested on OSX. If all you want is a good, stable Elixir editor, stick with whatever you're using now until ElixirLS is more mature.*
3+
The Elixir Language Server provides a server that runs in the background, providing IDEs, editors, and other tools with information about Elixir Mix projects. It adheres to the [Language Server Protocol](https://github.com/Microsoft/language-server-protocol), a standard for frontend-independent IDE support. Debugger integration is accomplished through the similar [VS Code Debug Protocol](https://code.visualstudio.com/docs/extensionAPI/api-debugging).
44

5-
The Elixir Language Server provides a server that runs in the background, providing IDEs, editors, and other tools with information about Elixir Mix projects. It adheres to the [Language Server Protocol](https://github.com/Microsoft/language-server-protocol), a standard supported by Microsoft and Red Hat for frontend-independent IDE support. Debugger integration is accomplished through the similar [VS Code Debug Protocol](https://code.visualstudio.com/docs/extensionAPI/api-debugging).
5+
## Features
66

7-
Features include:
8-
9-
- Debugger support!!!
10-
- Inline reporting of build warnings and errors
7+
- Debugger support (requires Erlang >= OTP 19)
8+
- Automatic, incremental Dialyzer analysis (requires Erlang OTP 20)
9+
- Inline reporting of build warnings and errors (requires Elixir >= 1.6)
1110
- Documentation lookup on hover
1211
- Go-to-definition
1312
- Code completion
13+
- Code formatter (requries Elixir >= 1.6)
14+
15+
![Screenshot](images/screenshot.png?raw=true)
16+
17+
## Supported versions
18+
19+
Elixir:
20+
- 1.3 minimum
21+
- \>= 1.6.0-dev recommended. Required for reporting of build warnings and errors, and for code formatting support.
1422

15-
![Screenshot](screenshot.png?raw=true)
23+
Erlang:
24+
- OTP 18 minimum
25+
- OTP 20 recommended. >= OTP 19 is required for debugger support, and OTP 20 is recommended for automatic incremental Dialyzer integration.
1626

17-
## IDE support
27+
You may want to install Elixir and Erlang from source, using the [kiex](https://github.com/taylor/kiex) and [kerl](https://github.com/kerl/kerl) tools. This will let you go-to-definition for core Elixir and Erlang modules. Use `kiex install master` to install a pre-release version of Elixir.
28+
29+
## IDE plugins
1830

1931
ElixirLS is intended to be frontend-independent, but at this point has only been tested with VS Code. You can install the VS Code extension by searching ElixirLS from the "extensions" pane. The plugin code resides in the [vscode-elixir-ls](https://github.com/JakeBecker/vscode-elixir-ls) repo.
2032

@@ -43,24 +55,18 @@ In order to debug modules in `.exs` files (such as tests), they must be specifie
4355

4456
## Automatic builds and error reporting
4557

46-
In order to provide features like documentation look-up and code completion, ElixirLS needs to be able to load your project's compiled modules. ElixirLS attempts to compile your project automatically and reports build errors and warnings in the editor.
47-
48-
At the moment, this compilation is performed using a fork of Elixir 1.4's compiler. This is not a good long-term solution and replacing it is a high priority in the near future. See [this blog post](https://medium.com/@JakeBeckerCode/compiler-hacks-in-elixirls-6a6f04834f66) for an explanation.
49-
50-
To avoid interfering with the developer's CLI workflow, ElixirLS creates a folder `.elixir_ls` in the project root and saves its build output there, so add `.elixir_ls` to your gitignore file.
51-
52-
Note that compiling untrusted Elixir files can be dangerous. Elixir can execute arbitrary code at compile time, which is how it can support such extensive metaprogramming. Consequently, compiling untrusted Elixir code is a lot like executing an untrusted script. Since ElixirLS compiles your code automatically, opening a project in an ElixirLS-enabled editor has the same risks as running `mix compile`. It's an unlikely attack vector, but worth being aware of.
58+
Builds are performed automatically when files are saved. If you want this to happen automatically when you type, you can turn on "autosave" in your IDE.
5359

54-
## Contributing
60+
Starting in Elixir 1.6, Mix compilers adhere to the [Mix.Task.Compiler](https://hexdocs.pm/mix/master/Mix.Task.Compiler.html) behaviour and return their error and warning diagnostics in a standardized way. If you're using Elixir >= 1.6, errors and warnings will be shown inline in your code as well as in the "Problems" pane in the IDE. If you're using an earlier version of Elixir, you'll need to look at the text log from the extension to see the errors and warnings.
5561

56-
While you can develop ElixirLS on its own, it's easiest to test out changes if you clone the [vscode-elixir-ls](https://github.com/JakeBecker/vscode-elixir-ls) repository instead. It includes ElixirLS as a Git submodule, so you can make your changes in the submodule directory and launch the extension from the parent directory via the included "Launch Extension" configuration in `launch.json`. See the README on that repo for more details.
62+
## Dialyzer integration
5763

58-
### Building and running
64+
If you're using Erlang >= OTP 20, ElixirLS will automatically analyze your project with Dialyzer after each successful build. It maintains a "manifest" file in `.elixir_ls/dialyzer_manifest` that stores the results of the analysis. The initial analysis for a project can take a few minutes, but after that's completed, modules are re-analyzed only if necessary, so subsequent analyses are typically very fast -- often less than a second. It also looks at your modules' abstract code to determine whether they reference any modules that haven't been analyzed and includes them automatically.
5965

60-
You can run the apps with `mix run` from the `apps/language_server` or `apps/debugger` directories. To build for release, run the `release.sh` script. It compiles these two apps to escripts in the `release` folder.
66+
You can control which warnings are shown using the `elixirLS.dialyzerWarnOpts` setting in your project or IDE's `settings.json`. To disable it completely, set `elixirLS.dialyzerEnabled` to false.
6167

62-
Elixir escripts typically embed Elixir, which is not what we want because users will want to run it against their own system Elixir installation. In order to support this, there are scripts in the `bin` folder that look for the system-wide Elixir installation and set the `ERL_LIBS` environment variable prior to running the escripts. These scripts are copied to the `release` folder by `release.sh`.
68+
ElixirLS's Dialyzer integration uses internal, undocumented Dialyzer APIs, and so it won't be robust against changes to these APIs in future Erlang versions.
6369

6470
## Acknowledgements and related projects
6571

66-
ElixirLS isn't the first frontend-independent server for Elixir language support. The original was [Alchemist Server](https://github.com/tonini/alchemist-server/), which powers the [Alchemist](https://github.com/tonini/alchemist.el) plugin for Emacs. Another project, [Elixir Sense](https://github.com/msaraiva/elixir_sense), builds upon Alchemist and powers the [Elixir plugin for Atom](https://github.com/msaraiva/atom-elixir) as well as another VS Code plugin, [VSCode Elixir](https://github.com/fr1zle/vscode-elixir). ElixirLS uses Elixir Sense for several code insight features. Credit for those projects goes to their respective authors.
72+
ElixirLS isn't the first frontend-independent server for Elixir language support. The original was [Alchemist Server](https://github.com/tonini/alchemist-server/), which powers the [Alchemist](https://github.com/tonini/alchemist.el) plugin for Emacs. Another project, [Elixir Sense](https://github.com/msaraiva/elixir_sense), builds upon Alchemist and powers the [Elixir plugin for Atom](https://github.com/msaraiva/atom-elixir) as well as another VS Code plugin, [VSCode Elixir](https://github.com/fr1zle/vscode-elixir). ElixirLS uses Elixir Sense for several code insight features. Credit for those projects goes to their respective authors.

apps/debugger/lib/debugger.ex

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,7 @@ defmodule ElixirLS.Debugger do
1313
children = [
1414
# Define workers and child supervisors to be supervised
1515
worker(ElixirLS.Debugger.Output, [ElixirLS.Debugger.Output]),
16-
worker(ElixirLS.Debugger.OutputDevice,
17-
[:user, "stdout", [change_all_gls?: change_all_gls?()]],
18-
[id: ElixirLS.Debugger.OutputDevice.Stdout]),
19-
worker(ElixirLS.Debugger.OutputDevice, [:standard_error, "stderr"],
20-
[id: ElixirLS.Debugger.OutputDevice.Stderr]),
2116
worker(ElixirLS.Debugger.Server, [[name: ElixirLS.Debugger.Server]]),
22-
worker(ElixirLS.IOHandler, [ElixirLS.Debugger.Server, [name: ElixirLS.Debugger.IOHandler]]),
2317
]
2418

2519
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
@@ -29,10 +23,14 @@ defmodule ElixirLS.Debugger do
2923
end
3024

3125
def stop(_state) do
32-
:init.stop
33-
end
26+
# If IO is being intercepted (meaning we're running in production), allow time to flush errors
27+
# then kill the VM
28+
if ElixirLS.Utils.WireProtocol.io_intercepted?() do
29+
IO.puts("Stopping ElixirLS debugger due to errors.")
30+
:timer.sleep(100)
31+
:init.stop(1)
32+
end
3433

35-
defp change_all_gls? do
36-
!(Enum.any?(Application.started_applications, &match?({:mix, _, _}, &1)) and Mix.env == :test)
34+
:ok
3735
end
3836
end

apps/debugger/lib/debugger/cli.ex

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
defmodule ElixirLS.Debugger.CLI do
2-
2+
alias ElixirLS.Utils.WireProtocol
3+
alias ElixirLS.Debugger.{Output, Server}
4+
35
def main(_args) do
6+
WireProtocol.intercept_output(&Output.print/1, &Output.print_err/1)
7+
8+
Application.ensure_all_started(:elixir_ls_debugger, :permanent)
9+
410
Mix.Local.append_archives
511
Mix.Local.append_paths
6-
:timer.sleep(:infinity)
12+
13+
WireProtocol.stream_packets(&Server.receive_packet/1)
714
end
8-
end
15+
end

apps/debugger/lib/debugger/output.ex

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@ defmodule ElixirLS.Debugger.Output do
22
@moduledoc """
33
Implements the JSON-based request protocol for VS Code debug adapters.
44
5-
VS Code debug protocol specifies that a message is either a request, a response, or an event.
5+
VS Code debug protocol specifies that a message is either a request, a response, or an event.
66
All messages must include a sequence number. This server keeps a counter to ensure that messages
77
are sent with sequence numbers that are unique and sequential, and includes client functions for
88
sending these messages.
99
"""
10+
import ElixirLS.Utils.WireProtocol, only: [send: 1]
1011
use GenServer
1112
use ElixirLS.Debugger.Protocol
1213

1314
## Client API
1415

1516
def start_link(name \\ nil) do
16-
GenServer.start_link(__MODULE__, 1, name: name)
17+
GenServer.start(__MODULE__, 1, name: name)
1718
end
1819

1920
def send_response(server \\ __MODULE__, request_packet, response_body) do
@@ -24,19 +25,35 @@ defmodule ElixirLS.Debugger.Output do
2425
GenServer.call(server, {:send_event, event, body})
2526
end
2627

28+
def print(server \\ __MODULE__, str) do
29+
send_event(
30+
server,
31+
"output",
32+
%{"category" => "stdout", "output" => to_string(str)}
33+
)
34+
end
35+
36+
def print_err(server \\ __MODULE__, str) do
37+
send_event(
38+
server,
39+
"output",
40+
%{"category" => "stderr", "output" => to_string(str)}
41+
)
42+
end
43+
2744
## Server callbacks
2845

2946
def handle_call({:send_response, request_packet, body}, _from, seq) do
30-
ElixirLS.IOHandler.send(response(seq, request_packet["seq"], request_packet["command"], body))
47+
send(response(seq, request_packet["seq"], request_packet["command"], body))
3148
{:reply, :ok, seq + 1}
3249
end
3350

3451
def handle_call({:send_event, event, body}, _from, seq) do
35-
ElixirLS.IOHandler.send(event(seq, event, body))
52+
send(event(seq, event, body))
3653
{:reply, :ok, seq + 1}
3754
end
3855

3956
def handle_call(message, from, s) do
4057
super(message, from, s)
4158
end
42-
end
59+
end

apps/debugger/lib/debugger/server.ex

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ defmodule ElixirLS.Debugger.Server do
5757
## Server Callbacks
5858

5959
def init(opts) do
60+
Process.flag(:trap_exit, true)
6061
:int.start
6162
state = if opts[:output], do: %__MODULE__{output: opts[:output]}, else: %__MODULE__{}
6263
{:ok, state}
@@ -97,30 +98,23 @@ defmodule ElixirLS.Debugger.Server do
9798
{:noreply, %{state | task_ref: nil}}
9899
end
99100

100-
def handle_info(msg, state) do
101-
super(msg, state)
102-
end
103-
104-
def terminate(:normal, state) do
105-
super(:normal, state)
101+
def handle_info({:EXIT, _, :normal}, state) do
102+
{:noreply, state}
106103
end
107104

108-
def terminate(reason, state) do
109-
IO.puts :standard_error, "Debug server terminated abnormally because " <>
110-
Exception.format_exit(reason)
111-
super(reason, state)
105+
def handle_info(msg, state) do
106+
super(msg, state)
112107
end
113108

114109
## Helpers
115110

116111
defp handle_request(initialize_req(_, client_info), state) do
117112
check_erlang_version()
118-
Process.group_leader(self(), Process.whereis(:user))
119113
{capabilities(), %{state | client_info: client_info}}
120114
end
121115

122116
defp handle_request(launch_req(_, config), state) do
123-
Process.spawn(fn -> initialize(config) end, [:link])
117+
initialize(config)
124118
{%{}, %{state | config: config}}
125119
end
126120

@@ -406,23 +400,23 @@ defmodule ElixirLS.Debugger.Server do
406400
set_stack_trace_mode(config["stackTraceMode"])
407401

408402
File.cd!(project_dir)
409-
Application.ensure_started(:mix)
410-
Mix.Local.append_archives
411-
Mix.Local.append_paths
412-
413403
Code.load_file(System.get_env("MIX_EXS") || "mix.exs")
414404
task = task || Mix.Project.config[:default_task]
415405
unless mix_env, do: change_env(task)
416406

417-
Mix.Tasks.Loadconfig.run([])
418-
Mix.Tasks.Deps.Loadpaths.run([])
419-
Mix.Tasks.Loadpaths.run([])
407+
Mix.Task.run("loadconfig")
420408
unless is_list(task_args) and "--no-compile" in task_args do
421-
Mix.Tasks.Compile.run([])
409+
case Mix.Task.run("compile", ["--ignore-module-conflict"]) do
410+
{:error, _} ->
411+
IO.puts(:standard_error, "Aborting debugger due to compile errors")
412+
:init.stop(1)
413+
_ ->
414+
:ok
415+
end
422416
end
423417

424-
# Reenable all tasks in case they're run from the debugged code
425-
for task <- Mix.Task.all_modules, do: Mix.Task.reenable(task)
418+
Mix.Task.run("app.start", [])
419+
# Mix.TasksServer.clear()
426420

427421
interpret_modules_in(Mix.Project.build_path)
428422

@@ -501,8 +495,7 @@ defmodule ElixirLS.Debugger.Server do
501495
end
502496

503497
defp launch_task(task, args) do
504-
Process.group_leader(self(), Process.whereis(:user))
505-
Mix.Task.rerun(task, args)
498+
Mix.Task.run(task, args)
506499
end
507500

508501
# Interpreting modules defined in .exs files requires that we first load the file and save any

apps/debugger/lib/debugger/variables.ex

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ defmodule ElixirLS.Debugger.Variables do
3434
end
3535

3636
def children(var, start, count) when is_map(var) do
37-
children =
37+
children =
3838
var
3939
|> Map.to_list
4040
|> Enum.slice(start || 0, count || map_size(var))
4141

4242
for {key, value} <- children do
43-
name =
43+
name =
4444
if is_atom(key) and not String.starts_with?(to_string(key), "Elixir.") do
4545
to_string(key)
4646
else
@@ -53,7 +53,7 @@ defmodule ElixirLS.Debugger.Variables do
5353
def children(_var, _start, _count) do
5454
[]
5555
end
56-
56+
5757
def num_children(var) when is_list(var) do
5858
Enum.count(var)
5959
end
@@ -66,11 +66,11 @@ defmodule ElixirLS.Debugger.Variables do
6666
if byte_size(var) > 1, do: byte_size(var), else: 0
6767
end
6868

69-
def num_children(var) when is_tuple(var) do
69+
def num_children(var) when is_tuple(var) do
7070
tuple_size(var)
7171
end
7272

73-
def num_children(var) when is_map(var) do
73+
def num_children(var) when is_map(var) do
7474
map_size(var)
7575
end
7676

@@ -96,8 +96,8 @@ defmodule ElixirLS.Debugger.Variables do
9696
def type(_), do: "term"
9797

9898
defp with_index_as_name(vars, start) do
99-
for {var, idx} <- Enum.with_index(vars, start || 0) do
99+
for {var, idx} <- Enum.with_index(vars, start) do
100100
{"#{idx}", var}
101101
end
102102
end
103-
end
103+
end

apps/debugger/mix.exs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ defmodule ElixirLS.Debugger.Mixfile do
33

44
def project do
55
[app: :debugger,
6-
version: "0.1.0",
6+
version: "0.2.0",
77
build_path: "../../_build",
88
config_path: "config/config.exs",
99
deps_path: "../../deps",
1010
lockfile: "../../mix.lock",
11-
elixir: "~> 1.3",
11+
elixir: "~> 1.6.0-dev",
1212
build_embedded: false,
1313
start_permanent: true,
1414
build_per_environment: false,
@@ -22,7 +22,7 @@ defmodule ElixirLS.Debugger.Mixfile do
2222
# Type "mix help compile.app" for more information
2323
def application do
2424
# Specify extra applications you'll use from Erlang/Elixir
25-
[mod: {ElixirLS.Debugger, []}, applications: []]
25+
[mod: {ElixirLS.Debugger, []}, extra_applications: [:mix, :logger]]
2626
end
2727

2828
# Dependencies can be Hex packages:
@@ -40,14 +40,15 @@ defmodule ElixirLS.Debugger.Mixfile do
4040
# Type "mix help deps" for more examples and options
4141
defp deps do
4242
[{:elixir_sense, github: "msaraiva/elixir_sense"},
43-
{:io_handler, in_umbrella: true}]
43+
{:elixir_ls_utils, in_umbrella: true}]
4444
end
4545

4646
defp escript do
47-
[main_module: ElixirLS.Debugger.CLI,
48-
embed_elixir: false,
47+
[main_module: ElixirLS.Debugger.CLI,
48+
app: nil,
49+
embed_elixir: false,
4950
path: "../../release/debugger",
50-
strip_beam: false,
51+
strip_beam: false,
5152
comment: escript_comment()]
5253
end
5354

0 commit comments

Comments
 (0)