Extract the function/method body text that follows the docblock at the cursor position. Returns the text between the opening `{` and matching closing `}` of the function/method declaration. Returns `None` if the body cannot be located (e.g. abstract method, or the docblock is not followed by a function).
(content: &str, position: Position)
| 698 | /// be located (e.g. abstract method, or the docblock is not followed by |
| 699 | /// a function). |
| 700 | pub(crate) fn extract_function_body(content: &str, position: Position) -> Option<String> { |
| 701 | let after_docblock = { |
| 702 | let byte_offset = position_to_byte_offset(content, position); |
| 703 | let after_cursor = &content[byte_offset.min(content.len())..]; |
| 704 | |
| 705 | if let Some(close_pos) = after_cursor.find("*/") { |
| 706 | after_cursor[close_pos + 2..].to_string() |
| 707 | } else { |
| 708 | after_cursor.to_string() |
| 709 | } |
| 710 | }; |
| 711 | |
| 712 | // Find the `function` keyword to confirm this is a function/method. |
| 713 | let func_idx = { |
| 714 | let lower = after_docblock.to_lowercase(); |
| 715 | let mut start = 0; |
| 716 | let mut found = None; |
| 717 | while let Some(pos) = lower[start..].find("function") { |
| 718 | let abs = start + pos; |
| 719 | let before_ok = abs == 0 || !after_docblock.as_bytes()[abs - 1].is_ascii_alphanumeric(); |
| 720 | let after_pos = abs + 8; // "function".len() |
| 721 | let after_ok = after_pos >= after_docblock.len() |
| 722 | || !after_docblock.as_bytes()[after_pos].is_ascii_alphanumeric(); |
| 723 | if before_ok && after_ok { |
| 724 | found = Some(abs); |
| 725 | break; |
| 726 | } |
| 727 | start = abs + 8; |
| 728 | } |
| 729 | found? |
| 730 | }; |
| 731 | |
| 732 | let after_func = &after_docblock[func_idx..]; |
| 733 | |
| 734 | // Find the opening brace of the function body. |
| 735 | let open_brace = after_func.find('{')?; |
| 736 | let body_start = open_brace + 1; |
| 737 | |
| 738 | // Walk forward to find the matching closing brace. |
| 739 | let mut depth = 1u32; |
| 740 | let mut pos = body_start; |
| 741 | let bytes = after_func.as_bytes(); |
| 742 | // Track whether we are inside a string literal to avoid counting |
| 743 | // braces inside strings. |
| 744 | let mut in_single_quote = false; |
| 745 | let mut in_double_quote = false; |
| 746 | while pos < bytes.len() && depth > 0 { |
| 747 | let b = bytes[pos]; |
| 748 | if in_single_quote { |
| 749 | if b == b'\\' { |
| 750 | pos += 1; // skip escaped char |
| 751 | } else if b == b'\'' { |
| 752 | in_single_quote = false; |
| 753 | } |
| 754 | } else if in_double_quote { |
| 755 | if b == b'\\' { |
| 756 | pos += 1; // skip escaped char |
| 757 | } else if b == b'"' { |