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

Feat: Add VCS change picker for current document and workspace #5472

Closed
Closed
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
13 changes: 12 additions & 1 deletion helix-term/src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
pub(crate) mod dap;
pub(crate) mod lsp;
pub(crate) mod typed;
pub(crate) mod vcs;

pub use dap::*;
use helix_vcs::Hunk;
pub use lsp::*;
use tui::widgets::Row;
pub use typed::*;
pub use vcs::*;

use helix_core::{
char_idx_at_visual_offset, comment,
Expand Down Expand Up @@ -77,7 +79,6 @@ pub struct Context<'a> {
pub register: Option<char>,
pub count: Option<NonZeroUsize>,
pub editor: &'a mut Editor,

pub callback: Option<crate::compositor::Callback>,
pub on_next_key_callback: Option<Box<dyn FnOnce(&mut Context, KeyEvent)>>,
pub jobs: &'a mut Jobs,
Expand Down Expand Up @@ -286,6 +287,7 @@ impl MappableCommand {
workspace_symbol_picker, "Open workspace symbol picker",
diagnostics_picker, "Open diagnostic picker",
workspace_diagnostics_picker, "Open workspace diagnostic picker",
vcs_change_picker, "Open VCS changes picker",
last_picker, "Open last picker",
insert_at_line_start, "Insert at start of line",
insert_at_line_end, "Insert at end of line",
Expand Down Expand Up @@ -546,6 +548,15 @@ impl PartialEq for MappableCommand {
}
}

/// Used in both LSP and VCS commands to determine if the source path should be shown or not.
///
/// The usual case is hiding for a single-document picker and showing for a multiple-document one.
#[derive(Debug, Copy, Clone, PartialEq)]
enum SourcePathFormat {
Show,
Hide,
}

fn no_op(_cx: &mut Context) {}

type MoveFn =
Expand Down
20 changes: 7 additions & 13 deletions helix-term/src/commands/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use tui::{
widgets::Row,
};

use super::{align_view, push_jump, Align, Context, Editor, Open};
use super::{align_view, push_jump, Align, Context, Editor, Open, SourcePathFormat};

use helix_core::{path, Selection};
use helix_view::{document::Mode, editor::Action, theme::Style};
Expand Down Expand Up @@ -111,7 +111,7 @@ struct PickerDiagnostic {
}

impl ui::menu::Item for PickerDiagnostic {
type Data = (DiagnosticStyles, DiagnosticsFormat);
type Data = (DiagnosticStyles, SourcePathFormat);

fn format(&self, (styles, format): &Self::Data) -> Row {
let mut style = self
Expand Down Expand Up @@ -140,8 +140,8 @@ impl ui::menu::Item for PickerDiagnostic {
.unwrap_or_default();

let path = match format {
DiagnosticsFormat::HideSourcePath => String::new(),
DiagnosticsFormat::ShowSourcePath => {
SourcePathFormat::Hide => String::new(),
SourcePathFormat::Show => {
let path = path::get_truncated_path(self.url.path());
format!("{}: ", path.to_string_lossy())
}
Expand Down Expand Up @@ -251,17 +251,11 @@ fn sym_picker(
.truncate_start(false)
}

#[derive(Copy, Clone, PartialEq)]
enum DiagnosticsFormat {
ShowSourcePath,
HideSourcePath,
}

fn diag_picker(
cx: &Context,
diagnostics: BTreeMap<lsp::Url, Vec<lsp::Diagnostic>>,
current_path: Option<lsp::Url>,
format: DiagnosticsFormat,
format: SourcePathFormat,
offset_encoding: OffsetEncoding,
) -> FilePicker<PickerDiagnostic> {
// TODO: drop current_path comparison and instead use workspace: bool flag?
Expand Down Expand Up @@ -446,7 +440,7 @@ pub fn diagnostics_picker(cx: &mut Context) {
cx,
[(current_url.clone(), diagnostics)].into(),
Some(current_url),
DiagnosticsFormat::HideSourcePath,
SourcePathFormat::Hide,
offset_encoding,
);
cx.push_layer(Box::new(overlayed(picker)));
Expand All @@ -463,7 +457,7 @@ pub fn workspace_diagnostics_picker(cx: &mut Context) {
cx,
diagnostics,
current_url,
DiagnosticsFormat::ShowSourcePath,
SourcePathFormat::Show,
offset_encoding,
);
cx.push_layer(Box::new(overlayed(picker)));
Expand Down
163 changes: 163 additions & 0 deletions helix-term/src/commands/vcs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
use std::{ops::Range, path::PathBuf};

use helix_core::Selection;
use helix_lsp::Url;
use helix_view::theme::Style;
use helix_view::Document;
use tui::widgets::{Cell, Row};

use super::{align_view, push_jump, Align, Context, SourcePathFormat};
use crate::ui::{menu::Item, FilePicker};

/// Picker to list the VCS changes in the current document
pub fn vcs_change_picker(cx: &mut Context) {
let current_doc = doc!(cx.editor);

let mut changes = Vec::new();
add_changes_for_doc(&mut changes, current_doc);

let picker = vcs_picker(cx, changes, current_doc.url(), SourcePathFormat::Hide);
cx.push_layer(Box::new(picker));
}

// TODO: once both #5472 and #5645 are merged, do a workspace-changes picker

fn add_changes_for_doc(changes: &mut Vec<DiffBlock>, doc: &Document) {
let url = match doc.url() {
Some(url) => url,
None => return,
};

if let Some(diff_handle) = doc.diff_handle() {
let file_hunks = diff_handle.hunks();
if file_hunks.is_empty() {
return;
}

changes.reserve(file_hunks.len() as _);

for i in 0..file_hunks.len() {
let hunk = file_hunks.nth_hunk(i);

let ty = if hunk.is_pure_insertion() {
DiffType::PureInsertion
} else if hunk.is_pure_removal() {
DiffType::PureRemoval
} else {
DiffType::Delta
};

let range = hunk.after;
let line = doc
.text()
.get_line(range.start as usize)
.map_or_else(Default::default, |l| l.to_string());

changes.push(DiffBlock {
url: url.clone(),
ty,
line,
range,
});
}
}
}

struct DiffStyles {
insertion: Style,
removal: Style,
delta: Style,
}

struct DiffBlock {
url: Url,
ty: DiffType,
line: String,
range: Range<u32>,
}

#[derive(Debug, Copy, Clone)]
enum DiffType {
PureInsertion,
PureRemoval,
Delta,
}

impl DiffType {
fn as_str(&self) -> &'static str {
match self {
DiffType::PureInsertion => "[+]",
DiffType::PureRemoval => "[-]",
DiffType::Delta => "[~]",
}
}
}

impl Item for DiffBlock {
type Data = (DiffStyles, SourcePathFormat);

fn format(&self, (styles, source_path_format): &Self::Data) -> Row {
let path = match source_path_format {
SourcePathFormat::Hide => String::new(),
SourcePathFormat::Show => {
let path = helix_core::path::get_truncated_path(self.url.path());
format!("{}:", path.to_string_lossy())
}
};

let diff_style = match self.ty {
DiffType::PureInsertion => styles.insertion,
DiffType::PureRemoval => styles.removal,
DiffType::Delta => styles.delta,
};

Row::new([
Cell::from(path),
Cell::from(self.ty.as_str()).style(diff_style),
Cell::from(self.line.as_str()),
])
}
}

fn vcs_picker(
cx: &Context,
changes: Vec<DiffBlock>,
current_path: Option<Url>,
show_source_path: SourcePathFormat,
) -> FilePicker<DiffBlock> {
let styles = DiffStyles {
insertion: cx.editor.theme.get("diff.plus"),
removal: cx.editor.theme.get("diff.minus"),
delta: cx.editor.theme.get("diff.delta"),
};

FilePicker::new(
changes,
(styles, show_source_path),
move |cx, DiffBlock { url, range, .. }, action| {
if current_path.as_ref() == Some(url) {
let (view, doc) = current!(cx.editor);
push_jump(view, doc);
} else {
let path = url.to_file_path().unwrap();
cx.editor.open(&path, action).expect("editor.open failed");
}

let (view, doc) = current!(cx.editor);

let anchor = doc.text().line_to_char(range.start as usize);
let head = doc
.text()
.line_to_char(range.end as usize)
.saturating_sub(1);
doc.set_selection(view.id, Selection::single(anchor, head));
align_view(doc, view, Align::Center);
},
move |_editor, DiffBlock { url, range, .. }| {
Some((
PathBuf::from(url.path()).into(),
Some((range.start as usize, range.end.saturating_sub(1) as usize)),
))
},
)
}
1 change: 1 addition & 0 deletions helix-term/src/keymap/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ pub fn default() -> HashMap<Mode, Keymap> {
"S" => workspace_symbol_picker,
"d" => diagnostics_picker,
"D" => workspace_diagnostics_picker,
"v" => vcs_change_picker,
"a" => code_action,
"'" => last_picker,
"g" => { "Debug (experimental)" sticky=true
Expand Down
2 changes: 1 addition & 1 deletion helix-vcs/src/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ impl Hunk {
after: u32::MAX..u32::MAX,
};

/// Inverts a change so that `before`
/// Inverts a change so that `before` becomes `after` and conversely.
pub fn invert(&self) -> Hunk {
Hunk {
before: self.after.clone(),
Expand Down