Skip to content
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

Implement file duplication #1972

Merged
merged 6 commits into from
Jan 12, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- [#1960](https://github.com/lapce/lapce/pull/1960): Add sticky headers and code lens for PHP
- [#1968](https://github.com/lapce/lapce/pull/1968): Completion lens (disabled by default)
- ![image](https://user-images.githubusercontent.com/13157904/211959283-c3229cfc-28d7-4676-a50d-aec7d47cde9f.png)
- [#1972](https://github.com/lapce/lapce/pull/1972): Add file duplication option in fs tree context menu

### Bug Fixes
- [#1911](https://github.com/lapce/lapce/pull/1911): Fix movement on selections with left/right arrow keys
Expand Down
16 changes: 16 additions & 0 deletions lapce-data/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,11 @@ pub enum LapceUICommand {
CreateDirectory {
path: PathBuf,
},
/// Copy an existing file to the given name and then open it
DuplicateFileOpen {
existing_path: PathBuf,
new_path: PathBuf,
},
RenamePath {
from: PathBuf,
to: PathBuf,
Expand All @@ -944,6 +949,17 @@ pub enum LapceUICommand {
TrashPath {
path: PathBuf,
},
/// Start duplicating a specific file in view at the given index
ExplorerStartDuplicate {
/// The index into the explorer's file listing
list_index: usize,
/// The level that it should be indented to
indent_level: usize,
/// The folder that the file/directory is being created within
base_path: PathBuf,
/// The name of the file being duplicated
name: String,
},
/// Start renaming a specific file in view at the given index
ExplorerStartRename {
/// The index into the explorer's file listing
Expand Down
78 changes: 77 additions & 1 deletion lapce-data/src/explorer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,25 @@ pub enum Naming {
/// The folder that the file/directory is being created within
base_path: PathBuf,
},
/// Duplicating an existing file
Duplicating {
/// The index that the file being created should appear at
/// Note that when duplicating, it is not yet actually created.
list_index: usize,
/// Indentation level
indent_level: usize,
/// The folder that the file/directory is being created within
base_path: PathBuf,
/// The name of the file being duplicated
name: String,
},
}
impl Naming {
pub fn list_index(&self) -> usize {
match self {
Naming::Renaming { list_index, .. }
| Naming::Naming { list_index, .. } => *list_index,
| Naming::Naming { list_index, .. }
| Naming::Duplicating { list_index, .. } => *list_index,
}
}
}
Expand Down Expand Up @@ -366,6 +379,21 @@ impl FileExplorerData {
Target::Auto,
));
}
Naming::Duplicating {
base_path, name, ..
} => {
let new_path = base_path.join(target_name);

let cmd = LapceUICommand::DuplicateFileOpen {
existing_path: base_path.join(name),
new_path,
};
ctx.submit_command(Command::new(
LAPCE_UI_COMMAND,
cmd,
Target::Auto,
));
}
}

self.cancel_naming();
Expand Down Expand Up @@ -410,6 +438,54 @@ impl FileExplorerData {
));
}

/// Show the naming input for the given file at the index
/// Requires `main_split` for getting the input to set its content
/// Requires `ctx` to switch focus to the input
pub fn start_duplicating(
&mut self,
ctx: &mut EventCtx,
main_split: &mut LapceMainSplitData,
list_index: usize,
indent_level: usize,
base_path: PathBuf,
name: String,
) {
self.cancel_naming();
self.naming = Some(Naming::Duplicating {
list_index,
indent_level,
base_path,
name: name.clone(),
});

// Set the text of the input
let doc = main_split
.local_docs
.get_mut(&LocalBufferKind::PathName)
.unwrap();
Arc::make_mut(doc).reload(Rope::from(name), true);

// TODO: We could provide a configuration option to only select the filename at first,
// which would fit a common case of just wanting to change the filename and not the ext
// (or that could be the default)

// Select all of the text, allowing them to quickly completely change the name if they wish
let editor = main_split
.editors
.get_mut(&self.renaming_editor_view_id)
.unwrap();
let offset = doc.buffer().line_end_offset(0, true);
Arc::make_mut(editor).cursor.mode =
CursorMode::Insert(Selection::region(0, offset));

// Focus on the input
ctx.submit_command(Command::new(
LAPCE_UI_COMMAND,
LapceUICommand::Focus,
Target::Widget(editor.view_id),
));
}

/// Show the renaming input for the given file at the index
/// Requires `main_split` for getting the input to set its content
/// Requires `ctx` to switch focus to the input
Expand Down
31 changes: 31 additions & 0 deletions lapce-proxy/src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,37 @@ impl ProxyHandler for Dispatcher {
});
self.respond_rpc(id, result);
}
DuplicatePath {
existing_path,
new_path,
} => {
// We first check if the destination already exists, because copy can overwrite it
// and that's not the default behavior we want for when a user duplicates a document.
let result = if new_path.exists() {
Err(RpcError {
code: 0,
message: format!("{:?} already exists", new_path),
})
} else {
if let Some(parent) = new_path.parent() {
if let Err(error) = std::fs::create_dir_all(parent) {
let result = Err(RpcError {
code: 0,
message: error.to_string(),
});
self.respond_rpc(id, result);
return;
}
}
std::fs::copy(existing_path, new_path)
.map(|_| ProxyResponse::Success {})
.map_err(|e| RpcError {
code: 0,
message: e.to_string(),
})
};
self.respond_rpc(id, result);
}
RenamePath { from, to } => {
// We first check if the destination already exists, because rename can overwrite it
// and that's not the default behavior we want for when a user renames a document.
Expand Down
19 changes: 19 additions & 0 deletions lapce-rpc/src/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ pub enum ProxyRequest {
TrashPath {
path: PathBuf,
},
DuplicatePath {
existing_path: PathBuf,
new_path: PathBuf,
},
RenamePath {
from: PathBuf,
to: PathBuf,
Expand Down Expand Up @@ -576,6 +580,21 @@ impl ProxyRpcHandler {
self.request_async(ProxyRequest::TrashPath { path }, f);
}

pub fn duplicate_path(
&self,
existing_path: PathBuf,
new_path: PathBuf,
f: impl ProxyCallback + 'static,
) {
self.request_async(
ProxyRequest::DuplicatePath {
existing_path,
new_path,
},
f,
);
}

pub fn rename_path(
&self,
from: PathBuf,
Expand Down
31 changes: 30 additions & 1 deletion lapce-ui/src/explorer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ fn draw_name_input(
Naming::Renaming { .. } => {
name_edit_input.paint(ctx, data, env);
}
Naming::Naming { .. } => {
Naming::Naming { .. } | Naming::Duplicating { .. } => {
name_edit_input.paint(ctx, data, env);
// Skip forward by an entry
// This is fine since we aren't using i as an index, but as an offset-multiple in painting
Expand Down Expand Up @@ -831,6 +831,30 @@ impl Widget<LapceTabData> for FileExplorerFileList {
);
menu = menu.entry(item);

if !node.is_dir {
let item = druid::MenuItem::new("Duplicate")
.command(Command::new(
LAPCE_UI_COMMAND,
LapceUICommand::ExplorerStartDuplicate {
list_index: index,
indent_level,
base_path: node
.path_buf
.parent()
.expect("file without parent")
.to_owned(),
name: node
.path_buf
.file_name()
.expect("file without name")
.to_string_lossy()
.into_owned(),
},
Target::Auto,
));
menu = menu.entry(item);
}

let trash_text = if node.is_dir {
"Move Directory to Trash"
} else {
Expand Down Expand Up @@ -952,6 +976,11 @@ impl Widget<LapceTabData> for FileExplorerFileList {
list_index,
indent_level,
..
}
| Naming::Duplicating {
list_index,
indent_level,
..
} => (list_index, indent_level),
};

Expand Down
52 changes: 52 additions & 0 deletions lapce-ui/src/tab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1904,6 +1904,41 @@ impl LapceTab {
);
ctx.set_handled();
}
LapceUICommand::DuplicateFileOpen {
existing_path,
new_path,
} => {
let new_path_clone = new_path.clone();
let event_sink = ctx.get_external_handle();
let tab_id = data.id;
let explorer = data.file_explorer.clone();
data.proxy.proxy_rpc.duplicate_path(
existing_path.clone(),
new_path.clone(),
Box::new(move |res| {
match res {
Ok(_) => {
let _ = event_sink.submit_command(
LAPCE_UI_COMMAND,
LapceUICommand::OpenFile(
new_path_clone,
false,
),
Target::Widget(tab_id),
);
}
Err(err) => {
// TODO: Inform the user through a corner-notif
log::warn!(
"Failed to duplicate file: {:?}",
err,
);
}
}
explorer.reload();
}),
);
}
LapceUICommand::RenamePath { from, to } => {
let explorer = data.file_explorer.clone();
data.proxy.proxy_rpc.rename_path(
Expand Down Expand Up @@ -1949,6 +1984,23 @@ impl LapceTab {
);
ctx.set_handled();
}
LapceUICommand::ExplorerStartDuplicate {
list_index,
indent_level,
base_path,
name,
} => {
let file_explorer = Arc::make_mut(&mut data.file_explorer);
file_explorer.start_duplicating(
ctx,
&mut data.main_split,
*list_index,
*indent_level,
base_path.clone(),
name.clone(),
);
ctx.set_handled();
}
LapceUICommand::ExplorerStartRename {
list_index,
indent_level,
Expand Down