Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* open the external editor from the status diff view [[@WaterWhisperer](https://github.com/WaterWhisperer)] ([#2805](https://github.com/gitui-org/gitui/issues/2805))
* automatically convert spaces to dashes when creating or renaming a branch [[@pbouillon]](https//pbouillon.github.io)] ([#2916](https://github.com/gitui-org/gitui/pull/2916))
* support rewording non-HEAD commits when `commit.gpgsign` is enabled (gpg format only) [[@guerinoni](https://github.com/guerinoni)] ([#2959](https://github.com/gitui-org/gitui/pull/2959))
* in the revision file tree `edit [e]` is now only offered on the head revision, older revisions offer `open [o]` which dumps that revision of the file into a temporary file and opens it ([#2147](https://github.com/gitui-org/gitui/issues/2147))

### Fixes
* crash when opening submodule ([#2895](https://github.com/gitui-org/gitui/issues/2895))
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ shellexpand = "3.1"
simplelog = { version = "0.12", default-features = false }
struct-patch = "0.10"
syntect = { version = "5.3", default-features = false, features = ["default-syntaxes", "default-themes", "html", "parsing", "plist-load"] }
tempfile = "3"
two-face = { version = "0.4.4", default-features = false }
unicode-segmentation = "1.12"
unicode-truncate = "2.0"
Expand Down
94 changes: 86 additions & 8 deletions src/components/revision_files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ use anyhow::Result;
use asyncgit::{
asyncjob::AsyncSingleJob,
sync::{
get_commit_info, CommitId, CommitInfo, RepoPathRef, TreeFile,
get_commit_info, get_head, tree_file_content, CommitId,
CommitInfo, RepoPathRef, TreeFile,
},
AsyncGitNotification, AsyncTreeFilesJob,
};
Expand Down Expand Up @@ -54,6 +55,7 @@ pub struct RevisionFilesComponent {
scroll: VerticalScroll,
visible: bool,
revision: Option<CommitInfo>,
revision_is_head: bool,
focus: Focus,
key_config: SharedKeyConfig,
select_file: Option<PathBuf>,
Expand All @@ -76,6 +78,7 @@ impl RevisionFilesComponent {
env.sender_git.clone(),
),
revision: None,
revision_is_head: false,
focus: Focus::Tree,
key_config: env.key_config.clone(),
repo: env.repo.clone(),
Expand All @@ -100,6 +103,9 @@ impl RevisionFilesComponent {
Some(get_commit_info(&self.repo.borrow(), &commit)?);
}

self.revision_is_head = get_head(&self.repo.borrow())
.is_ok_and(|head| head == commit);

Ok(())
}

Expand Down Expand Up @@ -265,6 +271,54 @@ impl RevisionFilesComponent {
})
}

/// dumps the selected file at the currently viewed revision into a
/// temporary file and opens that in the external editor
fn open_file_revision(&self) -> Result<()> {
let Some(file_path) = self.selected_file_path_with_prefix()
else {
return Ok(());
};

let path = Path::new(&file_path);

let Some(file) = self
.files
.as_ref()
.and_then(|files| files.iter().find(|f| f.path == path))
else {
return Ok(());
};

let content = tree_file_content(&self.repo.borrow(), file)?;

let file_name = path
.file_name()
.map(|name| name.to_string_lossy().to_string())
.unwrap_or_default();

let temp_file = tempfile::Builder::new()
.prefix(&format!("gitui-{}-", self.revision_short_id()))
.suffix(&format!("-{file_name}"))
.tempfile()?
.into_temp_path()
.keep()?;

std::fs::write(&temp_file, content)?;

self.queue.push(InternalEvent::OpenExternalEditor(Some(
temp_file.to_string_lossy().to_string(),
)));

Ok(())
}

fn revision_short_id(&self) -> String {
self.revision
.as_ref()
.map(|commit| commit.id.get_short_string())
.unwrap_or_default()
}

fn selection_changed(&mut self) {
//TODO: retrieve TreeFile from tree datastructure
if let Some(file) = self.selected_file_path_with_prefix() {
Expand Down Expand Up @@ -437,11 +491,21 @@ impl Component for RevisionFilesComponent {
)
.order(order::NAV),
);
out.push(CommandInfo::new(
strings::commands::edit_item(&self.key_config),
self.tree.selected_file().is_some(),
true,
));
if self.revision_is_head {
out.push(CommandInfo::new(
strings::commands::edit_item(&self.key_config),
self.tree.selected_file().is_some(),
true,
));
} else {
out.push(CommandInfo::new(
strings::commands::open_file_revision(
&self.key_config,
),
self.tree.selected_file().is_some(),
true,
));
}
out.push(
CommandInfo::new(
strings::commands::open_file_history(
Expand Down Expand Up @@ -516,9 +580,23 @@ impl Component for RevisionFilesComponent {
self.open_finder();
return Ok(EventState::Consumed);
}
} else if key_match(
key,
self.key_config.keys.open_file_revision,
) {
if !self.revision_is_head {
try_or_popup!(
self,
"failed to open file revision:",
self.open_file_revision()
);
return Ok(EventState::Consumed);
}
} else if key_match(key, self.key_config.keys.edit_file) {
if let Some(file) =
self.selected_file_path_with_prefix()
if let Some(file) = self
.revision_is_head
.then(|| self.selected_file_path_with_prefix())
.flatten()
{
//Note: switch to status tab so its clear we are
// not altering a file inside a revision here
Expand Down
2 changes: 2 additions & 0 deletions src/keys/key_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ pub struct KeysList {
pub blame: GituiKeyEvent,
pub file_history: GituiKeyEvent,
pub edit_file: GituiKeyEvent,
pub open_file_revision: GituiKeyEvent,
pub status_stage_all: GituiKeyEvent,
pub status_reset_item: GituiKeyEvent,
pub status_ignore_file: GituiKeyEvent,
Expand Down Expand Up @@ -168,6 +169,7 @@ impl Default for KeysList {
blame: GituiKeyEvent::new(KeyCode::Char('B'), KeyModifiers::SHIFT),
file_history: GituiKeyEvent::new(KeyCode::Char('H'), KeyModifiers::SHIFT),
edit_file: GituiKeyEvent::new(KeyCode::Char('e'), KeyModifiers::empty()),
open_file_revision: GituiKeyEvent::new(KeyCode::Char('o'), KeyModifiers::empty()),
status_stage_all: GituiKeyEvent::new(KeyCode::Char('a'), KeyModifiers::empty()),
status_reset_item: GituiKeyEvent::new(KeyCode::Char('D'), KeyModifiers::SHIFT),
diff_reset_lines: GituiKeyEvent::new(KeyCode::Char('d'), KeyModifiers::empty()),
Expand Down
12 changes: 12 additions & 0 deletions src/strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,18 @@ pub mod commands {
CMD_GROUP_CHANGES,
)
}
pub fn open_file_revision(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!(
"Open [{}]",
key_config.get_hint(key_config.keys.open_file_revision),
),
"open the selected revision of the file in an external editor (as a temporary file)",
CMD_GROUP_CHANGES,
)
}
pub fn stage_item(key_config: &SharedKeyConfig) -> CommandText {
CommandText::new(
format!(
Expand Down