@@ -11,6 +11,8 @@ defmodule ElixirLS.LanguageServer.Providers.CodeLens do
11
11
"""
12
12
13
13
alias ElixirLS.LanguageServer . { Server , SourceFile }
14
+ alias ElixirSense.Core.Parser
15
+ alias ElixirSense.Core.State
14
16
alias Erl2ex.Convert . { Context , ErlForms }
15
17
alias Erl2ex.Pipeline . { Parse , ModuleData , ExSpec }
16
18
import ElixirLS.LanguageServer.Protocol
@@ -110,30 +112,104 @@ defmodule ElixirLS.LanguageServer.Providers.CodeLens do
110
112
end
111
113
end
112
114
113
- def code_lens ( server_instance_id , uri , text ) do
115
+ def spec_code_lens ( server_instance_id , uri , text ) do
114
116
resp =
115
117
for { _ , line , { mod , fun , arity } , contract , is_macro } <- Server . suggest_contracts ( uri ) ,
116
118
SourceFile . function_def_on_line? ( text , line , fun ) ,
117
119
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
133
131
}
134
- }
132
+ )
135
133
end
136
134
137
135
{ :ok , resp }
138
136
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
139
215
end
0 commit comments