use std::{
fs,
io::{
self,
ErrorKind
},
path::PathBuf
};
use ra_ap_ide_db::EditionedFileId;
use ra_ap_project_model::{
CargoConfig,
ProjectWorkspace,
ProjectManifest,
};
use ra_ap_ide::{
Analysis,
AnalysisHost,
};
use ra_ap_hir::Semantics;
use ra_ap_ide_assists::Assist;
use ra_ap_vfs::AbsPathBuf;
use crate::{
error::ExtractionError,
extraction_utils::{
apply_extract_function,
convert_to_abs_path_buf,
filter_extract_function_assist,
get_assists,
get_cargo_config,
get_cargo_toml,
get_manifest_dir,
load_project_manifest,
load_project_workspace,
load_workspace_data,
run_analysis,
check_braces,
check_comment,
trim_range,
generate_frange,
},
};
#[derive(Debug, PartialEq, Clone)]
pub struct ExtractionInput {
pub file_path: String,
pub output_path: String,
pub new_fn_name: String,
pub start_idx: u32,
pub end_idx: u32,
}
impl ExtractionInput {
pub fn new(
file_path: &str,
output_path: &str,
new_fn_name: &str,
start_idx: u32,
end_idx: u32,
) -> Self { ExtractionInput {
file_path: file_path.to_string(),
output_path: output_path.to_string(),
new_fn_name: new_fn_name.to_string(),
start_idx,
end_idx,
}
}
#[allow(dead_code)]
pub fn new_absolute(
file_path: &str,
output_path: &str,
new_fn_name: &str,
start_idx: u32,
end_idx: u32,
) -> Self { ExtractionInput {
file_path: convert_to_abs_path_buf(file_path).unwrap().as_str().to_string(),
output_path: convert_to_abs_path_buf(output_path).unwrap().as_str().to_string(),
new_fn_name: new_fn_name.to_string(),
start_idx,
end_idx,
}
}
}
fn check_file_exists(file_path: &str) -> Result<(), ExtractionError> {
if fs::metadata(file_path).is_err() {
return Err(ExtractionError::Io(io::Error::new(
ErrorKind::NotFound,
format!("File not found: {}", file_path),
)));
}
Ok(())
}
fn input_output_not_same(input: &ExtractionInput) -> Result<(), ExtractionError> {
if input.file_path == input.output_path {
return Err(ExtractionError::Io(io::Error::new(
ErrorKind::InvalidInput,
"Input and output files cannot be the same",
)));
}
Ok(())
}
fn check_idx(input: &ExtractionInput) -> Result<(), ExtractionError> {
if input.start_idx == input.end_idx {
return Err(ExtractionError::SameIdx);
} else if input.start_idx > input.end_idx {
return Err(ExtractionError::InvalidIdxPair);
}
if input.start_idx == 0 {
return Err(ExtractionError::InvalidStartIdx);
}
if input.end_idx == 0 {
return Err(ExtractionError::InvalidEndIdx);
}
Ok(())
}
fn verify_input(input: &ExtractionInput) -> Result<(), ExtractionError> {
check_file_exists(&input.file_path)?;
input_output_not_same(&input)?;
check_idx(input)?;
Ok(())
}
pub fn extract_method(input: ExtractionInput) -> Result<PathBuf, ExtractionError> {
let input_path: &str = &input.file_path;
let output_path: &str = &input.output_path;
let callee_name: &str = &input.new_fn_name;
let start_idx: u32 = input.start_idx;
let end_idx: u32 = input.end_idx;
let input_abs_path: AbsPathBuf = convert_to_abs_path_buf(input_path).unwrap();
let output_abs_path: AbsPathBuf = convert_to_abs_path_buf(output_path).unwrap();
verify_input(&input)?;
let manifest_dir: PathBuf = get_manifest_dir(
&PathBuf::from(input_abs_path.as_str())
)?;
let cargo_toml: AbsPathBuf = get_cargo_toml( &manifest_dir );
let project_manifest: ProjectManifest = load_project_manifest( &cargo_toml );
let cargo_config: CargoConfig = get_cargo_config( &project_manifest );
let workspace: ProjectWorkspace = load_project_workspace( &project_manifest, &cargo_config );
let (db, vfs) = load_workspace_data(workspace, &cargo_config);
let range_: (u32, u32) = (
start_idx,
end_idx,
);
let sema: Semantics<'_, ra_ap_ide::RootDatabase> = Semantics::new( &db );
let frange_ = generate_frange( &input_abs_path, &vfs, range_.clone() );
let edition = EditionedFileId::current_edition( frange_.file_id );
let source_file = sema.parse( edition );
let range: (u32, u32) = trim_range( &source_file, &range_ );
check_comment( &source_file, &range )?;
check_braces( &source_file, &range )?;
let analysis_host: AnalysisHost = AnalysisHost::with_database( db );
let analysis: Analysis = run_analysis( analysis_host );
let assists: Vec<Assist> = get_assists(&analysis, &vfs, &input_abs_path, range);
let assist: Assist = filter_extract_function_assist(assists)?;
apply_extract_function(
&assist,
&input_abs_path,
&output_abs_path,
&vfs,
&callee_name,
);
Ok( PathBuf::from(output_abs_path.as_str()) )
}