Resolve a deferred "Extract Constant" code action by computing the full workspace edit. Called from `resolve_code_action` when `action_kind` is `"refactor.extractConstant"` or `"refactor.extractConstantAll"`.
(
&self,
data: &CodeActionData,
content: &str,
)
| 727 | /// Called from `resolve_code_action` when `action_kind` is |
| 728 | /// `"refactor.extractConstant"` or `"refactor.extractConstantAll"`. |
| 729 | pub(crate) fn resolve_extract_constant( |
| 730 | &self, |
| 731 | data: &CodeActionData, |
| 732 | content: &str, |
| 733 | ) -> Option<WorkspaceEdit> { |
| 734 | let all_occurrences = data |
| 735 | .extra |
| 736 | .get("all_occurrences") |
| 737 | .and_then(|v| v.as_bool()) |
| 738 | .unwrap_or(data.action_kind == "refactor.extractConstantAll"); |
| 739 | |
| 740 | let start_offset = position_to_byte_offset(content, data.range.start); |
| 741 | let end_offset = position_to_byte_offset(content, data.range.end); |
| 742 | |
| 743 | if start_offset >= end_offset || end_offset > content.len() { |
| 744 | return None; |
| 745 | } |
| 746 | |
| 747 | let selected_text = &content[start_offset..end_offset]; |
| 748 | let trimmed = selected_text.trim(); |
| 749 | |
| 750 | if trimmed.is_empty() || !is_extractable_literal(trimmed) { |
| 751 | return None; |
| 752 | } |
| 753 | |
| 754 | // Find class body information. |
| 755 | let class_info = find_class_body_info(content, start_offset as u32)?; |
| 756 | |
| 757 | // Generate constant name and deduplicate. |
| 758 | let base_name = generate_constant_name(trimmed); |
| 759 | let const_name = deduplicate_constant_name(&base_name, &class_info.existing_constants); |
| 760 | |
| 761 | let visibility = class_info.context_visibility; |
| 762 | let indent = detect_member_indent(content, class_info.body_start); |
| 763 | let php_version = self.php_version(); |
| 764 | |
| 765 | // Determine insertion point. |
| 766 | let insert_offset = if let Some(after_const) = class_info.after_last_constant { |
| 767 | // Insert after the last constant. Find the next newline. |
| 768 | let rest = &content[after_const..]; |
| 769 | if let Some(nl) = rest.find('\n') { |
| 770 | after_const + nl + 1 |
| 771 | } else { |
| 772 | after_const |
| 773 | } |
| 774 | } else { |
| 775 | // No existing constants — insert at the top of the class body. |
| 776 | // Find the first newline after the opening brace. |
| 777 | let rest = &content[class_info.body_start..]; |
| 778 | if let Some(nl) = rest.find('\n') { |
| 779 | class_info.body_start + nl + 1 |
| 780 | } else { |
| 781 | class_info.body_start + 1 |
| 782 | } |
| 783 | }; |
| 784 | |
| 785 | // Build the constant declaration with optional type annotation. |
| 786 | let type_name = literal_type_name(trimmed); |