mt-lang 0.3.38 → 0.3.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/docs/lsp-performance.md +347 -0
- data/lib/milk_tea/base.rb +1 -1
- data/lib/milk_tea/lsp/diagnostics.rb +15 -5
- data/lib/milk_tea/lsp/server/completion.rb +100 -77
- data/lib/milk_tea/lsp/server/diagnostics_scheduling.rb +4 -6
- data/lib/milk_tea/lsp/server/formatting.rb +13 -3
- data/lib/milk_tea/lsp/server/hover.rb +321 -41
- data/lib/milk_tea/lsp/server/lifecycle.rb +12 -0
- data/lib/milk_tea/lsp/server/references.rb +3 -1
- data/lib/milk_tea/lsp/server/semantic_tokens.rb +55 -16
- data/lib/milk_tea/lsp/server/text_documents.rb +2 -2
- data/lib/milk_tea/lsp/server/type_hierarchy.rb +2 -2
- data/lib/milk_tea/lsp/server/utilities.rb +4 -0
- data/lib/milk_tea/lsp/server.rb +9 -0
- data/lib/milk_tea/lsp/workspace/analysis.rb +14 -3
- data/lib/milk_tea/lsp/workspace/caches.rb +17 -1
- data/lib/milk_tea/lsp/workspace/collection.rb +4 -0
- data/lib/milk_tea/lsp/workspace/module_index.rb +134 -0
- data/lib/milk_tea/lsp/workspace/store.rb +9 -4
- data/lib/milk_tea/lsp/workspace.rb +8 -0
- metadata +4 -2
|
@@ -271,6 +271,24 @@ module MilkTea
|
|
|
271
271
|
'Subscription' => 'Opaque handle returned by `event.subscribe`. Pass to `event.unsubscribe` to remove a listener.',
|
|
272
272
|
}.freeze
|
|
273
273
|
|
|
274
|
+
VEC_METHOD_SIGNATURES = {
|
|
275
|
+
'with' => 'function with(...) -> vec',
|
|
276
|
+
'dot' => 'function dot(other: vec) -> float',
|
|
277
|
+
'length' => 'function length() -> float',
|
|
278
|
+
'length_sq' => 'function length_sq() -> float',
|
|
279
|
+
'cross' => 'function cross(other: vec) -> vec',
|
|
280
|
+
'normalized' => 'function normalized() -> vec',
|
|
281
|
+
'squared_len' => 'function squared_len() -> float',
|
|
282
|
+
}.freeze
|
|
283
|
+
|
|
284
|
+
PTR_METHOD_SIGNATURES = {
|
|
285
|
+
'load' => 'function load() -> T',
|
|
286
|
+
'store' => 'function store(value: T) -> void',
|
|
287
|
+
'load_at' => 'function load_at(offset: ptr_uint) -> T',
|
|
288
|
+
'store_at' => 'function store_at(offset: ptr_uint, value: T) -> void',
|
|
289
|
+
'cast' => 'function cast() -> ptr[U]',
|
|
290
|
+
}.freeze
|
|
291
|
+
|
|
274
292
|
BUILTIN_TYPE_METHOD_SIGNATURES = {
|
|
275
293
|
'atomic' => {
|
|
276
294
|
'load' => 'function load() -> T',
|
|
@@ -298,6 +316,44 @@ module MilkTea
|
|
|
298
316
|
'as_str' => 'static function as_str() -> str',
|
|
299
317
|
'as_cstr' => 'static function as_cstr() -> cstr',
|
|
300
318
|
},
|
|
319
|
+
'vec2' => VEC_METHOD_SIGNATURES,
|
|
320
|
+
'vec3' => VEC_METHOD_SIGNATURES,
|
|
321
|
+
'vec4' => VEC_METHOD_SIGNATURES,
|
|
322
|
+
'ivec2' => VEC_METHOD_SIGNATURES,
|
|
323
|
+
'ivec3' => VEC_METHOD_SIGNATURES,
|
|
324
|
+
'ivec4' => VEC_METHOD_SIGNATURES,
|
|
325
|
+
'mat3' => {
|
|
326
|
+
'identity' => 'static function identity() -> mat3',
|
|
327
|
+
'with' => 'function with(...) -> mat3',
|
|
328
|
+
},
|
|
329
|
+
'mat4' => {
|
|
330
|
+
'identity' => 'static function identity() -> mat4',
|
|
331
|
+
'with' => 'function with(...) -> mat4',
|
|
332
|
+
},
|
|
333
|
+
'quat' => {
|
|
334
|
+
'identity' => 'static function identity() -> quat',
|
|
335
|
+
'with' => 'function with(x: float, y: float, z: float, w: float) -> quat',
|
|
336
|
+
},
|
|
337
|
+
'simd' => {
|
|
338
|
+
'with' => 'function with(index: int, value: T) -> simd[T, N]',
|
|
339
|
+
'load' => 'function load() -> T',
|
|
340
|
+
'store' => 'function store(value: T) -> void',
|
|
341
|
+
},
|
|
342
|
+
'array' => {
|
|
343
|
+
'as_span' => 'function as_span() -> span[T]',
|
|
344
|
+
'len' => 'function len() -> ptr_uint',
|
|
345
|
+
'with' => 'function with(index: int, value: T) -> array[T, N]',
|
|
346
|
+
},
|
|
347
|
+
'span' => {
|
|
348
|
+
'len' => 'function len() -> ptr_uint',
|
|
349
|
+
},
|
|
350
|
+
'str' => {
|
|
351
|
+
'len' => 'function len() -> ptr_uint',
|
|
352
|
+
'slice' => 'function slice(start: ptr_uint, len: ptr_uint) -> str',
|
|
353
|
+
},
|
|
354
|
+
'ptr' => PTR_METHOD_SIGNATURES,
|
|
355
|
+
'const_ptr' => PTR_METHOD_SIGNATURES,
|
|
356
|
+
'own' => PTR_METHOD_SIGNATURES,
|
|
301
357
|
}.freeze
|
|
302
358
|
|
|
303
359
|
def handle_hover(params)
|
|
@@ -466,11 +522,11 @@ module MilkTea
|
|
|
466
522
|
docs ||= BUILTIN_TYPE_DOCS[name]
|
|
467
523
|
elsif (binding = facts.values[name])
|
|
468
524
|
signature = value_hover_signature(binding)
|
|
469
|
-
elsif
|
|
525
|
+
elsif bare_member_name_candidate?(tokens, token_index) && (member_info = find_member_in_types(facts, name))
|
|
470
526
|
type, member, value = member_info
|
|
471
527
|
signature = value ? "#{type.name}.#{member} = #{value}" : "#{type.name}.#{member}"
|
|
472
528
|
resolved_via_type_member = true
|
|
473
|
-
elsif
|
|
529
|
+
elsif bare_member_name_candidate?(tokens, token_index) && (arm_sig = find_variant_arm_in_types(facts, name))
|
|
474
530
|
signature = arm_sig
|
|
475
531
|
resolved_via_type_member = true
|
|
476
532
|
elsif (import_binding = facts.imports[name])
|
|
@@ -526,7 +582,7 @@ module MilkTea
|
|
|
526
582
|
if (method_binding = methods[name])
|
|
527
583
|
signature = method_signature(method_binding)
|
|
528
584
|
else
|
|
529
|
-
type_base = receiver_type.to_s[/^([A-Za-
|
|
585
|
+
type_base = receiver_type.to_s[/^([A-Za-z0-9_]+)/, 1]
|
|
530
586
|
if type_base && (method_sigs = BUILTIN_TYPE_METHOD_SIGNATURES[type_base])
|
|
531
587
|
signature = method_sigs[name]
|
|
532
588
|
end
|
|
@@ -549,8 +605,14 @@ module MilkTea
|
|
|
549
605
|
end
|
|
550
606
|
|
|
551
607
|
unless signature
|
|
552
|
-
if token_index && tokens
|
|
553
|
-
|
|
608
|
+
if token_index && tokens
|
|
609
|
+
if (type_param = type_parameter_hover_signature(tokens, token_index))
|
|
610
|
+
signature = type_param
|
|
611
|
+
elsif (attr_sig = builtin_attribute_hover_signature(tokens, token_index))
|
|
612
|
+
signature = attr_sig
|
|
613
|
+
elsif attribute_target_token?(tokens, token_index)
|
|
614
|
+
signature = "attribute target #{name}"
|
|
615
|
+
end
|
|
554
616
|
end
|
|
555
617
|
end
|
|
556
618
|
|
|
@@ -858,15 +920,20 @@ module MilkTea
|
|
|
858
920
|
def completion_function_documentation(uri, name, cache:)
|
|
859
921
|
key = [uri, name]
|
|
860
922
|
return cache[key] if cache.key?(key)
|
|
923
|
+
# Server-scoped memo so per-keystroke completion does not re-resolve the
|
|
924
|
+
# definition and re-extract docs for every candidate on every request.
|
|
925
|
+
if @completion_docs_cache.key?(key)
|
|
926
|
+
return cache[key] = @completion_docs_cache[key]
|
|
927
|
+
end
|
|
861
928
|
|
|
862
929
|
definition_entry = @workspace.find_definition_token_global(name, preferred_uri: uri)
|
|
863
930
|
unless definition_entry
|
|
864
931
|
cache[key] = ''
|
|
865
|
-
return ''
|
|
932
|
+
return @completion_docs_cache[key] = ''
|
|
866
933
|
end
|
|
867
934
|
|
|
868
935
|
doc_comment = @workspace.doc_comment_data_for_definition(definition_entry[:uri], definition_entry[:token])
|
|
869
|
-
cache[key] = signature_help_markdown_for_doc_comment(doc_comment)
|
|
936
|
+
@completion_docs_cache[key] = cache[key] = signature_help_markdown_for_doc_comment(doc_comment)
|
|
870
937
|
end
|
|
871
938
|
|
|
872
939
|
def hover_source_label_for_definition(definition_entry)
|
|
@@ -1018,14 +1085,25 @@ module MilkTea
|
|
|
1018
1085
|
|
|
1019
1086
|
method_receiver_type = project_method_receiver_type_for_completion(current_type)
|
|
1020
1087
|
method_info = member_method_info_for_receiver_type(facts, method_receiver_type, segment[:name])
|
|
1088
|
+
if method_info.nil?
|
|
1089
|
+
# Native/builtin receivers (vectors, matrices, pointers, arrays,
|
|
1090
|
+
# simd, atomic, ...) have no facts.methods entries; fall back to the
|
|
1091
|
+
# builtin method signature table so member calls resolve accurately
|
|
1092
|
+
# instead of falling through to a bare-name match.
|
|
1093
|
+
base = method_receiver_type.to_s[/^([A-Za-z0-9_]+)/, 1]
|
|
1094
|
+
method_info = base && BUILTIN_TYPE_METHOD_SIGNATURES[base]&.fetch(segment[:name], nil)
|
|
1095
|
+
method_info = { signature: method_info, module_name: nil, binding: nil } if method_info.is_a?(String)
|
|
1096
|
+
end
|
|
1021
1097
|
return nil unless method_info
|
|
1022
1098
|
|
|
1023
|
-
source_location =
|
|
1024
|
-
|
|
1099
|
+
source_location = if method_info[:binding]
|
|
1100
|
+
module_member_binding_location(current_uri, method_info[:module_name], segment[:name], method_info[:binding])
|
|
1101
|
+
end
|
|
1102
|
+
source_location ||= (module_member_definition_location(current_uri, method_info[:module_name], segment[:name]) if method_info[:module_name])
|
|
1025
1103
|
definition_entry = hover_definition_entry_from_location(source_location)
|
|
1026
1104
|
|
|
1027
1105
|
return {
|
|
1028
|
-
signature: method_signature(method_info[:binding]),
|
|
1106
|
+
signature: method_info[:signature] || (method_info[:binding] && method_signature(method_info[:binding])),
|
|
1029
1107
|
docs: hover_doc_comment_for_definition(definition_entry),
|
|
1030
1108
|
source: hover_source_label_from_location(source_location),
|
|
1031
1109
|
source_uri: hover_source_uri_from_location(source_location),
|
|
@@ -1197,9 +1275,88 @@ module MilkTea
|
|
|
1197
1275
|
end
|
|
1198
1276
|
end
|
|
1199
1277
|
|
|
1278
|
+
# Variant arm constructors: `Option[int].some(value = 42)`.
|
|
1279
|
+
if (arm_info = variant_arm_constructor_info(facts, tokens, token_index))
|
|
1280
|
+
field_type = arm_info[:fields][field_name]
|
|
1281
|
+
if field_type
|
|
1282
|
+
return {
|
|
1283
|
+
signature: "#{field_name}: #{field_type}",
|
|
1284
|
+
docs: nil,
|
|
1285
|
+
source: nil,
|
|
1286
|
+
source_uri: nil,
|
|
1287
|
+
source_line: nil,
|
|
1288
|
+
}
|
|
1289
|
+
end
|
|
1290
|
+
end
|
|
1291
|
+
|
|
1292
|
+
# Builtin constructors with named parameters: `span[T](data, len)`.
|
|
1293
|
+
if (builtin_arg = BUILTIN_NAMED_ARGUMENT_SIGNATURES.dig(receiver_name, field_name))
|
|
1294
|
+
return {
|
|
1295
|
+
signature: builtin_arg,
|
|
1296
|
+
docs: nil,
|
|
1297
|
+
source: nil,
|
|
1298
|
+
source_uri: nil,
|
|
1299
|
+
source_line: nil,
|
|
1300
|
+
}
|
|
1301
|
+
end
|
|
1302
|
+
|
|
1200
1303
|
nil
|
|
1201
1304
|
end
|
|
1202
1305
|
|
|
1306
|
+
BUILTIN_NAMED_ARGUMENT_SIGNATURES = {
|
|
1307
|
+
'span' => {
|
|
1308
|
+
'data' => 'data: ptr[T]',
|
|
1309
|
+
'len' => 'len: ptr_uint',
|
|
1310
|
+
},
|
|
1311
|
+
}.freeze
|
|
1312
|
+
|
|
1313
|
+
def variant_arm_constructor_info(facts, tokens, token_index)
|
|
1314
|
+
opener_index = parameter_list_opener_index(tokens, token_index)
|
|
1315
|
+
return nil unless opener_index
|
|
1316
|
+
|
|
1317
|
+
head_index = previous_non_trivia_token_index(tokens, opener_index)
|
|
1318
|
+
return nil unless head_index && tokens[head_index].type == :identifier
|
|
1319
|
+
|
|
1320
|
+
arm_name = tokens[head_index].lexeme
|
|
1321
|
+
|
|
1322
|
+
# Walk back over `.`, optional generic args `[...]`, to the type name.
|
|
1323
|
+
idx = head_index - 1
|
|
1324
|
+
idx = previous_non_trivia_token_index(tokens, idx) if idx >= 0 && tokens[idx].type == :dot
|
|
1325
|
+
if idx >= 0 && tokens[idx].type == :rbracket
|
|
1326
|
+
lbracket = matching_opener_index(tokens, idx)
|
|
1327
|
+
return nil unless lbracket
|
|
1328
|
+
|
|
1329
|
+
idx = previous_non_trivia_token_index(tokens, lbracket)
|
|
1330
|
+
end
|
|
1331
|
+
return nil unless idx && tokens[idx].type == :identifier
|
|
1332
|
+
|
|
1333
|
+
type_name = tokens[idx].lexeme
|
|
1334
|
+
type = resolve_arm_receiver_type(facts, tokens, idx)
|
|
1335
|
+
|
|
1336
|
+
arm = if type.respond_to?(:arm)
|
|
1337
|
+
type.arm(arm_name)
|
|
1338
|
+
elsif type.respond_to?(:arms)
|
|
1339
|
+
type.arms[arm_name]
|
|
1340
|
+
end
|
|
1341
|
+
return nil unless arm
|
|
1342
|
+
|
|
1343
|
+
{ arm_name: arm_name, fields: arm }
|
|
1344
|
+
end
|
|
1345
|
+
|
|
1346
|
+
def resolve_arm_receiver_type(facts, tokens, type_name_index)
|
|
1347
|
+
type_name = tokens[type_name_index].lexeme
|
|
1348
|
+
return facts.types[type_name] if facts.types.key?(type_name)
|
|
1349
|
+
|
|
1350
|
+
dot_index = previous_non_trivia_token_index(tokens, type_name_index)
|
|
1351
|
+
return nil unless dot_index && tokens[dot_index].type == :dot
|
|
1352
|
+
|
|
1353
|
+
module_index = previous_non_trivia_token_index(tokens, dot_index)
|
|
1354
|
+
return nil unless module_index && tokens[module_index].type == :identifier
|
|
1355
|
+
|
|
1356
|
+
module_binding = facts.imports[tokens[module_index].lexeme]
|
|
1357
|
+
module_binding&.types&.fetch(type_name, nil)
|
|
1358
|
+
end
|
|
1359
|
+
|
|
1203
1360
|
def named_argument_param_result(field_name, param, callable_ast)
|
|
1204
1361
|
param_ast = callable_ast.params.find { |p| p.name == field_name }
|
|
1205
1362
|
source_location = param_ast ? ast_name_range(field_name, param_ast.line, param_ast.column) : nil
|
|
@@ -1243,7 +1400,15 @@ module MilkTea
|
|
|
1243
1400
|
|
|
1244
1401
|
head_index = previous_non_trivia_token_index(tokens, opener_index)
|
|
1245
1402
|
return nil unless head_index
|
|
1246
|
-
|
|
1403
|
+
|
|
1404
|
+
# Skip generic type arguments before the callee: `span[int](...)`.
|
|
1405
|
+
if tokens[head_index].type == :rbracket
|
|
1406
|
+
lbracket_index = matching_opener_index(tokens, head_index)
|
|
1407
|
+
return nil unless lbracket_index
|
|
1408
|
+
|
|
1409
|
+
head_index = previous_non_trivia_token_index(tokens, lbracket_index)
|
|
1410
|
+
end
|
|
1411
|
+
return nil unless head_index && tokens[head_index].type == :identifier
|
|
1247
1412
|
|
|
1248
1413
|
tokens[head_index].lexeme
|
|
1249
1414
|
end
|
|
@@ -1485,8 +1650,11 @@ module MilkTea
|
|
|
1485
1650
|
def attribute_target_token?(tokens, index)
|
|
1486
1651
|
return false unless index && tokens[index]&.type == :identifier
|
|
1487
1652
|
|
|
1488
|
-
|
|
1489
|
-
return false unless
|
|
1653
|
+
opener = enclosing_bracket_opener_index(tokens, index, :lbracket, :rbracket)
|
|
1654
|
+
return false unless opener
|
|
1655
|
+
|
|
1656
|
+
before_opener = previous_non_trivia_token_index(tokens, opener)
|
|
1657
|
+
return false unless before_opener && tokens[before_opener].type == :attribute
|
|
1490
1658
|
|
|
1491
1659
|
after = next_non_trivia_token_index(tokens, index + 1)
|
|
1492
1660
|
return false unless after && [:rbracket, :comma].include?(tokens[after].type)
|
|
@@ -1494,6 +1662,98 @@ module MilkTea
|
|
|
1494
1662
|
true
|
|
1495
1663
|
end
|
|
1496
1664
|
|
|
1665
|
+
BUILTIN_ATTRIBUTE_SIGNATURES = {
|
|
1666
|
+
'packed' => 'built-in attribute packed',
|
|
1667
|
+
'deprecated' => 'built-in attribute deprecated(message: str)',
|
|
1668
|
+
'align' => 'built-in attribute align(bytes: int)',
|
|
1669
|
+
}.freeze
|
|
1670
|
+
|
|
1671
|
+
def builtin_attribute_hover_signature(tokens, token_index)
|
|
1672
|
+
return nil unless token_index && tokens[token_index]&.type == :identifier
|
|
1673
|
+
|
|
1674
|
+
name = tokens[token_index].lexeme
|
|
1675
|
+
signature = BUILTIN_ATTRIBUTE_SIGNATURES[name]
|
|
1676
|
+
return nil unless signature
|
|
1677
|
+
|
|
1678
|
+
prev = previous_non_trivia_token_index(tokens, token_index)
|
|
1679
|
+
return nil unless prev && tokens[prev].type == :lbracket
|
|
1680
|
+
|
|
1681
|
+
at_index = previous_non_trivia_token_index(tokens, prev)
|
|
1682
|
+
return nil unless at_index && tokens[at_index].type == :at
|
|
1683
|
+
|
|
1684
|
+
signature
|
|
1685
|
+
end
|
|
1686
|
+
|
|
1687
|
+
# A bare identifier that could be a flags/enum/variant member name.
|
|
1688
|
+
# Non-builtin names always qualify; builtin-named identifiers (e.g.
|
|
1689
|
+
# `read`) qualify only when not a call/specialization, so a builtin call
|
|
1690
|
+
# keeps resolving to the builtin instead of a member collision.
|
|
1691
|
+
def bare_member_name_candidate?(tokens, token_index)
|
|
1692
|
+
return false unless token_index && tokens[token_index]&.type == :identifier
|
|
1693
|
+
|
|
1694
|
+
name = tokens[token_index].lexeme
|
|
1695
|
+
return true unless BUILTIN_CALL_HOVER_INFO.key?(name)
|
|
1696
|
+
|
|
1697
|
+
nxt = next_non_trivia_token_index(tokens, token_index + 1)
|
|
1698
|
+
nxt.nil? || ![:lparen, :lbracket].include?(tokens[nxt].type)
|
|
1699
|
+
end
|
|
1700
|
+
|
|
1701
|
+
# Hover content for an identifier sitting in a `[...]` type-parameter or
|
|
1702
|
+
# type-argument position: `struct Pair[A, B]:`, `ref[T]`, `Result[T, E]`,
|
|
1703
|
+
# and generic value parameters `[N: int]`. Returns nil when the brackets
|
|
1704
|
+
# belong to something else (attribute targets, array indices).
|
|
1705
|
+
def type_parameter_hover_signature(tokens, index)
|
|
1706
|
+
return nil unless index && tokens[index]&.type == :identifier
|
|
1707
|
+
|
|
1708
|
+
opener = enclosing_bracket_opener_index(tokens, index, :lbracket, :rbracket)
|
|
1709
|
+
return nil unless opener
|
|
1710
|
+
|
|
1711
|
+
before_opener = previous_non_trivia_token_index(tokens, opener)
|
|
1712
|
+
return nil if before_opener && tokens[before_opener].type == :attribute
|
|
1713
|
+
|
|
1714
|
+
token = tokens[index]
|
|
1715
|
+
next_index = next_non_trivia_token_index(tokens, index + 1)
|
|
1716
|
+
prev_index = previous_non_trivia_token_index(tokens, index)
|
|
1717
|
+
|
|
1718
|
+
if next_index && tokens[next_index].type == :colon
|
|
1719
|
+
# Generic value parameter declaration: `[N: int]`.
|
|
1720
|
+
value_type = next_non_trivia_token_index(tokens, next_index + 1)
|
|
1721
|
+
value_type_text = value_type ? tokens[value_type].lexeme : nil
|
|
1722
|
+
type_text = value_type_text ? ": #{value_type_text}" : ""
|
|
1723
|
+
return "generic value parameter #{token.lexeme}#{type_text}"
|
|
1724
|
+
end
|
|
1725
|
+
|
|
1726
|
+
if prev_index && [:lbracket, :comma].include?(tokens[prev_index].type) &&
|
|
1727
|
+
next_index && tokens[next_index].type == :implements
|
|
1728
|
+
# Constrained declaration: `[T implements Damageable]`.
|
|
1729
|
+
return "type parameter #{token.lexeme}"
|
|
1730
|
+
end
|
|
1731
|
+
|
|
1732
|
+
if prev_index && [:lbracket, :comma].include?(tokens[prev_index].type) &&
|
|
1733
|
+
next_index && [:rbracket, :comma].include?(tokens[next_index].type)
|
|
1734
|
+
return "type parameter #{token.lexeme}"
|
|
1735
|
+
end
|
|
1736
|
+
|
|
1737
|
+
nil
|
|
1738
|
+
end
|
|
1739
|
+
|
|
1740
|
+
def enclosing_bracket_opener_index(tokens, index, opener_type, closer_type)
|
|
1741
|
+
depth = 0
|
|
1742
|
+
i = index
|
|
1743
|
+
while i >= 0
|
|
1744
|
+
tok = tokens[i]
|
|
1745
|
+
if tok.type == closer_type
|
|
1746
|
+
depth += 1
|
|
1747
|
+
elsif tok.type == opener_type
|
|
1748
|
+
return i if depth.zero?
|
|
1749
|
+
|
|
1750
|
+
depth -= 1
|
|
1751
|
+
end
|
|
1752
|
+
i -= 1
|
|
1753
|
+
end
|
|
1754
|
+
nil
|
|
1755
|
+
end
|
|
1756
|
+
|
|
1497
1757
|
def call_argument_token?(tokens, index)
|
|
1498
1758
|
return false unless index && tokens[index]&.type == :identifier
|
|
1499
1759
|
|
|
@@ -1777,9 +2037,15 @@ module MilkTea
|
|
|
1777
2037
|
end
|
|
1778
2038
|
|
|
1779
2039
|
def enclosing_completion_frame(facts, line)
|
|
2040
|
+
# Memoized for the duration of one semantic-token build; completion and
|
|
2041
|
+
# hover only resolve a handful of identifiers per request so they
|
|
2042
|
+
# repopulate the (nil) cache harmlessly.
|
|
2043
|
+
memo_key = [:frame, facts.object_id, line]
|
|
2044
|
+
return semantic_build_memo(memo_key) if semantic_build_memo?(memo_key)
|
|
2045
|
+
|
|
1780
2046
|
frames = Array(facts.local_completion_frames)
|
|
1781
2047
|
containing = frames.select { |frame| frame.start_line && frame.end_line && frame.start_line <= line && line <= frame.end_line }
|
|
1782
|
-
containing.min_by { |frame| frame.end_line - frame.start_line }
|
|
2048
|
+
store_semantic_build_memo(memo_key, containing.min_by { |frame| frame.end_line - frame.start_line })
|
|
1783
2049
|
end
|
|
1784
2050
|
|
|
1785
2051
|
def latest_completion_snapshot(frame, line, char)
|
|
@@ -1823,40 +2089,54 @@ module MilkTea
|
|
|
1823
2089
|
def for_binding_context_at(tokens, token_index)
|
|
1824
2090
|
return nil unless token_index
|
|
1825
2091
|
|
|
1826
|
-
|
|
1827
|
-
|
|
2092
|
+
tok = tokens[token_index]
|
|
2093
|
+
return nil unless tok&.type == :identifier
|
|
1828
2094
|
|
|
2095
|
+
# Find the `for` keyword on the same line at or before the binding.
|
|
2096
|
+
for_index = nil
|
|
2097
|
+
i = token_index
|
|
1829
2098
|
while i >= 0
|
|
1830
2099
|
current = tokens[i]
|
|
1831
|
-
break if current.type == :newline && current.line !=
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
when "in"
|
|
1836
|
-
passed_in = true
|
|
1837
|
-
when "for"
|
|
1838
|
-
if passed_in
|
|
1839
|
-
ie = token_index + 1
|
|
1840
|
-
while ie < tokens.length
|
|
1841
|
-
nt = tokens[ie]
|
|
1842
|
-
break if nt.type == :newline || nt.type == :colon
|
|
1843
|
-
|
|
1844
|
-
ie += 1
|
|
1845
|
-
end
|
|
1846
|
-
iterable_text = tokens[(token_index + 1)...ie].map(&:lexeme).join(" ")
|
|
1847
|
-
iterable_text = iterable_text.gsub(/\s+/, " ").strip
|
|
1848
|
-
return {
|
|
1849
|
-
name: tokens[token_index].lexeme,
|
|
1850
|
-
iterable_text: iterable_text.empty? ? nil : iterable_text,
|
|
1851
|
-
}
|
|
1852
|
-
end
|
|
1853
|
-
end
|
|
2100
|
+
break if current.type == :newline && current.line != tok.line
|
|
2101
|
+
if current.type == :for && current.line == tok.line
|
|
2102
|
+
for_index = i
|
|
2103
|
+
break
|
|
1854
2104
|
end
|
|
1855
|
-
|
|
1856
2105
|
i -= 1
|
|
1857
2106
|
end
|
|
2107
|
+
return nil unless for_index
|
|
2108
|
+
|
|
2109
|
+
# Find `in` after the bindings, still on the same line.
|
|
2110
|
+
in_index = nil
|
|
2111
|
+
j = for_index + 1
|
|
2112
|
+
while j < tokens.length
|
|
2113
|
+
t = tokens[j]
|
|
2114
|
+
break if t.type == :newline && t.line != tok.line
|
|
2115
|
+
if t.type == :in && t.line == tok.line
|
|
2116
|
+
in_index = j
|
|
2117
|
+
break
|
|
2118
|
+
end
|
|
2119
|
+
j += 1
|
|
2120
|
+
end
|
|
2121
|
+
return nil unless in_index
|
|
1858
2122
|
|
|
1859
|
-
|
|
2123
|
+
# The hovered token must be one of the bindings between `for` and `in`.
|
|
2124
|
+
return nil unless token_index > for_index && token_index < in_index
|
|
2125
|
+
|
|
2126
|
+
ie = in_index + 1
|
|
2127
|
+
while ie < tokens.length
|
|
2128
|
+
nt = tokens[ie]
|
|
2129
|
+
break if nt.type == :newline || nt.type == :colon
|
|
2130
|
+
|
|
2131
|
+
ie += 1
|
|
2132
|
+
end
|
|
2133
|
+
iterable_text = tokens[(in_index + 1)...ie].map(&:lexeme).join(" ")
|
|
2134
|
+
iterable_text = iterable_text.gsub(/\s+/, " ").strip
|
|
2135
|
+
|
|
2136
|
+
{
|
|
2137
|
+
name: tok.lexeme,
|
|
2138
|
+
iterable_text: iterable_text.empty? ? nil : iterable_text,
|
|
2139
|
+
}
|
|
1860
2140
|
end
|
|
1861
2141
|
|
|
1862
2142
|
def resolve_lexical_local_hover_signature(definition_uri, name, definition_token)
|
|
@@ -23,6 +23,12 @@ module MilkTea
|
|
|
23
23
|
@semantic_tokens_cache.clear
|
|
24
24
|
@semantic_tokens_delta_cache.clear
|
|
25
25
|
@fixall_cache.clear
|
|
26
|
+
@completion_session_cache.clear
|
|
27
|
+
@completion_session_order.clear
|
|
28
|
+
@completion_session_order_set.clear
|
|
29
|
+
@completion_docs_cache.clear
|
|
30
|
+
@completion_resolve_cache.clear
|
|
31
|
+
@document_symbol_cache.clear
|
|
26
32
|
@definition_file_token_cache.clear
|
|
27
33
|
@definition_file_ast_cache.clear
|
|
28
34
|
MilkTea::Types::Registry.reset!
|
|
@@ -138,6 +144,7 @@ module MilkTea
|
|
|
138
144
|
@_indexing_thread = Thread.new do
|
|
139
145
|
progress = create_progress(title: 'Milk Tea LSP: Indexing workspace', message: 'Scanning source files...')
|
|
140
146
|
@workspace.index_workspace(@root_uri) { |pct, msg| progress.report(percentage: pct, message: msg) }
|
|
147
|
+
@workspace.refresh_module_index_for_workspace
|
|
141
148
|
document_count = @workspace.all_documents.length
|
|
142
149
|
progress.done(message: "#{document_count} document#{document_count == 1 ? '' : 's'} indexed")
|
|
143
150
|
log_message(:info, "Milk Tea LSP ready — #{document_count} document#{document_count == 1 ? '' : 's'} indexed")
|
|
@@ -182,6 +189,7 @@ module MilkTea
|
|
|
182
189
|
Thread.new do
|
|
183
190
|
progress = create_progress(title: 'Milk Tea LSP: Reindexing workspace', message: 'Scanning source files...')
|
|
184
191
|
@workspace.index_workspace(@root_uri) { |pct, msg| progress.report(percentage: pct, message: msg) }
|
|
192
|
+
@workspace.refresh_module_index_for_workspace
|
|
185
193
|
doc_count = @workspace.all_documents.length
|
|
186
194
|
progress.done(message: "#{doc_count} document#{doc_count == 1 ? '' : 's'} indexed")
|
|
187
195
|
|
|
@@ -200,6 +208,10 @@ module MilkTea
|
|
|
200
208
|
next unless old_uri && new_uri
|
|
201
209
|
|
|
202
210
|
@workspace.rename_indexed_file(old_uri, new_uri)
|
|
211
|
+
@workspace.apply_module_index_events(
|
|
212
|
+
[{ 'uri' => old_uri, 'type' => 3 }, { 'uri' => new_uri, 'type' => 1 }],
|
|
213
|
+
skip_open: false,
|
|
214
|
+
)
|
|
203
215
|
end
|
|
204
216
|
nil
|
|
205
217
|
end
|
|
@@ -168,7 +168,9 @@ module MilkTea
|
|
|
168
168
|
end
|
|
169
169
|
|
|
170
170
|
def module_level_reference_locations(uri, name, facts, include_declaration:, cross_file_allowed: true)
|
|
171
|
-
|
|
171
|
+
# Use the facts' own AST so binding_resolution node object_ids line up;
|
|
172
|
+
# a separately-parsed AST would miss every binding lookup.
|
|
173
|
+
ast = facts&.ast || @workspace.get_ast(uri)
|
|
172
174
|
return [] unless ast
|
|
173
175
|
|
|
174
176
|
results = []
|