Skip to content

Commit 4a272c5

Browse files
move
1 parent 5458255 commit 4a272c5

File tree

4 files changed

+205
-174
lines changed

4 files changed

+205
-174
lines changed

pyrefly/lib/lsp/non_wasm/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub mod lsp;
1313
pub mod module_helpers;
1414
pub mod protocol;
1515
pub mod queue;
16+
pub mod refactors;
1617
pub mod server;
1718
pub mod stdlib;
1819
pub mod transaction_manager;
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
use std::ffi::OsStr;
9+
use std::fs;
10+
use std::path::Path;
11+
12+
use lsp_types::ClientCapabilities;
13+
use lsp_types::CodeAction;
14+
use lsp_types::CodeActionKind;
15+
use lsp_types::CodeActionOrCommand;
16+
use lsp_types::DeleteFile;
17+
use lsp_types::DeleteFileOptions;
18+
use lsp_types::DocumentChangeOperation;
19+
use lsp_types::DocumentChanges;
20+
use lsp_types::RenameFile;
21+
use lsp_types::ResourceOp;
22+
use lsp_types::ResourceOperationKind;
23+
use lsp_types::Url;
24+
use lsp_types::WorkspaceEdit;
25+
26+
fn supports_workspace_edit_document_changes(capabilities: &ClientCapabilities) -> bool {
27+
capabilities
28+
.workspace
29+
.as_ref()
30+
.and_then(|workspace| workspace.workspace_edit.as_ref())
31+
.and_then(|workspace_edit| workspace_edit.document_changes)
32+
.unwrap_or(false)
33+
}
34+
35+
fn supports_workspace_edit_resource_ops(
36+
capabilities: &ClientCapabilities,
37+
required: &[ResourceOperationKind],
38+
) -> bool {
39+
let supported = capabilities
40+
.workspace
41+
.as_ref()
42+
.and_then(|workspace| workspace.workspace_edit.as_ref())
43+
.and_then(|workspace_edit| workspace_edit.resource_operations.as_ref());
44+
required
45+
.iter()
46+
.all(|kind| supported.is_some_and(|ops| ops.contains(kind)))
47+
}
48+
49+
fn package_dir_is_empty(dir: &Path, init_file: &OsStr) -> bool {
50+
let entries = match fs::read_dir(dir) {
51+
Ok(entries) => entries,
52+
Err(_) => return false,
53+
};
54+
for entry in entries.flatten() {
55+
let name = entry.file_name();
56+
if name == init_file || name == OsStr::new("__pycache__") {
57+
continue;
58+
}
59+
return false;
60+
}
61+
true
62+
}
63+
64+
pub(crate) fn convert_module_package_code_actions(
65+
capabilities: &ClientCapabilities,
66+
uri: &Url,
67+
) -> Vec<CodeActionOrCommand> {
68+
if !supports_workspace_edit_document_changes(capabilities) {
69+
return Vec::new();
70+
}
71+
let path = match uri.to_file_path() {
72+
Ok(path) => path,
73+
Err(_) => return Vec::new(),
74+
};
75+
let extension = match path.extension().and_then(|ext| ext.to_str()) {
76+
Some(ext @ "py") | Some(ext @ "pyi") => ext,
77+
_ => return Vec::new(),
78+
};
79+
if !path.is_file() {
80+
return Vec::new();
81+
}
82+
let Some(file_stem) = path.file_stem().and_then(|stem| stem.to_str()) else {
83+
return Vec::new();
84+
};
85+
let mut actions = Vec::new();
86+
if file_stem == "__init__" {
87+
if !supports_workspace_edit_resource_ops(
88+
capabilities,
89+
&[ResourceOperationKind::Rename, ResourceOperationKind::Delete],
90+
) {
91+
return actions;
92+
}
93+
let Some(package_dir) = path.parent() else {
94+
return actions;
95+
};
96+
let Some(package_name) = package_dir.file_name().and_then(|name| name.to_str()) else {
97+
return actions;
98+
};
99+
let Some(parent_dir) = package_dir.parent() else {
100+
return actions;
101+
};
102+
let new_path = parent_dir.join(format!("{package_name}.{extension}"));
103+
if new_path.exists() {
104+
return actions;
105+
}
106+
let Some(init_name) = path.file_name() else {
107+
return actions;
108+
};
109+
if !package_dir_is_empty(package_dir, init_name) {
110+
return actions;
111+
}
112+
let old_uri = match Url::from_file_path(&path) {
113+
Ok(uri) => uri,
114+
Err(_) => return actions,
115+
};
116+
let new_uri = match Url::from_file_path(&new_path) {
117+
Ok(uri) => uri,
118+
Err(_) => return actions,
119+
};
120+
let package_uri = match Url::from_file_path(package_dir) {
121+
Ok(uri) => uri,
122+
Err(_) => return actions,
123+
};
124+
let operations = vec![
125+
DocumentChangeOperation::Op(ResourceOp::Rename(RenameFile {
126+
old_uri,
127+
new_uri,
128+
options: None,
129+
annotation_id: None,
130+
})),
131+
DocumentChangeOperation::Op(ResourceOp::Delete(DeleteFile {
132+
uri: package_uri,
133+
options: Some(DeleteFileOptions {
134+
recursive: Some(true),
135+
ignore_if_not_exists: Some(true),
136+
annotation_id: None,
137+
}),
138+
})),
139+
];
140+
actions.push(CodeActionOrCommand::CodeAction(CodeAction {
141+
title: "Convert package to module".to_owned(),
142+
kind: Some(CodeActionKind::new("refactor.move")),
143+
edit: Some(WorkspaceEdit {
144+
document_changes: Some(DocumentChanges::Operations(operations)),
145+
..Default::default()
146+
}),
147+
..Default::default()
148+
}));
149+
} else {
150+
if !supports_workspace_edit_resource_ops(capabilities, &[ResourceOperationKind::Rename]) {
151+
return actions;
152+
}
153+
let Some(parent_dir) = path.parent() else {
154+
return actions;
155+
};
156+
let package_dir = parent_dir.join(file_stem);
157+
if package_dir.exists() {
158+
return actions;
159+
}
160+
let new_path = package_dir.join(format!("__init__.{extension}"));
161+
if new_path.exists() {
162+
return actions;
163+
}
164+
let old_uri = match Url::from_file_path(&path) {
165+
Ok(uri) => uri,
166+
Err(_) => return actions,
167+
};
168+
let new_uri = match Url::from_file_path(&new_path) {
169+
Ok(uri) => uri,
170+
Err(_) => return actions,
171+
};
172+
let operations = vec![DocumentChangeOperation::Op(ResourceOp::Rename(
173+
RenameFile {
174+
old_uri,
175+
new_uri,
176+
options: None,
177+
annotation_id: None,
178+
},
179+
))];
180+
actions.push(CodeActionOrCommand::CodeAction(CodeAction {
181+
title: "Convert module to package".to_owned(),
182+
kind: Some(CodeActionKind::new("refactor.move")),
183+
edit: Some(WorkspaceEdit {
184+
document_changes: Some(DocumentChanges::Operations(operations)),
185+
..Default::default()
186+
}),
187+
..Default::default()
188+
}));
189+
}
190+
actions
191+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
pub(crate) mod convert_module_package;

0 commit comments

Comments
 (0)