1use std::{
2 fs,
3 io::{
4 self,
5 ErrorKind
6 },
7 path::PathBuf
8};
9
10use ra_ap_ide_db::EditionedFileId;
11use ra_ap_project_model::{
12 CargoConfig,
13 ProjectWorkspace,
14 ProjectManifest,
15};
16
17use ra_ap_ide::{
18 Analysis,
19 AnalysisHost,
20 TextSize,
21};
22
23use ra_ap_syntax::{
24 algo, ast::HasName, AstNode, SourceFile
25};
26
27use ra_ap_hir::Semantics;
28
29use ra_ap_ide_assists::Assist;
30
31use ra_ap_vfs::AbsPathBuf;
32
33use crate::{
34 error::ExtractionError,
35 extraction_utils::{
36 apply_extract_function,
37 convert_to_abs_path_buf,
38 filter_extract_function_assist,
39 get_assists,
40 get_cargo_config,
41 get_cargo_toml,
42 get_manifest_dir,
43 load_project_manifest,
44 load_project_workspace,
45 load_workspace_data,
46 run_analysis,
47 check_braces,
48 check_comment,
49 trim_range,
50 generate_frange,
51 },
52};
53
54#[derive(Debug, PartialEq, Clone)]
55pub struct ExtractionInput {
56 pub file_path: String,
57 pub new_fn_name: String,
58 pub start_idx: u32,
59 pub end_idx: u32,
60}
61
62impl ExtractionInput {
63 pub fn new(
64 file_path: &str,
65 new_fn_name: &str,
66 start_idx: u32,
67 end_idx: u32,
68 ) -> Self { ExtractionInput {
69 file_path: file_path.to_string(),
70 new_fn_name: new_fn_name.to_string(),
71 start_idx,
72 end_idx,
73 }
74 }
75
76 #[allow(dead_code)]
77 pub fn new_absolute(
78 file_path: &str,
79 new_fn_name: &str,
80 start_idx: u32,
81 end_idx: u32,
82 ) -> Self { ExtractionInput {
83 file_path: convert_to_abs_path_buf(file_path).unwrap().as_str().to_string(),
84 new_fn_name: new_fn_name.to_string(),
85 start_idx,
86 end_idx,
87 }
88 }
89}
90
91fn check_file_exists(file_path: &str) -> Result<(), ExtractionError> {
97 if fs::metadata(file_path).is_err() {
98 return Err(ExtractionError::Io(io::Error::new(
99 ErrorKind::NotFound,
100 format!("File not found: {}", file_path),
101 )));
102 }
103 Ok(())
104}
105
106fn check_idx(input: &ExtractionInput) -> Result<(), ExtractionError> {
108 if input.start_idx == input.end_idx {
109 return Err(ExtractionError::SameIdx);
110 } else if input.start_idx > input.end_idx {
111 return Err(ExtractionError::InvalidIdxPair);
112 }
113 if input.start_idx == 0 {
114 return Err(ExtractionError::InvalidStartIdx);
115 }
116 if input.end_idx == 0 {
117 return Err(ExtractionError::InvalidEndIdx);
118 }
119 Ok(())
120}
121
122fn verify_input(input: &ExtractionInput) -> Result<(), ExtractionError> {
123 check_file_exists(&input.file_path)?;
125 check_idx(input)?;
126
127 Ok(())
128}
129
130pub fn extract_method(input: ExtractionInput) -> Result<(String, String), ExtractionError> {
138
139 let input_path: &str = &input.file_path;
141 let callee_name: &str = &input.new_fn_name;
142 let start_idx: u32 = input.start_idx;
143 let end_idx: u32 = input.end_idx;
144
145 let input_abs_path: AbsPathBuf = convert_to_abs_path_buf(input_path).unwrap();
147
148 verify_input(&input)?;
150
151 let manifest_dir: PathBuf = get_manifest_dir(
152 &PathBuf::from(input_abs_path.as_str())
153 )?;
154 let cargo_toml: AbsPathBuf = get_cargo_toml( &manifest_dir );
155 let project_manifest: ProjectManifest = load_project_manifest( &cargo_toml );
158 let cargo_config: CargoConfig = get_cargo_config( &project_manifest );
161 let workspace: ProjectWorkspace = load_project_workspace( &project_manifest, &cargo_config );
164 let (db, vfs) = load_workspace_data(workspace, &cargo_config);
167
168 let range_: (u32, u32) = (
170 start_idx,
171 end_idx,
172 );
173
174 let sema: Semantics<'_, ra_ap_ide::RootDatabase> = Semantics::new( &db );
180 let frange_: ra_ap_hir::FileRangeWrapper<ra_ap_vfs::FileId> = generate_frange( &input_abs_path, &vfs, range_.clone() );
181 let edition: EditionedFileId = EditionedFileId::current_edition( frange_.file_id );
182 let source_file: SourceFile = sema.parse( edition );
183 let range: (u32, u32) = trim_range( &source_file, &range_ );
184 check_comment( &source_file, &range )?;
185 check_braces( &source_file, &range )?;
186
187 let analysis_host: AnalysisHost = AnalysisHost::with_database( db );
188 let analysis: Analysis = run_analysis( analysis_host );
189
190 let assists: Vec<Assist> = get_assists( &analysis, &vfs, &input_abs_path, range );
191 let assist: Assist = filter_extract_function_assist( assists )?;
192
193
194 let modified_code: String = apply_extract_function(
195 &assist,
196 &input_abs_path,
197 &vfs,
198 &callee_name,
199 )?;
200
201 let parent_method: String = parent_method(
202 &source_file,
203 range,
204 )?;
205
206 Ok( (modified_code, parent_method) )
207}
208
209pub fn parent_method(
213 source_file: &SourceFile,
214 range: (u32, u32),
215) -> Result<String, ExtractionError> {
216 let start: TextSize = TextSize::new(range.0);
217
218 let node: Option<ra_ap_syntax::ast::Fn> = algo::find_node_at_offset::<ra_ap_syntax::ast::Fn>(
220 source_file.syntax(),
221 start,
222 );
223
224 let fn_name: String = match node {
225 Some(n) => n.name().map_or("".to_string(), |name| name.text().to_string()),
226 None => "".to_string(),
227 };
228
229 if fn_name.is_empty() {
230 return Err(ExtractionError::ParentMethodNotFound);
231 }
232
233 Ok( fn_name.trim().to_string() )
234
235}