@@ -11,6 +11,8 @@ defmodule ElixirLS.LanguageServer.Providers.CodeLens do
1111 """
1212
1313 alias ElixirLS.LanguageServer . { Server , SourceFile }
14+ alias ElixirSense.Core.Parser
15+ alias ElixirSense.Core.State
1416 alias Erl2ex.Convert . { Context , ErlForms }
1517 alias Erl2ex.Pipeline . { Parse , ModuleData , ExSpec }
1618 import ElixirLS.LanguageServer.Protocol
@@ -110,30 +112,104 @@ defmodule ElixirLS.LanguageServer.Providers.CodeLens do
110112 end
111113 end
112114
113- def code_lens ( server_instance_id , uri , text ) do
115+ def spec_code_lens ( server_instance_id , uri , text ) do
114116 resp =
115117 for { _ , line , { mod , fun , arity } , contract , is_macro } <- Server . suggest_contracts ( uri ) ,
116118 SourceFile . function_def_on_line? ( text , line , fun ) ,
117119 spec = ContractTranslator . translate_contract ( fun , contract , is_macro ) do
118- % {
119- "range" => range ( line - 1 , 0 , line - 1 , 0 ) ,
120- "command" => % {
121- "title" => "@spec #{ spec } " ,
122- "command" => "spec:#{ server_instance_id } " ,
123- "arguments" => [
124- % {
125- "uri" => uri ,
126- "mod" => to_string ( mod ) ,
127- "fun" => to_string ( fun ) ,
128- "arity" => arity ,
129- "spec" => spec ,
130- "line" => line
131- }
132- ]
120+ build_code_lens (
121+ line ,
122+ "@spec #{ spec } " ,
123+ "spec:#{ server_instance_id } " ,
124+ % {
125+ "uri" => uri ,
126+ "mod" => to_string ( mod ) ,
127+ "fun" => to_string ( fun ) ,
128+ "arity" => arity ,
129+ "spec" => spec ,
130+ "line" => line
133131 }
134- }
132+ )
135133 end
136134
137135 { :ok , resp }
138136 end
137+
138+ def test_code_lens ( uri , src ) do
139+ file_path = SourceFile . path_from_uri ( uri )
140+
141+ if imports? ( src , ExUnit.Case ) do
142+ test_calls = calls_to ( src , :test )
143+ describe_calls = calls_to ( src , :describe )
144+
145+ calls_lenses =
146+ for { line , _col } <- test_calls ++ describe_calls do
147+ test_filter = "#{ file_path } :#{ line } "
148+
149+ build_code_lens ( line , "Run test" , "elixir.test.run" , test_filter )
150+ end
151+
152+ file_lens = build_code_lens ( 1 , "Run test" , "elixir.test.run" , file_path )
153+
154+ { :ok , [ file_lens | calls_lenses ] }
155+ end
156+ end
157+
158+ @ spec imports? ( String . t ( ) , [ atom ( ) ] | atom ( ) ) :: boolean ( )
159+ defp imports? ( buffer , modules ) do
160+ buffer_file_metadata =
161+ buffer
162+ |> Parser . parse_string ( true , true , 1 )
163+
164+ imports_set =
165+ buffer_file_metadata . lines_to_env
166+ |> get_imports ( )
167+ |> MapSet . new ( )
168+
169+ modules
170+ |> List . wrap ( )
171+ |> MapSet . new ( )
172+ |> MapSet . subset? ( imports_set )
173+ end
174+
175+ defp get_imports ( lines_to_env ) do
176+ % State.Env { imports: imports } =
177+ lines_to_env
178+ |> Enum . max_by ( fn { k , _v } -> k end )
179+ |> elem ( 1 )
180+
181+ imports
182+ end
183+
184+ @ spec calls_to ( String . t ( ) , atom ( ) | { atom ( ) , integer ( ) } ) :: [ { pos_integer ( ) , pos_integer ( ) } ]
185+ defp calls_to ( buffer , function ) do
186+ buffer_file_metadata =
187+ buffer
188+ |> Parser . parse_string ( true , true , 1 )
189+
190+ buffer_file_metadata . calls
191+ |> Enum . map ( fn { _k , v } -> v end )
192+ |> List . flatten ( )
193+ |> Enum . filter ( & is_call_to ( & 1 , function ) )
194+ |> Enum . map ( fn call -> call . position end )
195+ end
196+
197+ defp is_call_to ( % State.CallInfo { } = call_info , { function , arity } ) do
198+ call_info . func == function and call_info . arity == arity
199+ end
200+
201+ defp is_call_to ( % State.CallInfo { } = call_info , function ) when is_atom ( function ) do
202+ call_info . func == function
203+ end
204+
205+ def build_code_lens ( line , title , command , argument ) do
206+ % {
207+ "range" => range ( line - 1 , 0 , line - 1 , 0 ) ,
208+ "command" => % {
209+ "title" => title ,
210+ "command" => command ,
211+ "arguments" => [ argument ]
212+ }
213+ }
214+ end
139215end
0 commit comments