Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental project structure #773

Merged
merged 21 commits into from
Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,6 @@ jobs:
mix deps.get
- name: Restore timestamps to prevent unnecessary recompilation
run: IFS=$'\n'; for f in $(git ls-files); do touch -d "$(git log -n 1 --pretty='%cI' -- $f)" "$f"; done
- run: mix format --check-formatted
- run: cd apps/language_server && mix format --check-formatted
- run: MIX_ENV=test mix format --check-formatted
- run: cd apps/language_server && MIX_ENV=test mix format --check-formatted
- run: mix dialyzer_vendored
12 changes: 11 additions & 1 deletion apps/language_server/.formatter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,25 @@ impossible_to_format = [
"test/fixtures/project_with_tests/test/error_test.exs"
]

deps =
if Mix.env() == :test do
[:patch]
else
[]
end

proto_dsl = [
defalias: 1,
defenum: 1,
defnotification: 2,
defrequest: 2,
defnotification: 3,
defrequest: 3,
defresponse: 1,
deftype: 1
]

[
import_deps: deps,
export: [
locals_without_parens: proto_dsl
],
Expand Down
49 changes: 34 additions & 15 deletions apps/language_server/lib/language_server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,21 @@ defmodule ElixirLS.LanguageServer do
alias ElixirLS.LanguageServer
alias ElixirLS.LanguageServer.Experimental

# @maybe_experimental_server [Experimental.Server]
@maybe_experimental_server []

@impl Application
def start(_type, _args) do
children = [
Experimental.SourceFile.Store,
{ElixirLS.LanguageServer.Server, ElixirLS.LanguageServer.Server},
Experimental.Server,
{ElixirLS.LanguageServer.PacketRouter,
[LanguageServer.Server] ++ @maybe_experimental_server},
{ElixirLS.LanguageServer.JsonRpc,
name: ElixirLS.LanguageServer.JsonRpc, language_server: LanguageServer.PacketRouter},
{ElixirLS.LanguageServer.Providers.WorkspaceSymbols, []},
{ElixirLS.LanguageServer.Tracer, []},
{ElixirLS.LanguageServer.ExUnitTestTracer, []}
]
Experimental.LanguageServer.persist_enabled_state()

children =
[
maybe_experimental_supervisor(),
{ElixirLS.LanguageServer.Server, ElixirLS.LanguageServer.Server},
maybe_packet_router(),
jsonrpc(),
{ElixirLS.LanguageServer.Providers.WorkspaceSymbols, []},
{ElixirLS.LanguageServer.Tracer, []},
{ElixirLS.LanguageServer.ExUnitTestTracer, []}
]
|> Enum.reject(&is_nil/1)

opts = [strategy: :one_for_one, name: LanguageServer.Supervisor, max_restarts: 0]
Supervisor.start_link(children, opts)
Expand All @@ -42,4 +40,25 @@ defmodule ElixirLS.LanguageServer do

:ok
end

defp maybe_experimental_supervisor do
if Experimental.LanguageServer.enabled?() do
Experimental.Supervisor
end
end

defp maybe_packet_router do
if Experimental.LanguageServer.enabled?() do
{ElixirLS.LanguageServer.PacketRouter, [LanguageServer.Server, Experimental.Server]}
end
end

defp jsonrpc do
if Experimental.LanguageServer.enabled?() do
{ElixirLS.LanguageServer.JsonRpc,
name: ElixirLS.LanguageServer.JsonRpc, language_server: LanguageServer.PacketRouter}
else
{ElixirLS.LanguageServer.JsonRpc, name: ElixirLS.LanguageServer.JsonRpc}
end
end
end
3 changes: 2 additions & 1 deletion apps/language_server/lib/language_server/dialyzer/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Utils do
@spec dialyzable?(module()) :: boolean()
def dialyzable?(module) do
file = get_beam_file(module)

is_list(file) and match?({:ok, _}, :dialyzer_utils.get_core_from_beam(file))
end

@spec get_beam_file(module()) :: charlist() | :preloaded | :non_existing | :cover_compiled
def get_beam_file(module) do
case :code.which(module) do
file when is_list(file) ->
[_ | _] = file ->
file

other ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
defmodule ElixirLS.LanguageServer.Experimental.CodeMod.Ast do
alias ElixirLS.LanguageServer.Experimental.SourceFile

@type source :: SourceFile.t() | String.t()
@type t ::
atom()
| binary()
| [any()]
| number()
| {any(), any()}
| {atom() | {any(), [any()], atom() | [any()]}, Keyword.t(), atom() | [any()]}

@spec from(source) :: t
def from(%SourceFile{} = source_file) do
source_file
|> SourceFile.to_string()
|> from()
end

def from(s) when is_binary(s) do
ElixirSense.string_to_quoted(s, 1, 6, token_metadata: true)
end

@spec to_string(t()) :: String.t()
def to_string(ast) do
Sourceror.to_string(ast)
end
end
106 changes: 106 additions & 0 deletions apps/language_server/lib/language_server/experimental/code_mod/diff.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
defmodule ElixirLS.LanguageServer.Experimental.CodeMod.Diff do
alias ElixirLS.LanguageServer.Experimental.CodeUnit
alias ElixirLS.LanguageServer.Experimental.Protocol.Types.Position
alias ElixirLS.LanguageServer.Experimental.Protocol.Types.Range
alias ElixirLS.LanguageServer.Experimental.Protocol.Types.TextEdit

@spec diff(String.t(), String.t()) :: [TextEdit.t()]
def diff(source, dest) do
source
|> String.myers_difference(dest)
|> to_text_edits()
end

defp to_text_edits(difference) do
{_, {current_line, prev_lines}} =
Enum.reduce(difference, {{0, 0}, {[], []}}, fn
{diff_type, diff_string}, {position, edits} ->
apply_diff(diff_type, position, diff_string, edits)
end)

[current_line | prev_lines]
|> Enum.flat_map(fn line_edits ->
line_edits
|> Enum.reduce([], &collapse/2)
|> Enum.reverse()
end)
end

# This collapses a delete and an an insert that are adjacent to one another
# into a single insert, changing the delete to insert the text from the
# insert rather than ""
# It's a small optimization, but it was in the original
defp collapse(
%TextEdit{
new_text: "",
range: %Range{
end: %Position{character: same_character, line: same_line}
}
} = delete_edit,
[
%TextEdit{
new_text: insert_text,
range:
%Range{
start: %Position{character: same_character, line: same_line}
} = _insert_edit
}
| rest
]
)
when byte_size(insert_text) > 0 do
collapsed_edit = %TextEdit{delete_edit | new_text: insert_text}
[collapsed_edit | rest]
end

defp collapse(%TextEdit{} = edit, edits) do
[edit | edits]
end

defp apply_diff(:eq, position, doc_string, edits) do
advance(doc_string, position, edits)
end

defp apply_diff(:del, {line, code_unit} = position, change, edits) do
{after_pos, {current_line, prev_lines}} = advance(change, position, edits)
{edit_end_line, edit_end_unit} = after_pos
current_line = [edit("", line, code_unit, edit_end_line, edit_end_unit) | current_line]
{after_pos, {current_line, prev_lines}}
end

defp apply_diff(:ins, {line, code_unit} = position, change, {current_line, prev_lines}) do
current_line = [edit(change, line, code_unit, line, code_unit) | current_line]
advance(change, position, {current_line, prev_lines})
end

defp advance(<<>>, position, edits) do
{position, edits}
end

for ending <- ["\r\n", "\r", "\n"] do
defp advance(<<unquote(ending), rest::binary>>, {line, _unit}, {current_line, prev_lines}) do
edits = {[], [current_line | prev_lines]}
advance(rest, {line + 1, 0}, edits)
end
end

defp advance(<<c, rest::binary>>, {line, unit}, edits) when c < 128 do
advance(rest, {line, unit + 1}, edits)
end

defp advance(<<c::utf8, rest::binary>>, {line, unit}, edits) do
increment = CodeUnit.count(:utf16, <<c::utf8>>)
advance(rest, {line, unit + increment}, edits)
end

defp edit(text, start_line, start_unit, end_line, end_unit) do
TextEdit.new(
new_text: text,
range:
Range.new(
start: Position.new(line: start_line, character: start_unit),
end: Position.new(line: end_line, character: end_unit)
)
)
end
end
Loading