1Code.require_file("test_helper.exs", __DIR__)
2
3defmodule CodeFragmentTest do
4  use ExUnit.Case, async: true
5
6  doctest Code.Fragment
7  alias Code.Fragment, as: CF
8
9  describe "cursor_context/2" do
10    test "expressions" do
11      assert CF.cursor_context([]) == :expr
12      assert CF.cursor_context(",") == :expr
13      assert CF.cursor_context("[") == :expr
14      assert CF.cursor_context("<<") == :expr
15      assert CF.cursor_context("=>") == :expr
16      assert CF.cursor_context("->") == :expr
17      assert CF.cursor_context("foo(<<") == :expr
18      assert CF.cursor_context("hello: ") == :expr
19      assert CF.cursor_context("\n") == :expr
20      assert CF.cursor_context('\n') == :expr
21      assert CF.cursor_context("\n\n") == :expr
22      assert CF.cursor_context('\n\n') == :expr
23    end
24
25    test "local_or_var" do
26      assert CF.cursor_context("hello_wo") == {:local_or_var, 'hello_wo'}
27      assert CF.cursor_context("hello_world?") == {:local_or_var, 'hello_world?'}
28      assert CF.cursor_context("hello_world!") == {:local_or_var, 'hello_world!'}
29      assert CF.cursor_context("hello/wor") == {:local_or_var, 'wor'}
30      assert CF.cursor_context("hello..wor") == {:local_or_var, 'wor'}
31      assert CF.cursor_context("hello::wor") == {:local_or_var, 'wor'}
32      assert CF.cursor_context("[hello_wo") == {:local_or_var, 'hello_wo'}
33      assert CF.cursor_context("'hello_wo") == {:local_or_var, 'hello_wo'}
34      assert CF.cursor_context("hellò_wó") == {:local_or_var, 'hellò_wó'}
35      assert CF.cursor_context("hello? world") == {:local_or_var, 'world'}
36      assert CF.cursor_context("hello! world") == {:local_or_var, 'world'}
37      assert CF.cursor_context("hello: world") == {:local_or_var, 'world'}
38    end
39
40    test "dot" do
41      assert CF.cursor_context("hello.") == {:dot, {:var, 'hello'}, ''}
42      assert CF.cursor_context(":hello.") == {:dot, {:unquoted_atom, 'hello'}, ''}
43      assert CF.cursor_context("nested.map.") == {:dot, {:dot, {:var, 'nested'}, 'map'}, ''}
44
45      assert CF.cursor_context("Hello.") == {:dot, {:alias, 'Hello'}, ''}
46      assert CF.cursor_context("Hello.World.") == {:dot, {:alias, 'Hello.World'}, ''}
47      assert CF.cursor_context("Hello.wor") == {:dot, {:alias, 'Hello'}, 'wor'}
48      assert CF.cursor_context("hello.wor") == {:dot, {:var, 'hello'}, 'wor'}
49      assert CF.cursor_context("Hello.++") == {:dot, {:alias, 'Hello'}, '++'}
50      assert CF.cursor_context(":hello.wor") == {:dot, {:unquoted_atom, 'hello'}, 'wor'}
51      assert CF.cursor_context(":hell@o.wor") == {:dot, {:unquoted_atom, 'hell@o'}, 'wor'}
52      assert CF.cursor_context(":he@ll@o.wor") == {:dot, {:unquoted_atom, 'he@ll@o'}, 'wor'}
53      assert CF.cursor_context(":hell@@o.wor") == {:dot, {:unquoted_atom, 'hell@@o'}, 'wor'}
54      assert CF.cursor_context("@hello.wor") == {:dot, {:module_attribute, 'hello'}, 'wor'}
55
56      assert CF.cursor_context("nested.map.wor") ==
57               {:dot, {:dot, {:var, 'nested'}, 'map'}, 'wor'}
58    end
59
60    test "local_arity" do
61      assert CF.cursor_context("hello/") == {:local_arity, 'hello'}
62    end
63
64    test "local_call" do
65      assert CF.cursor_context("hello\s") == {:local_call, 'hello'}
66      assert CF.cursor_context("hello\t") == {:local_call, 'hello'}
67      assert CF.cursor_context("hello(") == {:local_call, 'hello'}
68      assert CF.cursor_context("hello(\s") == {:local_call, 'hello'}
69      assert CF.cursor_context("hello(\t") == {:local_call, 'hello'}
70    end
71
72    test "dot_arity" do
73      assert CF.cursor_context("Foo.hello/") == {:dot_arity, {:alias, 'Foo'}, 'hello'}
74      assert CF.cursor_context("Foo.+/") == {:dot_arity, {:alias, 'Foo'}, '+'}
75      assert CF.cursor_context("Foo . hello /") == {:dot_arity, {:alias, 'Foo'}, 'hello'}
76      assert CF.cursor_context("Foo . + /") == {:dot_arity, {:alias, 'Foo'}, '+'}
77      assert CF.cursor_context("foo.hello/") == {:dot_arity, {:var, 'foo'}, 'hello'}
78      assert CF.cursor_context(":foo.hello/") == {:dot_arity, {:unquoted_atom, 'foo'}, 'hello'}
79      assert CF.cursor_context("@f.hello/") == {:dot_arity, {:module_attribute, 'f'}, 'hello'}
80    end
81
82    test "dot_call" do
83      assert CF.cursor_context("Foo.hello\s") == {:dot_call, {:alias, 'Foo'}, 'hello'}
84      assert CF.cursor_context("Foo.hello\t") == {:dot_call, {:alias, 'Foo'}, 'hello'}
85      assert CF.cursor_context("Foo.hello(") == {:dot_call, {:alias, 'Foo'}, 'hello'}
86      assert CF.cursor_context("Foo.hello(\s") == {:dot_call, {:alias, 'Foo'}, 'hello'}
87      assert CF.cursor_context("Foo.hello(\t") == {:dot_call, {:alias, 'Foo'}, 'hello'}
88      assert CF.cursor_context("Foo . hello (") == {:dot_call, {:alias, 'Foo'}, 'hello'}
89      assert CF.cursor_context("Foo . hello (\s") == {:dot_call, {:alias, 'Foo'}, 'hello'}
90      assert CF.cursor_context("Foo . hello (\t") == {:dot_call, {:alias, 'Foo'}, 'hello'}
91
92      assert CF.cursor_context(":foo.hello\s") == {:dot_call, {:unquoted_atom, 'foo'}, 'hello'}
93      assert CF.cursor_context(":foo.hello\t") == {:dot_call, {:unquoted_atom, 'foo'}, 'hello'}
94      assert CF.cursor_context(":foo.hello(") == {:dot_call, {:unquoted_atom, 'foo'}, 'hello'}
95      assert CF.cursor_context(":foo.hello(\s") == {:dot_call, {:unquoted_atom, 'foo'}, 'hello'}
96      assert CF.cursor_context(":foo.hello(\t") == {:dot_call, {:unquoted_atom, 'foo'}, 'hello'}
97      assert CF.cursor_context(":foo.hello\s") == {:dot_call, {:unquoted_atom, 'foo'}, 'hello'}
98
99      assert CF.cursor_context("foo.hello\s") == {:dot_call, {:var, 'foo'}, 'hello'}
100      assert CF.cursor_context("foo.hello\t") == {:dot_call, {:var, 'foo'}, 'hello'}
101      assert CF.cursor_context("foo.hello(") == {:dot_call, {:var, 'foo'}, 'hello'}
102      assert CF.cursor_context("foo.hello(\s") == {:dot_call, {:var, 'foo'}, 'hello'}
103      assert CF.cursor_context("foo.hello(\t") == {:dot_call, {:var, 'foo'}, 'hello'}
104
105      assert CF.cursor_context("@f.hello\s") == {:dot_call, {:module_attribute, 'f'}, 'hello'}
106      assert CF.cursor_context("@f.hello\t") == {:dot_call, {:module_attribute, 'f'}, 'hello'}
107      assert CF.cursor_context("@f.hello(") == {:dot_call, {:module_attribute, 'f'}, 'hello'}
108      assert CF.cursor_context("@f.hello(\s") == {:dot_call, {:module_attribute, 'f'}, 'hello'}
109      assert CF.cursor_context("@f.hello(\t") == {:dot_call, {:module_attribute, 'f'}, 'hello'}
110
111      assert CF.cursor_context("Foo.+\s") == {:dot_call, {:alias, 'Foo'}, '+'}
112      assert CF.cursor_context("Foo.+\t") == {:dot_call, {:alias, 'Foo'}, '+'}
113      assert CF.cursor_context("Foo.+(") == {:dot_call, {:alias, 'Foo'}, '+'}
114      assert CF.cursor_context("Foo.+(\s") == {:dot_call, {:alias, 'Foo'}, '+'}
115      assert CF.cursor_context("Foo.+(\t") == {:dot_call, {:alias, 'Foo'}, '+'}
116      assert CF.cursor_context("Foo . + (") == {:dot_call, {:alias, 'Foo'}, '+'}
117      assert CF.cursor_context("Foo . + (\s") == {:dot_call, {:alias, 'Foo'}, '+'}
118      assert CF.cursor_context("Foo . + (\t") == {:dot_call, {:alias, 'Foo'}, '+'}
119    end
120
121    test "alias" do
122      assert CF.cursor_context("HelloWor") == {:alias, 'HelloWor'}
123      assert CF.cursor_context("Hello.Wor") == {:alias, 'Hello.Wor'}
124      assert CF.cursor_context("Hello . Wor") == {:alias, 'Hello.Wor'}
125      assert CF.cursor_context("Hello::Wor") == {:alias, 'Wor'}
126      assert CF.cursor_context("Hello..Wor") == {:alias, 'Wor'}
127    end
128
129    test "structs" do
130      assert CF.cursor_context("%") == {:struct, ''}
131      assert CF.cursor_context(":%") == {:unquoted_atom, '%'}
132      assert CF.cursor_context("::%") == {:struct, ''}
133
134      assert CF.cursor_context("%HelloWor") == {:struct, 'HelloWor'}
135      assert CF.cursor_context("%Hello.") == {:struct, 'Hello.'}
136      assert CF.cursor_context("%Hello.Wor") == {:struct, 'Hello.Wor'}
137      assert CF.cursor_context("% Hello . Wor") == {:struct, 'Hello.Wor'}
138    end
139
140    test "unquoted atom" do
141      assert CF.cursor_context(":") == {:unquoted_atom, ''}
142      assert CF.cursor_context(":HelloWor") == {:unquoted_atom, 'HelloWor'}
143      assert CF.cursor_context(":HelloWór") == {:unquoted_atom, 'HelloWór'}
144      assert CF.cursor_context(":hello_wor") == {:unquoted_atom, 'hello_wor'}
145      assert CF.cursor_context(":Óla_mundo") == {:unquoted_atom, 'Óla_mundo'}
146      assert CF.cursor_context(":Ol@_mundo") == {:unquoted_atom, 'Ol@_mundo'}
147      assert CF.cursor_context(":Ol@") == {:unquoted_atom, 'Ol@'}
148      assert CF.cursor_context("foo:hello_wor") == {:unquoted_atom, 'hello_wor'}
149
150      # Operators from atoms
151      assert CF.cursor_context(":+") == {:unquoted_atom, '+'}
152      assert CF.cursor_context(":or") == {:unquoted_atom, 'or'}
153      assert CF.cursor_context(":<") == {:unquoted_atom, '<'}
154      assert CF.cursor_context(":.") == {:unquoted_atom, '.'}
155      assert CF.cursor_context(":..") == {:unquoted_atom, '..'}
156      assert CF.cursor_context(":->") == {:unquoted_atom, '->'}
157      assert CF.cursor_context(":%") == {:unquoted_atom, '%'}
158    end
159
160    test "operators" do
161      assert CF.cursor_context("+") == {:operator, '+'}
162      assert CF.cursor_context("++") == {:operator, '++'}
163      assert CF.cursor_context("!") == {:operator, '!'}
164      assert CF.cursor_context("<") == {:operator, '<'}
165      assert CF.cursor_context("<<<") == {:operator, '<<<'}
166      assert CF.cursor_context("..") == {:operator, '..'}
167      assert CF.cursor_context("<~") == {:operator, '<~'}
168      assert CF.cursor_context("=~") == {:operator, '=~'}
169      assert CF.cursor_context("<~>") == {:operator, '<~>'}
170      assert CF.cursor_context("::") == {:operator, '::'}
171
172      assert CF.cursor_context("+ ") == {:operator_call, '+'}
173      assert CF.cursor_context("++ ") == {:operator_call, '++'}
174      assert CF.cursor_context("! ") == {:operator_call, '!'}
175      assert CF.cursor_context("< ") == {:operator_call, '<'}
176      assert CF.cursor_context("<<< ") == {:operator_call, '<<<'}
177      assert CF.cursor_context(".. ") == {:operator_call, '..'}
178      assert CF.cursor_context("<~ ") == {:operator_call, '<~'}
179      assert CF.cursor_context("=~ ") == {:operator_call, '=~'}
180      assert CF.cursor_context("<~> ") == {:operator_call, '<~>'}
181      assert CF.cursor_context(":: ") == {:operator_call, '::'}
182
183      assert CF.cursor_context("+/") == {:operator_arity, '+'}
184      assert CF.cursor_context("++/") == {:operator_arity, '++'}
185      assert CF.cursor_context("!/") == {:operator_arity, '!'}
186      assert CF.cursor_context("</") == {:operator_arity, '<'}
187      assert CF.cursor_context("<<</") == {:operator_arity, '<<<'}
188      assert CF.cursor_context("../") == {:operator_arity, '..'}
189      assert CF.cursor_context("<~/") == {:operator_arity, '<~'}
190      assert CF.cursor_context("=~/") == {:operator_arity, '=~'}
191      assert CF.cursor_context("<~>/") == {:operator_arity, '<~>'}
192      assert CF.cursor_context("::/") == {:operator_arity, '::'}
193
194      # Unknown operators altogether
195      assert CF.cursor_context("***") == :none
196
197      # Textual operators are shown as local_or_var UNLESS there is space
198      assert CF.cursor_context("when") == {:local_or_var, 'when'}
199      assert CF.cursor_context("when ") == {:operator_call, 'when'}
200      assert CF.cursor_context("when.") == :none
201
202      assert CF.cursor_context("not") == {:local_or_var, 'not'}
203      assert CF.cursor_context("not ") == {:operator_call, 'not'}
204      assert CF.cursor_context("not.") == :none
205    end
206
207    test "incomplete operators" do
208      assert CF.cursor_context("~~") == {:operator, '~~'}
209      assert CF.cursor_context("~~ ") == :none
210      assert CF.cursor_context("^^") == {:operator, '^^'}
211      assert CF.cursor_context("^^ ") == :none
212
213      assert CF.cursor_context("Foo.~") == {:dot, {:alias, 'Foo'}, '~'}
214      assert CF.cursor_context("Foo . ~") == {:dot, {:alias, 'Foo'}, '~'}
215      assert CF.cursor_context("Foo.~~") == {:dot, {:alias, 'Foo'}, '~~'}
216      assert CF.cursor_context("Foo . ~~") == {:dot, {:alias, 'Foo'}, '~~'}
217      assert CF.cursor_context("Foo.~ ") == :none
218      assert CF.cursor_context("Foo.~~ ") == :none
219      assert CF.cursor_context("Foo.^^") == {:dot, {:alias, 'Foo'}, '^^'}
220      assert CF.cursor_context("Foo . ^^") == {:dot, {:alias, 'Foo'}, '^^'}
221      assert CF.cursor_context("Foo.^^ ") == :none
222    end
223
224    test "sigil" do
225      assert CF.cursor_context("~") == {:sigil, ''}
226      assert CF.cursor_context("~ ") == :none
227
228      assert CF.cursor_context("~r") == {:sigil, 'r'}
229      assert CF.cursor_context("~r/") == :none
230      assert CF.cursor_context("~r<") == :none
231
232      assert CF.cursor_context("~R") == {:sigil, 'R'}
233      assert CF.cursor_context("~R/") == :none
234      assert CF.cursor_context("~R<") == :none
235    end
236
237    test "module attribute" do
238      assert CF.cursor_context("@") == {:module_attribute, ''}
239      assert CF.cursor_context("@hello_wo") == {:module_attribute, 'hello_wo'}
240    end
241
242    test "none" do
243      # Punctuation
244      assert CF.cursor_context(")") == :none
245      assert CF.cursor_context("}") == :none
246      assert CF.cursor_context(">>") == :none
247      assert CF.cursor_context("'") == :none
248      assert CF.cursor_context("\"") == :none
249
250      # Numbers
251      assert CF.cursor_context("123") == :none
252      assert CF.cursor_context("123?") == :none
253      assert CF.cursor_context("123!") == :none
254      assert CF.cursor_context("123var?") == :none
255      assert CF.cursor_context("0x") == :none
256
257      # Codepoints
258      assert CF.cursor_context("?") == :none
259      assert CF.cursor_context("?a") == :none
260      assert CF.cursor_context("?foo") == :none
261
262      # Dots
263      assert CF.cursor_context(".") == :none
264      assert CF.cursor_context("Mundo.Óla") == :none
265      assert CF.cursor_context(":hello.World") == :none
266
267      # Aliases
268      assert CF.cursor_context("Hello::Wór") == :none
269      assert CF.cursor_context("ÓlaMundo") == :none
270      assert CF.cursor_context("HelloWór") == :none
271      assert CF.cursor_context("@Hello") == :none
272      assert CF.cursor_context("Hello(") == :none
273      assert CF.cursor_context("Hello ") == :none
274      assert CF.cursor_context("hello.World") == :none
275
276      # Identifier
277      assert CF.cursor_context("foo@bar") == :none
278      assert CF.cursor_context("@foo@bar") == :none
279    end
280
281    test "newlines" do
282      assert CF.cursor_context("this+does-not*matter\nHello.") == {:dot, {:alias, 'Hello'}, ''}
283      assert CF.cursor_context('this+does-not*matter\nHello.') == {:dot, {:alias, 'Hello'}, ''}
284    end
285  end
286
287  describe "surround_context/2" do
288    test "newlines" do
289      for i <- 1..8 do
290        assert CF.surround_context("\n\nhello_wo\n", {3, i}) == %{
291                 context: {:local_or_var, 'hello_wo'},
292                 begin: {3, 1},
293                 end: {3, 9}
294               }
295      end
296    end
297
298    test "column out of range" do
299      assert CF.surround_context("hello", {1, 20}) == :none
300    end
301
302    test "local_or_var" do
303      for i <- 1..8 do
304        assert CF.surround_context("hello_wo", {1, i}) == %{
305                 context: {:local_or_var, 'hello_wo'},
306                 begin: {1, 1},
307                 end: {1, 9}
308               }
309      end
310
311      assert CF.surround_context("hello_wo", {1, 9}) == :none
312
313      for i <- 2..9 do
314        assert CF.surround_context(" hello_wo", {1, i}) == %{
315                 context: {:local_or_var, 'hello_wo'},
316                 begin: {1, 2},
317                 end: {1, 10}
318               }
319      end
320
321      assert CF.surround_context(" hello_wo", {1, 10}) == :none
322
323      for i <- 1..6 do
324        assert CF.surround_context("hello!", {1, i}) == %{
325                 context: {:local_or_var, 'hello!'},
326                 begin: {1, 1},
327                 end: {1, 7}
328               }
329      end
330
331      assert CF.surround_context("hello!", {1, 7}) == :none
332
333      for i <- 1..5 do
334        assert CF.surround_context("안녕_세상", {1, i}) == %{
335                 context: {:local_or_var, '안녕_세상'},
336                 begin: {1, 1},
337                 end: {1, 6}
338               }
339      end
340
341      assert CF.surround_context("안녕_세상", {1, 6}) == :none
342
343      # Keywords are not local or var
344      for keyword <- ~w(do end after catch else rescue) do
345        assert CF.surround_context(keyword, {1, 1}) == :none
346      end
347    end
348
349    test "local call" do
350      for i <- 1..8 do
351        assert CF.surround_context("hello_wo(", {1, i}) == %{
352                 context: {:local_call, 'hello_wo'},
353                 begin: {1, 1},
354                 end: {1, 9}
355               }
356      end
357
358      assert CF.surround_context("hello_wo(", {1, 9}) == :none
359
360      for i <- 1..8 do
361        assert CF.surround_context("hello_wo (", {1, i}) == %{
362                 context: {:local_call, 'hello_wo'},
363                 begin: {1, 1},
364                 end: {1, 9}
365               }
366      end
367
368      assert CF.surround_context("hello_wo (", {1, 9}) == :none
369
370      for i <- 1..6 do
371        assert CF.surround_context("hello!(", {1, i}) == %{
372                 context: {:local_call, 'hello!'},
373                 begin: {1, 1},
374                 end: {1, 7}
375               }
376      end
377
378      assert CF.surround_context("hello!(", {1, 7}) == :none
379
380      for i <- 1..5 do
381        assert CF.surround_context("안녕_세상(", {1, i}) == %{
382                 context: {:local_call, '안녕_세상'},
383                 begin: {1, 1},
384                 end: {1, 6}
385               }
386      end
387
388      assert CF.surround_context("안녕_세상(", {1, 6}) == :none
389    end
390
391    test "local arity" do
392      for i <- 1..8 do
393        assert CF.surround_context("hello_wo/", {1, i}) == %{
394                 context: {:local_arity, 'hello_wo'},
395                 begin: {1, 1},
396                 end: {1, 9}
397               }
398      end
399
400      assert CF.surround_context("hello_wo/", {1, 9}) == :none
401
402      for i <- 1..8 do
403        assert CF.surround_context("hello_wo /", {1, i}) == %{
404                 context: {:local_arity, 'hello_wo'},
405                 begin: {1, 1},
406                 end: {1, 9}
407               }
408      end
409
410      assert CF.surround_context("hello_wo /", {1, 9}) == :none
411
412      for i <- 1..6 do
413        assert CF.surround_context("hello!/", {1, i}) == %{
414                 context: {:local_arity, 'hello!'},
415                 begin: {1, 1},
416                 end: {1, 7}
417               }
418      end
419
420      assert CF.surround_context("hello!/", {1, 7}) == :none
421
422      for i <- 1..5 do
423        assert CF.surround_context("안녕_세상/", {1, i}) == %{
424                 context: {:local_arity, '안녕_세상'},
425                 begin: {1, 1},
426                 end: {1, 6}
427               }
428      end
429
430      assert CF.surround_context("안녕_세상/", {1, 6}) == :none
431    end
432
433    test "textual operators" do
434      for op <- ~w(when not or and in), i <- 1..byte_size(op) do
435        assert CF.surround_context("#{op}", {1, i}) == %{
436                 context: {:operator, String.to_charlist(op)},
437                 begin: {1, 1},
438                 end: {1, byte_size(op) + 1}
439               }
440      end
441    end
442
443    test "dot" do
444      for i <- 1..5 do
445        assert CF.surround_context("Hello.wor", {1, i}) == %{
446                 context: {:alias, 'Hello'},
447                 begin: {1, 1},
448                 end: {1, 6}
449               }
450      end
451
452      for i <- 6..9 do
453        assert CF.surround_context("Hello.wor", {1, i}) == %{
454                 context: {:dot, {:alias, 'Hello'}, 'wor'},
455                 begin: {1, 1},
456                 end: {1, 10}
457               }
458      end
459
460      assert CF.surround_context("Hello.", {1, 6}) == :none
461
462      for i <- 1..5 do
463        assert CF.surround_context("Hello . wor", {1, i}) == %{
464                 context: {:alias, 'Hello'},
465                 begin: {1, 1},
466                 end: {1, 6}
467               }
468      end
469
470      for i <- 6..11 do
471        assert CF.surround_context("Hello . wor", {1, i}) == %{
472                 context: {:dot, {:alias, 'Hello'}, 'wor'},
473                 begin: {1, 1},
474                 end: {1, 12}
475               }
476      end
477
478      assert CF.surround_context("Hello .", {1, 6}) == :none
479
480      for i <- 1..5 do
481        assert CF.surround_context("hello.wor", {1, i}) == %{
482                 context: {:local_or_var, 'hello'},
483                 begin: {1, 1},
484                 end: {1, 6}
485               }
486      end
487
488      for i <- 6..9 do
489        assert CF.surround_context("hello.wor", {1, i}) == %{
490                 context: {:dot, {:var, 'hello'}, 'wor'},
491                 begin: {1, 1},
492                 end: {1, 10}
493               }
494      end
495    end
496
497    test "alias" do
498      for i <- 1..8 do
499        assert CF.surround_context("HelloWor", {1, i}) == %{
500                 context: {:alias, 'HelloWor'},
501                 begin: {1, 1},
502                 end: {1, 9}
503               }
504      end
505
506      assert CF.surround_context("HelloWor", {1, 9}) == :none
507
508      for i <- 2..9 do
509        assert CF.surround_context(" HelloWor", {1, i}) == %{
510                 context: {:alias, 'HelloWor'},
511                 begin: {1, 2},
512                 end: {1, 10}
513               }
514      end
515
516      assert CF.surround_context(" HelloWor", {1, 10}) == :none
517
518      for i <- 1..9 do
519        assert CF.surround_context("Hello.Wor", {1, i}) == %{
520                 context: {:alias, 'Hello.Wor'},
521                 begin: {1, 1},
522                 end: {1, 10}
523               }
524      end
525
526      assert CF.surround_context("Hello.Wor", {1, 10}) == :none
527
528      for i <- 1..11 do
529        assert CF.surround_context("Hello . Wor", {1, i}) == %{
530                 context: {:alias, 'Hello.Wor'},
531                 begin: {1, 1},
532                 end: {1, 12}
533               }
534      end
535
536      assert CF.surround_context("Hello . Wor", {1, 12}) == :none
537
538      for i <- 1..15 do
539        assert CF.surround_context("Foo . Bar . Baz", {1, i}) == %{
540                 context: {:alias, 'Foo.Bar.Baz'},
541                 begin: {1, 1},
542                 end: {1, 16}
543               }
544      end
545    end
546
547    test "struct" do
548      assert CF.surround_context("%", {1, 1}) == :none
549      assert CF.surround_context("::%", {1, 1}) == :none
550      assert CF.surround_context("::%", {1, 2}) == :none
551      assert CF.surround_context("::%Hello", {1, 1}) == :none
552      assert CF.surround_context("::%Hello", {1, 2}) == :none
553
554      assert CF.surround_context("::%Hello", {1, 3}) == %{
555               context: {:struct, 'Hello'},
556               begin: {1, 3},
557               end: {1, 9}
558             }
559
560      assert CF.surround_context("::% Hello", {1, 3}) == %{
561               context: {:struct, 'Hello'},
562               begin: {1, 3},
563               end: {1, 10}
564             }
565
566      assert CF.surround_context("::% Hello", {1, 4}) == %{
567               context: {:struct, 'Hello'},
568               begin: {1, 3},
569               end: {1, 10}
570             }
571
572      # Alias
573      assert CF.surround_context("%HelloWor", {1, 1}) == %{
574               context: {:struct, 'HelloWor'},
575               begin: {1, 1},
576               end: {1, 10}
577             }
578
579      for i <- 2..9 do
580        assert CF.surround_context("%HelloWor", {1, i}) == %{
581                 context: {:struct, 'HelloWor'},
582                 begin: {1, 1},
583                 end: {1, 10}
584               }
585      end
586
587      assert CF.surround_context("%HelloWor", {1, 10}) == :none
588
589      # With dot
590      assert CF.surround_context("%Hello.Wor", {1, 1}) == %{
591               context: {:struct, 'Hello.Wor'},
592               begin: {1, 1},
593               end: {1, 11}
594             }
595
596      for i <- 2..10 do
597        assert CF.surround_context("%Hello.Wor", {1, i}) == %{
598                 context: {:struct, 'Hello.Wor'},
599                 begin: {1, 1},
600                 end: {1, 11}
601               }
602      end
603
604      assert CF.surround_context("%Hello.Wor", {1, 11}) == :none
605
606      # With spaces
607      assert CF.surround_context("% Hello . Wor", {1, 1}) == %{
608               context: {:struct, 'Hello.Wor'},
609               begin: {1, 1},
610               end: {1, 14}
611             }
612
613      for i <- 2..13 do
614        assert CF.surround_context("% Hello . Wor", {1, i}) == %{
615                 context: {:struct, 'Hello.Wor'},
616                 begin: {1, 1},
617                 end: {1, 14}
618               }
619      end
620
621      assert CF.surround_context("% Hello . Wor", {1, 14}) == :none
622    end
623
624    test "module attributes" do
625      for i <- 1..10 do
626        assert CF.surround_context("@hello_wor", {1, i}) == %{
627                 context: {:module_attribute, 'hello_wor'},
628                 begin: {1, 1},
629                 end: {1, 11}
630               }
631      end
632
633      assert CF.surround_context("@Hello", {1, 1}) == :none
634    end
635
636    test "operators" do
637      for i <- 2..4 do
638        assert CF.surround_context("1<<<3", {1, i}) == %{
639                 context: {:operator, '<<<'},
640                 begin: {1, 2},
641                 end: {1, 5}
642               }
643      end
644
645      for i <- 3..5 do
646        assert CF.surround_context("1 <<< 3", {1, i}) == %{
647                 context: {:operator, '<<<'},
648                 begin: {1, 3},
649                 end: {1, 6}
650               }
651      end
652
653      for i <- 2..3 do
654        assert CF.surround_context("1::3", {1, i}) == %{
655                 context: {:operator, '::'},
656                 begin: {1, 2},
657                 end: {1, 4}
658               }
659      end
660
661      for i <- 3..4 do
662        assert CF.surround_context("1 :: 3", {1, i}) == %{
663                 context: {:operator, '::'},
664                 begin: {1, 3},
665                 end: {1, 5}
666               }
667      end
668
669      for i <- 2..3 do
670        assert CF.surround_context("x..y", {1, i}) == %{
671                 context: {:operator, '..'},
672                 begin: {1, 2},
673                 end: {1, 4}
674               }
675      end
676
677      for i <- 3..4 do
678        assert CF.surround_context("x .. y", {1, i}) == %{
679                 context: {:operator, '..'},
680                 begin: {1, 3},
681                 end: {1, 5}
682               }
683      end
684
685      assert CF.surround_context("@", {1, 1}) == %{
686               context: {:operator, '@'},
687               begin: {1, 1},
688               end: {1, 2}
689             }
690
691      assert CF.surround_context("!", {1, 1}) == %{
692               context: {:operator, '!'},
693               begin: {1, 1},
694               end: {1, 2}
695             }
696
697      assert CF.surround_context("!foo", {1, 1}) == %{
698               context: {:operator, '!'},
699               begin: {1, 1},
700               end: {1, 2}
701             }
702
703      assert CF.surround_context("foo !bar", {1, 5}) == %{
704               context: {:operator, '!'},
705               begin: {1, 5},
706               end: {1, 6}
707             }
708    end
709
710    test "sigil" do
711      assert CF.surround_context("~", {1, 1}) == :none
712      assert CF.surround_context("~~r", {1, 1}) == :none
713      assert CF.surround_context("~~r", {1, 2}) == :none
714
715      assert CF.surround_context("~~~", {1, 1}) == %{
716               begin: {1, 1},
717               context: {:operator, '~~~'},
718               end: {1, 4}
719             }
720
721      assert CF.surround_context("~r/foo/", {1, 1}) == %{
722               begin: {1, 1},
723               context: {:sigil, 'r'},
724               end: {1, 3}
725             }
726
727      assert CF.surround_context("~r/foo/", {1, 2}) == %{
728               begin: {1, 1},
729               context: {:sigil, 'r'},
730               end: {1, 3}
731             }
732
733      assert CF.surround_context("~r/foo/", {1, 3}) == :none
734
735      assert CF.surround_context("~R<foo>", {1, 1}) == %{
736               begin: {1, 1},
737               context: {:sigil, 'R'},
738               end: {1, 3}
739             }
740
741      assert CF.surround_context("~R<foo>", {1, 2}) == %{
742               begin: {1, 1},
743               context: {:sigil, 'R'},
744               end: {1, 3}
745             }
746
747      assert CF.surround_context("~R<foo>", {1, 3}) == :none
748    end
749
750    test "dot operator" do
751      for i <- 4..7 do
752        assert CF.surround_context("Foo.<<<", {1, i}) == %{
753                 context: {:dot, {:alias, 'Foo'}, '<<<'},
754                 begin: {1, 1},
755                 end: {1, 8}
756               }
757      end
758
759      for i <- 4..9 do
760        assert CF.surround_context("Foo . <<<", {1, i}) == %{
761                 context: {:dot, {:alias, 'Foo'}, '<<<'},
762                 begin: {1, 1},
763                 end: {1, 10}
764               }
765      end
766
767      for i <- 4..6 do
768        assert CF.surround_context("Foo.::", {1, i}) == %{
769                 context: {:dot, {:alias, 'Foo'}, '::'},
770                 begin: {1, 1},
771                 end: {1, 7}
772               }
773      end
774
775      for i <- 4..8 do
776        assert CF.surround_context("Foo . ::", {1, i}) == %{
777                 context: {:dot, {:alias, 'Foo'}, '::'},
778                 begin: {1, 1},
779                 end: {1, 9}
780               }
781      end
782    end
783
784    test "unquoted atom" do
785      for i <- 1..10 do
786        assert CF.surround_context(":hello_wor", {1, i}) == %{
787                 context: {:unquoted_atom, 'hello_wor'},
788                 begin: {1, 1},
789                 end: {1, 11}
790               }
791      end
792
793      for i <- 1..10 do
794        assert CF.surround_context(":Hello@Wor", {1, i}) == %{
795                 context: {:unquoted_atom, 'Hello@Wor'},
796                 begin: {1, 1},
797                 end: {1, 11}
798               }
799      end
800
801      assert CF.surround_context(":", {1, 1}) == :none
802    end
803  end
804
805  describe "container_cursor_to_quoted/2" do
806    def s2q(arg, opts \\ []), do: Code.string_to_quoted(arg, opts)
807    def cc2q(arg, opts \\ []), do: CF.container_cursor_to_quoted(arg, opts)
808
809    test "completes terminators" do
810      assert cc2q("(") == s2q("(__cursor__())")
811      assert cc2q("[") == s2q("[__cursor__()]")
812      assert cc2q("{") == s2q("{__cursor__()}")
813      assert cc2q("<<") == s2q("<<__cursor__()>>")
814      assert cc2q("foo do") == s2q("foo do __cursor__() end")
815      assert cc2q("foo do true else") == s2q("foo do true else __cursor__() end")
816    end
817
818    test "inside interpolation" do
819      assert cc2q(~S|"foo #{(|) == s2q(~S|"foo #{(__cursor__())}"|)
820      assert cc2q(~S|"foo #{"bar #{{|) == s2q(~S|"foo #{"bar #{{__cursor__()}}"}"|)
821    end
822
823    test "do-end blocks" do
824      assert cc2q("foo(bar do baz") == s2q("foo(bar do __cursor__() end)")
825      assert cc2q("foo(bar do baz ") == s2q("foo(bar do baz(__cursor__()) end)")
826      assert cc2q("foo(bar do baz(") == s2q("foo(bar do baz(__cursor__()) end)")
827      assert cc2q("foo(bar do baz bat,") == s2q("foo(bar do baz(bat, __cursor__()) end)")
828
829      assert {:error, {_, "syntax error before: ", "'end'"}} = cc2q("foo(bar do baz, bat")
830    end
831
832    test "keyword lists" do
833      assert cc2q("[bar: ") == s2q("[bar: __cursor__()]")
834      assert cc2q("[bar: baz,") == s2q("[bar: baz, __cursor__()]")
835      assert cc2q("[arg, bar: baz,") == s2q("[arg, bar: baz, __cursor__()]")
836      assert cc2q("[arg: val, bar: baz,") == s2q("[arg: val, bar: baz, __cursor__()]")
837
838      assert cc2q("{arg, bar: ") == s2q("{arg, bar: __cursor__()}")
839      assert cc2q("{arg, bar: baz,") == s2q("{arg, bar: baz, __cursor__()}")
840      assert cc2q("{arg: val, bar: baz,") == s2q("{arg: val, bar: baz, __cursor__()}")
841
842      assert cc2q("foo(bar: ") == s2q("foo(bar: __cursor__())")
843      assert cc2q("foo(bar: baz,") == s2q("foo([bar: baz, __cursor__()])")
844      assert cc2q("foo(arg, bar: ") == s2q("foo(arg, bar: __cursor__())")
845      assert cc2q("foo(arg, bar: baz,") == s2q("foo(arg, [bar: baz, __cursor__()])")
846      assert cc2q("foo(arg: val, bar: ") == s2q("foo(arg: val, bar: __cursor__())")
847      assert cc2q("foo(arg: val, bar: baz,") == s2q("foo([arg: val, bar: baz, __cursor__()])")
848
849      assert cc2q("foo bar: ") == s2q("foo(bar: __cursor__())")
850      assert cc2q("foo bar: baz,") == s2q("foo([bar: baz, __cursor__()])")
851      assert cc2q("foo arg, bar: ") == s2q("foo(arg, bar: __cursor__())")
852      assert cc2q("foo arg, bar: baz,") == s2q("foo(arg, [bar: baz, __cursor__()])")
853      assert cc2q("foo arg: val, bar: ") == s2q("foo(arg: val, bar: __cursor__())")
854      assert cc2q("foo arg: val, bar: baz,") == s2q("foo([arg: val, bar: baz, __cursor__()])")
855    end
856
857    test "maps and structs" do
858      assert cc2q("%") == s2q("__cursor__()")
859      assert cc2q("%{") == s2q("%{__cursor__()}")
860      assert cc2q("%{bar:") == s2q("%{__cursor__()}")
861      assert cc2q("%{bar: ") == s2q("%{bar: __cursor__()}")
862      assert cc2q("%{bar: baz,") == s2q("%{bar: baz, __cursor__()}")
863
864      assert cc2q("%Foo") == s2q("__cursor__()")
865      assert cc2q("%Foo{") == s2q("%Foo{__cursor__()}")
866      assert cc2q("%Foo{bar: ") == s2q("%Foo{bar: __cursor__()}")
867      assert cc2q("%Foo{bar: baz,") == s2q("%Foo{bar: baz, __cursor__()}")
868    end
869
870    test "removes tokens until opening" do
871      assert cc2q("(123") == s2q("(__cursor__())")
872      assert cc2q("[foo") == s2q("[__cursor__()]")
873      assert cc2q("{'foo'") == s2q("{__cursor__()}")
874      assert cc2q("<<1+2") == s2q("<<__cursor__()>>")
875      assert cc2q("foo do :atom") == s2q("foo do __cursor__() end")
876      assert cc2q("foo(:atom") == s2q("foo(__cursor__())")
877    end
878
879    test "removes tokens until comma" do
880      assert cc2q("[bar, 123") == s2q("[bar, __cursor__()]")
881      assert cc2q("{bar, 'foo'") == s2q("{bar, __cursor__()}")
882      assert cc2q("<<bar, 1+2") == s2q("<<bar, __cursor__()>>")
883      assert cc2q("foo(bar, :atom") == s2q("foo(bar, __cursor__())")
884      assert cc2q("foo bar, :atom") == s2q("foo(bar, __cursor__())")
885    end
886
887    test "removes functions" do
888      assert cc2q("(fn") == s2q("(__cursor__())")
889      assert cc2q("(fn x") == s2q("(__cursor__())")
890      assert cc2q("(fn x ->") == s2q("(__cursor__())")
891      assert cc2q("(fn x -> x") == s2q("(__cursor__())")
892      assert cc2q("(fn x, y -> x + y") == s2q("(__cursor__())")
893      assert cc2q("(fn x, y -> x + y end") == s2q("(__cursor__())")
894    end
895
896    test "removes captures" do
897      assert cc2q("[& &1") == s2q("[__cursor__()]")
898      assert cc2q("[&(&1") == s2q("[__cursor__()]")
899    end
900
901    test "removes closed terminators" do
902      assert cc2q("foo([1, 2, 3] |>") == s2q("foo(__cursor__())")
903      assert cc2q("foo({1, 2, 3} |>") == s2q("foo(__cursor__())")
904      assert cc2q("foo((1, 2, 3) |>") == s2q("foo(__cursor__())")
905      assert cc2q("foo(<<1, 2, 3>> |>") == s2q("foo(__cursor__())")
906      assert cc2q("foo(bar do :done end |>") == s2q("foo(__cursor__())")
907    end
908
909    test "incomplete expressions" do
910      assert cc2q("foo(123, :") == s2q("foo(123, __cursor__())")
911      assert cc2q("foo(123, %") == s2q("foo(123, __cursor__())")
912      assert cc2q("foo(123, 0x") == s2q("foo(123, __cursor__())")
913      assert cc2q("foo(123, ~") == s2q("foo(123, __cursor__())")
914      assert cc2q("foo(123, ~r") == s2q("foo(123, __cursor__())")
915      assert cc2q("foo(123, ~r/") == s2q("foo(123, __cursor__())")
916    end
917
918    test "options" do
919      opts = [columns: true]
920      assert cc2q("foo(", opts) == s2q("foo(__cursor__())", opts)
921      assert cc2q("foo(123,", opts) == s2q("foo(123,__cursor__())", opts)
922
923      opts = [token_metadata: true]
924      assert cc2q("foo(", opts) == s2q("foo(__cursor__())", opts)
925      assert cc2q("foo(123,", opts) == s2q("foo(123,__cursor__())", opts)
926    end
927  end
928end
929