windjammer-lsp 0.45.0

Language Server Protocol implementation for Windjammer
Documentation
//! Code refactoring and transformation capabilities
//!
//! This module provides advanced refactoring tools that make it easy to safely
//! transform code while maintaining correctness. All refactorings are:
//! - Type-aware: Understand the code structure and types
//! - Safe: Won't break existing code
//! - Preview-able: Show changes before applying
//! - Undo-able: Via LSP workspace edits

pub mod add_mut;
pub mod ast_utils;
pub mod batch;
pub mod change_signature;
pub mod extract_function;
pub mod inline;
pub mod introduce_variable;
pub mod move_item;
pub mod preview;
pub mod scope_analyzer;

use crate::database::WindjammerDatabase;
use tower_lsp::lsp_types::*;

/// Main refactoring engine that coordinates all refactoring operations
pub struct RefactoringEngine<'a> {
    db: &'a WindjammerDatabase,
}

impl<'a> RefactoringEngine<'a> {
    /// Create a new refactoring engine
    pub fn new(db: &'a WindjammerDatabase) -> Self {
        Self { db }
    }

    /// Get the database (for use by refactoring submodules)
    #[allow(dead_code)]
    pub fn db(&self) -> &WindjammerDatabase {
        self.db
    }

    /// Generate code actions for a given text position
    pub fn code_actions(
        &self,
        uri: &Url,
        range: Range,
        context: &CodeActionContext,
        source: Option<&str>,
    ) -> Vec<CodeActionOrCommand> {
        let mut actions = Vec::new();

        // Add `mut` quick-fix - check diagnostics for mutability errors
        if let Some(source) = source {
            for diagnostic in &context.diagnostics {
                if let Some(action) = self.add_mut_action(uri, diagnostic, source) {
                    actions.push(CodeActionOrCommand::CodeAction(action));
                }
            }
        }

        // Extract Function - only show if selection is non-empty
        if range.start != range.end {
            if let Some(action) = self.extract_function_action(uri, range) {
                actions.push(CodeActionOrCommand::CodeAction(action));
            }
        }

        // Inline Variable - show if cursor is on a variable definition
        if let Some(action) = self.inline_variable_action(uri, range.start) {
            actions.push(CodeActionOrCommand::CodeAction(action));
        }

        // Introduce Variable - show if selection is an expression
        if range.start != range.end {
            if let Some(action) = self.introduce_variable_action(uri, range) {
                actions.push(CodeActionOrCommand::CodeAction(action));
            }
        }

        actions
    }

    /// Create "Add mut" quick-fix code action
    fn add_mut_action(
        &self,
        uri: &Url,
        diagnostic: &Diagnostic,
        source: &str,
    ) -> Option<CodeAction> {
        // Check if this is a mutability error
        let message = &diagnostic.message;
        if !message.contains("immutable") && !message.contains("cannot assign") {
            return None;
        }

        // Extract variable name from error message
        // Format: "cannot assign twice to immutable variable `x`"
        let var_name = if let Some(start) = message.find('`') {
            if let Some(end) = message[start + 1..].find('`') {
                message[start + 1..start + 1 + end].to_string()
            } else {
                return None;
            }
        } else {
            return None;
        };

        let fix =
            add_mut::AddMutFix::new(uri.clone(), var_name.clone(), diagnostic.range.start.line);

        // Generate the workspace edit directly
        let edit = fix.execute(source).ok()?;

        Some(CodeAction {
            title: format!("Add `mut` to `{}`", var_name),
            kind: Some(CodeActionKind::QUICKFIX),
            diagnostics: Some(vec![diagnostic.clone()]),
            edit: Some(edit),
            command: None,
            is_preferred: Some(true),
            disabled: None,
            data: None,
        })
    }

