-
Notifications
You must be signed in to change notification settings - Fork 207
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
salvage code actions from experimental server
- Loading branch information
1 parent
371ef39
commit 43b656d
Showing
18 changed files
with
1,718 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
defmodule ElixirLS.LanguageServer.CodeUnit do | ||
@moduledoc """ | ||
Code unit and offset conversions | ||
The LSP protocol speaks in positions, which defines where something happens in a document. | ||
Positions have a start and an end, which are defined as code unit _offsets_ from the beginning | ||
of a line. this module helps to convert between utf8, which most of the world speaks | ||
natively, and utf16, which has been forced upon us by microsoft. | ||
Converting between offsets and code units is 0(n), and allocations only happen if a | ||
multi-byte character is detected, at which point, only that character is allocated. | ||
This exploits the fact that most source code consists of ascii characters, with at best, | ||
sporadic multi-byte characters in it. Thus, the vast majority of documents will not require | ||
any allocations at all. | ||
""" | ||
@type utf8_code_unit :: non_neg_integer() | ||
@type utf16_code_unit :: non_neg_integer() | ||
@type utf8_offset :: non_neg_integer() | ||
@type utf16_offset :: non_neg_integer() | ||
|
||
@type error :: {:error, :misaligned} | {:error, :out_of_bounds} | ||
|
||
# public | ||
|
||
@doc """ | ||
Converts a utf8 character offset into a utf16 character offset. This implementation | ||
clamps the maximum size of an offset so that any initial character position can be | ||
passed in and the offset returned will reflect the end of the line. | ||
""" | ||
@spec utf16_offset(String.t(), utf8_offset()) :: utf16_offset() | ||
def utf16_offset(binary, character_position) do | ||
do_utf16_offset(binary, character_position, 0) | ||
end | ||
|
||
@doc """ | ||
Converts a utf16 character offset into a utf8 character offset. This implementation | ||
clamps the maximum size of an offset so that any initial character position can be | ||
passed in and the offset returned will reflect the end of the line. | ||
""" | ||
@spec utf8_offset(String.t(), utf16_offset()) :: utf8_offset() | ||
def utf8_offset(binary, character_position) do | ||
do_utf8_offset(binary, character_position, 0) | ||
end | ||
|
||
@spec to_utf8(String.t(), utf16_code_unit()) :: {:ok, utf8_code_unit()} | error | ||
def to_utf8(binary, utf16_unit) do | ||
do_to_utf8(binary, utf16_unit, 0) | ||
end | ||
|
||
@spec to_utf16(String.t(), utf8_code_unit()) :: {:ok, utf16_code_unit()} | error | ||
def to_utf16(binary, utf16_unit) do | ||
do_to_utf16(binary, utf16_unit, 0) | ||
end | ||
|
||
def count(:utf16, binary) do | ||
do_count_utf16(binary, 0) | ||
end | ||
|
||
# Private | ||
|
||
# UTF-16 | ||
|
||
def do_count_utf16(<<>>, count) do | ||
count | ||
end | ||
|
||
def do_count_utf16(<<c, rest::binary>>, count) when c < 128 do | ||
do_count_utf16(rest, count + 1) | ||
end | ||
|
||
def do_count_utf16(<<c::utf8, rest::binary>>, count) do | ||
increment = | ||
<<c::utf16>> | ||
|> byte_size() | ||
|> div(2) | ||
|
||
do_count_utf16(rest, count + increment) | ||
end | ||
|
||
defp do_utf16_offset(_, 0, offset) do | ||
offset | ||
end | ||
|
||
defp do_utf16_offset(<<>>, _, offset) do | ||
# this clause pegs the offset at the end of the string | ||
# no matter the character index | ||
offset | ||
end | ||
|
||
defp do_utf16_offset(<<c, rest::binary>>, remaining, offset) when c < 128 do | ||
do_utf16_offset(rest, remaining - 1, offset + 1) | ||
end | ||
|
||
defp do_utf16_offset(<<c::utf8, rest::binary>>, remaining, offset) do | ||
s = <<c::utf8>> | ||
increment = utf16_size(s) | ||
do_utf16_offset(rest, remaining - 1, offset + increment) | ||
end | ||
|
||
defp do_to_utf16(_, 0, utf16_unit) do | ||
{:ok, utf16_unit} | ||
end | ||
|
||
defp do_to_utf16(_, utf8_unit, _) when utf8_unit < 0 do | ||
{:error, :misaligned} | ||
end | ||
|
||
defp do_to_utf16(<<>>, _remaining, _utf16_unit) do | ||
{:error, :out_of_bounds} | ||
end | ||
|
||
defp do_to_utf16(<<c, rest::binary>>, utf8_unit, utf16_unit) when c < 128 do | ||
do_to_utf16(rest, utf8_unit - 1, utf16_unit + 1) | ||
end | ||
|
||
defp do_to_utf16(<<c::utf8, rest::binary>>, utf8_unit, utf16_unit) do | ||
utf8_string = <<c::utf8>> | ||
increment = utf16_size(utf8_string) | ||
decrement = byte_size(utf8_string) | ||
|
||
do_to_utf16(rest, utf8_unit - decrement, utf16_unit + increment) | ||
end | ||
|
||
defp utf16_size(binary) when is_binary(binary) do | ||
binary | ||
|> :unicode.characters_to_binary(:utf8, :utf16) | ||
|> byte_size() | ||
|> div(2) | ||
end | ||
|
||
# UTF-8 | ||
|
||
defp do_utf8_offset(_, 0, offset) do | ||
offset | ||
end | ||
|
||
defp do_utf8_offset(<<>>, _, offset) do | ||
# this clause pegs the offset at the end of the string | ||
# no matter the character index | ||
offset | ||
end | ||
|
||
defp do_utf8_offset(<<c, rest::binary>>, remaining, offset) when c < 128 do | ||
do_utf8_offset(rest, remaining - 1, offset + 1) | ||
end | ||
|
||
defp do_utf8_offset(<<c::utf8, rest::binary>>, remaining, offset) do | ||
s = <<c::utf8>> | ||
increment = utf8_size(s) | ||
decrement = utf16_size(s) | ||
do_utf8_offset(rest, remaining - decrement, offset + increment) | ||
end | ||
|
||
defp do_to_utf8(_, 0, utf8_unit) do | ||
{:ok, utf8_unit} | ||
end | ||
|
||
defp do_to_utf8(_, utf_16_units, _) when utf_16_units < 0 do | ||
{:error, :misaligned} | ||
end | ||
|
||
defp do_to_utf8(<<>>, _remaining, _utf8_unit) do | ||
{:error, :out_of_bounds} | ||
end | ||
|
||
defp do_to_utf8(<<c, rest::binary>>, utf16_unit, utf8_unit) when c < 128 do | ||
do_to_utf8(rest, utf16_unit - 1, utf8_unit + 1) | ||
end | ||
|
||
defp do_to_utf8(<<c::utf8, rest::binary>>, utf16_unit, utf8_unit) do | ||
utf8_code_units = byte_size(<<c::utf8>>) | ||
utf16_code_units = utf16_size(<<c::utf8>>) | ||
|
||
do_to_utf8(rest, utf16_unit - utf16_code_units, utf8_unit + utf8_code_units) | ||
end | ||
|
||
defp utf8_size(binary) when is_binary(binary) do | ||
byte_size(binary) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
apps/language_server/lib/language_server/providers/code_action.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
defmodule ElixirLS.LanguageServer.Providers.CodeAction do | ||
alias ElixirLS.LanguageServer.Providers.CodeAction.ReplaceRemoteFunction | ||
alias ElixirLS.LanguageServer.Providers.CodeAction.ReplaceWithUnderscore | ||
|
||
@code_actions [ReplaceRemoteFunction, ReplaceWithUnderscore] | ||
|
||
def code_actions(source_file, uri, diagnostic) do | ||
code_actions = Enum.flat_map(@code_actions, & &1.apply(source_file, uri, diagnostic)) | ||
|
||
{:ok, code_actions} | ||
end | ||
end |
24 changes: 24 additions & 0 deletions
24
apps/language_server/lib/language_server/providers/code_action/code_action_result.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
defmodule ElixirLS.LanguageServer.Providers.CodeAction.CodeActionResult do | ||
alias ElixirLS.LanguageServer.Protocol.TextEdit | ||
|
||
@type t :: %{ | ||
title: String.t(), | ||
kind: String.t(), | ||
edit: %{ | ||
changes: %{String.t() => TextEdit.t()} | ||
} | ||
} | ||
|
||
@spec new(String.t(), String.t(), [TextEdit.t()], String.t()) :: t() | ||
def new(title, kind, text_edits, uri) do | ||
%{ | ||
:title => title, | ||
:kind => kind, | ||
:edit => %{ | ||
:changes => %{ | ||
uri => text_edits | ||
} | ||
} | ||
} | ||
end | ||
end |
53 changes: 53 additions & 0 deletions
53
apps/language_server/lib/language_server/providers/code_action/helpers.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
defmodule ElixirLS.LanguageServer.Providers.CodeAction.Helpers do | ||
alias ElixirLS.LanguageServer.Protocol.TextEdit | ||
alias ElixirLS.LanguageServer.Providers.CodeMod.Ast | ||
alias ElixirLS.LanguageServer.Providers.CodeMod.Text | ||
|
||
@spec update_line(TextEdit.t(), non_neg_integer()) :: TextEdit.t() | ||
def update_line( | ||
%TextEdit{range: %{"start" => start_line, "end" => end_line}} = text_edit, | ||
line_number | ||
) do | ||
%TextEdit{ | ||
text_edit | ||
| range: %{ | ||
"start" => %{start_line | "line" => line_number}, | ||
"end" => %{end_line | "line" => line_number} | ||
} | ||
} | ||
end | ||
|
||
@spec to_one_line_string(Ast.t()) :: {:ok, String.t()} | :error | ||
def to_one_line_string(updated_ast) do | ||
updated_ast | ||
|> Ast.to_string() | ||
# We're dealing with a single error on a single line. | ||
# If the line doesn't compile (like it has a do with no end), ElixirSense | ||
# adds additional lines to documents with errors. Also, in case of a one-line do, | ||
# ElixirSense creates do with end from the AST. | ||
|> maybe_recover_one_line_do(updated_ast) | ||
|> Text.fetch_line(0) | ||
end | ||
|
||
@do_regex ~r/\s*do\s*/ | ||
defp maybe_recover_one_line_do(updated_text, {_name, context, _children} = _updated_ast) do | ||
wrong_do_end_conditions = [ | ||
not Keyword.has_key?(context, :do), | ||
not Keyword.has_key?(context, :end), | ||
Regex.match?(@do_regex, updated_text), | ||
String.ends_with?(updated_text, "\nend") | ||
] | ||
|
||
if Enum.all?(wrong_do_end_conditions) do | ||
updated_text | ||
|> String.replace(@do_regex, ", do: ") | ||
|> String.trim_trailing("\nend") | ||
else | ||
updated_text | ||
end | ||
end | ||
|
||
defp maybe_recover_one_line_do(updated_text, _updated_ast) do | ||
updated_text | ||
end | ||
end |
Oops, something went wrong.