Module: MilkTea::LSP::Server::ServerHover
- Included in:
- MilkTea::LSP::Server
- Defined in:
- lib/milk_tea/lsp/server/hover.rb
Constant Summary collapse
- KEYWORD_HOVER_INFO =
{ 'size_of' => { signature: 'size_of(Type) -> ptr_uint', docs: '`size_of(Type)` evaluates the compile-time size (in bytes) of the type.', }, 'align_of' => { signature: 'align_of(Type) -> ptr_uint', docs: '`align_of(Type)` evaluates the compile-time alignment (in bytes) of the type.', }, 'offset_of' => { signature: 'offset_of(Type, field) -> ptr_uint', docs: '`offset_of(Type, field)` evaluates the compile-time byte offset of `field` within `Type`.', }, }.freeze
- LANGUAGE_KEYWORD_HOVER_INFO =
{ 'let' => { signature: 'let', docs: 'Immutable local variable declaration: `let name = value` or `let name: Type`. Supports `else` guard with `T?`, `Option[T]`, or `Result[T, E]`.', }, 'var' => { signature: 'var', docs: 'Mutable local or module-level variable: `var name: Type = initializer`. Supports `else` guard. Module `var` requires explicit type.', }, 'const' => { signature: 'const', docs: 'Compile-time constant, requires explicit type and initializer. Block-bodied form `const NAME -> TYPE:` evaluates at compile time.', }, 'function' => { signature: 'function', docs: 'Ordinary function declaration: `function name(params) -> ReturnType:`. Parameters are non-rebindable. Return type defaults to `void`.', }, 'async' => { signature: 'async', docs: 'Marks a function as async; the declared return type is lifted to `Task[T]`. Use `await` inside async functions.', }, 'await' => { signature: 'await', docs: 'Awaits a `Task[T]` inside an async function, yielding the unwrapped `T`. Only allowed in async function bodies.', }, 'struct' => { signature: 'struct', docs: 'Value-type struct with named, typed fields: `struct Name: field: Type`. Supports generics, nested structs, and `implements`.', }, 'enum' => { signature: 'enum', docs: 'Integer-backed enumeration: `enum Name: BackingType`. Backing type defaults to `int`. Values auto-increment from 0 or the last explicit value.', }, 'flags' => { signature: 'flags', docs: 'Bitmask type backed by an integer primitive: `flags Name: uint`. Members must be compile-time integer constants.', }, 'union' => { signature: 'union', docs: 'Overlapped storage union: `union Name: field1: Type1, field2: Type2`. All fields share the same memory.', }, 'variant' => { signature: 'variant', docs: 'Tagged union: `variant Name: arm1(field: Type), arm2`. Arms may carry named payload fields. No-payload arms are bare identifiers.', }, 'opaque' => { signature: 'opaque', docs: 'Externally-defined type with unknown layout: `opaque Name`. May declare `implements` for interface conformance.', }, 'type' => { signature: 'type', docs: 'Type alias: `type Name = ExistingType`. Generic type parameters are supported.', }, 'interface' => { signature: 'interface', docs: 'Declares a method contract: `interface Name: function method(params) -> T`. Implemented via `implements`; used at runtime via `dyn[Interface]`.', }, 'extending' => { signature: 'extending', docs: 'Extends an existing type with methods: `extending Type: function method():`. Supports `function`, `editable function`, and `static function`.', }, 'implements' => { signature: 'implements', docs: 'Nominal interface conformance on `struct` or `opaque`: `struct Foo implements Interface`. Must be declared on the type definition.', }, 'editable' => { signature: 'editable', docs: 'Method receiver modifier: `editable function`. Grants mutable access to `this`. Used in interfaces and extending blocks.', }, 'static' => { signature: 'static', docs: 'Static method modifier: `static function`. No `this` receiver. Used in interfaces and extending blocks for constructors and utilities.', }, 'import' => { signature: 'import', docs: 'Module import: `import module.path` or `import module.path as alias`. Module lookup resolves `a.b.c` to `a/b/c.mt`.', }, 'public' => { signature: 'public', docs: 'Export visibility modifier: `public function`, `public type`, etc. Rejected on `extending`, `external`, and `static_assert` declarations.', }, 'if' => { signature: 'if', docs: 'Conditional branch: `if condition:`. Condition must be `bool`. Supports `else if` and `else`. Inline form: `if cond: stmt else: stmt`.', }, 'else' => { signature: 'else', docs: 'Else branch for `if`. Chains as `else if condition:` for additional branches. Supports inline single-statement form `else: stmt`.', }, 'match' => { signature: 'match', docs: 'Pattern match on enum, variant, integer, `str`, or tuple. Expression form produces a value. Must be exhaustive without `_` wildcard.', }, 'for' => { signature: 'for', docs: 'Loop: `for i in 0..count:` (exclusive range) or `for item in iterable:`. Multi-iteration `for left, right in xs, ys:` iterates arrays/spans in lockstep.', }, 'while' => { signature: 'while', docs: 'Conditional loop: `while condition:`. Condition must be `bool`. Supports `break`, `continue`, and `defer` inside the body.', }, 'return' => { signature: 'return', docs: 'Returns a value from a function. Not allowed inside `defer` blocks.', }, 'break' => { signature: 'break', docs: 'Exits the nearest enclosing `for`, `while`, or `parallel for` loop.', }, 'continue' => { signature: 'continue', docs: 'Skips to the next iteration of the nearest enclosing `for` or `while` loop.', }, 'defer' => { signature: 'defer', docs: 'Defers an expression or block to function exit: `defer expr` or `defer:`. Multiple defers execute in LIFO order. `return` is not allowed inside.', }, 'unsafe' => { signature: 'unsafe', docs: 'Required for pointer indexing, raw pointer dereference, pointer arithmetic, pointer casts, and `reinterpret[...]`. Single-expr or block form.', }, 'external' => { signature: 'external', docs: 'Raw C ABI surface: `external function name(params) -> T`. No body, supports variadic `...`. Also marks external files with `external` header.', }, 'foreign' => { signature: 'foreign', docs: 'Foreign function bridging: `foreign function name(params) -> T = c.FuncName`. Supports `in`, `out`, `inout`, and `consuming` parameter modes.', }, 'consuming' => { signature: 'consuming', docs: 'Foreign function parameter mode: takes ownership of the argument. The caller\'s binding is consumed. Only on `foreign function` params.', }, 'when' => { signature: 'when', docs: 'Compile-time conditional: `when CONSTANT:`. Only the chosen branch is type-checked and emitted. Requires `else` unless exhaustive.', }, 'inline' => { signature: 'inline', docs: 'Compile-time unrolling modifier: `inline for` (loop unrolling), `inline while`, `inline match`, `inline if`. Only the active branch emits code.', }, 'emit' => { signature: 'emit', docs: 'Emits a declaration at compile time: `emit function ...`. Only allowed inside `const function` or `inline` bodies.', }, 'parallel' => { signature: 'parallel', docs: 'Concurrency construct: `parallel for i in 0..N:` (data-parallel loop) or `parallel:` block (concurrent statement dispatch). Uses OS threads via libuv.', }, 'detach' => { signature: 'detach', docs: 'Spawns work on a separate thread, returning a `Handle`: `let h = detach func()`. Use `gather` to wait. Supports global function calls only.', }, 'gather' => { signature: 'gather', docs: 'Blocks until one or more `detach` handles complete: `gather h1, h2`. Takes one or more `Handle` values.', }, 'event' => { signature: 'event', docs: 'Typed publisher/subscriber: `event name[capacity]` or `event name[capacity](PayloadType)`. Supports `subscribe`, `emit`, `unsubscribe`, `wait`.', }, 'attribute' => { signature: 'attribute', docs: 'Declares a reusable declaration attribute: `attribute[target, ...] name(params)`. Targets: struct, field, callable, const, event, enum, flags, union, variant.', }, 'proc' => { signature: 'proc', docs: 'Ref-counted closure: `proc(params...) -> T: body`. Captures values by value. Storable in structs, arrays, and tuples.', }, 'fn' => { signature: 'fn', docs: 'Function pointer type: `fn(params...) -> T`. Points to module-level functions. No captured state; capture-free.', }, 'dyn' => { signature: 'dyn', docs: 'Runtime interface value: `dyn[Interface]`. A fat pointer carrying a data pointer and vtable. Constructed via `adapt[Interface](value)`.', }, 'is' => { signature: 'is', docs: 'Variant arm membership test: `expr is Variant.arm`. Desugars to a `match` expression evaluating to `bool`. Supports `not` negation.', }, 'pass' => { signature: 'pass', docs: 'Explicit no-op statement for intentionally empty block bodies.', }, 'null' => { signature: 'null', docs: 'Null value for nullable types (`T?`). Use typed `null[T]` when context cannot determine the target type.', }, 'true' => { signature: 'true', docs: 'Boolean literal `true`. Type is `bool`.', }, 'false' => { signature: 'false', docs: 'Boolean literal `false`. Type is `bool`.', }, 'static_assert' => { signature: 'static_assert', docs: 'Compile-time assertion: `static_assert(condition, message)`. Fails compilation if the compile-time condition is false.', }, }.freeze
- BUILTIN_TYPE_DOCS =
{ 'bool' => '1-byte boolean: `true` or `false`.', 'byte' => '8-bit signed integer. Range: -128 to 127.', 'ubyte' => '8-bit unsigned integer. Range: 0 to 255. Also used for `char` literals like `\'A\'`.', 'char' => '8-bit character type (alias for `ubyte`).', 'short' => '16-bit signed integer. Range: -32,768 to 32,767.', 'ushort' => '16-bit unsigned integer. Range: 0 to 65,535.', 'int' => '32-bit signed integer. Range: -2,147,483,648 to 2,147,483,647. Default enum backing type.', 'uint' => '32-bit unsigned integer. Range: 0 to 4,294,967,295.', 'long' => '64-bit signed integer.', 'ulong' => '64-bit unsigned integer.', 'ptr_int' => 'Pointer-sized signed integer. Width matches the target platform pointer size.', 'ptr_uint' => 'Pointer-sized unsigned integer. Return type for `size_of`, `align_of`, `offset_of`.', 'float' => '32-bit IEEE 754 single-precision float.', 'double' => '64-bit IEEE 754 double-precision float.', 'void' => 'Empty type for functions with no return value. Not a storable type.', 'str' => 'Non-owning UTF-8 string view (pointer + length). Not null-terminated.', 'cstr' => 'Null-terminated C string. Used at FFI boundaries.', 'vec2' => '2-component float vector. Fields: `.x`, `.y`. Supports component-wise arithmetic.', 'vec3' => '3-component float vector. Fields: `.x`, `.y`, `.z`. Supports component-wise arithmetic and `dot`/`cross`/`length` via `std.linear_algebra`.', 'vec4' => '4-component float vector. Fields: `.x`, `.y`, `.z`, `.w`. Supports component-wise arithmetic.', 'ivec2' => '2-component integer vector. Fields: `.x`, `.y`. Supports component-wise arithmetic.', 'ivec3' => '3-component integer vector. Fields: `.x`, `.y`, `.z`. Supports component-wise arithmetic.', 'ivec4' => '4-component integer vector. Fields: `.x`, `.y`, `.z`, `.w`. Supports component-wise arithmetic.', 'mat3' => '3×3 column-major float matrix. Columns: `.col0`–`.col2` (each `vec3`). Supports `identity`, `transpose` via `std.linear_algebra`.', 'mat4' => '4×4 column-major float matrix. Columns: `.col0`–`.col3` (each `vec4`). Supports `identity`, `transpose` via `std.linear_algebra`.', 'quat' => 'Quaternion. Fields: `.x`, `.y`, `.z`, `.w`. Layout-compatible with `vec4`. Supports `identity`, `conjugate` via `std.linear_algebra`.', 'ptr' => 'Generic pointer type: `ptr[T]`. Raw mutable pointer. Requires `unsafe` for indexing, dereference, and arithmetic.', 'const_ptr' => 'Read-only pointer type: `const_ptr[T]`. Immutable pointer, does not require `unsafe` for dereference.', 'own' => 'Owning heap pointer: `own[T]`. Auto-dereferences like `ref`. Storable, returnable, and nullable. Allocated via `heap.must_alloc[T](count)`.', 'ref' => 'Non-null borrow reference: `ref[T]`. Auto-dereferences for member access and method calls. Cannot be stored in module variables or constants.', 'span' => 'Borrowed pointer-plus-length view: `span[T]`. Constructed via `span[T](data = ..., len = ...)`. Arrays coerce implicitly.', 'array' => 'Fixed-length array: `array[T, N]`. Constructed via `array[T, N](elements...)`. Omitted trailing elements default to zero.', 'str_buffer' => 'Fixed-capacity mutable UTF-8 text buffer: `str_buffer[N]`. Methods: `assign`, `append`, `assign_format`, `append_format`, `clear`, `as_str`, `as_cstr`.', 'atomic' => 'Atomic value for lock-free concurrent access: `atomic[T]`. `T` must be a primitive integer or `bool`. Methods: `load`, `store`, `add`, `sub`, `exchange`. Uses C11 `_Atomic`.', 'Task' => 'Async task future: `Task[T]`. Returned by `async function`. Use `await` to unwrap, or `aio.wait`/`aio.run` to drive.', 'Option' => 'Built-in optional type: `Option[T]`. Arms: `some(value: T)` and `none`. Use `let ... else:` or `?` for safe unwrapping.', 'Result' => 'Built-in result type: `Result[T, E]`. Arms: `success(value: T)` and `failure(error: E)`. Use `let ... else:` or `?` for error propagation.', 'SoA' => 'Struct-of-Arrays: `SoA[T, N]`. Each struct field becomes a separate array of length `N`. Access `soa[i].field` reads from column `field` at row `i`.', 'simd' => 'SIMD vector type: `simd[T, N]`. Fixed-width vector of `N` lanes of numeric primitive type `T`. Supports component-wise arithmetic, lane access via `[i]`, and explicit aligned/unaligned load/store. Lowers to GCC/Clang vector extensions for portable, readable C output.', 'struct_handle' => 'Compile-time handle for a struct type. Obtained via reflection builtins like `fields_of`.', 'field_handle' => 'Compile-time handle for a struct field. Exposes `.name` and `.type`. Obtained via `field_of` and `fields_of`.', 'callable_handle' => 'Compile-time handle for a callable declaration. Obtained via `callable_of`. Used with `has_attribute`, `attribute_of`.', 'attribute_handle' => 'Compile-time handle for an applied attribute. Obtained via `attribute_of` and `attributes_of`. Use `attribute_arg[T]` to read arguments.', 'member_handle' => 'Compile-time handle for an enum or flags member. Exposes `.name` and `.value`. Obtained via `members_of`.', 'EventError' => 'Built-in enum returned when event listener capacity is exhausted. Single member: `full`.', 'Subscription' => 'Opaque handle returned by `event.subscribe`. Pass to `event.unsubscribe` to remove a listener.', }.freeze
- VEC_METHOD_SIGNATURES =
{ 'with' => 'function with(...) -> vec', 'dot' => 'function dot(other: vec) -> float', 'length' => 'function length() -> float', 'length_sq' => 'function length_sq() -> float', 'cross' => 'function cross(other: vec) -> vec', 'normalized' => 'function normalized() -> vec', 'squared_len' => 'function squared_len() -> float', }.freeze
- PTR_METHOD_SIGNATURES =
{ 'load' => 'function load() -> T', 'store' => 'function store(value: T) -> void', 'load_at' => 'function load_at(offset: ptr_uint) -> T', 'store_at' => 'function store_at(offset: ptr_uint, value: T) -> void', 'cast' => 'function cast() -> ptr[U]', }.freeze
- BUILTIN_TYPE_METHOD_SIGNATURES =
{ 'atomic' => { 'load' => 'function load() -> T', 'store' => 'static function store(value: T) -> void', 'add' => 'static function add(value: T) -> T', 'sub' => 'static function sub(value: T) -> T', 'exchange' => 'static function exchange(value: T) -> T', 'compare_exchange' => 'static function compare_exchange(expected: T, desired: T) -> bool', }, 'event' => { 'subscribe' => 'function subscribe(listener) -> Result[Subscription, EventError]', 'subscribe_once' => 'function subscribe_once(listener) -> Result[Subscription, EventError]', 'unsubscribe' => 'function unsubscribe(subscription) -> bool', 'emit' => 'function emit() / function emit(payload)', 'wait' => 'function wait() -> Task[Result[T, EventError]]', }, 'str_buffer' => { 'assign' => 'static function assign(value: str) -> void', 'append' => 'static function append(value: str) -> void', 'assign_format' => 'static function assign_format(fmt: str) -> void', 'append_format' => 'static function append_format(fmt: str) -> void', 'clear' => 'static function clear() -> void', 'len' => 'static function len() -> ptr_uint', 'capacity' => 'static function capacity() -> ptr_uint', 'as_str' => 'static function as_str() -> str', 'as_cstr' => 'static function as_cstr() -> cstr', }, 'vec2' => VEC_METHOD_SIGNATURES, 'vec3' => VEC_METHOD_SIGNATURES, 'vec4' => VEC_METHOD_SIGNATURES, 'ivec2' => VEC_METHOD_SIGNATURES, 'ivec3' => VEC_METHOD_SIGNATURES, 'ivec4' => VEC_METHOD_SIGNATURES, 'mat3' => { 'identity' => 'static function identity() -> mat3', 'with' => 'function with(...) -> mat3', }, 'mat4' => { 'identity' => 'static function identity() -> mat4', 'with' => 'function with(...) -> mat4', }, 'quat' => { 'identity' => 'static function identity() -> quat', 'with' => 'function with(x: float, y: float, z: float, w: float) -> quat', }, 'simd' => { 'with' => 'function with(index: int, value: T) -> simd[T, N]', 'load' => 'function load() -> T', 'store' => 'function store(value: T) -> void', }, 'array' => { 'as_span' => 'function as_span() -> span[T]', 'len' => 'function len() -> ptr_uint', 'with' => 'function with(index: int, value: T) -> array[T, N]', }, 'span' => { 'len' => 'function len() -> ptr_uint', }, 'str' => { 'len' => 'function len() -> ptr_uint', 'slice' => 'function slice(start: ptr_uint, len: ptr_uint) -> str', }, 'ptr' => PTR_METHOD_SIGNATURES, 'const_ptr' => PTR_METHOD_SIGNATURES, 'own' => PTR_METHOD_SIGNATURES, }.freeze
- BUILTIN_NAMED_ARGUMENT_SIGNATURES =
{ 'span' => { 'data' => 'data: ptr[T]', 'len' => 'len: ptr_uint', }, }.freeze
- BUILTIN_ATTRIBUTE_SIGNATURES =
{ 'packed' => 'built-in attribute packed', 'deprecated' => 'built-in attribute deprecated(message: str)', 'align' => 'built-in attribute align(bytes: int)', }.freeze
Instance Method Summary collapse
- #append_structured_doc_comment_markdown(lines, doc_comment) ⇒ Object
- #attribute_target_token?(tokens, index) ⇒ Boolean
-
#bare_member_name_candidate?(tokens, token_index) ⇒ Boolean
A bare identifier that could be a flags/enum/variant member name.
- #builtin_attribute_hover_signature(tokens, token_index) ⇒ Object
- #builtin_call_hover_info(name, tokens, token_index) ⇒ Object
- #builtin_hover_info(name, tokens, token_index) ⇒ Object
- #builtin_in_member_access_context?(tokens, token_index) ⇒ Boolean
- #builtin_keyword_hover_info(token) ⇒ Object
- #builtin_specialized_call_hover_info(name, tokens, token_index) ⇒ Object
- #builtin_type_constructor_hover_info(name, tokens, token_index) ⇒ Object
- #builtin_value_specialization_info(name, tokens, token_index) ⇒ Object
- #call_argument_token?(tokens, index) ⇒ Boolean
- #completion_function_documentation(uri, name, cache:) ⇒ Object
- #declared_generic_local_hover_binding(facts, name, line) ⇒ Object
- #doc_tag_param_descriptions(doc_comment) ⇒ Object
- #doc_tag_return_description(doc_comment) ⇒ Object
- #embedded_heredoc_or_format_heredoc_token?(token) ⇒ Boolean
- #enclosing_bracket_opener_index(tokens, index, opener_type, closer_type) ⇒ Object
- #enclosing_completion_frame(facts, line) ⇒ Object
- #field_declaration_receiver_info(facts, tokens, token_index) ⇒ Object
- #field_hover_signature(name, type) ⇒ Object
- #find_member_in_types(facts, name) ⇒ Object
- #find_method_entry(methods_table, receiver_type, method_name) ⇒ Object
- #find_nested_type_by_short_name(facts, short_name) ⇒ Object
- #find_variant_arm_in_types(facts, name) ⇒ Object
- #for_binding_context_at(tokens, token_index) ⇒ Object
- #format_params(params) ⇒ Object
- #fstring_interpolation_token_context(tokens, lsp_line, lsp_char) ⇒ Object
- #handle_hover(params) ⇒ Object
- #hover_definition_entry_from_location(location) ⇒ Object
- #hover_doc_comment_data_for_definition(definition_entry) ⇒ Object
- #hover_doc_comment_for_definition(definition_entry) ⇒ Object
- #hover_source_label(uri, line) ⇒ Object
- #hover_source_label_for_definition(definition_entry) ⇒ Object
- #hover_source_label_from_location(location) ⇒ Object
- #hover_source_line_for_definition(definition_entry) ⇒ Object
- #hover_source_line_from_location(location) ⇒ Object
- #hover_source_uri_for_definition(definition_entry) ⇒ Object
- #hover_source_uri_from_location(location) ⇒ Object
- #interface_method_signature(binding) ⇒ Object
- #interface_signature(binding) ⇒ Object
- #interpolation_expression_tokens(part) ⇒ Object
- #keyword_member_access_token?(tokens, token_index) ⇒ Boolean
- #latest_completion_snapshot(frame, line, char) ⇒ Object
- #member_access_chain_at(tokens, token_index) ⇒ Object
- #member_access_token?(context) ⇒ Boolean
- #member_method_info_for_receiver_type(facts, receiver_type, method_name) ⇒ Object
- #method_binding_at_token(facts, token) ⇒ Object
- #method_signature(binding) ⇒ Object
- #named_argument_callee_name(tokens, token_index) ⇒ Object
- #named_argument_label_receiver_info(facts, tokens, token_index) ⇒ Object
- #named_argument_param_result(field_name, param, callable_ast) ⇒ Object
- #named_argument_parameter_info(facts, tokens, token_index) ⇒ Object
- #param_doc_for_name(callable_ast, param_name) ⇒ Object
- #parameter_declaration_token?(tokens, index) ⇒ Boolean
- #render_builtin_specialization(tokens) ⇒ Object
- #render_hover_markdown(info) ⇒ Object
- #resolve_arm_receiver_type(facts, tokens, type_name_index) ⇒ Object
- #resolve_as_binding_declaration_hover_binding(facts, name, line, char) ⇒ Object
- #resolve_dot_receiver_value_type(facts, receiver_name, line, char) ⇒ Object
- #resolve_enum_member_access_info(current_uri, facts, tokens, token_index) ⇒ Object
- #resolve_enum_member_definition_location(current_uri, facts, tokens, token_index) ⇒ Object
- #resolve_enum_member_hover_info(current_uri, facts, tokens, token_index) ⇒ Object
- #resolve_field_declaration_hover_info(current_uri, facts, tokens, token_index) ⇒ Object
- #resolve_for_binding_hover_info(tokens, token_index) ⇒ Object
- #resolve_hover_info(uri, lsp_line, lsp_char, token: nil, tokens: nil, token_index: nil, stages: nil) ⇒ Object
- #resolve_lexical_local_hover_signature(definition_uri, name, definition_token) ⇒ Object
- #resolve_local_hover_binding(facts, name, line, char) ⇒ Object
- #resolve_local_hover_type(facts, name, line, char) ⇒ Object
- #resolve_member_access_hover_info(current_uri, facts, tokens, token_index) ⇒ Object
- #resolve_named_argument_label_hover_info(current_uri, facts, tokens, token_index) ⇒ Object
- #same_line_future_completion_snapshot(frame, line, char) ⇒ Object
- #shorten_qualified_types(signature) ⇒ Object
- #signature_help_doc_comment_for_call(uri, name, lsp_line, lsp_char) ⇒ Object
- #signature_help_markdown_for_doc_comment(doc_comment) ⇒ Object
- #token_contains_position?(token, target_line, target_char) ⇒ Boolean
- #token_context_at(uri, lsp_line, lsp_char) ⇒ Object
- #type_hover_signature(name, type) ⇒ Object
-
#type_parameter_hover_signature(tokens, index) ⇒ Object
Hover content for an identifier sitting in a
[...]type-parameter or type-argument position:struct Pair[A, B]:,ref[T],Result[T, E], and generic value parameters[N: int]. - #value_hover_signature(binding) ⇒ Object
- #variant_arm_constructor_info(facts, tokens, token_index) ⇒ Object
Instance Method Details
#append_structured_doc_comment_markdown(lines, doc_comment) ⇒ Object
799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 799 def append_structured_doc_comment_markdown(lines, doc_comment) body = doc_comment[:body_markdown].to_s.strip = doc_comment.fetch(:tags, {}) params = Array([:params]).reject { |entry| entry[:name].to_s.strip.empty? } returns = [:returns] throws = Array([:throws]).reject { |entry| entry[:text].to_s.strip.empty? } see_also = Array([:see]).reject { |entry| entry[:text].to_s.strip.empty? } wrote = false unless body.empty? lines << "" lines << body wrote = true end unless params.empty? lines << "" lines << "**Parameters**" params.each do |entry| description = entry[:text].to_s.strip if description.empty? lines << "- `#{entry[:name]}`" else lines << "- `#{entry[:name]}`: #{description}" end end wrote = true end if returns description = returns[:text].to_s.strip unless description.empty? lines << "" lines << "**Returns**" lines << description wrote = true end end unless throws.empty? lines << "" lines << "**Throws**" throws.each do |entry| lines << "- #{entry[:text]}" end wrote = true end unless see_also.empty? lines << "" lines << "**See Also**" see_also.each do |entry| lines << "- #{entry[:text]}" end wrote = true end wrote end |
#attribute_target_token?(tokens, index) ⇒ Boolean
1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1650 def attribute_target_token?(tokens, index) return false unless index && tokens[index]&.type == :identifier opener = enclosing_bracket_opener_index(tokens, index, :lbracket, :rbracket) return false unless opener before_opener = previous_non_trivia_token_index(tokens, opener) return false unless before_opener && tokens[before_opener].type == :attribute after = next_non_trivia_token_index(tokens, index + 1) return false unless after && [:rbracket, :comma].include?(tokens[after].type) true end |
#bare_member_name_candidate?(tokens, token_index) ⇒ Boolean
A bare identifier that could be a flags/enum/variant member name.
Non-builtin names always qualify; builtin-named identifiers (e.g.
read) qualify only when not a call/specialization, so a builtin call
keeps resolving to the builtin instead of a member collision.
1691 1692 1693 1694 1695 1696 1697 1698 1699 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1691 def (tokens, token_index) return false unless token_index && tokens[token_index]&.type == :identifier name = tokens[token_index].lexeme return true unless BUILTIN_CALL_HOVER_INFO.key?(name) nxt = next_non_trivia_token_index(tokens, token_index + 1) nxt.nil? || ![:lparen, :lbracket].include?(tokens[nxt].type) end |
#builtin_attribute_hover_signature(tokens, token_index) ⇒ Object
1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1671 def builtin_attribute_hover_signature(tokens, token_index) return nil unless token_index && tokens[token_index]&.type == :identifier name = tokens[token_index].lexeme signature = BUILTIN_ATTRIBUTE_SIGNATURES[name] return nil unless signature prev = previous_non_trivia_token_index(tokens, token_index) return nil unless prev && tokens[prev].type == :lbracket at_index = previous_non_trivia_token_index(tokens, prev) return nil unless at_index && tokens[at_index].type == :at signature end |
#builtin_call_hover_info(name, tokens, token_index) ⇒ Object
1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1953 def builtin_call_hover_info(name, tokens, token_index) info = BUILTIN_CALL_HOVER_INFO[name] return nil unless info return nil if builtin_in_member_access_context?(tokens, token_index) next_index = next_non_trivia_token_index(tokens, token_index + 1) return nil unless next_index next_type = tokens[next_index].type return nil unless next_type == :lparen || next_type == :lbracket info end |
#builtin_hover_info(name, tokens, token_index) ⇒ Object
1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1626 def builtin_hover_info(name, tokens, token_index) specialization_info = builtin_value_specialization_info(name, tokens, token_index) return specialization_info if specialization_info specialized_call_info = builtin_specialized_call_hover_info(name, tokens, token_index) return specialized_call_info if specialized_call_info type_constructor_info = builtin_type_constructor_hover_info(name, tokens, token_index) return type_constructor_info if type_constructor_info builtin_call_hover_info(name, tokens, token_index) end |
#builtin_in_member_access_context?(tokens, token_index) ⇒ Boolean
1767 1768 1769 1770 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1767 def builtin_in_member_access_context?(tokens, token_index) prev_index = previous_non_trivia_token_index(tokens, token_index) prev_index && tokens[prev_index].type == :dot end |
#builtin_keyword_hover_info(token) ⇒ Object
1967 1968 1969 1970 1971 1972 1973 1974 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1967 def builtin_keyword_hover_info(token) return nil unless token info = KEYWORD_HOVER_INFO[token.lexeme] return info if info && [:size_of, :align_of, :offset_of].include?(token.type) LANGUAGE_KEYWORD_HOVER_INFO[token.lexeme] end |
#builtin_specialized_call_hover_info(name, tokens, token_index) ⇒ Object
1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1904 def builtin_specialized_call_hover_info(name, tokens, token_index) return nil unless BUILTIN_ASSOCIATED_HOOK_NAMES.include?(name) || name == 'attribute_arg' return nil if builtin_in_member_access_context?(tokens, token_index) lbracket_index = next_non_trivia_token_index(tokens, token_index + 1) return nil unless lbracket_index && tokens[lbracket_index].type == :lbracket rbracket_index = matching_closer_index(tokens, lbracket_index, :lbracket, :rbracket) return nil unless rbracket_index after_bracket_index = next_non_trivia_token_index(tokens, rbracket_index + 1) return nil unless after_bracket_index && tokens[after_bracket_index].type == :lparen specialization = render_builtin_specialization(tokens[token_index..rbracket_index]) if name == 'attribute_arg' target_type = render_builtin_specialization(tokens[(lbracket_index + 1)...rbracket_index]) return nil if target_type.empty? return { signature: "builtin #{specialization}(attribute, param_name) -> #{target_type}", docs: '`attribute_arg[T](attribute, param_name)` returns the compile-time argument value for the named attribute parameter; `T` must exactly match the declared parameter type.' } end docs = case name when 'hash' '`hash[T](value)` lowers to `T.hash(value: const_ptr[T]) -> uint` after borrowing safe lvalues or forwarding existing refs and pointers.' when 'equal' '`equal[T](left, right)` lowers to `T.equal(left: const_ptr[T], right: const_ptr[T]) -> bool` after borrowing safe lvalues or forwarding existing refs and pointers.' when 'order' '`order[T](left, right)` lowers to `T.order(left: const_ptr[T], right: const_ptr[T]) -> int` after borrowing safe lvalues or forwarding existing refs and pointers.' end signature = case name when 'hash' "builtin #{specialization}(value) -> uint" when 'equal' "builtin #{specialization}(left, right) -> bool" when 'order' "builtin #{specialization}(left, right) -> int" end { signature: signature, docs: docs, } end |
#builtin_type_constructor_hover_info(name, tokens, token_index) ⇒ Object
1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1822 def builtin_type_constructor_hover_info(name, tokens, token_index) return nil unless %w[array span Option Result SoA str_buffer ref ptr const_ptr own Task atomic simd].include?(name) lbracket_index = next_non_trivia_token_index(tokens, token_index + 1) return nil unless lbracket_index && tokens[lbracket_index].type == :lbracket rbracket_index = matching_closer_index(tokens, lbracket_index, :lbracket, :rbracket) return nil unless rbracket_index specialization = render_builtin_specialization(tokens[token_index..rbracket_index]) after_bracket_index = next_non_trivia_token_index(tokens, rbracket_index + 1) if after_bracket_index && tokens[after_bracket_index].type == :lparen docs = case name when 'array' '`array[T, N](...)` constructs a fixed-length array value of type `array[T, N]`.' when 'span' '`span[T](data = ..., len = ...)` constructs a span view over contiguous `T` storage.' when 'SoA' '`SoA[T, N](...)` constructs a Struct-of-Arrays value with `N` elements of type `T`. Fields are stored in separate contiguous arrays.' when 'simd' '`simd[T, N](...)` constructs a SIMD vector value with `N` lanes of type `T`. Lanes are stored in a single vector register.' when 'Option' '`Option[T]` is a built-in optional type with arms `some(value: T)` and `none`.' when 'Result' '`Result[T, E]` is a built-in result type with arms `success(value: T)` and `failure(error: E)`.' end return { signature: case name when 'array' "builtin #{specialization}(...) -> #{specialization}" when 'span' "builtin #{specialization}(data = ..., len = ...) -> #{specialization}" when 'SoA' "builtin #{specialization}(...) -> #{specialization}" when 'simd' "builtin #{specialization}(...) -> #{specialization}" when 'Option' "builtin #{specialization}(some: value = ...) / #{specialization}(none:)" when 'Result' "builtin #{specialization}(success: value = ...) / #{specialization}(failure: error = ...)" end, docs: docs, } end docs = case name when 'array' '`array[T, N]` is the built-in fixed-length array type.' when 'span' '`span[T]` is the built-in non-owning contiguous view type.' when 'SoA' '`SoA[T, N]` is the built-in Struct-of-Arrays type. Each struct field is stored in a separate contiguous array of `N` elements, improving SIMD/cache behavior for parallel field access.' when 'simd' '`simd[T, N]` is the built-in SIMD vector type. Fixed-width vector of `N` numeric lanes. Supports component-wise arithmetic, lane access via `[i]`, and explicit aligned/unaligned load/store.' when 'Option' '`Option[T]` is the built-in optional value type with `some(value = ...)` and `none` arms.' when 'Result' '`Result[T, E]` is the built-in success/failure type with `success(value = ...)` and `failure(error = ...)` arms.' when 'str_buffer' '`str_buffer[N]` is a fixed-capacity mutable UTF-8 text buffer. Methods: `assign`, `append`, `assign_format`, `append_format`, `clear`, `len`, `as_str`, `as_cstr`.' when 'ref' '`ref[T]` is a non-null borrow reference. Auto-dereferences for member access and method calls. Cannot be stored in module-level variables, constants, or nested containers.' when 'ptr' '`ptr[T]` is a raw mutable pointer. Indexing, dereference, and arithmetic require `unsafe`. Nullable via `ptr[T]?`.' when 'const_ptr' '`const_ptr[T]` is a read-only pointer. Does not require `unsafe` for dereference.' when 'own' '`own[T]` is an owning heap pointer. Auto-dereferences like `ref`. Storable, returnable, and nullable. Allocated via `heap.must_alloc[T](count)`.' when 'Task' '`Task[T]` is an async task future. Returned by `async function`. Use `await` to unwrap, or `aio.wait`/`aio.run` to drive.' when 'atomic' '`atomic[T]` is an atomic value for lock-free concurrent access. `T` must be a primitive integer or `bool`. Methods: `load`, `store`, `add`, `sub`, `exchange`.' end { signature: "builtin type #{specialization}", docs: docs, } end |
#builtin_value_specialization_info(name, tokens, token_index) ⇒ Object
1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1788 def builtin_value_specialization_info(name, tokens, token_index) return nil unless %w[zero default reinterpret].include?(name) return nil if builtin_in_member_access_context?(tokens, token_index) lbracket_index = next_non_trivia_token_index(tokens, token_index + 1) return nil unless lbracket_index && tokens[lbracket_index].type == :lbracket rbracket_index = matching_closer_index(tokens, lbracket_index, :lbracket, :rbracket) return nil unless rbracket_index specialization = render_builtin_specialization(tokens[token_index..rbracket_index]) target_type = render_builtin_specialization(tokens[(lbracket_index + 1)...rbracket_index]) return nil if target_type.empty? docs = case name when 'zero' '`zero[T]` returns the raw zero-initialized value for `T`. It is a value form, not a callable.' when 'default' '`default[T]` returns the semantic default value for `T` and requires an accessible zero-argument associated function `T.default()` that returns `T`. It is a value form, not a callable.' when 'reinterpret' '`reinterpret[T](value)` bit-casts a value to `T`; it requires `unsafe` and compatible concrete sized types.' end { signature: if name == 'reinterpret' "builtin #{specialization}(value) -> #{target_type}" else "builtin #{specialization} -> #{target_type}" end, docs: docs, } end |
#call_argument_token?(tokens, index) ⇒ Boolean
1757 1758 1759 1760 1761 1762 1763 1764 1765 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1757 def call_argument_token?(tokens, index) return false unless index && tokens[index]&.type == :identifier prev = previous_non_trivia_token_index(tokens, index) return false unless prev && [:lparen, :comma].include?(tokens[prev].type) nxt = next_non_trivia_token_index(tokens, index + 1) !nxt || ![:colon, :rbracket, :comma].include?(tokens[nxt].type) end |
#completion_function_documentation(uri, name, cache:) ⇒ Object
920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 920 def completion_function_documentation(uri, name, cache:) key = [uri, name] return cache[key] if cache.key?(key) # Server-scoped memo so per-keystroke completion does not re-resolve the # definition and re-extract docs for every candidate on every request. if @completion_docs_cache.key?(key) return cache[key] = @completion_docs_cache[key] end definition_entry = @workspace.find_definition_token_global(name, preferred_uri: uri) unless definition_entry cache[key] = '' return @completion_docs_cache[key] = '' end doc_comment = @workspace.doc_comment_data_for_definition(definition_entry[:uri], definition_entry[:token]) @completion_docs_cache[key] = cache[key] = signature_help_markdown_for_doc_comment(doc_comment) end |
#declared_generic_local_hover_binding(facts, name, line) ⇒ Object
2032 2033 2034 2035 2036 2037 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 2032 def declared_generic_local_hover_binding(facts, name, line) binding = generic_function_binding_for_line(facts, line) return nil unless binding binding.body_params.find { |param| param.name == name } end |
#doc_tag_param_descriptions(doc_comment) ⇒ Object
900 901 902 903 904 905 906 907 908 909 910 911 912 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 900 def doc_tag_param_descriptions(doc_comment) return {} unless doc_comment.is_a?(Hash) Array(doc_comment.dig(:tags, :params)).each_with_object({}) do |entry, docs| name = entry[:name].to_s next if name.empty? text = entry[:text].to_s.strip next if text.empty? docs[name] = text end end |
#doc_tag_return_description(doc_comment) ⇒ Object
914 915 916 917 918 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 914 def doc_tag_return_description(doc_comment) return '' unless doc_comment.is_a?(Hash) doc_comment.dig(:tags, :returns, :text).to_s.strip end |
#embedded_heredoc_or_format_heredoc_token?(token) ⇒ Boolean
669 670 671 672 673 674 675 676 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 669 def (token) return false unless [:string, :cstring, :fstring].include?(token.type) tag = token.lexeme[/\A(?:f|c)?<<-([A-Za-z_][A-Za-z0-9_]*)[ \t]*\n/, 1] return false if tag.nil? %w[GLSL VERT FRAG COMP JSON JSONC SQL HTML].include?(tag) end |
#enclosing_bracket_opener_index(tokens, index, opener_type, closer_type) ⇒ Object
1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1740 def enclosing_bracket_opener_index(tokens, index, opener_type, closer_type) depth = 0 i = index while i >= 0 tok = tokens[i] if tok.type == closer_type depth += 1 elsif tok.type == opener_type return i if depth.zero? depth -= 1 end i -= 1 end nil end |
#enclosing_completion_frame(facts, line) ⇒ Object
2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 2039 def enclosing_completion_frame(facts, line) # Memoized for the duration of one semantic-token build; completion and # hover only resolve a handful of identifiers per request so they # repopulate the (nil) cache harmlessly. memo_key = [:frame, facts.object_id, line] return semantic_build_memo(memo_key) if semantic_build_memo?(memo_key) frames = Array(facts.local_completion_frames) containing = frames.select { |frame| frame.start_line && frame.end_line && frame.start_line <= line && line <= frame.end_line } store_semantic_build_memo(memo_key, containing.min_by { |frame| frame.end_line - frame.start_line }) end |
#field_declaration_receiver_info(facts, tokens, token_index) ⇒ Object
1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1416 def field_declaration_receiver_info(facts, tokens, token_index) token = tokens[token_index] return nil unless token i = token_index - 1 while i >= 0 current = tokens[i] i -= 1 next if [:newline, :indent, :dedent, :eof].include?(current.type) next if current.line == token.line next if current.column >= token.column header_line_tokens = non_trivia_tokens_on_line(tokens, current.line) header = header_line_tokens.first type_index = 1 if header&.type == :public header = header_line_tokens[1] type_index = 2 end return nil unless [:struct, :union].include?(header&.type) type_token = header_line_tokens[type_index] return nil unless type_token&.type == :identifier return resolve_type_receiver_info(facts, type_token.lexeme, type_token.lexeme) end nil end |
#field_hover_signature(name, type) ⇒ Object
1585 1586 1587 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1585 def field_hover_signature(name, type) "field #{name}: #{type}" end |
#find_member_in_types(facts, name) ⇒ Object
1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1596 def find_member_in_types(facts, name) facts.types.reverse_each do |_key, type| next unless type.respond_to?(:members) member = type.members.find { |m| m == name } if member value = type.respond_to?(:member_value) ? type.member_value(name) : nil return [type, member, value] end end nil end |
#find_method_entry(methods_table, receiver_type, method_name) ⇒ Object
1152 1153 1154 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1152 def find_method_entry(methods_table, receiver_type, method_name) methods_table.fetch(receiver_type, {})[method_name] || methods_table.fetch(receiver_type, {})["static:#{method_name}"] end |
#find_nested_type_by_short_name(facts, short_name) ⇒ Object
1589 1590 1591 1592 1593 1594 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1589 def find_nested_type_by_short_name(facts, short_name) matches = facts.types.keys.select { |k| k.to_s.end_with?(".#{short_name}") } return nil unless matches.length == 1 facts.types[matches.first] end |
#find_variant_arm_in_types(facts, name) ⇒ Object
1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1609 def find_variant_arm_in_types(facts, name) facts.types.reverse_each do |_key, type| arm = nil if type.respond_to?(:arms) arm = type.arms[name] elsif type.respond_to?(:arm_names) && type.arm_names.include?(name) arm = type.arm(name) end next unless arm fields = arm.map { |fname, ftype| "#{fname}: #{ftype}" }.join(", ") field_str = fields.empty? ? "" : "(#{fields})" return "#{type.name}.#{name}#{field_str}" end nil end |
#for_binding_context_at(tokens, token_index) ⇒ Object
2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 2089 def for_binding_context_at(tokens, token_index) return nil unless token_index tok = tokens[token_index] return nil unless tok&.type == :identifier # Find the `for` keyword on the same line at or before the binding. for_index = nil i = token_index while i >= 0 current = tokens[i] break if current.type == :newline && current.line != tok.line if current.type == :for && current.line == tok.line for_index = i break end i -= 1 end return nil unless for_index # Find `in` after the bindings, still on the same line. in_index = nil j = for_index + 1 while j < tokens.length t = tokens[j] break if t.type == :newline && t.line != tok.line if t.type == :in && t.line == tok.line in_index = j break end j += 1 end return nil unless in_index # The hovered token must be one of the bindings between `for` and `in`. return nil unless token_index > for_index && token_index < in_index ie = in_index + 1 while ie < tokens.length nt = tokens[ie] break if nt.type == :newline || nt.type == :colon ie += 1 end iterable_text = tokens[(in_index + 1)...ie].map(&:lexeme).join(" ") iterable_text = iterable_text.gsub(/\s+/, " ").strip { name: tok.lexeme, iterable_text: iterable_text.empty? ? nil : iterable_text, } end |
#format_params(params) ⇒ Object
1009 1010 1011 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1009 def format_params(params) params.map { |p| "#{p.name}: #{p.type}" }.join(', ') end |
#fstring_interpolation_token_context(tokens, lsp_line, lsp_char) ⇒ Object
678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 678 def fstring_interpolation_token_context(tokens, lsp_line, lsp_char) target_line = lsp_line + 1 target_char = lsp_char + 1 fstring_token = tokens.find do |token| next false unless token.type == :fstring token_contains_position?(token, target_line, target_char) end return nil unless fstring_token Array(fstring_token.literal).each do |part| next unless part[:kind] == :expr next unless part[:line] == target_line expression_tokens = interpolation_expression_tokens(part) token = expression_tokens.find { |candidate| token_contains_position?(candidate, target_line, target_char) } next unless token return { token: token, tokens: expression_tokens, token_index: expression_tokens.index(token), } end nil end |
#handle_hover(params) ⇒ Object
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 359 def handle_hover(params) stages = new_perf_stages total_start = stages ? monotonic_time : nil uri = params['textDocument']['uri'] lsp_line = params['position']['line'] lsp_char = params['position']['character'] token_kind = 'none' result_state = 'miss' context = measure_perf_stage(stages, 'context') { token_context_at(uri, lsp_line, lsp_char) } token = context&.fetch(:token, nil) token_kind = token&.type || :none unless token&.type == :identifier return nil if token.nil? unless member_access_token?(context) info = builtin_keyword_hover_info(token) || BUILTIN_CALL_HOVER_INFO[token.lexeme]&.slice(:signature, :docs) if info result_state = 'hit' return { contents: { kind: 'markdown', value: render_hover_markdown(info) }, range: token_to_range(token) } end result_state = 'not-identifier' return nil end end info = resolve_hover_info(uri, lsp_line, lsp_char, token: token, tokens: context[:tokens], token_index: context[:token_index], stages: stages) return nil unless info result = measure_perf_stage(stages, 'render') do { contents: { kind: 'markdown', value: render_hover_markdown(info) }, range: token_to_range(token) } end result_state = 'hit' result rescue StandardError => e result_state = 'error' warn "Error in hover handler: #{e.}" nil ensure log_request_stage_breakdown('textDocument/hover', total_start, uri: uri, stages: stages, summary: "token=#{token_kind} result=#{result_state}") end |
#hover_definition_entry_from_location(location) ⇒ Object
997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 997 def hover_definition_entry_from_location(location) return nil unless location start = location.dig(:range, :start) return nil unless start token = @workspace.find_token_at(location[:uri], start[:line], start[:character]) return nil unless token { uri: location[:uri], token: token } end |
#hover_doc_comment_data_for_definition(definition_entry) ⇒ Object
865 866 867 868 869 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 865 def hover_doc_comment_data_for_definition(definition_entry) return nil unless definition_entry @workspace.doc_comment_data_for_definition(definition_entry[:uri], definition_entry[:token]) end |
#hover_doc_comment_for_definition(definition_entry) ⇒ Object
859 860 861 862 863 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 859 def hover_doc_comment_for_definition(definition_entry) return nil unless definition_entry @workspace.doc_comment_for_definition(definition_entry[:uri], definition_entry[:token]) end |
#hover_source_label(uri, line) ⇒ Object
977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 977 def hover_source_label(uri, line) path = uri_to_path(uri) return nil unless path display = path if @root_uri root_path = uri_to_path(@root_uri) if root_path begin relative = Pathname.new(path).relative_path_from(Pathname.new(root_path)).to_s display = relative unless relative.start_with?('..') rescue StandardError display = path end end end "#{display}:#{line}" end |
#hover_source_label_for_definition(definition_entry) ⇒ Object
939 940 941 942 943 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 939 def hover_source_label_for_definition(definition_entry) return nil unless definition_entry hover_source_label(definition_entry[:uri], definition_entry[:token].line) end |
#hover_source_label_from_location(location) ⇒ Object
957 958 959 960 961 962 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 957 def hover_source_label_from_location(location) return nil unless location line = location.dig(:range, :start, :line) hover_source_label(location[:uri], (line || 0) + 1) end |
#hover_source_line_for_definition(definition_entry) ⇒ Object
951 952 953 954 955 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 951 def hover_source_line_for_definition(definition_entry) return nil unless definition_entry definition_entry[:token].line end |
#hover_source_line_from_location(location) ⇒ Object
970 971 972 973 974 975 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 970 def hover_source_line_from_location(location) return nil unless location line = location.dig(:range, :start, :line) (line || 0) + 1 end |
#hover_source_uri_for_definition(definition_entry) ⇒ Object
945 946 947 948 949 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 945 def hover_source_uri_for_definition(definition_entry) return nil unless definition_entry definition_entry[:uri] end |
#hover_source_uri_from_location(location) ⇒ Object
964 965 966 967 968 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 964 def hover_source_uri_from_location(location) return nil unless location location[:uri] end |
#interface_method_signature(binding) ⇒ Object
1021 1022 1023 1024 1025 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1021 def interface_method_signature(binding) keyword = binding.kind == :editable ? 'editable function' : 'function' keyword = "async #{keyword}" if binding.async "#{keyword} #{binding.name}(#{format_params(binding.params)}) -> #{binding.return_type}" end |
#interface_signature(binding) ⇒ Object
1013 1014 1015 1016 1017 1018 1019 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1013 def interface_signature(binding) method_lines = binding.methods.values.map do |method| " #{interface_method_signature(method)}" end (["interface #{binding.name}"] + method_lines).join("\n") end |
#interpolation_expression_tokens(part) ⇒ Object
707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 707 def interpolation_expression_tokens(part) source = part[:source] return [] if source.nil? || source.strip.empty? MilkTea::Lexer.new(source).lex .reject { |token| [:newline, :indent, :dedent, :eof].include?(token.type) } .map do |token| adjusted_line = part[:line] + token.line - 1 adjusted_column = token.line == 1 ? (part[:column] + token.column - 1) : token.column token.with( line: adjusted_line, column: adjusted_column, start_offset: nil, end_offset: nil, leading_trivia: [].freeze, trailing_trivia: [].freeze, ) end rescue MilkTea::LexError [] end |
#keyword_member_access_token?(tokens, token_index) ⇒ Boolean
1780 1781 1782 1783 1784 1785 1786 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1780 def keyword_member_access_token?(tokens, token_index) return false unless tokens && token_index tok = tokens[token_index] return false unless tok && tok.type != :identifier prev = previous_non_trivia_token_index(tokens, token_index) prev && tokens[prev].type == :dot end |
#latest_completion_snapshot(frame, line, char) ⇒ Object
2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 2051 def latest_completion_snapshot(frame, line, char) snapshots = Array(frame.snapshots) snapshots.reverse_each do |snapshot| next if snapshot.line > line next if snapshot.line == line && snapshot.column > char return snapshot end nil end |
#member_access_chain_at(tokens, token_index) ⇒ Object
1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1479 def member_access_chain_at(tokens, token_index) token = tokens[token_index] return nil unless token&.type == :identifier indices = [token_index] current_index = token_index loop do dot_index = previous_non_trivia_token_index(tokens, current_index) break unless dot_index && tokens[dot_index].type == :dot && tokens[dot_index].line == token.line receiver_index = previous_non_trivia_token_index(tokens, dot_index) break unless receiver_index && tokens[receiver_index].type == :identifier && tokens[receiver_index].line == token.line indices.unshift(receiver_index) current_index = receiver_index end current_index = token_index loop do dot_index = next_non_trivia_token_index(tokens, current_index + 1) break unless dot_index && tokens[dot_index].type == :dot && tokens[dot_index].line == token.line member_index = next_non_trivia_token_index(tokens, dot_index + 1) break unless member_index && tokens[member_index].type == :identifier && tokens[member_index].line == token.line indices << member_index current_index = member_index end return nil if indices.length < 2 { line: token.line, char: token.column + token.lexeme.length, segments: indices.each_with_index.map do |index, position| { name: tokens[index].lexeme, token_index: index, position: position, } end, } end |
#member_access_token?(context) ⇒ Boolean
1772 1773 1774 1775 1776 1777 1778 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1772 def member_access_token?(context) tokens = context[:tokens] token_index = context[:token_index] return false unless tokens && token_index prev = previous_non_trivia_token_index(tokens, token_index) prev && tokens[prev].type == :dot end |
#member_method_info_for_receiver_type(facts, receiver_type, method_name) ⇒ Object
1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1117 def member_method_info_for_receiver_type(facts, receiver_type, method_name) return nil unless receiver_type dispatch_receiver_type = method_dispatch_receiver_type_for_completion(receiver_type) if (binding = find_method_entry(facts.methods, receiver_type, method_name)) return { binding: binding, module_name: facts.module_name, } end if dispatch_receiver_type != receiver_type && (binding = find_method_entry(facts.methods, dispatch_receiver_type, method_name)) return { binding: binding, module_name: facts.module_name, } end facts.imports.each_value do |module_binding| binding = find_method_entry(module_binding.methods, receiver_type, method_name) if binding.nil? && dispatch_receiver_type != receiver_type binding = find_method_entry(module_binding.methods, dispatch_receiver_type, method_name) end next unless binding return { binding: binding, module_name: module_binding.name, } end nil end |
#method_binding_at_token(facts, token) ⇒ Object
1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1524 def method_binding_at_token(facts, token) facts.methods.each_value do |methods| methods.each_value do |binding| next unless binding.name == token.lexeme next unless binding.ast.is_a?(AST::MethodDef) next unless binding.ast.line == token.line next unless binding.ast.column == token.column return binding end end facts.interfaces.each_value do |interface_binding| interface_binding.methods.each_value do |method_binding| next unless method_binding.name == token.lexeme next unless method_binding.ast.line == token.line next unless method_binding.ast.column == token.column return method_binding end end nil end |
#method_signature(binding) ⇒ Object
1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1549 def method_signature(binding) async_prefix = (binding.respond_to?(:async) && binding.async) || (binding.ast.respond_to?(:async) && binding.ast.async) ? 'async ' : '' if binding.respond_to?(:type) && binding.type params_str = format_params(binding.type.params) keyword = case binding.ast.kind when :editable "editable function" when :static "static function" else "function" end "#{async_prefix}#{keyword} #{binding.name}(#{params_str}) -> #{binding.type.return_type}" else params_str = binding.params.map { |p| "#{p.name}: #{p.type}" }.join(', ') keyword = case binding.kind when :editable "editable function" when :static "static function" else "function" end "#{async_prefix}#{keyword} #{binding.name}(#{params_str}) -> #{binding.return_type}" end end |
#named_argument_callee_name(tokens, token_index) ⇒ Object
1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1397 def named_argument_callee_name(tokens, token_index) opener_index = parameter_list_opener_index(tokens, token_index) return nil unless opener_index head_index = previous_non_trivia_token_index(tokens, opener_index) return nil unless head_index # Skip generic type arguments before the callee: `span[int](...)`. if tokens[head_index].type == :rbracket lbracket_index = matching_opener_index(tokens, head_index) return nil unless lbracket_index head_index = previous_non_trivia_token_index(tokens, lbracket_index) end return nil unless head_index && tokens[head_index].type == :identifier tokens[head_index].lexeme end |
#named_argument_label_receiver_info(facts, tokens, token_index) ⇒ Object
1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1447 def named_argument_label_receiver_info(facts, tokens, token_index) opener_index = parameter_list_opener_index(tokens, token_index) return nil unless opener_index head_index = previous_non_trivia_token_index(tokens, opener_index) return nil unless head_index if tokens[head_index].type == :rbracket lbracket_index = matching_opener_index(tokens, head_index) return nil unless lbracket_index head_index = previous_non_trivia_token_index(tokens, lbracket_index) return nil unless head_index end head = tokens[head_index] return nil unless head.type == :identifier receiver_name = head.lexeme receiver_path = receiver_name dot_index = previous_non_trivia_token_index(tokens, head_index) if dot_index && tokens[dot_index].type == :dot module_index = previous_non_trivia_token_index(tokens, dot_index) return nil unless module_index && tokens[module_index].type == :identifier receiver_path = "#{tokens[module_index].lexeme}.#{receiver_name}" end resolve_type_receiver_info(facts, receiver_name, receiver_path) end |
#named_argument_param_result(field_name, param, callable_ast) ⇒ Object
1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1360 def named_argument_param_result(field_name, param, callable_ast) param_ast = callable_ast.params.find { |p| p.name == field_name } source_location = param_ast ? ast_name_range(field_name, param_ast.line, param_ast.column) : nil = callable_ast.respond_to?(:doc_comment) ? param_doc_for_name(callable_ast, field_name) : nil { signature: "#{field_name}: #{param.type}", docs: , source: hover_source_label_from_location(source_location), source_uri: hover_source_uri_from_location(source_location), source_line: hover_source_line_from_location(source_location), } end |
#named_argument_parameter_info(facts, tokens, token_index) ⇒ Object
1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1255 def named_argument_parameter_info(facts, tokens, token_index) receiver_name = named_argument_callee_name(tokens, token_index) return nil unless receiver_name field_name = tokens[token_index].lexeme if facts.functions.key?(receiver_name) func = facts.functions[receiver_name] func.type.params.each do |param| next unless param.name == field_name return named_argument_param_result(field_name, param, func.ast) end end facts.methods.each_value do |methods| method = methods[receiver_name] next unless method method.type.params.each do |param| next unless param.name == field_name return named_argument_param_result(field_name, param, method.ast) end end # Variant arm constructors: `Option[int].some(value = 42)`. if (arm_info = variant_arm_constructor_info(facts, tokens, token_index)) field_type = arm_info[:fields][field_name] if field_type return { signature: "#{field_name}: #{field_type}", docs: nil, source: nil, source_uri: nil, source_line: nil, } end end # Builtin constructors with named parameters: `span[T](data, len)`. if (builtin_arg = BUILTIN_NAMED_ARGUMENT_SIGNATURES.dig(receiver_name, field_name)) return { signature: builtin_arg, docs: nil, source: nil, source_uri: nil, source_line: nil, } end nil end |
#param_doc_for_name(callable_ast, param_name) ⇒ Object
1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1374 def param_doc_for_name(callable_ast, param_name) return nil unless callable_ast.respond_to?(:doc_comment) return nil unless callable_ast.doc_comment lines = callable_ast.doc_comment.lines param_lines = [] in_param = false lines.each do |line| if line =~ /@param\s+#{Regexp.escape(param_name)}\b/ in_param = true param_lines << line.sub(/^\s*##\s*@param\s+#{Regexp.escape(param_name)}\s*/, "") next end if in_param if line =~ /@param\b/ break end param_lines << line.sub(/^\s*##\s*/, "") end end param_lines.join(" ").strip.empty? ? nil : param_lines.join(" ").strip end |
#parameter_declaration_token?(tokens, index) ⇒ Boolean
1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1639 def parameter_declaration_token?(tokens, index) return false unless index && tokens[index]&.type == :identifier prev = previous_non_trivia_token_index(tokens, index) return false unless prev return false unless [:lparen, :comma].include?(tokens[prev].type) nxt = next_non_trivia_token_index(tokens, index + 1) nxt && tokens[nxt].type == :colon end |
#render_builtin_specialization(tokens) ⇒ Object
1976 1977 1978 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1976 def render_builtin_specialization(tokens) Array(tokens).map(&:lexeme).join.gsub(',', ', ') end |
#render_hover_markdown(info) ⇒ Object
746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 746 def render_hover_markdown(info) lines = [] signature = info[:signature].to_s shortened, modules = shorten_qualified_types(signature) lines << "```milk-tea" lines << shortened lines << "```" unless modules.empty? prefix = 'From' lines << "*#{prefix} #{modules.join(', ')}*" end rendered_doc_comment = false if info[:doc_comment].is_a?(Hash) rendered_doc_comment = append_structured_doc_comment_markdown(lines, info[:doc_comment]) end docs = info[:docs].to_s.strip unless rendered_doc_comment || docs.empty? lines << "" lines << docs end source_uri = info[:source_uri] source_line = info[:source_line] source_label = info[:source].to_s.strip unless source_label.empty? lines << "" if source_uri && source_line link_uri = "#{source_uri}#L#{source_line}" lines << "Defined at: [#{source_label}](#{link_uri})" else lines << "Defined at: #{source_label}" end end lines.join("\n") end |
#resolve_arm_receiver_type(facts, tokens, type_name_index) ⇒ Object
1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1346 def resolve_arm_receiver_type(facts, tokens, type_name_index) type_name = tokens[type_name_index].lexeme return facts.types[type_name] if facts.types.key?(type_name) dot_index = previous_non_trivia_token_index(tokens, type_name_index) return nil unless dot_index && tokens[dot_index].type == :dot module_index = previous_non_trivia_token_index(tokens, dot_index) return nil unless module_index && tokens[module_index].type == :identifier module_binding = facts.imports[tokens[module_index].lexeme] module_binding&.types&.fetch(type_name, nil) end |
#resolve_as_binding_declaration_hover_binding(facts, name, line, char) ⇒ Object
2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 2013 def resolve_as_binding_declaration_hover_binding(facts, name, line, char) frame = enclosing_completion_frame(facts, line) return nil unless frame Array(frame.snapshots).each do |snapshot| next if snapshot.line < line next if snapshot.line == line && snapshot.column <= char binding = snapshot.bindings[name] return binding if binding end nil end |
#resolve_dot_receiver_value_type(facts, receiver_name, line, char) ⇒ Object
1027 1028 1029 1030 1031 1032 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1027 def resolve_dot_receiver_value_type(facts, receiver_name, line, char) local_type = resolve_local_hover_type(facts, receiver_name, line, char) return local_type if local_type facts.values[receiver_name]&.type end |
#resolve_enum_member_access_info(current_uri, facts, tokens, token_index) ⇒ Object
1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1176 def resolve_enum_member_access_info(current_uri, facts, tokens, token_index) return nil unless type_name_member_access?(tokens, token_index, facts) token = tokens[token_index] token_end_char = token.column - 1 + token.lexeme.length receiver_name = @workspace.find_dot_receiver(current_uri, token.line - 1, token_end_char) receiver_path = @workspace.find_dot_receiver_path(current_uri, token.line - 1, token_end_char) receiver_info = resolve_type_receiver_info(facts, receiver_name, receiver_path) return nil unless receiver_info receiver_type = receiver_info[:type] return nil unless receiver_type.is_a?(Types::EnumBase) return nil unless receiver_type.member(token.lexeme) owner_module_name = receiver_type.respond_to?(:module_name) ? receiver_type.module_name : receiver_info[:module_name] { receiver_label: receiver_info[:label], member_name: token.lexeme, value_text: enum_member_value_text(current_uri, owner_module_name, receiver_type.name, token.lexeme), location: enum_member_definition_location(current_uri, owner_module_name, receiver_type.name, token.lexeme), } end |
#resolve_enum_member_definition_location(current_uri, facts, tokens, token_index) ⇒ Object
1172 1173 1174 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1172 def resolve_enum_member_definition_location(current_uri, facts, tokens, token_index) resolve_enum_member_access_info(current_uri, facts, tokens, token_index)&.fetch(:location, nil) end |
#resolve_enum_member_hover_info(current_uri, facts, tokens, token_index) ⇒ Object
1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1156 def resolve_enum_member_hover_info(current_uri, facts, tokens, token_index) member_info = resolve_enum_member_access_info(current_uri, facts, tokens, token_index) return nil unless member_info signature = "#{member_info[:member_name]}: #{member_info[:receiver_label]}" signature += " = #{member_info[:value_text]}" if member_info[:value_text] { signature: signature, docs: nil, source: hover_source_label_from_location(member_info[:location]), source_uri: hover_source_uri_from_location(member_info[:location]), source_line: hover_source_line_from_location(member_info[:location]), } end |
#resolve_field_declaration_hover_info(current_uri, facts, tokens, token_index) ⇒ Object
1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1200 def resolve_field_declaration_hover_info(current_uri, facts, tokens, token_index) receiver_info = field_declaration_receiver_info(facts, tokens, token_index) return nil unless receiver_info field_name = tokens[token_index].lexeme receiver_type = project_field_receiver_type_for_completion(receiver_info[:type], facts) return nil unless receiver_type.respond_to?(:field) field_type = receiver_type.field(field_name) return nil unless field_type source_location = field_definition_location(current_uri, receiver_type, field_name) { signature: field_hover_signature(field_name, field_type), docs: nil, source: hover_source_label_from_location(source_location), source_uri: hover_source_uri_from_location(source_location), source_line: hover_source_line_from_location(source_location), } end |
#resolve_for_binding_hover_info(tokens, token_index) ⇒ Object
2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 2073 def resolve_for_binding_hover_info(tokens, token_index) tok = tokens[token_index] return nil unless tok&.type == :identifier binding_info = for_binding_context_at(tokens, token_index) return nil unless binding_info binding_name = binding_info[:name] iterable_text = binding_info[:iterable_text] || "collection" { signature: "for binding #{binding_name} (iterating over #{iterable_text})", docs: nil, } end |
#resolve_hover_info(uri, lsp_line, lsp_char, token: nil, tokens: nil, token_index: nil, stages: nil) ⇒ Object
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 414 def resolve_hover_info(uri, lsp_line, lsp_char, token: nil, tokens: nil, token_index: nil, stages: nil) if token.nil? context = measure_perf_stage(stages, 'context') { token_context_at(uri, lsp_line, lsp_char) } return nil unless context token = context[:token] tokens = context[:tokens] token_index = context[:token_index] end tokens ||= @workspace.get_tokens(uri) || [] token_index = tokens.index(token) if token_index.nil? member_access_kw = token_index && keyword_member_access_token?(tokens, token_index) return nil unless token&.type == :identifier || member_access_kw if token_index module_info = module_declaration_info_at(tokens, token_index) if module_info location = module_definition_location(uri, module_info[:module_name]) return { signature: "module #{module_info[:module_name]}", docs: nil, source: hover_source_label_from_location(location), source_uri: hover_source_uri_from_location(location), source_line: hover_source_line_from_location(location), } end end if token_index import_info = import_path_info_at(tokens, token_index) if import_info location = module_definition_location(uri, import_info[:module_name]) return { signature: "module #{import_info[:module_name]}", docs: nil, source: hover_source_label_from_location(location), source_uri: hover_source_uri_from_location(location), source_line: hover_source_line_from_location(location), } end end facts = measure_perf_stage(stages, 'facts') do @workspace.get_facts(uri, allow_last_good_fallback: allow_hover_last_good_fallback?(uri)) end return nil unless facts if token_index && field_declaration_token?(tokens, token_index) return resolve_field_declaration_hover_info(uri, facts, tokens, token_index) end if token_index && named_argument_label_token?(tokens, token_index) return resolve_named_argument_label_hover_info(uri, facts, tokens, token_index) end if token_index && (member_hover = resolve_member_access_hover_info(uri, facts, tokens, token_index)) return member_hover end if token_index && (enum_member_hover = resolve_enum_member_hover_info(uri, facts, tokens, token_index)) return enum_member_hover end name = token.lexeme signature = nil docs = nil source_location = nil resolved_via_type_member = false if (binding = method_binding_at_token(facts, token)) signature = method_signature(binding) source_location = module_member_binding_location(uri, facts.module_name, name, binding) end unless signature if token_index && (for_binding_info = resolve_for_binding_hover_info(tokens, token_index)) signature = for_binding_info[:signature] end end unless signature if token_index && match_arm_binding_token?(tokens, token_index) if (local_binding = resolve_as_binding_declaration_hover_binding(facts, name, lsp_line + 1, lsp_char + 1)) signature = value_hover_signature(local_binding) end end end unless signature if (local_binding = resolve_local_hover_binding(facts, name, lsp_line + 1, lsp_char + 1)) signature = value_hover_signature(local_binding) end end unless signature if (binding = facts.functions[name]) params_str = format_params(binding.type.params) signature = "function #{name}(#{params_str}) -> #{binding.type.return_type}" elsif (binding = facts.interfaces[name]) signature = interface_signature(binding) source_location = module_member_definition_location(uri, binding.module_name, name) elsif facts.types.key?(name) type = facts.types[name] signature = type_hover_signature(name, type) docs ||= BUILTIN_TYPE_DOCS[name] elsif !name.include?(".") && (nested = find_nested_type_by_short_name(facts, name)) signature = type_hover_signature(name, nested) docs ||= BUILTIN_TYPE_DOCS[name] elsif (binding = facts.values[name]) signature = value_hover_signature(binding) elsif (tokens, token_index) && (member_info = find_member_in_types(facts, name)) type, member, value = member_info signature = value ? "#{type.name}.#{member} = #{value}" : "#{type.name}.#{member}" resolved_via_type_member = true elsif (tokens, token_index) && (arm_sig = find_variant_arm_in_types(facts, name)) signature = arm_sig resolved_via_type_member = true elsif (import_binding = facts.imports[name]) signature = "module #{import_binding.name}" source_location = module_definition_location(uri, import_binding.name) elsif (attr_binding = facts.attributes[name]) targets = attr_binding.targets.map(&:to_s).join(", ") params_str = attr_binding.params.map { |p| "#{p.name}: #{p.type}" }.join(", ") signature = "attribute [#{targets}] #{name}(#{params_str})" source_location ||= module_definition_location(uri, facts.module_name) else dot_receiver = @workspace.find_dot_receiver(uri, lsp_line, lsp_char) dot_receiver_path = @workspace.find_dot_receiver_path(uri, lsp_line, lsp_char) if dot_receiver && (module_binding = facts.imports[dot_receiver]) if (fn = module_binding.functions[name]) params_str = format_params(fn.type.params) signature = "function #{name}(#{params_str}) -> #{fn.type.return_type}" source_location = module_member_binding_location(uri, module_binding.name, name, fn) elsif (val = module_binding.values[name]) signature = value_hover_signature(val) elsif module_binding.types.key?(name) signature = "type #{name}" docs ||= BUILTIN_TYPE_DOCS[name] elsif (binding = module_binding.interfaces[name]) signature = interface_signature(binding) source_location = module_member_definition_location(uri, module_binding.name, name) end if signature source_location ||= module_member_definition_location(uri, module_binding.name, name) source_location ||= module_definition_location(uri, module_binding.name) end end unless signature if (type_method = resolve_static_type_receiver_method(facts, dot_receiver, dot_receiver_path, name)) signature = method_signature(type_method[:binding]) source_location = module_member_binding_location(uri, type_method[:module_name], name, type_method[:binding]) source_location ||= module_member_definition_location(uri, type_method[:module_name], name) source_location ||= module_definition_location(uri, type_method[:module_name]) end end unless signature if dot_receiver receiver_name = dot_receiver receiver_binding = resolve_local_hover_binding(facts, receiver_name, lsp_line + 1, lsp_char + 1) || facts.values[receiver_name] || facts.functions[receiver_name] if receiver_binding && receiver_binding.type receiver_type = receiver_binding.respond_to?(:storage_type) ? receiver_binding.storage_type : receiver_binding.type methods = methods_for_receiver_type(facts, receiver_type) if (method_binding = methods[name]) signature = method_signature(method_binding) else type_base = receiver_type.to_s[/^([A-Za-z0-9_]+)/, 1] if type_base && (method_sigs = BUILTIN_TYPE_METHOD_SIGNATURES[type_base]) signature = method_sigs[name] end end end end end unless signature if token_index && (builtin_info = builtin_hover_info(name, tokens, token_index)) signature = builtin_info[:signature] docs = builtin_info[:docs] end end unless signature if token_index && tokens && parameter_declaration_token?(tokens, token_index) signature = resolve_lexical_local_hover_signature(uri, name, tokens[token_index]) end end unless signature if token_index && tokens if (type_param = type_parameter_hover_signature(tokens, token_index)) signature = type_param elsif (attr_sig = builtin_attribute_hover_signature(tokens, token_index)) signature = attr_sig elsif attribute_target_token?(tokens, token_index) signature = "attribute target #{name}" end end end end return nil unless signature end definition_entry = if source_location measure_perf_stage(stages, 'definition_entry') { hover_definition_entry_from_location(source_location) } elsif resolved_via_type_member nil else measure_perf_stage(stages, 'global_definition') do @workspace.find_definition_token_global( name, preferred_uri: uri, before_line: lsp_line + 1, before_char: lsp_char + 1, ) end end source_uri = hover_source_uri_for_definition(definition_entry) || hover_source_uri_from_location(source_location) source_line = hover_source_line_for_definition(definition_entry) || hover_source_line_from_location(source_location) { signature: signature, docs: docs || hover_doc_comment_for_definition(definition_entry), doc_comment: hover_doc_comment_data_for_definition(definition_entry), source: hover_source_label_for_definition(definition_entry) || hover_source_label_from_location(source_location), source_uri: source_uri, source_line: source_line, } end |
#resolve_lexical_local_hover_signature(definition_uri, name, definition_token) ⇒ Object
2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 2142 def resolve_lexical_local_hover_signature(definition_uri, name, definition_token) kind = nil type_str = nil tokens = @workspace.get_tokens(definition_uri) if tokens def_index = tokens.index(definition_token) if def_index prev = previous_non_trivia_token_index(tokens, def_index) if prev case tokens[prev].lexeme when "let" then kind = :let when "var" then kind = :var when "const" then kind = :const when "struct" then kind = :struct_type when "function", "async", "external", "foreign" then kind = :function when "enum" then kind = :enum_type when "flags" then kind = :flags_type when "union" then kind = :union_type when "variant" then kind = :variant_type when "(" then kind = :param when "," then kind = :param end end if kind next_idx = next_non_trivia_token_index(tokens, def_index + 1) if next_idx && tokens[next_idx].type == :colon type_start = next_non_trivia_token_index(tokens, next_idx + 1) if type_start && tokens[type_start].type == :identifier type_str = tokens[type_start].lexeme type_end = type_start loop do next_tok_idx = next_non_trivia_token_index(tokens, type_end + 1) break unless next_tok_idx && %i[identifier lbracket rbracket integer comma].include?(tokens[next_tok_idx].type) type_str += tokens[next_tok_idx].lexeme type_end = next_tok_idx break if tokens[type_end].type == :equal end type_str = type_str.sub(/\s*=.*/, "").strip end end end end end kind ||= :let unless %i[let var const param].include?(kind) kind = kind.to_s.sub(/_type$/, "") return "#{kind} #{name}" end display_kind = kind == :param ? "parameter" : kind.to_s mutability = kind == :var ? "mutable" : "immutable" if type_str && !type_str.empty? "#{display_kind} #{name}: #{type_str} (#{mutability})" else "#{kind} #{name} (#{mutability})" end end |
#resolve_local_hover_binding(facts, name, line, char) ⇒ Object
1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1998 def resolve_local_hover_binding(facts, name, line, char) declared_binding = declared_generic_local_hover_binding(facts, name, line) return declared_binding if declared_binding frame = enclosing_completion_frame(facts, line) return nil unless frame snapshot = latest_completion_snapshot(frame, line, char) binding = snapshot&.bindings&.dig(name) return binding if binding future_snapshot = same_line_future_completion_snapshot(frame, line, char) future_snapshot&.bindings&.dig(name) end |
#resolve_local_hover_type(facts, name, line, char) ⇒ Object
2028 2029 2030 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 2028 def resolve_local_hover_type(facts, name, line, char) resolve_local_hover_binding(facts, name, line, char)&.type end |
#resolve_member_access_hover_info(current_uri, facts, tokens, token_index) ⇒ Object
1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1034 def resolve_member_access_hover_info(current_uri, facts, tokens, token_index) chain = member_access_chain_at(tokens, token_index) return nil unless chain hovered_segment = chain[:segments].find { |segment| segment[:token_index] == token_index } return nil unless hovered_segment && hovered_segment[:position].positive? current_type = resolve_dot_receiver_value_type( facts, chain[:segments].first[:name], chain[:line], chain[:char], ) unless current_type first_name = chain[:segments].first[:name] current_type = facts.types[first_name] end return nil unless current_type chain[:segments][1..hovered_segment[:position]].each do |segment| field_receiver_type = project_field_receiver_type_for_completion(current_type, facts) if field_receiver_type.respond_to?(:field) && (field_type = field_receiver_type.field(segment[:name])) source_location = field_definition_location(current_uri, field_receiver_type, segment[:name]) if segment[:token_index] == token_index definition_entry = hover_definition_entry_from_location(source_location) return { signature: field_hover_signature(segment[:name], field_type), docs: hover_doc_comment_for_definition(definition_entry), source: hover_source_label_from_location(source_location), source_uri: hover_source_uri_from_location(source_location), source_line: hover_source_line_from_location(source_location), } end current_type = field_type next end if current_type.respond_to?(:nested_types) && (nested = current_type.nested_types[segment[:name]]) if segment[:token_index] == token_index return { signature: type_hover_signature(segment[:name], nested), docs: BUILTIN_TYPE_DOCS[segment[:name]], } end current_type = nested next end next unless segment[:token_index] == token_index method_receiver_type = project_method_receiver_type_for_completion(current_type) method_info = member_method_info_for_receiver_type(facts, method_receiver_type, segment[:name]) if method_info.nil? # Native/builtin receivers (vectors, matrices, pointers, arrays, # simd, atomic, ...) have no facts.methods entries; fall back to the # builtin method signature table so member calls resolve accurately # instead of falling through to a bare-name match. base = method_receiver_type.to_s[/^([A-Za-z0-9_]+)/, 1] method_info = base && BUILTIN_TYPE_METHOD_SIGNATURES[base]&.fetch(segment[:name], nil) method_info = { signature: method_info, module_name: nil, binding: nil } if method_info.is_a?(String) end return nil unless method_info source_location = if method_info[:binding] module_member_binding_location(current_uri, method_info[:module_name], segment[:name], method_info[:binding]) end source_location ||= (module_member_definition_location(current_uri, method_info[:module_name], segment[:name]) if method_info[:module_name]) definition_entry = hover_definition_entry_from_location(source_location) return { signature: method_info[:signature] || (method_info[:binding] && method_signature(method_info[:binding])), docs: hover_doc_comment_for_definition(definition_entry), source: hover_source_label_from_location(source_location), source_uri: hover_source_uri_from_location(source_location), source_line: hover_source_line_from_location(source_location), } end nil end |
#resolve_named_argument_label_hover_info(current_uri, facts, tokens, token_index) ⇒ Object
1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1222 def resolve_named_argument_label_hover_info(current_uri, facts, tokens, token_index) receiver_info = named_argument_label_receiver_info(facts, tokens, token_index) field_name = tokens[token_index].lexeme if receiver_info receiver_type = project_field_receiver_type_for_completion(receiver_info[:type], facts) if receiver_type.respond_to?(:field) field_type = receiver_type.field(field_name) if field_type source_location = field_definition_location(current_uri, receiver_type, field_name) return { signature: field_hover_signature(field_name, field_type), docs: nil, source: hover_source_label_from_location(source_location), source_uri: hover_source_uri_from_location(source_location), source_line: hover_source_line_from_location(source_location), } end end end param_info = named_argument_parameter_info(facts, tokens, token_index) return nil unless param_info { signature: param_info[:signature], docs: param_info[:docs], source: param_info[:source], source_uri: param_info[:source_uri], source_line: param_info[:source_line], } end |
#same_line_future_completion_snapshot(frame, line, char) ⇒ Object
2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 2062 def same_line_future_completion_snapshot(frame, line, char) snapshots = Array(frame.snapshots) snapshots.each do |snapshot| next unless snapshot.line == line next if snapshot.column <= char return snapshot end nil end |
#shorten_qualified_types(signature) ⇒ Object
788 789 790 791 792 793 794 795 796 797 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 788 def shorten_qualified_types(signature) modules = [] shortened = signature.gsub(/\b([a-z_][\w]*(?:\.[a-z_][\w]*)*\.)([A-Z]\w*(?:\[[^\]]*\])?(?:\s*\|\s*(?:[a-z_][\w]*(?:\.[a-z_][\w]*)*\.)?[A-Z]\w*(?:\[[^\]]*\])?)*)\b/) do prefix = $1 mod_name = prefix.delete_suffix('.') modules << mod_name unless modules.include?(mod_name) $2 end [shortened, modules] end |
#signature_help_doc_comment_for_call(uri, name, lsp_line, lsp_char) ⇒ Object
871 872 873 874 875 876 877 878 879 880 881 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 871 def signature_help_doc_comment_for_call(uri, name, lsp_line, lsp_char) definition_entry = @workspace.find_definition_token_global( name, preferred_uri: uri, before_line: lsp_line + 1, before_char: lsp_char + 1, ) return nil unless definition_entry @workspace.doc_comment_data_for_definition(definition_entry[:uri], definition_entry[:token]) end |
#signature_help_markdown_for_doc_comment(doc_comment) ⇒ Object
883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 883 def signature_help_markdown_for_doc_comment(doc_comment) return '' unless doc_comment.is_a?(Hash) lines = [] body = doc_comment[:body_markdown].to_s.strip lines << body unless body.empty? returns = doc_tag_return_description(doc_comment) unless returns.empty? lines << '' unless lines.empty? lines << "**Returns**" lines << returns end lines.join("\n") end |
#token_contains_position?(token, target_line, target_char) ⇒ Boolean
730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 730 def token_contains_position?(token, target_line, target_char) segments = token.lexeme.split("\n", -1) end_line = token.line + segments.length - 1 return false if target_line < token.line || target_line > end_line if segments.length == 1 return token.column <= target_char && target_char < (token.column + segments.first.length) end if target_line == token.line return token.column <= target_char && target_char <= (token.column + segments.first.length - 1) end target_char <= segments.fetch(target_line - token.line).length end |
#token_context_at(uri, lsp_line, lsp_char) ⇒ Object
652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 652 def token_context_at(uri, lsp_line, lsp_char) tokens = @workspace.get_tokens(uri) || [] interpolation_context = fstring_interpolation_token_context(tokens, lsp_line, lsp_char) return interpolation_context if interpolation_context token = @workspace.find_token_at(uri, lsp_line, lsp_char) return nil unless token return nil if (token) { token: token, tokens: tokens, token_index: tokens.index(token), } end |
#type_hover_signature(name, type) ⇒ Object
1578 1579 1580 1581 1582 1583 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1578 def type_hover_signature(name, type) rendered_type = type.to_s return "type #{name}" if rendered_type == name "type #{name} = #{rendered_type}" end |
#type_parameter_hover_signature(tokens, index) ⇒ Object
Hover content for an identifier sitting in a [...] type-parameter or
type-argument position: struct Pair[A, B]:, ref[T], Result[T, E],
and generic value parameters [N: int]. Returns nil when the brackets
belong to something else (attribute targets, array indices).
1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1705 def type_parameter_hover_signature(tokens, index) return nil unless index && tokens[index]&.type == :identifier opener = enclosing_bracket_opener_index(tokens, index, :lbracket, :rbracket) return nil unless opener before_opener = previous_non_trivia_token_index(tokens, opener) return nil if before_opener && tokens[before_opener].type == :attribute token = tokens[index] next_index = next_non_trivia_token_index(tokens, index + 1) prev_index = previous_non_trivia_token_index(tokens, index) if next_index && tokens[next_index].type == :colon # Generic value parameter declaration: `[N: int]`. value_type = next_non_trivia_token_index(tokens, next_index + 1) value_type_text = value_type ? tokens[value_type].lexeme : nil type_text = value_type_text ? ": #{value_type_text}" : "" return "generic value parameter #{token.lexeme}#{type_text}" end if prev_index && [:lbracket, :comma].include?(tokens[prev_index].type) && next_index && tokens[next_index].type == :implements # Constrained declaration: `[T implements Damageable]`. return "type parameter #{token.lexeme}" end if prev_index && [:lbracket, :comma].include?(tokens[prev_index].type) && next_index && [:rbracket, :comma].include?(tokens[next_index].type) return "type parameter #{token.lexeme}" end nil end |
#value_hover_signature(binding) ⇒ Object
1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1980 def value_hover_signature(binding) case binding.kind when :const "const #{binding.name}: #{binding.type} (immutable)" when :var "var #{binding.name}: #{binding.type} (mutable)" when :let "let #{binding.name}: #{binding.type} (immutable)" when :param "parameter #{binding.name}: #{binding.type} (immutable)" when :local suffix = binding.mutable ? 'mutable' : 'immutable' "local #{binding.name}: #{binding.type} (#{suffix})" else "#{binding.name}: #{binding.type}" end end |
#variant_arm_constructor_info(facts, tokens, token_index) ⇒ Object
1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 |
# File 'lib/milk_tea/lsp/server/hover.rb', line 1313 def variant_arm_constructor_info(facts, tokens, token_index) opener_index = parameter_list_opener_index(tokens, token_index) return nil unless opener_index head_index = previous_non_trivia_token_index(tokens, opener_index) return nil unless head_index && tokens[head_index].type == :identifier arm_name = tokens[head_index].lexeme # Walk back over `.`, optional generic args `[...]`, to the type name. idx = head_index - 1 idx = previous_non_trivia_token_index(tokens, idx) if idx >= 0 && tokens[idx].type == :dot if idx >= 0 && tokens[idx].type == :rbracket lbracket = matching_opener_index(tokens, idx) return nil unless lbracket idx = previous_non_trivia_token_index(tokens, lbracket) end return nil unless idx && tokens[idx].type == :identifier type_name = tokens[idx].lexeme type = resolve_arm_receiver_type(facts, tokens, idx) arm = if type.respond_to?(:arm) type.arm(arm_name) elsif type.respond_to?(:arms) type.arms[arm_name] end return nil unless arm { arm_name: arm_name, fields: arm } end |