    /// Create "Extract Function" code action
    fn extract_function_action(&self, uri: &Url, range: Range) -> Option<CodeAction> {
        // TODO: Check if selection is valid for extraction
        // For now, always offer the action if there's a selection

        Some(CodeAction {
            title: "Extract Function".to_string(),
            kind: Some(CodeActionKind::REFACTOR_EXTRACT),
            diagnostics: None,
            edit: None, // Will be computed when user accepts
            command: Some(Command {
                title: "Extract Function".to_string(),
                command: "windjammer.refactor.extractFunction".to_string(),
                arguments: Some(vec![
                    serde_json::to_value(uri).ok()?,
                    serde_json::to_value(range).ok()?,
                ]),
            }),
            is_preferred: Some(true),
            disabled: None,
            data: None,
        })
    }

    /// Create "Inline Variable" code action
    fn inline_variable_action(&self, _uri: &Url, _position: Position) -> Option<CodeAction> {
        // TODO: Check if position is on a variable definition
        // For now, return None until implemented
        None
    }

    /// Create "Introduce Variable" code action
    fn introduce_variable_action(&self, _uri: &Url, _range: Range) -> Option<CodeAction> {
        // TODO: Check if selection is an expression
        // For now, return None until implemented
        None
    }

    /// Execute "Extract Function" refactoring
    /// Execute "Extract Function" refactoring (called via LSP command)
    #[allow(dead_code)]
    pub fn execute_extract_function(
        &self,
        uri: &Url,
        range: Range,
        function_name: &str,
        source: &str,
    ) -> Result<WorkspaceEdit, String> {
        let extractor = extract_function::ExtractFunction::new(self.db, uri.clone(), range);
        extractor.execute(function_name, source)
    }

    /// Execute "Inline Variable" refactoring
    /// Execute "Inline Variable" refactoring (called via LSP command)
    #[allow(dead_code)]
    pub fn execute_inline_variable(
        &self,
        uri: &Url,
        position: Position,
        source: &str,
    ) -> Result<WorkspaceEdit, String> {
        let inliner = inline::InlineRefactoring::new(self.db, uri.clone(), position);
        inliner.execute(source)
    }

    /// Execute "Introduce Variable" refactoring
    /// Execute "Introduce Variable" refactoring (called via LSP command)
    #[allow(dead_code)]
    pub fn execute_introduce_variable(
        &self,
        uri: &Url,
        range: Range,
        variable_name: &str,
        source: &str,
    ) -> Result<WorkspaceEdit, String> {
        let introducer = introduce_variable::IntroduceVariable::new(self.db, uri.clone(), range);
        introducer.execute(variable_name, source)
    }

    /// Execute "Change Signature" refactoring
    /// Execute "Change Signature" refactoring (called via LSP command)
    #[allow(dead_code)]
    pub fn execute_change_signature(
        &self,
        uri: &Url,
        position: Position,
        changes: &[change_signature::ParameterChange],
        source: &str,
    ) -> Result<WorkspaceEdit, String> {
        let changer = change_signature::ChangeSignature::new(self.db, uri.clone(), position);
        changer.execute(changes, source)
    }

    /// Execute "Move Item" refactoring
    /// Execute "Move Item" refactoring (called via LSP command)
    #[allow(dead_code)]
    pub fn execute_move_item(
        &self,
        source_uri: &Url,
        target_uri: &Url,
        position: Position,
        source_content: &str,
        target_content: &str,
    ) -> Result<WorkspaceEdit, String> {
        let mover =
            move_item::MoveItem::new(self.db, source_uri.clone(), target_uri.clone(), position);
        mover.execute(source_content, target_content)
    }

    /// Execute "Add mut" quick-fix (called via LSP command)
    #[allow(dead_code)]
    pub fn execute_add_mut(
        &self,
        uri: &Url,
        variable_name: &str,
        line: u32,
        source: &str,
    ) -> Result<WorkspaceEdit, String> {
        let fix = add_mut::AddMutFix::new(uri.clone(), variable_name.to_string(), line);
        fix.execute(source)
    }
}

/// Result of a refactoring operation (for future preview functionality)
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct RefactoringResult {
    /// The workspace edit to apply
    pub edit: WorkspaceEdit,
    /// Preview text to show the user
    pub preview: String,
    /// Success message
    pub message: String,
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_refactoring_engine_creation() {
        // Basic smoke test - will expand as we implement features
        // For now, just verify the module compiles
    }
}