WIP contents diff
[?]
Apr 28, 2025, 5:35 PM
MJDGPSHGF62FTVWZBE7MFNJTUQD42OBVJEOSVPBT553UFJLTEMXQCDependencies
- [2]
6YZAVBWUInitial commit - [3]
EC3TVL4Xadd untracked files - [4]
KT5UYXGKfix selection after adding file, add changed file diffs - [5]
A5YBC77Vrecord! - [6]
D7A7MSIHallow to defer or abandon record, add buttons - [7]
4WO3ZJM2show untracked files' contents - [8]
CFYW3HGZwip: display changed files - [9]
AMPZ2BXKshow changed files diffs (only Edit atm) - [10]
AXSXZQDGfix updating changed file contents, styling - [11]
V55EAIWQadd src file LRU cache - [12]
NRCUG4R2load changed files src when selected - [13]
Y5ATDI2Hconvert changed file diffs and load src only if any needs it - [14]
YBLPPHZNshow contents for move, del and undel - [15]
UMO6U2ZTpartition the change files diffs on whether they have content - [16]
HOJZI52Yrename flowers_ui to inflorescence - [17]
W4LFX7IHgroup diffs by file name - [18]
WT3GA27Padd cursor with selection - [*]
SWWE2R6Mdisplay basic repo stuff
Change contents
- edit in crates/libflowers_client/src/repo.rs at line 7
use std::cmp; - replacement in crates/libflowers_client/src/repo.rs at line 49
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, strum::Display)]#[derive(Clone, Debug, PartialEq, Eq, strum::Display)] - edit in crates/libflowers_client/src/repo.rs at line 75
}impl PartialOrd for ChangedFileDiff {fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {diff_line(self).zip(diff_line(other)).map(|(left, right)| left.cmp(&right))} - edit in crates/libflowers_client/src/repo.rs at line 85
impl Ord for ChangedFileDiff {fn cmp(&self, other: &Self) -> cmp::Ordering {diff_line(self).zip(diff_line(other)).map(|(left, right)| left.cmp(&right))// TODO better ordering by type.unwrap_or(cmp::Ordering::Equal)}}fn diff_line(diff: &ChangedFileDiff) -> Option<usize> {match diff {ChangedFileDiff::Move => None,ChangedFileDiff::Del => None,ChangedFileDiff::Undel => None,ChangedFileDiff::Add => None,ChangedFileDiff::SolveNameConflict => None,ChangedFileDiff::UnsolveNameConflict => None,ChangedFileDiff::Edit { line, .. } => Some(*line),ChangedFileDiff::Replacement { line, .. } => Some(*line),ChangedFileDiff::SolveOrderConflict => None,ChangedFileDiff::UnsolveOrderConflict => None,ChangedFileDiff::ResurrectZombines => None,ChangedFileDiff::AddRoot => None,ChangedFileDiff::DelRoot => None,}} - edit in crates/inflorescence/src/main.rs at line 1
mod contents_diff; - edit in crates/inflorescence/src/main.rs at line 194
Move, - edit in crates/inflorescence/src/main.rs at line 198
Move, - replacement in crates/inflorescence/src/main.rs at line 888
acc_with.push(ChangedFileDiffWithContents::Move)acc_without.push(ChangedFileDiffWithoutContents::Move) - replacement in crates/inflorescence/src/main.rs at line 1151[13.6545]→[13.6545:6593](∅→∅),[9.5261]→[8.364:392](∅→∅),[13.6593]→[8.364:392](∅→∅),[8.364]→[8.364:392](∅→∅),[8.392]→[10.381:444](∅→∅),[10.444]→[9.5262:5330](∅→∅),[8.447]→[9.5262:5330](∅→∅),[9.5330]→[8.495:515](∅→∅),[8.495]→[8.495:515](∅→∅)
let diffs = [el(text("TODO"))];el(column([view_diff_header(format!("{path} diff:")),el(scrollable(column(diffs).spacing(SPACING))),]))match state.src_files_cache.peek(path) {Some(SrcFile::Loaded(content)) => {let file_content = match content {FileEditorContent::Decoded(content) => {content.text()}FileEditorContent::ShortBase64(_) => todo!(),FileEditorContent::UnknownEncoding => todo!(),};let with_contents = diffs.with_contents.iter().map(|change| match dbg!(change) {ChangedFileDiffWithContents::Add => {contents_diff::ChangedFileDiffWithContents::Add}ChangedFileDiffWithContents::Edit {line,deleted,contents,} => contents_diff::ChangedFileDiffWithContents::Edit {line: *line,deleted: *deleted,contents: match contents {FileEditorContent::Decoded(content) => contents_diff::ChangeContents::Decoded(content.text()) ,FileEditorContent::ShortBase64(_) => todo!(),FileEditorContent::UnknownEncoding => todo!(),},},ChangedFileDiffWithContents::Replacement {line,change_contents,replacement_contents,} => contents_diff::ChangedFileDiffWithContents::Replacement {line:*line ,change_contents: match change_contents {FileEditorContent::Decoded(content) => contents_diff::ChangeContents::Decoded(content.text()) ,FileEditorContent::ShortBase64(_) => todo!(),FileEditorContent::UnknownEncoding => todo!(),},replacement_contents: match replacement_contents {FileEditorContent::Decoded(content) => contents_diff::ChangeContents::Decoded(content.text()) ,FileEditorContent::ShortBase64(_) => todo!(),FileEditorContent::UnknownEncoding => todo!(),}},ChangedFileDiffWithContents::Del => contents_diff::ChangedFileDiffWithContents::Del,ChangedFileDiffWithContents::Undel => contents_diff::ChangedFileDiffWithContents::Undel,}).collect::<Vec<_>>();let state =contents_diff::init(&file_content, &with_contents);let diffs =contents_diff::view(state).map(|msg| todo!());el(column([view_diff_header(format!("{path} diff:")),el(scrollable(diffs)),]))}Some(SrcFile::Loading) => el(text("loading...")),None => todo!(),} - replacement in crates/inflorescence/src/main.rs at line 1265
fn el<'a, E, M>(e: E) -> Element<'a, M>pub fn el<'a, E, M>(e: E) -> Element<'a, M> - file addition: contents_diff.rs[16.120]
//! A changed file's contents diff.use std::cmp;use iced::widget::{column, row, text};use iced::{Element, Font};use crate::el;#[derive(Debug)]pub enum ChangedFileDiffWithContents {Add,Edit {line: usize,deleted: bool,contents: ChangeContents,},Replacement {line: usize,/// Deleted linechange_contents: ChangeContents,/// Added linesreplacement_contents: ChangeContents,},Del,Undel,}#[derive(Debug)]pub enum ChangeContents {Decoded(String),/// Short byte sequence of unknown encoding encoded with base64 for/// display. Must be shorter than [`MAX_LEN_BASE64_DISPLAY`]ShortBase64(String),UnknownEncoding,}#[derive(Debug)]pub struct State {pub sections: Vec<Section>,pub max_line_num: usize,}#[derive(Debug)]pub enum Section {Unchanged(Lines),/// `deleted` and `added` are together because for/// `ChangedFileDiffWithContents::Replacement` they begin on the same line/// numberChanged {deleted: Lines,added: Lines,},}pub fn init(file_contents: &str,changes: &[ChangedFileDiffWithContents],) -> State {let changes_len = changes.len();let mut file_lines = file_contents.split('\n');let mut sections = Vec::with_capacity(changes.len());let mut current_line: usize = 1;let mut max_line_num: usize = 1;for change in changes {match change {ChangedFileDiffWithContents::Add| ChangedFileDiffWithContents::Undel => {debug_assert_eq!(changes_len, 1);let added: Vec<_> = file_lines.map(str::to_string).collect();let max_line_num = added.len();sections.push(Section::Changed {deleted: vec![],added,});return State {sections,max_line_num,};}ChangedFileDiffWithContents::Del => {debug_assert_eq!(changes_len, 1);let deleted: Vec<_> = file_lines.map(str::to_string).collect();let max_line_num = deleted.len();sections.push(Section::Changed {deleted,added: vec![],});return State {sections,max_line_num,};}ChangedFileDiffWithContents::Edit {line,deleted,contents,} => {let lines_before = line - current_line;if lines_before != 0 {current_line = *line;sections.push(Section::Unchanged(take_n_lines(&mut file_lines,lines_before,)));}if *deleted {let deleted = contents_to_lines(contents);max_line_num = current_line + deleted.len();sections.push(Section::Changed {deleted,added: vec![],});} else {let added = contents_to_lines(contents);current_line += added.len();max_line_num = current_line;drop_n_lines(&mut file_lines, added.len());sections.push(Section::Changed {deleted: vec![],added,});}}ChangedFileDiffWithContents::Replacement {line,change_contents,replacement_contents,} => {let lines_before = line - current_line;if lines_before != 0 {current_line = *line;sections.push(Section::Unchanged(take_n_lines(&mut file_lines,lines_before,)));}let added = contents_to_lines(replacement_contents);let deleted = contents_to_lines(change_contents);max_line_num =current_line + cmp::max(added.len(), deleted.len());current_line += added.len();drop_n_lines(&mut file_lines, added.len());sections.push(Section::Changed { deleted, added });}}}let rest: Lines = file_lines.map(str::to_string).collect();if !rest.is_empty() {max_line_num = current_line + rest.len();sections.push(Section::Unchanged(rest));}State {sections,max_line_num,}}fn contents_to_lines(contents: &ChangeContents) -> Lines {match contents {ChangeContents::Decoded(string) => {string.split('\n').map(str::to_string).collect()}ChangeContents::ShortBase64(_) => todo!(),ChangeContents::UnknownEncoding => todo!(),}}fn take_n_lines(file_lines: &mut std::str::Split<'_, char>, n: usize) -> Lines {let mut lines = Vec::with_capacity(n);for _ in 0..n {lines.push(file_lines.next().unwrap().to_string());}lines}fn drop_n_lines(file_lines: &mut std::str::Split<'_, char>, n: usize) {for _ in 0..n {file_lines.next().unwrap();}}/// INVARIANT: There must be no new-lines in any of the strings, the source/// string must be split on those.pub type Lines = Vec<String>;#[derive(Debug)]pub enum Msg {}pub fn update(state: &mut State, msg: Msg) {}pub fn view(state: State) -> Element<'static, Msg> {let mut current_line = 1;let line_num_digits = state.max_line_num.to_string().len();let sections_view = state.sections.iter().flat_map(|section| match section {Section::Unchanged(lines) => {let res = lines.iter().enumerate().map(|(ix, line)| {el(row([el(code(" ")),el(code(format!("{:width$} ",current_line + ix,width = line_num_digits))),el(code(line.clone())),]))}).collect::<Vec<_>>();current_line += lines.len();res}Section::Changed { deleted, added } => {let res = deleted.iter().enumerate().map(|(ix, line)| {el(row([el(code("- ")),el(code(format!("{:width$} ",current_line + ix,width = line_num_digits))),el(code(line.clone())),]))}).chain(added.iter().enumerate().map(|(ix, line)| {el(row([el(code("+ ")),el(code(format!("{:width$} ",current_line + ix,width = line_num_digits))),el(code(line.clone())),]))})).collect::<Vec<_>>();current_line += added.len();res}}).collect::<Vec<_>>();el(column(sections_view))}fn code<'a>(txt: impl text::IntoFragment<'a>) -> iced::widget::Text<'a> {text(txt).font(Font::MONOSPACE)}