mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
Text editor input and session
This commit is contained in:
@@ -19,9 +19,11 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
extern crate tempfile;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
DialogCallback, DialogYesNoOption, FileExplorerTab, FileTransferActivity, FsEntry, InputEvent,
|
DialogCallback, DialogYesNoOption, FileExplorerTab, FileTransferActivity, FsEntry, InputEvent,
|
||||||
InputField, InputMode, OnInputSubmitCallback, PopupType,
|
InputField, InputMode, LogLevel, OnInputSubmitCallback, PopupType,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crossterm::event::{KeyCode, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyModifiers};
|
||||||
@@ -175,10 +177,6 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Char(ch) => match ch {
|
KeyCode::Char(ch) => match ch {
|
||||||
'q' | 'Q' => {
|
|
||||||
// Create quit prompt dialog
|
|
||||||
self.input_mode = self.create_quit_popup();
|
|
||||||
}
|
|
||||||
'e' | 'E' => {
|
'e' | 'E' => {
|
||||||
// Get file at index
|
// Get file at index
|
||||||
if let Some(entry) = self.local.files.get(self.local.index) {
|
if let Some(entry) = self.local.files.get(self.local.index) {
|
||||||
@@ -223,6 +221,38 @@ impl FileTransferActivity {
|
|||||||
let pwd: PathBuf = self.local.wrkdir.clone();
|
let pwd: PathBuf = self.local.wrkdir.clone();
|
||||||
self.local_scan(pwd.as_path());
|
self.local_scan(pwd.as_path());
|
||||||
}
|
}
|
||||||
|
'o' | 'O' => {
|
||||||
|
// Edit local file
|
||||||
|
if self.local.files.get(self.local.index).is_some() {
|
||||||
|
// Clone entry due to mutable stuff...
|
||||||
|
let fsentry: FsEntry =
|
||||||
|
self.local.files.get(self.local.index).unwrap().clone();
|
||||||
|
// Check if file
|
||||||
|
if fsentry.is_file() {
|
||||||
|
self.log(
|
||||||
|
LogLevel::Info,
|
||||||
|
format!(
|
||||||
|
"Opening file \"{}\"...",
|
||||||
|
fsentry.get_abs_path().display()
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
// Edit file
|
||||||
|
match self.edit_local_file(fsentry.get_abs_path().as_path()) {
|
||||||
|
Ok(_) => {
|
||||||
|
// Reload directory
|
||||||
|
let pwd: PathBuf = self.local.wrkdir.clone();
|
||||||
|
self.local_scan(pwd.as_path());
|
||||||
|
}
|
||||||
|
Err(err) => self.log_and_alert(LogLevel::Error, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'q' | 'Q' => {
|
||||||
|
// Create quit prompt dialog
|
||||||
|
self.input_mode = self.create_quit_popup();
|
||||||
|
}
|
||||||
'r' | 'R' => {
|
'r' | 'R' => {
|
||||||
// Rename
|
// Rename
|
||||||
self.input_mode = InputMode::Popup(PopupType::Input(
|
self.input_mode = InputMode::Popup(PopupType::Input(
|
||||||
@@ -402,6 +432,33 @@ impl FileTransferActivity {
|
|||||||
// Reload file entries
|
// Reload file entries
|
||||||
self.reload_remote_dir();
|
self.reload_remote_dir();
|
||||||
}
|
}
|
||||||
|
'o' | 'O' => {
|
||||||
|
// Edit remote file
|
||||||
|
if self.remote.files.get(self.remote.index).is_some() {
|
||||||
|
// Clone entry due to mutable stuff...
|
||||||
|
let fsentry: FsEntry =
|
||||||
|
self.remote.files.get(self.remote.index).unwrap().clone();
|
||||||
|
// Check if file
|
||||||
|
if let FsEntry::File(file) = fsentry {
|
||||||
|
self.log(
|
||||||
|
LogLevel::Info,
|
||||||
|
format!("Opening file \"{}\"...", file.abs_path.display())
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
// Edit file
|
||||||
|
match self.edit_remote_file(&file) {
|
||||||
|
Ok(_) => {
|
||||||
|
// Reload directory
|
||||||
|
let pwd: PathBuf = self.remote.wrkdir.clone();
|
||||||
|
self.remote_scan(pwd.as_path());
|
||||||
|
}
|
||||||
|
Err(err) => self.log_and_alert(LogLevel::Error, err),
|
||||||
|
}
|
||||||
|
// Put input mode back to normal
|
||||||
|
self.input_mode = InputMode::Explorer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
'q' | 'Q' => {
|
'q' | 'Q' => {
|
||||||
// Create quit prompt dialog
|
// Create quit prompt dialog
|
||||||
self.input_mode = self.create_quit_popup();
|
self.input_mode = self.create_quit_popup();
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ extern crate tempfile;
|
|||||||
// Locals
|
// Locals
|
||||||
use super::{FileTransferActivity, InputMode, LogLevel, PopupType};
|
use super::{FileTransferActivity, InputMode, LogLevel, PopupType};
|
||||||
use crate::fs::{FsEntry, FsFile};
|
use crate::fs::{FsEntry, FsFile};
|
||||||
use crate::utils::fmt_millis;
|
use crate::utils::{fmt_millis, hash_sha256_file};
|
||||||
|
|
||||||
// Ext
|
// Ext
|
||||||
use bytesize::ByteSize;
|
use bytesize::ByteSize;
|
||||||
@@ -546,12 +546,7 @@ impl FileTransferActivity {
|
|||||||
// Apply file mode to file
|
// Apply file mode to file
|
||||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
||||||
if let Some(pex) = remote.unix_pex {
|
if let Some(pex) = remote.unix_pex {
|
||||||
if let Err(err) = self
|
if let Err(err) = self.context.as_ref().unwrap().local.chmod(local, pex)
|
||||||
.context
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.local
|
|
||||||
.chmod(local, pex)
|
|
||||||
{
|
{
|
||||||
self.log(
|
self.log(
|
||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
@@ -723,10 +718,10 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### edit_file
|
/// ### edit_local_file
|
||||||
///
|
///
|
||||||
/// Edit a file on localhost
|
/// Edit a file on localhost
|
||||||
pub(super) fn edit_file(&mut self, path: &Path) {
|
pub(super) fn edit_local_file(&mut self, path: &Path) -> Result<(), String> {
|
||||||
// Read first 2048 bytes or less from file to check if it is textual
|
// Read first 2048 bytes or less from file to check if it is textual
|
||||||
match OpenOptions::new().read(true).open(path) {
|
match OpenOptions::new().read(true).open(path) {
|
||||||
Ok(mut f) => {
|
Ok(mut f) => {
|
||||||
@@ -735,25 +730,16 @@ impl FileTransferActivity {
|
|||||||
match f.read(&mut buff) {
|
match f.read(&mut buff) {
|
||||||
Ok(size) => {
|
Ok(size) => {
|
||||||
if content_inspector::inspect(&buff[0..size]).is_binary() {
|
if content_inspector::inspect(&buff[0..size]).is_binary() {
|
||||||
self.log_and_alert(
|
return Err(format!("Could not open file in editor: file is binary"));
|
||||||
LogLevel::Error,
|
|
||||||
format!("Could not open file in editor: file is binary"),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
self.log_and_alert(
|
return Err(format!("Could not read file: {}", err));
|
||||||
LogLevel::Error,
|
|
||||||
format!("Could not read file: {}", err),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
self.log_and_alert(LogLevel::Error, format!("Could not read file: {}", err));
|
return Err(format!("Could not read file: {}", err));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Put input mode back to normal
|
// Put input mode back to normal
|
||||||
@@ -772,9 +758,7 @@ impl FileTransferActivity {
|
|||||||
)
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
),
|
),
|
||||||
Err(err) => {
|
Err(err) => return Err(format!("Could not open editor: {}", err)),
|
||||||
self.log_and_alert(LogLevel::Error, format!("Could not open editor: {}", err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if let Some(ctx) = self.context.as_mut() {
|
if let Some(ctx) = self.context.as_mut() {
|
||||||
// Clear screen
|
// Clear screen
|
||||||
@@ -784,5 +768,92 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
// Re-enable raw mode
|
// Re-enable raw mode
|
||||||
let _ = enable_raw_mode();
|
let _ = enable_raw_mode();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### edit_remote_file
|
||||||
|
///
|
||||||
|
/// Edit file on remote host
|
||||||
|
pub(super) fn edit_remote_file(&mut self, file: &FsFile) -> Result<(), String> {
|
||||||
|
// Create temp file
|
||||||
|
let tmpfile: tempfile::NamedTempFile = match tempfile::NamedTempFile::new() {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!("Could not create temporary file: {}", err));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Download file
|
||||||
|
if let Err(err) = self.filetransfer_recv_file(tmpfile.path(), file) {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
// Get current file hash
|
||||||
|
let prev_hash: String = match hash_sha256_file(tmpfile.path()) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!(
|
||||||
|
"Could not get sha256 for \"{}\": {}",
|
||||||
|
file.abs_path.display(),
|
||||||
|
err
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Edit file
|
||||||
|
if let Err(err) = self.edit_local_file(tmpfile.path()) {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
// Check if file has changed
|
||||||
|
let new_hash: String = match hash_sha256_file(tmpfile.path()) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!(
|
||||||
|
"Could not get sha256 for \"{}\": {}",
|
||||||
|
file.abs_path.display(),
|
||||||
|
err
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// If hash is different, write changes
|
||||||
|
match new_hash != prev_hash {
|
||||||
|
true => {
|
||||||
|
self.log(
|
||||||
|
LogLevel::Info,
|
||||||
|
format!(
|
||||||
|
"File \"{}\" has changed; writing changes to remote",
|
||||||
|
file.abs_path.display()
|
||||||
|
)
|
||||||
|
.as_ref(),
|
||||||
|
);
|
||||||
|
// Get local fs entry
|
||||||
|
let tmpfile_entry: FsEntry =
|
||||||
|
match self.context.as_ref().unwrap().local.stat(tmpfile.path()) {
|
||||||
|
Ok(e) => e,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(format!(
|
||||||
|
"Could not stat \"{}\": {}",
|
||||||
|
tmpfile.path().display(),
|
||||||
|
err
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Write file
|
||||||
|
let tmpfile_entry: &FsFile = match &tmpfile_entry {
|
||||||
|
FsEntry::Directory(_) => panic!("tempfile is a directory for some reason"),
|
||||||
|
FsEntry::File(f) => f,
|
||||||
|
};
|
||||||
|
// Send file
|
||||||
|
if let Err(err) =
|
||||||
|
self.filetransfer_send_file(tmpfile_entry, file.abs_path.as_path())
|
||||||
|
{
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false => {
|
||||||
|
self.log(
|
||||||
|
LogLevel::Info,
|
||||||
|
format!("File \"{}\" hasn't changed", file.abs_path.display()).as_ref(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user