Improve stash picker ui
This commit is contained in:
parent
d03557748b
commit
4f11b9ef56
5 changed files with 80 additions and 12 deletions
|
@ -994,7 +994,7 @@ impl GitRepository for RealGitRepository {
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
let output = new_std_command(&git_binary_path)
|
let output = new_std_command(&git_binary_path)
|
||||||
.current_dir(working_directory?)
|
.current_dir(working_directory?)
|
||||||
.args(&["stash", "list", "--pretty=%gd:%H:%s"])
|
.args(&["stash", "list", "--pretty=format:%gd%x00%H%x00%ct%x00%s"])
|
||||||
.output()?;
|
.output()?;
|
||||||
if output.status.success() {
|
if output.status.success() {
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
|
|
@ -7,6 +7,8 @@ pub struct StashEntry {
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
pub oid: Oid,
|
pub oid: Oid,
|
||||||
pub message: String,
|
pub message: String,
|
||||||
|
pub branch: Option<String>,
|
||||||
|
pub timestamp: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
|
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
|
||||||
|
@ -28,7 +30,7 @@ impl FromStr for GitStash {
|
||||||
let entries = s
|
let entries = s
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.filter_map(|entry| {
|
.filter_map(|entry| {
|
||||||
let mut parts = entry.splitn(3, ':');
|
let mut parts = entry.splitn(4, '\0');
|
||||||
let raw_idx = parts.next().and_then(|i| {
|
let raw_idx = parts.next().and_then(|i| {
|
||||||
let trimmed = i.trim();
|
let trimmed = i.trim();
|
||||||
if trimmed.starts_with("stash@{") && trimmed.ends_with('}') {
|
if trimmed.starts_with("stash@{") && trimmed.ends_with('}') {
|
||||||
|
@ -40,15 +42,21 @@ impl FromStr for GitStash {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let raw_oid = parts.next();
|
let raw_oid = parts.next();
|
||||||
|
let raw_date = parts.next().and_then(|d| d.parse().ok());
|
||||||
let message = parts.next();
|
let message = parts.next();
|
||||||
|
|
||||||
if let (Some(raw_idx), Some(raw_oid), Some(message)) = (raw_idx, raw_oid, message) {
|
if let (Some(raw_idx), Some(raw_oid), Some(raw_date), Some(message)) =
|
||||||
|
(raw_idx, raw_oid, raw_date, message)
|
||||||
|
{
|
||||||
|
let (branch, message) = parse_stash_entry(message);
|
||||||
let index = raw_idx.parse::<usize>().ok()?;
|
let index = raw_idx.parse::<usize>().ok()?;
|
||||||
let oid = Oid::from_str(raw_oid).ok()?;
|
let oid = Oid::from_str(raw_oid).ok()?;
|
||||||
let entry = StashEntry {
|
let entry = StashEntry {
|
||||||
index,
|
index,
|
||||||
oid,
|
oid,
|
||||||
message: message.to_string(),
|
message: message.to_string(),
|
||||||
|
branch: branch.map(Into::into),
|
||||||
|
timestamp: raw_date,
|
||||||
};
|
};
|
||||||
return Some(entry);
|
return Some(entry);
|
||||||
}
|
}
|
||||||
|
@ -60,3 +68,26 @@ impl FromStr for GitStash {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_stash_entry(input: &str) -> (Option<&str>, &str) {
|
||||||
|
// Try to match "WIP on <branch>: <message>" pattern
|
||||||
|
if let Some(stripped) = input.strip_prefix("WIP on ") {
|
||||||
|
if let Some(colon_pos) = stripped.find(": ") {
|
||||||
|
let branch = &stripped[..colon_pos];
|
||||||
|
let message = &stripped[colon_pos + 2..];
|
||||||
|
return (Some(branch), message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to match "On <branch>: <message>" pattern
|
||||||
|
if let Some(stripped) = input.strip_prefix("On ") {
|
||||||
|
if let Some(colon_pos) = stripped.find(": ") {
|
||||||
|
let branch = &stripped[..colon_pos];
|
||||||
|
let message = &stripped[colon_pos + 2..];
|
||||||
|
return (Some(branch), message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edge case: format doesn't match, return None for branch and full string as message
|
||||||
|
(None, input)
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,9 @@ use gpui::{
|
||||||
use picker::{Picker, PickerDelegate, PickerEditorPosition};
|
use picker::{Picker, PickerDelegate, PickerEditorPosition};
|
||||||
use project::git_store::{Repository, RepositoryEvent};
|
use project::git_store::{Repository, RepositoryEvent};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use ui::{HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, prelude::*};
|
use time::OffsetDateTime;
|
||||||
|
use time_format::format_local_timestamp;
|
||||||
|
use ui::{HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::notifications::DetachAndPromptErr;
|
use workspace::notifications::DetachAndPromptErr;
|
||||||
use workspace::{ModalView, Workspace};
|
use workspace::{ModalView, Workspace};
|
||||||
|
@ -220,6 +222,10 @@ impl StashListDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn format_message(ix: usize, message: &String) -> String {
|
||||||
|
format!("#{}: {}", ix, message)
|
||||||
|
}
|
||||||
|
|
||||||
fn drop_stash_at(&self, ix: usize, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
fn drop_stash_at(&self, ix: usize, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
let Some(entry_match) = self.matches.get(ix) else {
|
let Some(entry_match) = self.matches.get(ix) else {
|
||||||
return;
|
return;
|
||||||
|
@ -310,7 +316,12 @@ impl PickerDelegate for StashListDelegate {
|
||||||
let candidates = all_stash_entries
|
let candidates = all_stash_entries
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(ix, entry)| StringMatchCandidate::new(ix, &entry.message))
|
.map(|(ix, entry)| {
|
||||||
|
StringMatchCandidate::new(
|
||||||
|
ix,
|
||||||
|
&Self::format_message(entry.index, &entry.message),
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect::<Vec<StringMatchCandidate>>();
|
.collect::<Vec<StringMatchCandidate>>();
|
||||||
fuzzy::match_strings(
|
fuzzy::match_strings(
|
||||||
&candidates,
|
&candidates,
|
||||||
|
@ -367,7 +378,8 @@ impl PickerDelegate for StashListDelegate {
|
||||||
) -> Option<Self::ListItem> {
|
) -> Option<Self::ListItem> {
|
||||||
let entry_match = &self.matches[ix];
|
let entry_match = &self.matches[ix];
|
||||||
|
|
||||||
let mut stash_message = entry_match.entry.message.clone();
|
let mut stash_message =
|
||||||
|
Self::format_message(entry_match.entry.index, &entry_match.entry.message);
|
||||||
let mut positions = entry_match.positions.clone();
|
let mut positions = entry_match.positions.clone();
|
||||||
|
|
||||||
if stash_message.is_ascii() {
|
if stash_message.is_ascii() {
|
||||||
|
@ -394,9 +406,27 @@ impl PickerDelegate for StashListDelegate {
|
||||||
|
|
||||||
let stash_name = HighlightedLabel::new(stash_message, positions).into_any_element();
|
let stash_name = HighlightedLabel::new(stash_message, positions).into_any_element();
|
||||||
|
|
||||||
let stash_index_label = Label::new(format!("stash@{{{}}}", entry_match.entry.index))
|
let stash_index_label = Label::new(
|
||||||
.size(LabelSize::Small)
|
entry_match
|
||||||
.color(Color::Muted);
|
.entry
|
||||||
|
.branch
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted);
|
||||||
|
|
||||||
|
let absolute_timestamp = format_local_timestamp(
|
||||||
|
OffsetDateTime::from_unix_timestamp(entry_match.entry.timestamp)
|
||||||
|
.unwrap_or(OffsetDateTime::now_utc()),
|
||||||
|
OffsetDateTime::now_utc(),
|
||||||
|
time_format::TimestampFormat::MediumAbsolute,
|
||||||
|
);
|
||||||
|
let tooltip_text = format!(
|
||||||
|
"stash@{{{}}} created {}",
|
||||||
|
entry_match.entry.index, absolute_timestamp
|
||||||
|
);
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
ListItem::new(SharedString::from(format!("stash-{ix}")))
|
ListItem::new(SharedString::from(format!("stash-{ix}")))
|
||||||
|
@ -412,7 +442,8 @@ impl PickerDelegate for StashListDelegate {
|
||||||
.child(stash_name)
|
.child(stash_name)
|
||||||
.child(stash_index_label.into_element()),
|
.child(stash_index_label.into_element()),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
|
.tooltip(Tooltip::text(tooltip_text)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2928,7 +2928,9 @@ pub fn stash_to_proto(entry: &StashEntry) -> proto::StashEntry {
|
||||||
proto::StashEntry {
|
proto::StashEntry {
|
||||||
oid: entry.oid.as_bytes().to_vec(),
|
oid: entry.oid.as_bytes().to_vec(),
|
||||||
message: entry.message.clone(),
|
message: entry.message.clone(),
|
||||||
index: entry.index as i64,
|
branch: entry.branch.clone(),
|
||||||
|
index: entry.index as u64,
|
||||||
|
timestamp: entry.timestamp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2937,6 +2939,8 @@ pub fn proto_to_stash(entry: &proto::StashEntry) -> Result<StashEntry> {
|
||||||
oid: Oid::from_bytes(&entry.oid)?,
|
oid: Oid::from_bytes(&entry.oid)?,
|
||||||
message: entry.message.clone(),
|
message: entry.message.clone(),
|
||||||
index: entry.index as usize,
|
index: entry.index as usize,
|
||||||
|
branch: entry.branch.clone(),
|
||||||
|
timestamp: entry.timestamp,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -287,7 +287,9 @@ message StatusEntry {
|
||||||
message StashEntry {
|
message StashEntry {
|
||||||
bytes oid = 1;
|
bytes oid = 1;
|
||||||
string message = 2;
|
string message = 2;
|
||||||
int64 index = 3;
|
optional string branch = 3;
|
||||||
|
uint64 index = 4;
|
||||||
|
int64 timestamp = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Stage {
|
message Stage {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue