Skip to content

Commit bcb7e2e

Browse files
authored
feat: spitfire (#368)
Incorporates experimental usage of the [Spitfire](https://github.com/elixir-tools/spitfire) parser. To enable, the server should be started with `NEXTLS_SPITFIRE_ENABLED=1`. `elixir-tools.nvim` and `elixir-tools.vscode` will have settings to enable this for you.
1 parent 77b92f8 commit bcb7e2e

File tree

16 files changed

+217
-125
lines changed

16 files changed

+217
-125
lines changed

.github/workflows/ci.yaml

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,90 +7,97 @@ on:
77
jobs:
88
tests:
99
runs-on: ${{matrix.os}}
10-
name: Test (${{matrix.os}})
10+
name: Test (${{matrix.os}}) - spitfire=${{matrix.spitfire}}
1111

1212
strategy:
1313
matrix:
14+
spitfire: [0, 1]
1415
os:
1516
- ubuntu-latest
1617
- macos-14
1718

1819
steps:
1920
- uses: actions/checkout@v4
20-
- uses: jdx/mise-action@v2
21+
- uses: DeterminateSystems/nix-installer-action@main
22+
- uses: DeterminateSystems/magic-nix-cache-action@main
23+
2124
- uses: actions/cache@v4
2225
with:
2326
path: |
2427
deps
2528
_build
26-
key: ${{ matrix.os }}-mix-${{ hashFiles('**/.mise.toml') }}-${{ hashFiles('**/mix.lock') }}
29+
key: ${{ matrix.os }}-mix-${{ hashFiles('**/flake.nix') }}-${{ hashFiles('**/mix.lock') }}
2730
restore-keys: |
28-
${{ matrix.os }}-mix-${{ hashFiles('**/.mise.toml') }}-
31+
${{ matrix.os }}-mix-${{ hashFiles('**/flake.nix') }}-
2932
3033
- name: Install Dependencies
31-
run: mix deps.get
34+
run: nix develop --command bash -c 'mix deps.get'
3235

3336
- name: Start EPMD
34-
run: epmd -daemon
37+
run: nix develop --command bash -c 'epmd -daemon'
3538

3639
- name: Compile
3740
env:
3841
MIX_ENV: test
39-
run: mix compile
42+
run: nix develop --command bash -c 'mix compile'
4043

4144
- name: remove tmp dir
4245
run: rm -rf tmp
4346

4447
- name: Run Tests
45-
run: elixir --erl '-kernel prevent_overlapping_partitions false' -S mix test --max-cases 1
48+
env:
49+
NEXTLS_SPITFIRE_ENABLED: ${{ matrix.spitfire }}
50+
run: nix develop --command bash -c "elixir --erl '-kernel prevent_overlapping_partitions false' -S mix test --max-cases 1"
4651

4752
formatter:
4853
runs-on: ubuntu-latest
4954
name: Formatter
5055

5156
steps:
5257
- uses: actions/checkout@v4
53-
- uses: jdx/mise-action@v2
58+
- uses: DeterminateSystems/nix-installer-action@main
59+
- uses: DeterminateSystems/magic-nix-cache-action@main
5460
- uses: actions/cache@v4
5561
with:
5662
path: |
5763
deps
5864
_build
59-
key: ${{ runner.os }}-mix-${{ hashFiles('**/.mise.toml') }}-${{ hashFiles('**/mix.lock') }}
65+
key: ${{ runner.os }}-mix-${{ hashFiles('**/flake.nix') }}-${{ hashFiles('**/mix.lock') }}
6066
restore-keys: |
61-
${{ runner.os }}-mix-${{ hashFiles('**/.mise.toml') }}-
67+
${{ runner.os }}-mix-${{ hashFiles('**/flake.nix') }}-
6268
6369
- name: Install Dependencies
64-
run: mix deps.get
70+
run: nix develop --command bash -c 'mix deps.get'
6571

6672
- name: Run Formatter
67-
run: mix format --check-formatted
73+
run: nix develop --command bash -c 'mix format --check-formatted'
6874

6975
dialyzer:
7076
runs-on: ubuntu-latest
7177
steps:
7278
- uses: actions/checkout@v4
73-
- uses: jdx/mise-action@v2
79+
- uses: DeterminateSystems/nix-installer-action@main
80+
- uses: DeterminateSystems/magic-nix-cache-action@main
7481

7582
# Don't cache PLTs based on mix.lock hash, as Dialyzer can incrementally update even old ones
7683
# Cache key based on Elixir & Erlang version (also useful when running in matrix)
7784
- name: Restore PLT cache
7885
uses: actions/cache/restore@v4
7986
id: plt_cache
8087
with:
81-
key: ${{ runner.os }}-mix-${{ hashFiles('**/.mise.toml') }}-${{ hashFiles('**/mix.lock') }}
88+
key: ${{ runner.os }}-mix-${{ hashFiles('**/flake.nix') }}-${{ hashFiles('**/mix.lock') }}
8289
restore-keys: |
83-
${{ runner.os }}-mix-${{ hashFiles('**/.mise.toml') }}-
90+
${{ runner.os }}-mix-${{ hashFiles('**/flake.nix') }}-
8491
path: |
8592
priv/plts
8693
8794
- name: Install Dependencies
88-
run: mix deps.get
95+
run: nix develop --command bash -c 'mix deps.get'
8996

9097
# Create PLTs if no cache was found
9198
- name: Create PLTs
9299
if: steps.plt_cache.outputs.cache-hit != 'true'
93-
run: mix dialyzer --plt
100+
run: nix develop --command bash -c 'mix dialyzer --plt'
94101

95102
# By default, the GitHub Cache action will only save the cache if all steps in the job succeed,
96103
# so we separate the cache restore and save steps in case running dialyzer fails.
@@ -99,12 +106,12 @@ jobs:
99106
if: steps.plt_cache.outputs.cache-hit != 'true'
100107
id: plt_cache_save
101108
with:
102-
key: ${{ runner.os }}-mix-${{ hashFiles('**/.mise.toml') }}-${{ hashFiles('**/mix.lock') }}
109+
key: ${{ runner.os }}-mix-${{ hashFiles('**/flake.nix') }}-${{ hashFiles('**/mix.lock') }}
103110
path: |
104111
priv/plts
105112
106113
- name: Run dialyzer
107-
run: mix dialyzer --format github
114+
run: nix develop --command bash -c 'mix dialyzer --format github'
108115

109116
release-test:
110117
runs-on: ${{matrix.os.name}}
@@ -122,23 +129,24 @@ jobs:
122129

123130
steps:
124131
- uses: actions/checkout@v4
125-
- uses: jdx/mise-action@v2
132+
- uses: DeterminateSystems/nix-installer-action@main
133+
- uses: DeterminateSystems/magic-nix-cache-action@main
126134
- uses: actions/cache@v4
127135
with:
128136
path: |
129137
deps
130-
key: ${{ matrix.os.name }}-mix-prod-${{ hashFiles('**/.mise.toml') }}-${{ hashFiles('**/mix.lock') }}
138+
key: ${{ matrix.os.name }}-mix-prod-${{ hashFiles('**/flake.nix') }}-${{ hashFiles('**/mix.lock') }}
131139
restore-keys: |
132-
${{ matrix.os.name }}-mix-prod-${{ hashFiles('**/.mise.toml') }}-
140+
${{ matrix.os.name }}-mix-prod-${{ hashFiles('**/flake.nix') }}-
133141
134142
- name: Install Dependencies
135-
run: mix deps.get --only prod
143+
run: nix develop --command bash -c 'mix deps.get --only prod'
136144

137145
- name: Release
138146
env:
139147
MIX_ENV: prod
140148
BURRITO_TARGET: ${{ matrix.os.target }}
141-
run: mix release
149+
run: nix develop --command bash -c 'mix release'
142150

143151
nix-build:
144152
strategy:

.github/workflows/release.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ jobs:
5555
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
5656
steps:
5757
- uses: actions/checkout@v4
58-
- uses: jdx/mise-action@v2
58+
- uses: DeterminateSystems/nix-installer-action@main
59+
- uses: DeterminateSystems/magic-nix-cache-action@main
5960
- run: chmod +x "$PWD/bin/7z"
6061
- run: brew install 7zip
6162
- run: mix local.hex --force

.mise.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
KERL_BUILD_DOCS = "yes"
33

44
[tools]
5-
erlang = "26.1.2"
6-
elixir = "1.15.7-otp-26"
5+
erlang = "26.2.2"
6+
elixir = "ref:52eaf1456182d5d6cce22a4f5c3f6ec9f4dcbfd9"
77
zig = "0.11.0"
8-

flake.nix

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{
2-
inputs = {nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";};
2+
inputs = {
3+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
4+
};
35

46
nixConfig = {
57
extra-substituters = ["https://elixir-tools.cachix.org"];
@@ -19,10 +21,23 @@
1921
lib.genAttrs (builtins.attrNames burritoExe) (system: let
2022
pkgs = nixpkgs.legacyPackages.${system};
2123
beamPackages = pkgs.beam_minimal.packages.erlang_26;
22-
elixir = beamPackages.elixir_1_15;
2324
beam = fetchTarball beams.${system};
2425
rawmusl = musls.${system};
2526
musl = lib.optionals nixpkgs.legacyPackages.${system}.stdenv.isLinux (builtins.fetchurl (nixpkgs.lib.attrsets.getAttrs ["url" "sha256"] musls.${system}));
27+
otp = (pkgs.beam.packagesWith beamPackages.erlang).extend (final: prev: {
28+
elixir_1_17 = prev.elixir_1_16.override {
29+
rev = "52eaf1456182d5d6cce22a4f5c3f6ec9f4dcbfd9";
30+
# You can discover this using Trust On First Use by filling in `lib.fakeHash`
31+
sha256 = "sha256-fOsV+jVIzsa38hQDvAjhUqee36nt8kG6AOpOQJnSZ74=";
32+
version = "1.17.0-dev";
33+
};
34+
35+
elixir = final.elixir_1_17;
36+
# This will get upstreamed into nix-beam-flakes at some point
37+
rebar = prev.rebar.overrideAttrs (_old: {doCheck = false;});
38+
rebar3 = prev.rebar3.overrideAttrs (_old: {doCheck = false;});
39+
});
40+
elixir = otp.elixir;
2641
in
2742
f {inherit system pkgs beamPackages elixir beam rawmusl musl;});
2843

@@ -100,7 +115,7 @@
100115
src = self.outPath;
101116
inherit version elixir;
102117
pname = "next-ls-deps";
103-
hash = "sha256-U5d8DftG0i1c4JiutUentNlRsefFgR4Mfc3eKqnKR3U=";
118+
hash = "sha256-RYPweYD1GD0D6A7ZkrtD3h7arCVimdStcOhrrlHFrnw=";
104119
mixEnv = "prod";
105120
};
106121

@@ -149,7 +164,15 @@
149164
beamPackages,
150165
elixir,
151166
...
152-
}: {
167+
}: let
168+
aliased_7zz = pkgs.symlinkJoin {
169+
name = "7zz-aliased";
170+
paths = [pkgs._7zz];
171+
postBuild = ''
172+
ln -s ${pkgs._7zz}/bin/7zz $out/bin/7z
173+
'';
174+
};
175+
in {
153176
default = pkgs.mkShell {
154177
# The Nix packages provided in the environment
155178
packages = [

lib/next_ls/document_symbol.ex

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,35 @@ defmodule NextLS.DocumentSymbol do
55
alias GenLSP.Structures.Position
66
alias GenLSP.Structures.Range
77

8-
# we set the literal encoder so that we can know when atoms and strings start and end
9-
# this makes it useful for knowing the exact locations of struct field definitions
108
@spec fetch(text :: String.t()) :: list(DocumentSymbol.t())
119
def fetch(text) do
12-
text
13-
|> Code.string_to_quoted!(
14-
literal_encoder: fn literal, meta ->
15-
if is_atom(literal) or is_binary(literal) do
16-
{:ok, {:__literal__, meta, [literal]}}
17-
else
18-
{:ok, literal}
19-
end
20-
end,
21-
unescape: false,
22-
token_metadata: true,
23-
columns: true
24-
)
10+
ast =
11+
case NextLS.Parser.parse(
12+
text,
13+
# we set the literal encoder so that we can know when atoms and strings start and end
14+
# this makes it useful for knowing the exact locations of struct field definitions
15+
literal_encoder: fn literal, meta ->
16+
if is_atom(literal) or is_binary(literal) do
17+
{:ok, {:__literal__, meta, [literal]}}
18+
else
19+
{:ok, literal}
20+
end
21+
end,
22+
unescape: false,
23+
token_metadata: true,
24+
columns: true
25+
) do
26+
{:error, ast, _errors} ->
27+
ast
28+
29+
{:error, _} ->
30+
raise "Failed to parse!"
31+
32+
{:ok, ast} ->
33+
ast
34+
end
35+
36+
ast
2537
|> walker(nil)
2638
|> List.wrap()
2739
end

lib/next_ls/helpers/ast_helpers.ex

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ defmodule NextLS.ASTHelpers do
7272
defp postwalk(ast, acc, _module), do: {ast, acc}
7373

7474
defp ast_from_file(file) do
75-
file |> File.read!() |> Code.string_to_quoted!(columns: true)
75+
file |> File.read!() |> NextLS.Parser.parse!(columns: true)
7676
end
7777
end
7878

@@ -99,53 +99,57 @@ defmodule NextLS.ASTHelpers do
9999
"""
100100

101101
def extract_alias_range(code, {start, stop}, ale) do
102-
lines =
103-
code
104-
|> String.split("\n")
105-
|> Enum.map(&String.split(&1, ""))
106-
|> Enum.slice((start.line - 1)..(stop.line - 1))
107-
108-
code =
109-
if start.line == stop.line do
110-
[line] = lines
111-
112-
line
113-
|> Enum.slice(start.col..stop.col)
114-
|> Enum.join()
115-
else
116-
[first | rest] = lines
117-
first = Enum.drop(first, start.col)
118-
119-
[last | rest] = Enum.reverse(rest)
120-
121-
length = Enum.count(last)
122-
last = Enum.drop(last, -(length - stop.col - 1))
123-
124-
Enum.map_join([first | Enum.reverse([last | rest])], "\n", &Enum.join(&1, ""))
125-
end
126-
127102
{_, range} =
128103
code
129-
|> Code.string_to_quoted!(columns: true, column: start.col, token_metadata: true)
130-
|> Macro.prewalk(nil, fn ast, range ->
131-
range =
132-
case ast do
133-
{:__aliases__, meta, aliases} ->
134-
if ale == List.last(aliases) do
135-
{{meta[:line] + start.line - 1, meta[:column]},
136-
{meta[:last][:line] + start.line - 1, meta[:last][:column] + String.length(to_string(ale)) - 1}}
137-
else
104+
|> NextLS.Parser.parse!(columns: true, token_metadata: true)
105+
|> Macro.prewalk(nil, fn
106+
ast, nil = range ->
107+
range =
108+
case ast do
109+
{:__aliases__, meta, aliases} ->
110+
if ale == List.last(aliases) do
111+
found_range =
112+
{{meta[:line], meta[:column]},
113+
{meta[:last][:line], meta[:last][:column] + String.length(to_string(ale)) - 1}}
114+
115+
if NextLS.ASTHelpers.inside?({{start.line, start.col}, {stop.line, stop.col}}, found_range) do
116+
found_range
117+
else
118+
range
119+
end
120+
else
121+
range
122+
end
123+
124+
_ ->
138125
range
139-
end
126+
end
140127

141-
_ ->
142-
range
143-
end
128+
{ast, range}
144129

145-
{ast, range}
130+
ast, range ->
131+
{ast, range}
146132
end)
147133

148134
range
149135
end
150136
end
137+
138+
def inside?(outer, {{_, _}, {_, _}} = target) do
139+
{{outer_startl, outer_startc}, {outer_endl, outer_endc}} = outer
140+
{target_start, target_end} = target
141+
142+
Enum.all?([target_start, target_end], fn {line, col} ->
143+
if outer_startl <= line and line <= outer_endl do
144+
cond do
145+
outer_startl < line and line < outer_endl -> true
146+
outer_startl == line and outer_startc <= col -> true
147+
outer_endl == line and col <= outer_endc -> true
148+
true -> false
149+
end
150+
else
151+
false
152+
end
153+
end)
154+
end
151155
end

0 commit comments

Comments
 (0)