Skip to content

refactor: use trait instead of macro for commands #388

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 82 additions & 76 deletions crates/emmylua_ls/src/handlers/command/commands/emmy_auto_require.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,88 +10,94 @@ use crate::{
util::{module_name_convert, time_cancel_token},
};

pub const COMMAND: &str = "emmy.auto.require";

pub async fn handle(context: ServerContextSnapshot, args: Vec<Value>) -> Option<()> {
let add_to: FileId = serde_json::from_value(args.get(0)?.clone()).ok()?;
let need_require_file_id: FileId = serde_json::from_value(args.get(1)?.clone()).ok()?;
let position: Position = serde_json::from_value(args.get(2)?.clone()).ok()?;

let analysis = context.analysis.read().await;
let semantic_model = analysis.compilation.get_semantic_model(add_to)?;
let module_info = semantic_model
.get_db()
.get_module_index()
.get_module(need_require_file_id)?;
let emmyrc = semantic_model.get_emmyrc();
let require_like_func = &emmyrc.runtime.require_like_function;
let auto_require_func = emmyrc.completion.auto_require_function.clone();
let file_conversion = emmyrc.completion.auto_require_naming_convention;
let local_name = module_name_convert(&module_info.name, file_conversion);
let require_str = format!(
"local {} = {}(\"{}\")",
local_name, auto_require_func, module_info.full_module_name
);
let document = semantic_model.get_document();
let offset = document.get_offset(position.line as usize, position.character as usize)?;
let root_block = semantic_model.get_root().get_block()?;
let mut last_require_stat: Option<LuaStat> = None;
for stat in root_block.get_stats() {
if stat.get_position() > offset {
break;
}
use super::CommandSpec;

pub struct AutoRequireCommand;

impl CommandSpec for AutoRequireCommand {
const COMMAND: &str = "emmy.auto.require";

async fn handle(context: ServerContextSnapshot, args: Vec<Value>) -> Option<()> {
let add_to: FileId = serde_json::from_value(args.get(0)?.clone()).ok()?;
let need_require_file_id: FileId = serde_json::from_value(args.get(1)?.clone()).ok()?;
let position: Position = serde_json::from_value(args.get(2)?.clone()).ok()?;

let analysis = context.analysis.read().await;
let semantic_model = analysis.compilation.get_semantic_model(add_to)?;
let module_info = semantic_model
.get_db()
.get_module_index()
.get_module(need_require_file_id)?;
let emmyrc = semantic_model.get_emmyrc();
let require_like_func = &emmyrc.runtime.require_like_function;
let auto_require_func = emmyrc.completion.auto_require_function.clone();
let file_conversion = emmyrc.completion.auto_require_naming_convention;
let local_name = module_name_convert(&module_info.name, file_conversion);
let require_str = format!(
"local {} = {}(\"{}\")",
local_name, auto_require_func, module_info.full_module_name
);
let document = semantic_model.get_document();
let offset = document.get_offset(position.line as usize, position.character as usize)?;
let root_block = semantic_model.get_root().get_block()?;
let mut last_require_stat: Option<LuaStat> = None;
for stat in root_block.get_stats() {
if stat.get_position() > offset {
break;
}

if is_require_stat(stat.clone(), &require_like_func).unwrap_or(false) {
last_require_stat = Some(stat);
if is_require_stat(stat.clone(), &require_like_func).unwrap_or(false) {
last_require_stat = Some(stat);
}
}
}

let line = if let Some(last_require_stat) = last_require_stat {
let last_require_stat_end = last_require_stat.get_range().end();
document.get_line(last_require_stat_end)? + 1
} else {
0
};

let text_edit = TextEdit {
range: lsp_types::Range {
start: Position {
line: line as u32,
character: 0,
let line = if let Some(last_require_stat) = last_require_stat {
let last_require_stat_end = last_require_stat.get_range().end();
document.get_line(last_require_stat_end)? + 1
} else {
0
};

let text_edit = TextEdit {
range: lsp_types::Range {
start: Position {
line: line as u32,
character: 0,
},
end: Position {
line: line as u32,
character: 0,
},
},
end: Position {
line: line as u32,
character: 0,
new_text: format!("{}\n", require_str),
};

let uri = document.get_uri();
let mut changes = HashMap::new();
changes.insert(uri.clone(), vec![text_edit.clone()]);

let client = context.client;
let cancel_token = time_cancel_token(Duration::from_secs(5));
let apply_edit_params = ApplyWorkspaceEditParams {
label: None,
edit: WorkspaceEdit {
changes: Some(changes),
document_changes: None,
change_annotations: None,
},
},
new_text: format!("{}\n", require_str),
};

let uri = document.get_uri();
let mut changes = HashMap::new();
changes.insert(uri.clone(), vec![text_edit.clone()]);

let client = context.client;
let cancel_token = time_cancel_token(Duration::from_secs(5));
let apply_edit_params = ApplyWorkspaceEditParams {
label: None,
edit: WorkspaceEdit {
changes: Some(changes),
document_changes: None,
change_annotations: None,
},
};

tokio::spawn(async move {
let res = client.apply_edit(apply_edit_params, cancel_token).await;
if let Some(res) = res {
if !res.applied {
log::error!("Failed to apply edit: {:?}", res.failure_reason);
};

tokio::spawn(async move {
let res = client.apply_edit(apply_edit_params, cancel_token).await;
if let Some(res) = res {
if !res.applied {
log::error!("Failed to apply edit: {:?}", res.failure_reason);
}
}
}
});
});

Some(())
Some(())
}
}

fn is_require_stat(stat: LuaStat, require_like_func: &Vec<String>) -> Option<bool> {
Expand Down Expand Up @@ -152,7 +158,7 @@ pub fn make_auto_require(

Command {
title: title.to_string(),
command: COMMAND.to_string(),
command: AutoRequireCommand::COMMAND.to_string(),
arguments: Some(args),
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use tokio::sync::RwLock;

use crate::context::{ServerContextSnapshot, WorkspaceManager};

pub const COMMAND: &str = "emmy.disable.code";
use super::CommandSpec;

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum DisableAction {
Expand All @@ -17,18 +18,24 @@ pub enum DisableAction {
DisableProject,
}

pub async fn handle(context: ServerContextSnapshot, args: Vec<Value>) -> Option<()> {
let action: DisableAction = serde_json::from_value(args.get(0)?.clone()).ok()?;
let code: DiagnosticCode = serde_json::from_value(args.get(3)?.clone()).ok()?;
pub struct DisableCodeCommand;

impl CommandSpec for DisableCodeCommand {
const COMMAND: &str = "emmy.disable.code";

async fn handle(context: ServerContextSnapshot, args: Vec<Value>) -> Option<()> {
let action: DisableAction = serde_json::from_value(args.get(0)?.clone()).ok()?;
let code: DiagnosticCode = serde_json::from_value(args.get(3)?.clone()).ok()?;

match action {
DisableAction::DisableProject => {
add_disable_project(context.workspace_manager, code).await;
match action {
DisableAction::DisableProject => {
add_disable_project(context.workspace_manager, code).await;
}
_ => {}
}
_ => {}
}

Some(())
Some(())
}
}

pub fn make_disable_code_command(
Expand All @@ -47,7 +54,7 @@ pub fn make_disable_code_command(

Command {
title: title.to_string(),
command: COMMAND.to_string(),
command: DisableCodeCommand::COMMAND.to_string(),
arguments: Some(args),
}
}
Expand Down
14 changes: 10 additions & 4 deletions crates/emmylua_ls/src/handlers/command/commands/emmy_fix_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@ use serde_json::Value;

use crate::context::ServerContextSnapshot;

pub const COMMAND: &str = "emmy.fix.format";
use super::CommandSpec;

#[allow(unused)]
pub async fn handle(context: ServerContextSnapshot, args: Vec<Value>) -> Option<()> {
Some(())
pub struct FixFormatCommand;

impl CommandSpec for FixFormatCommand {
const COMMAND: &str = "emmy.fix.format";

#[allow(unused)]
async fn handle(context: ServerContextSnapshot, args: Vec<Value>) -> Option<()> {
Some(())
}
}
58 changes: 24 additions & 34 deletions crates/emmylua_ls/src/handlers/command/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
use std::sync::LazyLock;

use emmy_auto_require::AutoRequireCommand;
use emmy_disable_code::DisableCodeCommand;
use emmy_fix_format::FixFormatCommand;
use serde_json::Value;

use crate::context::ServerContextSnapshot;
Expand All @@ -9,48 +14,33 @@ mod emmy_fix_format;
pub use emmy_auto_require::make_auto_require;
pub use emmy_disable_code::{make_disable_code_command, DisableAction};

pub fn get_commands_list() -> Vec<String> {
let mut commands = Vec::new();
macro_rules! command_from {
($($module:ident),*) => {
$(
let command_str = $module::COMMAND.to_string();
commands.push(command_str);
)*
};
}

command_from!(emmy_auto_require);
command_from!(emmy_disable_code);
command_from!(emmy_fix_format);
pub trait CommandSpec {
const COMMAND: &str;

commands
async fn handle(context: ServerContextSnapshot, args: Vec<Value>) -> Option<()>;
}

macro_rules! command_dispatch {
($cmd_name:expr, $context:expr, $args:expr, [ $( $module:ident ),+ ]) => {
match $cmd_name {
$(
$module::COMMAND => {
$module::handle($context, $args).await;
}
)+
_ => {}
}
};
static COMMANDS: LazyLock<Vec<String>> = LazyLock::new(|| {
vec![
AutoRequireCommand::COMMAND.to_string(),
DisableCodeCommand::COMMAND.to_string(),
FixFormatCommand::COMMAND.to_string(),
]
});

pub fn get_commands_list() -> Vec<String> {
COMMANDS.clone()
}

pub async fn dispatch_command(
context: ServerContextSnapshot,
command_name: &str,
args: Vec<Value>,
) -> Option<()> {
command_dispatch!(
command_name,
context,
args,
[emmy_auto_require, emmy_disable_code, emmy_fix_format]
);

Some(())
match command_name {
AutoRequireCommand::COMMAND => AutoRequireCommand::handle(context, args).await,
DisableCodeCommand::COMMAND => DisableCodeCommand::handle(context, args).await,
FixFormatCommand::COMMAND => FixFormatCommand::handle(context, args).await,
_ => Some(()),
}
}